plotagents 0.0.0-stage → 0.7.0

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.
Files changed (67) hide show
  1. package/AGENTS.md +98 -0
  2. package/LICENSE +21 -0
  3. package/README.md +217 -2
  4. package/bin/plotagents.js +73 -0
  5. package/dist/plotagents-geo.js +79 -0
  6. package/dist/plotagents.js +11611 -0
  7. package/docs/api.md +172 -0
  8. package/docs/charts.md +352 -0
  9. package/docs/colorscales.md +46 -0
  10. package/docs/dashboards.md +79 -0
  11. package/docs/getting-started.md +142 -0
  12. package/docs/rendering.md +47 -0
  13. package/docs/themes.md +116 -0
  14. package/index.d.ts +384 -0
  15. package/package.json +80 -4
  16. package/schema.json +1874 -0
  17. package/src/axes.js +152 -0
  18. package/src/charts/bar.js +350 -0
  19. package/src/charts/categorical.js +495 -0
  20. package/src/charts/chord.js +218 -0
  21. package/src/charts/choropleth.js +807 -0
  22. package/src/charts/dist.js +530 -0
  23. package/src/charts/donut.js +232 -0
  24. package/src/charts/flowchart.js +407 -0
  25. package/src/charts/gantt.js +460 -0
  26. package/src/charts/heatmap.js +76 -0
  27. package/src/charts/kpi.js +100 -0
  28. package/src/charts/line.js +551 -0
  29. package/src/charts/network.js +707 -0
  30. package/src/charts/pointmap.js +599 -0
  31. package/src/charts/pyramid.js +177 -0
  32. package/src/charts/radar.js +158 -0
  33. package/src/charts/regplot.js +370 -0
  34. package/src/charts/sankey.js +169 -0
  35. package/src/charts/scatter.js +214 -0
  36. package/src/charts/sunburst.js +136 -0
  37. package/src/charts/treemap.js +197 -0
  38. package/src/charts/wordcloud.js +163 -0
  39. package/src/colorscales.js +182 -0
  40. package/src/context.js +10 -0
  41. package/src/dashboard.js +438 -0
  42. package/src/dashed.js +90 -0
  43. package/src/data.js +137 -0
  44. package/src/facet.js +84 -0
  45. package/src/format.js +203 -0
  46. package/src/frame.js +226 -0
  47. package/src/geo/chile.js +7 -0
  48. package/src/geo/data/chile-regiones.js +12 -0
  49. package/src/geo/data/usa-estados.js +12 -0
  50. package/src/geo/data/world.js +12 -0
  51. package/src/geo/projection.js +217 -0
  52. package/src/geo/registry.js +110 -0
  53. package/src/geo/usa.js +8 -0
  54. package/src/geo/world.js +7 -0
  55. package/src/hierarchy.js +139 -0
  56. package/src/index.js +336 -0
  57. package/src/infer.js +58 -0
  58. package/src/interact.js +483 -0
  59. package/src/lint.js +417 -0
  60. package/src/palette.js +59 -0
  61. package/src/runtime.js +416 -0
  62. package/src/scale.js +241 -0
  63. package/src/schema.js +337 -0
  64. package/src/stats.js +303 -0
  65. package/src/svg.js +85 -0
  66. package/src/text.js +61 -0
  67. package/src/theme.js +311 -0
