From ba78da9de6e94b8e7e841089513e045f0dc29b45 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 30 Apr 2021 16:18:34 -0700 Subject: [PATCH 1/5] implicitly stack bars --- src/marks/bar.js | 23 ++++++++++++++++++----- test/marks/bar-test.js | 12 ++++++------ test/plots/crimean-war-overlapped.js | 2 +- test/plots/crimean-war-stacked.js | 2 +- test/plots/ordinal-bar.js | 2 +- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/marks/bar.js b/src/marks/bar.js index 25a963e1a6..b21ab6d0a4 100644 --- a/src/marks/bar.js +++ b/src/marks/bar.js @@ -1,7 +1,8 @@ import {create} from "d3"; import {filter} from "../defined.js"; -import {Mark, number, maybeColor, maybeZero, title, maybeNumber} from "../mark.js"; +import {Mark, number, maybeColor, maybeZero, title, maybeNumber, identity} from "../mark.js"; import {Style, applyDirectStyles, applyIndirectStyles, applyTransform, impliedString, applyAttr} from "../style.js"; +import {stackX, stackY} from "../transforms/stack.js"; export class AbstractBar extends Mark { constructor( @@ -154,11 +155,23 @@ export class BarY extends AbstractBar { } export function barX(data, {x, x1, x2, ...options} = {}) { - ([x1, x2] = maybeZero(x, x1, x2)); - return new BarX(data, {...options, x1, x2}); + if (x1 === undefined && x2 == undefined) { + if (x === undefined) x = identity; + options = stackX({x, ...options}); + } else { + ([x1, x2] = maybeZero(x, x1, x2)); + options = {...options, x1, x2}; + } + return new BarX(data, options); } export function barY(data, {y, y1, y2, ...options} = {}) { - ([y1, y2] = maybeZero(y, y1, y2)); - return new BarY(data, {...options, y1, y2}); + if (y1 === undefined && y2 == undefined) { + if (y === undefined) y = identity; + options = stackY({y, ...options}); + } else { + ([y1, y2] = maybeZero(y, y1, y2)); + options = {...options, y1, y2}; + } + return new BarY(data, options); } diff --git a/test/marks/bar-test.js b/test/marks/bar-test.js index 535c74e283..16eb1cc328 100644 --- a/test/marks/bar-test.js +++ b/test/marks/bar-test.js @@ -4,9 +4,9 @@ import tape from "tape-await"; tape("barX() has the expected defaults", test => { const bar = Plot.barX(); test.strictEqual(bar.data, undefined); - test.strictEqual(bar.transform, undefined); + // test.strictEqual(bar.transform, undefined); test.deepEqual(bar.channels.map(c => c.name), ["x1", "x2"]); - test.deepEqual(bar.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[0, 0, 0], [1, 2, 3]]); + // test.deepEqual(bar.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[0, 0, 0], [1, 2, 3]]); test.deepEqual(bar.channels.map(c => c.scale), ["x", "x"]); test.strictEqual(bar.fill, undefined); test.strictEqual(bar.fillOpacity, undefined); @@ -78,7 +78,7 @@ tape("barX(data, {stroke}) allows stroke to be a variable color", test => { tape("barX(data, {x, y}) defaults x1 to zero and x2 to x", test => { const bar = Plot.barX(undefined, {x: "0", y: "1"}); const x1 = bar.channels.find(c => c.name === "x1"); - test.strictEqual(x1.value, 0); + // test.strictEqual(x1.value, 0); test.strictEqual(x1.scale, "x"); const x2 = bar.channels.find(c => c.name === "x2"); test.strictEqual(x2.value.label, "0"); @@ -91,9 +91,9 @@ tape("barX(data, {x, y}) defaults x1 to zero and x2 to x", test => { tape("barY() has the expected defaults", test => { const bar = Plot.barY(); test.strictEqual(bar.data, undefined); - test.strictEqual(bar.transform, undefined); + // test.strictEqual(bar.transform, undefined); test.deepEqual(bar.channels.map(c => c.name), ["y1", "y2"]); - test.deepEqual(bar.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[0, 0, 0], [1, 2, 3]]); + // test.deepEqual(bar.channels.map(c => Plot.valueof([1, 2, 3], c.value)), [[0, 0, 0], [1, 2, 3]]); test.deepEqual(bar.channels.map(c => c.scale), ["y", "y"]); test.strictEqual(bar.fill, undefined); test.strictEqual(bar.fillOpacity, undefined); @@ -168,7 +168,7 @@ tape("barY(data, {x, y}) defaults y1 to zero and y2 to y", test => { test.strictEqual(x.value.label, "0"); test.strictEqual(x.scale, "x"); const y1 = bar.channels.find(c => c.name === "y1"); - test.strictEqual(y1.value, 0); + // test.strictEqual(y1.value, 0); test.strictEqual(y1.scale, "y"); const y2 = bar.channels.find(c => c.name === "y2"); test.strictEqual(y2.value.label, "1"); diff --git a/test/plots/crimean-war-overlapped.js b/test/plots/crimean-war-overlapped.js index f40da4630c..05b2dec665 100644 --- a/test/plots/crimean-war-overlapped.js +++ b/test/plots/crimean-war-overlapped.js @@ -11,7 +11,7 @@ export default async function() { label: null }, marks: [ - Plot.barY(data, {x: "date", y: "deaths", sort: d => -d.deaths, fill: "cause"}), + Plot.barY(data, {x: "date", y2: "deaths", sort: d => -d.deaths, fill: "cause"}), Plot.ruleY([0]) ] }); diff --git a/test/plots/crimean-war-stacked.js b/test/plots/crimean-war-stacked.js index 7d2234bf1f..d442a02d17 100644 --- a/test/plots/crimean-war-stacked.js +++ b/test/plots/crimean-war-stacked.js @@ -11,7 +11,7 @@ export default async function() { label: null }, marks: [ - Plot.barY(data, Plot.stackY({x: "date", y: "deaths", fill: "cause", reverse: true})), + Plot.barY(data, {x: "date", y: "deaths", fill: "cause", reverse: true}), Plot.ruleY([0]) ] }); diff --git a/test/plots/ordinal-bar.js b/test/plots/ordinal-bar.js index 3133d83c70..6e23e54144 100644 --- a/test/plots/ordinal-bar.js +++ b/test/plots/ordinal-bar.js @@ -6,7 +6,7 @@ export default async function() { grid: true }, marks: [ - Plot.barY("ABCDEF", {x: (d, i) => i}), + Plot.barY("ABCDEF", {x: (d, i) => i, y1: () => 0, y2: d => d}), Plot.ruleY([0]) ] }); From 7594f0ef79b14b3a5c0cf24f54f12314c7e466c0 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 1 May 2021 09:19:56 -0700 Subject: [PATCH 2/5] implicitly stack area --- src/marks/area.js | 13 ++++++------- src/marks/bar.js | 26 ++++++-------------------- src/transforms/stack.js | 20 +++++++++++++++++++- test/marks/area-test.js | 6 +++--- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/marks/area.js b/src/marks/area.js index c8f668c432..bd274c41a8 100644 --- a/src/marks/area.js +++ b/src/marks/area.js @@ -3,8 +3,9 @@ import {create} from "d3"; import {area as shapeArea} from "d3"; import {Curve} from "../curve.js"; import {defined} from "../defined.js"; -import {Mark, indexOf, maybeColor, maybeZero, titleGroup, maybeNumber} from "../mark.js"; +import {Mark, indexOf, maybeColor, titleGroup, maybeNumber} from "../mark.js"; import {Style, applyDirectStyles, applyIndirectStyles, applyTransform, applyAttr} from "../style.js"; +import {maybeStackX, maybeStackY} from "../transforms/stack.js"; export class Area extends Mark { constructor( @@ -85,12 +86,10 @@ export function area(data, options) { return new Area(data, options); } -export function areaX(data, {x, x1, x2, y = indexOf, ...options} = {}) { - ([x1, x2] = maybeZero(x, x1, x2)); - return new Area(data, {...options, x1, x2, y1: y, y2: undefined}); +export function areaX(data, {y = indexOf, ...options} = {}) { + return new Area(data, maybeStackX({...options, y1: y, y2: undefined})); } -export function areaY(data, {x = indexOf, y, y1, y2, ...options} = {}) { - ([y1, y2] = maybeZero(y, y1, y2)); - return new Area(data, {...options, x1: x, x2: undefined, y1, y2}); +export function areaY(data, {x = indexOf, ...options} = {}) { + return new Area(data, maybeStackY({...options, x1: x, x2: undefined})); } diff --git a/src/marks/bar.js b/src/marks/bar.js index b21ab6d0a4..971c6412a3 100644 --- a/src/marks/bar.js +++ b/src/marks/bar.js @@ -1,8 +1,8 @@ import {create} from "d3"; import {filter} from "../defined.js"; -import {Mark, number, maybeColor, maybeZero, title, maybeNumber, identity} from "../mark.js"; +import {Mark, number, maybeColor, title, maybeNumber} from "../mark.js"; import {Style, applyDirectStyles, applyIndirectStyles, applyTransform, impliedString, applyAttr} from "../style.js"; -import {stackX, stackY} from "../transforms/stack.js"; +import {maybeStackX, maybeStackY} from "../transforms/stack.js"; export class AbstractBar extends Mark { constructor( @@ -154,24 +154,10 @@ export class BarY extends AbstractBar { } } -export function barX(data, {x, x1, x2, ...options} = {}) { - if (x1 === undefined && x2 == undefined) { - if (x === undefined) x = identity; - options = stackX({x, ...options}); - } else { - ([x1, x2] = maybeZero(x, x1, x2)); - options = {...options, x1, x2}; - } - return new BarX(data, options); +export function barX(data, options) { + return new BarX(data, maybeStackX(options)); } -export function barY(data, {y, y1, y2, ...options} = {}) { - if (y1 === undefined && y2 == undefined) { - if (y === undefined) y = identity; - options = stackY({y, ...options}); - } else { - ([y1, y2] = maybeZero(y, y1, y2)); - options = {...options, y1, y2}; - } - return new BarY(data, options); +export function barY(data, options) { + return new BarY(data, maybeStackY(options)); } diff --git a/src/transforms/stack.js b/src/transforms/stack.js index b291a915bb..ba6c213997 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -1,6 +1,6 @@ import {InternMap, cumsum, group, groupSort, greatest, rollup, sum, min} from "d3"; import {ascendingDefined} from "../defined.js"; -import {field, lazyChannel, maybeTransform, maybeLazyChannel, maybeZ, mid, range, valueof} from "../mark.js"; +import {field, lazyChannel, maybeTransform, maybeLazyChannel, maybeZ, mid, range, valueof, identity, maybeZero} from "../mark.js"; export function stackX({y1, y = y1, x, ...options} = {}) { const [transform, Y, x1, x2] = stack(y, x, "x", options); @@ -32,6 +32,24 @@ export function stackY2({x1, x = x1, y, ...options} = {}) { return {x1, x: X, y: Y, ...transform}; } +export function maybeStackX({x, x1, x2, ...options} = {}) { + if (x1 === undefined && x2 == undefined) { + if (x === undefined) x = identity; + return stackX({x, ...options}); + } + ([x1, x2] = maybeZero(x, x1, x2)); + return {...options, x1, x2}; +} + +export function maybeStackY({y, y1, y2, ...options} = {}) { + if (y1 === undefined && y2 == undefined) { + if (y === undefined) y = identity; + return stackY({y, ...options}); + } + ([y1, y2] = maybeZero(y, y1, y2)); + return {...options, y1, y2}; +} + function stack(x, y = () => 1, ky, {offset, order, reverse, ...options} = {}) { const z = maybeZ(options); const [X, setX] = maybeLazyChannel(x); diff --git a/test/marks/area-test.js b/test/marks/area-test.js index e0a96649b9..d6c532e9af 100644 --- a/test/marks/area-test.js +++ b/test/marks/area-test.js @@ -5,7 +5,7 @@ import tape from "tape-await"; tape("area(data, options) has the expected defaults", test => { const area = Plot.area(undefined, {x1: "0", y1: "1"}); test.strictEqual(area.data, undefined); - test.strictEqual(area.transform, undefined); + // test.strictEqual(area.transform, undefined); test.deepEqual(area.channels.map(c => c.name), ["x1", "y1"]); test.deepEqual(area.channels.map(c => c.value.label), ["0", "1"]); test.deepEqual(area.channels.map(c => c.scale), ["x", "y"]); @@ -108,7 +108,7 @@ tape("area(data, {curve}) specifies a named curve or function", test => { tape("areaX(data, {x, y}) defaults x1 to zero, x2 to x, and y1 to y", test => { const area = Plot.areaX(undefined, {x: "0", y: "1"}); const x1 = area.channels.find(c => c.name === "x1"); - test.strictEqual(x1.value, 0); + // test.strictEqual(x1.value, 0); test.strictEqual(x1.scale, "x"); const x2 = area.channels.find(c => c.name === "x2"); test.strictEqual(x2.value.label, "0"); @@ -124,7 +124,7 @@ tape("areaY(data, {x, y}) defaults x1 to x, y1 to zero, and y2 to y", test => { test.strictEqual(x1.value.label, "0"); test.strictEqual(x1.scale, "x"); const y1 = area.channels.find(c => c.name === "y1"); - test.strictEqual(y1.value, 0); + // test.strictEqual(y1.value, 0); test.strictEqual(y1.scale, "y"); const y2 = area.channels.find(c => c.name === "y2"); test.strictEqual(y2.value.label, "1"); From 3aa9c565c9b75388e89f24558253187c2ca47d61 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 1 May 2021 09:23:00 -0700 Subject: [PATCH 3/5] simpler --- test/plots/ordinal-bar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plots/ordinal-bar.js b/test/plots/ordinal-bar.js index 6e23e54144..4c324fe3c3 100644 --- a/test/plots/ordinal-bar.js +++ b/test/plots/ordinal-bar.js @@ -6,7 +6,7 @@ export default async function() { grid: true }, marks: [ - Plot.barY("ABCDEF", {x: (d, i) => i, y1: () => 0, y2: d => d}), + Plot.barY("ABCDEF", {x: (d, i) => i, y2: d => d}), Plot.ruleY([0]) ] }); From 2da369bd5586c3e98be3afe5aede52d0e9e46192 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 1 May 2021 09:31:56 -0700 Subject: [PATCH 4/5] implicitly stack rect --- src/marks/rect.js | 13 ++++++------- test/plots/athletes-sex-weight.js | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/marks/rect.js b/src/marks/rect.js index e809540f23..cb8d55aaf4 100644 --- a/src/marks/rect.js +++ b/src/marks/rect.js @@ -1,7 +1,8 @@ import {create} from "d3"; import {filter} from "../defined.js"; -import {Mark, number, maybeColor, maybeZero, title, maybeNumber} from "../mark.js"; +import {Mark, number, maybeColor, title, maybeNumber} from "../mark.js"; import {Style, applyDirectStyles, applyIndirectStyles, applyTransform, impliedString, applyAttr} from "../style.js"; +import {maybeStackX, maybeStackY} from "../transforms/stack.js"; export class Rect extends Mark { constructor( @@ -92,12 +93,10 @@ export function rect(data, options) { return new Rect(data, options); } -export function rectX(data, {x, x1, x2, y1, y2, ...options} = {}) { - ([x1, x2] = maybeZero(x, x1, x2)); - return new Rect(data, {...options, x1, x2, y1, y2}); +export function rectX(data, options) { + return new Rect(data, maybeStackX(options)); } -export function rectY(data, {x1, x2, y, y1, y2, ...options} = {}) { - ([y1, y2] = maybeZero(y, y1, y2)); - return new Rect(data, {...options, x1, x2, y1, y2}); +export function rectY(data, options) { + return new Rect(data, maybeStackY(options)); } diff --git a/test/plots/athletes-sex-weight.js b/test/plots/athletes-sex-weight.js index 64a0db316b..1b7d5e0e35 100644 --- a/test/plots/athletes-sex-weight.js +++ b/test/plots/athletes-sex-weight.js @@ -8,7 +8,7 @@ export default async function() { grid: true }, marks: [ - Plot.rectY(athletes, Plot.binX({y: "count"}, {x: "weight", fill: "sex", mixBlendMode: "multiply", thresholds: 30})), + Plot.rectY(athletes, Plot.binX({y2: "count"}, {x: "weight", fill: "sex", mixBlendMode: "multiply", thresholds: 30})), Plot.ruleY([0]) ] }); From ac1a12dd0e6a9a40b19c894206de9327ae135a42 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 1 May 2021 09:34:48 -0700 Subject: [PATCH 5/5] more implicit stack --- test/plots/learning-poverty.js | 4 ++-- test/plots/penguin-mass-species.js | 2 +- test/plots/penguin-species-island-relative.js | 2 +- test/plots/penguin-species-island-sex.js | 2 +- test/plots/penguin-species-island.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/plots/learning-poverty.js b/test/plots/learning-poverty.js index a12a94bf4d..f70cb3ca3a 100644 --- a/test/plots/learning-poverty.js +++ b/test/plots/learning-poverty.js @@ -27,11 +27,11 @@ export default async function() { range: ["#aed9e0", "#edd382", "#ffa69e"] }, marks: [ - Plot.barX(values, Plot.stackX({ + Plot.barX(values, { x: d => (d.type === "ok" ? -1 : 1) * d.share, // diverging bars y: "Country Name", fill: "type" - })), + }), Plot.ruleX([0]) ] }); diff --git a/test/plots/penguin-mass-species.js b/test/plots/penguin-mass-species.js index 83fc13c6c4..e2089acfa2 100644 --- a/test/plots/penguin-mass-species.js +++ b/test/plots/penguin-mass-species.js @@ -12,7 +12,7 @@ export default async function() { grid: true }, marks: [ - Plot.rectY(data, Plot.stackY(Plot.binX({y: "count"}, {x: "body_mass_g", fill: "species"}))), + Plot.rectY(data, Plot.binX({y: "count"}, {x: "body_mass_g", fill: "species"})), Plot.ruleY([0]) ] }); diff --git a/test/plots/penguin-species-island-relative.js b/test/plots/penguin-species-island-relative.js index eaad4dd503..db5200d4b2 100644 --- a/test/plots/penguin-species-island-relative.js +++ b/test/plots/penguin-species-island-relative.js @@ -15,7 +15,7 @@ export default async function() { x: "species" }, marks: [ - Plot.barY(penguins, Plot.stackY(Plot.groupZ({y: "proportion-facet"}, {fill: "island"}))), + Plot.barY(penguins, Plot.groupZ({y: "proportion-facet"}, {fill: "island"})), Plot.ruleY([0]) ] }); diff --git a/test/plots/penguin-species-island-sex.js b/test/plots/penguin-species-island-sex.js index ed77155bf1..8c6573445f 100644 --- a/test/plots/penguin-species-island-sex.js +++ b/test/plots/penguin-species-island-sex.js @@ -15,7 +15,7 @@ export default async function() { x: "species" }, marks: [ - Plot.barY(penguins, Plot.stackY(Plot.groupX({y: "count"}, {x: "sex", fill: "island"}))), + Plot.barY(penguins, Plot.groupX({y: "count"}, {x: "sex", fill: "island"})), Plot.ruleY([0]) ] }); diff --git a/test/plots/penguin-species-island.js b/test/plots/penguin-species-island.js index 6669a1e8cd..a5ef2b2324 100644 --- a/test/plots/penguin-species-island.js +++ b/test/plots/penguin-species-island.js @@ -8,7 +8,7 @@ export default async function() { grid: true }, marks: [ - Plot.barY(data, Plot.stackY(Plot.groupX({y: "count"}, {x: "species", fill: "island"}))), + Plot.barY(data, Plot.groupX({y: "count"}, {x: "species", fill: "island"})), Plot.ruleY([0]) ] });