From 79ceefe55eaa165ee80b751d25cf72fd6da4a20a Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 23 Oct 2023 20:04:53 -0700 Subject: [PATCH] checkpoint docs --- docs/.vitepress/config.ts | 1 + docs/marks/difference.md | 96 +++++++++++++++++++++++++++++++++++++++ src/marks/difference.js | 6 +-- test/plots/difference.ts | 34 +++++++------- 4 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 docs/marks/difference.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index df91d8fd3a..711a330ad1 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -88,6 +88,7 @@ export default defineConfig({ {text: "Contour", link: "/marks/contour"}, {text: "Delaunay", link: "/marks/delaunay"}, {text: "Density", link: "/marks/density"}, + {text: "Difference", link: "/marks/difference"}, {text: "Dot", link: "/marks/dot"}, {text: "Frame", link: "/marks/frame"}, {text: "Geo", link: "/marks/geo"}, diff --git a/docs/marks/difference.md b/docs/marks/difference.md new file mode 100644 index 0000000000..57d6bca8e6 --- /dev/null +++ b/docs/marks/difference.md @@ -0,0 +1,96 @@ + + +# Difference mark + +The **difference mark** compares a primary metric to a secondary metric. + +:::plot +```js +Plot.plot({ + y: {grid: true}, + marks: [ + Plot.ruleY([1]), + Plot.differenceY(aapl, Plot.normalizeY({ + x: "Date", + y1: "Close", + y2: Plot.valueof(goog, "Close"), + tip: true + })) + ] +}) +``` +::: + +:::plot +```js +Plot.plot({ + y: {grid: true}, + marks: [ + Plot.differenceY(aapl, { + x1: "Date", + x2: (d) => d3.utcYear.offset(d.Date), + y: "Close", + tip: true + }) + ] +}) +``` +::: + +

+ +

+ +:::plot +```js +Plot.plot({ + x: {label: "Date"}, + y: {grid: true}, + marks: [ + Plot.differenceY(aapl, { + x1: (d, i, data) => d.Date < offset(data[0].Date) ? null : d.Date, + x2: (d, i, data) => data.at(-1).Date < offset(d.Date) ? null : offset(d.Date), + y: "Close" + }) + ] +}) +``` +::: + +:::plot +```js +Plot.plot({ + x: {label: "Date"}, + y: {grid: true}, + marks: [ + Plot.differenceY(aapl, { + x: (d, i) => i < shift ? null : d.Date, + y1: "Close", + y2: (d, i) => aapl[i - shift]?.Close, + tip: true + }) + ] +}) +``` +::: diff --git a/src/marks/difference.js b/src/marks/difference.js index a011c18bb7..f8b52a05bd 100644 --- a/src/marks/difference.js +++ b/src/marks/difference.js @@ -37,7 +37,7 @@ export function differenceY( y2, fill: positiveColor, fillOpacity: positiveOpacity, - render: composeRender(render, renderDifference(true)), + render: composeRender(render, clipDifference(true)), ...options }), {ariaLabel: "positive difference"} @@ -50,7 +50,7 @@ export function differenceY( y2, fill: negativeColor, fillOpacity: negativeOpacity, - render: composeRender(render, renderDifference(false)), + render: composeRender(render, clipDifference(false)), ...options }), {ariaLabel: "negative difference"} @@ -95,7 +95,7 @@ function memo(v) { return {transform: (data) => V || (V = valueof(data, value)), label}; } -function renderDifference(positive) { +function clipDifference(positive) { return (index, scales, channels, dimensions, context, next) => { const clip = getClipId(); const clipPath = create("svg:clipPath", context).attr("id", clip).node(); diff --git a/test/plots/difference.ts b/test/plots/difference.ts index f984474ebe..e7c89c094b 100644 --- a/test/plots/difference.ts +++ b/test/plots/difference.ts @@ -9,7 +9,7 @@ export async function differenceY() { const x = aapl.map((d) => d.Date); const y1 = aapl.map((d, i, data) => d.Close / data[0].Close); const y2 = goog.map((d, i, data) => d.Close / data[0].Close); - return Plot.differenceY(aapl, {x: {value: x, label: "Date"}, y1: {value: y1, label: "Close"}, y2, tip: true}).plot(); + return Plot.differenceY(aapl, {x, y1: {value: y1, label: "Close"}, y2, tip: true}).plot(); } export async function differenceYRandom() { @@ -48,21 +48,23 @@ export async function differenceYVariable() { // before and the year after the dataset, x1 and x2 are padded with NaN. export async function differenceY1() { const aapl = await d3.csv("data/aapl.csv", d3.autoType); - const interval = d3.utcYear; - const start = interval.offset(aapl[0].Date, 1); - const end = interval.offset(aapl[aapl.length - 1].Date, -1); - const x1 = aapl.map((d) => (d.Date < start ? NaN : d.Date)); - const x2 = aapl.map((d) => (d.Date > end ? NaN : interval.offset(d.Date, 1))); - const y = aapl.map((d) => d.Close); - return Plot.differenceY(aapl, { - x1, - x2, - y, - positiveOpacity: 0.2, - positiveColor: "currentColor", - negativeOpacity: 0.8, - negativeColor: "red" - }).plot(); + return Plot.differenceY(aapl, shiftX(d3.utcYear, {x: "Date", y: "Close"})).plot({y: {grid: true}}); +} + +function shiftX(interval, options) { + return Plot.map( + { + x1(D) { + const min = interval.offset(d3.min(D), 1); + return D.map((d) => (d < min ? null : d)); + }, + x2(D) { + const max = interval.offset(d3.max(D), -1); + return D.map((d) => (max < d ? null : interval.offset(d, 1))); + } + }, + options + ); } export async function differenceFilterX() {