From 7afd9cd880694e2455726734b1a8833f910d5284 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Thu, 23 Sep 2021 13:48:15 -0700 Subject: [PATCH] default insets for intervals --- src/transforms/bin.js | 30 ++-- src/transforms/inset.js | 17 ++ src/transforms/interval.js | 11 +- test/output/aaplCloseRect.svg | 153 ------------------ test/output/aaplVolumeRect.svg | 131 +++++++++++++++ ...aapl-close-rect.js => aapl-volume-rect.js} | 8 +- test/plots/index.js | 2 +- 7 files changed, 169 insertions(+), 183 deletions(-) create mode 100644 src/transforms/inset.js delete mode 100644 test/output/aaplCloseRect.svg create mode 100644 test/output/aaplVolumeRect.svg rename test/plots/{aapl-close-rect.js => aapl-volume-rect.js} (59%) diff --git a/src/transforms/bin.js b/src/transforms/bin.js index 1a9397bf1b..09d96b6469 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -1,31 +1,25 @@ import {bin as binner, extent, thresholdFreedmanDiaconis, thresholdScott, thresholdSturges, utcTickInterval} from "d3"; import {valueof, range, identity, maybeLazyChannel, maybeTuple, maybeColor, maybeValue, mid, labelof, isTemporal} from "../mark.js"; -import {offset} from "../style.js"; import {basic} from "./basic.js"; import {maybeEvaluator, maybeGroup, maybeOutput, maybeOutputs, maybeReduce, maybeSort, maybeSubgroup, reduceCount, reduceIdentity} from "./group.js"; +import {maybeInsetX, maybeInsetY} from "./inset.js"; // Group on {z, fill, stroke}, then optionally on y, then bin x. -export function binX(outputs = {y: "count"}, {inset, insetLeft, insetRight, ...options} = {}) { - let {x, y} = options; - x = maybeBinValue(x, options, identity); - ([insetLeft, insetRight] = maybeInset(inset, insetLeft, insetRight)); - return binn(x, null, null, y, outputs, {inset, insetLeft, insetRight, ...options}); +export function binX(outputs = {y: "count"}, options = {}) { + const {x, y} = options; + return binn(maybeBinValue(x, options, identity), null, null, y, outputs, maybeInsetX(options)); } // Group on {z, fill, stroke}, then optionally on x, then bin y. -export function binY(outputs = {x: "count"}, {inset, insetTop, insetBottom, ...options} = {}) { - let {x, y} = options; - y = maybeBinValue(y, options, identity); - ([insetTop, insetBottom] = maybeInset(inset, insetTop, insetBottom)); - return binn(null, y, x, null, outputs, {inset, insetTop, insetBottom, ...options}); +export function binY(outputs = {x: "count"}, options = {}) { + const {x, y} = options; + return binn(null, maybeBinValue(y, options, identity), x, null, outputs, maybeInsetY(options)); } // Group on {z, fill, stroke}, then bin on x and y. -export function bin(outputs = {fill: "count"}, {inset, insetTop, insetRight, insetBottom, insetLeft, ...options} = {}) { +export function bin(outputs = {fill: "count"}, options = {}) { const {x, y} = maybeBinValueTuple(options); - ([insetTop, insetBottom] = maybeInset(inset, insetTop, insetBottom)); - ([insetLeft, insetRight] = maybeInset(inset, insetLeft, insetRight)); - return binn(x, y, null, null, outputs, {inset, insetTop, insetRight, insetBottom, insetLeft, ...options}); + return binn(x, y, null, null, outputs, maybeInsetX(maybeInsetY(options))); } function binn( @@ -252,9 +246,3 @@ function binfilter([{x0, x1}, set]) { function binempty() { return new Uint32Array(0); } - -function maybeInset(inset, inset1, inset2) { - return inset === undefined && inset1 === undefined && inset2 === undefined - ? (offset ? [1, 0] : [0.5, 0.5]) - : [inset1, inset2]; -} diff --git a/src/transforms/inset.js b/src/transforms/inset.js new file mode 100644 index 0000000000..46cd750d44 --- /dev/null +++ b/src/transforms/inset.js @@ -0,0 +1,17 @@ +import {offset} from "../style.js"; + +export function maybeInsetX({inset, insetLeft, insetRight, ...options} = {}) { + ([insetLeft, insetRight] = maybeInset(inset, insetLeft, insetRight)); + return {inset, insetLeft, insetRight, ...options}; +} + +export function maybeInsetY({inset, insetTop, insetBottom, ...options} = {}) { + ([insetTop, insetBottom] = maybeInset(inset, insetTop, insetBottom)); + return {inset, insetTop, insetBottom, ...options}; +} + +function maybeInset(inset, inset1, inset2) { + return inset === undefined && inset1 === undefined && inset2 === undefined + ? (offset ? [1, 0] : [0.5, 0.5]) + : [inset1, inset2]; +} diff --git a/src/transforms/interval.js b/src/transforms/interval.js index a7675244fe..a84e061713 100644 --- a/src/transforms/interval.js +++ b/src/transforms/interval.js @@ -1,4 +1,5 @@ import {labelof, maybeValue, valueof} from "../mark.js"; +import {maybeInsetX, maybeInsetY} from "./inset.js"; // TODO Allow the interval to be specified as a string, e.g. “day” or “hour”? // This will require the interval knowing the type of the associated scale to @@ -17,25 +18,25 @@ function maybeIntervalValue(value, {interval} = {}) { return value; } -function maybeIntervalK(k, options = {}) { +function maybeIntervalK(k, maybeInsetK, options = {}) { const {[k]: v, [`${k}1`]: v1, [`${k}2`]: v2} = options; const {value, interval} = maybeIntervalValue(v, options); if (interval == null) return options; let V1; const tv1 = data => V1 || (V1 = valueof(data, value).map(v => interval.floor(v))); const label = labelof(v); - return { + return maybeInsetK({ ...options, [k]: undefined, [`${k}1`]: v1 === undefined ? {transform: tv1, label} : v1, [`${k}2`]: v2 === undefined ? {transform: () => tv1().map(v => interval.offset(v)), label} : v2 - }; + }); } export function maybeIntervalX(options) { - return maybeIntervalK("x", options); + return maybeIntervalK("x", maybeInsetX, options); } export function maybeIntervalY(options = {}) { - return maybeIntervalK("y", options); + return maybeIntervalK("y", maybeInsetY, options); } diff --git a/test/output/aaplCloseRect.svg b/test/output/aaplCloseRect.svg deleted file mode 100644 index e736e4e50a..0000000000 --- a/test/output/aaplCloseRect.svg +++ /dev/null @@ -1,153 +0,0 @@ - - - - - 0 - - - - 20 - - - - 40 - - - - 60 - - - - 80 - - - - 100 - - - - 120 - - - - 140 - - - - 160 - - - - 180 - ↑ Close - - - - February - - - March - - - April - - - May - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/test/output/aaplVolumeRect.svg b/test/output/aaplVolumeRect.svg new file mode 100644 index 0000000000..8b767e80ae --- /dev/null +++ b/test/output/aaplVolumeRect.svg @@ -0,0 +1,131 @@ + + + + + 0 + + + + 5 + + + + 10 + + + + 15 + + + + 20 + + + + 25 + + + + 30 + + + + 35 + + + + 40 + + + + 45 + + + + 50 + + + + 55 + + + + 60 + + + + 65 + ↑ Daily trade volume (millions) + + + + Mar 18 + + + Mar 25 + + + April + + + Apr 08 + + + Apr 15 + + + Apr 22 + + + Apr 29 + + + May 06 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/aapl-close-rect.js b/test/plots/aapl-volume-rect.js similarity index 59% rename from test/plots/aapl-close-rect.js rename to test/plots/aapl-volume-rect.js index 6f1e55543c..60f5a14254 100644 --- a/test/plots/aapl-close-rect.js +++ b/test/plots/aapl-volume-rect.js @@ -2,13 +2,15 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export default async function() { - const AAPL = (await d3.csv("data/aapl.csv", d3.autoType)).slice(-90); + const AAPL = (await d3.csv("data/aapl.csv", d3.autoType)).slice(-40); return Plot.plot({ y: { - grid: true + grid: true, + transform: d => d / 1e6, + label: "↑ Daily trade volume (millions)" }, marks: [ - Plot.rectY(AAPL, {x: "Date", interval: d3.utcDay, y: "Close"}), + Plot.rectY(AAPL, {x: "Date", interval: d3.utcDay, y: "Volume"}), Plot.ruleY([0]) ] }); diff --git a/test/plots/index.js b/test/plots/index.js index 71ab723acb..4dc18f294b 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -1,10 +1,10 @@ export {default as aaplCandlestick} from "./aapl-candlestick.js"; export {default as aaplChangeVolume} from "./aapl-change-volume.js"; export {default as aaplClose} from "./aapl-close.js"; -export {default as aaplCloseRect} from "./aapl-close-rect.js"; export {default as aaplCloseUntyped} from "./aapl-close-untyped.js"; export {default as aaplMonthly} from "./aapl-monthly.js"; export {default as aaplVolume} from "./aapl-volume.js"; +export {default as aaplVolumeRect} from "./aapl-volume-rect.js"; export {default as anscombeQuartet} from "./anscombe-quartet.js"; export {default as athletesHeightWeight} from "./athletes-height-weight.js"; export {default as athletesHeightWeightBin} from "./athletes-height-weight-bin.js";