diff --git a/src/transforms/bin.js b/src/transforms/bin.js
index 02321a3345..5b31e74bda 100644
--- a/src/transforms/bin.js
+++ b/src/transforms/bin.js
@@ -67,22 +67,22 @@ export function bin(outputs = {fill: "count"}, options = {}) {
return binn(x, y, null, null, outputs, maybeInsetX(maybeInsetY(options)));
}
-function maybeDenseInterval(bin, k, options = {}) {
+function maybeDenseInterval(bin, x, y, options) {
if (options?.interval == null) return options;
const {reduce = reduceFirst} = options;
- const outputs = {filter: null};
- if (options[k] != null) outputs[k] = reduce;
- if (options[`${k}1`] != null) outputs[`${k}1`] = reduce;
- if (options[`${k}2`] != null) outputs[`${k}2`] = reduce;
+ const outputs = {filter: null, [x]: `${x}1`};
+ if (options[y] != null) outputs[y] = reduce;
+ if (options[`${y}1`] != null) outputs[`${y}1`] = reduce;
+ if (options[`${y}2`] != null) outputs[`${y}2`] = reduce;
return bin(outputs, options);
}
export function maybeDenseIntervalX(options = {}) {
- return maybeDenseInterval(binX, "y", withTip(options, "x"));
+ return maybeDenseInterval(binX, "x", "y", withTip(options, "x"));
}
export function maybeDenseIntervalY(options = {}) {
- return maybeDenseInterval(binY, "x", withTip(options, "y"));
+ return maybeDenseInterval(binY, "y", "x", withTip(options, "y"));
}
function binn(
diff --git a/test/output/aaplInterval.svg b/test/output/aaplInterval.svg
index 0a058a34e0..5e76b5099f 100644
--- a/test/output/aaplInterval.svg
+++ b/test/output/aaplInterval.svg
@@ -36,16 +36,16 @@
72
-
-
-
-
+
+
+
+
- Jun2013
- Jul
- Aug
- Sep
+ Jun2013
+ Jul
+ Aug
+ Sep
diff --git a/test/output/availability.svg b/test/output/availability.svg
index 0e552df66c..42fa1b9c8f 100644
--- a/test/output/availability.svg
+++ b/test/output/availability.svg
@@ -33,20 +33,20 @@
↑ value
-
-
-
-
-
-
+
+
+
+
+
+
- Jan2020
- Apr
- Jul
- Oct
- Jan2021
- Apr
+ Jan2020
+ Apr
+ Jul
+ Oct
+ Jan2021
+ Apr
diff --git a/test/output/downloads.svg b/test/output/downloads.svg
index 5d36dcdcf5..a763b3fd9f 100644
--- a/test/output/downloads.svg
+++ b/test/output/downloads.svg
@@ -47,18 +47,18 @@
↑ downloads
-
-
-
-
-
+
+
+
+
+
- 2018
- 2019
- 2020
- 2021
- 2022
+ 2018
+ 2019
+ 2020
+ 2021
+ 2022
diff --git a/test/output/integerIntervalArea.html b/test/output/integerIntervalArea.html
index 762c0649d0..a54660a221 100644
--- a/test/output/integerIntervalArea.html
+++ b/test/output/integerIntervalArea.html
@@ -70,28 +70,30 @@
↑ y
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
x →
diff --git a/test/output/lineInterval.svg b/test/output/lineInterval.svg
new file mode 100644
index 0000000000..02554c8ff6
--- /dev/null
+++ b/test/output/lineInterval.svg
@@ -0,0 +1,127 @@
+
\ No newline at end of file
diff --git a/test/plots/index.ts b/test/plots/index.ts
index 19bc952011..0d9fcaa806 100644
--- a/test/plots/index.ts
+++ b/test/plots/index.ts
@@ -11,8 +11,8 @@ import "./aapl-volume.js";
import "./anscombe-quartet.js";
import "./arc.js";
import "./armadillo.js";
-import "./arrow.js";
import "./arrow-dates.js";
+import "./arrow.js";
import "./aspectRatio.js";
import "./athletes-bins-colors.js";
import "./athletes-birthdays.js";
@@ -115,8 +115,8 @@ import "./google-trends-ridgeline.js";
import "./graticule.js";
import "./greek-gods.js";
import "./grid-choropleth.js";
-import "./grouped-rects.js";
import "./group-markers.js";
+import "./grouped-rects.js";
import "./hadcrut-warming-stripes.js";
import "./heatmap.js";
import "./hexbin-oranges.js";
@@ -154,6 +154,7 @@ import "./letter-frequency-lollipop.js";
import "./letter-frequency-wheel.js";
import "./libor-projections.js";
import "./likert-survey.js";
+import "./line-interval.js";
import "./linear-regression-cars.js";
import "./linear-regression-mtcars.js";
import "./linear-regression-penguins.js";
@@ -234,10 +235,10 @@ import "./population-by-latitude.js";
import "./population-by-longitude.js";
import "./projection-bleed-edges.js";
import "./projection-bleed-edges2.js";
-import "./projection-domain-ratio.js";
import "./projection-clip-angle-frame.js";
import "./projection-clip-angle.js";
import "./projection-clip-berghaus.js";
+import "./projection-domain-ratio.js";
import "./projection-fit-antarctica.js";
import "./projection-fit-bertin1953.js";
import "./projection-fit-conic.js";
diff --git a/test/plots/line-interval.ts b/test/plots/line-interval.ts
new file mode 100644
index 0000000000..56b3f6efc7
--- /dev/null
+++ b/test/plots/line-interval.ts
@@ -0,0 +1,30 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+import {test} from "test/plot";
+
+test(async function lineInterval() {
+ const random = d3.randomLcg(42);
+ const logins = ["Alice", "Bob", "Carol", "Dave", "Eve"];
+ const dates = d3.utcDays(new Date("2024-01-01"), new Date("2024-01-15"));
+ const activity = dates.flatMap((date) => logins.filter(() => random() > 0.15).map((login) => ({date, login})));
+ return Plot.plot({
+ height: 200,
+ marks: [
+ Plot.ruleY([0]),
+ Plot.areaY(activity, {
+ x: "date",
+ interval: "day",
+ fill: "login",
+ order: "-sum",
+ fillOpacity: 0.3,
+ y: 1,
+ curve: "catmull-rom"
+ }),
+ Plot.lineY(
+ activity,
+ Plot.stackY({x: "date", interval: "day", stroke: "login", order: "-sum", curve: "catmull-rom"})
+ ),
+ Plot.dot(activity, Plot.stackY({x: "date", interval: "day", z: "login", fill: "login", order: "-sum"}))
+ ]
+ });
+});