package/docs/api.md ADDED
@@ -0,0 +1,172 @@
1
+ # API reference
2
+
3
+ ```js
4
+ import {
5
+ plot, safePlot, lint, describe, render, toHTML, configure,
6
+ dashboard, safeDashboard, renderDashboard, lintDashboard, planDashboard,
7
+ schema, dashboardSchema, toolDefinition, validatePalette, registerGeo, countWords,
8
+ } from 'plotagents';
9
+ ```
10
+
11
+ Full types are in [`index.d.ts`](../index.d.ts) and every spec option in [`schema.json`](../schema.json).
12
+
13
+ ## Drawing
14
+
15
+ ### `plot(spec)`
16
+
17
+ Draws a chart and returns an SVG string. Works in Node, Deno, Bun and the browser. Throws a `PlotError` when the spec cannot be drawn; the error's `hint` says how to fix it.
18
+
19
+ ```js
20
+ const svg = plot({ type: 'bar', data, x: 'region', y: 'sales', title: 'Biobío leads with 30%' });
21
+ ```
22
+
23
+ ### `safePlot(spec)`
24
+
25
+ The version to expose as an agent tool: it never throws.
26
+
27
+ ```js
28
+ const { ok, svg, type, description, errors, warnings, suggestions } = safePlot(spec);
29
+ ```
30
+
31
+ | Field | Meaning |
32
+ |---|---|
33
+ | `ok` | `true` when the chart was drawn. |
34
+ | `svg` | The SVG string, or `null`. |
35
+ | `type` | The resolved chart type (useful when `type` was inferred). |
36
+ | `description` | Alt text for the chart. |
37
+ | `errors` | Problems that stop the chart: `{ code, message, fix }`. |
38
+ | `warnings` | Problems that make the chart misleading or hard to read. |
39
+ | `suggestions` | Design improvements, such as a better title or `highlight`. |
40
+
41
+ ### `lint(spec)`
42
+
43
+ Reviews a spec without drawing it and returns `{ ok, type, errors, warnings, suggestions }`. Messages follow the spec's `locale` (Spanish by default, English with `locale: "en"`); each one carries a `fix` with the change to make.
44
+
45
+ ### `dashboard(spec)` · `safeDashboard(spec)` · `renderDashboard(el, spec)`
46
+
47
+ A self-contained HTML dashboard with best-practice layout, shared colors, KPIs and filters. `safeDashboard` never throws and returns `{ ok, html, plan, errors, warnings, suggestions }`; `renderDashboard` draws into an element in the browser; `lintDashboard` reviews; `planDashboard` returns the layout decisions. See [Dashboards](dashboards.md).
48
+
49
+ ### `describe(spec)`
50
+
51
+ Returns a text description of the chart, such as "Highest: Biobío with 1,240 (30%)…". The same text goes into the SVG's `<desc>` for screen readers.
52
+
53
+ ## In the browser
54
+
55
+ ### `render(target, spec, opts?)`
56
+
57
+ Draws into an element (a CSS selector or a DOM node) with tooltips, responsive width, time controls when the spec asks for them (`slider: true`), drill-down for treemaps and sunbursts, click-to-focus for networks, collapsible Gantt phases, and legend clicks (shift + click excludes an entry; in treemaps and sunbursts a click zooms to the category).
58
+
59
+ ```js
60
+ const chart = plotagents.render('#chart', spec);
61
+ chart.update({ ...spec, highlight: 'Ñuble' }); // redraw
62
+ chart.destroy(); // remove listeners and markup
63
+ ```
64
+
65
+ | Option | Meaning |
66
+ |---|---|
67
+ | `controls` | `false` hides time controls even when the spec asks for them (`slider: true`). |
68
+
69
+ The handle exposes `svg` (the current `<svg>` element) and `spec`.
70
+
71
+ ### `attach(root)`
72
+
73
+ Adds tooltips to a container that already holds a plotagents SVG, for example one rendered on the server.
74
+
75
+ ### `toHTML(specs, opts?)`
76
+
77
+ A self-contained HTML page with one or more interactive charts and the library inlined.
78
+
79
+ | Option | Default | Meaning |
80
+ |---|---|---|
81
+ | `title` | `"Gráficos"` | Page title. |
82
+ | `lang` | `"es"` | Page language. |
83
+ | `columns` | `1` | Two or more lay charts out in a grid. |
84
+
85
+ ## Configuration
86
+
87
+ ### `configure(options)`
88
+
89
+ Sets defaults for every later chart; the spec always wins.
90
+
91
+ ```js
92
+ configure({ font: 'Poppins', textScale: 1.5, locale: 'en' });
93
+ configure(); // returns the current defaults
94
+ configure(null); // clears them
95
+ ```
96
+
97
+ It accepts any spec option: `font`, `textScale`, `locale`, `theme`, `palette`, `ramp`, `colorScale`, `legend` and so on. A spec's own `theme` object is merged over the global one.
98
+
99
+ ### `registerTheme(name, theme)`
100
+
101
+ Registers a named theme (merged over `extends`, `papel` by default) so `theme: name` and `configure({ theme: name })` can use it. See [Themes](themes.md#your-own-theme-everywhere).
102
+
103
+ ```js
104
+ registerTheme('acme', { background: '#FFF', text: '#111', categorical: 'tab10', grid: { dash: '2 3' } });
105
+ configure({ theme: 'acme' });
106
+ ```
107
+
108
+ ### `colorScaleNames()` · `resolveColorScale(name)`
109
+
110
+ All color scale names grouped by kind (`sequential`, `diverging`, `cyclic`, `qualitative`), and the stops of one scale: `{ name, stops, kind }`. `interpolateColor(stops, t)` returns the color at `t ∈ [0, 1]` (OKLab).
111
+
112
+ ### `validatePalette(colors, opts?)`
113
+
114
+ Checks a custom palette with the same tests the built-in one passes. See [themes](themes.md#validating-a-palette).
115
+
116
+ ```js
117
+ validatePalette(['#223C6A', '#E69B0A', '#D21428'], { mode: 'light', surface: '#FFFFFF' });
118
+ // → { ok: false, checks: [{ name: 'lightness', status: 'fail', detail: '…' }, …] }
119
+ ```
120
+
121
+ ### `registerGeo(name, featureCollection, aliases?)`
122
+
123
+ Registers your own GeoJSON so specs can use `geo: name`. Chile and the world are built in.
124
+
125
+ ## For agents
126
+
127
+ ### `toolDefinition({ name?, flavor? })`
128
+
129
+ Returns a tool definition built from the JSON Schema: `flavor: "anthropic"` (default) gives `{ name, description, input_schema }`, `flavor: "openai"` gives `{ type: "function", function: { … } }`.
130
+
131
+ ```js
132
+ import Anthropic from '@anthropic-ai/sdk';
133
+ import { safePlot, toolDefinition } from 'plotagents';
134
+
135
+ const client = new Anthropic();
136
+ const res = await client.messages.create({
137
+ model: process.env.ANTHROPIC_MODEL,
138
+ max_tokens: 4096,
139
+ tools: [toolDefinition()],
140
+ messages: [{ role: 'user', content: 'Plot the distribution of service times per clinic: …' }],
141
+ });
142
+ const call = res.content.find((b) => b.type === 'tool_use');
143
+ const { ok, svg, errors } = safePlot(call.input);
144
+ // If !ok, send `errors` back as the tool_result: every error carries a `fix`.
145
+ ```
146
+
147
+ ### `schema`
148
+
149
+ The JSON Schema of a spec, also published as [`schema.json`](../schema.json) and importable as `plotagents/schema.json`.
150
+
151
+ ## Helpers
152
+
153
+ | Export | Meaning |
154
+ |---|---|
155
+ | `TYPES` | Every chart type name. |
156
+ | `themes`, `getTheme(t)`, `themeFor(spec)` | Built-in themes and the resolved theme of a spec. |
157
+ | `palettes`, `ramps` | Named categorical palettes and sequential/diverging ramps. |
158
+ | `timeValues(spec)` | Sorted values of the `time` field. |
159
+ | `makeFormat(fmt, locale, values)` | The number formatter a chart would use. |
160
+ | `formatNumber(v, decimals, locale)`, `formatCompact(v, locale)` | Locale-aware number formatting. |
161
+ | `squarify(items, x, y, w, h)` | The treemap layout algorithm. |
162
+ | `bundleSource()` | Node only: the browser build as a string, for inlining. |
163
+
164
+ ## Package entry points
165
+
166
+ | Import | Contents |
167
+ |---|---|
168
+ | `plotagents` | Everything above (ES module). |
169
+ | `plotagents/schema.json` | The JSON Schema. |
170
+ | `plotagents/browser` | `dist/plotagents.js`: a `<script>` that defines `window.plotagents`. |
171
+ | `plotagents/browser/geo` | `dist/plotagents-geo.js`: Chile and world geometries for the browser build. |
172
+ | `plotagents/geo/chile`, `plotagents/geo/world` | The geometries as ES modules. |
package/docs/charts.md ADDED
@@ -0,0 +1,352 @@
1
+ # Chart reference
2
+
3
+ Every chart takes a spec with `data` (rows) plus field names for each role. The options below are specific to each type; the [common options](#common-options) apply to all of them. Every option is also described in [`schema.json`](../schema.json).
4
+
5
+ ## Common options
6
+
7
+ | Option | Meaning |
8
+ |---|---|
9
+ | `type` | Chart type or alias. Omit (or `"auto"`) to infer it from the data. |
10
+ | `data` | Array of row objects. `{ A: 1, B: 2 }` and arrays of numbers are accepted too. |
11
+ | `title` | The finding, as a sentence. Wraps; never truncated. |
12
+ | `subtitle` | How to read the chart: measure, unit, period. |
13
+ | `source` | Data source, shown small at the bottom right. |
14
+ | `series` / `hue` | Field that splits the data into colored groups. |
15
+ | `highlight` | One name or a list: emphasize these, mute the rest. |
16
+ | `colors` | `{ "name": "#hex" }` to pin an entity's color. |
17
+ | `format`, `xFormat`, `yFormat` | `"number" \| "compact" \| "percent" \| "currency" \| "integer"` or `{ prefix, suffix, decimals, currency }`. |
18
+ | `locale` | `"es"` (default: `1.234,5`) or `"en"` (`1,234.5`). |
19
+ | `xLabel`, `yLabel` | Axis titles (horizontal, never rotated). |
20
+ | `xScale`, `yScale` | `"log"` where it makes sense. |
21
+ | `xDomain`, `yDomain` | Fixed `[min, max]`. |
22
+ | `refLines` | `[{ y: 600, label: "Target" }]` (or `x`, or `value` for bars). |
23
+ | `facet` (`col`), `columns`, `sharey`, `sharex` | Small multiples, one panel per value. |
24
+ | `time`, `timeValue` | One period at a time: the latest by default, or `timeValue`. The period is appended to the subtitle. |
25
+ | `slider`, `play` | Browser time controls, **off by default**. `slider: true` adds period buttons when there are few periods (≤ 8) and a minimal slider when there are many; line and area charts get a two-handle range. `"buttons"` or `"slider"` force one; `play: false` hides the play button. |
26
+ | `compare` | With `time`: compare against `"previous"` (default), `"first"`, a given period, or `false`. |
27
+ | `theme`, `palette`, `ramp` | See [themes](themes.md). |
28
+ | `colorScale` (`cmap`) | Any matplotlib colormap (`"viridis"`, `"RdBu_r"`…) or your own list of colors. See [color scales](colorscales.md). |
29
+ | `font`, `textScale` | Typeface (Google Fonts name, CSS stack or `{ family, import }`) and a text-size multiplier (aliases `fontScale`, `labelScale`). Set them for every chart with `configure()`. |
30
+ | `width`, `height`, `padding` | Pixels. Height is automatic for horizontal bars, heatmaps, KPI tiles, and narrow treemaps, sunbursts and radars. |
31
+ | `legend` | Position: `"top"` (default), `"bottom"`, `"left"`, `"right"`, or `false` to hide it. Side legends move to the bottom below 520 px. Setting a position also forces a legend where the chart would otherwise label lines directly. |
32
+ | `exclude` | One name or a list: drop these legend entries (series, first-level groups) and recompute axes, shares and layout. Colors stay fixed and the excluded entries stay in the legend, faded and struck through. |
33
+
34
+ ### In the browser
35
+
36
+ - **Tooltips** sit next to the hovered mark, never over the title or subtitle, and stay inside the chart. Their header takes the mark's color, with white or dark text depending on contrast.
37
+ - **Legend**: hovering a key highlights its marks; a click pins the highlight. **Shift + click** excludes that entry (click it again to bring it back). In treemaps and sunbursts a plain click zooms the whole plot to that category; click it again, a breadcrumb, or press Esc to go back.
38
+
39
+ ## Compare
40
+
41
+ ### `bar` · `column` · `barplot` · `count` (`countplot`)
42
+
43
+ `x` category, `y` value. Columns for few short categories, horizontal bars when labels are long or there are many; values at the bar tips and no axis when every bar is labeled.
44
+
45
+ | Option | Meaning |
46
+ |---|---|
47
+ | `stack` | `true` stacks series; `"percent"` stacks to 100%. |
48
+ | `horizontal` | Force orientation. |
49
+ | `sort` | `"desc"` (default for categories), `"asc"`, `"none"`, `"alpha"`. Years and dates keep their order. |
50
+ | `top` | Keep the N largest, fold the rest into "Other" (automatic past 40 categories). |
51
+ | `ci`, `errorbar`, `estimator` | `barplot`: mean ± 95% CI (`"ci"`), `"sd"` or `"se"` error bars. |
52
+ | `aggregate` | `"sum"` (default), `"mean"`, `"max"`, `"min"`, `"count"`, `"first"`, `"last"`. `countplot` counts rows. |
53
+ | `labels` | `true`, `false` or `"auto"`. |
54
+ | `barWidth` | Max bar thickness in px. |
55
+
56
+ ## Time
57
+
58
+ ### `line` · `area`
59
+
60
+ `x` date (`"2024-03"`, `"2024-03-15"`, `Date`), year or number; `y` value or list of fields (wide table → series).
61
+
62
+ | Option | Meaning |
63
+ |---|---|
64
+ | `labels` | End labels (default on for every series that fits; colliding labels get leader lines). |
65
+ | `annotations` | `[{ x: "2024-01", label: "New branch" }]`. |
66
+ | `index` | Rebase each series to 100 at its first value. |
67
+ | `ci`, `errorbar`, `estimator` | seaborn `lineplot`: mean and confidence band when there are several rows per x. |
68
+ | `curve` | `"linear"` (default), `"smooth"` (monotone, never invents peaks), `"step"`. |
69
+ | `zero` | Force the y axis to include zero (on by default for areas and when it costs little). |
70
+ | `points` | Markers on every point (automatic for short series). |
71
+ | `stack` | Areas: `true` (default with series), `"percent"`, or `false` to overlap. |
72
+ | `fill` | Single line: `false` removes the soft gradient. |
73
+ | `xRange` | Visible range `[from, to]`; this is what the range slider sets (`slider: true`). |
74
+
75
+ ## Projects
76
+
77
+ ### `gantt` (`timeline`, `roadmap`, `schedule`, `cronograma`)
78
+
79
+ One row per task: `task`, `start` and `end` (or `duration`). Dates like `"2026-03-02"` or `"2026-03"`, or plain numbers for weeks or sprints. Day dates are **inclusive**: a task from March 3 to March 5 lasts 3 days. A row without `end`, or with `end` equal to `start`, is a milestone, drawn as a diamond with its date.
80
+
81
+ The time axis sits on top, like a calendar. With `group`, each phase opens with its name and a thin summary bar in its color, so no legend is needed. The filled part of each bar is the progress; the rest stays in a light tint of the same color. Dependencies are thin right-angle arrows behind the bars.
82
+
83
+ ```js
84
+ plot({
85
+ type: 'gantt',
86
+ title: 'The project goes live on June 15; the ERP integration is the critical path',
87
+ data: [
88
+ { phase: 'Design', task: 'Data model', start: '2026-02-09', end: '2026-03-06', progress: 80 },
89
+ { phase: 'Build', task: 'ERP integration', start: '2026-03-16', end: '2026-04-30', progress: 10, after: 'Data model' },
90
+ { phase: 'Launch', task: 'Go-live', start: '2026-06-15' },
91
+ ],
92
+ task: 'task', start: 'start', end: 'end', group: 'phase', progress: 'progress', dependsOn: 'after',
93
+ today: '2026-03-12', locale: 'en',
94
+ });
95
+ ```
96
+
97
+ | Option | Meaning |
98
+ |---|---|
99
+ | `task`, `start`, `end` | Field names (detected when they are called `task`/`tarea`, `start`/`inicio`, `end`/`fin`). |
100
+ | `duration` | Length instead of `end`: days on a date axis, units on a numeric one. |
101
+ | `group` (`series`, `hue`) | Phase or team: one colored section per value. `groupHeaders: false` uses a color legend instead. |
102
+ | `progress` | 0–1 or 0–100. Shown as the filled part, with the percentage inside the bar when it fits. |
103
+ | `dependsOn` | Field with the predecessor's name, or several: `"A, B"`. |
104
+ | `milestone` | Boolean field that forces milestones. |
105
+ | `today` | A date (or `true`) for the "today" line, labeled on the axis. `refLines: [{ x, label }]` adds more. |
106
+ | `dashed`, `dashedLabel` | Hatch tentative tasks: a field (`dashed: "tentative"`), names or phases. |
107
+ | `highlight` | Emphasize a task or a whole phase. |
108
+ | `sort` | `"start"` orders tasks by date (default: data order, grouped by phase). |
109
+ | `rowHeight`, `xLabel`, `unit` | Row height in px; axis title and unit for numeric axes. |
110
+ | `collapsed`, `collapsible` | Phases drawn as one summary row (duration-weighted progress, milestone diamonds): a list of names or `true`. In the browser a click on a phase toggles it (Alt + click toggles all); the time axis and name column stay put. `collapsible: false` hides the chevrons. |
111
+ | `critical`, `criticalColor` | Outline the critical path: tasks with zero slack along `dependsOn` (working days on day dates), with accent arrows. |
112
+ | `weekends` | Weekend bands on day-date plans of up to 120 days; `false` turns them off. |
113
+
114
+ `lint` warns about tasks that end before they start and suggests a `today` line. The alt text names the span, the longest task, the milestones and the overall progress weighted by duration.
115
+
116
+ ## Relationships
117
+
118
+ ### `scatter` · `bubble`
119
+
120
+ `x`, `y` numeric; `size` for bubbles (area-proportional); `label` field with `labels` (number of points or list of names) for selective labels; `trend: true` adds a least-squares line with R².
121
+
122
+ ### `regplot` (`lmplot`) · `residplot` · `jointplot`
123
+
124
+ | Option | Meaning |
125
+ |---|---|
126
+ | `ci` | Confidence band level (95 by default, `false` to hide). |
127
+ | `order` | Polynomial degree. |
128
+ | `lowess` (+ `frac`) | Local regression instead of a line. |
129
+ | `logx` | Fit `y ~ log(x)` and use a log x axis. |
130
+ | `scatter` | `false` hides the points. |
131
+ | `truncate` | Draw the fit only over the data range (default `true`). |
132
+ | `annotate` | Show R² and n. |
133
+ | `label`, `labels` | Name selected points. |
134
+ | `kind` | `jointplot`: `"scatter"` or `"reg"`. |
135
+ | `marginal` | `jointplot`: `"hist"` or `"kde"`. |
136
+
137
+ ### `radar` (`spider`)
138
+
139
+ Long data (`axis`, `value`, `series`) or wide data (`axes: [fields]`, one row per series with `label`). `normalize` scales each axis to its own maximum (tooltips keep raw values); `grid: "polygon" | "circle"`; `domain`. Best with ≤ 4 series.
140
+
141
+ ## Distributions
142
+
143
+ ### `hist` (`histplot`) · `kde` (`kdeplot`) · `ecdf`
144
+
145
+ | Option | Meaning |
146
+ |---|---|
147
+ | `bins`, `binWidth` | Bin count or width (default: Freedman–Diaconis, rounded to nice edges). |
148
+ | `stat` | `"count"`, `"percent"`, `"density"`, `"probability"`. |
149
+ | `kde` | Overlay a density curve on the histogram. |
150
+ | `multiple` | With `hue`: `"layer"`, `"stack"`, `"dodge"`, `"fill"`. |
151
+ | `element` | `"bars"` or `"step"`. |
152
+ | `cumulative` | Cumulative distribution. |
153
+ | `fill`, `bwAdjust`, `cut`, `clip`, `rug` | Density options. |
154
+ | `complementary` | `ecdf`: plot 1 − F(x). |
155
+
156
+ ### `box` · `violin` · `strip` · `swarm` (`beeswarm`) · `point` (`pointplot`)
157
+
158
+ `x` category and `y` measure (swap them, or set `horizontal`, for horizontal). Long labels or many categories switch to horizontal automatically.
159
+
160
+ | Option | Meaning |
161
+ |---|---|
162
+ | `points` | Box: overlay the observations. |
163
+ | `showMeans` | Mark the mean. |
164
+ | `inner` | Violin: `"box"` (default), `"quartiles"`, `"points"`, `"none"`. |
165
+ | `split` | Violin with two `hue` levels: one half each. |
166
+ | `scale` | Violin width: `"width"`, `"area"`, `"count"`. |
167
+ | `jitter`, `dodge`, `radius` | Strip and swarm layout. |
168
+ | `estimator`, `errorbar` | Point plot: `"mean"`/`"median"`/`"sum"` and `"ci"`/`"sd"`/`"se"`/`"pi"`. |
169
+ | `order`, `hueOrder` | Category and group order. |
170
+
171
+ ## Part-to-whole
172
+
173
+ ### `treemap` · `sunburst`
174
+
175
+ `path: [level1, level2, …]` of any depth (or `group` + `label`), `value`. Rows with the same path are summed.
176
+
177
+ | Option | Meaning |
178
+ |---|---|
179
+ | `depth` | Show only the first N levels. |
180
+ | `focus` | Path to drill into, e.g. `["Mining"]`. In the browser, clicking a group or its legend key does it (the category fills the whole plot) and breadcrumbs or Esc go back. |
181
+ | `exclude` | Level-1 groups to leave out; the rest fill the plane and shares are recomputed. Shift + click on a legend key does it in the browser. |
182
+ | `showValues` | Append values to the labels. |
183
+ | `colorBy` | `"change"` colors tiles by % change vs the comparison period (growth cool, decline warm); `"label"` colors a single-level treemap by item. |
184
+ | `shade` | Subtle lightness steps inside each category. Off by default: one solid color per category. |
185
+ | `compareData` | Rows of an earlier period to compute changes without `time`. |
186
+ | `hole`, `centerValue`, `centerLabel` | Sunburst center. |
187
+
188
+ Areas are exact: there are no header bands taking space from a group. Categories are told apart by one solid color, a 4 px gap between level-1 groups (2 px and 1 px deeper) and the legend, which lists each group's share. With `time`, every tile also shows its change vs the previous period (▲ 12%). Colors belong to the level-1 group and are computed over the full tree and across all periods, so they never change when drilling or sliding.
189
+
190
+ ### `pyramid` (`populationpyramid`)
191
+
192
+ Age groups (`age` or `y`), two sides (`sex`, `series` or `hue`) and a value. Back-to-back bars with the ages in the center column; `percent: true` for shares of the population; `compareData`, or `time` + `compare`, draws an earlier year as a stepped outline.
193
+
194
+ ### `donut` · `pie`
195
+
196
+ `label`, `value`. More than `maxSlices` (6) folds into "Other".
197
+
198
+ | Option | Meaning |
199
+ |---|---|
200
+ | `labels` | `"auto"`, `"inside"`, `"outside"` (leader lines), `"legend"`, `false`. |
201
+ | `half` | Semicircle. |
202
+ | `thickness` | Ring thickness (pie is `1`). |
203
+ | `sort` | `"desc"` or `"none"`. |
204
+ | `centerValue`, `centerLabel` | Donut center. |
205
+
206
+ ## Maps
207
+
208
+ ### `choropleth` (`map`) · `symbolmap` (`bubblemap`)
209
+
210
+ Chilean regions (`geo: "chile"`, default), US states (`geo: "usa"`) and world countries (`geo: "world"`) ship with the library, and **any geometry of your own** works too (see below). `key` is the data field to match and `value` the measure; a text `value` (a party, a language, a zone) colors by category with a legend of swatches. Matching is forgiving: names, codes and aliases, ignoring accents, case and "Región de" ("RM", "Biobio", "XIII", "Aisén", "ARG", "EE.UU.", "TX", "Nueva York", "48").
211
+
212
+ **United States.** 50 states and DC in an Albers equal-area projection (`albersUsa`, the same composite as `d3.geoAlbersUsa`): Alaska (at 0.35×) and Hawaii sit in insets at the bottom left with a fine dashed frame; Puerto Rico gets its own inset only when the data includes it (`territories: true` forces it). Each state shows its postal code, plus the value when it fits; the small northeastern states (VT, NH, MA, RI, CT, NJ, DE, MD, DC) are labeled in a column on the right with leader lines. `values: false` keeps only the codes; `labels: false` removes them.
213
+
214
+ | Option | Meaning |
215
+ |---|---|
216
+ | `geo` | `"chile"`, `"usa"`, `"world"`, a name registered with `registerGeo()`, a GeoJSON or TopoJSON object, or (browser) a URL. |
217
+ | `featureKey`, `nameKey` | Feature property to match, and the one with the display name (both detected when omitted). |
218
+ | `fitTo` | Zoom to regions, a `[lonMin, latMin, lonMax, latMax]` box or `"markers"`. |
219
+ | `markers`, `layers` | Points by lon/lat on top; extra GeoJSON lines, polygons or points over or under the map. |
220
+ | `scale`, `classes` | `"quantile"` (default), `"equal"`, `"continuous"` or a list of breaks; 5 classes by default. |
221
+ | `midpoint` / `diverging` | Diverging classes around a value (e.g. 0 for changes). |
222
+ | `missing` | `"plain"` drops the hatch and the "No data" legend entry. |
223
+ | `labels`, `bars` | Chile gets a north-to-south column with name, bar and value; `bars: false` hides the bars. World maps label the top N. |
224
+ | `projection` | `"mercator"`, `"equalEarth"`, `"albersUsa"`, `"equirectangular"`, `"conic"`, `"identity"`, `"planar"` or `{ type, parallels, rotate }` (picked automatically). |
225
+ | `borderColor`, `borderWidth` | Region borders. |
226
+ | `dashed`, `dashedLabel` | Hatch some regions (preliminary figures, disputed areas): names, codes or a data field. They keep their class color. |
227
+ | `maxRadius` | `symbolmap`: largest circle radius. |
228
+
229
+ #### Your own geometry: GeoJSON and TopoJSON
230
+
231
+ `geo` takes whatever you have: a GeoJSON `FeatureCollection`, a single `Feature`, an array of features or a bare geometry, or a TopoJSON `Topology` (`geoObject` picks the object; default the first). Polygons are filled, lines are stroked and points become dots colored by value. In the browser, `render()` also accepts a URL and downloads it once: `geo: "/maps/comunas.topojson"`. In Node, read the file and pass the object.
232
+
233
+ ```js
234
+ import { readFileSync } from 'node:fs';
235
+ const comunas = JSON.parse(readFileSync('comunas-biobio.geojson', 'utf8'));
236
+ plot({
237
+ type: 'choropleth',
238
+ geo: comunas, // properties: { CUT_COM: "08101", NOM_COM: "Concepción", … }
239
+ data: [{ comuna: 'Concepción', poverty: 7.1 }, { comuna: 'Coronel', poverty: 12.4 }],
240
+ key: 'comuna', value: 'poverty', // NOM_COM is found automatically
241
+ markers: { data: hospitals, lon: 'lon', lat: 'lat', label: 'name', legend: 'Hospital' },
242
+ layers: [{ geo: rivers, label: 'Biobío river', stroke: '#5BA3E0', width: 2 }],
243
+ fitTo: ['Concepción', 'Talcahuano', 'Hualpén'],
244
+ });
245
+ ```
246
+
247
+ - **Matching without configuration.** Rows are matched against the usual keys (code, id, ISO-3, name, aliases); if some rows don't match, plotagents finds the feature property whose values do (`NOM_COM`, `CUT_REG`, `GEOID`, `ADMIN`…). `featureKey` forces one. Display names come from `nameKey`, or the first of `name`, `NOMBRE`, `NAME_1`, `ADMIN` and similar.
248
+ - **Projections.** Picked from the geometry: Mercator for a country or region, Equal Earth for the world, and a plain cartesian plane when coordinates are not longitudes and latitudes (UTM metres, for instance). Set `projection` to `"equirectangular"`, `"conic"` (or `{ type: "conic", parallels: [-30, -45], rotate: 72 }`), `"identity"` (pixel coordinates, y down, for hand-drawn or tile maps) or `"planar"` (y up).
249
+ - **Framing.** `fitTo` zooms to some regions, a box `[lonMin, latMin, lonMax, latMax]` or `"markers"`; the rest of the map stays as clipped context. `tall: false` turns off the side column for elongated shapes.
250
+ - **Markers.** `markers: [{ lon, lat, label, value }]` or `{ data, lon, lat, label, size, color, legend, maxRadius }`: dots on top, area-proportional when there is a `size`, labels placed where they don't collide.
251
+ - **Layers.** `layers: [{ geo, label, stroke, width, fill, dash, opacity, below }]` overlays rivers, roads, routes, faults or district lines (or draws them under the regions with `below: true`). A `label` adds a legend key.
252
+ - **Style.** `borderColor`, `borderWidth`, `pointRadius`, `lineWidth`, `ramp` (a named or custom sequential ramp), `palette` and `colors` (categories).
253
+ - **Register once, use by name.** `registerGeo('biobio', comunas, ['comunas-biobio'])` and then `geo: 'biobio'` in every spec, which keeps tool calls short for an agent.
254
+
255
+ In the browser (`render()` or `toHTML()`), maps are interactive: hovering outlines the shape; clicking a region zooms to fit it (click again, double-click, Esc or the reset button to go back); Ctrl/⌘ + scroll zooms around the pointer (plain scroll keeps scrolling the page, as `chartjs-plugin-zoom` recommends with its `modifierKey`); pinch zooms on touch screens; drag pans when zoomed; +/− buttons work from the keyboard. Each color class in the legend is a key: hover it to light up its regions, click to pin. With `time`, every region's color transitions into the next period's instead of jumping, and the zoom is kept while the slider plays.
256
+
257
+ `lint` warns about rows that match no region or country. Sources: Natural Earth 1:110m (public domain) for the world; Biblioteca del Congreso Nacional for Chilean regions (attribution in `src/geo/data/chile-regiones.js`); U.S. Census Bureau cartographic boundaries 1:10m (public domain) via us-atlas 3.0.1 for US states. In the browser, `dist/plotagents-geo.js` adds the geometries; `toHTML()` includes it only when a page has a map.
258
+
259
+ ### `pointmap` (`dotmap`, `tilemap`)
260
+
261
+ Dots at longitude/latitude over a real basemap in Web Mercator. Fields `lon`/`lat` are found by name (lon, lng, longitude, longitud / lat, latitude, latitud). `size` → circle area (key in the corner, largest labeled); `hue` → category colors with a legend; numeric `value` → sequential ramp (`midpoint` for diverging, `colorScale` for any matplotlib map). Zoom and center fit the points (`zoom`, `center: [lon, lat]` to fix them).
262
+
263
+ ```js
264
+ plot({ type: 'pointmap', data: cities, lon: 'lon', lat: 'lat', size: 'population', label: 'city',
265
+ title: 'Santiago concentrates 40% of the population', source: 'INE' });
266
+ ```
267
+
268
+ | Option | Meaning |
269
+ |---|---|
270
+ | `tiles` | `"osm"` (default, OpenStreetMap), `"esri-gray"`, `"esri-dark"`, `"carto-light"` / `"carto-dark"` (free key: carto.com/basemaps/apikey), `"mapbox-light"` / `"mapbox-dark"` (public `pk.` token in `tilesKey`; `sk.` tokens are refused), `{ url, attribution, subdomains, maxZoom, retina }`, or `false` (vector world outline only). |
271
+ | `tilesKey` | Key or token for CARTO, Mapbox or Esri. |
272
+ | `tileStyle` | `"muted"` (default) desaturates and tints the tiles to the theme (a navy night map on `noche`); `"original"` keeps them as they are. |
273
+ | `tileOpacity`, `zoom`, `center` | Tile opacity, fixed zoom level and `[lon, lat]` center. |
274
+ | `labels`, `labelValues` | How many dots to name (8 largest by default), or a list; `labelValues: false` hides values. |
275
+
276
+ The attribution is always drawn. Tiles are external images: they show in inline SVG, `render()` and `toHTML()`, **not** when the SVG is used as `<img>`, opened from a PDF converter, or offline; then the vector world outline underneath still gives context. `await embedTiles(svg)` inlines them as data: URIs (not for OSM, whose tile policy forbids bulk downloads; use `esri-gray` or your own server). For heavy traffic, use a provider with a key or your own tiles: OSM's servers are donated.
277
+
278
+ ## Dashed shapes: estimates, projections, provisional figures
279
+
280
+ Some numbers are not firm yet. Instead of a new color (which reads as a new category), plotagents keeps the shape's color and changes its texture: a hatched fill (stripes of the color over a light tint of it) and a dashed outline at 3:1 contrast. Lines get a dashed stroke with hollow points. It works in `bar`, `line`, `area`, `donut`/`pie`, `choropleth` and `symbolmap`.
281
+
282
+ | Option | Meaning |
283
+ |---|---|
284
+ | `dashed` | A category, series or region name, or a list (`["Texas", "Ohio"]`); a data field whose truthy rows are dashed (`"provisional"`, accepts `true`, `1`, `"sí"`, `"yes"`, `"x"`); `{ field, equals }`; or `true` for everything. |
285
+ | `dashedFrom` | On a time axis (`line`, `area`, `bar`): the first x that is projected. The line turns dashed from the last firm point, projected points are hollow, stacked areas are hatched in each series' color and a soft band with `dashedLabel` marks the stretch. |
286
+ | `dashedLabel` | What it means: "Projection", "Preliminary", "Estimate". It is the legend entry (hover it to light up only the dashed shapes), the band label and a line in the tooltip. Defaults to "Estimado"/"Estimate"; `lint` suggests setting it. |
287
+
288
+ This follows the Chart.js advice of using patterns (as `patternomaly` does) and `segment.borderDash` for projected stretches, so the distinction never depends on color alone.
289
+
290
+ ## Networks and flows
291
+
292
+ ### `network` (`graph`)
293
+
294
+ Edges in `data` (`from`, `to`, optional `value`), optional `nodes` with `id`, `group` and `size`. Node area ∝ weight (weighted degree by default), color by group; edges curve in the color of their source node (`curveness`, default 0.3; `0` = straight), 1 px wide without a value field. The largest nodes are named (ink with a halo, never overlapping each other or a node); every name is in the tooltip. Hovering a node lights up its neighbors and their edges.
295
+
296
+ `layout` picks the placement (all deterministic, all fitted to the area):
297
+
298
+ | `layout` | Placement |
299
+ |---|---|
300
+ | `force` (default) | Force-directed (Barnes–Hut above 150 nodes). |
301
+ | `circular` (`circle`) | Around a circle by group, a gap between groups, names rotated outward. |
302
+ | `radial` | A tree from `focus` (or the most connected node); each ring is one step further. Links outside the tree are faint. |
303
+ | `concentric` | Rings by weight, the largest in the centre, by group within each ring. |
304
+ | `clusters` | One sunflower cluster per group, space ∝ √size. |
305
+ | `tree` | The radial tree laid flat (left → right on wide areas), S-shaped connectors. |
306
+ | `grid` | A grid ordered by group and weight, every node named. |
307
+
308
+ `labels` (number, list or `true`), `highlight` (a node and its neighbors), `focus` (a node id: it goes to the center, or the root in `radial`/`tree`, and is highlighted with its neighbors, all labeled; in the browser a click does it with a smooth transition, and a second click or Esc returns), `iterations`, `maxRadius`, `curveness`.
309
+
310
+ ### `chord`
311
+
312
+ `from`, `to`, `value` rows, or `matrix` + `names`. `directed` (default false), `sort`, `ribbonColor: "smaller" | "larger" | "source"`, `labelOrientation: "radial"`.
313
+
314
+ ### `flowchart` (`flow`, `diagram`)
315
+
316
+ `nodes` with `id`, `label`, `type` (`start`, `end`, `process`, `decision`, `data`, `note`), optional `lane` (swimlanes) and `group`; `edges` (or `data`) with `from`, `to` and an optional `label` ("Yes"/"No"). Layered layout with automatic direction (`direction: "TB" | "LR"`), orthogonal connectors with arrows, loops routed around the side. `nodeWidth`, `swimlanes: false`.
317
+
318
+ ## Grids, flows and numbers
319
+
320
+ ### `heatmap`
321
+
322
+ `x`, `y` categories and `value`. Sequential ramp by default; `midpoint` (or `diverging: true`) for a diverging ramp. Values are written in cells when they fit.
323
+
324
+ ### `sankey`
325
+
326
+ `from`, `to`, `value` (also detects `source`/`target`). Color starts at the first stage with more than one node and flows downstream.
327
+
328
+ ### `wordcloud` (`words`, `tagcloud`, `nube`)
329
+
330
+ Words sized by frequency, done carefully: every word horizontal; each word's **area** proportional to its count (font size grows with the square root); a deterministic spiral layout from the most frequent word outwards, with no collisions; one hue that darkens with frequency, or one color per `series` with a legend. If the important words don't fit, the whole cloud shrinks; the words that still don't fit are named in the alt text, never cut off.
331
+
332
+ ```js
333
+ plot({ type: 'wordcloud', data, text: 'word', value: 'mentions', title: '“Data” dominates what companies ask for' });
334
+ plot({ type: 'wordcloud', text: openAnswers, title: '…', subtitle: 'Mentions in 312 open answers' }); // raw text
335
+ ```
336
+
337
+ | Option | Meaning |
338
+ |---|---|
339
+ | `text` | Field with the words, or the raw text to count (a string or a list of strings). Raw text drops Spanish and English stopwords and numbers. |
340
+ | `textField` | A field of free text in each row (open survey answers): words are counted across rows. |
341
+ | `value` | Weight of each word (default: 1 per row, summed). |
342
+ | `series` | Color by group, with a legend. |
343
+ | `top` | Most words to show (default 80). |
344
+ | `stopwords`, `minLength` | Extra words to ignore; shortest word kept (default 3). |
345
+ | `minFont`, `maxFont` | Font size range. |
346
+ | `highlight` | Words to emphasize; the rest are muted. |
347
+
348
+ A cloud is good at "what comes up most" and bad at "by how much". `lint` suggests a bar chart (`top: 15`) when there are few words, and asks for a `subtitle` that says what was counted.
349
+
350
+ ### `kpi`
351
+
352
+ `items: [{ label, value, delta, previous, period, good: "up" | "down", trend: [...], target, format }]`. Delta is a fraction (`0.12` → +12%); `good: "down"` for costs. Tiles reflow into balanced rows.
@@ -0,0 +1,46 @@
1
+ # Color scales
2
+
3
+ Every colormap that ships with matplotlib is available by name, plus your own. Use them wherever a chart maps a number to a color: heatmaps, choropleths, point maps, treemaps colored by change and word clouds.
4
+
5
+ ```js
6
+ plot({ type: 'heatmap', data, x: 'hour', y: 'day', value: 'trips', colorScale: 'viridis' });
7
+ plot({ type: 'choropleth', geo: 'chile', data, key: 'region', value: 'change', colorScale: 'RdBu' });
8
+ plot({ ...spec, colorScale: 'magma_r' }); // _r reverses any map
9
+ plot({ ...spec, colorScale: ['#FFF7EC', '#FC8D59', '#7F0000'] }); // your own stops
10
+ plot({ ...spec, colorScale: { name: 'coolwarm', mid: 100 } }); // diverging around 100
11
+ ```
12
+
13
+ - **Names are case-insensitive** (`'rdbu'` works) and `_r` reverses any map.
14
+ - **Diverging maps center themselves**: on 0 when the data crosses zero, otherwise on the middle of the data. Set `mid` (or `midpoint`) to choose the center.
15
+ - **Your own scale** is a list of two or more colors, evenly spaced from low to high, or `{ colors, reverse, mid }`.
16
+ - Interpolation happens in OKLab, so steps look even. The stops were sampled from matplotlib 3.9 and stay within 2 ΔE (×100) of the original.
17
+ - `cmap` and `colormap` are aliases of `colorScale`. The older `ramp` option still works.
18
+
19
+ ## Categorical palettes
20
+
21
+ `palette` takes a list of colors, a plotagents palette, or one of matplotlib's qualitative maps:
22
+
23
+ ```js
24
+ plot({ ...spec, palette: 'tab10' });
25
+ plot({ ...spec, palette: 'Set2' });
26
+ plot({ ...spec, palette: 'viridis' }); // a continuous map, sampled into distinct colors
27
+ ```
28
+
29
+ The default palette is validated for color-vision deficiencies; `lint` checks any other palette you bring (see [Themes](themes.md#validating-a-palette)).
30
+
31
+ ## Which one?
32
+
33
+ | Data | Use | Avoid |
34
+ |---|---|---|
35
+ | Ordered values, low → high | `viridis`, `cividis`, `magma`, `Blues`, `YlGnBu` | `jet`, `rainbow`, `hsv` (they invent edges where the data has none) |
36
+ | Values around a meaningful center (0, a target, an average) | `RdBu`, `coolwarm`, `PuOr`, `BrBG` | a sequential map |
37
+ | Angles, hours of the day, months | `twilight`, `hsv` | a sequential map |
38
+ | Categories | `palette: 'tab10'`, `'Set2'`, `'Dark2'` | any continuous map as `colorScale` |
39
+
40
+ `lint` flags the rainbow maps with a suggestion, and refuses a qualitative map used as a continuous scale.
41
+
42
+ ## All scales
43
+
44
+ {{colorscales}}
45
+
46
+ The list is also available in code: `colorScaleNames()` returns the names grouped by kind, and `resolveColorScale(name)` returns `{ name, stops, kind }`.