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/AGENTS.md
CHANGED
|
@@ -14,7 +14,7 @@ const svg = plot({ type, data, x, y, series, title, subtitle, source });
|
|
|
14
14
|
3. **Always set `source`.** If the data is made up, use `source: "illustrative data"`.
|
|
15
15
|
4. `data` is a list of rows (`[{ region: "Biobío", sales: 1240 }]`). For a wide table, pass `y: ["revenue", "costs"]`.
|
|
16
16
|
5. Call `safePlot(spec)` as your tool: it never throws and returns `errors`, `warnings` and `suggestions`, each with a `fix`. If `ok` is `false`, fix the spec and call again.
|
|
17
|
-
6. Set `locale: "en"` for English number formats (default is Spanish: `1.234,5`).
|
|
17
|
+
6. Set `locale: "en"` for English number formats (default is Spanish: `1.234,5`); also `"pt"`, `"fr"`, `"de"`, or `"auto"` to recognize it from the text. Tables recognize it on their own.
|
|
18
18
|
|
|
19
19
|
## Which type
|
|
20
20
|
|
|
@@ -24,39 +24,53 @@ const svg = plot({ type, data, x, y, series, title, subtitle, source });
|
|
|
24
24
|
| Count rows per category | `countplot` | `x` |
|
|
25
25
|
| Mean per category with a CI | `barplot` or `point` | `x`, `y`, `hue?` |
|
|
26
26
|
| Change over time | `line` | `x` date (`"2024-03"`) or year, `y`, `series?`; `ci: true` with several observations per x |
|
|
27
|
+
| A project plan or schedule | `gantt` | `task`, `start`, `end` (or `duration`), `group?`, `progress?`, `dependsOn?`, `today?`, `collapsed?` |
|
|
27
28
|
| Volume over time | `area` | like `line`; stacks with `series` |
|
|
28
29
|
| Measures on different scales | `line` + `index: true`, or `facet` | **there is no dual axis** |
|
|
29
30
|
| Exponential growth | `line` + `yScale: "log"` | |
|
|
30
31
|
| Relationship between two measures | `regplot` (fit + CI) / `scatter` / `bubble` | `x`, `y`, `hue?`, `size?`, `label?` |
|
|
31
32
|
| Relationship + marginals | `jointplot` | `x`, `y` |
|
|
33
|
+
| Which measures move together | `corr` (`correlation`) | `vars?` (default: every numeric field), `method?` (`"spearman"`), `alpha?: 0.05`; `cluster: true` or `type: "clustermap"` groups them with a dendrogram |
|
|
34
|
+
| Every pair of measures at once | `pairplot` (`splom`) | `vars` (3–6), `hue?`, `corner?`, `kind?: "reg"` |
|
|
32
35
|
| Distribution of one measure | `hist` (`kde: true`), `kde`, `ecdf` | `x`, `hue?` |
|
|
33
36
|
| Distribution by category | `box`, `violin`, `strip`, `swarm` | `x` category, `y` measure, `hue?` |
|
|
34
37
|
| Multi-dimensional profile | `radar` | `axis`, `value`, `series` (≤ 4 series) |
|
|
35
38
|
| Hierarchical part-to-whole | `treemap` or `sunburst` | `path: [level1, level2, …]`, `value` |
|
|
36
39
|
| Part-to-whole, ≤ 6 parts | `donut` / `pie` | `label`, `value`; `half: true` for a semicircle |
|
|
37
40
|
| Magnitude on a grid | `heatmap` | `x`, `y`, `value` |
|
|
38
|
-
| A value per region, state or country | `choropleth` (`symbolmap` for totals) | `geo: "chile" \| "usa" \| "world"
|
|
39
|
-
|
|
|
41
|
+
| A value per region, state or country | `choropleth` (`symbolmap` for totals) | `geo: "chile" \| "usa" \| "world"` or any GeoJSON/TopoJSON object, `key`, `value` (text → one color per category) |
|
|
42
|
+
| Dots at real places on a street map | `pointmap` | `lon`, `lat` (auto-detected), `size?`, `hue?`, `value?`, `label?`; `tiles: "osm"` (default) / `"esri-gray"` / `"esri-dark"`, or carto-* / mapbox-* with `tilesKey` |
|
|
43
|
+
| Who is connected to whom | `network` | edges `from`, `to`, `value?`; `nodes?` with `group`; `layout?` |
|
|
40
44
|
| Exchanges between a few groups | `chord` | `from`, `to`, `value` |
|
|
41
45
|
| Age structure | `pyramid` | `age`, `sex`, `value` |
|
|
42
46
|
| A process with decisions | `flowchart` | `nodes` (`type`, `lane`), `edges` (`label`) |
|
|
43
47
|
| Flows between stages | `sankey` | `from`, `to`, `value` |
|
|
44
48
|
| One or more numbers | `kpi` | `items: [{ label, value, delta, period, trend, target, good }]` |
|
|
49
|
+
| Exact values to look up | `table` | `columns?: [field \| { field, type, label, format, good, target }]`, `sortBy?`, `total?`, `group?`; types `bar`, `delta`, `heat`, `sparkline`, `progress`, `badge`, `check` are inferred when omitted |
|
|
50
|
+
| What comes up most in text | `wordcloud` | `text` (field or raw text), `value?`, `series?`; prefer `bar` + `top` to compare exactly |
|
|
45
51
|
|
|
46
52
|
seaborn names work (`histplot`, `kdeplot`, `boxplot`, `violinplot`, `lmplot`, `stripplot`, `swarmplot`, `pointplot`), and `hue` is an alias of `series`. Leave `type` out to infer it from the data.
|
|
47
53
|
|
|
48
54
|
## Most useful options
|
|
49
55
|
|
|
50
|
-
- `highlight: "Ñuble"`: emphasize one category or series and mute the rest.
|
|
56
|
+
- `highlight: "Ñuble"`: emphasize one category or series and mute the rest. `exclude: ["Maule"]` drops entries and recomputes the chart; colors stay put.
|
|
57
|
+
- `legend: "top" | "bottom" | "left" | "right" | false`: where the legend goes (top by default).
|
|
58
|
+
- `colorScale: "viridis"`: any matplotlib colormap for heatmaps, maps and treemaps by change (`"RdBu"` diverges around 0; `_r` reverses; or a list of colors). `palette: "tab10"` for categories.
|
|
51
59
|
- `dashed` for what is not a firm number (estimates, projections, provisional figures): a name, a list, or a data field (`dashed: "provisional"`). Shapes keep their color, with a hatched fill and a dashed outline. On a time axis, `dashedFrom: 2025` dashes lines, areas and columns from that year on. **Always set `dashedLabel`** ("Projection", "Preliminary") so readers know what the hatching means.
|
|
52
60
|
- `facet: "region"` (+ `columns`): one panel per value, on a shared scale.
|
|
53
|
-
- `time: "year"`: one period at a time (the latest) with % change vs the previous one
|
|
61
|
+
- `time: "year"`: one period at a time (the latest) with % change vs the previous one. Time controls are **off by default**: add `slider: true` only when the person asks to move through periods (buttons for ≤ 8 periods, a minimal slider otherwise; `"buttons"` / `"slider"` force one, `play: false` hides play). Treemaps can be colored by change with `colorBy: "change"`.
|
|
54
62
|
- `refLines: [{ y: 600, label: "Target" }]` and `annotations: [{ x: "2024-01", label: "New branch" }]`.
|
|
55
63
|
- `format: "percent" | "currency" | "compact" | "integer"` or `{ prefix, suffix, decimals }`.
|
|
56
64
|
- `xScale` / `yScale: "log"`.
|
|
65
|
+
- `table`: give a title that states the finding like any chart. More than 15 rows paginate in tens (`pageSize`), phones get one card per row, and the browser adds sort, search and paging. Use `good: "down"` on costs so ▲ turns red, `progress` with `target: "budget"` for spent vs budget, and `total: true` for a totals row. Keep 4–7 columns.
|
|
57
66
|
- `theme: "noche"` or a JSON object (`{ extends, categorical, type, marks, font }`); `palette: [...]` for your own colors.
|
|
58
67
|
- `font: "Poppins"` and `textScale: 1.5` (presentation mode); `configure({ font, textScale })` sets them for every chart.
|
|
59
68
|
- `network` + `focus: "node id"`: center a node and highlight its neighbors.
|
|
69
|
+
- Your own map: `geo: <GeoJSON or TopoJSON>`; the matching property is found for you (`featureKey` to force it). Add `markers: [{ lon, lat, label }]`, `layers: [{ geo, label }]` for rivers or routes, and `fitTo: ["Biobío"]` or a `[lonMin, latMin, lonMax, latMax]` box to zoom.
|
|
70
|
+
- `gantt`: day `end` dates are inclusive; a row without `end` is a milestone. Always set `today` on a live plan. `collapsed: ["Phase 2"]` (or `true`) folds phases into one summary bar; in the browser a click on a phase toggles it.
|
|
71
|
+
- `network` + `layout: "force" | "circular" | "radial" | "concentric" | "clusters" | "tree" | "grid"` picks the placement; `curveness: 0` straightens edges.
|
|
72
|
+
- `pointmap` draws on OpenStreetMap tiles by default (`tiles: "esri-gray"`, `"mapbox-light"` + `tilesKey`, or your own `{ url, attribution }`). Tiles load only when the SVG is inline or in `toHTML`, not inside `<img>`.
|
|
73
|
+
- Your own look for every chart: `registerTheme("acme", { background, text, grid: { color, dash }, categorical: "tab10", font })` then `configure({ theme: "acme" })`.
|
|
60
74
|
- `geo: "usa"`: states match by postal code ("TX"), English or Spanish name, or FIPS ("48"). Alaska and Hawaii are insets; Puerto Rico appears only when your data has it. In the browser, maps zoom on click, Ctrl/⌘ + scroll or pinch.
|
|
61
75
|
|
|
62
76
|
## Mistakes the library already handles
|
|
@@ -69,6 +83,16 @@ seaborn names work (`histplot`, `kdeplot`, `boxplot`, `violinplot`, `lmplot`, `s
|
|
|
69
83
|
- Numbers as text ("1.234,5", "12%", "$ 3.000") → parsed automatically.
|
|
70
84
|
- Tens of thousands of points → drawn as batched paths, still fast.
|
|
71
85
|
|
|
86
|
+
## Dashboards
|
|
87
|
+
|
|
88
|
+
When someone asks for a dashboard, call `safeDashboard({ title, subtitle, source, data, kpis, charts, filters })` (tool: `toolDefinition({ kind: 'dashboard' })`). Layout, widths, shared colors and filters are decided for you:
|
|
89
|
+
|
|
90
|
+
- `title` is the **question** the dashboard answers; every chart still gets its finding as title.
|
|
91
|
+
- At most **5 KPIs**, computed from `data` when possible: `{ label, field, agg: "sum", compare: { field: "year", current: 2025, previous: 2024 }, trendBy: "month" }`.
|
|
92
|
+
- **4–6 charts**. The first time series (or the one with `primary: true`) goes full width; the rest fill balanced rows.
|
|
93
|
+
- `filters: ["region", "channel"]` become buttons that filter every chart and KPI.
|
|
94
|
+
- No more than one donut; one `source` for the page.
|
|
95
|
+
|
|
72
96
|
## Outputs
|
|
73
97
|
|
|
74
98
|
- `plot(spec)` → SVG string. Drop it into HTML, Markdown, an `.svg` file or a PDF.
|
package/README.md
CHANGED
|
@@ -4,14 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
**[Documentation, live examples and playground →](https://carlosnavarrete.cl/plotagents/)**
|
|
6
6
|
|
|
7
|
-
Data and field names go in; an accessible SVG comes out, with careful typography, colorblind-safe colors and tooltips.
|
|
7
|
+
Data and field names go in; an accessible SVG comes out, with careful typography, colorblind-safe colors and tooltips. 34 chart types: the ones people reach for in seaborn, multi-level treemaps and sunbursts, choropleth maps of Chile, the United States and the world, networks, chord diagrams, population pyramids, flow charts and Gantt charts, plus word clouds, a dashboard builder with best-practice layouts, opt-in time controls, small multiples and fully JSON-configurable themes. Zero dependencies. Runs in Node, Deno, Bun and the browser.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install
|
|
10
|
+
npm install plotagents
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
plotagents is not on the npm registry yet, so install it from GitHub for now; `npm install plotagents` will work once it is published.
|
|
14
|
-
|
|
15
13
|
```js
|
|
16
14
|
import { plot } from 'plotagents';
|
|
17
15
|
|
|
@@ -42,11 +40,12 @@ Agents are good at data and bad at charts: dual axes, 12-slice pies, titles that
|
|
|
42
40
|
- **One axis.** There is no dual axis; `index: true` rebases series to 100 and `facet` draws panels on a shared scale.
|
|
43
41
|
- **Labels at the end of every line**, always. When they collide they are spread apart and joined to their point by a thin leader line; a legend appears only when needed.
|
|
44
42
|
- **Every pixel for the data.** The y axis sits inside the plot on its gridline; margins and type scale down on phones; treemaps, sunbursts, horizontal bars, heatmaps and KPI tiles size their own height.
|
|
45
|
-
- **Color follows the entity.** Eight validated colors in a fixed order; "Copper" keeps its color when you filter, drill down or move
|
|
46
|
-
- **Legend and chart are connected.** Hover a legend key to light up its marks, click to pin
|
|
43
|
+
- **Color follows the entity.** Eight validated colors in a fixed order; "Copper" keeps its color when you filter, drill down or move through periods. One solid color per category.
|
|
44
|
+
- **Legend and chart are connected.** Hover a legend key to light up its marks, click to pin, shift + click to drop that entry (axes and shares recompute, colors stay put). In treemaps a click zooms the whole plot to the category. The legend goes top by default, or `legend: "bottom" | "left" | "right"`.
|
|
45
|
+
- **Tooltips that never cover the title.** They sit next to the hovered mark, stay inside the chart, and their header wears the mark's color with white or dark text picked by contrast. Arrow keys walk through the data.
|
|
47
46
|
- **Estimates look like estimates.** `dashed` hatches the shapes that are projections or provisional figures (same color, striped fill, dashed outline) and `dashedFrom: 2025` turns lines, areas and columns dashed from that point on, over a soft labeled band. The legend says what the hatching means; hovering it lights up just those shapes.
|
|
48
|
-
- **Maps you can explore.** Click a state or region to zoom to it, Ctrl/⌘ + scroll or pinch to zoom, drag to pan; +/−/reset buttons for the keyboard. Hovering outlines the shape instead of dimming the map, and each color class in the legend lights up its regions.
|
|
49
|
-
- **Networks you can explore.** Click a node and it glides to the center with its connections highlighted; click again or press Esc to go back.
|
|
47
|
+
- **Maps you can explore.** Click a state or region to zoom to it, Ctrl/⌘ + scroll or pinch to zoom, drag to pan; +/−/reset buttons for the keyboard. Hovering outlines the shape instead of dimming the map, and each color class in the legend lights up its regions. Moving through years, colors glide from one to the next.
|
|
48
|
+
- **Networks you can explore.** Curved edges in the source node's color, sized nodes and labels that never pile up. Pick the placement with `layout` (force, circular, radial, concentric, clusters, tree, grid). Click a node and it glides to the center with its connections highlighted; click again or press Esc to go back.
|
|
50
49
|
- **Thin marks, quiet grid.** 4 px rounded data ends, 2.25 px lines, 2 px surface gaps instead of borders.
|
|
51
50
|
- **Locale-aware numbers.** Spanish (Chile) by default: `1.234,5`, `56,1%`, `$18.500`. `locale: 'en'` gives `1,234.5`.
|
|
52
51
|
|
|
@@ -56,15 +55,19 @@ Agents are good at data and bad at charts: dual axes, 12-slice pies, titles that
|
|
|
56
55
|
|---|---|
|
|
57
56
|
| Compare categories | `bar` (`barplot` with confidence intervals), `count` (`countplot`) |
|
|
58
57
|
| Change over time | `line` (`lineplot`, `ci` for a confidence band), `area` |
|
|
59
|
-
| Relationships | `scatter`, `bubble`, `regplot` (`lmplot`), `residplot`, `jointplot` |
|
|
58
|
+
| Relationships | `scatter`, `bubble`, `regplot` (`lmplot`), `residplot`, `jointplot`, `corr` (correlation matrix; `clustermap` with a dendrogram), `pairplot` (`splom`) |
|
|
60
59
|
| Profiles | `radar` (`spider`) |
|
|
61
60
|
| Distributions | `hist` (`histplot`), `kde` (`kdeplot`), `ecdf`, `box` (`boxplot`), `violin`, `strip`, `swarm` (`beeswarm`), `point` (`pointplot`) |
|
|
62
61
|
| Part-to-whole | `treemap` (any depth, with % change over time), `sunburst`, `donut`, `pie` |
|
|
63
|
-
| Maps | `choropleth` (Chile by region, US by state, world by country,
|
|
62
|
+
| Maps | `choropleth` (Chile by region, US by state, world by country, or your own GeoJSON / TopoJSON with markers, layers and framing), `symbolmap`, `pointmap` (lat/lon dots over OpenStreetMap or Mapbox tiles) |
|
|
63
|
+
| Projects | `gantt` (`timeline`, `roadmap`): phases that fold and unfold, progress, milestones, dependencies and a today line |
|
|
64
64
|
| Networks and flows | `network`, `chord`, `sankey`, `flowchart` (with swimlanes) |
|
|
65
65
|
| Populations | `pyramid` |
|
|
66
66
|
| Grids | `heatmap` |
|
|
67
67
|
| Numbers | `kpi` |
|
|
68
|
+
| Exact values | `table`: an indicator per column (data bars, ▲/▼ changes, cell colors, sparklines, progress against a target, status pills, ✓), pages of 10, cards on phones, click-to-sort and search in the browser, language recognized from the text |
|
|
69
|
+
| Text | `wordcloud` (words sized by frequency, or raw text to count) |
|
|
70
|
+
| Dashboards | `dashboard({ title, kpis, charts, filters })`: a whole HTML dashboard with best-practice layout ([guide](docs/dashboards.md)) |
|
|
68
71
|
|
|
69
72
|
Leave `type` out and it is inferred: dates → `line`, two measures → `scatter`, `source`/`target` → `sankey`, `path` → `treemap`, categories → `bar`. Every option is documented in [`docs/charts.md`](docs/charts.md).
|
|
70
73
|
|
|
@@ -78,11 +81,12 @@ Leave `type` out and it is inferred: dates → `line`, two measures → `scatter
|
|
|
78
81
|
## Time, panels and drill-down
|
|
79
82
|
|
|
80
83
|
```js
|
|
81
|
-
// One period at a time
|
|
82
|
-
|
|
84
|
+
// One period at a time (the latest). Controls are opt-in: `slider: true` adds
|
|
85
|
+
// period buttons (few periods) or a minimal slider (many), with a play button.
|
|
86
|
+
plot({ type: 'treemap', data, path: ['sector', 'product'], value: 'value', time: 'year', slider: true });
|
|
83
87
|
|
|
84
|
-
// Time series:
|
|
85
|
-
render('#chart', { type: 'line', data, x: 'month', y: 'sales', series: 'region' });
|
|
88
|
+
// Time series: `slider: true` adds a two-handle range in the browser.
|
|
89
|
+
render('#chart', { type: 'line', data, x: 'month', y: 'sales', series: 'region', slider: true });
|
|
86
90
|
|
|
87
91
|
// Small multiples (seaborn's col / col_wrap) with shared scales and colors.
|
|
88
92
|
plot({ type: 'area', data, x: 'month', y: 'sales', facet: 'region', columns: 3 });
|
|
@@ -125,6 +129,15 @@ plot({
|
|
|
125
129
|
});
|
|
126
130
|
```
|
|
127
131
|
|
|
132
|
+
Register it once and every chart uses it; the default look never changes unless you ask:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
registerTheme('acme', { background: '#FFF', text: '#111827', grid: { color: '#EEF0F4', dash: '2 3' }, categorical: 'tab10', font: 'Inter' });
|
|
136
|
+
configure({ theme: 'acme' });
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Every matplotlib colormap works by name for heatmaps, maps and treemaps by change: `colorScale: 'viridis'`, `'RdBu_r'`, or your own list of colors ([color scales](docs/colorscales.md)).
|
|
140
|
+
|
|
128
141
|
`validatePalette(colors)` checks a custom palette with the same tests the built-in one passed (lightness band, chroma floor, colorblind separation, normal-vision separation), and `lint` runs it for you.
|
|
129
142
|
|
|
130
143
|
## SVG, not Canvas
|
|
@@ -149,13 +162,29 @@ import { plot, safePlot, lint, describe, render, toHTML, schema, toolDefinition,
|
|
|
149
162
|
| `safePlot(spec)` | `{ ok, svg, description, errors, warnings, suggestions }`. Never throws. |
|
|
150
163
|
| `lint(spec)` | Review of the spec without drawing: errors and design suggestions. |
|
|
151
164
|
| `describe(spec)` | Alt text ("Highest: Biobío with 1,240 (30%)…"). |
|
|
152
|
-
| `render(el, spec)` | Browser: tooltips, responsive width, time
|
|
165
|
+
| `render(el, spec)` | Browser: tooltips, responsive width, drill-down, and time controls when the spec asks for them. |
|
|
153
166
|
| `toHTML(specs)` | A self-contained, interactive HTML page in one file. |
|
|
154
167
|
| `schema` / `toolDefinition({ flavor })` | JSON Schema of the spec and a tool definition for Claude (`anthropic`) or OpenAI (`openai`). |
|
|
155
168
|
| `validatePalette(colors, opts)` | `{ ok, checks }`. |
|
|
156
169
|
|
|
157
170
|
In the browser without a bundler, `<script src="dist/plotagents.js"></script>` defines `window.plotagents`. Full types are in [`index.d.ts`](index.d.ts) and every option in [`schema.json`](schema.json).
|
|
158
171
|
|
|
172
|
+
## Teach your AI to use it (Agent Skill)
|
|
173
|
+
|
|
174
|
+
[`skills/plotagents/SKILL.md`](skills/plotagents/SKILL.md) tells any AI assistant to make its charts with plotagents, with the essentials built in. It also checks npm for a newer release and reads the latest guide when there is one.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npx skills add carlosnavarreteliz/plotagents
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
That works for Claude Code, Cursor, Codex and other agents that read skills. By hand, for Claude Code:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
mkdir -p ~/.claude/skills/plotagents && curl -sL https://raw.githubusercontent.com/carlosnavarreteliz/plotagents/master/skills/plotagents/SKILL.md -o ~/.claude/skills/plotagents/SKILL.md
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
In the Claude app, zip the `skills/plotagents` folder and upload it under Settings → Capabilities → Skills.
|
|
187
|
+
|
|
159
188
|
## As an agent tool
|
|
160
189
|
|
|
161
190
|
```js
|
package/bin/plotagents.js
CHANGED
|
@@ -1,48 +1,74 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// CLI: plotagents spec.json [-o salida.svg|.html] [--lint] [--schema] [--tool]
|
|
2
|
+
// CLI: plotagents spec.json [-o salida.svg|.html] [--lint] [--schema] [--tool [openai] [dashboard]]
|
|
3
3
|
// cat spec.json | plotagents > grafico.svg
|
|
4
|
+
// A spec with a `charts` list is a dashboard: it renders to a self-contained HTML page.
|
|
4
5
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { plot, toHTML, lint, schema, toolDefinition, describe } from '../src/index.js';
|
|
6
|
+
import { plot, toHTML, lint, schema, toolDefinition, describe, dashboard, lintDashboard, dashboardSchema } from '../src/index.js';
|
|
7
|
+
|
|
8
|
+
// `plotagents … | head` closes the pipe early: exit quietly instead of crashing.
|
|
9
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
10
|
+
stream.on('error', (e) => { if (e && e.code === 'EPIPE') process.exit(0); throw e; });
|
|
11
|
+
// Escritura bloqueante: process.exit() no corta una salida larga (> 64 KB) en un pipe.
|
|
12
|
+
stream._handle?.setBlocking?.(true);
|
|
13
|
+
}
|
|
6
14
|
|
|
7
15
|
const args = process.argv.slice(2);
|
|
8
16
|
const flag = (f) => args.includes(f);
|
|
9
17
|
const opt = (f) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : undefined; };
|
|
10
|
-
const
|
|
18
|
+
const VALUE_FLAGS = new Set(['-o', '--out', '--tool']);
|
|
19
|
+
const TOOL_ARGS = new Set(['openai', 'anthropic', 'dashboard', 'chart']);
|
|
20
|
+
const input = args.find((a, i) => !a.startsWith('-') && !VALUE_FLAGS.has(args[i - 1]) && !(TOOL_ARGS.has(a) && args.slice(0, i).includes('--tool')));
|
|
21
|
+
const print = (s) => process.stdout.write(s.endsWith('\n') ? s : s + '\n');
|
|
11
22
|
|
|
12
23
|
if (flag('-h') || flag('--help')) {
|
|
13
|
-
|
|
24
|
+
print(`plotagents — gráficos bellos desde un spec JSON
|
|
14
25
|
|
|
15
26
|
Uso:
|
|
16
27
|
plotagents spec.json SVG a la salida estándar
|
|
17
28
|
plotagents spec.json -o grafico.svg guarda SVG
|
|
18
29
|
plotagents spec.json -o reporte.html página HTML interactiva (acepta un arreglo de specs)
|
|
30
|
+
plotagents tablero.json -o tablero.html dashboard (un spec con \`charts: [...]\`)
|
|
19
31
|
plotagents spec.json --lint revisa el spec y sugiere mejoras (JSON)
|
|
20
32
|
plotagents spec.json --describe descripción en texto del gráfico
|
|
21
|
-
plotagents --schema
|
|
22
|
-
plotagents --tool [openai]
|
|
33
|
+
plotagents --schema [dashboard] JSON Schema del spec (o del dashboard)
|
|
34
|
+
plotagents --tool [openai] [dashboard] definición de herramienta para agentes
|
|
23
35
|
cat spec.json | plotagents también lee de stdin`);
|
|
24
36
|
process.exit(0);
|
|
25
37
|
}
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
// Tool and schema flags accept "openai" and/or "dashboard" in any order after the flag.
|
|
39
|
+
const after = (f) => { const i = args.indexOf(f); return i >= 0 ? args.slice(i + 1).filter((a) => TOOL_ARGS.has(a)) : []; };
|
|
40
|
+
if (flag('--schema')) { print(JSON.stringify(after('--schema').includes('dashboard') ? dashboardSchema : schema, null, 2)); process.exit(0); }
|
|
41
|
+
if (flag('--tool')) {
|
|
42
|
+
const t = after('--tool');
|
|
43
|
+
print(JSON.stringify(toolDefinition({ flavor: t.includes('openai') ? 'openai' : 'anthropic', kind: t.includes('dashboard') ? 'dashboard' : 'chart' }), null, 2));
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
28
46
|
|
|
29
47
|
let raw;
|
|
30
48
|
try { raw = input ? readFileSync(input, 'utf8') : readFileSync(0, 'utf8'); } catch (e) { console.error('No pude leer el spec:', e.message); process.exit(2); }
|
|
31
49
|
let spec;
|
|
32
50
|
try { spec = JSON.parse(raw); } catch (e) { console.error('El spec no es JSON válido:', e.message); process.exit(2); }
|
|
33
51
|
|
|
52
|
+
const isDashboard = (s) => !!s && typeof s === 'object' && !Array.isArray(s) && (Array.isArray(s.charts) || (Array.isArray(s.kpis) && s.type === undefined));
|
|
53
|
+
|
|
34
54
|
if (flag('--lint')) {
|
|
35
55
|
const list = Array.isArray(spec) ? spec : [spec];
|
|
36
|
-
const out = list.map(lint);
|
|
37
|
-
|
|
56
|
+
const out = list.map((s) => (isDashboard(s) ? lintDashboard(s) : lint(s)));
|
|
57
|
+
print(JSON.stringify(Array.isArray(spec) ? out : out[0], null, 2));
|
|
38
58
|
process.exit(out.every((r) => r.ok) ? 0 : 1);
|
|
39
59
|
}
|
|
40
60
|
try {
|
|
41
|
-
if (flag('--describe')) { console.log((Array.isArray(spec) ? spec : [spec]).map(describe).join('\n')); process.exit(0); }
|
|
42
61
|
const outFile = opt('-o') ?? opt('--out');
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
if (isDashboard(spec)) {
|
|
63
|
+
if (flag('--describe')) { print([spec.title, ...spec.charts.map((c) => { try { return describe({ data: spec.data, ...c }); } catch (e) { return e.message.split('\n')[0]; } })].filter(Boolean).join('\n')); process.exit(0); }
|
|
64
|
+
const html = dashboard(spec);
|
|
65
|
+
if (outFile) { writeFileSync(outFile, html); console.error(`✓ ${outFile}`); } else process.stdout.write(html);
|
|
66
|
+
} else {
|
|
67
|
+
if (flag('--describe')) { print((Array.isArray(spec) ? spec : [spec]).map(describe).join('\n')); process.exit(0); }
|
|
68
|
+
const html = (outFile && outFile.endsWith('.html')) || flag('--html') || Array.isArray(spec);
|
|
69
|
+
const result = html ? toHTML(spec, { title: (Array.isArray(spec) ? spec[0] : spec).title || 'plotagents' }) : plot(spec);
|
|
70
|
+
if (outFile) { writeFileSync(outFile, result); console.error(`✓ ${outFile}`); } else process.stdout.write(result);
|
|
71
|
+
}
|
|
46
72
|
} catch (e) {
|
|
47
73
|
console.error(e.message);
|
|
48
74
|
process.exit(1);
|
package/dist/plotagents-geo.js
CHANGED