chart-factory 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/chart-factory-table.js +1400 -0
- package/dist/chart-factory.css +2368 -0
- package/dist/chart-factory.js +11398 -0
- package/dist/chart-factory.min.js +7 -0
- package/docs/API.md +1815 -0
- package/docs/chart-guide.md +872 -0
- package/docs/color-guide.md +134 -0
- package/docs/conformance-matrix.md +864 -0
- package/docs/theming.md +107 -0
- package/index.d.ts +10453 -0
- package/package.json +83 -0
- package/src/core/base.css +508 -0
- package/src/core/builder-metadata.generated.mjs +406 -0
- package/src/core/d3-base.js +249 -0
- package/src/core/d3-chart-factory.js +18547 -0
- package/src/core/d3-data.js +0 -0
- package/src/core/d3-suggest.js +565 -0
- package/src/core/example-nav.css +62 -0
- package/src/core/example-nav.js +249 -0
- package/src/core/palettes.js +310 -0
- package/src/core/theme-toggle.js +126 -0
- package/src/core/tokens.css +792 -0
- package/src/table/d3-table.js +1570 -0
- package/src/table/table.css +899 -0
- package/src/table/tokens.css +157 -0
package/docs/API.md
ADDED
|
@@ -0,0 +1,1815 @@
|
|
|
1
|
+
# ChartFactory API Reference
|
|
2
|
+
|
|
3
|
+
Complete per-builder configuration reference for the chart-factory chart library — a token-driven D3.js (v7+) component library exposing `ChartFactory.{Area, Line, Slope, Bar, Scatter, Dot, Table}` builders. Every chart is **responsive by default**: it fills its container, re-renders on container resize, and returns a live facade object. Setup, tokens, and examples: see the [README](../README.md) and [CLAUDE.md](../CLAUDE.md) (the "Universal Config Conventions" and "Responsive Layout" sections there summarize the shared behavior documented below).
|
|
4
|
+
|
|
5
|
+
Source of truth: `src/core/d3-chart-factory.js` (SVG charts) and `src/table/d3-table.js` (tables). Each builder is called as `ChartFactory.<Family>.<createX>(selector, config)`.
|
|
6
|
+
|
|
7
|
+
Per-builder support for every universal option — including caveats and the defect backlog — is tracked in the generated [conformance matrix](conformance-matrix.md) (source: `metadata/*.mjs`, regenerate with `npm run conformance`).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Universal options
|
|
12
|
+
|
|
13
|
+
These are normalized centrally (responsive wrapper + shared helpers) and work on every SVG builder unless a builder's section says otherwise.
|
|
14
|
+
|
|
15
|
+
| Key | Default | Description |
|
|
16
|
+
|-----|---------|-------------|
|
|
17
|
+
| `data` | `[]` | Array of rows (or series objects, per builder). Empty/missing data logs a `console.warn` and renders blank. |
|
|
18
|
+
| `size` | auto | Density tier: `'mini'` (≤340px container), `'full'` (≤620px), `'wide'` (>620px). Sets font size, `axisTicks` (4/6/8), `pointRadius` (3/4/4), preset margins. Pass explicitly to pin a tier. A fourth tier, `'instagram'`, is **opt-in only** (never auto-selected): poster-scale geometry (936×660, fat margins, `axisTicks` 5, `pointRadius` 10) for 1080px social-feed exports viewed at phone size. Pair it with `<html data-size="instagram">`, which scales the text/stroke/marker/annotation tokens to match (`tokens.css`); colors are untouched so it composes with `data-theme="dark"`, and like dark mode a runtime flip needs a re-render (`ChartFactory.rerenderAll()`). |
|
|
19
|
+
| `width` | measured container width | Passing a **number opts out of responsive rendering** (fixed-size legacy path, no ResizeObserver). |
|
|
20
|
+
| `height` | `width / aspectRatio` | A number pins the height (width stays fluid). Ignored by intrinsic-height row charts (horizontal Bar, Dot, Gantt) which compute height from row count. |
|
|
21
|
+
| `maxHeight` | — | Caps the aspect-derived height: the chart grows proportionally until it hits this ceiling, then keeps widening at a fixed height. Use for wide timelines you don't want to get tall. (Distinct from `height`, which is fixed at every width.) |
|
|
22
|
+
| `aspectRatio` | 1.72 (slope 1.375, bump 2.0, marginal scatter 1.25) | Width/height ratio used to derive height from measured width. |
|
|
23
|
+
| `responsive` | `true` | `false` = fixed-size render at preset/`width` size. (`Dot.createUnitHistogram` is always non-responsive.) |
|
|
24
|
+
| `margin` | preset (`full`: `{top:20, right:30, bottom:40, left:60}`) | Plot margins. **Ignored by builders that auto-measure label margins** (horizontal bars, dot rows, slope, bump, gantt). |
|
|
25
|
+
| `axisTicks` | 4 / 6 / 8 by tier | Tick count for both axes on grid charts. Explicit `0` draws no ticks (domain nicing keeps its default granularity). |
|
|
26
|
+
| `pointRadius` | 3 / 4 by tier | Data-point dot radius on line/area charts. Explicit `0` hides the dots. |
|
|
27
|
+
| `xFormat` / `yFormat` | adaptive (time axes) or raw | Axis tick formatters. `xFormat` always wins over the automatic time format — and also opts that axis out of the automatic label de-crowding below. |
|
|
28
|
+
| *(x-label de-crowding)* | automatic | After every render, x-axis tick labels that would **collide at the rendered width** are fixed automatically: date labels shorten a tier at a time (`April` → `Apr` → `A`, `2024` → `’24`), then — if still colliding, or on numeric axes — every other label is hidden (the grounded left anchor stays; tick marks remain). Band/categorical axes, rotated labels, and axes with an explicit `xFormat`/`xTickValues` are left alone. |
|
|
29
|
+
| `showXAxis` / `showYAxis` | `true` | `false` removes that axis (ticks, labels, and axis-like label columns on row charts). Margins are **not** reclaimed — pair with an explicit `margin` to recover the space. Per-builder support in the [conformance matrix](conformance-matrix.md). |
|
|
30
|
+
| `showGridX` / `showGridY` | builder default | `false` removes vertical / horizontal gridlines. `na` where the builder draws no such grid. |
|
|
31
|
+
| `showAxisLabels` | `true` | `false` removes axis tick-label text; tick/domain lines and `xLabel`/`yLabel` axis titles stay. |
|
|
32
|
+
| `xTickValues` / `yTickValues` | automatic | Explicit tick values for a continuous axis (overrides `axisTicks` count; values outside the domain are dropped; date-ish values coerced on time axes). `na` on band/categorical axes. |
|
|
33
|
+
| `xDomain` / `xMin` / `xMax` | data extent | X domain override; `[lo, hi]` and min/max spellings are bidirectional aliases. |
|
|
34
|
+
| `allowNonZeroBaseline` | `false` | Bar builders whose marks encode length from a zero baseline (`Bar.createBasic` / `createInline` / `createLabelAbove` / `createProgress` / `createBullet` / `createStacked` / `createGrouped` / `createVertical` / `createVerticalStacked`) clamp an explicit value-axis domain back to include 0 and warn once — a truncated baseline misstates bar-length ratios. Pass `true` to keep the truncated domain deliberately. |
|
|
35
|
+
| `yDomain` / `yMin` / `yMax` | builder-specific mode | Y domain override. Default modes: `zero` `[0, max*1.1]`, `padded` extent ±10%, `ratio` `[min*0.9, max*1.1]`, `symmetric` `[-absMax, absMax]` (noted per builder). |
|
|
36
|
+
| `niceX` | `false` | Trend x axes span the EXACT data extent — the line/area touches both plot edges, with no dead space after the last point (no trailing-year gap on time axes, no day-100 overhang on a 90-day index axis; `groundedXTicks` still labels clean in-domain boundaries). Pass `niceX: true` to round both ends out to the next tick interval instead. Ignored when `xDomain` is given. **Trend x axes only** (Area/Line); Scatter builders use padded extents without `.nice()`, and row charts (Dot) apply their own documented extent rounding. |
|
|
37
|
+
| `xScaleType` / `yScaleType` | `'linear'` | `'log'` for a logarithmic axis. Supported on trend x (Area/Line, via `xScaleType`) and on `Scatter.createBasic` / `Scatter.createAnimated` (both axes). Log domains are forced positive; non-positive data is nudged up rather than producing NaN geometry. Pass `xFormat`/`yFormat` for readable tick labels (e.g. `d3.format('~s')`). |
|
|
38
|
+
| `annotations` | — | Array of reference lines / bands / callouts drawn with the chart's live scales and **re-drawn on every resize** (see below). |
|
|
39
|
+
| `extraLayers` | — | Array of extra mark series — `points`, `line`, or `bars` — drawn with the chart's live scales, optionally from **their own dataset**, clipped to the plot and re-drawn on every resize (see Extra layers below). The demand-backed slice of a composition grammar: dots behind a trend line, bar+line combos. Not a general layer grammar — no second axes. |
|
|
40
|
+
| `onRender` | — | `(chart) => void` fired after the initial render and every resize/`setConfig` re-render. `chart` is the live render result (`svg` inner group, `x`/`y` scales, `width`/`height`). Use for custom overlays that must survive re-renders. |
|
|
41
|
+
| `title` / `subtitle` | — | Chart-shell heading rendered by the wrapper above the svg (15px bold / 11px muted via the `--shell-*` tokens). Re-rendered with the chart, so theme flips re-theme it. |
|
|
42
|
+
| `footer` | — | Shell footer strip below the chart: a string (left-aligned) or `{left, right}`. Styled like the showcase card footers (xs, faint, uppercase, top rule). |
|
|
43
|
+
| `story` | — | Narrative "story of the chart" module rendered below the svg, **above the footer** when both are present. A string is shorthand for `{ html, variant: 'panel' }`; full form `{ html, variant, label?, icon?, accent? }`. `html` is trusted markup (same trust model as `tooltipContent`). Variants (`div.chart-story.chart-story--<variant>`): `'panel'` (default) — soft accent-tinted panel with an accent hairline; optional `icon` (inline-SVG string) then a bold accent-colored `label` lead the prose inline. `'bubble'` — container-background card with a border, soft shadow, and a small tail pointing up at the chart; `label` renders as a small uppercase overline (icon leads it). `'band'` — full-width dark band with light text; `label` as a lighter-tint overline, and `<b>`/`<strong>` inside the html render in the accent color. **The band spans the chart container, not any outer card the consuming page might have.** `accent` overrides the `--story-accent` token (default `var(--chart-primary)`) for the tint/label/bold emphasis; all colors flow through the `--story-*` tokens, so the module re-themes with the shell on re-render. No default `label` — nothing renders unless provided. |
|
|
44
|
+
| `legend` | — | Shell legend below the chart through the real `createLegend`. Pass `{items, colors}` explicitly (var() token strings re-theme via CSS), or `true` to derive from `keys`/`labels`/`colors` config on stacked/grouped-style builders (defaults to the `--area-color-*` palette as var() strings). Add `interactive: true` to turn it into a series highlighter (hover previews, click isolates); drivable via `chart.highlightSeries(ordinal \| null)`. Works on builders emitting the `data-series` marker (multi-line, stacked/streamgraph areas, stacked/grouped/vertical bars, dot). |
|
|
45
|
+
| `featured` | — | `true` switches the shell title/subtitle from the 15/11 card tier to the 20/14 featured tier (zoom modals, standalone pages). |
|
|
46
|
+
| `state` | — | `'loading' \| 'empty' \| 'error'` renders a built-in state instead of the chart, in the chart's exact footprint (zero layout jump when data lands). Loading shows a family-aware skeleton — undulating trend (line/area), breathing columns (vertical bars), breathing rows (horizontal bars/dot), quiet dots elsewhere — breathing at a constant 5s cadence with a straight shimmer band clipped to the shapes. `data: []` auto-renders `'empty'`. `setData(rows)` with real rows implicitly ends `'loading'` (`fetch().then(chart.setData)` just works); `'error'` is always explicit. |
|
|
47
|
+
| `emptyMessage` | `'No data'` | Primary text of the empty state. |
|
|
48
|
+
| `errorMessage` | `'Couldn't load this chart'` | Primary text of the error state. |
|
|
49
|
+
| `showTooltip` | `true` | `false` disables the built-in tooltip. |
|
|
50
|
+
| `tooltipContent` | — | `(d) => html` — full override of the tooltip HTML. |
|
|
51
|
+
| `tooltipValueFormat` | falls back to `valueFormat`, then the standard format | Tooltip value formatter. Alias: `tooltipFormat`. Standard fallback: integers `toLocaleString()`, floats `toFixed(1)`. **Applies where the tooltip has a value row**; coordinate-style tooltips (scatter x/y readouts: basic, logos, animated, comet, trajectory, quadrant, diagonal, contour, the marginal variants, KDE) format through `xFormat`/`yFormat`, and gantt ranges through `dateFormat`. The formatter's **argument** varies by builder — see the Line charts intro. |
|
|
52
|
+
| `labelFormat` | x value | `(d) => string` — tooltip label row (usually the x value) on trend charts. |
|
|
53
|
+
| `valueFormat` | builder-specific | Value formatter (tooltip, and on label-drawing charts also the drawn labels). |
|
|
54
|
+
| `animate` | builder-specific | Alias: `animated`. The responsive wrapper injects `animate:false` on resize re-renders so charts don't re-animate. |
|
|
55
|
+
| `linkGroup` | — | `'name'` or `{ name, labels?, sharedX? }` — enroll the chart in a **linked-hover group** (see Linked charts below): hovering any member mirrors a crosshair guide onto every other member (with per-series dots + value labels where the builder provides a `linkPoints` probe), and members share one union x-domain. `labels: false` hides the value labels; `sharedX: false` (or an explicit `xDomain`) opts the chart out of domain sharing. Per-builder support in the [conformance matrix](conformance-matrix.md). |
|
|
56
|
+
| `brush` | — | `true` or `{ mode: 'highlight' \| 'zoom', onBrush }` — a **1-D range brush** on the plot area (see Brush-to-filter below): drag selects an x range, click clears, the selection survives re-renders and broadcasts through the chart's `linkGroup`. `onBrush(range \| null, chart)` is the app hook for filtering tables/KPIs. Facade `setBrush(range \| null)` drives it programmatically. Per-builder support in the [conformance matrix](conformance-matrix.md). |
|
|
57
|
+
| `diagnostics` | `true` | `false` disables config diagnostics for this chart. By default every `create*` call validates the config keys against the builder's conformance metadata and `console.warn`s on unknown options, universal options the builder doesn't support (per the [conformance matrix](conformance-matrix.md)), and malformed `xDomain`/`yDomain`. Wrong DATA keys are loud too: on covered builders (slope family, dot family, bar families, `Line.createMulti` series) a required data field that is undefined in every row `console.error`s naming the builder, the expected row shape, and the received keys. One warning per (builder, key) per page. |
|
|
58
|
+
|
|
59
|
+
**Explicit zeros are honored.** Numeric options where `0` is meaningful never
|
|
60
|
+
fall back on falsy input — `dotRadius`/`pointRadius: 0` (hide dots),
|
|
61
|
+
`axisTicks`/`xTicks`/`yTicks`/`tickCount: 0` (no ticks), `dotOpacity`/
|
|
62
|
+
`fillOpacity`/`bandOpacity`/`trailOpacity: 0` (invisible), `dotStrokeWidth`/
|
|
63
|
+
`lineWidth`/`trailWidth: 0` (no stroke), `animationDuration`/`staggerDelay`/
|
|
64
|
+
`binDelay: 0` (instant), `borderRadius: 0` (square corners), `dotGap`/
|
|
65
|
+
`rugLength: 0`. The same applies to design tokens read via `getCSSVarNumber`:
|
|
66
|
+
a token explicitly set to `0` is a real value, not "unset".
|
|
67
|
+
|
|
68
|
+
**Tooltip shell.** Built-in tooltips render through `TooltipManager.rows(rows)` with a fixed visual hierarchy — title > values > labels > notes. Row shapes (also available to custom charts and `tooltipContent` authors):
|
|
69
|
+
|
|
70
|
+
| Row | Renders |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `{ title, color? }` | Strong header (semibold, `--tooltip-title-font-size` 15px, black; optional swatch) |
|
|
73
|
+
| `{ label, value, color? }` | Metric row — muted label left, semibold tabular-numeral value right |
|
|
74
|
+
| `{ value, color? }` | Bare emphasized value |
|
|
75
|
+
| `{ note, color? }` | Small aside (`--font-size-xs`), muted or colored |
|
|
76
|
+
| `+ footer: true` | Hairline rule above the row (totals) |
|
|
77
|
+
| `+ bar: 0..1` | 3px share bar under the row in the row's color |
|
|
78
|
+
|
|
79
|
+
Body text is `--tooltip-font-size` (12px, independent of chart axis text); rows are spaced by `--tooltip-row-gap` (4px). Labels are `--color-text-subtle` gray flush left; values are black tabular numerals flush right. Every built-in tooltip leads with a `title` (the entity/category/x value).
|
|
80
|
+
|
|
81
|
+
Scatter x/y point tooltips show each axis on its own labeled row: label from `xTooltipLabel`/`yTooltipLabel` (use these for a concise label when the axis title is long), else `xLabel`/`yLabel`, else `X`/`Y`; value from `xFormat`/`yFormat`. Custom tooltips can return `ChartFactory.TooltipManager.rows([...])` from `tooltipContent(d)` to reuse the shell hierarchy (as the quadrant example does).
|
|
82
|
+
|
|
83
|
+
**Annotations.** Pass `annotations: [ ... ]` to draw reference lines, shaded bands, and point callouts using the chart's own scales. They are re-drawn on every render, so they survive resize (unlike appending to `chart.svg` yourself, which is wiped each re-render). Available on any chart that exposes `x`/`y` scales — every Area/Line/Bar/Scatter builder (band-scale axes take category values; per-builder caveats in the [conformance matrix](conformance-matrix.md)) and every Dot row chart except `Dot.createUnitHistogram`. Slope charts (no x scale) and tables don't draw them.
|
|
84
|
+
|
|
85
|
+
| Shape | Draws |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `{ type: 'yLine', value, label?, color?, dash?, width?, labelAlign?, dy? }` | Horizontal reference line at `y = value` (e.g. a target, a baseline). |
|
|
88
|
+
| `{ type: 'xLine', value, ..., dy? }` | Vertical line at `x = value`. For time axes, `value` may be a `Date` or date string. |
|
|
89
|
+
| `{ type: 'yBand', from, to, label?, fill?, opacity? }` | Horizontal shaded region between two y values. |
|
|
90
|
+
| `{ type: 'xBand', from, to, ... }` | Vertical shaded region (e.g. a recession band, a quarter). |
|
|
91
|
+
| `{ type: 'region', xFrom?, xTo?, yFrom?, yTo?, fill?, opacity?, label? }` | Rectangular shaded region; each bound defaults to its domain edge, so `xFrom: 40, yFrom: 40` alone shades the up-and-right quadrant corner (the 40/40 club), and all four bounds shade an arbitrary box (a strike zone). |
|
|
92
|
+
| `{ type: 'segment', x1, y1, x2, y2, label?, stroke?, dash?, width?, labelColor? }` | A line between two **data-coordinate** points, mapped through the chart's live scales (time-axis values may be Dates or date strings). The label sits at the segment midpoint. |
|
|
93
|
+
| `{ type: 'diagonal', label?, stroke?, dash?, width? }` | Sugar for the identity line **y = x**, clipped to the overlap of the current x/y domains (e.g. a break-even or displayed-equals-actual line). Continuous scales only; if the domains don't overlap, nothing is drawn. |
|
|
94
|
+
| `{ type: 'point', x, y, label?, title?, color?, r?, dx?, dy?, leader? }` | A marked point with an optional callout label. `title` upgrades it to an **editorial two-line callout**: a larger bold value line (`--annotation-title-size`, 13px) with `label` beneath as the sub-line, placed as a block above the dot by default. `leader` draws a hairline from the dot's edge to the label — `'auto'` (default) only once the label sits displaced past `--annotation-leader-threshold` (14px, e.g. after de-collision), `true` always, `false` never; `leaderColor` overrides its stroke. |
|
|
95
|
+
| `{ type: 'event', x, label?, guide?, guideColor?, guideWidth? }` | A **point-in-time event**: a short tick *below the x-axis* with the label wrapped beneath it (NYT-approval-chart style), keeping the plot itself clean. `guide: true` adds a dotted vertical guide line through the plot, drawn **behind** the data marks. The root SVG grows to fit the band — no `margin.bottom` budgeting needed. Events outside the current x domain are dropped. |
|
|
96
|
+
|
|
97
|
+
Defaults: neutral token color, dashed lines (`dash: false` for solid), 8%-opacity band fill. Reference geometry (lines, bands, regions, segments, diagonal) draws **behind** the data marks; labels and point callouts stay on top.
|
|
98
|
+
|
|
99
|
+
**Event-band layout is automatic.** Each event label hangs toward whichever side of its tick has more room and wraps to the available gap; labels that would still collide are greedily packed into staggered rows (row height sized to the tallest wrapped label), with the event's tick extending down to meet its label. Styling tokens: `--annotation-event-band-offset`, `--annotation-event-tick-height`, `--annotation-event-font-size`, `--annotation-event-guide-color`.
|
|
100
|
+
|
|
101
|
+
**Labels place themselves — no per-annotation tuning for overlap.** Two automatic passes run after all annotations draw:
|
|
102
|
+
- *Edge-clamp (all labels):* each label's horizontal position is clamped to the plot's actual pixel width (measured with the same canvas text-measurer the bar/table builders use), so a label near the left or right edge — e.g. a `point` on the most recent data point, at the plot's rightmost pixel by construction — shifts inward just enough to stay fully inside the SVG instead of clipping.
|
|
103
|
+
- *Auto-stack (top labels — `xLine` / `xBand`):* labels for vertical lines/bands that sit close together on the x-axis would otherwise overprint each other; they're greedily packed into stacked rows so none overlap. Two nearby flip lines end up one above the other automatically.
|
|
104
|
+
|
|
105
|
+
`dx` / `dy` remain for *deliberate* placement (nudging a single label), not for edge-avoidance or de-collision — those are handled for you. Example:
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
Line.createBasic('#chart', {
|
|
109
|
+
data, xAccessor: d => d.date, yAccessor: d => d.rate,
|
|
110
|
+
annotations: [
|
|
111
|
+
{ type: 'yLine', value: 5, label: 'Fed target', color: '#22c55e' },
|
|
112
|
+
{ type: 'xBand', from: '2020-02-01', to: '2020-04-01', label: 'COVID', opacity: 0.1 },
|
|
113
|
+
{ type: 'point', x: '2020-04-15', y: 14.7, label: 'Peak' },
|
|
114
|
+
{ type: 'event', x: '2020-03-15', label: 'Rates cut to zero', guide: true }
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For anything the annotation types don't cover, use `onRender(chart)` — it fires after every render with the live scales, so custom d3 you append inside it also survives resize.
|
|
120
|
+
|
|
121
|
+
**Extra layers.** Pass `extraLayers: [ ... ]` to draw additional mark series with the chart's live scales — the light-composition primitive for "this chart needs one more thing on it" (dots behind a trend line, a comparison line from another dataset, small bars under a rate line). Layers re-draw on every render (resize-safe, like annotations), clip to the plot area, and are **decorative**: `pointer-events: none`, so the host chart keeps its hover/tooltip. Available wherever annotations are (any builder returning `svg` + `x`/`y` scales). Layers draw beneath annotations; `behind: true` slots a layer under the chart's own marks (but above grid/axes).
|
|
122
|
+
|
|
123
|
+
| Layer | Draws |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `{ type: 'points', data?, x?, y?, color?, r?, opacity?, behind?, label?, format? }` | One circle per row (default `r` 3). |
|
|
126
|
+
| `{ type: 'line', data?, x?, y?, color?, width?, dash?, curve?, opacity?, behind?, label?, format? }` | A path through the rows; `curve`: `'linear'` (default) \| `'monotone'` \| `'basis'` \| `'step'`; null/NaN y values break the path (`.defined()`). |
|
|
127
|
+
| `{ type: 'bars', data?, x?, y?, color?, barWidth?, baseline?, opacity?, behind?, label?, format? }` | Vertical bars from `baseline` (default 0) to each y; auto width from point spacing unless `barWidth`. |
|
|
128
|
+
|
|
129
|
+
`data` defaults to the chart's own rows; `x`/`y` accept field-name strings (like accessors) or functions and default to `d.x`/`d.y`; `color` defaults to `--chart-neutral` (var() strings re-theme live) and on `points`/`bars` may be a **per-row function** (`d => d.beat ? green : red` threshold coloring). On band-x hosts (vertical bars), layer marks center in each band. Deliberately **not** a layer grammar: no second axes, no per-layer scales — a layer whose values need their own axis wants its own chart (link them with `linkGroup`).
|
|
130
|
+
|
|
131
|
+
**Layers in the tooltip.** Give a layer a `label` and it **answers hover alongside the host series**: the chart's hover tooltip gains a `label: value` row (formatted by the layer's `format`, else the standard number format), a snapped open dot rides the layer mark, and the layer joins the chart's `linkPoints` (so linked charts mirror it too). Works on the builders using the shared hover machinery — `Line.createBasic` / `createYearly` / `createSmoothed` / `createAreaGradient` / `createMulti` / `createYoY`, plus `Area.createBasic` / `createSmoothed`. Unlabeled layers stay purely decorative.
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
Line.createCumulativeVsMoving('#trend', {
|
|
135
|
+
data: series, x: 'ab',
|
|
136
|
+
extraLayers: [
|
|
137
|
+
{ type: 'points', data: hrGames, x: 'ab', y: 'avgAt', color: 'var(--chart-red)', r: 3.5 }, // HR games on the trend
|
|
138
|
+
{ type: 'bars', data: weekly, x: 'ab', y: 'hits', color: 'var(--area-color-2)', behind: true, opacity: 0.35 }
|
|
139
|
+
]
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Accessor alias groups** (bidirectional, applied before the builder runs): `startAccessor` ≡ `beforeAccessor` · `endAccessor` ≡ `afterAccessor` · `lowAccessor` ≡ `lowerAccessor` ≡ `ciLowAccessor` · `highAccessor` ≡ `upperAccessor` ≡ `ciHighAccessor`.
|
|
144
|
+
|
|
145
|
+
**Field-name accessors (string form).** Every `*Accessor` option also accepts a **field-name string**: `xAccessor: 'date'` ≡ `xAccessor: d => d.date`. `x` and `y` are string shorthands for `xAccessor`/`yAccessor` (`{ x: 'date', y: 'value' }`). This makes chart configs fully JSON-serializable. The conversion runs before alias mirroring, so alias spellings (`beforeAccessor: 'from'`) work too. Exception: `colorAccessor` — documented as "constant color or function" (`Bar.createGantt`) — keeps a string as a literal color.
|
|
146
|
+
|
|
147
|
+
**X-axis auto-detection** (trend charts: Area, Line): if `xAccessor` yields `Date` objects or date-parseable strings, the x scale becomes `scaleTime` (strings are coerced to Dates everywhere); numbers stay linear. Data is sorted ascending by x at ingest.
|
|
148
|
+
|
|
149
|
+
**Value-ramped line stroke** (`strokeGradient`). On the single-line trend builders — `Line.createBasic`, `Line.createYearly`, `Line.createSmoothed`, `Line.createForecastBand`, `Line.createDynamicBand` — set `strokeGradient` to color the line by its y-value with a vertical gradient. The value is either an **array of colors** (blended piecewise in Lab space, so a gray midpoint stays a real gray instead of washing out to white) or a **`t => color` function** (any d3 sequential interpolator), where `t` runs `0` at the bottom of the domain to `1` at the top. The gradient maps across the y-scale domain by default (override with `strokeGradientDomain: [lo, hi]`) and is drawn in user space against the live scale, so it re-maps correctly on resize — no `onRender` needed. Only the main line is ramped; band fills, the dashed forecast line, hover markers, and legends keep their flat colors.
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
Line.createForecastBand('#chart', {
|
|
153
|
+
data, xAccessor: d => d.date,
|
|
154
|
+
actualAccessor: d => d.mean, upperAccessor: d => d.avgMax, lowerAccessor: d => d.avgMin,
|
|
155
|
+
strokeGradient: ['#2e7ebd', '#8b8e94', '#e2711d'], // cold → gray → hot, by y-value
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Linked charts (`linkGroup`).** The dashboard linking primitive: charts that share an x meaning (time, at-bat number, day index) join a named group and cooperate — layout stays application territory.
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
Line.createCumulativeVsMoving('#main', { data: rows, x: 'ab', linkGroup: 'trends' });
|
|
163
|
+
Line.createBasic('#companion', { data: hrRows, x: 'ab', y: 'hr', linkGroup: 'trends', showPoints: false });
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
- **Shared crosshair.** Hovering any member draws a `.linked-hover` layer (dashed guide at the exact shared x, dots snapped to each series' nearest point, value labels formatted through that chart's `yFormat`) on every *other* member; `pointerleave` clears the whole group. The hovered chart keeps its own native hover/tooltip when it has one; charts without native crosshair machinery (e.g. `Line.createBasic` with `showPoints: false`) get the linked layer while hovered too, so there's always local feedback. Builders that return a `linkPoints(x)` probe (the Line trend builders) get dots + labels; other supported builders mirror the guide line only.
|
|
167
|
+
- **Shared x-domain.** Each member contributes its raw x extent; the group nices the union once and injects it into every member (with `.nice()` disabled so tier-dependent tick counts can't round members apart) — a chart whose data stops short still renders the full group range. An explicit user `xDomain` always wins, and `sharedX: false` keeps the crosshair but not the domain.
|
|
168
|
+
- **Programmatic driving.** `chart.hover(xValue | null)` draws/clears one chart's linked layer (no group needed). `ChartFactory.getLinkGroup(name)` returns `{ name, charts, hover(x|null), colorFor(category) }` — `hover` scrubs the whole group (a table-row hover or a custom scrubber uses this), and `colorFor` hands out stable shared category colors, first-come from the area palette, for hand-built configs whose series should agree across the group.
|
|
169
|
+
- **Lifecycle.** Membership follows the config: `setConfig({ linkGroup: 'other' })` re-groups, `destroy()` leaves; the last member leaving deletes the group. Linking survives resize/re-renders (listeners live on the container; the layer is drawn from the live scales).
|
|
170
|
+
|
|
171
|
+
Styling: guide reuses `--color-text-faint` + `--hover-dash-pattern`; dots are `--link-dot-radius` (3.5) with a `--color-bg-container` separator ring; labels are `--font-size-xs` with a background halo. Contract tests: `tests/linked-hover.spec.js` plus a per-builder cell in the conformance contract.
|
|
172
|
+
|
|
173
|
+
**Brush-to-filter (`brush`).** A one-dimensional range selection that cooperates with linked charts — the library provides the selection primitive and the group broadcast; what gets *filtered* (tables, KPIs, queries) stays application territory via `onBrush`.
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
Line.createBasic('#overview', { data, x: 'day', y: 'sessions', linkGroup: 'traffic',
|
|
177
|
+
brush: { mode: 'zoom', onBrush: (range) => updateSummary(range) } });
|
|
178
|
+
Line.createBasic('#detail', { data, x: 'day', y: 'conversion', linkGroup: 'traffic' });
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- **Gesture**: drag horizontally to select; drag the window or its handles to adjust; click without dragging to clear. The selection is kept in **data coordinates** and restored across resize/`setConfig` re-renders.
|
|
182
|
+
- **`mode: 'highlight'`** (default): every other group member veils the area *outside* the selection (`--brush-veil-opacity`) — focus without losing context. **`mode: 'zoom'`**: every other member re-renders with its x-domain pinned to the range while the brushed chart keeps the full domain (the overview in a focus+context pair); clearing restores the shared union domain.
|
|
183
|
+
- **`onBrush(range | null, chart)`** fires **live while the user scrubs** and again when the gesture ends — re-aggregate bar charts, KPIs, and tables in it and they re-shape under the drag. It never fires from programmatic moves or re-render restores (no update loops). `range` values match the x scale's type (numbers or Dates). Aggregations over a few thousand rows are fine per-event; throttle inside the callback if yours is expensive.
|
|
184
|
+
- **Programmatic**: facade `chart.setBrush([lo, hi] | null)` moves/clears the selection and applies group effects without firing `onBrush`.
|
|
185
|
+
- **Pointer trade-off**: the brush overlay owns the pointer, so the brushed chart's own hover tooltip is quiet while `brush` is enabled. Linked-crosshair mirroring still works (container-level listeners) — put the brush on the overview chart and keep tooltips on the detail charts.
|
|
186
|
+
|
|
187
|
+
**Facet helper (`ChartFactory.facet`).** Small multiples: partition one dataset by a categorical field and render one chart per category, with the coordination hand-rolled grids get wrong — and nothing more (the panels land in a plain CSS grid; layout beyond that is application territory).
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
const facets = ChartFactory.facet('#grid', {
|
|
191
|
+
data: rows,
|
|
192
|
+
by: 'division',
|
|
193
|
+
chart: { family: 'Histogram', method: 'createBasic', config: { valueAccessor: 'woba', bins: 20 } },
|
|
194
|
+
columns: 3,
|
|
195
|
+
axes: 'outer',
|
|
196
|
+
colorByPanel: true
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- **Partition**: `by` is a field name or accessor; panels sort by category (`sort: 'asc' | 'desc' | comparator`). Each panel gets the shared `chart.config` plus its rows — the spec stays serializable.
|
|
201
|
+
- **Comparable panels**: `sharedX`/`sharedY` (default true) union the numeric/time domains across panels and pin stragglers with explicit domain overrides. Band/categorical axes are left alone; builders whose metadata doesn't support the override opt out automatically.
|
|
202
|
+
- **Axes**: `'all'` (default) or `'outer'` — y axis only on the first column, x axis only on the last row, via the universal `showXAxis`/`showYAxis` toggles (margins are not reclaimed, per their contract). `'outer'` with `sharedX/sharedY: false` warns: hiding axes that differ between panels reads as comparable when it isn't — use `axes: 'all'` with independent domains.
|
|
203
|
+
- **Chrome**: panel titles from the category value, rendered through the **standard chart shell** (`title` config) so they carry the library's title typography (`titles: false` hides them, a formatter customizes; an explicit `title` in the shared config wins). `colorByPanel` assigns each panel an area-palette color where the builder supports `color`.
|
|
204
|
+
- **Lifecycle**: returns `{ charts, categories, setData, rerender, destroy }` — `setData` re-partitions (categories may appear/disappear), `destroy` removes everything; a second `facet` call on the same container replaces the first. Panels are ordinary responsive charts — config `linkGroup`/`brush` inside `chart.config` works as usual.
|
|
205
|
+
|
|
206
|
+
**Return facade.** Every call returns a Proxy over the latest render:
|
|
207
|
+
|
|
208
|
+
- Latest-render members: `svg`, `x`/`xScale`, `y`/`yScale` (both spellings always present), `width`, `height` (plot area), plus builder-specific extras noted per builder.
|
|
209
|
+
- Lifecycle methods (the contract the React adapter builds on — identical on responsive and fixed-size charts):
|
|
210
|
+
- `setConfig(partial)` — merge config + re-render; state passed here survives resizes.
|
|
211
|
+
- `setData(data)` — sugar for `setConfig({ data })`: swap the dataset, keep every other option; the new data survives subsequent resize re-renders. Passing an empty array warns, same as create.
|
|
212
|
+
- `rerender()` — force a re-render at the current container size.
|
|
213
|
+
- `hover(xValue | null)` — draw (or clear) the chart's linked-crosshair layer at an x value in data coordinates (see Linked charts above); works with or without a `linkGroup`.
|
|
214
|
+
- `setBrush(range | null)` — move (or clear) the brush selection in data coordinates (see Brush-to-filter above); group effects apply, `onBrush` does not fire. No-op unless the chart was created with a `brush` config.
|
|
215
|
+
- `destroy()` — disconnects the observer, removes the SVG, stops animation loops, hides tooltips. **Idempotent**: calling it again is a silent no-op (safe under React StrictMode's double-invoked effect cleanup).
|
|
216
|
+
- `destroyed` — boolean, `false` while live, `true` after `destroy()`.
|
|
217
|
+
|
|
218
|
+
**Post-destroy contract.** After `destroy()`, the facade is inert: `setConfig`/`setData`/`rerender` warn once (per chart) and no-op; latest-render property reads (`svg`, `x`, `width`, …) return `undefined`; `prop in chart` / `Object.keys(chart)` report only the lifecycle methods. Create a new chart instead of reviving a destroyed one.
|
|
219
|
+
|
|
220
|
+
**One chart per container.** A second `create*` call on the same element destroys and replaces the first — this holds for responsive charts, fixed-size charts (`width: N` / `responsive: false`), and mixed cases (fixed over responsive and vice versa). Creating again after `destroy()` on the same container also works and yields a fully functional chart (StrictMode mount → cleanup → mount).
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Area charts (5)
|
|
225
|
+
|
|
226
|
+
Data rows: `{ x, y }` (single-series) or `{ x, key1, key2, ... }` (stacked variants). Time-aware x.
|
|
227
|
+
|
|
228
|
+
### Area.createBasic
|
|
229
|
+
|
|
230
|
+
Single-series area with line overlay and point tooltips. Example: `examples/area-chart/basic-example.html`.
|
|
231
|
+
|
|
232
|
+
Data: `{ x: 1, y: 42 }`
|
|
233
|
+
|
|
234
|
+
| Key | Default | Description |
|
|
235
|
+
|-----|---------|-------------|
|
|
236
|
+
| `xAccessor` | `d => d.x` | X value. |
|
|
237
|
+
| `yAccessor` | `d => d.y` | Y value. |
|
|
238
|
+
| `color` | `--chart-primary` (#3b82f6) | Fill/stroke color. |
|
|
239
|
+
| `showPoints` | auto | Hoverable data-point circles — auto-on only when dots get ≥ 5 radii of horizontal room at the rendered width; crowded charts draw the line alone (values stay hoverable via the hover-line). Explicit `true`/`false` always wins; a crowded explicit `true` logs a one-time warning. |
|
|
240
|
+
|
|
241
|
+
Y domain mode: `zero`. Returns: base facade only.
|
|
242
|
+
|
|
243
|
+
### Area.createDiverging
|
|
244
|
+
|
|
245
|
+
Single series filled against a baseline — above in one color, below in another — with the line stroke switching color exactly at the crossing and a solid baseline rule. Example: `examples/area-chart/diverging-example.html`.
|
|
246
|
+
|
|
247
|
+
Data: `{ x: 1, y: 4.9 }`
|
|
248
|
+
|
|
249
|
+
| Key | Default | Description |
|
|
250
|
+
|-----|---------|-------------|
|
|
251
|
+
| `xAccessor` | `d => d.x` | X value. |
|
|
252
|
+
| `yAccessor` | `d => d.y` | Y value. |
|
|
253
|
+
| `baseline` | `0` | Reference value the fill diverges around; always folded into the y domain so both sides exist. |
|
|
254
|
+
| `positiveColor` | `--chart-trend-up` (#22c55e) | Fill/stroke above the baseline. |
|
|
255
|
+
| `negativeColor` | `--chart-trend-down` (#ef4444) | Fill/stroke below the baseline. |
|
|
256
|
+
| `curve` | `d3.curveLinear` | Any d3 curve, applied to area and line. |
|
|
257
|
+
| `showPoints` | `false` | Opt-in sign-colored data-point circles (diverging series are usually dense). |
|
|
258
|
+
|
|
259
|
+
Y domain mode: `padded` over the data plus the baseline (never zero-anchored — a baseline of 4.45 shouldn't drag the axis to 0). The hover tooltip and `linkPoints` dots color by which side of the baseline the value sits on. Returns: base facade only.
|
|
260
|
+
|
|
261
|
+
### Area.createStacked
|
|
262
|
+
|
|
263
|
+
Stacked multi-series area. Example: `examples/area-chart/stacked-example.html`.
|
|
264
|
+
|
|
265
|
+
Data: `{ x: 1, series1: 10, series2: 20 }`
|
|
266
|
+
|
|
267
|
+
| Key | Default | Description |
|
|
268
|
+
|-----|---------|-------------|
|
|
269
|
+
| `keys` | `[]` (required) | Data keys to stack, in order. |
|
|
270
|
+
| `labels` | `{}` | `{key: 'Display name'}` for tooltip/legend. |
|
|
271
|
+
| `colors` | `getAreaColors()` | One per key. |
|
|
272
|
+
| `xAccessor` | `d => d.x` | X value. |
|
|
273
|
+
| `showTotal` | `true` | Append a Total row to the tooltip. |
|
|
274
|
+
| `totalFormat` | raw | Formatter for the tooltip total. |
|
|
275
|
+
|
|
276
|
+
Returns extras: `color` (ordinal scale), `keys`.
|
|
277
|
+
|
|
278
|
+
### Area.createNormalized
|
|
279
|
+
|
|
280
|
+
100% stacked area (y axis fixed 0–100%). Example: `examples/area-chart/normalized-example.html`. Same keys as `createStacked` minus `showTotal`/`totalFormat`; `yDomain` defaults to `[0, 1]`; tooltip shows per-key percentages. Returns extras: `color`, `keys`.
|
|
281
|
+
|
|
282
|
+
### Area.createSmoothed
|
|
283
|
+
|
|
284
|
+
Single-series smoothed (cardinal curve) area with vertical gradient fill. Example: `examples/area-chart/smoothed-example.html`. Same keys as `createBasic` plus:
|
|
285
|
+
|
|
286
|
+
| Key | Default | Description |
|
|
287
|
+
|-----|---------|-------------|
|
|
288
|
+
| `tension` | `--area-curve-tension` (0.4) | Cardinal curve tension. |
|
|
289
|
+
|
|
290
|
+
### Area.createStreamgraph
|
|
291
|
+
|
|
292
|
+
Wiggle-offset streamgraph with smart inline layer labels. Example: `examples/area-chart/streamgraph-example.html`.
|
|
293
|
+
|
|
294
|
+
| Key | Default | Description |
|
|
295
|
+
|-----|---------|-------------|
|
|
296
|
+
| `keys` / `labels` / `colors` / `xAccessor` | as createStacked | — |
|
|
297
|
+
| `showLabels` | `true` | Inline layer labels (drawn only where the layer is thick/wide enough). |
|
|
298
|
+
| `showTotal` | `true` | Total row in the tooltip. |
|
|
299
|
+
| `totalFormat` | raw | Tooltip total formatter. |
|
|
300
|
+
|
|
301
|
+
Returns extras: `color`, `keys`, `highlightKey(key|null)` — dims all other layers (pass `null` to reset).
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Line charts (14)
|
|
306
|
+
|
|
307
|
+
Data rows: `{ x, y }` unless noted. Time-aware x. `showPoints` is forced `false` on the dense-series variants (Yearly, Combined, YoY, Textures, AreaGradient, GrowthRate, band charts) which use a hover-line tooltip instead.
|
|
308
|
+
|
|
309
|
+
**`tooltipValueFormat` argument varies.** On the single-line trend builders (`createBasic`, `createYearly`, `createSmoothed`, `createAreaGradient`, `createGrowthRate`, `createTextures`, and `createCombined`'s raw "Daily" row — its moving-average row is hardcoded to `toFixed(0)`) the formatter receives the hovered **datum** `d`, so read your own field off it. On `createMulti`, `createYoY`, and the band/two-scale charts (`createForecastBand`, `createDynamicBand`, `createPulseAnomalies`, `createCumulativeVsMoving`) it receives the **raw number** (applied to each value row separately). Per-builder details are in the [conformance matrix](conformance-matrix.md) caveats.
|
|
310
|
+
|
|
311
|
+
### Line.createBasic
|
|
312
|
+
|
|
313
|
+
Single line with dots. Example: `examples/line-chart/basic-example.html`.
|
|
314
|
+
|
|
315
|
+
| Key | Default | Description |
|
|
316
|
+
|-----|---------|-------------|
|
|
317
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Accessors. |
|
|
318
|
+
| `color` | `--chart-primary` (#3b82f6) | Line color. |
|
|
319
|
+
| `showPoints` | auto | Hoverable dots (grow on hover) — auto-on only when dots get ≥ 5 radii of horizontal room at the rendered width; explicit `true`/`false` always wins (crowded `true` logs a one-time warning). |
|
|
320
|
+
|
|
321
|
+
Y domain mode: `zero`.
|
|
322
|
+
|
|
323
|
+
### Line.createMulti
|
|
324
|
+
|
|
325
|
+
Multiple lines sharing one x axis. Demonstrated in `examples/line-chart/showcase.html`.
|
|
326
|
+
|
|
327
|
+
Data: `series: [{ name, data: [{x,y}], color?, yAccessor?, valueFormat?, width?, opacity?, front? }]`
|
|
328
|
+
|
|
329
|
+
| Key | Default | Description |
|
|
330
|
+
|-----|---------|-------------|
|
|
331
|
+
| `series` | `[]` (required) | Per-series: `name` ('Series N'), `data`, `color` (palette), `yAccessor` (`d.y`), `valueFormat` (tooltip), plus the emphasis keys `width` (stroke-width px; `--line-stroke-width` when unset), `opacity` (stroke opacity 0–1; 1 when unset), `front` (raise the path above sibling series after all series render — the hover overlay stays topmost, so the shared tooltip keeps firing). Compose your own emphasis schemes from these three. |
|
|
332
|
+
| `colors` | `getLineColors()` | Fallback palette. |
|
|
333
|
+
| `xAccessor` | `d => d.x` | Shared x accessor. |
|
|
334
|
+
| `yAccessor` | `d => d.y` | Shared y accessor — the fallback for every series; a per-series `yAccessor` wins. |
|
|
335
|
+
| `showPoints` | auto | Per-series dot markers — on when the densest series gets ≥ 5 radii of horizontal dot spacing at the rendered width. An explicit `true`/`false` always wins (crowded `true` logs a one-time warning). |
|
|
336
|
+
| `curve` | `d3.curveLinear` | Any d3 curve factory, applied to every series' line generator (same semantics as `Line.createYearly`; hover bisection is unaffected). |
|
|
337
|
+
| `seriesLabels` | off | Direct label at each series' last defined point, in the series color (text = series name; `seriesLabelFormat` overrides). Labels render into the right margin, which is **auto-sized to the widest rendered label** (measured with `seriesLabelFormat` applied) unless you pin `margin.right` yourself — and make `legend` redundant. Colliding labels nudge apart top-down (min spacing = label line height), clamped to the plot; a label the nudge pass displaces from its line end gets a hairline leader tick back to the point it names, so dense endings stay attributable. Muted series' labels never drop below 0.55 opacity. |
|
|
338
|
+
| `seriesLabelFormat` | series name | `({ series, datum, value }) => string` — label text when `seriesLabels` is on; receives the series spec entry, its last defined point, and that point's y value (e.g. "Alvarez 34"-style labels). |
|
|
339
|
+
| `endDots` | off | Permanent series-color marker on each series' last defined point. The breathing room around it is a transparent gap masked out of the series' own path (not a bg-colored ring), so gridlines and neighboring series stay visible inside the gap. Inherits per-series `opacity`; sits above `front`-raised paths; takes no pointer events. Pairs naturally with `seriesLabels`. |
|
|
340
|
+
| `endDotRadius` | `4` | Radius of the `endDots` markers. |
|
|
341
|
+
| `endDotGap` | `2.5` | Width of the transparent gap between an `endDots` marker and its own line (masked, not painted). |
|
|
342
|
+
| `tooltipSortRows` | off | `true` \| `(a, b) => number` — re-order the tooltip's value rows at each hover position (title rows stay first). `true` sorts descending by the raw value at the hovered x; a function is used as the comparator over rows (raw value on `row._raw`). |
|
|
343
|
+
| `tooltipRowFilter` | off | `(row, xVal) => boolean` — drop tooltip value rows failing the predicate at the hovered x (title rows always kept; raw value on `row._raw`). Applied before `tooltipSortRows`. |
|
|
344
|
+
|
|
345
|
+
A shared hover-line tooltip works **regardless of `showPoints`**: moving over the plot snaps a vertical line to the nearest x and shows every series' value at that x in one tooltip (x title + one row per series). Date x-values render as clean dates (e.g. "Jan 1, 2019") in every trend tooltip — pass `labelFormat` to override. Labeled `extraLayers` rows carry `_raw` too, so they filter/sort alongside the series rows.
|
|
346
|
+
|
|
347
|
+
X domain spans every series. Y domain mode: `zero`. Returns extras: `series`.
|
|
348
|
+
|
|
349
|
+
### Line.createYearly
|
|
350
|
+
|
|
351
|
+
Dense time series, no dots, hover-line tooltip. Example: `examples/line-chart/yearly-example.html`.
|
|
352
|
+
|
|
353
|
+
| Key | Default | Description |
|
|
354
|
+
|-----|---------|-------------|
|
|
355
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Accessors. |
|
|
356
|
+
| `color` | `--chart-primary` | Line color. |
|
|
357
|
+
| `curve` | `d3.curveLinear` | Any d3 curve. |
|
|
358
|
+
|
|
359
|
+
Y domain mode: `padded`.
|
|
360
|
+
|
|
361
|
+
### Line.createSmoothed
|
|
362
|
+
|
|
363
|
+
Delegator → `createYearly` with `curve` **forced** to `d3.curveBasis` — a `curve` you pass is silently overridden (call `createYearly` directly to choose a curve). Example: `examples/line-chart/smoothed-example.html`.
|
|
364
|
+
|
|
365
|
+
### Line.createCombined
|
|
366
|
+
|
|
367
|
+
Raw daily line (muted) + moving-average overlay. Example: `examples/line-chart/combined-example.html`.
|
|
368
|
+
|
|
369
|
+
| Key | Default | Description |
|
|
370
|
+
|-----|---------|-------------|
|
|
371
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Accessors. |
|
|
372
|
+
| `windowSize` | 14 | Trailing moving-average window (points). |
|
|
373
|
+
| `rawColor` | `--chart-primary-light` | Raw-data line color. |
|
|
374
|
+
| `avgColor` | `--chart-primary` | Moving-average line color. |
|
|
375
|
+
|
|
376
|
+
Y domain mode: `padded`. Returns extras: `movingAvgData` (rows with `_movingAvg`).
|
|
377
|
+
|
|
378
|
+
### Line.createYoY
|
|
379
|
+
|
|
380
|
+
Year-over-year overlay (multiple series on the same axis). Example: `examples/line-chart/yoy-example.html`.
|
|
381
|
+
|
|
382
|
+
Data: `series: [{ name, data, color?, xAccessor?, yAccessor?, width?, opacity?, front? }]`
|
|
383
|
+
|
|
384
|
+
| Key | Default | Description |
|
|
385
|
+
|-----|---------|-------------|
|
|
386
|
+
| `series` | `[]` (required) | One entry per year. Per-series emphasis keys as on Line.createMulti: `width` (stroke-width px), `opacity` (stroke opacity 0–1), `front` (raise above siblings; the hover overlay stays topmost). |
|
|
387
|
+
| `colors` | `[--chart-series-1 #94a3b8, --chart-series-2 #64748b, --chart-series-3 #3b82f6]` | Oldest→newest emphasis palette. |
|
|
388
|
+
| `xAccessor` / `yAccessor` | `d.day` / `d.value` | Note the non-standard defaults. |
|
|
389
|
+
| `curve` | `d3.curveBasis` | Curve. |
|
|
390
|
+
| `seriesLabels` | off | Same as Line.createMulti — direct end-of-line labels (right margin auto-sized unless `margin.right` is pinned; makes `legend` redundant). |
|
|
391
|
+
| `seriesLabelFormat` | series name | Same as Line.createMulti — `({ series, datum, value }) => string`. |
|
|
392
|
+
| `tooltipSortRows` | off | Same as Line.createMulti — `true` or an `(a, b) => number` comparator re-ordering the tooltip's value rows at each hover. |
|
|
393
|
+
| `tooltipRowFilter` | off | Same as Line.createMulti — a `(row, xVal) => boolean` predicate dropping tooltip value rows at each hover. |
|
|
394
|
+
|
|
395
|
+
Notes: hover bisects each series independently (shared hover-line tooltip), so unequal point counts — e.g. a partial current season — are safe. Returns extras: `yearSeries`.
|
|
396
|
+
|
|
397
|
+
### Line.createTextures
|
|
398
|
+
|
|
399
|
+
Multi-line with per-series dash textures. Example: `examples/line-chart/textures-example.html`.
|
|
400
|
+
|
|
401
|
+
Data: `series: [{ name, data, color?, dash?, xAccessor?, yAccessor? }]` — `dash` is an SVG dasharray string (e.g. `'6,3'`; `''` = solid).
|
|
402
|
+
|
|
403
|
+
| Key | Default | Description |
|
|
404
|
+
|-----|---------|-------------|
|
|
405
|
+
| `series` | `[]` (required) | See above. Per-series `color` defaults to `--chart-primary` (not a rotating palette). |
|
|
406
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Shared fallbacks. |
|
|
407
|
+
|
|
408
|
+
`yDomain` default: extent ±15%. Hover looks up the same **index** in every series — series should have aligned point counts. Returns extras: `lineSeries`.
|
|
409
|
+
|
|
410
|
+
### Line.createAreaGradient
|
|
411
|
+
|
|
412
|
+
Line with gradient area fill. Example: `examples/line-chart/area-gradient-example.html`.
|
|
413
|
+
|
|
414
|
+
| Key | Default | Description |
|
|
415
|
+
|-----|---------|-------------|
|
|
416
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Accessors. |
|
|
417
|
+
| `color` | `--chart-primary` | Line + gradient color (opacity `--area-gradient-start` 0.3 → `--area-gradient-end` 0.02). |
|
|
418
|
+
|
|
419
|
+
Y domain mode: `padded`.
|
|
420
|
+
|
|
421
|
+
### Line.createGrowthRate
|
|
422
|
+
|
|
423
|
+
Positive/negative colored line around a zero baseline. Example: `examples/line-chart/growth-example.html`.
|
|
424
|
+
|
|
425
|
+
| Key | Default | Description |
|
|
426
|
+
|-----|---------|-------------|
|
|
427
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.value` | Note `yAccessor` default. |
|
|
428
|
+
| `upColor` | `--chart-trend-up` (#22c55e) | Positive segments. |
|
|
429
|
+
| `downColor` | `--chart-trend-down` (#ef4444) | Negative segments. |
|
|
430
|
+
|
|
431
|
+
Y domain mode: `symmetric` (never niced). Default `yFormat`: `+X%` style.
|
|
432
|
+
|
|
433
|
+
### Line.createForecastBand
|
|
434
|
+
|
|
435
|
+
Actual line inside a confidence band, with anomaly markers where the actual exits the band. Example: `examples/line-chart/forecast-example.html`.
|
|
436
|
+
|
|
437
|
+
Data: `{ x, actual, upper, lower }`
|
|
438
|
+
|
|
439
|
+
| Key | Default | Description |
|
|
440
|
+
|-----|---------|-------------|
|
|
441
|
+
| `xAccessor` / `actualAccessor` | `d.x` / `d.actual` | X / actual values. |
|
|
442
|
+
| `upperAccessor` / `lowerAccessor` | `d.upper` / `d.lower` | Band bounds (aliases: high/ciHigh, low/ciLow). |
|
|
443
|
+
| `bandColor` | `--chart-primary` | Band fill (opacity `--area-opacity` 0.15). |
|
|
444
|
+
| `actualColor` | `--chart-actual` (#3b82f6) | Line color. |
|
|
445
|
+
| `anomalyColor` | `--chart-anomaly` (#ef4444) | Dashed vertical anomaly lines. |
|
|
446
|
+
| `showAnomalies` | `true` | Draw anomaly markers. |
|
|
447
|
+
| `tooltipFormat` | `v => v.toFixed(1)` | Tooltip number format (this variant formats raw numbers, not rows). |
|
|
448
|
+
|
|
449
|
+
Y domain mode: `ratio`. Note: `forecastStart` is **not** read by this builder (only `createDynamicBand`).
|
|
450
|
+
|
|
451
|
+
### Line.createDynamicBand
|
|
452
|
+
|
|
453
|
+
Actual → forecast handoff with an expanding band and divider. Example: `examples/line-chart/dynamic-band-example.html`.
|
|
454
|
+
|
|
455
|
+
Data: `{ x, actual?, forecast?, upper, lower }` (actual null after `forecastStart`, forecast null before)
|
|
456
|
+
|
|
457
|
+
| Key | Default | Description |
|
|
458
|
+
|-----|---------|-------------|
|
|
459
|
+
| `xAccessor` / `actualAccessor` / `forecastAccessor` | `d.x` / `d.actual` / `d.forecast` | Values. |
|
|
460
|
+
| `upperAccessor` / `lowerAccessor` | `d.upper` / `d.lower` | Band bounds. |
|
|
461
|
+
| `forecastStart` | — | X value where the forecast begins (divider line + band start). |
|
|
462
|
+
| `bandColor` | `--chart-primary` | Band fill. |
|
|
463
|
+
| `forecastColor` | `--chart-forecast` | Forecast (dashed) line. |
|
|
464
|
+
| `actualColor` | `--chart-actual` | Actual line. |
|
|
465
|
+
| `dividerColor` | `--chart-series-1` (#94a3b8) | Divider line. |
|
|
466
|
+
| `tooltipFormat` | `v => v.toFixed(1)` | Tooltip number format. |
|
|
467
|
+
|
|
468
|
+
Y domain mode: `padded`.
|
|
469
|
+
|
|
470
|
+
### Line.createPulseAnomalies
|
|
471
|
+
|
|
472
|
+
Band chart whose out-of-band points get pulsing anomaly dots. Example: `examples/line-chart/pulse-example.html`.
|
|
473
|
+
|
|
474
|
+
Data: `{ x, actual, upper, lower }` — by default anomalies are **computed** as points outside the band; pass `anomalyAccessor` to flag them yourself.
|
|
475
|
+
|
|
476
|
+
| Key | Default | Description |
|
|
477
|
+
|-----|---------|-------------|
|
|
478
|
+
| `xAccessor` / `actualAccessor` / `upperAccessor` / `lowerAccessor` | `d.x` / `d.actual` / `d.upper` / `d.lower` | Values and band. |
|
|
479
|
+
| `anomalyAccessor` | out-of-band auto-detection | `(d) => boolean` — explicit caller-supplied anomaly flags (override the band-based detection). |
|
|
480
|
+
| `bandColor` | `--chart-primary` | Band fill. |
|
|
481
|
+
| `actualColor` | `--chart-actual` | Line. |
|
|
482
|
+
| `anomalyColor` | `--chart-anomaly` | Pulse rings + dots (CSS animation; radii from `--anomaly-pulse-radius` 4 / `--anomaly-dot-radius` 3). |
|
|
483
|
+
| `tooltipFormat` | `v => v.toFixed(1)` | Tooltip number format. |
|
|
484
|
+
|
|
485
|
+
Returns extras: `outliers` (the anomalous rows).
|
|
486
|
+
|
|
487
|
+
### Line.createCumulativeVsMoving
|
|
488
|
+
|
|
489
|
+
Two-scale comparison of cumulative total vs moving average. Example: `examples/line-chart/cumulative-vs-moving-example.html`.
|
|
490
|
+
|
|
491
|
+
Data: `{ x, cumulative, moving }` (pre-computed)
|
|
492
|
+
|
|
493
|
+
| Key | Default | Description |
|
|
494
|
+
|-----|---------|-------------|
|
|
495
|
+
| `xAccessor` / `cumulativeAccessor` / `movingAccessor` | `d.x` / `d.cumulative` / `d.moving` | Values. |
|
|
496
|
+
| `cumulativeColor` | `--chart-neutral` (#6b7280) | Cumulative line. |
|
|
497
|
+
| `movingColor` | `--chart-purple` (#a855f7) | Moving-average line. |
|
|
498
|
+
| `curve` | `d3.curveBasis` | Curve. |
|
|
499
|
+
| `tooltipFormat` | `v => v.toFixed(1)` | Tooltip number format. |
|
|
500
|
+
|
|
501
|
+
### Line.createBumpChart
|
|
502
|
+
|
|
503
|
+
Ranking changes over periods. Example: `examples/line-chart/bump-example.html`.
|
|
504
|
+
|
|
505
|
+
Data: `[{ name, rankings: [{ period: '2020', rank: 1 }, ...] }]`
|
|
506
|
+
|
|
507
|
+
| Key | Default | Description |
|
|
508
|
+
|-----|---------|-------------|
|
|
509
|
+
| `periods` | first entity's periods | X-axis period labels. |
|
|
510
|
+
| `maxRank` | max rank in data | Y-axis depth (rank 1 at top). |
|
|
511
|
+
| `colors` | `getLineColors().concat(getAreaColors())` | Per-entity palette. |
|
|
512
|
+
| `colorScale` | ordinal over entity names | Full override. |
|
|
513
|
+
| `curve` | `d3.curveBumpX` | Curve. |
|
|
514
|
+
| `showRankLabels` | `true` | Rank numbers inside dots. |
|
|
515
|
+
| `thinPeriodLabels` | `true` | Auto-thin the period axis when labels would collide (weekly/daily bumps): keeps every k-th label, measured from the widest label vs. the column step, always keeping the last period. `false` labels every column. |
|
|
516
|
+
| `animate` | `true` | Staggered line-draw + dot fade-in. |
|
|
517
|
+
|
|
518
|
+
Notes: left/right margins are auto-measured from the longest entity name (labels on both sides); `margin` is not read. Aspect ratio 2.0. Returns extras: `data`, `colorScale`. Each entity's line is masked out from under its own dots (a transparent hole the size of the dot + its ring), so translucent dot fills — e.g. a host page fading non-featured entities via `fill-opacity` — never show the line through the disc behind the rank number.
|
|
519
|
+
|
|
520
|
+
An entity's `rankings` array doesn't need an entry at every period — this also supports a "churn" bump chart where the ranked set's membership changes over time (e.g. "top 8 by some measure" at each snapshot, where who qualifies changes). The start/end name label anchors at that entity's *actual* first/last ranking's position (falling back to the chart's outer margin only when that ranking is genuinely at the first/last period), so entities that enter or exit partway through don't get their label stranded in the margin, stacked on every other entity that also starts/ends elsewhere. An entity with a single ranking entry gets one label, not a mirrored duplicate at the same point. Rank ties at a label anchor (two entities sharing the first or last checkpoint's rank — common when the ranked stat itself ties) no longer overprint: labels sharing an x-anchor nudge apart top-down (min spacing = label line height, clamped to the plot), and any displaced label gets a hairline leader tick back to the dot it names, matching Line.createMulti's seriesLabels behavior. Dense charts (every entity spans every period — the common case) are unaffected. Because side-margin labels and close-together periods both need real width, a churn bump chart with several entities can outgrow a narrow container — `examples/recreations/congress-story.html` (Chapter 4) hides the name labels via a page-level media query below 560px, keeping the in-dot rank numbers and hover tooltip as the mobile fallback.
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Slope charts (7)
|
|
525
|
+
|
|
526
|
+
All seven variants share one implementation (`_createSlope`); the named variants only pre-fill config. Data uses **fixed keys** — there are no accessor options:
|
|
527
|
+
|
|
528
|
+
Data: `[{ name: 'Item A', before: 100, after: 120 }]`
|
|
529
|
+
|
|
530
|
+
Sizing: default height 400 (mini 220), aspect 1.375; side margins auto-clamped to 25% of width (70–120px), top/bottom 40. A user `margin`/`height` in config still wins.
|
|
531
|
+
|
|
532
|
+
Shared keys:
|
|
533
|
+
|
|
534
|
+
| Key | Default | Description |
|
|
535
|
+
|-----|---------|-------------|
|
|
536
|
+
| `leftLabel` / `rightLabel` | `'Before'` / `'After'` | Column headers. |
|
|
537
|
+
| `colorMode` | `'trend'` | `'trend'` (up green `--trend-up-color`, down red, flat gray), `'categorical'` (`d3.schemeTableau10`), `'highlight'`. |
|
|
538
|
+
| `highlightIndex` | `-1` | highlight mode: index of the highlighted item. |
|
|
539
|
+
| `highlightName` | — | highlight mode: match by `d.name` (wins over index). Highlight color `--slope-highlight-color` (#f97316), others `--slope-muted-color`. |
|
|
540
|
+
| `axisPosition` | `'top'` | `'top'`, `'bottom'`, or `'both'` — where the axis line/ticks and column headers sit. |
|
|
541
|
+
| `showNameOnLeft` | `false` | Left labels as `Name: value` instead of value only. |
|
|
542
|
+
| `showNameOnRight` | `true` | Right labels as `Name value`; `false` = value only. |
|
|
543
|
+
| `valueFormat` | `v.toFixed(1)` | Drawn label + tooltip format. |
|
|
544
|
+
| `yDomain` | `[min*0.9, max*1.1]` | Over all before/after values. |
|
|
545
|
+
| `animated` | `false` | Staggered line-draw animation. |
|
|
546
|
+
| `animationDuration` | `--slope-animation-duration` (800) | Per-line draw ms. |
|
|
547
|
+
| `staggerDelay` | `--slope-stagger-delay` (80) | Delay between lines. |
|
|
548
|
+
| `fadeInDuration` | `--slope-fade-duration` (300) | Dot/label fade ms. |
|
|
549
|
+
| `bands` | `null` | `{ divisions, colors?, labels? }` background bands (see createBands). |
|
|
550
|
+
| `dodgeLabels` | `true` | Close values overlap their side labels; per side, labels are greedily pushed apart to a minimum vertical gap (stack shifted back up if it overflows the plot), and displaced labels get a quiet leader line back to their dot's y. `false` restores raw positions. |
|
|
551
|
+
| `labelMinGap` | `--slope-label-min-gap` (15) | Minimum vertical px between stacked side labels when dodging (createIndexed defaults to `--slope-label-min-gap-stacked` (34) for its two-line labels). |
|
|
552
|
+
| `mergeOrigin` | `false` (`true` on createIndexed) | When every `before` value is equal (within 1e-6), collapse the left column into ONE origin dot + one label (indexed series, e.g. everything = 100). Falls back to normal columns silently when they differ. Origin dot fill: `originColor` (default `--color-black`); left-side dodging is skipped. |
|
|
553
|
+
| `originColor` | `--color-black` | Fill of the merged origin dot (mergeOrigin only). |
|
|
554
|
+
| `baseline` | `false` (`true` on createIndexed) | `true` draws a hairline across the plot at the merged origin value; a number draws it at that y value on any slope chart. Color `--slope-baseline-color` (defaults to `--color-border-light`); drawn behind lines/dots. |
|
|
555
|
+
| `rightLabelStacked` | `false` (`true` on createIndexed) | Right labels as two lines — bold name above, value below (second tspan at `dy: 1.25em`). Pair with a larger `labelMinGap`. |
|
|
556
|
+
| `rightValueFormat` | — | Formats the right label's value tspan from the **full datum** (`d => string`), overriding `valueFormat(d.after)` — how createIndexed renders signed deltas (`+37%`). Applies with or without `showNameOnRight`. |
|
|
557
|
+
|
|
558
|
+
Returns: `{ svg, svgRoot, defs, y, width, height, xLeft, xRight, getColor(d,i), dotRadius }` (no x scale — slope charts have two fixed columns).
|
|
559
|
+
|
|
560
|
+
Per-variant deltas:
|
|
561
|
+
|
|
562
|
+
- **Slope.createBasic** — passthrough (colorMode defaults to `'trend'`). Example: `examples/slope-chart/basic-example.html`.
|
|
563
|
+
- **Slope.createTrend** — forces `colorMode: 'trend'`. Demonstrated in `examples/slope-chart/showcase.html`.
|
|
564
|
+
- **Slope.createCategorical** — forces `colorMode: 'categorical'`. Example: `examples/slope-chart/categorical-example.html`.
|
|
565
|
+
- **Slope.createHighlight** — forces `colorMode: 'highlight'`; pass `highlightName` or `highlightIndex`. Example: `examples/slope-chart/highlight-example.html`.
|
|
566
|
+
- **Slope.createAnimated** — forces `animated: true`. Example: `examples/slope-chart/animated-example.html`.
|
|
567
|
+
- **Slope.createBands** — merges default `bands` `{ divisions: 4, colors: [--slope-band-1..4], labels: ['Top 25%','25-50%','50-75%','Bottom 25%'] }` and defaults `yDomain` to `[0, 100]`. Band colors are applied bottom-up (last color = top band). Example: `examples/slope-chart/bands-example.html`.
|
|
568
|
+
- **Slope.createIndexed** — the indexed "fan": pre-fills `colorMode: 'trend'`, `mergeOrigin: true`, `baseline: true`, `rightLabelStacked: true`, `labelMinGap: --slope-label-min-gap-stacked`, and a signed-percent `rightValueFormat` (`+37%` / `−9%` from before→after). Feed it items indexed to a common base (`before: 100`) and it draws one origin dot, a no-change hairline, and a fan of trend-colored lines with dodged two-line labels. Example: `examples/slope-chart/indexed-example.html`.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Bar charts (16)
|
|
573
|
+
|
|
574
|
+
The thirteen horizontal variants have **intrinsic height** — `rows × (--bar-height 24 + --bar-gap 8) + margins` (`--gantt-bar-height` 18 + 6 for gantt/range/waterfall) — and auto-measure the left margin from category labels; they are width-responsive only and do not read `margin`, `height`, or `aspectRatio`. Per-chart `barHeight` / `barGap` (px numbers) override the tokens for one chart — the intrinsic height follows. Row label fonts auto-fit dense charts: category/value text keeps its token size while the row pitch is roomy, shrinks with the pitch below that (8px floor), and inside-bar labels (inline categories, stacked/likert segment labels, progress inline values) hide or move outside once the bar is under ~12px thick. The three vertical variants use the normal aspect-derived height (their bar thickness falls out of the band scale: container width ÷ bar count).
|
|
575
|
+
|
|
576
|
+
### Bar.createBasic
|
|
577
|
+
|
|
578
|
+
Horizontal bars, category left, value at bar end. Example: `examples/bar-chart/basic-example.html`.
|
|
579
|
+
|
|
580
|
+
Data: `{ category: 'A', value: 42 }`
|
|
581
|
+
|
|
582
|
+
| Key | Default | Description |
|
|
583
|
+
|-----|---------|-------------|
|
|
584
|
+
| `categoryAccessor` | `d => d.category` | Row label. |
|
|
585
|
+
| `valueAccessor` | `d => d.value` | Bar length. |
|
|
586
|
+
| `valueFormat` | `v.toLocaleString()` | Value labels + tooltip. |
|
|
587
|
+
| `color` | `--chart-primary` | Bar fill — a constant or a `(d, i) => color` function for per-bar coloring (e.g. team colors). |
|
|
588
|
+
| `barHeight` / `barGap` | `--bar-height` (24) / `--bar-gap` (8) | Per-chart bar thickness / gap in px (override the tokens). |
|
|
589
|
+
|
|
590
|
+
X domain: `[0, max*1.1]`, widening to `[min*1.1, max*1.1]` when values go negative — negative bars grow left from a zero baseline (drawn when the domain spans both signs) with their value label at the outer end, and the left gutter reserves room for those labels. Use `createDiverging` when you want the two-color center-label treatment; plain `createBasic` handles mixed-sign data in one color. Bars animate on load (`--bar-animation-duration` 800ms, `--bar-stagger-delay` 50ms).
|
|
591
|
+
|
|
592
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
593
|
+
|
|
594
|
+
`labelAlign: 'left'` aligns the category labels to the chart's left edge instead of hugging the bars (default `'right'`).
|
|
595
|
+
|
|
596
|
+
### Bar.createInline
|
|
597
|
+
|
|
598
|
+
Same keys as `createBasic` (including `barHeight` / `barGap`); value labels render inside the bar (outside when the bar is too short — `--bar-inline-threshold`). Example: `examples/bar-chart/inline-example.html`.
|
|
599
|
+
|
|
600
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
601
|
+
|
|
602
|
+
### Bar.createLabelAbove
|
|
603
|
+
|
|
604
|
+
Same keys as `createBasic` (including `barHeight` / `barGap` — the 18px label band above each bar is fixed); category + value labels sit above each bar (taller rows). Example: `examples/bar-chart/label-above-example.html`. Returns `{ svg, x, width, height }` (no y scale).
|
|
605
|
+
|
|
606
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
607
|
+
|
|
608
|
+
### Bar.createProgress
|
|
609
|
+
|
|
610
|
+
Progress-style bars against a fixed maximum with a background track. Example: `examples/bar-chart/progress-example.html`.
|
|
611
|
+
|
|
612
|
+
| Key | Default | Description |
|
|
613
|
+
|-----|---------|-------------|
|
|
614
|
+
| `categoryAccessor` / `valueAccessor` | `d.category` / `d.value` | Accessors. |
|
|
615
|
+
| `maxValue` | 100 | Scale maximum (track length). |
|
|
616
|
+
| `valueFormat` | `` v => `${v.toFixed(1)}%` `` | Labels/tooltip. |
|
|
617
|
+
| `colorScale` | value-based | `(value) => color`; default: ≥90% green, ≥70% lime, ≥50% orange, else red. |
|
|
618
|
+
| `barHeight` / `barGap` | `--bar-height` (24) / `--bar-gap` (8) | Per-chart bar thickness / gap in px (override the tokens). |
|
|
619
|
+
|
|
620
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
621
|
+
|
|
622
|
+
`labelAlign: 'left'` aligns the category labels to the chart's left edge instead of hugging the bars (default `'right'`).
|
|
623
|
+
|
|
624
|
+
### Bar.createBullet
|
|
625
|
+
|
|
626
|
+
Bullet chart: measure bar + target tick + optional qualitative range bands. Example: `examples/bar-chart/bullet-example.html`.
|
|
627
|
+
|
|
628
|
+
Data: `{ category, value, target, ranges?: [n1, n2, ...] }`
|
|
629
|
+
|
|
630
|
+
| Key | Default | Description |
|
|
631
|
+
|-----|---------|-------------|
|
|
632
|
+
| `categoryAccessor` / `valueAccessor` | `d.category` / `d.value` | Accessors. |
|
|
633
|
+
| `targetAccessor` | `d => d.target` | Target tick position. |
|
|
634
|
+
| `rangesAccessor` | `d => d.ranges \|\| []` | Background range thresholds. |
|
|
635
|
+
| `color` | `--chart-primary` | Measure bar. |
|
|
636
|
+
| `targetColor` | `--color-black` | Target tick. |
|
|
637
|
+
| `valueFormat` | `v.toLocaleString()` | Labels/tooltip. |
|
|
638
|
+
| `barHeight` / `barGap` | `--bar-height` (24) / `--bar-gap` (8) | Per-chart bar thickness / gap in px (override the tokens). |
|
|
639
|
+
|
|
640
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
641
|
+
|
|
642
|
+
`labelAlign: 'left'` aligns the category labels to the chart's left edge instead of hugging the bars (default `'right'`).
|
|
643
|
+
|
|
644
|
+
### Bar.createDiverging
|
|
645
|
+
|
|
646
|
+
Bars diverging left/right from a center axis. Example: `examples/bar-chart/diverging-example.html`.
|
|
647
|
+
|
|
648
|
+
| Key | Default | Description |
|
|
649
|
+
|-----|---------|-------------|
|
|
650
|
+
| `categoryAccessor` / `valueAccessor` | `d.category` / `d.value` | Accessors (values may be negative). |
|
|
651
|
+
| `positiveColor` | `--chart-green` (#22c55e) | Right bars. |
|
|
652
|
+
| `negativeColor` | `--chart-red` (#ef4444) | Left bars. |
|
|
653
|
+
| `valueFormat` | `v.toLocaleString()` | Labels/tooltip. |
|
|
654
|
+
| `barHeight` / `barGap` | `--bar-height` (24) / `--bar-gap` (8) | Per-chart bar thickness / gap in px (override the tokens). |
|
|
655
|
+
|
|
656
|
+
Returns `{ svg, xLeft, xRight, y, width, height }`.
|
|
657
|
+
|
|
658
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
659
|
+
|
|
660
|
+
### Bar.createStacked
|
|
661
|
+
|
|
662
|
+
Horizontal stacked bars. Example: `examples/bar-chart/stacked-example.html`.
|
|
663
|
+
|
|
664
|
+
Data: `{ category, key1: n, key2: n, ... }`
|
|
665
|
+
|
|
666
|
+
| Key | Default | Description |
|
|
667
|
+
|-----|---------|-------------|
|
|
668
|
+
| `keys` | `[]` (required) | Series keys, in stack order. |
|
|
669
|
+
| `labels` | `{}` | Display names per key. |
|
|
670
|
+
| `colors` | `getAreaColors()` | One per key. |
|
|
671
|
+
| `categoryAccessor` | `d => d.category` | Row label. |
|
|
672
|
+
| `showSegmentLabels` | `false` | Value labels centered in each segment. |
|
|
673
|
+
| `valueFormat` | `v.toLocaleString()` | Labels/tooltip. |
|
|
674
|
+
| `barHeight` / `barGap` | `--bar-height` (24) / `--bar-gap` (8) | Per-chart bar thickness / gap in px (override the tokens). |
|
|
675
|
+
|
|
676
|
+
Returns extras: `colorScale`.
|
|
677
|
+
|
|
678
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
679
|
+
|
|
680
|
+
`labelAlign: 'left'` aligns the category labels to the chart's left edge instead of hugging the bars (default `'right'`).
|
|
681
|
+
|
|
682
|
+
### Bar.createGrouped
|
|
683
|
+
|
|
684
|
+
Grouped horizontal bars (one sub-bar per key per category). Example: `examples/bar-chart/grouped-example.html`. Keys: `keys`, `labels`, `colors`, `categoryAccessor`, `valueFormat` (as createStacked, no `showSegmentLabels`), plus `barHeight` (sub-bar thickness, default `--bar-height-mini` 18) and `barGap` (base gap between groups, default `--bar-gap` 8; rendered gap is this + 4). Returns `{ svg, x, y0, y1, colorScale, width, height }` (`y0` category band, `y1` inner band).
|
|
685
|
+
|
|
686
|
+
Progress-style extras: `maxValue` (alias `xMax`) pins the x domain to `[0, maxValue]` instead of the data max ×1.1, so every bar is read against a common scale; `showTrack: true` draws a full-width background track behind each bar (tokens `--bar-track-color` / `--bar-track-opacity`). Together they turn grouped bars into per-series progress bars out of `maxValue` (e.g. shares out of 100%). Demonstrated in `examples/recreations/538-comics-grouped.html`.
|
|
687
|
+
|
|
688
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
689
|
+
|
|
690
|
+
`labelAlign: 'left'` aligns the category labels to the chart's left edge instead of hugging the bars (default `'right'`).
|
|
691
|
+
|
|
692
|
+
### Bar.createLikert
|
|
693
|
+
|
|
694
|
+
Diverging Likert scale centered on neutral. Example: `examples/bar-chart/likert-example.html`.
|
|
695
|
+
|
|
696
|
+
Data: `{ question, key1: pct, ... }` — **note the category accessor default is `d.question`**.
|
|
697
|
+
|
|
698
|
+
| Key | Default | Description |
|
|
699
|
+
|-----|---------|-------------|
|
|
700
|
+
| `keys` | `[]` (required) | Ordered negative → positive. An **odd** key count makes the middle key neutral (drawn split across the center). |
|
|
701
|
+
| `labels` | `{}` | Display names. |
|
|
702
|
+
| `colors` | auto | Auto diverging palette from temperature tokens (cold blues → gray neutral → warm oranges) when omitted. |
|
|
703
|
+
| `categoryAccessor` | `d => d.question` | Row label. |
|
|
704
|
+
| `showSegmentLabels` | `true` | Percent labels in segments. |
|
|
705
|
+
| `valueFormat` | `Math.round(v) + '%'` | Labels/tooltip. |
|
|
706
|
+
| `maxExtent` | `max(±sums, 50)` | Half-width of the x domain (`[-maxExtent, maxExtent]`). The default floor of 50 suits survey percentages; set this (alias `xMax`) to fit the axis to non-percentage data — e.g. a population pyramid in millions where the 50 floor would otherwise strand the bars in the center. |
|
|
707
|
+
| `contextLabels` | `{left:'← Disagree', center:'Neutral', right:'Agree →'}` | Top axis captions. Pass `{left, center, right}` to relabel (empty string drops that one), or `false` to hide the row — e.g. `{left:'Male', center:'', right:'Female'}` for a population pyramid. |
|
|
708
|
+
| `animate` | `true` | Alias `animated`. `false` renders bars at final size immediately (no grow-in) — the responsive wrapper passes it on resize, and it's what you want when driving your own frame-to-frame animation via `update()`. |
|
|
709
|
+
| `barHeight` / `barGap` | `--bar-height` (24) / `--bar-gap` (8) | Per-chart bar thickness / gap in px (override the tokens). |
|
|
710
|
+
|
|
711
|
+
Returns extras: `colorScale`, `centerX`, and **`update(newData, { duration = 600 })`** — tween the bars to a new dataset with the same categories/keys but new values (rows match by index). Labels appear/disappear as bars cross the width threshold; `duration: 0` sets instantly. Keep the x domain fixed across frames (set `maxExtent` to span them all) so widths stay comparable. With two keys and no neutral the builder renders as a population pyramid (left/right wings); `examples/recreations/population-pyramid.html` steps one across 1950–2100 with a play/scrub control.
|
|
712
|
+
|
|
713
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
714
|
+
|
|
715
|
+
`labelAlign: 'left'` aligns the category labels to the chart's left edge instead of hugging the bars (default `'right'`).
|
|
716
|
+
|
|
717
|
+
### Bar.createGantt
|
|
718
|
+
|
|
719
|
+
Timeline bars. Example: `examples/bar-chart/gantt-example.html`.
|
|
720
|
+
|
|
721
|
+
Data: `{ label, start, end }` — Date objects give a time axis; plain numbers give a linear axis.
|
|
722
|
+
|
|
723
|
+
| Key | Default | Description |
|
|
724
|
+
|-----|---------|-------------|
|
|
725
|
+
| `labelAccessor` | `d => d.label` | Left row label. |
|
|
726
|
+
| `startAccessor` / `endAccessor` | `d.start` / `d.end` | Bar extent (aliases before/after). |
|
|
727
|
+
| `colorAccessor` | `--chart-primary` | `(d) => color` function or constant. |
|
|
728
|
+
| `rightLabelAccessor` | `null` | Optional right-side label (widens right margin). |
|
|
729
|
+
| `minTime` / `maxTime` | data extent | Axis domain. |
|
|
730
|
+
| `tickCount` | 10 (mini 4) | Vertical grid/tick count. |
|
|
731
|
+
| `dateFormat` | `d3.timeFormat('%b %Y')` for Dates | Axis + tooltip date format. |
|
|
732
|
+
| `milestones` | — | `[{ date, label, color? }]` vertical marker lines. |
|
|
733
|
+
| `groupAccessor` | `null` | Declared but currently **unused** in the implementation. |
|
|
734
|
+
| `barHeight` / `barGap` | `--gantt-bar-height` (18) / `--gantt-bar-gap` (6) | Per-chart bar thickness / gap in px (override the tokens). |
|
|
735
|
+
|
|
736
|
+
Width-responsive only (intrinsic height).
|
|
737
|
+
|
|
738
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--gantt-border-radius` 2).
|
|
739
|
+
|
|
740
|
+
`labelAlign: 'left'` aligns the category labels to the chart's left edge instead of hugging the bars (default `'right'`).
|
|
741
|
+
|
|
742
|
+
### Bar.createRange
|
|
743
|
+
|
|
744
|
+
Floating bars spanning `[low, high]` per category on a numeric value axis — temperature ranges, salary bands, confidence intervals. Thin wrapper over the gantt positional-bar geometry in its numeric (linear-axis) mode. Example: `examples/bar-chart/range-example.html`.
|
|
745
|
+
|
|
746
|
+
Data: `{ name, low, high }`
|
|
747
|
+
|
|
748
|
+
| Key | Default | Description |
|
|
749
|
+
|-----|---------|-------------|
|
|
750
|
+
| `nameAccessor` | `d => d.name` | Left row label. |
|
|
751
|
+
| `lowAccessor` / `highAccessor` | `d.low` / `d.high` | Bar extent (aliases lower/upper and ciLow/ciHigh). |
|
|
752
|
+
| `colorAccessor` | `color`, else `--chart-primary` | `(d) => color` function or constant; the universal `color` seeds it when omitted. |
|
|
753
|
+
| `rightLabelAccessor` | `null` | Optional right-side label after each bar (widens right margin). |
|
|
754
|
+
| `valueFormat` | raw value | Formats the axis ticks AND the "low – high" tooltip range (`xFormat` still wins on the axis). |
|
|
755
|
+
| `tickCount` | 10 (mini 4) | Vertical grid/tick count. |
|
|
756
|
+
| `barHeight` / `barGap` | `--gantt-bar-height` (18) / `--gantt-bar-gap` (6) | Per-chart bar thickness / gap in px (passed through to gantt; override the tokens). |
|
|
757
|
+
|
|
758
|
+
Width-responsive only (intrinsic height). Returns `{ svg, x, y, width, height }` — `y` is a band scale over row **indices**.
|
|
759
|
+
|
|
760
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (passed through to gantt; default `--gantt-border-radius` 2).
|
|
761
|
+
|
|
762
|
+
`labelAlign: 'left'` aligns the row labels to the chart's left edge instead of hugging the bars (default `'right'`; pass-through).
|
|
763
|
+
|
|
764
|
+
### Bar.createWaterfall
|
|
765
|
+
|
|
766
|
+
Waterfall: signed deltas in, cumulative floating bars out. Each `{ name, value }` row occupies the interval the running total moves through; `{ name, total: true }` rows draw the running total from zero (subtotals / grand total). Thin wrapper over the gantt positional-bar geometry (numeric axis) — the cumulative bookkeeping, sign coloring, and connector hairlines live in the wrapper. Example: `examples/bar-chart/waterfall-example.html`; a WSJ-style P&L bridge: `examples/recreations/profit-waterfall.html`.
|
|
767
|
+
|
|
768
|
+
Data: `{ name, value }` delta rows and `{ name, total: true }` total rows, in statement order.
|
|
769
|
+
|
|
770
|
+
| Key | Default | Description |
|
|
771
|
+
|-----|---------|-------------|
|
|
772
|
+
| `nameAccessor` | `d => d.name` | Left row label. |
|
|
773
|
+
| `valueAccessor` | `d => d.value` | Signed delta per step (ignored on total rows). |
|
|
774
|
+
| `totalAccessor` | `d => d.total` | Truthy marks a running-total bar drawn from zero. |
|
|
775
|
+
| `upColor` / `downColor` / `totalColor` | `--chart-trend-up` / `--chart-trend-down` / `--chart-neutral` | Kind-based bar colors. |
|
|
776
|
+
| `colorAccessor` | kind colors | Per-bar override — receives the computed step: the original datum plus `{ start, end, delta, running, kind }` (`kind` is `'up' | 'down' | 'total'`). A string is a literal constant color. |
|
|
777
|
+
| `valueFormat` | `v.toLocaleString()` | Axis ticks, total-row labels, and tooltip running totals. |
|
|
778
|
+
| `deltaFormat` | signed `valueFormat` | Signed step labels and tooltip change values. |
|
|
779
|
+
| `showValueLabels` | `true` | Right-side label per bar: signed delta (delta rows) or running total (total rows). |
|
|
780
|
+
| `showConnectors` | `true` | Vertical hairline at each handoff value, spanning the two adjacent rows (both bars share an edge exactly there). |
|
|
781
|
+
| `connectorColor` | `--waterfall-connector-color` | Connector hairline color. |
|
|
782
|
+
| `tickCount` | 10 (mini 4) | Vertical grid/tick count. |
|
|
783
|
+
| `barHeight` / `barGap` | `--gantt-bar-height` (18) / `--gantt-bar-gap` (6) | Per-chart bar thickness / gap in px (passed through to gantt; override the tokens). |
|
|
784
|
+
|
|
785
|
+
`xDomain`/`xMin`/`xMax` override the axis window only — bar extents always come from the cumulative math. `tooltipContent(d)` receives the computed step object. Width-responsive only (intrinsic height); returns `{ svg, x, y, width, height }` (`y` band over row indices).
|
|
786
|
+
|
|
787
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (passed through to gantt; default `--gantt-border-radius` 2).
|
|
788
|
+
|
|
789
|
+
`labelAlign: 'left'` aligns the row labels to the chart's left edge instead of hugging the bars (default `'right'`; pass-through).
|
|
790
|
+
|
|
791
|
+
### Bar.createPyramid
|
|
792
|
+
|
|
793
|
+
Population pyramid: two opposed magnitudes per category row — left wing vs right wing mirrored around a center axis. Thin wrapper over `Bar.createLikert` with exactly two keys (which renders raw left/right magnitudes, no neutral); inherits Likert's `update()` frame tween. Example: `examples/bar-chart/pyramid-example.html`; an animated 1950–2100 pyramid: `examples/recreations/population-pyramid.html`.
|
|
794
|
+
|
|
795
|
+
Data: `{ category, left, right }` — the two wing field names are configurable via `leftKey`/`rightKey`.
|
|
796
|
+
|
|
797
|
+
| Key | Default | Description |
|
|
798
|
+
|-----|---------|-------------|
|
|
799
|
+
| `leftKey` / `rightKey` | `'left'` / `'right'` | Fields holding the wing magnitudes. |
|
|
800
|
+
| `leftAccessor` / `rightAccessor` | `null` | Accessor alternative to the key fields (materialized internally; `update()` accepts raw rows either way). |
|
|
801
|
+
| `leftLabel` / `rightLabel` | the keys | Wing display names — context header + tooltip labels. |
|
|
802
|
+
| `categoryAccessor` | `d => d.category` | Row label. |
|
|
803
|
+
| `labels` | from `leftLabel`/`rightLabel` | Tooltip display names per key (overrides the derived ones). |
|
|
804
|
+
| `colors` | `[getAreaColors()[0], getAreaColors()[4]]` | `[leftColor, rightColor]`. |
|
|
805
|
+
| `maxExtent` | data max ×1.05 | Half-width of the x domain `[-maxExtent, maxExtent]` (alias `xMax`). Set explicitly to keep the axis fixed across `update()` frames. No Likert 50-percent floor — the default fits the data. |
|
|
806
|
+
| `valueFormat` | `v.toLocaleString()` | Segment labels + tooltip fallback. |
|
|
807
|
+
| `showSegmentLabels` | `true` | Value labels in segments wider than 30px. |
|
|
808
|
+
| `contextLabels` | `{left: leftLabel, center: '', right: rightLabel}` | Top caption row — override any text (empty string drops one) or `false` to hide the row. |
|
|
809
|
+
| `animate` | `true` | `false` renders at final size — what you want when driving `update()` yourself. |
|
|
810
|
+
| `barHeight` / `barGap` | `--bar-height` (24) / `--bar-gap` (8) | Per-chart bar thickness / gap in px (passed through to likert; override the tokens). |
|
|
811
|
+
|
|
812
|
+
Returns extras: `colorScale`, `centerX`, and `update(newData, { duration = 600 })` (see `Bar.createLikert`).
|
|
813
|
+
|
|
814
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (passed through to likert; default `--bar-border-radius` 3).
|
|
815
|
+
|
|
816
|
+
`labelAlign: 'left'` aligns the row labels to the chart's left edge instead of hugging the bars (default `'right'`; pass-through).
|
|
817
|
+
|
|
818
|
+
### Bar.createVertical
|
|
819
|
+
|
|
820
|
+
Vertical (column) bars. Example: `examples/bar-chart/vertical-example.html`.
|
|
821
|
+
|
|
822
|
+
Data: `{ x: 'Jan', y: 42 }`
|
|
823
|
+
|
|
824
|
+
| Key | Default | Description |
|
|
825
|
+
|-----|---------|-------------|
|
|
826
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Category / value. |
|
|
827
|
+
| `xFormat` | raw | Category tick format. |
|
|
828
|
+
| `yFormat` | `d3.format(',')` | Y ticks. |
|
|
829
|
+
| `valueFormat` | `yFormat` | Labels above bars + tooltip. |
|
|
830
|
+
| `color` | `--chart-primary` | Fill — a constant or a `(d) => color` function for per-bar coloring. |
|
|
831
|
+
| `showLabels` | auto | Value labels above bars. By default they auto-hide when the band pitch can't hold the widest formatted value (dense charts); pass `true`/`false` to force. |
|
|
832
|
+
| `rotateXLabels` | `false` | `true` tilts x-axis labels to −45° (or pass a number of degrees). A hint, not a contract: labels always thin to fit — rotated labels need less horizontal room, so more bands stay labeled — and when every band label is short (≤6 chars, e.g. years) the rotation is dropped so the thinned labels sit flat with no extra bottom margin. |
|
|
833
|
+
| `axisTicks` | 5 | Y grid lines. |
|
|
834
|
+
|
|
835
|
+
Y domain: `[0, max*1.1]`, widening to `[min*1.1, max*1.1]` when values go negative — negative columns hang below a solid zero baseline (drawn when the domain spans both signs) with their value label beneath them.
|
|
836
|
+
|
|
837
|
+
Density behavior (many bands): axis labels thin to **round numbers** when the categories are all integers (…1910, 1920 rather than …1901, 1906); band padding tightens (0.2 → 0.08) once bands drop below ~9px so the bars stay visible; corner rounding clamps to a third of the bar width; and the entrance stagger caps at ~600ms total regardless of bar count.
|
|
838
|
+
|
|
839
|
+
`borderRadius` (px) rounds the column corners; `0` gives plain rectangles (default `--bar-border-radius` 3, always clamped to a third of the bar width).
|
|
840
|
+
|
|
841
|
+
### Bar.createVerticalStacked
|
|
842
|
+
|
|
843
|
+
Vertical stacked columns. Example: `examples/bar-chart/vertical-stacked-example.html`.
|
|
844
|
+
|
|
845
|
+
| Key | Default | Description |
|
|
846
|
+
|-----|---------|-------------|
|
|
847
|
+
| `keys` / `labels` / `colors` | `[]` / `{}` / `getAreaColors().slice(0, keys.length)` | Series config. |
|
|
848
|
+
| `xAccessor` | `d => d.x` | Category. |
|
|
849
|
+
| `xFormat` / `yFormat` | raw / `d3.format(',')` | Ticks. |
|
|
850
|
+
| `valueFormat` | `yFormat` | Segment labels + tooltip. |
|
|
851
|
+
| `showSegmentLabels` | `true` | Labels inside segments. |
|
|
852
|
+
| `axisTicks` | 5 | Y grid lines. |
|
|
853
|
+
|
|
854
|
+
Returns extras: `colorScale`.
|
|
855
|
+
|
|
856
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
857
|
+
|
|
858
|
+
### Bar.createVerticalNormalized
|
|
859
|
+
|
|
860
|
+
100% stacked vertical columns. Example: `examples/bar-chart/vertical-normalized-example.html`. Keys as `createVerticalStacked` (no `yFormat`/`axisTicks` control — y is fixed 0–100%); `valueFormat` default `Math.round(v) + '%'`; tooltip values are percentages. Returns extras: `colorScale`.
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
`borderRadius` (px) rounds the bar corners; `0` gives plain rectangles (default `--bar-border-radius` 3).
|
|
865
|
+
|
|
866
|
+
## Scatter charts (18)
|
|
867
|
+
|
|
868
|
+
Default number format on axes/tooltips (`defaultNumFormat`): integers raw, floats to 1 decimal.
|
|
869
|
+
|
|
870
|
+
### Scatter.createBasic
|
|
871
|
+
|
|
872
|
+
X/Y scatter with optional category coloring and dot style presets. Example: `examples/scatter-plot/basic-example.html`.
|
|
873
|
+
|
|
874
|
+
Data: `{ x, y, category?, name? }`
|
|
875
|
+
|
|
876
|
+
| Key | Default | Description |
|
|
877
|
+
|-----|---------|-------------|
|
|
878
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Position. |
|
|
879
|
+
| `categoryAccessor` | `d => d.category` | Colors dots by category when present. |
|
|
880
|
+
| `nameAccessor` | `d => d.name` | Tooltip title (falls back to category, then `'Point'`). |
|
|
881
|
+
| `colors` | `getAreaColors()` | Category palette (assigned in encounter order). |
|
|
882
|
+
| `colorMap` | — | Explicit `{ category: color }` lookup (order-independent, unlike `colors`); unmapped categories fall back to the `colors` palette. |
|
|
883
|
+
| `dotRadius` | preset `pointRadius` (5) | Dot size. |
|
|
884
|
+
| `sizeAccessor` | — | `(d) => radius` per-dot size (overrides `dotRadius`). |
|
|
885
|
+
| `dotStyle` | `'filled'` | One of `DOT_STYLES`: `filled`, `outline`, `translucent`, `glow`, `ring`, `frosted`, `target`, `shadow`. |
|
|
886
|
+
| `hoverGuides` | `false` | `true` draws dotted droplines from the hovered dot to both axes (vertical to the x axis, horizontal to the y axis) — ties the tooltip's x/y readout back to the scales. Styled like the trend hover guide (`--color-text-faint`, `--hover-dash-pattern`). |
|
|
887
|
+
| `dotOpacity` | the style's opacity (0.7 if unset) | Dot opacity. An explicit value (including `0`) wins over the `dotStyle` preset's own opacity. |
|
|
888
|
+
| `xLabel` / `yLabel` | — | Axis titles. |
|
|
889
|
+
| `dotLabels` | off | Direct adjacent labels on chosen dots — the "name the 10 biggest dots in the cloud" pattern, with the 2D de-collision handled for you. `{ accessor, filter?, topN?, priority?, placement?, fontSize?, fontWeight?, color?, leader? }`; a bare field name or `(d) => string` is shorthand for `accessor`. `accessor` supplies the text; candidates are `filter`ed and/or cut to `topN` by `priority` (field name or `(d) => number`, default: dot radius — pass one on equal-radius charts to make `topN` meaningful). Each label tries its preferred `placement` side (`'below'` default), flips through the other sides, then steps outward — the first spot clear of every placed label, every candidate dot, and the plot bounds wins; an unresolvable overlap **drops the lowest-priority label** rather than overprint. A label displaced past `--annotation-leader-threshold` (14px) gets a hairline leader tick back to its dot — `leader: 'auto'` (default) / `'force'` / `'off'`, matching the annotation convention. `color` is a constant or `(d) => color` (default `--color-text-subtle`); glyphs are haloed in the container background, and labels re-place on every resize. Example: `examples/scatter-plot/dot-labels-example.html`. |
|
|
890
|
+
|
|
891
|
+
Returns extras: `colorScale`, `defs`, `dotStyleConfig`.
|
|
892
|
+
|
|
893
|
+
### Scatter.createLogos
|
|
894
|
+
|
|
895
|
+
Scatter with images instead of dots. Example: `examples/scatter-plot/logos-example.html`.
|
|
896
|
+
|
|
897
|
+
Data: `{ x, y, logo (or image/imageUrl), name }`
|
|
898
|
+
|
|
899
|
+
| Key | Default | Description |
|
|
900
|
+
|-----|---------|-------------|
|
|
901
|
+
| `imageAccessor` | `d.image \|\| d.imageUrl \|\| d.logo` | Image URL. |
|
|
902
|
+
| `labelAccessor` | `d.name \|\| d.label` | Tooltip name. |
|
|
903
|
+
| `imageSize` | 24 | Logo px. |
|
|
904
|
+
| `borderRadius` | 4 | Logo corner clip. |
|
|
905
|
+
| `xLabel` / `yLabel`, `xFormat` / `yFormat` | as createBasic | — (`valueFormat` is accepted but unused here too). |
|
|
906
|
+
|
|
907
|
+
Returns extras: `defs`.
|
|
908
|
+
|
|
909
|
+
### Scatter.createAnimated
|
|
910
|
+
|
|
911
|
+
`createBasic` + continuous random-walk animation with fading gradient trails. Example: `examples/scatter-plot/animated-example.html`.
|
|
912
|
+
|
|
913
|
+
Extra keys beyond `createBasic`:
|
|
914
|
+
|
|
915
|
+
| Key | Default | Description |
|
|
916
|
+
|-----|---------|-------------|
|
|
917
|
+
| `animInterval` | 2000 | Ms between moves (move takes 70% of it). |
|
|
918
|
+
| `showTrails` | `true` | Gradient trails behind dots. |
|
|
919
|
+
| `trailOpacity` | 0.7 | Trail head opacity. |
|
|
920
|
+
| `trailWidth` | 2.5 | Trail stroke width. |
|
|
921
|
+
| `animate` | `true` | `false` = static render, no interval. |
|
|
922
|
+
|
|
923
|
+
Returns extras: everything from `createBasic` + `stopAnimation()` (the responsive wrapper calls it automatically on re-render/destroy).
|
|
924
|
+
|
|
925
|
+
### Scatter.createTrajectory
|
|
926
|
+
|
|
927
|
+
Connected path trails per entity. Example: `examples/scatter-plot/trajectory-example.html`.
|
|
928
|
+
|
|
929
|
+
Data: `[{ name, category, points: [{x, y}, ...] }]`
|
|
930
|
+
|
|
931
|
+
| Key | Default | Description |
|
|
932
|
+
|-----|---------|-------------|
|
|
933
|
+
| `categoryAccessor` | `d => d.category` | Color grouping. |
|
|
934
|
+
| `colors` | `getAreaColors()` | Palette. |
|
|
935
|
+
| `xFormat` / `yFormat` | `defaultNumFormat` | Ticks/tooltip. |
|
|
936
|
+
|
|
937
|
+
Returns extras: `colorScale`.
|
|
938
|
+
|
|
939
|
+
### Scatter.createComet
|
|
940
|
+
|
|
941
|
+
Before→after comet tails. Example: `examples/scatter-plot/comet-example.html`.
|
|
942
|
+
|
|
943
|
+
Data: `[{ name, x1, y1, x2, y2, category }]` — fixed keys (tail at 1, head at 2).
|
|
944
|
+
|
|
945
|
+
| Key | Default | Description |
|
|
946
|
+
|-----|---------|-------------|
|
|
947
|
+
| `categoryAccessor` / `colors` | `d.category` / `getAreaColors()` | Coloring. |
|
|
948
|
+
| `headRadius` | 8 (mini 5) | Head dot. |
|
|
949
|
+
| `tailRadius` | 4 (mini 2.5) | Tail dot. |
|
|
950
|
+
| `xFormat` / `yFormat` | `defaultNumFormat` | Ticks/tooltip. |
|
|
951
|
+
|
|
952
|
+
Returns extras: `colorScale`, `defs`.
|
|
953
|
+
|
|
954
|
+
### Scatter.createBeeswarm
|
|
955
|
+
|
|
956
|
+
Horizontal beeswarm grouped into category rows. Demonstrated in `examples/scatter-plot/beeswarm-example.html`.
|
|
957
|
+
|
|
958
|
+
> **Placement is deterministic** across all beeswarm builders (Bostock's "dodge"): the value axis is exact and dots dodge along the other axis so **no two overlap** — the true-beeswarm look. (Previously a force simulation, which left dots bunched/overlapping in dense regions.) `simulationIterations` is now accepted but ignored. The intrinsic swarm width grows with dot count and `collisionRadius`; give a dense swarm (hundreds of dots, or multiple columns) enough container width, or it will exceed the plot and the safety clamp will pile the outermost dots against the edge.
|
|
959
|
+
|
|
960
|
+
Data: `{ value, category }`
|
|
961
|
+
|
|
962
|
+
| Key | Default | Description |
|
|
963
|
+
|-----|---------|-------------|
|
|
964
|
+
| `valueAccessor` | `d => d.value` | X position. |
|
|
965
|
+
| `categoryAccessor` | `d => d.category` | Row bands. |
|
|
966
|
+
| `colors` | `getAreaColors()` | Per-category. |
|
|
967
|
+
| `dotRadius` | preset (4) | Dot size. |
|
|
968
|
+
| `valueFormat` | `defaultNumFormat` | Ticks/tooltip. |
|
|
969
|
+
| `dotLabels` | off | Adjacent de-collided labels on chosen dots (`accessor`/`filter`/`topN`/`priority`/`placement`/`leader`) — same contract as `Scatter.createBasic`. |
|
|
970
|
+
|
|
971
|
+
Returns: `{ svg, x, yBand, colorScale, width, height }`.
|
|
972
|
+
|
|
973
|
+
### Scatter.createBeeswarmVertical
|
|
974
|
+
|
|
975
|
+
Vertical beeswarm (value on y), optional single column, labels inside dots. Example: `examples/scatter-plot/beeswarm-vertical-example.html`.
|
|
976
|
+
|
|
977
|
+
| Key | Default | Description |
|
|
978
|
+
|-----|---------|-------------|
|
|
979
|
+
| `valueAccessor` | `d => d.value` | Y position. |
|
|
980
|
+
| `categoryAccessor` | — | Column grouping; omitting it implies `singleColumn: true`. |
|
|
981
|
+
| `singleColumn` | `!categoryAccessor` | One centered column. |
|
|
982
|
+
| `colorAccessor` | `null` | `(d) => color` per item (wins over palette). |
|
|
983
|
+
| `colors` | `getAreaColors()` | Category palette. |
|
|
984
|
+
| `labelAccessor` | `null` | Text drawn inside each dot. |
|
|
985
|
+
| `nameAccessor` | `d => d.name` | Tooltip title. |
|
|
986
|
+
| `dotRadius` | preset (4) | Dot size. |
|
|
987
|
+
| `collisionRadius` | `dotRadius + 2` | Min center-to-center spacing is `2 × collisionRadius` (drives the deterministic dodge). |
|
|
988
|
+
| `dotStroke` / `dotStrokeWidth` / `dotOpacity` | white / auto / 1 | Dot ring styling. **`dotStrokeWidth` defaults from the dot packing**: `min(2, collisionRadius − dotRadius)`. With default spacing (`collisionRadius = dotRadius + 2`) that's the classic 2px halo; pack dots tight (small margin) and the ring auto-thins to a hairline — or vanishes — so a dense cloud doesn't render as a ring lattice. Because both radii are fixed config (not pixel density), this is resize-invariant. Pass an explicit `dotStrokeWidth` (including `0`) to override; see `examples/recreations/congress-story.html`, Chapter 3, which sets `collisionRadius: 4, dotRadius: 3.5` and gets a 0.5px ring with no stroke config at all. |
|
|
989
|
+
| `labelColor` / `labelFontSize` / `labelFontWeight` | white / `'8px'` / `'bold'` | In-dot labels. |
|
|
990
|
+
| `yDomain` | extent padded | Value axis. |
|
|
991
|
+
| `yTicks` | 8 | Grid lines. |
|
|
992
|
+
| `yLabel` | — | Axis title. |
|
|
993
|
+
| `showGrid` | `true` | Horizontal grid. |
|
|
994
|
+
| `valueFormat` | `defaultNumFormat` | **Axis** tick format. |
|
|
995
|
+
| `tooltipValueFormat` | `valueFormat` | **Tooltip** value format. |
|
|
996
|
+
| `labelFormat` | `null` | Tooltip label line. |
|
|
997
|
+
| `dotLabels` | off | Adjacent de-collided labels OUTSIDE chosen dots (`accessor`/`filter`/`topN`/`priority`/`placement`/`leader`) — same contract as `Scatter.createBasic`; complements the in-dot `labelAccessor`. |
|
|
998
|
+
|
|
999
|
+
Note the split: `valueFormat` formats the axis, `tooltipValueFormat` the tooltip. Returns: `{ svg (root), g, y, xBand, colorScale, dots, width, height }`.
|
|
1000
|
+
|
|
1001
|
+
### Scatter.createBeeswarmHorizontal
|
|
1002
|
+
|
|
1003
|
+
Horizontal single-row beeswarm; renders logos when `imageAccessor` is given, dots otherwise. Example: `examples/scatter-plot/beeswarm-horizontal-example.html`.
|
|
1004
|
+
|
|
1005
|
+
| Key | Default | Description |
|
|
1006
|
+
|-----|---------|-------------|
|
|
1007
|
+
| `valueAccessor` | `d => d.value` | X position. |
|
|
1008
|
+
| `colorAccessor` / `colors` / `categoryAccessor` | as vertical | Coloring. |
|
|
1009
|
+
| `imageAccessor` | `null` | Logo mode when set. |
|
|
1010
|
+
| `logoSize` | 28 | Logo px (logo mode). |
|
|
1011
|
+
| `dotRadius`, `dotStroke`, `dotStrokeWidth`, `dotOpacity` | as vertical | Dot mode. |
|
|
1012
|
+
| `nameAccessor` | `d => d.name` | Tooltip. |
|
|
1013
|
+
| `xDomain` | extent padded | Value axis. |
|
|
1014
|
+
| `xTicks` | 8 | Grid. |
|
|
1015
|
+
| `xLabel` | — | Axis title. |
|
|
1016
|
+
| `showGrid` | `true` | Vertical grid. |
|
|
1017
|
+
| `valueFormat` / `labelFormat` | `defaultNumFormat` / `null` | Axis / tooltip label. |
|
|
1018
|
+
| `margin` | `{top:20, right:30, bottom:50, left:30}` | Custom default. |
|
|
1019
|
+
| `dotLabels` | off | Adjacent de-collided labels on chosen dots/logos (`accessor`/`filter`/`topN`/`priority`/`placement`/`leader`) — same contract as `Scatter.createBasic`. |
|
|
1020
|
+
|
|
1021
|
+
Returns: `{ svg, g, x, colorScale, logos | dots, width, height }` (member name depends on mode).
|
|
1022
|
+
|
|
1023
|
+
### Scatter.createBeeswarmLogos
|
|
1024
|
+
|
|
1025
|
+
Vertical beeswarm of images. Example: `examples/scatter-plot/beeswarm-logos-example.html`.
|
|
1026
|
+
|
|
1027
|
+
| Key | Default | Description |
|
|
1028
|
+
|-----|---------|-------------|
|
|
1029
|
+
| `valueAccessor` | `d => d.value` | Y position. |
|
|
1030
|
+
| `imageAccessor` | `d.image \|\| d.logo` | Image URL. |
|
|
1031
|
+
| `nameAccessor` | `d => d.name` | Tooltip title. |
|
|
1032
|
+
| `logoSize` | 28 | Logo px. |
|
|
1033
|
+
| `collisionRadius` | `logoSize/2 + 2` | Collision radius. |
|
|
1034
|
+
| `dotOpacity` | 1 | Logo opacity. |
|
|
1035
|
+
| `yDomain` / `yTicks` / `yLabel` / `showGrid` | padded / 8 / — / `true` | Axis. |
|
|
1036
|
+
| `valueFormat` / `tooltipValueFormat` / `labelFormat` | axis / tooltip / tooltip-label | Same split as BeeswarmVertical. |
|
|
1037
|
+
| `margin` | `{top:20, right:40, bottom:30, left:60}` | Custom default. |
|
|
1038
|
+
| `dotLabels` | off | Adjacent de-collided labels on chosen logos (`accessor`/`filter`/`topN`/`priority`/`placement`/`leader`) — same contract as `Scatter.createBasic`. |
|
|
1039
|
+
|
|
1040
|
+
Returns: `{ svg, g, y, logos, width, height }`.
|
|
1041
|
+
|
|
1042
|
+
### Scatter.createHeatmap
|
|
1043
|
+
|
|
1044
|
+
Two modes. **Density mode** (set `gridSize`, or `densityMode: true`): bins raw x/y points into a pixel grid. **Categorical mode** (default): each row IS a cell — `{ x, y, value }`. Example: `examples/scatter-plot/heatmap-example.html`.
|
|
1045
|
+
|
|
1046
|
+
| Key | Default | Description |
|
|
1047
|
+
|-----|---------|-------------|
|
|
1048
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Point (density) or cell coordinate (categorical). |
|
|
1049
|
+
| `gridSize` | — (10 in density mode) | Cell size px; setting it enables density mode. |
|
|
1050
|
+
| `densityMode` | `gridSize` | Explicit density-mode switch. |
|
|
1051
|
+
| `valueAccessor` | `d => d.value` | Cell value (categorical mode). |
|
|
1052
|
+
| `xDomain` / `yDomain` | `[0, 100]` (density mode) | Linear domains. |
|
|
1053
|
+
| `colorScheme` | `d3.interpolateYlOrRd` | Sequential interpolator — a `(t) => color` function or its palette name as a string (`'YlOrRd'`, `'viridis'`). |
|
|
1054
|
+
| `colorDomain` | data-driven (`[0, maxCount]` density / value extent categorical) | `[lo, hi]` pins the color scale's input domain in both modes; out-of-range values clamp to the ramp ends. Alias: `valueDomain` — pass the same domain to side-by-side heatmaps to make them color-comparable. |
|
|
1055
|
+
| `yOrder` | `'asc'` | Categorical-mode row ordering: `'asc'` sorts ascending bottom-to-top, `'desc'` flips it (ascending reads top-to-bottom), or an explicit array of category values in top-to-bottom reading order. |
|
|
1056
|
+
| `xFormat` / `yFormat` / `xLabel` / `yLabel` | `defaultNumFormat` / — | Axes. |
|
|
1057
|
+
| `valueFormat` | `defaultNumFormat` | Tooltip. |
|
|
1058
|
+
|
|
1059
|
+
Returns: `{ svg, g, x, y, colorScale, width, height, maxCount }`.
|
|
1060
|
+
|
|
1061
|
+
### Scatter.createHexbin
|
|
1062
|
+
|
|
1063
|
+
Hexagonal density binning — uses the d3-hexbin plugin (`d3.hexbin`) when it's loaded, and falls back to a built-in hex-binning implementation when it isn't. Example: `examples/scatter-plot/hexbin-example.html`.
|
|
1064
|
+
|
|
1065
|
+
| Key | Default | Description |
|
|
1066
|
+
|-----|---------|-------------|
|
|
1067
|
+
| `xAccessor` / `yAccessor` | `d.x \|\| d[0]` / `d.y \|\| d[1]` | Points. |
|
|
1068
|
+
| `hexRadius` | 8 | Hex size px. |
|
|
1069
|
+
| `xDomain` / `yDomain` | data extent | Domains. |
|
|
1070
|
+
| `colorScheme` | `d3.interpolateViridis` | Density color — a `(t) => color` interpolator function or its name as a string. |
|
|
1071
|
+
| `showShadow` | `true` | Drop-shadow filter on hexes. |
|
|
1072
|
+
| `xLabel` / `yLabel` | — | Axis titles. |
|
|
1073
|
+
|
|
1074
|
+
Returns: `{ svg, g, x, y, bins, maxCount, width, height }`.
|
|
1075
|
+
|
|
1076
|
+
### Scatter.createContour
|
|
1077
|
+
|
|
1078
|
+
Filled density contours. Example: `examples/scatter-plot/contour-example.html`.
|
|
1079
|
+
|
|
1080
|
+
| Key | Default | Description |
|
|
1081
|
+
|-----|---------|-------------|
|
|
1082
|
+
| `xAccessor` / `yAccessor` | `d.x \|\| d[0]` / `d.y \|\| d[1]` | Points. |
|
|
1083
|
+
| `bandwidth` | 15 | KDE smoothing. |
|
|
1084
|
+
| `thresholds` | 20 | Contour levels. |
|
|
1085
|
+
| `colorScheme` | `d3.interpolateYlGnBu` | Fill — a `(t) => color` interpolator function or its name as a string. |
|
|
1086
|
+
| `fillOpacity` | 0.7 | Contour fill opacity. |
|
|
1087
|
+
| `showLines` | `true` | Contour outlines. |
|
|
1088
|
+
| `lineColor` / `lineWidth` | `'rgba(0,0,0,0.15)'` / 0.5 | Outline styling. |
|
|
1089
|
+
| `xDomain` / `yDomain` | padded extent | Domains. |
|
|
1090
|
+
| `xFormat` / `yFormat` / `xLabel` / `yLabel` | `defaultNumFormat` / — | Axes. |
|
|
1091
|
+
|
|
1092
|
+
Returns: `{ svg, g, x, y, colorScale, contours, maxDensity, width, height }`.
|
|
1093
|
+
|
|
1094
|
+
### Scatter.createQuadrant
|
|
1095
|
+
|
|
1096
|
+
Four-quadrant analysis with switchable center. Example: `examples/scatter-plot/quadrant-example.html`.
|
|
1097
|
+
|
|
1098
|
+
Data: `{ x, y, category, name, size? }`
|
|
1099
|
+
|
|
1100
|
+
| Key | Default | Description |
|
|
1101
|
+
|-----|---------|-------------|
|
|
1102
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Position. |
|
|
1103
|
+
| `categoryAccessor` / `nameAccessor` | `d.category` / `d.name` | Color / tooltip. |
|
|
1104
|
+
| `sizeAccessor` | `null` | Bubble sizing value. |
|
|
1105
|
+
| `sizeRange` | `[5, 18]` | Bubble radius range. |
|
|
1106
|
+
| `dotRadius` | 6 | Fixed radius when no `sizeAccessor`. |
|
|
1107
|
+
| `hoverGuides` | `false` | Dotted droplines from the hovered dot to both axes. |
|
|
1108
|
+
| `centerMode` | `'average'` | `'average'`, `'zero'`, or `'median'`. |
|
|
1109
|
+
| `centerX` / `centerY` | — | Explicit center (both required; wins over mode). |
|
|
1110
|
+
| `colorMap` | `null` | `{category: color}` mapping (wins over palette). |
|
|
1111
|
+
| `colors` | `getAreaColors()` | Palette. |
|
|
1112
|
+
| `quadrantLabels` | Stars / Question Marks / Cash Cows / Dogs | `{topRight, topLeft, bottomRight, bottomLeft}`. |
|
|
1113
|
+
| `quadrantColors` | subtle defaults | Same shape; background tints. |
|
|
1114
|
+
| `quadrantOpacity` | 0.04 | Tint opacity. |
|
|
1115
|
+
| `showGrid` | `true` | Grid lines. |
|
|
1116
|
+
| `tooltipLabel` | name → category | `(d) => string`. |
|
|
1117
|
+
| `xLabel` / `yLabel` / `xFormat` / `yFormat` | `''` / `defaultNumFormat` | Axes. |
|
|
1118
|
+
|
|
1119
|
+
Returns extras: `colorScale`, `sizeScale`, `update(mode)` (re-center to `'average'|'zero'|'median'`), `getCenter(mode)`. Prefer `chart.setConfig({centerMode})` if the state must survive a resize.
|
|
1120
|
+
|
|
1121
|
+
### Scatter.createDiagonal
|
|
1122
|
+
|
|
1123
|
+
Diagonal reference bands (e.g. offense vs defense). Example: `examples/scatter-plot/diagonal-example.html`.
|
|
1124
|
+
|
|
1125
|
+
| Key | Default | Description |
|
|
1126
|
+
|-----|---------|-------------|
|
|
1127
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Position. |
|
|
1128
|
+
| `reverseX` | `false` | Flip x so lower = better on the right. |
|
|
1129
|
+
| `bandOffsets` | `[100, 15, 10, 5, 0, -5, -10, -15, -100]` | Diagonal band boundaries (y−x offsets). |
|
|
1130
|
+
| `bandOpacity` | 0.5 | Band fill opacity. |
|
|
1131
|
+
| `greenScale` / `redScale` | built-in arrays | Band color ramps (good/bad sides). |
|
|
1132
|
+
| `dotRadius` | preset (5) | Dot size. |
|
|
1133
|
+
| `hoverGuides` | `false` | Dotted droplines from the hovered dot to both axes. |
|
|
1134
|
+
| `dotGoodColor` / `dotBadColor` | `--chart-green` / `--chart-red` | Dot color above/below the diagonal. |
|
|
1135
|
+
| `xFormat` / `yFormat` | `defaultNumFormat` | Axes/tooltip. |
|
|
1136
|
+
|
|
1137
|
+
### Scatter.createMarginal
|
|
1138
|
+
|
|
1139
|
+
Scatter with marginal histograms on top and right. Example: `examples/scatter-plot/marginal-example.html`. Aspect ratio 1.25.
|
|
1140
|
+
|
|
1141
|
+
| Key | Default | Description |
|
|
1142
|
+
|-----|---------|-------------|
|
|
1143
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Position. |
|
|
1144
|
+
| `categoryAccessor` / `colors` | `d.category` / `getAreaColors()` | Coloring. |
|
|
1145
|
+
| `binWidth` | 12 | Target px per histogram bin. |
|
|
1146
|
+
| `histHeight` | 40 | Marginal panel size px. |
|
|
1147
|
+
| `dotRadius` | preset (4) | Dot size. |
|
|
1148
|
+
|
|
1149
|
+
### Scatter.createMarginalKDE
|
|
1150
|
+
|
|
1151
|
+
Marginal KDE curves instead of histograms. Demonstrated in `examples/scatter-plot/showcase.html`.
|
|
1152
|
+
|
|
1153
|
+
| Key | Default | Description |
|
|
1154
|
+
|-----|---------|-------------|
|
|
1155
|
+
| `bandwidth` | 8 | KDE bandwidth. |
|
|
1156
|
+
| `kdeHeight` | 40 | Panel size (alias: `histHeight`). |
|
|
1157
|
+
| `categoryAccessor` / `colors` / `dotRadius` | as createMarginal | — |
|
|
1158
|
+
|
|
1159
|
+
Returns extras: `colorScale`.
|
|
1160
|
+
|
|
1161
|
+
### Scatter.createMarginalSimple
|
|
1162
|
+
|
|
1163
|
+
Single-color scatter with simple marginal histograms + rug marks. Example: `examples/scatter-plot/marginal-simple-example.html`.
|
|
1164
|
+
|
|
1165
|
+
| Key | Default | Description |
|
|
1166
|
+
|-----|---------|-------------|
|
|
1167
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Position. |
|
|
1168
|
+
| `nameAccessor` | `d => d.name` | Tooltip title (falls back to `'Point'`). |
|
|
1169
|
+
| `color` | `--chart-primary` | Everything. |
|
|
1170
|
+
| `rugLength` | 6 | Rug tick length px. |
|
|
1171
|
+
| `dotRadius` | preset (4) | Dot size. |
|
|
1172
|
+
|
|
1173
|
+
### Scatter.createKDE
|
|
1174
|
+
|
|
1175
|
+
2D kernel density fill with optional data dots. Example: `examples/scatter-plot/kde-example.html`.
|
|
1176
|
+
|
|
1177
|
+
| Key | Default | Description |
|
|
1178
|
+
|-----|---------|-------------|
|
|
1179
|
+
| `xAccessor` / `yAccessor` | `d.x` / `d.y` | Position. |
|
|
1180
|
+
| `bandwidth` | 18 | Density smoothing. |
|
|
1181
|
+
| `thresholds` | 8 | Density levels. |
|
|
1182
|
+
| `colorScheme` | `d3.interpolatePurples` | Fill — a `(t) => color` interpolator function or its name as a string. |
|
|
1183
|
+
| `showDots` | `true` | Overlay raw points. |
|
|
1184
|
+
| `dotColor` / `dotRadius` | `--chart-purple` / 2 | Point styling. |
|
|
1185
|
+
| `nameAccessor` | `d => d.name` | Tooltip title. |
|
|
1186
|
+
| `xFormat` / `yFormat` | `defaultNumFormat` | Axes/tooltip. |
|
|
1187
|
+
|
|
1188
|
+
---
|
|
1189
|
+
|
|
1190
|
+
## Dot charts (7)
|
|
1191
|
+
|
|
1192
|
+
Row charts: intrinsic height (`rows × --dot-row-height 36 + margins`), auto-measured left label margin — width-responsive only; `margin`/`height`/`aspectRatio` are not read (except `createUnitHistogram`'s own `margin`). All have a header row (`rowLabel` left, `valueLabel` centered) and return `xScale`/`yScale` (aliased to `x`/`y` by the facade).
|
|
1193
|
+
|
|
1194
|
+
### Dot.createBasic
|
|
1195
|
+
|
|
1196
|
+
One dot per category per row. Example: `examples/dot-chart/basic-example.html`.
|
|
1197
|
+
|
|
1198
|
+
Data: `{ name: 'Row', catA: 10, catB: 12 }` with `categories: [{ key: 'catA', label: 'A', color: '#...' }, ...]`
|
|
1199
|
+
|
|
1200
|
+
| Key | Default | Description |
|
|
1201
|
+
|-----|---------|-------------|
|
|
1202
|
+
| `categories` | `[]` (required) | `[{key, label?, color}]` — one dot per entry per row. |
|
|
1203
|
+
| `nameAccessor` | `d => d.name` | Row label. |
|
|
1204
|
+
| `rowLabel` / `valueLabel` | `'Item'` / `'Value'` | Header texts. |
|
|
1205
|
+
| `valueFormat` | `v.toFixed(1)` | Tooltip. |
|
|
1206
|
+
| `dotRadius` | 5 | Dot size. |
|
|
1207
|
+
| `axisTicks` | 5 | X ticks. |
|
|
1208
|
+
| `xMin` / `xMax` | floor/ceil of extent | X domain. |
|
|
1209
|
+
|
|
1210
|
+
### Dot.createDumbbell
|
|
1211
|
+
|
|
1212
|
+
Two dots joined by a connector. Example: `examples/dot-chart/dumbbell-example.html`.
|
|
1213
|
+
|
|
1214
|
+
Data: `{ name, start, end }` (aliases: `beforeAccessor`/`afterAccessor`)
|
|
1215
|
+
|
|
1216
|
+
| Key | Default | Description |
|
|
1217
|
+
|-----|---------|-------------|
|
|
1218
|
+
| `nameAccessor` | `d => d.name` | Row label. |
|
|
1219
|
+
| `startAccessor` / `endAccessor` | `d.start` / `d.end` | Endpoints. |
|
|
1220
|
+
| `startColor` / `endColor` | `--chart-primary` / `--color-text-muted` (#666) | Dot colors. |
|
|
1221
|
+
| `connectorColor` | `--dot-connector-color` (#ccc) | Line between dots. |
|
|
1222
|
+
| `startLabel` / `endLabel` | `'Start'` / `'End'` | Tooltip/legend labels. |
|
|
1223
|
+
| `showLabels` | `true` | Value labels beside dots. |
|
|
1224
|
+
| `rowLabel` / `valueLabel` / `valueFormat` / `dotRadius` | `'Item'` / `'Value'` / `toFixed(1)` / 5 | — |
|
|
1225
|
+
| `xMin` / `xMax` | extent rounded to 10s | X domain. |
|
|
1226
|
+
|
|
1227
|
+
Returns extras: `categories` (`[{key:'start', label, color}, {key:'end', ...}]` — ready for `createLegend`).
|
|
1228
|
+
|
|
1229
|
+
### Dot.createGradient
|
|
1230
|
+
|
|
1231
|
+
Dumbbell with a gradient connector fading start→end. Example: `examples/dot-chart/gradient-example.html`. Same keys as `createDumbbell` minus `connectorColor`/`showLabels` (gradient is built from `startColor`→`endColor`).
|
|
1232
|
+
|
|
1233
|
+
### Dot.createComet
|
|
1234
|
+
|
|
1235
|
+
Before→after comet tails with trend coloring and optional animation. Example: `examples/dot-chart/comet-example.html`.
|
|
1236
|
+
|
|
1237
|
+
Data: `{ name, before, after }` (aliases: `startAccessor`/`endAccessor`)
|
|
1238
|
+
|
|
1239
|
+
| Key | Default | Description |
|
|
1240
|
+
|-----|---------|-------------|
|
|
1241
|
+
| `nameAccessor` | `d => d.name` | Row label. |
|
|
1242
|
+
| `beforeAccessor` / `afterAccessor` | `d.before` / `d.after` | Tail / head. |
|
|
1243
|
+
| `upColor` / `downColor` | `--chart-green` / `--chart-red` | Trend color of tail+head. |
|
|
1244
|
+
| `animate` | `true` | Tails grow from before to after on load. |
|
|
1245
|
+
| `sortBy` | — | `'change'` sorts rows by delta (descending). |
|
|
1246
|
+
| `beforeLabel` / `afterLabel` | `'Before'` / `'After'` | Tooltip labels. |
|
|
1247
|
+
| `rowLabel` / `valueLabel` / `valueFormat` | `'Item'` / `'Value'` / `toFixed(0)` | — |
|
|
1248
|
+
| `xMin` / `xMax` | extent rounded to 50s | X domain. |
|
|
1249
|
+
| `valuePrefix` | `''` | Prepended to the drawn head value label (e.g. `'+'`). |
|
|
1250
|
+
|
|
1251
|
+
### Dot.createConfidence
|
|
1252
|
+
|
|
1253
|
+
Dot + confidence-interval band per row; significance vs a control row is computed automatically (CI not overlapping the control's CI). Example: `examples/dot-chart/confidence-example.html`.
|
|
1254
|
+
|
|
1255
|
+
Data: `{ name, value, ci_low, ci_high, isControl? }`
|
|
1256
|
+
|
|
1257
|
+
| Key | Default | Description |
|
|
1258
|
+
|-----|---------|-------------|
|
|
1259
|
+
| `nameAccessor` | `d => d.name` | Row label (control row rendered semibold). |
|
|
1260
|
+
| `valueAccessor` | `d => d.value` | Point estimate. |
|
|
1261
|
+
| `lowAccessor` / `highAccessor` | `d.ci_low` / `d.ci_high` | CI bounds (aliases lower/upper, ciLow/ciHigh). |
|
|
1262
|
+
| `isControlAccessor` | `d => d.isControl` | Marks the control row. |
|
|
1263
|
+
| `controlColor` | `--color-text-muted` (#666) | Control row. |
|
|
1264
|
+
| `treatmentColor` | `--chart-primary` | Non-significant rows. |
|
|
1265
|
+
| `significantColor` | `--chart-green` | Rows whose CI clears the control's. |
|
|
1266
|
+
| `bandHeight` | 14 | CI band px. |
|
|
1267
|
+
| `rowHeight` | 40 | Row px (overrides token). |
|
|
1268
|
+
| `dotRadius` | 5 | Dot size. |
|
|
1269
|
+
| `rowLabel` / `valueLabel` / `valueFormat` | `'Condition'` / `'Value'` / `toFixed(1)` | — |
|
|
1270
|
+
| `xMin` / `xMax` | extent rounded to 5s | X domain. |
|
|
1271
|
+
|
|
1272
|
+
### Dot.createPercentile
|
|
1273
|
+
|
|
1274
|
+
Value dot over a percentile-band strip. Example: `examples/dot-chart/percentile-example.html`.
|
|
1275
|
+
|
|
1276
|
+
Data: `{ name (or season), value, p0, p10, ..., p100 }`
|
|
1277
|
+
|
|
1278
|
+
| Key | Default | Description |
|
|
1279
|
+
|-----|---------|-------------|
|
|
1280
|
+
| `percentileKeys` | `['p0','p10',...,'p100']` | Band boundary keys, in order. |
|
|
1281
|
+
| `valueAccessor` | `d => d.value` | Dot position. |
|
|
1282
|
+
| `nameAccessor` | `d.name \|\| d.season` | Row label. |
|
|
1283
|
+
| `bandColors` | built-in ramp | One per band segment. |
|
|
1284
|
+
| `dotColor` | `--color-black` | Dot. |
|
|
1285
|
+
| `bandHeight` | 18 | Strip px. |
|
|
1286
|
+
| `rowHeight` | 40 | Row px. |
|
|
1287
|
+
| `dotRadius` | 6 | Dot size. |
|
|
1288
|
+
| `rowLabel` / `valueLabel` / `valueFormat` | `'Period'` / `'Value'` / `toFixed(3)` | — |
|
|
1289
|
+
| `xMin` / `xMax` | extent ±0.01 | X domain. |
|
|
1290
|
+
|
|
1291
|
+
Returns extras: `bandColors`.
|
|
1292
|
+
|
|
1293
|
+
### Dot.createUnitHistogram
|
|
1294
|
+
|
|
1295
|
+
Stacked-unit (icon) histogram — one circle per unit, stacked per category bin. Example: `examples/dot-chart/unit-histogram-example.html`.
|
|
1296
|
+
|
|
1297
|
+
Data: `{ category: 'Mon', count: 12 }`
|
|
1298
|
+
|
|
1299
|
+
| Key | Default | Description |
|
|
1300
|
+
|-----|---------|-------------|
|
|
1301
|
+
| `categoryAccessor` / `countAccessor` | `d.category` / `d.count` | Bin / unit count. |
|
|
1302
|
+
| `dotSize` | 10 | Unit square px. |
|
|
1303
|
+
| `dotGap` | 3 | Gap between units. |
|
|
1304
|
+
| `binWidth` | 32 | Column width px. |
|
|
1305
|
+
| `unitLabel` | `'1 unit'` | Tooltip unit text. |
|
|
1306
|
+
| `categoryFormat` | raw | Bin label format. |
|
|
1307
|
+
| `color` | `--chart-primary` | Constant or `(categoryLabel, binIndex) => color` per bin (NOT the datum). |
|
|
1308
|
+
| `animated` | `true` | Staggered unit drop-in. |
|
|
1309
|
+
| `animationDuration` / `staggerDelay` / `binDelay` | 300 / 40 / 80 | Animation ms. |
|
|
1310
|
+
| `margin` | `{top:30, right:30, bottom:50, left:30}` | Own default. |
|
|
1311
|
+
|
|
1312
|
+
**Always fixed-size** (`responsive: false` in metadata): both dimensions derive from bin count and max stack. Returns: `{ svg, xScale, width, height, baselineY, dotSize, dotGap }`.
|
|
1313
|
+
|
|
1314
|
+
---
|
|
1315
|
+
|
|
1316
|
+
## Histograms (2)
|
|
1317
|
+
|
|
1318
|
+
Distribution charts over a numeric field. Both builders share the binning
|
|
1319
|
+
options (`bins` count hint or explicit thresholds; `binWidth` fixed-width
|
|
1320
|
+
override) and the y-value `mode`: `'count'`, `'percent'` (share of n),
|
|
1321
|
+
`'density'` (share per unit width), or `'cumulative'` (running share, ends
|
|
1322
|
+
at 1). `xDomain` also pins the binning domain. Null/NaN values are dropped
|
|
1323
|
+
before binning.
|
|
1324
|
+
|
|
1325
|
+
### Histogram.createBasic
|
|
1326
|
+
|
|
1327
|
+
Single-series histogram. Data: `{ value: 23 }` (field picked by `valueAccessor`).
|
|
1328
|
+
|
|
1329
|
+
| Key | Default | Description |
|
|
1330
|
+
|-----|---------|-------------|
|
|
1331
|
+
| `valueAccessor` | `d.value` | Numeric field to bin. |
|
|
1332
|
+
| `bins` | d3 Sturges | Bin-count hint or explicit interior thresholds array. |
|
|
1333
|
+
| `binWidth` | — | Fixed bin width (anchored at a multiple of itself); wins over `bins`. |
|
|
1334
|
+
| `mode` | `'count'` | `'count' \| 'percent' \| 'density' \| 'cumulative'`. |
|
|
1335
|
+
| `barGap` | `--histogram-bar-gap` (1) | Pixel gap between bin bars. |
|
|
1336
|
+
| `color` | `--chart-primary` | Bar fill. |
|
|
1337
|
+
|
|
1338
|
+
`yFormat` defaults per mode (grouped counts vs percentages);
|
|
1339
|
+
`tooltipValueFormat` receives the raw bin value. Returns
|
|
1340
|
+
`{ svg, x, y, width, height, bins }`.
|
|
1341
|
+
|
|
1342
|
+
### Histogram.createOverlay
|
|
1343
|
+
|
|
1344
|
+
Multi-series histogram over **shared bin edges** computed from the pooled
|
|
1345
|
+
values, so the shapes compare honestly. Translucent overlap by default;
|
|
1346
|
+
`style: 'grouped'` renders side-by-side columns within each bin. Bars carry
|
|
1347
|
+
`data-series` markers, so `legend: { interactive: true }` hover/click
|
|
1348
|
+
isolation works.
|
|
1349
|
+
|
|
1350
|
+
| Key | Default | Description |
|
|
1351
|
+
|-----|---------|-------------|
|
|
1352
|
+
| `series` | `[]` | `[{ name, data, color?, valueAccessor? }]` — one entry per distribution. Required. |
|
|
1353
|
+
| `valueAccessor` | `d.value` | Shared accessor (per-series override wins). |
|
|
1354
|
+
| `bins` / `binWidth` | d3 Sturges / — | Shared binning, as in createBasic. |
|
|
1355
|
+
| `mode` | `'count'` | Per-series y transform (`percent` = share of that series). |
|
|
1356
|
+
| `style` | `'overlay'` | `'overlay' \| 'grouped'`. |
|
|
1357
|
+
| `barGap` | `--histogram-bar-gap` (1) | Pixel gap between bins. |
|
|
1358
|
+
| `colors` | line palette | Series palette (`series[].color` wins). |
|
|
1359
|
+
|
|
1360
|
+
Returns `{ svg, x, y, width, height, series }` (per-series bin arrays).
|
|
1361
|
+
|
|
1362
|
+
---
|
|
1363
|
+
|
|
1364
|
+
## Donut / pie (1)
|
|
1365
|
+
|
|
1366
|
+
### Donut.createBasic
|
|
1367
|
+
|
|
1368
|
+
One arc primitive — donut by default, pie via `innerRatio: 0`. Slices under
|
|
1369
|
+
`minSlice` (share of total) aggregate into an "Other" slice (a lone
|
|
1370
|
+
qualifier stays itself; `otherLabel` renames it). Labels are
|
|
1371
|
+
collision-managed: `labels: 'callout'` (default) draws leader-line callouts
|
|
1372
|
+
stacked per side so they never overlap; `'inside'` puts labels mid-ring in
|
|
1373
|
+
slices wide enough for their measured text and falls back to callouts for
|
|
1374
|
+
the skinny ones; `'none'` leaves labeling to the shell `legend`. Slices carry
|
|
1375
|
+
`data-series` markers for interactive legends. No cartesian axes —
|
|
1376
|
+
axis/domain/annotation options are n/a.
|
|
1377
|
+
|
|
1378
|
+
Data: `{ label: 'Online', value: 4200 }`
|
|
1379
|
+
|
|
1380
|
+
| Key | Default | Description |
|
|
1381
|
+
|-----|---------|-------------|
|
|
1382
|
+
| `labelAccessor` / `valueAccessor` | `d.label` / `d.value` | Slice fields (zero/negative values dropped). |
|
|
1383
|
+
| `innerRatio` | `--donut-inner-ratio` (0.62) | Inner radius share; `0` = pie. |
|
|
1384
|
+
| `minSlice` | 0 | Aggregate slices under this share into "Other". |
|
|
1385
|
+
| `otherLabel` | `'Other'` | Aggregated slice label (colored `--chart-neutral`). |
|
|
1386
|
+
| `sort` | `true` | Sort slices descending before layout. |
|
|
1387
|
+
| `labels` | `'callout'` | `'callout' \| 'inside' \| 'none'`. |
|
|
1388
|
+
| `minLabelShare` | 0.02 | Hide labels on slices under this share. |
|
|
1389
|
+
| `showTotal` | `true` | Center total (donut only). |
|
|
1390
|
+
| `centerLabel` | — | Small caption under the center total. |
|
|
1391
|
+
| `totalFormat` | `valueFormat` | Center-total formatter. |
|
|
1392
|
+
| `valueFormat` | `toLocaleString` | Value formatter (tooltip + total). |
|
|
1393
|
+
| `shareFormat` | `12.3%` | Share formatter (labels + tooltip note). |
|
|
1394
|
+
| `colors` / `colorMap` | area palette / — | Palette, or explicit label → color map. |
|
|
1395
|
+
|
|
1396
|
+
`animate: false` skips the arc sweep. Returns `{ svg, width, height, slices }`
|
|
1397
|
+
(slices include `share`, `isOther`, and aggregated `members`).
|
|
1398
|
+
|
|
1399
|
+
---
|
|
1400
|
+
|
|
1401
|
+
## Box plots (1)
|
|
1402
|
+
|
|
1403
|
+
### BoxPlot.createBasic
|
|
1404
|
+
|
|
1405
|
+
Five-number distribution boxes per category. Accepts **raw rows**
|
|
1406
|
+
(`{ category, value }`, grouped and summarized internally) or **precomputed
|
|
1407
|
+
stats rows** (`{ name, q1, median, q3, lo|min, hi|max, outliers? }` —
|
|
1408
|
+
detected when `q1` and `median` are present). Vertical by default;
|
|
1409
|
+
`orientation: 'horizontal'` transposes (categories on y). The tooltip lists
|
|
1410
|
+
the five numbers plus an outlier count.
|
|
1411
|
+
|
|
1412
|
+
| Key | Default | Description |
|
|
1413
|
+
|-----|---------|-------------|
|
|
1414
|
+
| `categoryAccessor` / `valueAccessor` | `d.category` / `d.value` | Raw-input fields. |
|
|
1415
|
+
| `whiskers` | `'tukey'` | `'tukey'` (1.5×IQR clamped to data) \| `'minmax'` \| number k (k×IQR). |
|
|
1416
|
+
| `orientation` | `'vertical'` | `'horizontal'` transposes axes. |
|
|
1417
|
+
| `jitter` | `false` | Deterministic jittered raw points behind each box (raw input only). |
|
|
1418
|
+
| `color` / `colorMap` | `--chart-primary` / — | Shared box color, or per-category map. |
|
|
1419
|
+
| `valueFormat` | 2-decimal | Tooltip five-number formatter (`tooltipValueFormat` wins). |
|
|
1420
|
+
|
|
1421
|
+
`yDomain`/`yFormat`/`yTickValues` drive the value axis (in either
|
|
1422
|
+
orientation); the category axis is a band scale. Returns
|
|
1423
|
+
`{ svg, x, y, width, height, groups }` (per-category stats incl. outliers).
|
|
1424
|
+
|
|
1425
|
+
---
|
|
1426
|
+
|
|
1427
|
+
## Calendar heatmaps (1)
|
|
1428
|
+
|
|
1429
|
+
### Calendar.createHeatmap
|
|
1430
|
+
|
|
1431
|
+
Daily heatmap in three layouts. `'weekly'` (default) is the GitHub grid:
|
|
1432
|
+
weekly columns × weekday rows, month labels above, weekday labels left, and
|
|
1433
|
+
a subtly wider gutter (`monthGap`) where a new month begins.
|
|
1434
|
+
`layout: 'monthRows'` flips it: one row per month (names left), day-of-month
|
|
1435
|
+
columns (1–31, numbers above). `layout: 'vertical'` transposes the weekly grid into a wall calendar:
|
|
1436
|
+
weekday columns, weeks flowing downward, month names at each month's first
|
|
1437
|
+
week row — still one cell per day, with the month gutter opening vertically. **Missing days keep their cell**, drawn as a
|
|
1438
|
+
quiet track — absence reads as absence, not zero. Height is intrinsic
|
|
1439
|
+
(derived from the measured width and cell size), so the chart is
|
|
1440
|
+
width-responsive without an aspect ratio. No cartesian axes — axis/domain
|
|
1441
|
+
options are n/a; the color ramp is the encoding.
|
|
1442
|
+
|
|
1443
|
+
Data: `{ date: '2025-03-14', value: 4 }` (same-day rows aggregate).
|
|
1444
|
+
|
|
1445
|
+
| Key | Default | Description |
|
|
1446
|
+
|-----|---------|-------------|
|
|
1447
|
+
| `dateAccessor` / `valueAccessor` | `d.date` / `d.value` | Day key (YYYY-MM-DD or Date) and value. |
|
|
1448
|
+
| `layout` | `'weekly'` | `'weekly'` grid, `'monthRows'` (day-of-month columns), or `'vertical'` (transposed weekly). |
|
|
1449
|
+
| `weekStart` | 0 (Sunday) | First weekday (weekly rows / vertical columns). |
|
|
1450
|
+
| `monthGap` | `--calendar-month-gap` (3) | Extra px gutter at month starts (columns in weekly, rows in vertical). |
|
|
1451
|
+
| `aggregate` | `d3.sum` | Aggregator for same-date rows — any `(values) => number` reducer (`d3.mean`, `d3.max`, …). |
|
|
1452
|
+
| `colorScheme` | `interpolateGreens` | A `(t) => color` d3 interpolator function or its string name (`'Blues'`, `'YlOrRd'`, …). |
|
|
1453
|
+
| `colorDomain` | `[min(0, data min), max]` | Pins the ramp domain; out-of-range clamps. Alias: `valueDomain`. |
|
|
1454
|
+
| `dateFormat` | raw key | Tooltip title formatter. |
|
|
1455
|
+
| `monthFormat` | `'Mar'` | Month band formatter (receives `'YYYY-MM'`). |
|
|
1456
|
+
| `valueFormat` | `toLocaleString` | Tooltip value formatter (`tooltipValueFormat` wins). |
|
|
1457
|
+
|
|
1458
|
+
Returns `{ svg, width, height, cells, colorScale }` (cells include
|
|
1459
|
+
`missing: true` days).
|
|
1460
|
+
|
|
1461
|
+
---
|
|
1462
|
+
|
|
1463
|
+
## Sankey (1)
|
|
1464
|
+
|
|
1465
|
+
### Sankey.createBasic
|
|
1466
|
+
|
|
1467
|
+
Flow diagram over flat link rows — `{ source, target, value }`; nodes are
|
|
1468
|
+
derived in first-appearance order and colored from the palette (or
|
|
1469
|
+
`colorMap`). Ribbons default to a **source→target gradient** (`linkColor`
|
|
1470
|
+
switches to `'source'`, `'target'`, or a literal color) at
|
|
1471
|
+
`--sankey-link-opacity`. **Hovering a node highlights its whole
|
|
1472
|
+
connection** — every link and node reachable upstream and downstream — and
|
|
1473
|
+
dims the rest.
|
|
1474
|
+
Non-positive/null values and self-loops are dropped; a cyclic flow warns and
|
|
1475
|
+
renders nothing. Layout: **d3-sankey when available** (pass the module via
|
|
1476
|
+
`sankey` in ESM builds — `import * as d3Sankey from 'd3-sankey'` — or load
|
|
1477
|
+
the UMD script so it attaches to the d3 global), otherwise a **built-in
|
|
1478
|
+
layered layout** (longest-path columns, barycenter ordering) renders with no
|
|
1479
|
+
plugin at all. No cartesian axes — axis/domain/annotation options are n/a.
|
|
1480
|
+
|
|
1481
|
+
Data: `{ source: 'Search', target: 'Product', value: 320 }`
|
|
1482
|
+
|
|
1483
|
+
| Key | Default | Description |
|
|
1484
|
+
|-----|---------|-------------|
|
|
1485
|
+
| `sourceAccessor` / `targetAccessor` | `d.source` / `d.target` | Link endpoints (node names). |
|
|
1486
|
+
| `valueAccessor` | `d.value` | Flow value (> 0). |
|
|
1487
|
+
| `linkColor` | `'gradient'` | `'gradient' \| 'source' \| 'target'` or a literal color. |
|
|
1488
|
+
| `orientation` | `'horizontal'` | `'vertical'` flows top → bottom (pair with `aspectRatio` ≈ 1.0). |
|
|
1489
|
+
| `nodeAlign` | `'justify'` | `'justify' \| 'left' \| 'right' \| 'center'` (fallback layout: justify/left). |
|
|
1490
|
+
| `nodeWidth` | `--sankey-node-width` (12) | Node bar width. |
|
|
1491
|
+
| `nodePadding` | `--sankey-node-padding` (12) | Vertical gap between nodes. |
|
|
1492
|
+
| `nodeValues` | — | `{ name: total }` floors (funnel mode; uses the built-in layout, defaults `linkShape` to taper). |
|
|
1493
|
+
| `linkShape` | `'ribbon'` | `'taper'`: filled shapes leaving the source at full bar width, narrowing to the link width at the target. |
|
|
1494
|
+
| `showValues` | `true` | Append node totals to labels. |
|
|
1495
|
+
| `valueFormat` | `toLocaleString` | Node/link value formatter. |
|
|
1496
|
+
| `colors` / `colorMap` | area palette / — | Node palette, or explicit name → color map. |
|
|
1497
|
+
| `sankey` | — | Optional d3-sankey module handle (ESM builds). |
|
|
1498
|
+
|
|
1499
|
+
Tooltips: links show `source → target` + value; nodes show name + throughput.
|
|
1500
|
+
On load the flow **sweeps in column by column**: roots fade in, each
|
|
1501
|
+
column's links draw over `--sankey-anim-duration`, a node reveals exactly
|
|
1502
|
+
when its last incoming ribbon completes, and the flow rests
|
|
1503
|
+
`--sankey-anim-pause` at every node before flowing on. `animate: false`
|
|
1504
|
+
renders the final state immediately, and resizes never replay the sweep.
|
|
1505
|
+
Returns `{ svg, width, height, nodes, links }` (positioned layout objects).
|
|
1506
|
+
|
|
1507
|
+
---
|
|
1508
|
+
|
|
1509
|
+
## Tables (5)
|
|
1510
|
+
|
|
1511
|
+
DOM `<table>`-based (not SVG, **not responsive-wrapped**). Requires one-time registration and the table CSS:
|
|
1512
|
+
|
|
1513
|
+
```html
|
|
1514
|
+
<link rel="stylesheet" href="src/table/tokens.css">
|
|
1515
|
+
<link rel="stylesheet" href="src/table/table.css">
|
|
1516
|
+
<script type="module">
|
|
1517
|
+
import { D3Table } from './src/table/d3-table.js';
|
|
1518
|
+
ChartFactory.Table.register(D3Table);
|
|
1519
|
+
</script>
|
|
1520
|
+
```
|
|
1521
|
+
|
|
1522
|
+
The `selector` is an element **ID without `#`** — ideally an existing `<table id="...">` with empty `<thead></thead><tbody></tbody>`, but any container element works (a `<table><thead><tbody>` is created inside non-table targets; an unmatched id `console.warn`s instead of failing silently). All five builders construct a `D3Table(selector, config)`; call `.render(data)` yourself.
|
|
1523
|
+
|
|
1524
|
+
**Layout:** by default (`layout: 'fill'`) the table fills its container — headers and text cells wrap (right-aligned/numeric/secondary cells never do) — and carries a computed min-width floor inside an auto-created `.d3-table-scroll` wrapper that scrolls horizontally when the container drops below the floor. `layout: 'fixed'` restores the legacy content-measured static widths.
|
|
1525
|
+
|
|
1526
|
+
**Sorting UI:** idle sortable columns show no glyph — sortability is signaled by the pointer cursor and hover/focus fill. The actively sorted column shows a small chevron (before the label on right-aligned columns, after it otherwise); sortable headers are real `<button>`s with `aria-sort` on the `<th>`. A sortable table pre-sorts on load using the first column with `defaultSort`; first click sorts text columns ascending, numeric/record columns descending. After a *user-initiated* sort, the sorted column (header included) gets a quiet neutral wash.
|
|
1527
|
+
|
|
1528
|
+
- **Table.createBasic** — `{ sortable: false, ...config }`.
|
|
1529
|
+
- **Table.createSortable** — `{ sortable: true, ...config }`.
|
|
1530
|
+
- **Table.createWithBars** — sortable; warns if `barColumns` is missing.
|
|
1531
|
+
- **Table.createWithHeatmap** — sortable; warns if `heatmapColumns` is missing.
|
|
1532
|
+
- **Table.createCombined** — sortable; pass both `barColumns` and `heatmapColumns`.
|
|
1533
|
+
|
|
1534
|
+
Examples: `examples/table/showcase.html`, `examples/table/sparkline-example.html`, `examples/table/percent-bar-example.html`, `examples/table/logos-example.html`.
|
|
1535
|
+
|
|
1536
|
+
### Table config
|
|
1537
|
+
|
|
1538
|
+
| Key | Default | Description |
|
|
1539
|
+
|-----|---------|-------------|
|
|
1540
|
+
| `columns` | required | Array of column definitions (below). |
|
|
1541
|
+
| `sortable` | per builder | Global sorting switch (columns still need `sortable: true`). |
|
|
1542
|
+
| `highlightSortedColumn` | unset | Sorted-column wash. Unset: only after a user-initiated sort; `true`: always; `false`: never. |
|
|
1543
|
+
| `highlightRows` | — | Array of `{key, value}` conditions, or plain strings matched against any cell value in the row. |
|
|
1544
|
+
| `layout` | `'fill'` | `'fill'` fits content up to the container width — columns take their natural widths, bar columns grow from their label-fit floor toward `--bar-column-max-width` (or their `maxWidth`) using the measured container slack (re-run on container resize), and leftover width stays outside the table as whitespace; below the readable floor the table scrolls horizontally. `'fixed'` is the legacy static render. |
|
|
1545
|
+
| `minTableWidth` | computed | Fill layout: override the readable-floor min-width (px). Default sums per-column-type minimums. |
|
|
1546
|
+
| `subRows` | — | Key of a row field holding an array of child rows (same column shape). Parents get a chevron toggle; children render indented, hidden until expanded, and re-sort within their parent. Expansion survives sorting and `update()`. |
|
|
1547
|
+
| `defaultExpanded` | `false` | With `subRows`: expand every parent on first render. |
|
|
1548
|
+
| `stickyHeader` | `false` | Header sticks while scrolling — inside the wrapper with `maxHeight`, to the page otherwise (real `position: sticky` when the table fits its container; a scroll-driven floating header when it overflows horizontally). |
|
|
1549
|
+
| `maxHeight` | — | With `stickyHeader`: cap table height (px); the wrapper scrolls vertically. |
|
|
1550
|
+
| `stickyFirstColumn` | `false` | First column pins during horizontal scroll, with an edge shadow while scrolled. |
|
|
1551
|
+
| `maxRows` | — | Show only the first N top-level rows with a "Show all N" toggle below the table. |
|
|
1552
|
+
| `showAllLabel` | `Show all N` | Override the expand label of the `maxRows` toggle. |
|
|
1553
|
+
| `showLessLabel` | `Show fewer` | Override the collapse label of the `maxRows` toggle. |
|
|
1554
|
+
| `footerRow` | — | Single-row shorthand for `footerRows`. |
|
|
1555
|
+
| `footerRows` | — | Row objects pinned in `tfoot` as bold summary rows (totals, per-game averages), exempt from sorting and `maxRows`. First row gets the heavy total rule. |
|
|
1556
|
+
| `barColumns` | — | `[{key, colorScale}]` — renders animated percent bars in those columns. Domain `[0, max]`. |
|
|
1557
|
+
| `heatmapColumns` | — | `[{key, colorScale}]` — heatmap boxes with auto contrast text. Domain `[min, max]`. |
|
|
1558
|
+
|
|
1559
|
+
Color scales: `'orange'`, `'blue'`, `'green'`, `'purple'`, `'teal'`, `'aqua'`, `'diverging'` (for +/- values).
|
|
1560
|
+
|
|
1561
|
+
Table tokens (`--bar-column-min-width`, `--table-image-size`, …) are read from the table's **own container**, so scoping an override on the wrapping element (`#my-table-wrap { --bar-column-min-width: 90px }`) works exactly like it does in CSS — no `:root` surgery needed.
|
|
1562
|
+
|
|
1563
|
+
### Column config
|
|
1564
|
+
|
|
1565
|
+
| Key | Default | Description |
|
|
1566
|
+
|-----|---------|-------------|
|
|
1567
|
+
| `key` | required | Row-object field. |
|
|
1568
|
+
| `header` | required | Header text (rendered uppercase). |
|
|
1569
|
+
| `sortable` | `false` | Enable click-to-sort on this column. |
|
|
1570
|
+
| `sortType` | `'numeric'` | `'numeric'`, `'text'`, or `'record'` (W-L strings, sorts by wins). |
|
|
1571
|
+
| `defaultSort` | by type | First-click direction (text `'asc'`, numeric/record `'desc'`). The first column with an explicit `defaultSort` also pre-sorts the table on load. |
|
|
1572
|
+
| `format` | — | `'currency'`, `'decimal'`, `'decimal2'`, `'integer'`, `'percentage'`. |
|
|
1573
|
+
| `align` | left | `'left'`, `'center'`, `'right'`. |
|
|
1574
|
+
| `className` | — | `'primary-cell'`, `'secondary-cell'`, `'muted-cell'` (affects auto-width sizing too). |
|
|
1575
|
+
| `render` | — | `(value, row) => htmlString` custom cell renderer. |
|
|
1576
|
+
| `subtitle` | — | Key of another field rendered as a second line under the value. |
|
|
1577
|
+
| `group` | — | Consecutive columns sharing a `group` string get one spanning header in an extra row above the column headers ("Passing" over passYds/passTd). |
|
|
1578
|
+
| `emphasize` | — | `(value, row) => false \| true \| 'strong'` — per-cell emphasis: `true` bold, `'strong'` bold-italic. Wraps the final cell content; composes with `format`/`render`. |
|
|
1579
|
+
| `image` | — | Key of a field holding an image URL (flag/logo/headshot) rendered before the cell text. Composes with `subtitle`/`format`/`render`; rows without a URL keep an invisible slot so text stays aligned. |
|
|
1580
|
+
| `imageShape` | `'rounded'` | `'circle'` (headshots), `'rounded'`, `'square'`. |
|
|
1581
|
+
| `imageSize` | token (22px) | Image width/height in px (`--table-image-size`). |
|
|
1582
|
+
| `imageFit` | by shape | `'cover'` or `'contain'` — defaults to cover for circles, contain otherwise (logos letterbox). |
|
|
1583
|
+
| `minWidth` / `maxWidth` | class-based caps | Explicit per-column bounds in px, applied as inline styles so they beat the token/static-CSS clamps in every layout. `minWidth` is a floor (only raises computed widths); `maxWidth` is a hard cap — except on a bar column, where it never shrinks below the value-label requirement (a warn fires instead of clipping). |
|
|
1584
|
+
| `wrap` | `false` | `true` lets this column's data cells wrap to multiple lines (long notes/descriptions) instead of the auto-width single-line default. In fill layout the first wrap column also becomes the table's flexible column: spare container width un-wraps its text (bar columns then pin at their cap instead of absorbing the slack), and it wraps only when space runs out (floor `--table-wrap-min-width`, 110px). Line-height via `--table-wrap-line-height`. |
|
|
1585
|
+
| `autoWidth` | `true` | `false` skips the measured-width class. |
|
|
1586
|
+
| `highlight` | `false` | Permanently highlight this column. |
|
|
1587
|
+
|
|
1588
|
+
### Instance methods
|
|
1589
|
+
|
|
1590
|
+
`render(data)` · `update(data)` (re-render) · `sort(columnKey, direction='asc')` · `reset()` (original order) · `toggleRow(row)` (expand/collapse a parent row object) · `destroy()`.
|
|
1591
|
+
|
|
1592
|
+
---
|
|
1593
|
+
|
|
1594
|
+
## Data utilities (`ChartFactory.data`)
|
|
1595
|
+
|
|
1596
|
+
Pure functions over row arrays for the aggregation step between a raw CSV and
|
|
1597
|
+
a builder's `data`/`series` config: grouped rollup, wide↔long pivots, numeric
|
|
1598
|
+
binning, top-N with an "Other" bucket, date coercion, and computed columns.
|
|
1599
|
+
No chart coupling; inputs are never mutated (every function returns new rows).
|
|
1600
|
+
Also available as `import { data } from 'chart-factory'`.
|
|
1601
|
+
|
|
1602
|
+
Conventions (shared across the module):
|
|
1603
|
+
|
|
1604
|
+
- **Fields are strings** (`data.topN(rows, 'budget', 5)`), so specs stay
|
|
1605
|
+
serializable. Functions are accepted only where a reduction or computed
|
|
1606
|
+
column genuinely needs code (custom `agg` reducers, `derive`).
|
|
1607
|
+
- **Agg shorthands**: `'count'` (group size) or `[field, 'sum'|'mean'|'median'|'min'|'max'|'count']`
|
|
1608
|
+
(`[field, 'count']` counts non-null values); a function `(groupRows) => value` for anything else.
|
|
1609
|
+
- **Null policy**: nulls never silently become 0. `nulls: 'drop' | 'keep' | 'zero'`
|
|
1610
|
+
on `rollup`/`bin`/`pivotLonger` — `'drop'` excludes null/undefined/NaN values
|
|
1611
|
+
from the aggregation (d3 semantics, default on rollup/bin), `'keep'` propagates
|
|
1612
|
+
them (any null in a group → null result; default on pivotLonger, preserving
|
|
1613
|
+
the library's line-gap convention), `'zero'` coerces them to 0. `pivotWider`
|
|
1614
|
+
instead takes `fill` (default `null`) for missing combinations.
|
|
1615
|
+
- **Deterministic ordering**: group/bin/pivot rows sort ascending by their
|
|
1616
|
+
key(s) (numbers numeric, Dates chronological); pivot columns keep
|
|
1617
|
+
first-appearance order. Invalid specs throw a `TypeError` naming the function.
|
|
1618
|
+
|
|
1619
|
+
### `data.rollup(rows, { by, agg, nulls })`
|
|
1620
|
+
|
|
1621
|
+
One output row per distinct combination of `by` keys (string or array of
|
|
1622
|
+
strings), carrying the group keys as fields plus one field per `agg` entry.
|
|
1623
|
+
`agg` omitted defaults to `{ count: 'count' }`. Computed group keys: `derive`
|
|
1624
|
+
them first.
|
|
1625
|
+
|
|
1626
|
+
```javascript
|
|
1627
|
+
data.rollup(rows, {
|
|
1628
|
+
by: ['chamber', 'congress'],
|
|
1629
|
+
agg: { medianAge: ['age', 'median'], n: 'count' }
|
|
1630
|
+
});
|
|
1631
|
+
// [{ chamber: 'house', congress: 80, medianAge: 51.5, n: 435 }, ...]
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
### `data.pivotWider(rows, { index, names, values, fill })`
|
|
1635
|
+
|
|
1636
|
+
Long → wide: spreads `names` values into columns, one row per `index` value
|
|
1637
|
+
(string or array). Missing combinations get `fill` (default `null` — pass
|
|
1638
|
+
`fill: 0` explicitly for count data). Duplicate (index, name) pairs keep the
|
|
1639
|
+
last value and `console.warn` — aggregate with `rollup` first.
|
|
1640
|
+
|
|
1641
|
+
```javascript
|
|
1642
|
+
data.pivotWider(counts, { index: 'year', names: 'category', values: 'n', fill: 0 });
|
|
1643
|
+
// [{ year: 1980, ok: 12, notalk: 9, ... }, ...] — feeds stacked/normalized builders
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
### `data.pivotLonger(rows, { cols, names, values, nulls })`
|
|
1647
|
+
|
|
1648
|
+
Wide → long: melts `cols` into one `{ [names], [values] }` row each (`names`
|
|
1649
|
+
defaults `'name'`, `values` defaults `'value'`), carrying all non-melted
|
|
1650
|
+
fields through. `nulls: 'keep'` (default) | `'drop'` | `'zero'` for
|
|
1651
|
+
null/missing cells.
|
|
1652
|
+
|
|
1653
|
+
### `data.topN(rows, key, n, { other, by, agg, direction })`
|
|
1654
|
+
|
|
1655
|
+
Top `n` rows by a numeric field (`direction: 'desc'` default; `'asc'` for
|
|
1656
|
+
bottom-n). With `other`, the remainder collapses into one row whose `by`
|
|
1657
|
+
field gets the `other` label and whose `key` is the remainder's `agg`
|
|
1658
|
+
(`'sum'` default, any agg shorthand or a function over the remainder rows);
|
|
1659
|
+
no Other row when `n >=` row count. Null keys sort last.
|
|
1660
|
+
|
|
1661
|
+
```javascript
|
|
1662
|
+
data.topN(films, 'budget', 12); // biggest budgets
|
|
1663
|
+
data.topN(regions, 'sales', 5, { by: 'region', other: 'Other' }); // 5 + Other row
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
### `data.bin(rows, key, { step, thresholds, extent, agg, nulls })`
|
|
1667
|
+
|
|
1668
|
+
Numeric binning → rows of `{ x0, x1, count, ...aggFields }`, sorted by `x0`,
|
|
1669
|
+
keeping empty in-range bins (count 0, agg fields `null`). `step` makes
|
|
1670
|
+
uniform bins aligned to multiples of step (a value exactly on the top aligned
|
|
1671
|
+
edge starts a new bin — floor semantics); `thresholds` (count hint or array)
|
|
1672
|
+
delegates to `d3.bin`; `extent` pins the domain. `nulls` (key policy):
|
|
1673
|
+
`'drop'` (default) or `'zero'`.
|
|
1674
|
+
|
|
1675
|
+
```javascript
|
|
1676
|
+
data.bin(films, 'year', { step: 5, agg: { avgGross: ['gross', 'mean'] } });
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
### `data.toDate(rows, fields, { format, nulls })`
|
|
1680
|
+
|
|
1681
|
+
Coerce string date fields (one name or an array) to `Date` objects, on
|
|
1682
|
+
copies. With `format`, parses via `d3.timeParse`; otherwise ISO strings /
|
|
1683
|
+
`new Date`, matching the builders' own time-axis coercion; finite numbers are
|
|
1684
|
+
epoch milliseconds. Unparseable values become `null` — never a silent epoch
|
|
1685
|
+
date. `nulls: 'keep'` (default) | `'drop'` (drop rows with a null coerced field).
|
|
1686
|
+
|
|
1687
|
+
### `data.derive(rows, spec)`
|
|
1688
|
+
|
|
1689
|
+
Computed columns: `data.derive(rows, { era: r => Math.floor(r.year / 5) * 5 })`
|
|
1690
|
+
returns copies with each spec field set to `fn(row, index)`. Specs evaluate
|
|
1691
|
+
against the original row (order-independent). Typical use: derive a computed
|
|
1692
|
+
group key, then `rollup` by it.
|
|
1693
|
+
|
|
1694
|
+
## Suggest (`ChartFactory.suggest`)
|
|
1695
|
+
|
|
1696
|
+
Runtime chart recommendation — [docs/chart-guide.md](chart-guide.md)'s
|
|
1697
|
+
signature table applied to real rows.
|
|
1698
|
+
|
|
1699
|
+
```javascript
|
|
1700
|
+
const { candidates, fields, signature } = ChartFactory.suggest(rows);
|
|
1701
|
+
candidates[0]; // { builder: 'Line.createMulti', score: 0.93, why: [...],
|
|
1702
|
+
// caveats: [...], data, config, transform, create }
|
|
1703
|
+
candidates[0].create('#chart'); // render the top suggestion
|
|
1704
|
+
candidates[1].create('#chart', { title: '...' }); // any candidate + config overrides
|
|
1705
|
+
```
|
|
1706
|
+
|
|
1707
|
+
How it works:
|
|
1708
|
+
|
|
1709
|
+
1. **Profile** each field (first 1,000 rows): `temporal` (Date / ISO string;
|
|
1710
|
+
year-like integer columns stand in when no real date field exists),
|
|
1711
|
+
`numeric` (numeric strings coerce — CSV-safe), `categorical` vs `id`
|
|
1712
|
+
(near-unique strings; promoted back to the category dimension when they're
|
|
1713
|
+
the only grouping candidate, e.g. 30 unique teams × one measure).
|
|
1714
|
+
2. **Signature**: measures × dimensions × temporal, rows-per-group, daily
|
|
1715
|
+
grain, same-unit measure pairs (`before/after`, `start/end`, year-pair
|
|
1716
|
+
column names).
|
|
1717
|
+
3. **Score** the guide's signature rules, adjusted by each builder's
|
|
1718
|
+
cardinality comfort ranges from the conformance metadata (a misfit is
|
|
1719
|
+
demoted with a reshape caveat, not hidden).
|
|
1720
|
+
4. **Synthesize** a render-ready `{ data, config }` per candidate in the
|
|
1721
|
+
builder's canonical record shape (mapped/grouped/pivoted — `transform`
|
|
1722
|
+
says what was done; input rows are never mutated). Every emitted config
|
|
1723
|
+
renders diagnostics-clean (contract-tested).
|
|
1724
|
+
|
|
1725
|
+
Honesty contract: `suggest` reads shape, not intent. What it cannot know —
|
|
1726
|
+
values are parts of one whole (donut/stacked), a quadrant center is
|
|
1727
|
+
defensible, two same-scale measures share a unit — ships as `caveats` on the
|
|
1728
|
+
affected candidates instead of silent confidence. Tables are never suggested
|
|
1729
|
+
(they require `Table.register()`); no rule matched ⇒ empty `candidates` +
|
|
1730
|
+
one `console.warn` pointing at the chart guide. `{ limit }` caps candidates
|
|
1731
|
+
(default 5).
|
|
1732
|
+
|
|
1733
|
+
## Validate (`ChartFactory.validate`)
|
|
1734
|
+
|
|
1735
|
+
The create-time config + data-shape diagnostics as a **return value** instead
|
|
1736
|
+
of console output — for callers that never see a browser console: agents
|
|
1737
|
+
generating configs, server-side validators, test harnesses. Pure metadata and
|
|
1738
|
+
data-shape checks; nothing renders and no DOM is touched, so it also works in
|
|
1739
|
+
Node (import the ESM entry).
|
|
1740
|
+
|
|
1741
|
+
```javascript
|
|
1742
|
+
const { valid, issues } = ChartFactory.validate('Bar.createBasic', {
|
|
1743
|
+
data: rows, // [{ label, value }] — wrong shape for this builder
|
|
1744
|
+
colour: 'red' // typo'd key
|
|
1745
|
+
});
|
|
1746
|
+
// valid === false
|
|
1747
|
+
// issues === [
|
|
1748
|
+
// { level: 'warning', code: 'unknown-option', key: 'colour', message: '…' },
|
|
1749
|
+
// { level: 'error', code: 'data-shape', key: 'data', message: '…' }
|
|
1750
|
+
// ]
|
|
1751
|
+
```
|
|
1752
|
+
|
|
1753
|
+
- `builder` is a conformance key (`'Line.createBasic'` — the keys of
|
|
1754
|
+
[conformance-matrix.md](conformance-matrix.md)). An unknown key returns an
|
|
1755
|
+
`unknown-builder` error with a near-miss suggestion (`'Line.createBsic'` →
|
|
1756
|
+
*did you mean 'Line.createBasic'?*).
|
|
1757
|
+
- **Levels**: `'error'` = the chart cannot render correctly (unknown builder,
|
|
1758
|
+
wrong data shape); `'warning'` = renders but something is wrong or ignored.
|
|
1759
|
+
`valid` is `true` when there are no errors.
|
|
1760
|
+
- **Codes**: `unknown-builder`, `invalid-config`, `unknown-option`,
|
|
1761
|
+
`unsupported-option`, `malformed-domain`, `missing-data`, `data-shape`,
|
|
1762
|
+
`zero-baseline` (a bar value-axis domain the render-time guard will clamp).
|
|
1763
|
+
- Message text is identical to what the console diagnostics emit at create
|
|
1764
|
+
time — one collector, two surfaces.
|
|
1765
|
+
- Deliberate differences from the console path: `validate()` ignores
|
|
1766
|
+
`diagnostics: false` (calling it *is* asking), and never dedupes — the same
|
|
1767
|
+
config returns the same issues on every call.
|
|
1768
|
+
- A config with `state: 'loading' | 'empty' | 'error'` skips the data checks,
|
|
1769
|
+
matching create-time behavior (the chart renders a placeholder).
|
|
1770
|
+
|
|
1771
|
+
## Standalone utilities
|
|
1772
|
+
|
|
1773
|
+
| Export | Description |
|
|
1774
|
+
|--------|-------------|
|
|
1775
|
+
| `createLegend(selector, items, colors)` | Simple flex legend of dot + label pairs. |
|
|
1776
|
+
| `TooltipManager` | Shared tooltip class (`show(html, event)`, `hide()`, `showLabelValue()`, static `rows([{label, value, color?, muted?}])`). |
|
|
1777
|
+
| `SIZE_PRESETS` | `{mini, full, wide}` density-tier definitions. |
|
|
1778
|
+
| `DOT_STYLES` | Scatter dot style presets (see `Scatter.createBasic`). |
|
|
1779
|
+
| `RESPONSIVE` | Live responsive tunables (`debounceMs` 150, tier breakpoints, `minRenderWidth` 120). |
|
|
1780
|
+
| `getAreaColors()` / `getLineColors()` | 6- / 5-color categorical token palettes (the `--area-color-*` house palette, **Set2** by default; both read the same tokens so multi-line and multi-area match). |
|
|
1781
|
+
| `getCSSVar(name, fallback)` / `getCSSVarNumber(name, fallback)` | Token readers. `getCSSVarNumber` honors a token set to `0` (falls back only when the token is missing/non-numeric). |
|
|
1782
|
+
| `rerenderAll()` | Re-render every live chart and registered table at its current size — the theme-switch companion (builders resolve tokens at render time). |
|
|
1783
|
+
| `setTheme(name)` | Set (or clear, with `null`/`'light'`) `<html data-theme>` and `rerenderAll()` — activates the built-in dark set or any companion theme stylesheet. Dispatches `d3t:themechange`. See [theming.md](theming.md). |
|
|
1784
|
+
| `palettes` | Built-in palette library — see [Palettes](#palettes-chartfactorypalettes) below. |
|
|
1785
|
+
|
|
1786
|
+
## Palettes (`ChartFactory.palettes`)
|
|
1787
|
+
|
|
1788
|
+
A curated library of 53 named palettes (sequential, diverging, categorical, alert) with accessibility metadata. **Set2** is the house default for every multi-series chart; single-series charts stay on `--chart-primary`.
|
|
1789
|
+
|
|
1790
|
+
A palette name is just a string, so it drops into the options you already use — configs stay pure JSON:
|
|
1791
|
+
|
|
1792
|
+
```js
|
|
1793
|
+
Bar.createGrouped('#el', { data, keys, colors: 'OkabeIto' }); // categorical name -> color array
|
|
1794
|
+
Scatter.createHeatmap('#el', { data, x, y, colorScheme: 'Viridis' }); // sequential/diverging name -> interpolator
|
|
1795
|
+
```
|
|
1796
|
+
|
|
1797
|
+
Categorical palettes pass through unchanged on both themes; sequential/diverging ramps re-anchor their pale end toward the container surface under `[data-theme="dark"]` (so a light-ended ramp doesn't glare), then re-theme on `rerenderAll()`.
|
|
1798
|
+
|
|
1799
|
+
**Residual gray:** when a builder falls back to its default palette, a series/key/slice named `Other` (the residual bucket from `data.topN` or the donut's `minSlice` fold; case-insensitive) is colored `--chart-neutral` gray and does **not** consume a palette slot — real categories keep their colors. Applies to stacked/normalized/streamgraph areas, stacked/grouped/vertical bars, multi-line, histogram overlay, donut, and the `legend: true` derived shell legend. Passing explicit `colors` or `colorMap` disables it. *Choosing* colors (sequential vs diverging, reserved meanings, CVD): see [color-guide.md](color-guide.md).
|
|
1800
|
+
|
|
1801
|
+
The `ChartFactory.palettes` surface:
|
|
1802
|
+
|
|
1803
|
+
| Member | Description |
|
|
1804
|
+
|--------|-------------|
|
|
1805
|
+
| `all` | `{ name: string[] }` map of every palette. |
|
|
1806
|
+
| `metadata` | `{ name: { type, tags, colorblindSafe, maxCategories?, description } }`. |
|
|
1807
|
+
| `get(name)` | Color array for a palette (or `undefined`). |
|
|
1808
|
+
| `type(name)` | `'sequential' \| 'diverging' \| 'categorical' \| 'alert'`. |
|
|
1809
|
+
| `list(type?)` | Palette names, optionally filtered by type. |
|
|
1810
|
+
| `isColorblindSafe(name)` | CVD-safe flag. |
|
|
1811
|
+
| `sample(name \| colors, n)` | `n` evenly-spaced colors. |
|
|
1812
|
+
| `ramp(name)` | The palette's colors as a theme-aware ramp. |
|
|
1813
|
+
| `ordinalScale(name \| colors, domain?)` | `d3.scaleOrdinal` over a categorical palette (raw hues). |
|
|
1814
|
+
| `colorScale(name \| colors, domain?)` | Categorical → ordinal; sequential/diverging → `d3.scaleSequential` over the theme-aware ramp. |
|
|
1815
|
+
| `getCountryColor(code, fallback?)` | Consistent per-country color (flag-inspired). |
|