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 1e848bcc7e..af17ef7e60 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, isObject} from "../options.js"; +import {hasXY, identity, indexOf, isObject, keyword} from "../options.js"; import {applyChannelStyles, applyDirectStyles, applyIndirectStyles, getPatternId} from "../style.js"; import {template} from "../template.js"; import {initializer} from "../transforms/basic.js"; @@ -16,9 +16,9 @@ const waffleDefaults = { }; export class WaffleX extends BarX { - constructor(data, {unit = 1, gap = 1, round, multiple, ...options} = {}) { + constructor(data, {unit, gap = 1, round, multiple, ...options} = {}) { super(data, wafflePolygon("x", options), waffleDefaults); - this.unit = Math.max(0, unit); + this.unit = maybeUnit(unit); this.gap = +gap; this.round = maybeRound(round); this.multiple = maybeMultiple(multiple); @@ -26,9 +26,9 @@ export class WaffleX extends BarX { } export class WaffleY extends BarY { - constructor(data, {unit = 1, gap = 1, round, multiple, ...options} = {}) { + constructor(data, {unit, gap = 1, round, multiple, ...options} = {}) { super(data, wafflePolygon("y", options), waffleDefaults); - this.unit = Math.max(0, unit); + this.unit = maybeUnit(unit); this.gap = +gap; this.round = maybeRound(round); this.multiple = maybeMultiple(multiple); @@ -40,7 +40,7 @@ function wafflePolygon(y, options) { const y1 = `${y}1`; const y2 = `${y}2`; return initializer(waffleRender(options), function (data, facets, channels, scales, dimensions) { - const {round, unit} = this; + const {round} = this; const Y1 = channels[y1].value; const Y2 = channels[y2].value; @@ -49,8 +49,18 @@ function wafflePolygon(y, options) { const barwidth = this[y === "y" ? "_width" : "_height"](scales, xy, dimensions); const barx = this[y === "y" ? "_x" : "_y"](scales, xy, 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; @@ -263,6 +273,10 @@ function maybeRound(round) { return round; } +function maybeUnit(unit = "auto") { + return typeof unit === "number" ? Math.max(0, unit) : keyword(unit, "unit", ["auto"]); +} + function maybeMultiple(multiple) { return multiple === undefined ? undefined : Math.max(1, Math.floor(multiple)); } 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 ead9a1bf96..b3fc550e35 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"}}))}`; +}