plotagents 0.5.0 → 0.8.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.
- package/AGENTS.md +29 -5
- package/README.md +44 -15
- package/bin/plotagents.js +40 -14
- package/dist/plotagents-geo.js +1 -1
- package/dist/plotagents.js +6161 -757
- package/docs/api.md +23 -5
- package/docs/charts.md +229 -10
- package/docs/colorscales.md +46 -0
- package/docs/dashboards.md +79 -0
- package/docs/es/agents.md +102 -0
- package/docs/es/api.md +172 -0
- package/docs/es/charts.md +436 -0
- package/docs/es/colorscales.md +46 -0
- package/docs/es/dashboards.md +79 -0
- package/docs/es/getting-started.md +142 -0
- package/docs/es/rendering.md +47 -0
- package/docs/es/themes.md +116 -0
- package/docs/getting-started.md +6 -2
- package/docs/themes.md +39 -9
- package/index.d.ts +275 -20
- package/package.json +11 -4
- package/schema.json +1382 -53
- package/skills/plotagents/SKILL.md +105 -0
- package/src/axes.js +11 -3
- package/src/charts/bar.js +40 -21
- package/src/charts/categorical.js +31 -7
- package/src/charts/chord.js +6 -4
- package/src/charts/choropleth.js +287 -37
- package/src/charts/corr.js +512 -0
- package/src/charts/dist.js +39 -21
- package/src/charts/donut.js +4 -2
- package/src/charts/gantt.js +460 -0
- package/src/charts/heatmap.js +2 -2
- package/src/charts/kpi.js +11 -8
- package/src/charts/line.js +187 -43
- package/src/charts/network.js +557 -189
- package/src/charts/pointmap.js +602 -0
- package/src/charts/radar.js +7 -1
- package/src/charts/regplot.js +16 -4
- package/src/charts/sankey.js +42 -6
- package/src/charts/scatter.js +103 -23
- package/src/charts/sunburst.js +7 -6
- package/src/charts/table.js +882 -0
- package/src/charts/treemap.js +12 -9
- package/src/charts/wordcloud.js +177 -0
- package/src/colorscales.js +193 -0
- package/src/dashboard.js +451 -0
- package/src/dashed.js +1 -1
- package/src/data.js +4 -2
- package/src/facet.js +4 -3
- package/src/format.js +80 -25
- package/src/frame.js +101 -29
- package/src/geo/projection.js +76 -9
- package/src/geo/registry.js +87 -8
- package/src/hierarchy.js +6 -0
- package/src/i18n.js +107 -0
- package/src/index.js +92 -14
- package/src/infer.js +18 -1
- package/src/interact.js +157 -36
- package/src/lint.js +353 -11
- package/src/runtime.js +301 -101
- package/src/scale.js +82 -11
- package/src/schema.js +235 -36
- package/src/stats.js +76 -17
- package/src/svg.js +4 -1
- package/src/text.js +5 -0
- package/src/theme.js +101 -14
package/docs/api.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
```js
|
|
4
4
|
import {
|
|
5
5
|
plot, safePlot, lint, describe, render, toHTML, configure,
|
|
6
|
-
|
|
6
|
+
dashboard, safeDashboard, renderDashboard, lintDashboard, planDashboard,
|
|
7
|
+
schema, dashboardSchema, toolDefinition, validatePalette, registerGeo, countWords,
|
|
7
8
|
} from 'plotagents';
|
|
8
9
|
```
|
|
9
10
|
|
|
@@ -39,7 +40,11 @@ const { ok, svg, type, description, errors, warnings, suggestions } = safePlot(s
|
|
|
39
40
|
|
|
40
41
|
### `lint(spec)`
|
|
41
42
|
|
|
42
|
-
Reviews a spec without drawing it and returns `{ ok, type, errors, warnings, suggestions }`. Messages
|
|
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).
|
|
43
48
|
|
|
44
49
|
### `describe(spec)`
|
|
45
50
|
|
|
@@ -49,7 +54,7 @@ Returns a text description of the chart, such as "Highest: Biobío with 1,240 (3
|
|
|
49
54
|
|
|
50
55
|
### `render(target, spec, opts?)`
|
|
51
56
|
|
|
52
|
-
Draws into an element (a CSS selector or a DOM node) with tooltips, responsive width, time
|
|
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).
|
|
53
58
|
|
|
54
59
|
```js
|
|
55
60
|
const chart = plotagents.render('#chart', spec);
|
|
@@ -59,7 +64,7 @@ chart.destroy(); // remove listeners and markup
|
|
|
59
64
|
|
|
60
65
|
| Option | Meaning |
|
|
61
66
|
|---|---|
|
|
62
|
-
| `controls` | `false` hides
|
|
67
|
+
| `controls` | `false` hides time controls even when the spec asks for them (`slider: true`). |
|
|
63
68
|
|
|
64
69
|
The handle exposes `svg` (the current `<svg>` element) and `spec`.
|
|
65
70
|
|
|
@@ -89,7 +94,20 @@ configure(); // returns the current defaults
|
|
|
89
94
|
configure(null); // clears them
|
|
90
95
|
```
|
|
91
96
|
|
|
92
|
-
It accepts any spec option: `font`, `textScale`, `locale`, `theme`, `palette`, `ramp` and so on.
|
|
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).
|
|
93
111
|
|
|
94
112
|
### `validatePalette(colors, opts?)`
|
|
95
113
|
|
package/docs/charts.md
CHANGED
|
@@ -15,18 +15,26 @@ Every chart takes a spec with `data` (rows) plus field names for each role. The
|
|
|
15
15
|
| `highlight` | One name or a list: emphasize these, mute the rest. |
|
|
16
16
|
| `colors` | `{ "name": "#hex" }` to pin an entity's color. |
|
|
17
17
|
| `format`, `xFormat`, `yFormat` | `"number" \| "compact" \| "percent" \| "currency" \| "integer"` or `{ prefix, suffix, decimals, currency }`. |
|
|
18
|
-
| `locale` | `"es"` (default: `1.234,5`)
|
|
18
|
+
| `locale` | `"es"` (default: `1.234,5`), `"en"` (`1,234.5`), `"pt"`, `"fr"` (`1 234,5`), `"de"`, or `"auto"`: recognized from the title, subtitle, column names and categories. Tables use `"auto"` when `locale` is omitted. |
|
|
19
19
|
| `xLabel`, `yLabel` | Axis titles (horizontal, never rotated). |
|
|
20
20
|
| `xScale`, `yScale` | `"log"` where it makes sense. |
|
|
21
21
|
| `xDomain`, `yDomain` | Fixed `[min, max]`. |
|
|
22
22
|
| `refLines` | `[{ y: 600, label: "Target" }]` (or `x`, or `value` for bars). |
|
|
23
23
|
| `facet` (`col`), `columns`, `sharey`, `sharex` | Small multiples, one panel per value. |
|
|
24
|
-
| `time`, `timeValue
|
|
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. |
|
|
25
26
|
| `compare` | With `time`: compare against `"previous"` (default), `"first"`, a given period, or `false`. |
|
|
26
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). |
|
|
27
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()`. |
|
|
28
30
|
| `width`, `height`, `padding` | Pixels. Height is automatic for horizontal bars, heatmaps, KPI tiles, and narrow treemaps, sunbursts and radars. |
|
|
29
|
-
| `legend` | `false`
|
|
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.
|
|
30
38
|
|
|
31
39
|
## Compare
|
|
32
40
|
|
|
@@ -62,7 +70,48 @@ Every chart takes a spec with `data` (rows) plus field names for each role. The
|
|
|
62
70
|
| `points` | Markers on every point (automatic for short series). |
|
|
63
71
|
| `stack` | Areas: `true` (default with series), `"percent"`, or `false` to overlap. |
|
|
64
72
|
| `fill` | Single line: `false` removes the soft gradient. |
|
|
65
|
-
| `xRange` | Visible range `[from, to]`; this is what the
|
|
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.
|
|
66
115
|
|
|
67
116
|
## Relationships
|
|
68
117
|
|
|
@@ -85,6 +134,40 @@ Every chart takes a spec with `data` (rows) plus field names for each role. The
|
|
|
85
134
|
| `kind` | `jointplot`: `"scatter"` or `"reg"`. |
|
|
86
135
|
| `marginal` | `jointplot`: `"hist"` or `"kde"`. |
|
|
87
136
|
|
|
137
|
+
### `corr` (`correlation`, `corrplot`) · `clustermap`
|
|
138
|
+
|
|
139
|
+
Correlation matrix of numeric fields, computed for you from the rows (pairwise complete cases, like `df.corr()`). Each pair appears once, below the diagonal, on a diverging scale fixed at −1 to 1, so the same color means the same strength in every chart. Long labels turn 45°. The tooltip gives r, n and the p-value.
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
plot({ type: 'corr', data, vars: ['income', 'schooling', 'poverty', 'density'], alpha: 0.05,
|
|
143
|
+
title: 'Where schooling is higher, income is higher (r = 0.85) and poverty lower (r = −0.81)' });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
| Option | Meaning |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `vars` | Fields to relate, in order. Default: every numeric field. `y: [...]` works too. |
|
|
149
|
+
| `method` | `"pearson"` (default) or `"spearman"` (ranks: monotonic relations, robust to outliers). |
|
|
150
|
+
| `triangle` | `"lower"` (default) or `"full"`. |
|
|
151
|
+
| `cluster` | Reorder by hierarchical clustering (average linkage on 1 − r) and draw the dendrogram on top. `type: "clustermap"` sets it and shows the full matrix; `dendrogram: false` keeps the order without the tree. |
|
|
152
|
+
| `alpha` | Grey out pairs with p ≥ alpha (Fisher's z) and add a legend key. |
|
|
153
|
+
| `order` | Variable order (list). |
|
|
154
|
+
| `highlight` | One variable: its row and column stay, the rest fade. |
|
|
155
|
+
| `labelsFor` | Display names, e.g. `{ areas_verdes: "Green space" }`. |
|
|
156
|
+
| `x`, `y`, `value` | Draw a matrix you already computed, in long form (one row per pair). |
|
|
157
|
+
|
|
158
|
+
### `pairplot` (`pairs`, `splom`, `scattermatrix`)
|
|
159
|
+
|
|
160
|
+
Scatter-plot matrix: each pair as a scatter below the diagonal, each variable's distribution on it (histogram, or density with `hue`), and Pearson r above, tinted by strength and split by `hue` group when there is room. Axes are shared by column and row; tick labels sit only on the outer edges.
|
|
161
|
+
|
|
162
|
+
| Option | Meaning |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `vars` | Fields to plot (3–6 read best). Default: the first 6 numeric fields. |
|
|
165
|
+
| `hue` | Color by group, with a legend. |
|
|
166
|
+
| `diag` | `"hist"` or `"kde"`. |
|
|
167
|
+
| `upper` | `"corr"` (default), `"scatter"` or `false`. `corner: true` keeps only the lower triangle. |
|
|
168
|
+
| `kind` | `"reg"` adds a least-squares line to every scatter panel. |
|
|
169
|
+
| `radius`, `bins`, `panelSize` | Point radius, histogram bins, maximum panel size. |
|
|
170
|
+
|
|
88
171
|
### `radar` (`spider`)
|
|
89
172
|
|
|
90
173
|
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.
|
|
@@ -128,7 +211,8 @@ Long data (`axis`, `value`, `series`) or wide data (`axes: [fields]`, one row pe
|
|
|
128
211
|
| Option | Meaning |
|
|
129
212
|
|---|---|
|
|
130
213
|
| `depth` | Show only the first N levels. |
|
|
131
|
-
| `focus` | Path to drill into, e.g. `["Mining"]`. In the browser, clicking a group does it and breadcrumbs go back. |
|
|
214
|
+
| `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. |
|
|
215
|
+
| `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. |
|
|
132
216
|
| `showValues` | Append values to the labels. |
|
|
133
217
|
| `colorBy` | `"change"` colors tiles by % change vs the comparison period (growth cool, decline warm); `"label"` colors a single-level treemap by item. |
|
|
134
218
|
| `shade` | Subtle lightness steps inside each category. Off by default: one solid color per category. |
|
|
@@ -157,26 +241,74 @@ Age groups (`age` or `y`), two sides (`sex`, `series` or `hue`) and a value. Bac
|
|
|
157
241
|
|
|
158
242
|
### `choropleth` (`map`) · `symbolmap` (`bubblemap`)
|
|
159
243
|
|
|
160
|
-
Chilean regions (`geo: "chile"`, default), US states (`geo: "usa"`) and world countries (`geo: "world"`) ship with the library
|
|
244
|
+
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").
|
|
161
245
|
|
|
162
246
|
**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.
|
|
163
247
|
|
|
164
248
|
| Option | Meaning |
|
|
165
249
|
|---|---|
|
|
166
|
-
| `geo` | `"chile"`, `"usa"`, `"world"`, a name registered with `registerGeo()`,
|
|
167
|
-
| `featureKey` | Feature property to match
|
|
250
|
+
| `geo` | `"chile"`, `"usa"`, `"world"`, a name registered with `registerGeo()`, a GeoJSON or TopoJSON object, or (browser) a URL. |
|
|
251
|
+
| `featureKey`, `nameKey` | Feature property to match, and the one with the display name (both detected when omitted). |
|
|
252
|
+
| `fitTo` | Zoom to regions, a `[lonMin, latMin, lonMax, latMax]` box or `"markers"`. |
|
|
253
|
+
| `markers`, `layers` | Points by lon/lat on top; extra GeoJSON lines, polygons or points over or under the map. |
|
|
168
254
|
| `scale`, `classes` | `"quantile"` (default), `"equal"`, `"continuous"` or a list of breaks; 5 classes by default. |
|
|
169
255
|
| `midpoint` / `diverging` | Diverging classes around a value (e.g. 0 for changes). |
|
|
170
256
|
| `missing` | `"plain"` drops the hatch and the "No data" legend entry. |
|
|
171
257
|
| `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. |
|
|
172
|
-
| `projection` | `"mercator"`, `"equalEarth"`
|
|
258
|
+
| `projection` | `"mercator"`, `"equalEarth"`, `"albersUsa"`, `"equirectangular"`, `"conic"`, `"identity"`, `"planar"` or `{ type, parallels, rotate }` (picked automatically). |
|
|
259
|
+
| `borderColor`, `borderWidth` | Region borders. |
|
|
173
260
|
| `dashed`, `dashedLabel` | Hatch some regions (preliminary figures, disputed areas): names, codes or a data field. They keep their class color. |
|
|
174
261
|
| `maxRadius` | `symbolmap`: largest circle radius. |
|
|
175
262
|
|
|
263
|
+
#### Your own geometry: GeoJSON and TopoJSON
|
|
264
|
+
|
|
265
|
+
`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.
|
|
266
|
+
|
|
267
|
+
```js
|
|
268
|
+
import { readFileSync } from 'node:fs';
|
|
269
|
+
const comunas = JSON.parse(readFileSync('comunas-biobio.geojson', 'utf8'));
|
|
270
|
+
plot({
|
|
271
|
+
type: 'choropleth',
|
|
272
|
+
geo: comunas, // properties: { CUT_COM: "08101", NOM_COM: "Concepción", … }
|
|
273
|
+
data: [{ comuna: 'Concepción', poverty: 7.1 }, { comuna: 'Coronel', poverty: 12.4 }],
|
|
274
|
+
key: 'comuna', value: 'poverty', // NOM_COM is found automatically
|
|
275
|
+
markers: { data: hospitals, lon: 'lon', lat: 'lat', label: 'name', legend: 'Hospital' },
|
|
276
|
+
layers: [{ geo: rivers, label: 'Biobío river', stroke: '#5BA3E0', width: 2 }],
|
|
277
|
+
fitTo: ['Concepción', 'Talcahuano', 'Hualpén'],
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
- **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.
|
|
282
|
+
- **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).
|
|
283
|
+
- **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.
|
|
284
|
+
- **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.
|
|
285
|
+
- **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.
|
|
286
|
+
- **Style.** `borderColor`, `borderWidth`, `pointRadius`, `lineWidth`, `ramp` (a named or custom sequential ramp), `palette` and `colors` (categories).
|
|
287
|
+
- **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.
|
|
288
|
+
|
|
176
289
|
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.
|
|
177
290
|
|
|
178
291
|
`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.
|
|
179
292
|
|
|
293
|
+
### `pointmap` (`dotmap`, `tilemap`)
|
|
294
|
+
|
|
295
|
+
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).
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
plot({ type: 'pointmap', data: cities, lon: 'lon', lat: 'lat', size: 'population', label: 'city',
|
|
299
|
+
title: 'Santiago concentrates 40% of the population', source: 'INE' });
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
| Option | Meaning |
|
|
303
|
+
|---|---|
|
|
304
|
+
| `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). |
|
|
305
|
+
| `tilesKey` | Key or token for CARTO, Mapbox or Esri. |
|
|
306
|
+
| `tileStyle` | `"muted"` (default) desaturates and tints the tiles to the theme (a navy night map on `noche`); `"original"` keeps them as they are. |
|
|
307
|
+
| `tileOpacity`, `zoom`, `center` | Tile opacity, fixed zoom level and `[lon, lat]` center. |
|
|
308
|
+
| `labels`, `labelValues` | How many dots to name (8 largest by default), or a list; `labelValues: false` hides values. |
|
|
309
|
+
|
|
310
|
+
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.
|
|
311
|
+
|
|
180
312
|
## Dashed shapes: estimates, projections, provisional figures
|
|
181
313
|
|
|
182
314
|
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`.
|
|
@@ -193,7 +325,21 @@ This follows the Chart.js advice of using patterns (as `patternomaly` does) and
|
|
|
193
325
|
|
|
194
326
|
### `network` (`graph`)
|
|
195
327
|
|
|
196
|
-
Edges in `data` (`from`, `to`, optional `value`), optional `nodes` with `id`, `group` and `size`.
|
|
328
|
+
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.
|
|
329
|
+
|
|
330
|
+
`layout` picks the placement (all deterministic, all fitted to the area):
|
|
331
|
+
|
|
332
|
+
| `layout` | Placement |
|
|
333
|
+
|---|---|
|
|
334
|
+
| `force` (default) | Force-directed (Barnes–Hut above 150 nodes). |
|
|
335
|
+
| `circular` (`circle`) | Around a circle by group, a gap between groups, names rotated outward. |
|
|
336
|
+
| `radial` | A tree from `focus` (or the most connected node); each ring is one step further. Links outside the tree are faint. |
|
|
337
|
+
| `concentric` | Rings by weight, the largest in the centre, by group within each ring. |
|
|
338
|
+
| `clusters` | One sunflower cluster per group, space ∝ √size. |
|
|
339
|
+
| `tree` | The radial tree laid flat (left → right on wide areas), S-shaped connectors. |
|
|
340
|
+
| `grid` | A grid ordered by group and weight, every node named. |
|
|
341
|
+
|
|
342
|
+
`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`.
|
|
197
343
|
|
|
198
344
|
### `chord`
|
|
199
345
|
|
|
@@ -213,6 +359,79 @@ Edges in `data` (`from`, `to`, optional `value`), optional `nodes` with `id`, `g
|
|
|
213
359
|
|
|
214
360
|
`from`, `to`, `value` (also detects `source`/`target`). Color starts at the first stage with more than one node and flows downstream.
|
|
215
361
|
|
|
362
|
+
### `wordcloud` (`words`, `tagcloud`, `nube`)
|
|
363
|
+
|
|
364
|
+
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.
|
|
365
|
+
|
|
366
|
+
```js
|
|
367
|
+
plot({ type: 'wordcloud', data, text: 'word', value: 'mentions', title: '“Data” dominates what companies ask for' });
|
|
368
|
+
plot({ type: 'wordcloud', text: openAnswers, title: '…', subtitle: 'Mentions in 312 open answers' }); // raw text
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
| Option | Meaning |
|
|
372
|
+
|---|---|
|
|
373
|
+
| `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. |
|
|
374
|
+
| `textField` | A field of free text in each row (open survey answers): words are counted across rows. |
|
|
375
|
+
| `value` | Weight of each word (default: 1 per row, summed). |
|
|
376
|
+
| `series` | Color by group, with a legend. |
|
|
377
|
+
| `top` | Most words to show (default 80). |
|
|
378
|
+
| `stopwords`, `minLength` | Extra words to ignore; shortest word kept (default 3). |
|
|
379
|
+
| `minFont`, `maxFont` | Font size range. |
|
|
380
|
+
| `highlight` | Words to emphasize; the rest are muted. |
|
|
381
|
+
|
|
382
|
+
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.
|
|
383
|
+
|
|
216
384
|
### `kpi`
|
|
217
385
|
|
|
218
386
|
`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.
|
|
387
|
+
|
|
388
|
+
## Tables
|
|
389
|
+
|
|
390
|
+
### `table` (`tabla`, `datatable`)
|
|
391
|
+
|
|
392
|
+
For when the reader needs to look up the exact number. Thin rules between rows and nothing else; numbers right-aligned in tabular figures with the same decimals down each column; empty cells as a dash. Each column gets the indicator that explains it.
|
|
393
|
+
|
|
394
|
+
```js
|
|
395
|
+
plot({
|
|
396
|
+
type: 'table',
|
|
397
|
+
title: 'Two of six projects are already over budget',
|
|
398
|
+
data: projects,
|
|
399
|
+
columns: ['project', 'area',
|
|
400
|
+
{ field: 'progress', type: 'progress' },
|
|
401
|
+
{ field: 'spent', label: 'Spent / budget', type: 'progress', target: 'budget', good: 'down' },
|
|
402
|
+
'status'],
|
|
403
|
+
sortBy: 'progress',
|
|
404
|
+
});
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
| Column `type` | Draws | Inferred when |
|
|
408
|
+
|---|---|---|
|
|
409
|
+
| `bar` | Value + data bar on a shared scale (diverging around 0) | The main numeric column (`value`/`y`, or the first number); `bars: false` turns it off |
|
|
410
|
+
| `delta` | ▲ / ▼ + signed change, green or red by `good` (`"down"` for costs) | Field named var, change, delta, growth, yoy, cambio, variación… |
|
|
411
|
+
| `heat` | Cell tinted by a sequential scale (diverging when it crosses 0); `colorScale` per column | — |
|
|
412
|
+
| `sparkline` | Mini line, last point in the accent | The field holds lists of numbers |
|
|
413
|
+
| `progress` | Percent + meter; against `target` (field or number) or as 0–1 / 0–100 | Field named progress, avance, completion…, or meta/target/goal with fractions |
|
|
414
|
+
| `badge` | Status pill | Values are status words (done, at risk, late, in progress… in es/en/pt/fr/de): good, warning, critical, info colors |
|
|
415
|
+
| `check` | ✓ or a dash, with an accessible label | Booleans, or sí/no, yes/no… |
|
|
416
|
+
| `date` | Readable date in the chart's language | ISO dates |
|
|
417
|
+
| `link` | Underlined link; only `http(s)` and `mailto` get an `href` | URLs |
|
|
418
|
+
| `rank` | Position under the current sort | `rank: true` |
|
|
419
|
+
| `text`, `number` | Text (wraps to 2–3 lines) and plain numbers | Everything else |
|
|
420
|
+
|
|
421
|
+
| Option | Meaning |
|
|
422
|
+
|---|---|
|
|
423
|
+
| `columns` | Order and type: field names or `{ field, label, type, format, align, width, good, target, colorScale, total, lines }`. Omit to use every field. `x`/`label` picks the row-name column and `y` a list of value columns. |
|
|
424
|
+
| `sortBy`, `sort` | Column and direction (`"desc"` for numbers, `"asc"` for text by default). `sort: "-sales"` works too. Empty cells always go last. |
|
|
425
|
+
| `pageSize`, `page` | Up to 15 rows are shown whole; beyond that, pages of 10. `pageSize: false` shows everything. The pager is numbers and arrows only (`11–20 / 248`, `‹ 1 2 3 … 25 ›`). |
|
|
426
|
+
| `query` | Keep rows that contain these words, ignoring accents and case. |
|
|
427
|
+
| `total` | Totals row: sums numbers, averages percents and progress, leaves changes blank. `{ sales: "mean" }` per column; `totalLabel` renames it. |
|
|
428
|
+
| `group` | Rows under a tinted band per value, with a count. |
|
|
429
|
+
| `highlight` | Rows whose name matches get a tint and an accent mark; the bars of the other rows are muted. |
|
|
430
|
+
| `top` | Keep the first N rows after sorting. |
|
|
431
|
+
| `cards` | `"auto"` (default) turns each row into a card when the columns do not fit (phones): name and main value on top, the bar under them, the other columns as label–value lines. `true` always, `false` never. |
|
|
432
|
+
| `density`, `striped` | `"compact"` / `"comfortable"` rows; zebra stripes for very wide tables. |
|
|
433
|
+
|
|
434
|
+
In the browser `render()` adds a search box above 20 rows (`search: false` hides it; `true` forces it), click-to-sort headers (natural order, reversed, then the original order), and page buttons; all of them work from the keyboard and announce `aria-sort` and `aria-current`. Resizing re-renders at the new width, so a table becomes cards on a phone and columns again on a desktop.
|
|
435
|
+
|
|
436
|
+
The language of the few words a table writes (Total, Search, No results) and the number format come from `locale`; without it, the table recognizes the language from its own title, headers and categories.
|
|
437
|
+
|
|
@@ -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 }`.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Dashboards
|
|
2
|
+
|
|
3
|
+
`dashboard(spec)` turns a handful of chart specs into a finished, self-contained HTML dashboard. Like a single chart, the design decisions come built in: you say what goes on the page, and the library decides where and how.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { dashboard } from 'plotagents';
|
|
7
|
+
import { writeFileSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
writeFileSync('sales.html', dashboard({
|
|
10
|
+
title: 'How are southern sales doing?',
|
|
11
|
+
subtitle: 'Sales by month, region and channel, 2024–2025, CLP millions',
|
|
12
|
+
source: 'Commercial ERP',
|
|
13
|
+
updated: '2026-01-05',
|
|
14
|
+
locale: 'en',
|
|
15
|
+
data: sales, // shared rows
|
|
16
|
+
kpis: [
|
|
17
|
+
{ label: 'Sales 2025', field: 'sales', agg: 'sum', format: 'compact',
|
|
18
|
+
compare: { field: 'year', current: 2025, previous: 2024 }, trendBy: 'month' },
|
|
19
|
+
{ label: 'Orders 2025', field: 'orders', agg: 'sum', format: 'compact',
|
|
20
|
+
compare: { field: 'year', current: 2025, previous: 2024 } },
|
|
21
|
+
{ label: 'Online share', value: 0.361, delta: 0.13, format: 'percent', period: 'vs 2024' },
|
|
22
|
+
],
|
|
23
|
+
filters: ['region', 'channel'],
|
|
24
|
+
charts: [
|
|
25
|
+
{ type: 'line', x: 'month', y: 'sales', series: 'region', highlight: 'Ñuble',
|
|
26
|
+
title: 'Ñuble grew 37% this year, faster than any other region' },
|
|
27
|
+
{ type: 'choropleth', geo: 'chile', key: 'region', value: 'sales', fitTo: ['Biobío', 'Ñuble', 'Maule', 'Araucanía'],
|
|
28
|
+
title: 'Biobío makes 36% of sales' },
|
|
29
|
+
{ type: 'bar', x: 'channel', y: 'sales', series: 'year',
|
|
30
|
+
title: 'Online is the only channel growing strongly' },
|
|
31
|
+
{ type: 'treemap', path: ['region', 'channel'], value: 'sales',
|
|
32
|
+
title: 'Physical stores are still half the business' },
|
|
33
|
+
],
|
|
34
|
+
}));
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The page works without JavaScript: every chart is drawn on the server, so it opens in any browser, prints and converts to PDF. When the browser build is available (it is, in Node), the page also gets tooltips, map zoom and live filters. In a web app, `renderDashboard('#board', spec)` draws the same dashboard into an element.
|
|
38
|
+
|
|
39
|
+
## What it decides for you
|
|
40
|
+
|
|
41
|
+
These defaults come from dashboard design practice: Stephen Few's *Information Dashboard Design*, the Z-pattern of reading, and what makes business dashboards hard to read.
|
|
42
|
+
|
|
43
|
+
| Rule | What plotagents does |
|
|
44
|
+
|---|---|
|
|
45
|
+
| **One question per dashboard** | The `title` is the question ("How are southern sales doing?"). `lintDashboard` warns above 9 charts and suggests splitting above 6. |
|
|
46
|
+
| **Numbers first** | KPIs go in one row at the top, at most 5 (warning above). Each one has a label, the figure, its change with an arrow **and** a sign (never color alone), the comparison period and an optional sparkline. |
|
|
47
|
+
| **Z-pattern reading** | The main chart comes right after the KPIs, at full width: the one marked `primary: true`, or else the first time series. |
|
|
48
|
+
| **Full rows, no holes** | A 12-column grid in balanced rows: 2 charts → 6 + 6 (8 + 4 when one is wide), 3 → 4 + 4 + 4, 4 → two rows of two, 5 → 3 + 2. Panels in a row share a height. |
|
|
49
|
+
| **Width follows the chart** | Time series, Gantt charts, heatmaps and flows read left to right, so they get wide panels. Donuts, KPIs and short rankings get narrow ones. Chile's map gets a narrow column. |
|
|
50
|
+
| **Same thing, same color** | One color map across every chart: "Ñuble" is the same color in the line, the treemap and the bars. `colors` pins colors for the whole page. |
|
|
51
|
+
| **One voice** | One theme, locale and typeface for every panel. The source appears once, at the bottom; a chart keeps its own source only when it differs. |
|
|
52
|
+
| **Filters as buttons** | `filters` become buttons when a field has up to 6 values, and a dropdown when it has more. They filter every chart that has the field, recompute the KPIs and say what is filtered in each subtitle. A filter on a chart's `time` field moves that chart to the chosen period. |
|
|
53
|
+
| **Phones** | One column in reading order; KPIs in two columns. |
|
|
54
|
+
| **Print** | Panels don't break across pages; the filter bar is not sticky on paper. |
|
|
55
|
+
|
|
56
|
+
## Spec
|
|
57
|
+
|
|
58
|
+
| Option | Meaning |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `title`, `subtitle` | The question, and the scope (measure, unit, period). |
|
|
61
|
+
| `source`, `updated`, `note` | Footer and header metadata. |
|
|
62
|
+
| `locale`, `theme`, `font`, `palette` | Applied to every chart (`theme: "noche"` for a dark dashboard). |
|
|
63
|
+
| `data` | Shared rows. Charts without `data` use them; computed KPIs and filters work on them. |
|
|
64
|
+
| `kpis` | `[{ label, value, delta, period, trend, good, format }]` (fixed) or `[{ label, field, agg, over, scale, compare, trendBy, format }]` (computed). `agg`: `sum`, `mean`, `median`, `min`, `max`, `count`, `distinct`, `ratio` (with `over`). `compare: { field, current, previous }` shows the current period and its change. `trendBy: "month"` draws the sparkline. |
|
|
65
|
+
| `charts` | Chart specs, as in `plot()`. Per chart: `primary: true`, `size: "full" \| "large" \| "medium" \| "small"` or `span` (of 12). |
|
|
66
|
+
| `filters` | `["region"]` or `[{ field, label, control: "buttons" \| "select", value }]`. |
|
|
67
|
+
| `layout` | `"auto"` (default), `"executive"` (main chart full width, then pairs) or `"grid"` (equal panels). |
|
|
68
|
+
| `colors` | `{ "Biobío": "#3A47B8" }` for every chart. |
|
|
69
|
+
|
|
70
|
+
## For agents
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { safeDashboard, toolDefinition } from 'plotagents';
|
|
74
|
+
|
|
75
|
+
const tool = toolDefinition({ kind: 'dashboard' }); // JSON Schema of the dashboard spec
|
|
76
|
+
const { ok, html, errors, warnings, suggestions } = safeDashboard(input);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`safeDashboard` never throws. It reviews the dashboard as a whole (too many charts or KPIs, several pies, mixed themes, filters on fields no chart has, panels without a title) and every chart in it, tagging each issue with the chart's number and title so the model knows which one to fix. `planDashboard(spec)` returns the layout decisions (panels, widths, rows, colors, filters) without drawing anything.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# plotagents · guía para agentes de IA
|
|
2
|
+
|
|
3
|
+
Una función, un objeto JSON. Sin decisiones de diseño: la biblioteca las toma por ti.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import { plot, safePlot } from 'plotagents';
|
|
7
|
+
const svg = plot({ type, data, x, y, series, title, subtitle, source });
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Reglas para un buen gráfico
|
|
11
|
+
|
|
12
|
+
1. **`title` es el hallazgo**, una oración completa con la cifra clave: "Biobío concentra el 30% de las ventas", no "Ventas por región".
|
|
13
|
+
2. **`subtitle` dice cómo leerlo**: medida, unidad y período.
|
|
14
|
+
3. **Define siempre `source`.** Si los datos son inventados, usa `source: "illustrative data"`.
|
|
15
|
+
4. `data` es una lista de filas (`[{ region: "Biobío", sales: 1240 }]`). Para una tabla ancha, pasa `y: ["revenue", "costs"]`.
|
|
16
|
+
5. Usa `safePlot(spec)` como tu herramienta: nunca lanza excepciones y devuelve `errors`, `warnings` y `suggestions`, cada uno con un `fix`. Si `ok` es `false`, corrige el spec y vuelve a llamarla.
|
|
17
|
+
6. Define `locale: "en"` para formatos numéricos en inglés (el valor por defecto es español: `1.234,5`); también hay `"pt"`, `"fr"`, `"de"` o `"auto"` para reconocerlo a partir del texto. Las tablas lo reconocen por sí solas.
|
|
18
|
+
|
|
19
|
+
## Qué tipo usar
|
|
20
|
+
|
|
21
|
+
| Para mostrar | `type` | Campos clave |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Comparar categorías | `bar` | `x`, `y`, `series?`, `stack?` (`true` / `"percent"`), `top?` |
|
|
24
|
+
| Contar filas por categoría | `countplot` | `x` |
|
|
25
|
+
| Media por categoría con un IC | `barplot` o `point` | `x`, `y`, `hue?` |
|
|
26
|
+
| Cambio en el tiempo | `line` | `x` fecha (`"2024-03"`) o año, `y`, `series?`; `ci: true` con varias observaciones por x |
|
|
27
|
+
| Un plan de proyecto o un cronograma | `gantt` | `task`, `start`, `end` (o `duration`), `group?`, `progress?`, `dependsOn?`, `today?`, `collapsed?` |
|
|
28
|
+
| Volumen en el tiempo | `area` | como `line`; se apila con `series` |
|
|
29
|
+
| Medidas en escalas distintas | `line` + `index: true`, o `facet` | **no hay doble eje** |
|
|
30
|
+
| Crecimiento exponencial | `line` + `yScale: "log"` | |
|
|
31
|
+
| Relación entre dos medidas | `regplot` (ajuste + IC) / `scatter` / `bubble` | `x`, `y`, `hue?`, `size?`, `label?` |
|
|
32
|
+
| Relación + marginales | `jointplot` | `x`, `y` |
|
|
33
|
+
| Qué medidas se mueven juntas | `corr` (`correlation`) | `vars?` (por defecto: todos los campos numéricos), `method?` (`"spearman"`), `alpha?: 0.05`; `cluster: true` o `type: "clustermap"` las agrupa con un dendrograma |
|
|
34
|
+
| Todos los pares de medidas a la vez | `pairplot` (`splom`) | `vars` (3–6), `hue?`, `corner?`, `kind?: "reg"` |
|
|
35
|
+
| Distribución de una medida | `hist` (`kde: true`), `kde`, `ecdf` | `x`, `hue?` |
|
|
36
|
+
| Distribución por categoría | `box`, `violin`, `strip`, `swarm` | `x` categoría, `y` medida, `hue?` |
|
|
37
|
+
| Perfil multidimensional | `radar` | `axis`, `value`, `series` (≤ 4 series) |
|
|
38
|
+
| Parte de un todo jerárquico | `treemap` o `sunburst` | `path: [level1, level2, …]`, `value` |
|
|
39
|
+
| Parte de un todo, ≤ 6 partes | `donut` / `pie` | `label`, `value`; `half: true` para un semicírculo |
|
|
40
|
+
| Magnitud en una grilla | `heatmap` | `x`, `y`, `value` |
|
|
41
|
+
| Un valor por región, estado o país | `choropleth` (`symbolmap` para totales) | `geo: "chile" \| "usa" \| "world"` o cualquier objeto GeoJSON/TopoJSON, `key`, `value` (texto → un color por categoría) |
|
|
42
|
+
| Puntos en lugares reales sobre un mapa de calles | `pointmap` | `lon`, `lat` (se detectan solos), `size?`, `hue?`, `value?`, `label?`; `tiles: "osm"` (por defecto) / `"esri-gray"` / `"esri-dark"`, o carto-* / mapbox-* con `tilesKey` |
|
|
43
|
+
| Quién se conecta con quién | `network` | aristas `from`, `to`, `value?`; `nodes?` con `group`; `layout?` |
|
|
44
|
+
| Intercambios entre pocos grupos | `chord` | `from`, `to`, `value` |
|
|
45
|
+
| Estructura por edad | `pyramid` | `age`, `sex`, `value` |
|
|
46
|
+
| Un proceso con decisiones | `flowchart` | `nodes` (`type`, `lane`), `edges` (`label`) |
|
|
47
|
+
| Flujos entre etapas | `sankey` | `from`, `to`, `value` |
|
|
48
|
+
| Una o más cifras | `kpi` | `items: [{ label, value, delta, period, trend, target, good }]` |
|
|
49
|
+
| Valores exactos para consultar | `table` | `columns?: [field \| { field, type, label, format, good, target }]`, `sortBy?`, `total?`, `group?`; los tipos `bar`, `delta`, `heat`, `sparkline`, `progress`, `badge` y `check` se infieren cuando se omiten |
|
|
50
|
+
| Lo que más aparece en un texto | `wordcloud` | `text` (campo o texto sin procesar), `value?`, `series?`; para comparar con exactitud, prefiere `bar` + `top` |
|
|
51
|
+
|
|
52
|
+
Los nombres de seaborn funcionan (`histplot`, `kdeplot`, `boxplot`, `violinplot`, `lmplot`, `stripplot`, `swarmplot`, `pointplot`), y `hue` es un alias de `series`. Omite `type` para que se infiera de los datos.
|
|
53
|
+
|
|
54
|
+
## Opciones más útiles
|
|
55
|
+
|
|
56
|
+
- `highlight: "Ñuble"`: destaca una categoría o serie y atenúa el resto. `exclude: ["Maule"]` quita entradas y recalcula el gráfico; los colores no cambian.
|
|
57
|
+
- `legend: "top" | "bottom" | "left" | "right" | false`: dónde va la leyenda (arriba por defecto).
|
|
58
|
+
- `colorScale: "viridis"`: cualquier mapa de colores de matplotlib para mapas de calor, mapas y treemaps por variación (`"RdBu"` diverge en torno a 0; `_r` lo invierte; también sirve una lista de colores). `palette: "tab10"` para categorías.
|
|
59
|
+
- `dashed` para lo que no es una cifra firme (estimaciones, proyecciones, cifras provisionales): un nombre, una lista o un campo de los datos (`dashed: "provisional"`). Las formas mantienen su color, con relleno achurado y contorno discontinuo. En un eje de tiempo, `dashedFrom: 2025` dibuja con trazo discontinuo las líneas, áreas y columnas desde ese año. **Define siempre `dashedLabel`** ("Proyección", "Preliminar") para que los lectores sepan qué significa el achurado.
|
|
60
|
+
- `facet: "region"` (+ `columns`): un panel por valor, con una escala compartida.
|
|
61
|
+
- `time: "year"`: un período a la vez (el más reciente) con la variación % respecto del anterior. Los controles de tiempo están **desactivados por defecto**: agrega `slider: true` solo cuando la persona pida recorrer los períodos (botones para ≤ 8 períodos y un deslizador mínimo en los demás casos; `"buttons"` / `"slider"` fuerzan uno de los dos, `play: false` oculta el botón de reproducir). Los treemaps se pueden colorear por variación con `colorBy: "change"`.
|
|
62
|
+
- `refLines: [{ y: 600, label: "Target" }]` y `annotations: [{ x: "2024-01", label: "New branch" }]`.
|
|
63
|
+
- `format: "percent" | "currency" | "compact" | "integer"` o `{ prefix, suffix, decimals }`.
|
|
64
|
+
- `xScale` / `yScale: "log"`.
|
|
65
|
+
- `table`: dale un título que enuncie el hallazgo, como a cualquier gráfico. Con más de 15 filas se pagina de a diez (`pageSize`), en teléfonos cada fila se muestra como una tarjeta y el navegador agrega orden, búsqueda y paginación. Usa `good: "down"` en los costos para que ▲ se vuelva rojo, `progress` con `target: "budget"` para el gasto frente al presupuesto y `total: true` para una fila de totales. Usa entre 4 y 7 columnas.
|
|
66
|
+
- `theme: "noche"` o un objeto JSON (`{ extends, categorical, type, marks, font }`); `palette: [...]` para tus propios colores.
|
|
67
|
+
- `font: "Poppins"` y `textScale: 1.5` (modo presentación); `configure({ font, textScale })` los define para todos los gráficos.
|
|
68
|
+
- `network` + `focus: "node id"`: centra un nodo y destaca sus vecinos.
|
|
69
|
+
- Tu propio mapa: `geo: <GeoJSON or TopoJSON>`; la propiedad que coincide se busca sola (`featureKey` para forzarla). Agrega `markers: [{ lon, lat, label }]`, `layers: [{ geo, label }]` para ríos o rutas, y `fitTo: ["Biobío"]` o una caja `[lonMin, latMin, lonMax, latMax]` para hacer zoom.
|
|
70
|
+
- `gantt`: las fechas `end` por día son inclusivas; una fila sin `end` es un hito. Define siempre `today` en un plan en curso. `collapsed: ["Phase 2"]` (o `true`) pliega las fases en una barra resumen; en el navegador, un clic en una fase la abre o la cierra.
|
|
71
|
+
- `network` + `layout: "force" | "circular" | "radial" | "concentric" | "clusters" | "tree" | "grid"` elige la disposición; `curveness: 0` endereza las aristas.
|
|
72
|
+
- `pointmap` dibuja sobre teselas de OpenStreetMap por defecto (`tiles: "esri-gray"`, `"mapbox-light"` + `tilesKey`, o tus propias `{ url, attribution }`). Las teselas solo se cargan cuando el SVG va incrustado en la página o en `toHTML`, no dentro de `<img>`.
|
|
73
|
+
- Tu propio estilo para todos los gráficos: `registerTheme("acme", { background, text, grid: { color, dash }, categorical: "tab10", font })` y luego `configure({ theme: "acme" })`.
|
|
74
|
+
- `geo: "usa"`: los estados coinciden por código postal ("TX"), nombre en inglés o en español, o código FIPS ("48"). Alaska y Hawái aparecen en recuadros; Puerto Rico aparece solo cuando tus datos lo incluyen. En el navegador, los mapas hacen zoom con un clic, con Ctrl/⌘ + rueda o pellizcando.
|
|
75
|
+
|
|
76
|
+
## Errores que la biblioteca ya resuelve
|
|
77
|
+
|
|
78
|
+
- Más de 8 series → de la 9.ª en adelante se pintan en gris; `lint` sugiere `highlight` o `facet`.
|
|
79
|
+
- Etiquetas de líneas que chocan → se separan con líneas guía.
|
|
80
|
+
- Dona con más de 6 partes → el resto se agrupa en "Otros".
|
|
81
|
+
- Etiquetas de categoría largas → el gráfico pasa a horizontal.
|
|
82
|
+
- Paleta propia difícil de distinguir → `lint` nombra el par problemático.
|
|
83
|
+
- Números como texto ("1.234,5", "12%", "$ 3.000") → se interpretan automáticamente.
|
|
84
|
+
- Decenas de miles de puntos → se dibujan como trazados agrupados y sigue siendo rápido.
|
|
85
|
+
|
|
86
|
+
## Tableros
|
|
87
|
+
|
|
88
|
+
Cuando alguien pida un dashboard, llama a `safeDashboard({ title, subtitle, source, data, kpis, charts, filters })` (herramienta: `toolDefinition({ kind: 'dashboard' })`). La disposición, los anchos, los colores compartidos y los filtros se deciden por ti:
|
|
89
|
+
|
|
90
|
+
- `title` es la **pregunta** que responde el tablero; cada gráfico igual lleva su hallazgo como título.
|
|
91
|
+
- Como máximo **5 KPI**, calculados a partir de `data` cuando sea posible: `{ label, field, agg: "sum", compare: { field: "year", current: 2025, previous: 2024 }, trendBy: "month" }`.
|
|
92
|
+
- **De 4 a 6 gráficos**. La primera serie de tiempo (o la que tenga `primary: true`) va a todo el ancho; el resto llena filas equilibradas.
|
|
93
|
+
- `filters: ["region", "channel"]` se convierten en botones que filtran todos los gráficos y KPI.
|
|
94
|
+
- No más de una dona; una sola `source` para la página.
|
|
95
|
+
|
|
96
|
+
## Salidas
|
|
97
|
+
|
|
98
|
+
- `plot(spec)` → cadena SVG. Insértala en HTML, Markdown, un archivo `.svg` o un PDF.
|
|
99
|
+
- `toHTML([spec1, spec2])` → página autocontenida con tooltips, deslizadores y drill-down.
|
|
100
|
+
- `describe(spec)` → texto alternativo del gráfico.
|
|
101
|
+
- `schema` / `toolDefinition()` → JSON Schema y definición de herramienta (Anthropic u OpenAI).
|
|
102
|
+
- CLI: `npx plotagents spec.json -o chart.svg`.
|