From bb8acbfbc0329d1c326b906dccb7d7027711ffca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 1 Jun 2023 11:55:54 +0200 Subject: [PATCH 1/4] Axes and grids now support the filter option Add an explicit error message if using the sort and reverse option closes #1457 closes #1655 --- src/marks/axis.js | 97 ++++----- test/output/axesFilter.svg | 45 +++++ test/output/usStatePopulationDiverging.svg | 220 +++++++++++++++++++++ test/plots/axis-labels.ts | 17 ++ test/plots/us-state-population-change.ts | 32 +++ 5 files changed, 365 insertions(+), 46 deletions(-) create mode 100644 test/output/axesFilter.svg create mode 100644 test/output/usStatePopulationDiverging.svg diff --git a/src/marks/axis.js b/src/marks/axis.js index 8b96182686..28ae4826ed 100644 --- a/src/marks/axis.js +++ b/src/marks/axis.js @@ -8,7 +8,7 @@ import {maybeColorChannel, maybeNumberChannel, maybeRangeInterval} from "../opti import {isTemporalScale} from "../scales.js"; import {offset} from "../style.js"; import {formatTimeTicks, isTimeYear, isUtcYear} from "../time.js"; -import {initializer} from "../transforms/basic.js"; +import {initializer, filter} from "../transforms/basic.js"; import {ruleX, ruleY} from "./rule.js"; import {text, textX, textY} from "./text.js"; import {vectorX, vectorY} from "./vector.js"; @@ -505,56 +505,61 @@ function labelOptions( }; } -function axisMark(mark, k, ariaLabel, data, options, initialize) { +function axisMark(mark, k, ariaLabel, data, {filter: f, sort: s, reverse: r, ...options}, initialize) { let channels; - const m = mark( - data, - initializer(options, function (data, facets, _channels, scales, dimensions, context) { - const initializeFacets = data == null && (k === "fx" || k === "fy"); - const {[k]: scale} = scales; - if (!scale) throw new Error(`missing scale: ${k}`); - let {ticks, tickSpacing, interval} = options; - if (isTemporalScale(scale) && typeof ticks === "string") (interval = ticks), (ticks = undefined); - if (data == null) { - if (isIterable(ticks)) { - data = arrayify(ticks); - } else if (scale.ticks) { - if (ticks !== undefined) { - data = scale.ticks(ticks); + + let i = initializer(options, function (data, facets, _channels, scales, dimensions, context) { + const initializeFacets = data == null && (k === "fx" || k === "fy"); + const {[k]: scale} = scales; + if (!scale) throw new Error(`missing scale: ${k}`); + let {ticks, tickSpacing, interval} = options; + if (isTemporalScale(scale) && typeof ticks === "string") (interval = ticks), (ticks = undefined); + if (data == null) { + if (isIterable(ticks)) { + data = arrayify(ticks); + } else if (scale.ticks) { + if (ticks !== undefined) { + data = scale.ticks(ticks); + } else { + interval = maybeRangeInterval(interval === undefined ? scale.interval : interval, scale.type); + if (interval !== undefined) { + // For time scales, we could pass the interval directly to + // scale.ticks because it’s supported by d3.utcTicks; but + // quantitative scales and d3.ticks do not support numeric + // intervals for scale.ticks, so we compute them here. + const [min, max] = extent(scale.domain()); + data = interval.range(min, interval.offset(interval.floor(max))); // inclusive max } else { - interval = maybeRangeInterval(interval === undefined ? scale.interval : interval, scale.type); - if (interval !== undefined) { - // For time scales, we could pass the interval directly to - // scale.ticks because it’s supported by d3.utcTicks; but - // quantitative scales and d3.ticks do not support numeric - // intervals for scale.ticks, so we compute them here. - const [min, max] = extent(scale.domain()); - data = interval.range(min, interval.offset(interval.floor(max))); // inclusive max - } else { - const [min, max] = extent(scale.range()); - ticks = (max - min) / (tickSpacing === undefined ? (k === "x" ? 80 : 35) : tickSpacing); - data = scale.ticks(ticks); - } + const [min, max] = extent(scale.range()); + ticks = (max - min) / (tickSpacing === undefined ? (k === "x" ? 80 : 35) : tickSpacing); + data = scale.ticks(ticks); } - } else { - data = scale.domain(); - } - if (k === "y" || k === "x") { - facets = [range(data)]; - } else { - channels[k] = {scale: k, value: identity}; } + } else { + data = scale.domain(); } - initialize?.call(this, scale, ticks, channels); - const initializedChannels = Object.fromEntries( - Object.entries(channels).map(([name, channel]) => { - return [name, {...channel, value: valueof(data, channel.value)}]; - }) - ); - if (initializeFacets) facets = context.filterFacets(data, initializedChannels); - return {data, facets, channels: initializedChannels}; - }) - ); + if (k === "y" || k === "x") { + facets = [range(data)]; + } else { + channels[k] = {scale: k, value: identity}; + } + } + initialize?.call(this, scale, ticks, channels); + const initializedChannels = Object.fromEntries( + Object.entries(channels).map(([name, channel]) => { + return [name, {...channel, value: valueof(data, channel.value)}]; + }) + ); + if (initializeFacets) facets = context.filterFacets(data, initializedChannels); + return {data, facets, channels: initializedChannels}; + }); + + // Apply the filter option after the initializer has determined the mark’s data. + if (f != null) i = filter(f, i); + if (s != null) throw new Error("Unsupported axis option: sort"); + if (r != null) throw new Error("Unsupported axis option: reverse"); + + const m = mark(data, i); if (data == null) { channels = m.channels; m.channels = {}; diff --git a/test/output/axesFilter.svg b/test/output/axesFilter.svg new file mode 100644 index 0000000000..8218746e64 --- /dev/null +++ b/test/output/axesFilter.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + A + B + + + + + + + 1 + 2 + + \ No newline at end of file diff --git a/test/output/usStatePopulationDiverging.svg b/test/output/usStatePopulationDiverging.svg new file mode 100644 index 0000000000..aea7ce7adc --- /dev/null +++ b/test/output/usStatePopulationDiverging.svg @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + −10 + −5 + +0 + +5 + +10 + +15 + + + ← decrease · Change in population, 2010–2019 (%) · increase → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mississippi + New York + Rhode Island + Pennsylvania + New Jersey + Michigan + Maine + Ohio + New Mexico + Kansas + Wisconsin + Missouri + Louisiana + Alabama + Wyoming + Kentucky + Alaska + New Hampshire + Arkansas + Iowa + Indiana + Hawaii + Maryland + Oklahoma + Nebraska + California + Massachusetts + Minnesota + Virginia + Tennessee + Montana + Delaware + South Dakota + Georgia + North Carolina + Oregon + South Carolina + Washington + North Dakota + Arizona + Idaho + Nevada + Florida + Colorado + Texas + Utah + District of Columbia + + + + + + + + + + Puerto Rico + West Virginia + Illinois + Vermont + Connecticut + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/axis-labels.ts b/test/plots/axis-labels.ts index fac2f941d6..6aeaa3b504 100644 --- a/test/plots/axis-labels.ts +++ b/test/plots/axis-labels.ts @@ -65,3 +65,20 @@ export async function axisLabelHref() { marks: [Plot.axisX({label: "Letter", href: (d) => `https://en.wikipedia.org/wiki/${d}`})] }); } + +export async function axesFilter() { + return Plot.plot({ + height: 100, + marks: [ + Plot.dot([ + ["A", 0], + ["B", 2], + [0, 1] + ]), + Plot.gridX({filter: (d) => d}), + Plot.gridY({filter: (d) => d}), + Plot.axisX({filter: (d) => d}), + Plot.axisY({filter: (d) => d}) + ] + }); +} diff --git a/test/plots/us-state-population-change.ts b/test/plots/us-state-population-change.ts index dad80d4c0d..0fe056936b 100644 --- a/test/plots/us-state-population-change.ts +++ b/test/plots/us-state-population-change.ts @@ -33,3 +33,35 @@ export async function usStatePopulationChange() { ] }); } + +export async function usStatePopulationDiverging() { + const statepop = await d3.csv("data/us-state-population-2010-2019.csv", d3.autoType); + const change = new Map(statepop.map((d) => [d.State, (d[2019] - d[2010]) / d[2010]])); + return Plot.plot({ + label: null, + x: { + axis: "top", + grid: true, + label: "← decrease · Change in population, 2010–2019 (%) · increase →", + labelAnchor: "center", + tickFormat: "+", + percent: true + }, + color: { + scheme: "PiYG", + type: "ordinal" + }, + marks: [ + Plot.barX(statepop, { + y: "State", + x: (d) => change.get(d.State), + fill: (d) => Math.sign(change.get(d.State)), + sort: {y: "x"} + }), + Plot.axisY({x: 0, filter: (d) => change.get(d) >= 0}), + Plot.axisY({x: 0, filter: (d) => change.get(d) < 0, anchor: "right"}), + Plot.gridX({stroke: "white", strokeOpacity: 0.5}), + Plot.ruleX([0]) + ] + }); +} From 15634d68225934e7f9d116acf8e1e9447399031e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 1 Jun 2023 22:42:08 +0200 Subject: [PATCH 2/4] modernize traffic-horizon with an axis filter --- test/output/trafficHorizon.html | 1346 +++++++++++++++---------------- test/plots/traffic-horizon.ts | 13 +- 2 files changed, 676 insertions(+), 683 deletions(-) diff --git a/test/output/trafficHorizon.html b/test/output/trafficHorizon.html index dc2d132b0f..140b0d80cc 100644 --- a/test/output/trafficHorizon.html +++ b/test/output/trafficHorizon.html @@ -50,1682 +50,1680 @@ white-space: pre; } - - - - - - - - - - - - - - - - - - - Jan 412 AM - 12 PM - Jan 512 AM - 12 PM - Jan 612 AM - 12 PM - Jan 712 AM - 12 PM - Jan 812 AM - 12 PM - Jan 912 AM - 12 PM - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - Von der Heydt + + + + + + + + + + + + + + + + + + 12 PM + Jan 512 AM + 12 PM + Jan 612 AM + 12 PM + Jan 712 AM + 12 PM + Jan 812 AM + 12 PM + Jan 912 AM + 12 PM + + + + + Von der Heydt - - Kirschheck + + Kirschheck - - Saarbrücken-Neuhaus + + Saarbrücken-Neuhaus - - Riegelsberg + + Riegelsberg - - Holz + + Holz - - Göttelborn + + Göttelborn - - Illingen + + Illingen - - AS Eppelborn + + AS Eppelborn - - Hasborn + + Hasborn - - Kastel + + Kastel - - Otzenhausen + + Otzenhausen - - Bierfeld + + Bierfeld - - Nonnweiler + + Nonnweiler - - Hetzerath + + Hetzerath - - Laufeld + + Laufeld - - Nettersheim + + Nettersheim - - Euskirchen/Bliesheim + + Euskirchen/Bliesheim - - Hürth + + Hürth - - Köln-Nord + + Köln-Nord - - Schloss Burg + + Schloss Burg - - Hagen-Vorhalle + + Hagen-Vorhalle - - Hengsen + + Hengsen - - Unna + + Unna - - Ascheberg + + Ascheberg - - Ladbergen + + Ladbergen - - Lotte + + Lotte - - HB-Silbersee + + HB-Silbersee - - HB-Weserbrücke + + HB-Weserbrücke - - HB-Mahndorfer See + + HB-Mahndorfer See - - Groß Ippener + + Groß Ippener - - Uphusen + + Uphusen - - Bockel + + Bockel - - Dibbersen + + Dibbersen - - Glüsingen + + Glüsingen - - Barsbüttel + + Barsbüttel - - Bad Schwartau + + Bad Schwartau - - Oldenburg (Holstein) + + Oldenburg (Holstein) - - Neustadt i. H.-Süd + + Neustadt i. H.-Süd diff --git a/test/plots/traffic-horizon.ts b/test/plots/traffic-horizon.ts index 3b229abad9..c864fb3249 100644 --- a/test/plots/traffic-horizon.ts +++ b/test/plots/traffic-horizon.ts @@ -10,9 +10,7 @@ export async function trafficHorizon() { return Plot.plot({ width: 960, height: 1100, - x: { - axis: "top" - }, + marginLeft: 0, y: { axis: null, domain: [0, step] @@ -30,13 +28,10 @@ export async function trafficHorizon() { axis: null, domain: data.map((d) => d.location) // respect input order }, - facet: { - data, - y: "location" - }, marks: [ - ticks.map((t) => Plot.areaY(data, {x: "date", y: (d) => d.vehicles - t, fill: t, clip: true})), - Plot.text(data, Plot.selectFirst({text: "location", frameAnchor: "left"})) + ticks.map((t) => Plot.areaY(data, {x: "date", y: (d) => d.vehicles - t, fy: "location", fill: t, clip: true})), + Plot.axisX({anchor: "top", filter: (d, i) => i > 0}), // don’t show the first tick + Plot.axisFy({frameAnchor: "left", fill: "currentColor", textStroke: "white", label: null}) ] }); } From b62c0962325f025f00d39ca7e50484ab69eb82a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Tue, 27 Jun 2023 23:32:10 +0200 Subject: [PATCH 3/4] support {filter, sort, reverse} --- src/marks/axis.js | 10 ++++------ src/transforms/basic.js | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/marks/axis.js b/src/marks/axis.js index 28ae4826ed..3003e3b9cf 100644 --- a/src/marks/axis.js +++ b/src/marks/axis.js @@ -8,7 +8,7 @@ import {maybeColorChannel, maybeNumberChannel, maybeRangeInterval} from "../opti import {isTemporalScale} from "../scales.js"; import {offset} from "../style.js"; import {formatTimeTicks, isTimeYear, isUtcYear} from "../time.js"; -import {initializer, filter} from "../transforms/basic.js"; +import {initializer, composeInitializer} from "../transforms/basic.js"; import {ruleX, ruleY} from "./rule.js"; import {text, textX, textY} from "./text.js"; import {vectorX, vectorY} from "./vector.js"; @@ -505,7 +505,7 @@ function labelOptions( }; } -function axisMark(mark, k, ariaLabel, data, {filter: f, sort: s, reverse: r, ...options}, initialize) { +function axisMark(mark, k, ariaLabel, data, {filter, sort, reverse, ...options}, initialize) { let channels; let i = initializer(options, function (data, facets, _channels, scales, dimensions, context) { @@ -554,10 +554,8 @@ function axisMark(mark, k, ariaLabel, data, {filter: f, sort: s, reverse: r, ... return {data, facets, channels: initializedChannels}; }); - // Apply the filter option after the initializer has determined the mark’s data. - if (f != null) i = filter(f, i); - if (s != null) throw new Error("Unsupported axis option: sort"); - if (r != null) throw new Error("Unsupported axis option: reverse"); + // Apply the sort and filter options after the initializer has determined the mark’s data. + i.initializer = composeInitializer(i.initializer, initializer({filter, sort, reverse}).initializer); const m = mark(data, i); if (data == null) { diff --git a/src/transforms/basic.js b/src/transforms/basic.js index 573032fa47..db1c659548 100644 --- a/src/transforms/basic.js +++ b/src/transforms/basic.js @@ -44,7 +44,7 @@ function composeTransform(t1, t2) { }; } -function composeInitializer(i1, i2) { +export function composeInitializer(i1, i2) { if (i1 == null) return i2 === null ? undefined : i2; if (i2 == null) return i1 === null ? undefined : i1; return function (data, facets, channels, ...args) { From 1195f02a31d38c456c647beff03c760c4cc4334f Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 27 Jun 2023 17:04:07 -0700 Subject: [PATCH 4/4] compose axis initializer --- src/marks/axis.js | 15 +- src/transforms/basic.js | 2 +- .../output/{axesFilter.svg => axisFilter.svg} | 0 test/output/trafficHorizon.html | 1340 ++++++++--------- .../usStatePopulationChangeRelative.svg | 220 +++ test/output/usStatePopulationDiverging.svg | 220 --- test/plots/axis-filter.ts | 18 + test/plots/axis-labels.ts | 17 - test/plots/index.ts | 1 + test/plots/traffic-horizon.ts | 8 +- test/plots/us-state-population-change.ts | 5 +- 11 files changed, 924 insertions(+), 922 deletions(-) rename test/output/{axesFilter.svg => axisFilter.svg} (100%) create mode 100644 test/output/usStatePopulationChangeRelative.svg delete mode 100644 test/output/usStatePopulationDiverging.svg create mode 100644 test/plots/axis-filter.ts diff --git a/src/marks/axis.js b/src/marks/axis.js index 7cdfccd82d..11984ca935 100644 --- a/src/marks/axis.js +++ b/src/marks/axis.js @@ -8,7 +8,7 @@ import {maybeColorChannel, maybeNumberChannel, maybeRangeInterval} from "../opti import {isTemporalScale} from "../scales.js"; import {offset} from "../style.js"; import {formatTimeTicks, isTimeYear, isUtcYear} from "../time.js"; -import {initializer, composeInitializer} from "../transforms/basic.js"; +import {initializer} from "../transforms/basic.js"; import {ruleX, ruleY} from "./rule.js"; import {text, textX, textY} from "./text.js"; import {vectorX, vectorY} from "./vector.js"; @@ -505,10 +505,10 @@ function labelOptions( }; } -function axisMark(mark, k, ariaLabel, data, {filter, sort, reverse, ...options}, initialize) { +function axisMark(mark, k, ariaLabel, data, options, initialize) { let channels; - let i = initializer(options, function (data, facets, _channels, scales, dimensions, context) { + function axisInitializer(data, facets, _channels, scales, dimensions, context) { const initializeFacets = data == null && (k === "fx" || k === "fy"); const {[k]: scale} = scales; if (!scale) throw new Error(`missing scale: ${k}`); @@ -552,12 +552,11 @@ function axisMark(mark, k, ariaLabel, data, {filter, sort, reverse, ...options}, ); if (initializeFacets) facets = context.filterFacets(data, initializedChannels); return {data, facets, channels: initializedChannels}; - }); - - // Apply the sort and filter options after the initializer has determined the mark’s data. - i.initializer = composeInitializer(i.initializer, initializer({filter, sort, reverse}).initializer); + } - const m = mark(data, i); + // Apply any basic initializers after the axis initializer computes the ticks. + const basicInitializer = initializer(options).initializer; + const m = mark(data, initializer({...options, initializer: axisInitializer}, basicInitializer)); if (data == null) { channels = m.channels; m.channels = {}; diff --git a/src/transforms/basic.js b/src/transforms/basic.js index db1c659548..573032fa47 100644 --- a/src/transforms/basic.js +++ b/src/transforms/basic.js @@ -44,7 +44,7 @@ function composeTransform(t1, t2) { }; } -export function composeInitializer(i1, i2) { +function composeInitializer(i1, i2) { if (i1 == null) return i2 === null ? undefined : i2; if (i2 == null) return i1 === null ? undefined : i1; return function (data, facets, channels, ...args) { diff --git a/test/output/axesFilter.svg b/test/output/axisFilter.svg similarity index 100% rename from test/output/axesFilter.svg rename to test/output/axisFilter.svg diff --git a/test/output/trafficHorizon.html b/test/output/trafficHorizon.html index 140b0d80cc..a05e049014 100644 --- a/test/output/trafficHorizon.html +++ b/test/output/trafficHorizon.html @@ -51,1679 +51,1679 @@ } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - - - - - - - - - + + + Von der Heydt - - - - 12 PM - Jan 512 AM - 12 PM - Jan 612 AM - 12 PM - Jan 712 AM - 12 PM - Jan 812 AM - 12 PM - Jan 912 AM - 12 PM + + Kirschheck - - - - Von der Heydt + + Saarbrücken-Neuhaus - - Kirschheck + + Riegelsberg - - Saarbrücken-Neuhaus + + Holz - - Riegelsberg + + Göttelborn - - Holz + + Illingen - - Göttelborn + + AS Eppelborn - - Illingen + + Hasborn - - AS Eppelborn + + Kastel - - Hasborn + + Otzenhausen - - Kastel + + Bierfeld - - Otzenhausen + + Nonnweiler - - Bierfeld + + Hetzerath - - Nonnweiler + + Laufeld - - Hetzerath + + Nettersheim - - Laufeld + + Euskirchen/Bliesheim - - Nettersheim + + Hürth - - Euskirchen/Bliesheim + + Köln-Nord - - Hürth + + Schloss Burg - - Köln-Nord + + Hagen-Vorhalle - - Schloss Burg + + Hengsen - - Hagen-Vorhalle + + Unna - - Hengsen + + Ascheberg - - Unna + + Ladbergen - - Ascheberg + + Lotte - - Ladbergen + + HB-Silbersee - - Lotte + + HB-Weserbrücke - - HB-Silbersee + + HB-Mahndorfer See - - HB-Weserbrücke + + Groß Ippener - - HB-Mahndorfer See + + Uphusen - - Groß Ippener + + Bockel - - Uphusen + + Dibbersen - - Bockel + + Glüsingen - - Dibbersen + + Barsbüttel - - Glüsingen + + Bad Schwartau - - Barsbüttel + + Oldenburg (Holstein) - - Bad Schwartau + + Neustadt i. H.-Süd - - Oldenburg (Holstein) + + + + + + + + + + + + + + - - Neustadt i. H.-Süd + + + + 12 PM + Jan 512 AM + 12 PM + Jan 612 AM + 12 PM + Jan 712 AM + 12 PM + Jan 812 AM + 12 PM + Jan 912 AM + 12 PM diff --git a/test/output/usStatePopulationChangeRelative.svg b/test/output/usStatePopulationChangeRelative.svg new file mode 100644 index 0000000000..887374e72a --- /dev/null +++ b/test/output/usStatePopulationChangeRelative.svg @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + −10 + −5 + +0 + +5 + +10 + +15 + + + ← decrease · Change in population, 2010–2019 (%) · increase → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mississippi + New York + Rhode Island + Pennsylvania + New Jersey + Michigan + Maine + Ohio + New Mexico + Kansas + Wisconsin + Missouri + Louisiana + Alabama + Wyoming + Kentucky + Alaska + New Hampshire + Arkansas + Iowa + Indiana + Hawaii + Maryland + Oklahoma + Nebraska + California + Massachusetts + Minnesota + Virginia + Tennessee + Montana + Delaware + South Dakota + Georgia + North Carolina + Oregon + South Carolina + Washington + North Dakota + Arizona + Idaho + Nevada + Florida + Colorado + Texas + Utah + District of Columbia + + + + + + + + + + Puerto Rico + West Virginia + Illinois + Vermont + Connecticut + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/usStatePopulationDiverging.svg b/test/output/usStatePopulationDiverging.svg deleted file mode 100644 index aea7ce7adc..0000000000 --- a/test/output/usStatePopulationDiverging.svg +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - - - - - - - - - - - - −10 - −5 - +0 - +5 - +10 - +15 - - - ← decrease · Change in population, 2010–2019 (%) · increase → - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Mississippi - New York - Rhode Island - Pennsylvania - New Jersey - Michigan - Maine - Ohio - New Mexico - Kansas - Wisconsin - Missouri - Louisiana - Alabama - Wyoming - Kentucky - Alaska - New Hampshire - Arkansas - Iowa - Indiana - Hawaii - Maryland - Oklahoma - Nebraska - California - Massachusetts - Minnesota - Virginia - Tennessee - Montana - Delaware - South Dakota - Georgia - North Carolina - Oregon - South Carolina - Washington - North Dakota - Arizona - Idaho - Nevada - Florida - Colorado - Texas - Utah - District of Columbia - - - - - - - - - - Puerto Rico - West Virginia - Illinois - Vermont - Connecticut - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/plots/axis-filter.ts b/test/plots/axis-filter.ts new file mode 100644 index 0000000000..41f9475d5f --- /dev/null +++ b/test/plots/axis-filter.ts @@ -0,0 +1,18 @@ +import * as Plot from "@observablehq/plot"; + +export async function axisFilter() { + return Plot.plot({ + height: 100, + marks: [ + Plot.dot([ + ["A", 0], + ["B", 2], + [0, 1] + ]), + Plot.gridX({filter: (d) => d}), + Plot.gridY({filter: (d) => d}), + Plot.axisX({filter: (d) => d}), + Plot.axisY({filter: (d) => d}) + ] + }); +} diff --git a/test/plots/axis-labels.ts b/test/plots/axis-labels.ts index 6aeaa3b504..fac2f941d6 100644 --- a/test/plots/axis-labels.ts +++ b/test/plots/axis-labels.ts @@ -65,20 +65,3 @@ export async function axisLabelHref() { marks: [Plot.axisX({label: "Letter", href: (d) => `https://en.wikipedia.org/wiki/${d}`})] }); } - -export async function axesFilter() { - return Plot.plot({ - height: 100, - marks: [ - Plot.dot([ - ["A", 0], - ["B", 2], - [0, 1] - ]), - Plot.gridX({filter: (d) => d}), - Plot.gridY({filter: (d) => d}), - Plot.axisX({filter: (d) => d}), - Plot.axisY({filter: (d) => d}) - ] - }); -} diff --git a/test/plots/index.ts b/test/plots/index.ts index bba13661b0..c81803dd82 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -27,6 +27,7 @@ export * from "./athletes-weight-cumulative.js"; export * from "./athletes-weight.js"; export * from "./autoplot.js"; export * from "./availability.js"; +export * from "./axis-filter.js"; export * from "./axis-labels.js"; export * from "./ballot-status-race.js"; export * from "./band-clip.js"; diff --git a/test/plots/traffic-horizon.ts b/test/plots/traffic-horizon.ts index c864fb3249..14ee18d1c1 100644 --- a/test/plots/traffic-horizon.ts +++ b/test/plots/traffic-horizon.ts @@ -10,7 +10,8 @@ export async function trafficHorizon() { return Plot.plot({ width: 960, height: 1100, - marginLeft: 0, + margin: 0, + marginTop: 30, y: { axis: null, domain: [0, step] @@ -25,13 +26,12 @@ export async function trafficHorizon() { legend: true }, fy: { - axis: null, domain: data.map((d) => d.location) // respect input order }, marks: [ ticks.map((t) => Plot.areaY(data, {x: "date", y: (d) => d.vehicles - t, fy: "location", fill: t, clip: true})), - Plot.axisX({anchor: "top", filter: (d, i) => i > 0}), // don’t show the first tick - Plot.axisFy({frameAnchor: "left", fill: "currentColor", textStroke: "white", label: null}) + Plot.axisFy({frameAnchor: "left", label: null}), + Plot.axisX({anchor: "top", filter: (d, i) => i > 0}) // drop first tick ] }); } diff --git a/test/plots/us-state-population-change.ts b/test/plots/us-state-population-change.ts index 0fe056936b..ed87f456e9 100644 --- a/test/plots/us-state-population-change.ts +++ b/test/plots/us-state-population-change.ts @@ -34,10 +34,11 @@ export async function usStatePopulationChange() { }); } -export async function usStatePopulationDiverging() { +export async function usStatePopulationChangeRelative() { const statepop = await d3.csv("data/us-state-population-2010-2019.csv", d3.autoType); const change = new Map(statepop.map((d) => [d.State, (d[2019] - d[2010]) / d[2010]])); return Plot.plot({ + height: 800, label: null, x: { axis: "top", @@ -58,7 +59,7 @@ export async function usStatePopulationDiverging() { fill: (d) => Math.sign(change.get(d.State)), sort: {y: "x"} }), - Plot.axisY({x: 0, filter: (d) => change.get(d) >= 0}), + Plot.axisY({x: 0, filter: (d) => change.get(d) >= 0, anchor: "left"}), Plot.axisY({x: 0, filter: (d) => change.get(d) < 0, anchor: "right"}), Plot.gridX({stroke: "white", strokeOpacity: 0.5}), Plot.ruleX([0])