diff --git a/src/transforms/bin.js b/src/transforms/bin.js
index 00d0bc2730..2af0502fa6 100644
--- a/src/transforms/bin.js
+++ b/src/transforms/bin.js
@@ -1,6 +1,6 @@
import {bin as binner, extent, thresholdFreedmanDiaconis, thresholdScott, thresholdSturges, utcTickInterval} from "d3";
import {valueof, range, identity, maybeLazyChannel, maybeTuple, maybeColorChannel, maybeValue, mid, labelof, isTemporal} from "../options.js";
-import {coerceDate} from "../scales.js";
+import {coerceDate, coerceNumber} from "../scales.js";
import {basic} from "./basic.js";
import {hasOutput, maybeEvaluator, maybeGroup, maybeOutput, maybeOutputs, maybeReduce, maybeSort, maybeSubgroup, reduceCount, reduceIdentity} from "./group.js";
import {maybeInsetX, maybeInsetY} from "./inset.js";
@@ -181,7 +181,7 @@ function maybeBin(options) {
if (options == null) return;
const {value, cumulative, domain = extent, thresholds} = options;
const bin = data => {
- let V = valueof(data, value);
+ let V = valueof(data, value, Array); // d3.bin prefers Array input
const bin = binner().value(i => V[i]);
if (isTemporal(V) || isTimeThresholds(thresholds)) {
V = V.map(coerceDate);
@@ -197,6 +197,7 @@ function maybeBin(options) {
}
bin.thresholds(t).domain([min, max]);
} else {
+ V = V.map(coerceNumber);
let d = domain;
let t = thresholds;
if (isInterval(t)) {
diff --git a/test/output/binStrings.svg b/test/output/binStrings.svg
new file mode 100644
index 0000000000..9f753a21b6
--- /dev/null
+++ b/test/output/binStrings.svg
@@ -0,0 +1,105 @@
+
\ No newline at end of file
diff --git a/test/output/binTimestamps.svg b/test/output/binTimestamps.svg
new file mode 100644
index 0000000000..02535494eb
--- /dev/null
+++ b/test/output/binTimestamps.svg
@@ -0,0 +1,86 @@
+
\ No newline at end of file
diff --git a/test/output/stringBins.svg b/test/output/stringBins.svg
new file mode 100644
index 0000000000..9f753a21b6
--- /dev/null
+++ b/test/output/stringBins.svg
@@ -0,0 +1,105 @@
+
\ No newline at end of file
diff --git a/test/plots/bin-strings.js b/test/plots/bin-strings.js
new file mode 100644
index 0000000000..2058fa0903
--- /dev/null
+++ b/test/plots/bin-strings.js
@@ -0,0 +1,5 @@
+import * as Plot from "@observablehq/plot";
+
+export default async function() {
+ return Plot.rectY(["9.6", "9.6", "14.8", "14.8", "7.2"], Plot.binX()).plot();
+}
diff --git a/test/plots/bin-timestamps.js b/test/plots/bin-timestamps.js
new file mode 100644
index 0000000000..21bfed562b
--- /dev/null
+++ b/test/plots/bin-timestamps.js
@@ -0,0 +1,7 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export default async function() {
+ const timestamps = Float64Array.of(1609459200000, 1609545600000, 1609632000000, 1609718400000, 1609804800000, 1609891200000, 1609977600000);
+ return Plot.rectY(timestamps, Plot.binX({y: "count"}, {interval: d3.utcDay})).plot();
+}
diff --git a/test/plots/index.js b/test/plots/index.js
index b9cf61dd18..c748254805 100644
--- a/test/plots/index.js
+++ b/test/plots/index.js
@@ -23,6 +23,8 @@ export {default as athletesWeightCumulative} from "./athletes-weight-cumulative.
export {default as availability} from "./availability.js";
export {default as ballotStatusRace} from "./ballot-status-race.js";
export {default as beckerBarley} from "./becker-barley.js";
+export {default as binStrings} from "./bin-strings.js";
+export {default as binTimestamps} from "./bin-timestamps.js";
export {default as boxplot} from "./boxplot.js";
export {default as caltrain} from "./caltrain.js";
export {default as caltrainDirection} from "./caltrain-direction.js";