From 5e50056e9be51e3e5622fbf264f5994e953be537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Sat, 26 Aug 2023 12:57:36 +0200 Subject: [PATCH 1/2] Empty bins fall back on the first element of their group for z, stroke and fill closes #1775 --- src/transforms/bin.js | 7 +- test/output/integerIntervalArea.html | 105 ++++++++++++++++++++++++++ test/output/integerIntervalAreaZ.html | 100 ++++++++++++++++++++++++ test/plots/integer-interval.ts | 52 +++++++++++++ 4 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 test/output/integerIntervalArea.html create mode 100644 test/output/integerIntervalAreaZ.html diff --git a/src/transforms/bin.js b/src/transforms/bin.js index ea84b234ba..e7dcb9aa7e 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -184,9 +184,10 @@ function binn( groupFacet.push(i++); groupData.push(reduceData.reduceIndex(b, data, extent)); if (K) GK.push(k); - if (Z) GZ.push(G === Z ? f : Z[b[0]]); - if (F) GF.push(G === F ? f : F[b[0]]); - if (S) GS.push(G === S ? f : S[b[0]]); + const u = b.length === 0 ? g[0] : b[0]; + if (Z) GZ.push(G === Z ? f : Z[u]); + if (F) GF.push(G === F ? f : F[u]); + if (S) GS.push(G === S ? f : S[u]); if (BX1) BX1.push(extent.x1), BX2.push(extent.x2); if (BY1) BY1.push(extent.y1), BY2.push(extent.y2); for (const o of outputs) o.reduce(b, extent); diff --git a/test/output/integerIntervalArea.html b/test/output/integerIntervalArea.html new file mode 100644 index 0000000000..45b07a8896 --- /dev/null +++ b/test/output/integerIntervalArea.html @@ -0,0 +1,105 @@ +
+
+ + + a + + b +
+ + + + + + + + + + + + + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + + + ↑ y + + + + + + + + + + + + + + + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + + + x → + + + + + + + +
\ No newline at end of file diff --git a/test/output/integerIntervalAreaZ.html b/test/output/integerIntervalAreaZ.html new file mode 100644 index 0000000000..773955f37c --- /dev/null +++ b/test/output/integerIntervalAreaZ.html @@ -0,0 +1,100 @@ +
+
+ + + P + + R + + a + + b + + c +
+ + + + + + + + + + + + + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + + + ↑ y + + + + + + + + + 0.0 + 1.0 + 2.0 + 3.0 + + + x → + + + + + + + + +
\ No newline at end of file diff --git a/test/plots/integer-interval.ts b/test/plots/integer-interval.ts index 76de1c5c99..2e89b65020 100644 --- a/test/plots/integer-interval.ts +++ b/test/plots/integer-interval.ts @@ -13,3 +13,55 @@ export async function integerInterval() { marks: [Plot.line(requests)] }); } + +export async function integerIntervalArea() { + const series = [ + {x: 0, y: 5, type: "a"}, + {x: 5, y: 7, type: "a"}, + {x: 5, y: 9, type: "b"}, + {x: 10, y: 4, type: "b"} + ]; + return Plot.plot({ + color: {legend: true}, + marks: [ + Plot.areaY(series, { + interval: 5, + x: "x", + y: "y", + fill: "type", + stroke: "type", + strokeWidth: 2, + fillOpacity: 0.7, + tip: true + }) + ] + }); +} + +export async function integerIntervalAreaZ() { + const series = [ + {x: 0, y: 5, type: "a", category: "P"}, + {x: 1, y: 7, type: "a", category: "P"}, + {x: 1, y: 9, type: "b", category: "P"}, + {x: 2, y: 4, type: "b", category: "P"}, + {x: 1, y: 1, type: "c", category: "R"}, + {x: 3, y: 7, type: "c", category: "R"} + ]; + return Plot.plot({ + x: {interval: 1}, + color: {scheme: "Paired", legend: true}, + marks: [ + Plot.areaY(series, { + x: "x", + y: "y", + interval: 1, + fill: "type", + stroke: "category", + z: "type", + strokeWidth: 2, + fillOpacity: 0.7, + tip: true + }) + ] + }); +} From 339d1b00e35d399ea32041236de0b4e3085a7fd4 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 26 Aug 2023 09:22:49 -0700 Subject: [PATCH 2/2] test edge case --- src/transforms/bin.js | 7 +- test/output/binFillFirstEmpty.html | 183 +++++++++++++++++++++++++++++ test/plots/bin-fill-first-empty.ts | 23 ++++ test/plots/index.ts | 1 + 4 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 test/output/binFillFirstEmpty.html create mode 100644 test/plots/bin-fill-first-empty.ts diff --git a/src/transforms/bin.js b/src/transforms/bin.js index e7dcb9aa7e..a63358fa56 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -184,10 +184,9 @@ function binn( groupFacet.push(i++); groupData.push(reduceData.reduceIndex(b, data, extent)); if (K) GK.push(k); - const u = b.length === 0 ? g[0] : b[0]; - if (Z) GZ.push(G === Z ? f : Z[u]); - if (F) GF.push(G === F ? f : F[u]); - if (S) GS.push(G === S ? f : S[u]); + if (Z) GZ.push(G === Z ? f : Z[(b.length > 0 ? b : g)[0]]); + if (F) GF.push(G === F ? f : F[(b.length > 0 ? b : g)[0]]); + if (S) GS.push(G === S ? f : S[(b.length > 0 ? b : g)[0]]); if (BX1) BX1.push(extent.x1), BX2.push(extent.x2); if (BY1) BY1.push(extent.y1), BY2.push(extent.y2); for (const o of outputs) o.reduce(b, extent); diff --git a/test/output/binFillFirstEmpty.html b/test/output/binFillFirstEmpty.html new file mode 100644 index 0000000000..754c41ec5a --- /dev/null +++ b/test/output/binFillFirstEmpty.html @@ -0,0 +1,183 @@ +
+
+ + + FEMALE + + MALE + + null +
+ + + + + + + + + + + + + + + + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + ↑ Frequency + + + + + + + + + + + + 3,000 + 3,500 + 4,000 + 4,500 + 5,000 + 5,500 + 6,000 + + + body_mass_g → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/test/plots/bin-fill-first-empty.ts b/test/plots/bin-fill-first-empty.ts new file mode 100644 index 0000000000..43e6587bb1 --- /dev/null +++ b/test/plots/bin-fill-first-empty.ts @@ -0,0 +1,23 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export async function binFillFirstEmpty() { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.rectY( + penguins, + Plot.binX( + { + y: "count", + filter: null // retain empty bins + }, + { + x: "body_mass_g", + z: null, // don’t group and stack + fill: "sex", // use the first sex value to color each bin + interval: 50, // force empty bins + insetTop: -0.5, // make empty bins visible + insetBottom: -0.5 // make empty bins visible + } + ) + ).plot({color: {legend: true}}); +} diff --git a/test/plots/index.ts b/test/plots/index.ts index 9fc03ef56f..35ded36808 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -37,6 +37,7 @@ export * from "./beagle.js"; export * from "./becker-barley.js"; export * from "./bigint.js"; export * from "./bin-1m.js"; +export * from "./bin-fill-first-empty.js"; export * from "./bin-strings.js"; export * from "./bin-timestamps.js"; export * from "./bounding-boxes.js";