diff --git a/src/marks/waffle.d.ts b/src/marks/waffle.d.ts index 9a7d847337..181db85ee0 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 97a9a6fead..17551c05a8 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; @@ -301,3 +311,11 @@ function waffleTip(tip) { ? {...tip, maxRadius: Infinity} : undefined; } + +function maybeUnit(unit = "auto") { + if (typeof unit === "number") { + if (unit <= 0 || !isFinite(unit)) throw new Error(`invalid unit: ${unit}`); + return unit; + } + return keyword(unit, "unit", ["auto"]); +} diff --git a/test/output/waffleAutoUnit.svg b/test/output/waffleAutoUnit.svg new file mode 100644 index 0000000000..d6314869c0 --- /dev/null +++ b/test/output/waffleAutoUnit.svg @@ -0,0 +1,79 @@ + + + + + 0 + 200,000 + 400,000 + 600,000 + 800,000 + 1,000,000 + 1,200,000 + 1,400,000 + 1,600,000 + 1,800,000 + 2,000,000 + 2,200,000 + 2,400,000 + 2,600,000 + 2,800,000 + 3,000,000 + + + + a + b + c + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/waffle.ts b/test/plots/waffle.ts index c1fd9803a2..b3d37f165d 100644 --- a/test/plots/waffle.ts +++ b/test/plots/waffle.ts @@ -431,3 +431,10 @@ test(function waffleShapes() { ] }); }); + +test(function waffleAutoUnit() { + return Plot.plot({ + marginLeft: 60, + marks: [Plot.waffleY([1e6, 2e6, 3e6], {x: ["a", "b", "c"], fill: ["a", "b", "c"]}), Plot.ruleY([0])] + }); +});