From 7dcccea37cb0f2eef3fe03d5972cb488ded26107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Sat, 10 Aug 2024 11:01:31 -0400 Subject: [PATCH 1/9] waffle tips --- src/marks/tip.js | 2 + src/marks/waffle.js | 101 ++++++-- test/output/waffleTip.svg | 67 +++++ test/output/waffleTipUnit.svg | 447 +++++++++++++++++++++++++++++++++ test/output/waffleTipUnitX.svg | 447 +++++++++++++++++++++++++++++++++ test/output/waffleTipX.svg | 70 ++++++ test/plots/waffle.ts | 33 +++ 7 files changed, 1145 insertions(+), 22 deletions(-) create mode 100644 test/output/waffleTip.svg create mode 100644 test/output/waffleTipUnit.svg create mode 100644 test/output/waffleTipUnitX.svg create mode 100644 test/output/waffleTipX.svg diff --git a/src/marks/tip.js b/src/marks/tip.js index bfb9d04cb2..718cab92f7 100644 --- a/src/marks/tip.js +++ b/src/marks/tip.js @@ -431,6 +431,8 @@ function* formatChannels(i, index, channels, scales, values) { function formatPair(formatValue, c1, c2, i) { return c2.hint?.length // e.g., stackY’s y1 and y2 ? `${formatValue(c2.value[i] - c1.value[i], i)}` + : c2.hint?.single // e.g., waffleY’s y1 and y2 + ? `${formatValue(c2.value[i], i)}` : `${formatValue(c1.value[i], i)}–${formatValue(c2.value[i], i)}`; } diff --git a/src/marks/waffle.js b/src/marks/waffle.js index c9d8771d21..0f4b173ba4 100644 --- a/src/marks/waffle.js +++ b/src/marks/waffle.js @@ -1,9 +1,11 @@ -import {extent, namespaces} from "d3"; +import {extent, namespaces, polygonCentroid} from "d3"; +import {valueObject} from "../channel.js"; import {create} from "../context.js"; import {composeRender} from "../mark.js"; import {hasXY, identity, indexOf} from "../options.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, getPatternId} from "../style.js"; import {template} from "../template.js"; +import {initializer} from "../transforms/basic.js"; import {maybeIdentityX, maybeIdentityY} from "../transforms/identity.js"; import {maybeIntervalX, maybeIntervalY} from "../transforms/interval.js"; import {maybeStackX, maybeStackY} from "../transforms/stack.js"; @@ -14,8 +16,10 @@ const waffleDefaults = { }; export class WaffleX extends BarX { - constructor(data, {unit = 1, gap = 1, round, render, multiple, ...options} = {}) { - super(data, {...options, render: composeRender(render, waffleRender("x"))}, waffleDefaults); + constructor(data, {unit = 1, gap = 1, round, render, multiple, tip, ...options} = {}) { + options = initializer({...options, render: composeRender(render, waffleRender("x"))}, waffleInitializer("x")); + if (tip) options = initializer({...options, tip}, waffleTipInitializer("x")); + super(data, options, waffleDefaults); this.unit = Math.max(0, unit); this.gap = +gap; this.round = maybeRound(round); @@ -24,8 +28,10 @@ export class WaffleX extends BarX { } export class WaffleY extends BarY { - constructor(data, {unit = 1, gap = 1, round, render, multiple, ...options} = {}) { - super(data, {...options, render: composeRender(render, waffleRender("y"))}, waffleDefaults); + constructor(data, {unit = 1, gap = 1, round, render, multiple, tip, ...options} = {}) { + options = initializer({...options, render: composeRender(render, waffleRender("y"))}, waffleInitializer("y")); + if (tip) options = initializer({...options, tip}, waffleTipInitializer("y")); + super(data, options, waffleDefaults); this.unit = Math.max(0, unit); this.gap = +gap; this.round = maybeRound(round); @@ -33,10 +39,11 @@ export class WaffleY extends BarY { } } -function waffleRender(y) { - return function (index, scales, values, dimensions, context) { - const {unit, gap, rx, ry, round} = this; - const {document} = context; +function waffleInitializer(y) { + return function (data, facets, channels, scales, dimensions) { + const {round, unit} = this; + + const values = valueObject(channels, scales); const Y1 = values.channels[`${y}1`].value; const Y2 = values.channels[`${y}2`].value; @@ -56,9 +63,65 @@ function waffleRender(y) { // TODO insets? const transform = y === "y" ? ([x, y]) => [x * cx, -y * cy] : ([x, y]) => [y * cy, x * cx]; + const P = Array.from(Y1, (_, i) => wafflePoints(round(Y1[i] / unit), round(Y2[i] / unit), multiple).map(transform)); + const tx = (barwidth - multiple * cx) / 2; - const x0 = typeof barx === "function" ? (i) => barx(i) + tx : barx + tx; - const y0 = scales[y](0); + this.x0 = typeof barx === "function" ? (i) => barx(i) + tx : barx + tx; + this.y0 = scales[y](0); + this.cx = cx; + this.cy = cy; + this.barwidth = barwidth; + this.barx = barx; + this.multiple = multiple; + + return {channels: {polygon: {value: P, source: null}}}; + }; +} + +function waffleTipInitializer(y) { + return function (data, facets, channels) { + const {x0, y0, barwidth} = this; + const P = channels.polygon.value; + const n = P.length; + const tx = typeof x0 === "function" ? (i) => x0(i) - barwidth / 2 : () => x0; + const ty = typeof y0 === "function" ? y0 : () => y0; + + const X = new Float64Array(n); + const Y = new Float64Array(n); + + const [ix, iy] = y === "y" ? [0, 1] : [1, 0]; + for (let i = 0; i < n; ++i) { + const c = polygonCentroid(P[i]); + X[i] = c[ix] + tx(i); + Y[i] = c[iy] + ty(i); + } + + // restore the tip value for y + const source = channels[`${y}2`].hint?.length + ? { + ...channels[`${y}1`], + value: Array.from(channels[`${y}1`].value, (d, i) => channels[`${y}2`].value[i] - d), + hint: {single: true} + } + : null; + + const x = y === "y" ? "x" : "y"; + return { + channels: { + [`${x}1`]: {value: X, scale: null, source: null}, + [`${x}2`]: {value: X, scale: null, source: null}, + [`${y}1`]: {value: Y, scale: null, source}, + [`${y}2`]: {value: Y, scale: null, source} + } + }; + }; +} + +function waffleRender(y) { + return function (index, scales, values, dimensions, context) { + const {gap, cx, cy, rx, ry, x0, y0} = this; + const {document} = context; + const polygon = values.channels.polygon.value; // Create a base pattern with shared attributes for cloning. const patternId = getPatternId(); @@ -95,13 +158,7 @@ function waffleRender(y) { .enter() .append("path") .attr("transform", y === "y" ? template`translate(${x0},${y0})` : template`translate(${y0},${x0})`) - .attr( - "d", - (i) => - `M${wafflePoints(round(Y1[i] / unit), round(Y2[i] / unit), multiple) - .map(transform) - .join("L")}Z` - ) + .attr("d", (i) => `M${polygon[i].join("L")}Z`) .attr("fill", (i) => `url(#${patternId}-${i})`) .attr("stroke", this.stroke == null ? null : (i) => `url(#${patternId}-${i})`) ) @@ -198,12 +255,12 @@ function spread(domain) { return max - min; } -export function waffleX(data, options = {}) { +export function waffleX(data, {tip, ...options} = {}) { if (!hasXY(options)) options = {...options, y: indexOf, x2: identity}; - return new WaffleX(data, maybeStackX(maybeIntervalX(maybeIdentityX(options)))); + return new WaffleX(data, {tip, ...maybeStackX(maybeIntervalX(maybeIdentityX(options)))}); } -export function waffleY(data, options = {}) { +export function waffleY(data, {tip, ...options} = {}) { if (!hasXY(options)) options = {...options, x: indexOf, y2: identity}; - return new WaffleY(data, maybeStackY(maybeIntervalY(maybeIdentityY(options)))); + return new WaffleY(data, {tip, ...maybeStackY(maybeIntervalY(maybeIdentityY(options)))}); } diff --git a/test/output/waffleTip.svg b/test/output/waffleTip.svg new file mode 100644 index 0000000000..c9925a71f1 --- /dev/null +++ b/test/output/waffleTip.svg @@ -0,0 +1,67 @@ + + + + + 0 + 20 + 40 + 60 + 80 + 100 + 120 + 140 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/waffleTipUnit.svg b/test/output/waffleTipUnit.svg new file mode 100644 index 0000000000..2b8dfcc906 --- /dev/null +++ b/test/output/waffleTipUnit.svg @@ -0,0 +1,447 @@ + + + + + 0 + 5 + 10 + 15 + 20 + 25 + 30 + + + + 0 + 1 + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/waffleTipUnitX.svg b/test/output/waffleTipUnitX.svg new file mode 100644 index 0000000000..e5e4a33dcc --- /dev/null +++ b/test/output/waffleTipUnitX.svg @@ -0,0 +1,447 @@ + + + + + 0 + 1 + 2 + + + + 0 + 5 + 10 + 15 + 20 + 25 + 30 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/waffleTipX.svg b/test/output/waffleTipX.svg new file mode 100644 index 0000000000..4940dfa48b --- /dev/null +++ b/test/output/waffleTipX.svg @@ -0,0 +1,70 @@ + + + + + 0 + 20 + 40 + 60 + 80 + 100 + 120 + 140 + + + quantity → + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index 455efd3a4a..eff19a9c12 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -246,3 +246,36 @@ export async function waffleYGrouped() { marks: [Plot.waffleY(athletes, Plot.groupX({y: "count"}, {x: "sport", unit: 10})), Plot.ruleY([0])] }); } + +export function waffleTip() { + return Plot.plot({ + color: {type: "sqrt", scheme: "spectral"}, + y: {inset: 12}, + marks: [Plot.waffleY([1, 4, 9, 24, 46, 66, 7], {x: null, fill: Plot.identity, tip: true})] + }); +} + +export function waffleTipUnit() { + return Plot.plot({ + y: {inset: 12}, + marks: [Plot.waffleY({length: 100}, {x: (d, i) => i % 3, y: 1, fill: d3.randomLcg(42), tip: true})] + }); +} + +export function waffleTipX() { + return Plot.plot({ + style: {overflow: "visible"}, + color: {type: "sqrt", scheme: "spectral"}, + x: {label: "quantity"}, + y: {inset: 12}, + marks: [Plot.waffleX([1, 4, 9, 24, 46, 66, 7], {y: null, fill: Plot.identity, tip: true})] + }); +} + +export function waffleTipUnitX() { + return Plot.plot({ + height: 300, + y: {inset: 12}, + marks: [Plot.waffleX({length: 100}, {multiple: 5, y: (d, i) => i % 3, x: 1, fill: d3.randomLcg(42), tip: true})] + }); +} From da7d094342878bb5147914ab33588620c53f4203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 14 Aug 2024 18:42:21 +0200 Subject: [PATCH 2/9] test faceting --- test/output/waffleTipFacet.svg | 2084 +++++++++++++++++++++++++++++ test/output/waffleTipFacetX.svg | 2080 +++++++++++++++++++++++++++++ test/output/waffleTipFacetXY.svg | 2085 ++++++++++++++++++++++++++++++ test/plots/waffle.ts | 26 + 4 files changed, 6275 insertions(+) create mode 100644 test/output/waffleTipFacet.svg create mode 100644 test/output/waffleTipFacetX.svg create mode 100644 test/output/waffleTipFacetXY.svg diff --git a/test/output/waffleTipFacet.svg b/test/output/waffleTipFacet.svg new file mode 100644 index 0000000000..83e2abf8c8 --- /dev/null +++ b/test/output/waffleTipFacet.svg @@ -0,0 +1,2084 @@ + + + + + 0 + + + 1 + + + + + + 0 + 10 + 20 + 30 + 40 + 50 + 60 + 70 + 80 + + + + + + 0 + 1 + 2 + + + 0 + 1 + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/waffleTipFacetX.svg b/test/output/waffleTipFacetX.svg new file mode 100644 index 0000000000..2fb0a28a3d --- /dev/null +++ b/test/output/waffleTipFacetX.svg @@ -0,0 +1,2080 @@ + + + + + 0 + + + 1 + + + + + + 0 + 1 + 2 + + + + + + 0 + 20 + 40 + 60 + 80 + + + 0 + 20 + 40 + 60 + 80 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/waffleTipFacetXY.svg b/test/output/waffleTipFacetXY.svg new file mode 100644 index 0000000000..a7dda41ded --- /dev/null +++ b/test/output/waffleTipFacetXY.svg @@ -0,0 +1,2085 @@ + + + + + 0 + + + 1 + + + + + 0 + + + 1 + + + 2 + + + + + + 0 + 50 + + + 0 + 50 + + + 0 + 50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index eff19a9c12..6201e82fbd 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -262,6 +262,14 @@ export function waffleTipUnit() { }); } +export function waffleTipFacet() { + return Plot.plot({ + marks: [ + Plot.waffleY({length: 500}, {x: (d, i) => i % 3, fx: (d, i) => i % 2, y: 1, fill: d3.randomLcg(42), tip: true}) + ] + }); +} + export function waffleTipX() { return Plot.plot({ style: {overflow: "visible"}, @@ -279,3 +287,21 @@ export function waffleTipUnitX() { marks: [Plot.waffleX({length: 100}, {multiple: 5, y: (d, i) => i % 3, x: 1, fill: d3.randomLcg(42), tip: true})] }); } + +export function waffleTipFacetX() { + return Plot.plot({ + height: 500, + marks: [ + Plot.waffleX({length: 500}, {y: (d, i) => i % 3, fx: (d, i) => i % 2, x: 1, fill: d3.randomLcg(42), tip: true}) + ] + }); +} + +export function waffleTipFacetXY() { + return Plot.plot({ + height: 600, + marks: [ + Plot.waffleX({length: 500}, {fx: (d, i) => i % 3, fy: (d, i) => i % 2, x: 1, fill: d3.randomLcg(42), tip: true}) + ] + }); +} From 1f0d63469fbe3a0e5df7c445184b26c917ec2cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 15 Aug 2024 11:14:00 +0200 Subject: [PATCH 3/9] simpler x --- src/marks/waffle.js | 11 +++++------ test/plots/waffle.ts | 7 ++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/marks/waffle.js b/src/marks/waffle.js index 0f4b173ba4..5013117909 100644 --- a/src/marks/waffle.js +++ b/src/marks/waffle.js @@ -16,9 +16,9 @@ const waffleDefaults = { }; export class WaffleX extends BarX { - constructor(data, {unit = 1, gap = 1, round, render, multiple, tip, ...options} = {}) { + constructor(data, {unit = 1, gap = 1, round, render, multiple, ...options} = {}) { options = initializer({...options, render: composeRender(render, waffleRender("x"))}, waffleInitializer("x")); - if (tip) options = initializer({...options, tip}, waffleTipInitializer("x")); + if (options.tip) options = initializer(options, waffleTipInitializer("x")); super(data, options, waffleDefaults); this.unit = Math.max(0, unit); this.gap = +gap; @@ -28,9 +28,9 @@ export class WaffleX extends BarX { } export class WaffleY extends BarY { - constructor(data, {unit = 1, gap = 1, round, render, multiple, tip, ...options} = {}) { + constructor(data, {unit = 1, gap = 1, round, render, multiple, ...options} = {}) { options = initializer({...options, render: composeRender(render, waffleRender("y"))}, waffleInitializer("y")); - if (tip) options = initializer({...options, tip}, waffleTipInitializer("y")); + if (options.tip) options = initializer(options, waffleTipInitializer("y")); super(data, options, waffleDefaults); this.unit = Math.max(0, unit); this.gap = +gap; @@ -108,8 +108,7 @@ function waffleTipInitializer(y) { const x = y === "y" ? "x" : "y"; return { channels: { - [`${x}1`]: {value: X, scale: null, source: null}, - [`${x}2`]: {value: X, scale: null, source: null}, + [x]: {value: X, scale: null, source: null}, [`${y}1`]: {value: Y, scale: null, source}, [`${y}2`]: {value: Y, scale: null, source} } diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index 6201e82fbd..8d5073ebd7 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -284,7 +284,12 @@ export function waffleTipUnitX() { return Plot.plot({ height: 300, y: {inset: 12}, - marks: [Plot.waffleX({length: 100}, {multiple: 5, y: (d, i) => i % 3, x: 1, fill: d3.randomLcg(42), tip: true})] + marks: [ + Plot.waffleX( + {length: 100}, + {multiple: 5, y: (d, i) => i % 3, x: 1, fill: d3.randomLcg(42), tip: {format: {x: false}}} + ) + ] }); } From caa50093b3d60a5f0ed27aba8a269b372ff01981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 15 Aug 2024 18:43:33 +0200 Subject: [PATCH 4/9] support waffle pointer, and simplify --- src/marks/waffle.js | 50 ++-- test/output/wafflePointer.svg | 450 ++++++++++++++++++++++++++++++++++ test/plots/waffle.ts | 12 + 3 files changed, 482 insertions(+), 30 deletions(-) create mode 100644 test/output/wafflePointer.svg diff --git a/src/marks/waffle.js b/src/marks/waffle.js index 5013117909..d5df9b02bf 100644 --- a/src/marks/waffle.js +++ b/src/marks/waffle.js @@ -18,7 +18,6 @@ const waffleDefaults = { export class WaffleX extends BarX { constructor(data, {unit = 1, gap = 1, round, render, multiple, ...options} = {}) { options = initializer({...options, render: composeRender(render, waffleRender("x"))}, waffleInitializer("x")); - if (options.tip) options = initializer(options, waffleTipInitializer("x")); super(data, options, waffleDefaults); this.unit = Math.max(0, unit); this.gap = +gap; @@ -30,7 +29,6 @@ export class WaffleX extends BarX { export class WaffleY extends BarY { constructor(data, {unit = 1, gap = 1, round, render, multiple, ...options} = {}) { options = initializer({...options, render: composeRender(render, waffleRender("y"))}, waffleInitializer("y")); - if (options.tip) options = initializer(options, waffleTipInitializer("y")); super(data, options, waffleDefaults); this.unit = Math.max(0, unit); this.gap = +gap; @@ -61,42 +59,34 @@ function waffleInitializer(y) { const cx = Math.min(barwidth / multiple, scale * multiple); const cy = scale * multiple; - // TODO insets? - const transform = y === "y" ? ([x, y]) => [x * cx, -y * cy] : ([x, y]) => [y * cy, x * cx]; - const P = Array.from(Y1, (_, i) => wafflePoints(round(Y1[i] / unit), round(Y2[i] / unit), multiple).map(transform)); - + // The reference position. const tx = (barwidth - multiple * cx) / 2; - this.x0 = typeof barx === "function" ? (i) => barx(i) + tx : barx + tx; - this.y0 = scales[y](0); - this.cx = cx; - this.cy = cy; - this.barwidth = barwidth; - this.barx = barx; - this.multiple = multiple; - - return {channels: {polygon: {value: P, source: null}}}; - }; -} + const x0 = typeof barx === "function" ? (i) => barx(i) + tx : barx + tx; + const y0 = scales[y](0); -function waffleTipInitializer(y) { - return function (data, facets, channels) { - const {x0, y0, barwidth} = this; - const P = channels.polygon.value; - const n = P.length; - const tx = typeof x0 === "function" ? (i) => x0(i) - barwidth / 2 : () => x0; - const ty = typeof y0 === "function" ? y0 : () => y0; + // TODO insets? + const transform = y === "y" ? ([x, y]) => [x * cx, -y * cy] : ([x, y]) => [y * cy, x * cx]; + const mx = typeof x0 === "function" ? (i) => x0(i) - barwidth / 2 : () => x0; + const [ix, iy] = y === "y" ? [0, 1] : [1, 0]; + const n = Y2.length; + const P = new Array(n); const X = new Float64Array(n); const Y = new Float64Array(n); - const [ix, iy] = y === "y" ? [0, 1] : [1, 0]; for (let i = 0; i < n; ++i) { + P[i] = wafflePoints(round(Y1[i] / unit), round(Y2[i] / unit), multiple).map(transform); const c = polygonCentroid(P[i]); - X[i] = c[ix] + tx(i); - Y[i] = c[iy] + ty(i); + X[i] = c[ix] + mx(i); + Y[i] = c[iy] + y0; } - // restore the tip value for y + this.cx = cx; + this.cy = cy; + this.x0 = x0; + this.y0 = y0; + + // Restore the tip value for y. const source = channels[`${y}2`].hint?.length ? { ...channels[`${y}1`], @@ -105,10 +95,10 @@ function waffleTipInitializer(y) { } : null; - const x = y === "y" ? "x" : "y"; return { channels: { - [x]: {value: X, scale: null, source: null}, + polygon: {value: P, source: null}, + [y === "y" ? "x" : "y"]: {value: X, scale: null, source: null}, [`${y}1`]: {value: Y, scale: null, source}, [`${y}2`]: {value: Y, scale: null, source} } diff --git a/test/output/wafflePointer.svg b/test/output/wafflePointer.svg new file mode 100644 index 0000000000..52bc316d01 --- /dev/null +++ b/test/output/wafflePointer.svg @@ -0,0 +1,450 @@ + + + + + 0 + 5 + 10 + 15 + 20 + 25 + 30 + + + + 0 + 1 + 2 + + + x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index 8d5073ebd7..bb5cf3a9e0 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -247,6 +247,18 @@ export async function waffleYGrouped() { }); } +export function wafflePointer() { + const random = d3.randomLcg(42); + const data = Array.from({length: 100}, (_, i) => ({x: i % 3, fill: random()})); + return Plot.plot({ + y: {inset: 12}, + marks: [ + Plot.waffleY(data, {x: "x", y: 1, fill: "#888"}), + Plot.waffleY(data, Plot.pointer({x: "x", y: 1, fill: "fill"})) + ] + }); +} + export function waffleTip() { return Plot.plot({ color: {type: "sqrt", scheme: "spectral"}, From 83d0a4dd025646d9787f9ece9fb8d884a5c2904d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 29 Aug 2024 11:00:04 +0200 Subject: [PATCH 5/9] crazy logic to compute the waffles' centroids --- src/marks/waffle.js | 41 ++++++- test/output/wafflePointerFractional.svg | 136 ++++++++++++++++++++++++ test/plots/waffle.ts | 36 +++++++ 3 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 test/output/wafflePointerFractional.svg diff --git a/src/marks/waffle.js b/src/marks/waffle.js index d5df9b02bf..870e9d00dd 100644 --- a/src/marks/waffle.js +++ b/src/marks/waffle.js @@ -1,4 +1,4 @@ -import {extent, namespaces, polygonCentroid} from "d3"; +import {extent, namespaces} from "d3"; import {valueObject} from "../channel.js"; import {create} from "../context.js"; import {composeRender} from "../mark.js"; @@ -76,7 +76,7 @@ function waffleInitializer(y) { for (let i = 0; i < n; ++i) { P[i] = wafflePoints(round(Y1[i] / unit), round(Y2[i] / unit), multiple).map(transform); - const c = polygonCentroid(P[i]); + const c = P[i].pop(); X[i] = c[ix] + mx(i); Y[i] = c[iy] + y0; } @@ -192,6 +192,8 @@ function waffleRender(y) { // Waffles can also represent fractional intervals (e.g., 2.4–10.1). These // require additional corner cuts, so the implementation below generates a few // more points. +// +// The last point describes the centroid (used for pointing) function wafflePoints(i1, i2, columns) { if (i1 < 0 || i2 < 0) { const k = Math.ceil(-Math.min(i1, i2) / columns); // shift negative to positive @@ -220,10 +222,43 @@ function wafflePoints(i1, i2, columns) { : [ [Math.floor(i2 % columns), Math.ceil(i2 / columns)], [0, Math.ceil(i2 / columns)] - ]) + ]), + centroid(i1, i2, columns) ]; } +function singleRowCentroid(i, j, columns) { + const c = Math.floor(j) - Math.floor(i); + return c === 0 // Single cell + ? [Math.floor(i % columns) + 0.5, Math.floor(i / columns) + (((i + j) / 2) % 1)] + : c === 1 // Two incomplete cells, use the overlap if it is large enough, otherwise use the largest + ? (j % 1) - (i % 1) > 0.5 + ? [Math.ceil(i % columns), Math.floor(j / columns) + ((i % 1) + (j % 1)) / 2] + : j % 1 > 1 - (i % 1) + ? [Math.floor(j % columns) + 0.5, Math.floor(j / columns) + (j % 1) / 2] + : [Math.floor(i % columns) + 0.5, Math.floor(i / columns) + (1 + (i % 1)) / 2] + : // At least one full cell, take their midpoint + [ + Math.ceil(i % columns) + Math.ceil(Math.floor(j) - Math.ceil(i)) / 2, + Math.floor(i / columns) + (j >= 1 + i ? 0.5 : ((i + j) / 2) % 1) + ]; +} + +function centroid(i1, i2, columns) { + const r = Math.floor(i2 / columns) - Math.floor(i1 / columns); + return r === 0 // Single row + ? singleRowCentroid(i1, i2, columns) + : // Two incomplete rows, use the midpoint of their overlap if they do, otherwise use the largest + r === 1 + ? Math.floor(i2 % columns) > Math.ceil(i1 % columns) + ? [(Math.floor(i2 % columns) + Math.ceil(i1 % columns)) / 2, Math.floor(i2 / columns)] + : i2 % columns > columns - (i1 % columns) + ? singleRowCentroid(i2 - (i2 % columns), i2, columns) + : singleRowCentroid(i1, columns * Math.ceil(i1 / columns), columns) + : // At least one full row, take the midpoint of all the rows that include the middle + [columns / 2, (Math.round(i1 / columns) + Math.round(i2 / columns)) / 2]; +} + function maybeRound(round) { if (round === undefined || round === false) return Number; if (round === true) return Math.round; diff --git a/test/output/wafflePointerFractional.svg b/test/output/wafflePointerFractional.svg new file mode 100644 index 0000000000..fd12792d0c --- /dev/null +++ b/test/output/wafflePointerFractional.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.51 + 0.99 + 0.5 + 6 + 0.3 + 1.6 + 9.1 + 2 + 18 + 6 + 0.5 + 2.5 + 46 + 34 + 20 + 7 + 0.5 + 0.1 + 0 + 2.5 + 1 + 0.1 + 0.8 + + + \ No newline at end of file diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index bb5cf3a9e0..9d587756e9 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -1,5 +1,6 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; +import {svg} from "htl"; const demographics = d3.csvParse( `group,label,freq @@ -259,6 +260,41 @@ export function wafflePointer() { }); } +export function wafflePointerFractional() { + const values = [0.51, 0.99, 0.5, 6, 0.3, 1.6, 9.1, 2, 18, 6, 0.5, 2.5, 46, 34, 20, 7, 0.5, 0.1, 0, 2.5, 1, 0.1, 0.8]; + const multiple = 16; + return Plot.plot({ + axis: null, + y: {insetTop: 12}, + color: {scheme: "Dark2"}, + marks: [ + Plot.waffleY(values, { + x: null, + multiple, + fill: (d, i) => i % 7, + tip: true + }), + Plot.waffleY(values, { + x: null, + multiple, + // eslint-disable-next-line + render: (index, scales, values, dimensions, context, next) => { + const format = (d: number) => +d.toFixed(2); + const labels = (values.channels.y1 as any).source.value; + return svg`${Array.from( + index, + (i) => + svg`${format(labels[i])}` + )}`; + } + }) + ] + }); +} export function waffleTip() { return Plot.plot({ color: {type: "sqrt", scheme: "spectral"}, From f27e32163c6f93e00f4b7de3912799f0aecc5550 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 9 Sep 2024 08:30:00 -0700 Subject: [PATCH 6/9] remove single hint --- src/marks/tip.js | 2 -- src/marks/waffle.js | 13 ++----------- test/plots/waffle.ts | 5 +++-- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/marks/tip.js b/src/marks/tip.js index 718cab92f7..bfb9d04cb2 100644 --- a/src/marks/tip.js +++ b/src/marks/tip.js @@ -431,8 +431,6 @@ function* formatChannels(i, index, channels, scales, values) { function formatPair(formatValue, c1, c2, i) { return c2.hint?.length // e.g., stackY’s y1 and y2 ? `${formatValue(c2.value[i] - c1.value[i], i)}` - : c2.hint?.single // e.g., waffleY’s y1 and y2 - ? `${formatValue(c2.value[i], i)}` : `${formatValue(c1.value[i], i)}–${formatValue(c2.value[i], i)}`; } diff --git a/src/marks/waffle.js b/src/marks/waffle.js index 870e9d00dd..2b8d07d6f5 100644 --- a/src/marks/waffle.js +++ b/src/marks/waffle.js @@ -86,21 +86,12 @@ function waffleInitializer(y) { this.x0 = x0; this.y0 = y0; - // Restore the tip value for y. - const source = channels[`${y}2`].hint?.length - ? { - ...channels[`${y}1`], - value: Array.from(channels[`${y}1`].value, (d, i) => channels[`${y}2`].value[i] - d), - hint: {single: true} - } - : null; - return { channels: { polygon: {value: P, source: null}, [y === "y" ? "x" : "y"]: {value: X, scale: null, source: null}, - [`${y}1`]: {value: Y, scale: null, source}, - [`${y}2`]: {value: Y, scale: null, source} + [`${y}1`]: {value: Y, scale: null, source: channels[`${y}1`]}, + [`${y}2`]: {value: Y, scale: null, source: channels[`${y}2`]} } }; }; diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index 9d587756e9..c30f69cc27 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -280,7 +280,8 @@ export function wafflePointerFractional() { // eslint-disable-next-line render: (index, scales, values, dimensions, context, next) => { const format = (d: number) => +d.toFixed(2); - const labels = (values.channels.y1 as any).source.value; + const y1 = (values.channels.y1 as any).source.value; + const y2 = (values.channels.y2 as any).source.value; return svg`${Array.from( index, (i) => @@ -288,7 +289,7 @@ export function wafflePointerFractional() { dy: "0.38em", x: values.x[i], y: values.y1[i] - }}>${format(labels[i])}` + }}>${format(y2[i] - y1[i])}` )}`; } }) From c7ef7263154fabe9658a6be661ef520ac9e6cbde Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 9 Sep 2024 08:38:53 -0700 Subject: [PATCH 7/9] prettier --- src/marks/waffle.js | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/marks/waffle.js b/src/marks/waffle.js index 2b8d07d6f5..c6eb4acc30 100644 --- a/src/marks/waffle.js +++ b/src/marks/waffle.js @@ -218,23 +218,6 @@ function wafflePoints(i1, i2, columns) { ]; } -function singleRowCentroid(i, j, columns) { - const c = Math.floor(j) - Math.floor(i); - return c === 0 // Single cell - ? [Math.floor(i % columns) + 0.5, Math.floor(i / columns) + (((i + j) / 2) % 1)] - : c === 1 // Two incomplete cells, use the overlap if it is large enough, otherwise use the largest - ? (j % 1) - (i % 1) > 0.5 - ? [Math.ceil(i % columns), Math.floor(j / columns) + ((i % 1) + (j % 1)) / 2] - : j % 1 > 1 - (i % 1) - ? [Math.floor(j % columns) + 0.5, Math.floor(j / columns) + (j % 1) / 2] - : [Math.floor(i % columns) + 0.5, Math.floor(i / columns) + (1 + (i % 1)) / 2] - : // At least one full cell, take their midpoint - [ - Math.ceil(i % columns) + Math.ceil(Math.floor(j) - Math.ceil(i)) / 2, - Math.floor(i / columns) + (j >= 1 + i ? 0.5 : ((i + j) / 2) % 1) - ]; -} - function centroid(i1, i2, columns) { const r = Math.floor(i2 / columns) - Math.floor(i1 / columns); return r === 0 // Single row @@ -250,6 +233,23 @@ function centroid(i1, i2, columns) { [columns / 2, (Math.round(i1 / columns) + Math.round(i2 / columns)) / 2]; } +function singleRowCentroid(i1, i2, columns) { + const c = Math.floor(i2) - Math.floor(i1); + return c === 0 // Single cell + ? [Math.floor(i1 % columns) + 0.5, Math.floor(i1 / columns) + (((i1 + i2) / 2) % 1)] + : c === 1 // Two incomplete cells, use the overlap if it is large enough, otherwise use the largest + ? (i2 % 1) - (i1 % 1) > 0.5 + ? [Math.ceil(i1 % columns), Math.floor(i2 / columns) + ((i1 % 1) + (i2 % 1)) / 2] + : i2 % 1 > 1 - (i1 % 1) + ? [Math.floor(i2 % columns) + 0.5, Math.floor(i2 / columns) + (i2 % 1) / 2] + : [Math.floor(i1 % columns) + 0.5, Math.floor(i1 / columns) + (1 + (i1 % 1)) / 2] + : // At least one full cell, take their midpoint + [ + Math.ceil(i1 % columns) + Math.ceil(Math.floor(i2) - Math.ceil(i1)) / 2, + Math.floor(i1 / columns) + (i2 >= 1 + i1 ? 0.5 : ((i1 + i2) / 2) % 1) + ]; +} + function maybeRound(round) { if (round === undefined || round === false) return Number; if (round === true) return Math.round; From 76a9826c72141d9f7e1629e45f02d4147c5a2b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Sat, 19 Oct 2024 18:11:05 +0200 Subject: [PATCH 8/9] fix waffle stroke (when constant) --- src/marks/waffle.js | 2 +- test/output/waffleStroke.svg | 24 +- test/output/waffleStrokeMixed.svg | 12 +- test/output/waffleStrokeNegative.svg | 12 +- test/output/waffleStrokePositive.svg | 12 +- test/output/waffleStrokeWidth.svg | 346 +++++++++++++++++++++++++ test/output/waffleStrokeWidthConst.svg | 346 +++++++++++++++++++++++++ test/plots/waffle.ts | 14 + 8 files changed, 737 insertions(+), 31 deletions(-) create mode 100644 test/output/waffleStrokeWidth.svg create mode 100644 test/output/waffleStrokeWidthConst.svg diff --git a/src/marks/waffle.js b/src/marks/waffle.js index 883a4a1b14..8df8cd6fc5 100644 --- a/src/marks/waffle.js +++ b/src/marks/waffle.js @@ -104,7 +104,7 @@ function waffleRender(y) { .join("L")}Z` ) .attr("fill", (i) => `url(#${patternId}-${i})`) - .attr("stroke", this.stroke == null ? null : (i) => `url(#${patternId}-${i})`) + .attr("stroke", this.stroke == null ? null : "none") .call(applyChannelStyles, this, {ariaLabel, href, title}) ) .node(); diff --git a/test/output/waffleStroke.svg b/test/output/waffleStroke.svg index ff8bc9a54a..a5e317c71a 100644 --- a/test/output/waffleStroke.svg +++ b/test/output/waffleStroke.svg @@ -66,12 +66,12 @@ - - - - - - + + + + + + @@ -92,11 +92,11 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/test/output/waffleStrokeMixed.svg b/test/output/waffleStrokeMixed.svg index fceae2f5af..a8fe5022d4 100644 --- a/test/output/waffleStrokeMixed.svg +++ b/test/output/waffleStrokeMixed.svg @@ -70,12 +70,12 @@ - - - - - - + + + + + + diff --git a/test/output/waffleStrokeNegative.svg b/test/output/waffleStrokeNegative.svg index 8e03565a10..263cc64772 100644 --- a/test/output/waffleStrokeNegative.svg +++ b/test/output/waffleStrokeNegative.svg @@ -100,12 +100,12 @@ - - - - - - + + + + + + diff --git a/test/output/waffleStrokePositive.svg b/test/output/waffleStrokePositive.svg index cb2ab9cd2b..7e6499571f 100644 --- a/test/output/waffleStrokePositive.svg +++ b/test/output/waffleStrokePositive.svg @@ -100,12 +100,12 @@ - - - - - - + + + + + + diff --git a/test/output/waffleStrokeWidth.svg b/test/output/waffleStrokeWidth.svg new file mode 100644 index 0000000000..73458ca22b --- /dev/null +++ b/test/output/waffleStrokeWidth.svg @@ -0,0 +1,346 @@ + + + + + 0 + 10 + 20 + 30 + 40 + 50 + 60 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/output/waffleStrokeWidthConst.svg b/test/output/waffleStrokeWidthConst.svg new file mode 100644 index 0000000000..9d6b885897 --- /dev/null +++ b/test/output/waffleStrokeWidthConst.svg @@ -0,0 +1,346 @@ + + + + + 0 + 10 + 20 + 30 + 40 + 50 + 60 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index fdfa98a025..90122b5885 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -264,3 +264,17 @@ export function waffleHref() { ] }); } + +export function waffleStrokeWidth() { + return Plot.plot({ + inset: 10, + marks: [Plot.waffleY({length: 77}, {y: 1, stroke: (d, i) => i % 7, gap: 15, strokeWidth: 15, strokeOpacity: 0.8})] + }); +} + +export function waffleStrokeWidthConst() { + return Plot.plot({ + inset: 10, + marks: [Plot.waffleY({length: 77}, {y: 1, stroke: "black", gap: 15, strokeWidth: 15, strokeOpacity: 0.8})] + }); +} From 1533c8675a1f553ffad2902ace73c2611850e960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Wed, 30 Oct 2024 16:57:02 +0100 Subject: [PATCH 9/9] unit = auto closes #2214 --- src/marks/waffle.d.ts | 8 +- src/marks/waffle.js | 29 +- test/output/waffleAutoUnit.html | 4241 +++++++++++++++++++++++++++++++ test/plots/waffle.ts | 10 +- 4 files changed, 4277 insertions(+), 11 deletions(-) create mode 100644 test/output/waffleAutoUnit.html diff --git a/src/marks/waffle.d.ts b/src/marks/waffle.d.ts index 08354a5724..0748b7fb1e 100644 --- a/src/marks/waffle.d.ts +++ b/src/marks/waffle.d.ts @@ -5,8 +5,12 @@ import type {BarXOptions, BarYOptions} from "./bar.js"; interface WaffleOptions { /** The number of cells per row or column; defaults to undefined for automatic. */ multiple?: number; - /** The quantity each cell represents; defaults to 1. */ - unit?: number; + /** + * The quantity each cell represents; defaults to "auto", which defaults to 1 + * unless this makes the cell size unreasonable — in which case it adopts a + * suitable power of 1,000. + */ + unit?: number | "auto"; /** The gap in pixels between cells; defaults to 1. */ gap?: number; /** If true, round to integers to avoid partial cells. */ diff --git a/src/marks/waffle.js b/src/marks/waffle.js index bc687ae5a2..91c1f5176d 100644 --- a/src/marks/waffle.js +++ b/src/marks/waffle.js @@ -2,7 +2,7 @@ import {extent, namespaces} from "d3"; import {valueObject} from "../channel.js"; import {create} from "../context.js"; import {composeRender} from "../mark.js"; -import {hasXY, identity, indexOf} from "../options.js"; +import {hasXY, identity, indexOf, keyword} from "../options.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, getPatternId} from "../style.js"; import {template} from "../template.js"; import {initializer} from "../transforms/basic.js"; @@ -16,10 +16,10 @@ const waffleDefaults = { }; export class WaffleX extends BarX { - constructor(data, {unit = 1, gap = 1, round, render, multiple, ...options} = {}) { + constructor(data, {unit, gap = 1, round, render, multiple, ...options} = {}) { options = initializer({...options, render: composeRender(render, waffleRender("x"))}, waffleInitializer("x")); super(data, options, waffleDefaults); - this.unit = Math.max(0, unit); + this.unit = maybeUnit(unit); this.gap = +gap; this.round = maybeRound(round); this.multiple = maybeMultiple(multiple); @@ -27,10 +27,10 @@ export class WaffleX extends BarX { } export class WaffleY extends BarY { - constructor(data, {unit = 1, gap = 1, round, render, multiple, ...options} = {}) { + constructor(data, {unit, gap = 1, round, render, multiple, ...options} = {}) { options = initializer({...options, render: composeRender(render, waffleRender("y"))}, waffleInitializer("y")); super(data, options, waffleDefaults); - this.unit = Math.max(0, unit); + this.unit = maybeUnit(unit); this.gap = +gap; this.round = maybeRound(round); this.multiple = maybeMultiple(multiple); @@ -39,8 +39,7 @@ export class WaffleY extends BarY { function waffleInitializer(y) { return function (data, facets, channels, scales, dimensions) { - const {round, unit} = this; - + const {round} = this; const values = valueObject(channels, scales); const Y1 = values.channels[`${y}1`].value; const Y2 = values.channels[`${y}2`].value; @@ -49,8 +48,18 @@ function waffleInitializer(y) { const barwidth = this[y === "y" ? "_width" : "_height"](scales, values, dimensions); const barx = this[y === "y" ? "_x" : "_y"](scales, values, dimensions); + // Auto unit: if the scale of a unit makes it so small that it is invisible, + // or conversely insanely large, adopt a different power of 10**3. + const p = scaleof(scales.scales[y]); // pixel length per unit of 1 + let {unit} = this; + if (unit === "auto") { + const area = barwidth * p; // pixel area per unit of 1 + if (area < 5 || area > 5e4) unit = 1000 ** Math.ceil((1 - Math.log10(area)) / 3); + else unit = 1; + } + // The length of a unit along y in pixels. - const scale = unit * scaleof(scales.scales[y]); + const scale = unit * p; // The number of cells on each row (or column) of the waffle. const {multiple = Math.max(1, Math.floor(Math.sqrt(barwidth / scale)))} = this; @@ -281,3 +290,7 @@ export function waffleY(data, {tip, ...options} = {}) { if (!hasXY(options)) options = {...options, x: indexOf, y2: identity}; return new WaffleY(data, {tip, ...maybeStackY(maybeIntervalY(maybeIdentityY(options)))}); } + +function maybeUnit(unit = "auto") { + return typeof unit === "number" ? Math.max(0, unit) : keyword(unit, "unit", ["auto"]); +} diff --git a/test/output/waffleAutoUnit.html b/test/output/waffleAutoUnit.html new file mode 100644 index 0000000000..7f1e62163c --- /dev/null +++ b/test/output/waffleAutoUnit.html @@ -0,0 +1,4241 @@ + + + + + 0 + 1 + + + + 0.0n + 0.5n + 1.0n + 1.5n + 2.0n + 2.5n + 3.0n + 3.5n + 4.0n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 1n + 2n + 3n + 4n + 5n + 6n + 7n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 2n + 4n + 6n + 8n + 10n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 2n + 4n + 6n + 8n + 10n + 12n + 14n + 16n + 18n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 5n + 10n + 15n + 20n + 25n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 5n + 10n + 15n + 20n + 25n + 30n + 35n + 40n + 45n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 10n + 20n + 30n + 40n + 50n + 60n + 70n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 20n + 40n + 60n + 80n + 100n + 120n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 20n + 40n + 60n + 80n + 100n + 120n + 140n + 160n + 180n + 200n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 50n + 100n + 150n + 200n + 250n + 300n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 100n + 200n + 300n + 400n + 500n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0n + 100n + 200n + 300n + 400n + 500n + 600n + 700n + 800n + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0µ + 0.2µ + 0.4µ + 0.6µ + 0.8µ + 1.0µ + 1.2µ + 1.4µ + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0µ + 0.2µ + 0.4µ + 0.6µ + 0.8µ + 1.0µ + 1.2µ + 1.4µ + 1.6µ + 1.8µ + 2.0µ + 2.2µ + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0µ + 0.5µ + 1.0µ + 1.5µ + 2.0µ + 2.5µ + 3.0µ + 3.5µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + 10µ + 12µ + 14µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + + 10µ + 15µ + 20µ + 25µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + + 10µ + 15µ + 20µ + 25µ + 30µ + 35µ + 40µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + 10µ + 20µ + 30µ + 40µ + 50µ + 60µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + 20µ + 40µ + 60µ + 80µ + 100µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + 20µ + 40µ + 60µ + 80µ + 100µ + 120µ + 140µ + 160µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + 50µ + 100µ + 150µ + 200µ + 250µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + 50µ + 100µ + 150µ + 200µ + 250µ + 300µ + 350µ + 400µ + 450µ + + + + + + + + + + + + + + + + 0 + 1 + + + + + 100µ + 200µ + 300µ + 400µ + 500µ + 600µ + 700µ + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0m + 0.2m + 0.4m + 0.6m + 0.8m + 1.0m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0m + 0.2m + 0.4m + 0.6m + 0.8m + 1.0m + 1.2m + 1.4m + 1.6m + 1.8m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0m + 0.5m + 1.0m + 1.5m + 2.0m + 2.5m + 3.0m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0m + 0.5m + 1.0m + 1.5m + 2.0m + 2.5m + 3.0m + 3.5m + 4.0m + 4.5m + 5.0m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 1m + 2m + 3m + 4m + 5m + 6m + 7m + 8m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 2m + 4m + 6m + 8m + 10m + 12m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 2m + 4m + 6m + 8m + 10m + 12m + 14m + 16m + 18m + 20m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 5m + 10m + 15m + 20m + 25m + 30m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 10m + 20m + 30m + 40m + 50m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 10m + 20m + 30m + 40m + 50m + 60m + 70m + 80m + 90m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 20m + 40m + 60m + 80m + 100m + 120m + 140m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 50m + 100m + 150m + 200m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 50m + 100m + 150m + 200m + 250m + 300m + 350m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0m + 100m + 200m + 300m + 400m + 500m + 600m + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0 + 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 0.6 + 0.7 + 0.8 + 0.9 + 1.0 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0 + 0.2 + 0.4 + 0.6 + 0.8 + 1.0 + 1.2 + 1.4 + 1.6 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0 + 0.5 + 1.0 + 1.5 + 2.0 + 2.5 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0 + 0.5 + 1.0 + 1.5 + 2.0 + 2.5 + 3.0 + 3.5 + 4.0 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 2 + 4 + 6 + 8 + 10 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 5 + 10 + 15 + 20 + 25 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 5 + 10 + 15 + 20 + 25 + 30 + 35 + 40 + 45 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 10 + 20 + 30 + 40 + 50 + 60 + 70 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 20 + 40 + 60 + 80 + 100 + 120 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 20 + 40 + 60 + 80 + 100 + 120 + 140 + 160 + 180 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 50 + 100 + 150 + 200 + 250 + 300 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 100 + 200 + 300 + 400 + 500 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0 + 100 + 200 + 300 + 400 + 500 + 600 + 700 + 800 + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0k + 0.2k + 0.4k + 0.6k + 0.8k + 1.0k + 1.2k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0k + 0.2k + 0.4k + 0.6k + 0.8k + 1.0k + 1.2k + 1.4k + 1.6k + 1.8k + 2.0k + 2.2k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0k + 0.5k + 1.0k + 1.5k + 2.0k + 2.5k + 3.0k + 3.5k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 1k + 2k + 3k + 4k + 5k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 1k + 2k + 3k + 4k + 5k + 6k + 7k + 8k + 9k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 2k + 4k + 6k + 8k + 10k + 12k + 14k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 5k + 10k + 15k + 20k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 5k + 10k + 15k + 20k + 25k + 30k + 35k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 10k + 20k + 30k + 40k + 50k + 60k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 20k + 40k + 60k + 80k + 100k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 20k + 40k + 60k + 80k + 100k + 120k + 140k + 160k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 50k + 100k + 150k + 200k + 250k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 50k + 100k + 150k + 200k + 250k + 300k + 350k + 400k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0k + 100k + 200k + 300k + 400k + 500k + 600k + 700k + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0M + 0.2M + 0.4M + 0.6M + 0.8M + 1.0M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0M + 0.2M + 0.4M + 0.6M + 0.8M + 1.0M + 1.2M + 1.4M + 1.6M + 1.8M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0M + 0.5M + 1.0M + 1.5M + 2.0M + 2.5M + 3.0M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0.0M + 0.5M + 1.0M + 1.5M + 2.0M + 2.5M + 3.0M + 3.5M + 4.0M + 4.5M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0M + 1M + 2M + 3M + 4M + 5M + 6M + 7M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0M + 2M + 4M + 6M + 8M + 10M + 12M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0M + 2M + 4M + 6M + 8M + 10M + 12M + 14M + 16M + 18M + 20M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0M + 5M + 10M + 15M + 20M + 25M + 30M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0M + 10M + 20M + 30M + 40M + 50M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0M + 10M + 20M + 30M + 40M + 50M + 60M + 70M + 80M + + + + + + + + + + + + + + + + 0 + 1 + + + + 0M + 20M + 40M + 60M + 80M + 100M + 120M + 140M + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index 7d406ce5cc..136403e32a 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -1,6 +1,6 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; -import {svg} from "htl"; +import {svg, html} from "htl"; const demographics = d3.csvParse( `group,label,freq @@ -430,3 +430,11 @@ export function waffleShapes() { ] }); } + +export function waffleAutoUnit() { + const random = d3.randomLcg(42); + return html`${d3 + .range(-40, 40) + .map((i) => 1.618 ** i) + .map((k) => Plot.waffleX([k, random() * k]).plot({x: {tickFormat: "s"}}))}`; +}