plotagents 0.7.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.
Files changed (53) hide show
  1. package/AGENTS.md +5 -1
  2. package/README.md +18 -1
  3. package/bin/plotagents.js +3 -1
  4. package/dist/plotagents-geo.js +1 -1
  5. package/dist/plotagents.js +1857 -110
  6. package/docs/charts.md +86 -1
  7. package/docs/es/agents.md +102 -0
  8. package/docs/es/api.md +172 -0
  9. package/docs/es/charts.md +436 -0
  10. package/docs/es/colorscales.md +46 -0
  11. package/docs/es/dashboards.md +79 -0
  12. package/docs/es/getting-started.md +142 -0
  13. package/docs/es/rendering.md +47 -0
  14. package/docs/es/themes.md +116 -0
  15. package/index.d.ts +92 -8
  16. package/package.json +3 -2
  17. package/schema.json +359 -9
  18. package/skills/plotagents/SKILL.md +105 -0
  19. package/src/charts/bar.js +13 -5
  20. package/src/charts/chord.js +3 -1
  21. package/src/charts/choropleth.js +3 -1
  22. package/src/charts/corr.js +512 -0
  23. package/src/charts/dist.js +2 -1
  24. package/src/charts/donut.js +3 -1
  25. package/src/charts/kpi.js +2 -2
  26. package/src/charts/line.js +19 -7
  27. package/src/charts/network.js +13 -8
  28. package/src/charts/pointmap.js +9 -6
  29. package/src/charts/regplot.js +9 -3
  30. package/src/charts/sankey.js +1 -1
  31. package/src/charts/scatter.js +4 -2
  32. package/src/charts/sunburst.js +2 -2
  33. package/src/charts/table.js +882 -0
  34. package/src/charts/treemap.js +2 -2
  35. package/src/charts/wordcloud.js +17 -3
  36. package/src/colorscales.js +12 -1
  37. package/src/dashboard.js +21 -8
  38. package/src/data.js +2 -0
  39. package/src/facet.js +4 -3
  40. package/src/format.js +36 -13
  41. package/src/frame.js +8 -5
  42. package/src/geo/projection.js +4 -2
  43. package/src/hierarchy.js +6 -0
  44. package/src/i18n.js +107 -0
  45. package/src/index.js +17 -4
  46. package/src/infer.js +9 -1
  47. package/src/lint.js +66 -4
  48. package/src/runtime.js +94 -6
  49. package/src/scale.js +3 -2
  50. package/src/schema.js +54 -11
  51. package/src/stats.js +5 -3
  52. package/src/svg.js +4 -1
  53. package/src/text.js +5 -0
@@ -0,0 +1,142 @@
1
+ # Primeros pasos
2
+
3
+ plotagents convierte un objeto JSON en un gráfico SVG terminado y accesible. Tú pasas filas de datos y nombras los campos; la biblioteca toma todas las decisiones de diseño: disposición del gráfico, colores, etiquetas, formatos numéricos y espaciado.
4
+
5
+ ## Instalación
6
+
7
+ ```bash
8
+ npm install plotagents
9
+ ```
10
+
11
+ O directo desde GitHub, para tener los últimos cambios aún no publicados:
12
+
13
+ ```bash
14
+ npm install github:carlosnavarreteliz/plotagents
15
+ ```
16
+
17
+ Funciona tal cual en Node 18 o superior, Deno y Bun. El paquete no tiene dependencias.
18
+
19
+ ## Tu primer gráfico
20
+
21
+ ```js
22
+ import { plot } from 'plotagents';
23
+
24
+ const svg = plot({
25
+ type: 'bar',
26
+ title: 'Biobío accounts for 30% of southern sales',
27
+ subtitle: 'Sales by region, CLP millions, 2025',
28
+ source: 'illustrative data',
29
+ data: [
30
+ { region: 'Biobío', sales: 1240 },
31
+ { region: 'Araucanía', sales: 880 },
32
+ { region: 'Ñuble', sales: 610 },
33
+ ],
34
+ x: 'region',
35
+ y: 'sales',
36
+ locale: 'en',
37
+ });
38
+ ```
39
+
40
+ `svg` es una cadena de texto. Guárdala en un archivo `.svg`, ponla en HTML o Markdown, o conviértela a PDF o PNG con cualquier herramienta de SVG.
41
+
42
+ ## Anatomía de un spec
43
+
44
+ | Campo | Qué es |
45
+ |---|---|
46
+ | `type` | El tipo de gráfico, como `bar`, `line` o `treemap`. Omítelo para que se infiera de los datos. |
47
+ | `data` | Una lista de filas: `[{ region: "Biobío", sales: 1240 }]`. |
48
+ | `x`, `y`, `series` | Qué campo va en cada lugar. Cada tipo de gráfico documenta sus propios roles en la [referencia de gráficos](charts.md). |
49
+ | `title` | El hallazgo, como una oración completa con la cifra clave. |
50
+ | `subtitle` | Cómo leer el gráfico: medida, unidad y período. |
51
+ | `source` | De dónde vienen los datos. Usa `"illustrative data"` cuando sean inventados. |
52
+ | `locale` | `"es"` (el valor por defecto, `1.234,5`) o `"en"` (`1,234.5`). |
53
+
54
+ ### Tablas anchas
55
+
56
+ Cuando cada medida está en su propia columna, pasa una lista en `y` y cada campo se convierte en una serie:
57
+
58
+ ```js
59
+ plot({
60
+ type: 'line',
61
+ data: [
62
+ { month: '2025-01', revenue: 120, costs: 90 },
63
+ { month: '2025-02', revenue: 132, costs: 94 },
64
+ ],
65
+ x: 'month',
66
+ y: ['revenue', 'costs'],
67
+ });
68
+ ```
69
+
70
+ ### Números escritos como texto
71
+
72
+ Valores como `"1.234,5"`, `"12%"` o `"$ 3.000"` se interpretan automáticamente, así que los datos copiados desde una planilla funcionan sin limpiarlos.
73
+
74
+ ## En el navegador
75
+
76
+ Sin un bundler, carga la versión para navegador y llama a `render()`. Dibuja dentro de un elemento y agrega tooltips, ancho adaptable, deslizadores de tiempo y drill-down.
77
+
78
+ ```html
79
+ <div id="chart"></div>
80
+ <script src="https://cdn.jsdelivr.net/gh/carlosnavarreteliz/plotagents@master/dist/plotagents.js"></script>
81
+ <script>
82
+ plotagents.render('#chart', {
83
+ type: 'line',
84
+ data: [
85
+ { month: '2025-01', sales: 120 },
86
+ { month: '2025-02', sales: 140 },
87
+ { month: '2025-03', sales: 135 },
88
+ ],
89
+ x: 'month',
90
+ y: 'sales',
91
+ title: 'Sales rose 12% in the first quarter',
92
+ locale: 'en',
93
+ });
94
+ </script>
95
+ ```
96
+
97
+ El mismo archivo viene en el paquete como `dist/plotagents.js`. Los mapas también necesitan las geometrías: agrega `dist/plotagents-geo.js` después de `plotagents.js`.
98
+
99
+ `render()` devuelve un controlador con `update(spec)`, para volver a dibujar con un spec nuevo, y `destroy()`, para quitar el gráfico y sus listeners.
100
+
101
+ ## Un informe completo en un archivo
102
+
103
+ `toHTML()` arma una página interactiva y autocontenida con la biblioteca incluida, así que funciona sin conexión y desde un archivo local.
104
+
105
+ ```js
106
+ import { writeFileSync } from 'node:fs';
107
+ import { toHTML } from 'plotagents';
108
+
109
+ writeFileSync('report.html', toHTML([spec1, spec2], { title: 'Monthly report', lang: 'en', columns: 2 }));
110
+ ```
111
+
112
+ ## Desde la línea de comandos
113
+
114
+ ```bash
115
+ npx plotagents spec.json -o chart.svg # SVG
116
+ npx plotagents specs.json -o report.html # several interactive charts on one page
117
+ npx plotagents spec.json --lint # review and suggestions
118
+ npx plotagents spec.json --describe # alt text
119
+ npx plotagents --tool # tool definition for an agent
120
+ ```
121
+
122
+ La CLI también lee un spec desde la entrada estándar: `cat spec.json | npx plotagents > chart.svg`.
123
+
124
+ ## Detectar errores
125
+
126
+ `plot()` lanza un `PlotError` con una pista cuando un spec no se puede dibujar. `safePlot()` nunca lanza excepciones, y `lint()` revisa un spec sin dibujarlo:
127
+
128
+ ```js
129
+ import { safePlot } from 'plotagents';
130
+
131
+ const { ok, svg, errors, warnings, suggestions } = safePlot(spec);
132
+ if (!ok) console.log(errors.map((e) => `${e.message} → ${e.fix}`));
133
+ ```
134
+
135
+ Cada error, advertencia y sugerencia trae un `fix` que dice qué cambiar.
136
+
137
+ ## Próximos pasos
138
+
139
+ - Recorre la [galería](../examples/) para encontrar el gráfico que necesitas y copiar su código.
140
+ - Prueba specs en vivo en el [playground](../playground/).
141
+ - Lee la [referencia de gráficos](charts.md) para conocer cada opción de cada tipo.
142
+ - Entrégale a un agente la [guía para agentes](agents.md) y la definición de la herramienta.
@@ -0,0 +1,47 @@
1
+ # Renderizado: ¿SVG o Canvas?
2
+
3
+ **Decisión: SVG en todas partes, con agrupación automática de trazados para marcas densas.** Evaluamos Canvas y lo descartamos: el SVG agrupado es más rápido que Canvas en todos los tamaños que probamos y conserva las propiedades que importan en una biblioteca que usan agentes.
4
+
5
+ ## Qué medimos
6
+
7
+ Gráfico de dispersión, dos colores, 720 × 420 px, Chromium (Playwright), tiempo desde que se inserta el marcado hasta el segundo cuadro de animación. Misma máquina, mismos datos.
8
+
9
+ | Puntos | SVG, un `<circle>` por punto | Canvas 2D (`arc` + `fill`) | SVG, un `<path>` por color | plotagents (SVG agrupado, gráfico completo) |
10
+ |---|---|---|---|---|
11
+ | 1.000 | 32 ms | 29 ms | 22 ms | 26 ms |
12
+ | 5.000 | 62 ms | 30 ms | 13 ms | 12 ms |
13
+ | 20.000 | 671 ms · 2,9 MB | 100 ms | 37 ms | 38 ms · 2,1 MB |
14
+ | 50.000 | 1.468 ms · 7,2 MB | 357 ms | 138 ms | 123 ms · 3,4 MB |
15
+
16
+ El costo del SVG ingenuo es el DOM: un elemento por marca. Dibujar todos los círculos de un color como arcos dentro de un solo `<path>` elimina ese costo, y el navegador rasteriza un trazado más rápido de lo que un script puede emitir decenas de miles de llamadas a Canvas.
17
+
18
+ Reprodúcelo con `node scripts/bench.js` (necesita Playwright con Chromium instalado).
19
+
20
+ ## Por qué SVG gana en esta biblioteca
21
+
22
+ | Necesidad | SVG | Canvas |
23
+ |---|---|---|
24
+ | Renderizar en un servidor, en Node, sin dependencias | Sí: el SVG es una cadena de texto | Necesita un módulo nativo (`node-canvas`) o un navegador headless |
25
+ | Que un agente pueda leer y revisar lo que generó | Sí: títulos, etiquetas y valores son texto | No: es un mapa de bits |
26
+ | Accesibilidad (`<title>`, `<desc>`, lectores de pantalla, selección de texto) | Incluida | Hay que reconstruirla a mano junto al canvas |
27
+ | Nitidez con cualquier zoom, en impresión y en PDF | Vectorial | Resolución fija; hay que volver a renderizar para cada densidad de píxeles |
28
+ | Pegar en diapositivas, Word, Figma, Markdown o correo | Funciona como archivo | Solo como imagen |
29
+ | Temas con CSS, modo oscuro, estados al pasar el cursor | Clases CSS | Redibujo manual |
30
+ | 50.000 puntos o más | Rápido con agrupación (ver arriba) | Rápido |
31
+
32
+ Canvas solo gana cuando un gráfico necesita redibujar cientos de miles de marcas en cada cuadro, como en transmisiones en vivo o campos de partículas animados. Eso está fuera de lo que necesita un gráfico para tomar decisiones, y mucho más allá del punto en que un mapa de calor o un gráfico de densidad (`kde`, `hist`) cuenta la historia mejor que puntos superpuestos.
33
+
34
+ ## Cómo funciona la agrupación
35
+
36
+ `dotLayer()` en `src/svg.js` recibe los círculos que un gráfico quiere dibujar:
37
+
38
+ - **Hasta 1.500 puntos:** un `<circle>` por punto, cada uno con su propio tooltip y estado al pasar el cursor.
39
+ - **Más de 1.500 puntos:** los círculos se agrupan por estilo (color, radio, opacidad) y cada grupo se convierte en un `<path>` de arcos. Un `<rect class="pa-near">` transparente lleva las coordenadas y el texto del tooltip de hasta 20.000 puntos, y la capa del navegador busca el punto más cercano dentro de 14 px al pasar el cursor y lo marca con un anillo.
40
+
41
+ Scatter, bubble, regplot, residplot, jointplot, strip y swarm la usan automáticamente. No hace falta ninguna opción.
42
+
43
+ ## Otras decisiones de rendimiento
44
+
45
+ - **Medición de texto sin DOM.** Tablas de anchos calibradas para tipografías sans humanistas permiten ajustar, partir y truncar las etiquetas de la misma forma en Node y en el navegador.
46
+ - **Números redondeados a dos decimales** en todas las coordenadas, para que los archivos sean livianos.
47
+ - **Un solo archivo para el navegador.** `toHTML()` incrusta `dist/plotagents.js` (sin dependencias), así que las páginas interactivas funcionan sin conexión, desde un archivo local o dentro de un artifact.
@@ -0,0 +1,116 @@
1
+ # Temas
2
+
3
+ Un tema es un objeto JSON. Pasa un nombre o un objeto; los objetos se combinan en profundidad sobre un tema base, así que solo escribes lo que cambia.
4
+
5
+ ```js
6
+ plot({ ...spec, theme: 'noche' }); // built-in dark theme
7
+ plot({ ...spec, theme: { extends: 'noche', marks: { lineWidth: 3 } } });
8
+ plot({ ...spec, palette: 'okabe-ito' }); // shortcut for theme.categorical
9
+ plot({ ...spec, ramp: 'teal' }); // shortcut for theme.sequential
10
+ ```
11
+
12
+ ## Valores por defecto de toda la biblioteca
13
+
14
+ ```js
15
+ configure({ font: 'Poppins', textScale: 1.4, theme: { extends: 'papel', marks: { lineWidth: 2.5 } } });
16
+ ```
17
+
18
+ Cada `plot()`, `render()`, `toHTML()` y dashboard posterior parte de estos valores; la especificación siempre manda. El objeto `theme` propio de una especificación se combina en profundidad **sobre** el tema global, así que `configure({ theme: 'noche' })` más `theme: { marks: { barRadius: 0 } }` sigue siendo oscuro. `configure()` devuelve los valores por defecto actuales y `configure(null)` los borra.
19
+
20
+ ## Tu propio tema, en todas partes
21
+
22
+ El tema por defecto (`papel`) nunca cambia a menos que lo pidas. Para cambiar el estilo de todos los gráficos, registra un tema una vez y déjalo como predeterminado:
23
+
24
+ ```js
25
+ import { registerTheme, configure } from 'plotagents';
26
+
27
+ registerTheme('acme', {
28
+ extends: 'papel', // or 'noche', or another registered theme
29
+ background: '#FFFFFF', // alias of surface
30
+ text: '#111827', // alias of ink
31
+ lede: '#4B5563',
32
+ muted: '#6B7280',
33
+ grid: { color: '#EEF0F4', dash: '2 3', width: 1 },
34
+ axis: '#C7CBD6',
35
+ categorical: 'tab10', // any palette or matplotlib qualitative map
36
+ sequential: 'viridis', // any colormap name
37
+ tooltip: { bg: '#111827', ink: '#FFFFFF' },
38
+ radius: 2, // bar, tile and swatch corners
39
+ font: 'Inter', // Google Fonts name or CSS stack
40
+ });
41
+
42
+ configure({ theme: 'acme' });
43
+ ```
44
+
45
+ `theme: 'acme'` también funciona en cada gráfico, y `{ extends: 'acme', … }` se construye sobre él. Los colores se validan: todo lo que no sea un color CSS (hex, `rgb()`, `hsl()` o un nombre de color) se descarta y se mantiene el valor base.
46
+
47
+ ## Temas incluidos
48
+
49
+ | Nombre | Modo | Superficie | Uso |
50
+ |---|---|---|---|
51
+ | `papel` (alias `light`) | claro | `#FCFBF9` papel | por defecto |
52
+ | `noche` (alias `dark`) | oscuro | `#0B0F2B` azul marino profundo | páginas oscuras, presentaciones |
53
+
54
+ Ambos comparten los mismos ocho tonos, escalonados por separado para cada superficie y validados sobre ella.
55
+
56
+ ## Claves
57
+
58
+ | Clave | Tipo | Significado |
59
+ |---|---|---|
60
+ | `extends` | nombre u objeto de tema | Base sobre la que se combina (por defecto `papel`, o `noche` cuando `mode: 'dark'`). |
61
+ | `mode` | `'light' \| 'dark'` | Afecta los tooltips y el contraste del texto sobre rellenos. |
62
+ | `surface` (alias `background`) | color | Fondo del gráfico. También se usa en las separaciones de 2 px y en los anillos alrededor de las marcas. |
63
+ | `ink` (alias `text`), `lede`, `muted` | color | Texto principal, texto secundario, texto de ejes y de la fuente. |
64
+ | `grid` | color o `{ color, dash, width }` | Líneas de cuadrícula (por defecto 1 px, continuas). `dash: '2 3'` dibuja líneas de cuadrícula punteadas. |
65
+ | `axis` | color o `{ color, width }` | Líneas base. |
66
+ | `tooltip` | `{ bg, ink }` | Colores de la tarjeta del tooltip en el navegador (por defecto: tarjeta blanca en temas claros, azul marino en oscuros). |
67
+ | `radius`, `barRadius` | número | Radio de las esquinas de las barras (por defecto 4). `radius` lo fija para todas las marcas que tienen esquinas. |
68
+ | `tint`, `tint2` | color | Rellenos discretos: tarjetas KPI, rieles de deslizadores, medidores. |
69
+ | `dim` | color | Marcas atenuadas cuando se usa `highlight`. |
70
+ | `other` | color | El grupo "Otros" y las series desde la novena. |
71
+ | `categorical` | colores, paleta o nombre de mapa de colores | Colores de identidad, asignados en este orden. |
72
+ | `sequential` | colores, rampa o nombre de mapa de colores | De claro → oscuro, para mapas de calor y mapas. |
73
+ | `diverging` | colores, rampa o nombre de mapa de colores | Dos polos con un punto medio neutro. |
74
+ | `status` | `{ good, warning, critical }` | Deltas y medidores de KPI. Nunca se usa para series. |
75
+ | `font` | `{ family, mono, import }` | Pila de fuentes y URL opcional de Google Fonts (`fontImport: true` la incrusta en el SVG). |
76
+ | `type` | `{ title, subtitle, label, axis, legend, source, titleWeight }` | Tamaños de letra en px. Los títulos se achican de forma automática en anchos angostos. |
77
+ | `marks` | `{ barRadius, barMax, barMaxH, lineWidth, pointRadius, gap, ring, areaOpacity, bandOpacity, dotOpacity }` | Geometría de las marcas. |
78
+ | `axes` | `{ inside, grid }` | `inside: true` pone las etiquetas de y sobre sus líneas de cuadrícula, dentro del gráfico (más ancho para los datos). |
79
+ | `layout` | `{ padding, paddingSmall }` | Margen exterior en anchos amplios y angostos. |
80
+
81
+ ## Paletas y rampas con nombre
82
+
83
+ | Paletas (`categorical`) | Rampas (`sequential` / `diverging`) |
84
+ |---|---|
85
+ | `cdia` (por defecto), `cdia-noche`, `okabe-ito`, `tableau10`, `udec`, más las de matplotlib `tab10`, `tab20`, `tab20b`, `tab20c`, `Set1`, `Set2`, `Set3`, `Pastel1`, `Pastel2`, `Paired`, `Accent`, `Dark2` | `indigo`, `magenta`, `teal`, `amber`, `indigo-magenta`, `teal-rust`, más todos los mapas de colores de matplotlib (consulta [Escalas de color](colorscales.md)) |
86
+
87
+ ## Validar una paleta
88
+
89
+ ```js
90
+ import { validatePalette } from 'plotagents';
91
+
92
+ validatePalette(['#223C6A', '#E69B0A', '#D21428'], { mode: 'light', surface: '#FFFFFF', locale: 'en' });
93
+ // { ok: false, checks: [
94
+ // { name: 'lightness', status: 'fail', detail: 'Outside the lightness band 0.43–0.77: #223C6A' }, … ] }
95
+ ```
96
+
97
+ | Prueba | Umbral | Por qué |
98
+ |---|---|---|
99
+ | Banda de luminosidad | OKLCH L 0.43–0.77 (claro), 0.48–0.67 (oscuro) | Demasiado claro desaparece; demasiado oscuro se lee como texto. |
100
+ | Croma mínimo | OKLCH C ≥ 0.10 | Por debajo, un tono se lee como gris. |
101
+ | Separación para daltonismo | ΔE OKLab × 100 ≥ 8 entre vecinos, con protanopía y deuteranopía simuladas (Machado 2009) | Los vecinos deben seguir distinguiéndose con deficiencias de visión del color. |
102
+ | Separación con visión normal | ΔE ≥ 15 | Los vecinos deben ser fáciles de distinguir para todos. |
103
+ | Contraste con la superficie | ≥ 3:1, solo advertencia | Por debajo, las marcas necesitan etiquetas visibles. |
104
+
105
+ Usa `pairs: 'all'` para diagramas de dispersión y mapas, donde dos colores cualesquiera pueden quedar uno al lado del otro. La paleta incluida pasa la prueba de todos los pares para sus tres primeros colores; por eso `lint` advierte sobre diagramas de dispersión con más de tres grupos.
106
+
107
+ `lint(spec)` ejecuta esta validación de forma automática cada vez que una especificación trae su propia paleta. Los mensajes siguen `locale` (español por defecto, `'en'` para inglés).
108
+
109
+ ## El color sigue a la entidad
110
+
111
+ Los colores se asignan a entidades, nunca a posiciones:
112
+
113
+ - Con `time`, los colores se calculan sobre todos los períodos, así que un grupo mantiene su color mientras se mueve el deslizador aunque cambie su posición en el ranking.
114
+ - Con `focus` (navegación en profundidad), el subárbol mantiene la familia de colores de su padre.
115
+ - Con `facet`, todos los paneles comparten un mismo mapa de colores.
116
+ - `colors: { "Copper": "#B5562E" }` fija cualquier entidad a un color.
package/index.d.ts CHANGED
@@ -5,11 +5,40 @@ export type ChartType =
5
5
  | 'regplot' | 'residplot' | 'jointplot' | 'hist' | 'kde' | 'ecdf'
6
6
  | 'box' | 'violin' | 'strip' | 'swarm' | 'point' | 'radar'
7
7
  | 'treemap' | 'sunburst' | 'donut' | 'pie' | 'heatmap' | 'sankey' | 'kpi'
8
- | 'network' | 'chord' | 'pyramid' | 'flowchart' | 'gantt' | 'wordcloud' | 'choropleth' | 'symbolmap' | 'pointmap'
8
+ | 'network' | 'chord' | 'pyramid' | 'flowchart' | 'gantt' | 'wordcloud' | 'choropleth' | 'symbolmap' | 'pointmap' | 'table' | 'corr' | 'pairplot'
9
9
  // Aliases (seaborn and others)
10
10
  | 'barplot' | 'countplot' | 'lineplot' | 'scatterplot' | 'histplot' | 'kdeplot' | 'ecdfplot'
11
11
  | 'lmplot' | 'boxplot' | 'violinplot' | 'stripplot' | 'swarmplot' | 'beeswarm' | 'pointplot' | 'spider'
12
- | 'timeline' | 'roadmap' | 'schedule' | 'cronograma' | 'map' | 'mapa';
12
+ | 'timeline' | 'roadmap' | 'schedule' | 'cronograma' | 'map' | 'mapa' | 'tabla' | 'datatable'
13
+ | 'correlation' | 'corrplot' | 'clustermap' | 'pairs' | 'splom' | 'scattermatrix';
14
+
15
+ export type Locale = 'es' | 'en' | 'pt' | 'fr' | 'de' | 'auto';
16
+
17
+ /** table: one column. A bare field name infers everything. */
18
+ export interface TableColumn {
19
+ field?: string;
20
+ label?: string;
21
+ /** Indicator. Inferred when omitted: bar on the main measure, delta on change fields, sparkline on lists of numbers, progress on 0–1 goals, badge on status words, check on booleans. */
22
+ type?: 'text' | 'number' | 'bar' | 'heat' | 'delta' | 'sparkline' | 'progress' | 'badge' | 'check' | 'date' | 'link' | 'rank';
23
+ format?: Format;
24
+ align?: 'left' | 'right' | 'center';
25
+ width?: number;
26
+ /** delta/progress: whether up is good (default) or down (costs). */
27
+ good?: 'up' | 'down';
28
+ /** progress: field or number to compare against. */
29
+ target?: string | number;
30
+ /** heat: matplotlib colormap or colors. */
31
+ colorScale?: string | string[] | { name?: string; colors?: string[]; reverse?: boolean; mid?: number };
32
+ /** Totals row for this column. */
33
+ total?: boolean | 'sum' | 'mean' | 'min' | 'max' | 'count';
34
+ /** Text: lines before truncating (default 2, 3 for long text). */
35
+ lines?: number;
36
+ /** link: field with the text shown instead of the URL. */
37
+ text?: string;
38
+ /** badge: { value: "#hex" }. */
39
+ colors?: Record<string, string>;
40
+ sortable?: boolean;
41
+ }
13
42
 
14
43
  export type Row = Record<string, string | number | Date | null | undefined>;
15
44
 
@@ -165,7 +194,26 @@ export interface Spec {
165
194
  lowess?: boolean;
166
195
  logx?: boolean;
167
196
  annotate?: boolean;
197
+ /** jointplot: center plot. pairplot: "reg" adds a fit line to every scatter panel. */
168
198
  kind?: 'scatter' | 'reg';
199
+ /** corr/pairplot: numeric fields to relate (default: all numeric; pairplot: first 6). */
200
+ vars?: string[];
201
+ /** corr: "pearson" (default) or "spearman". */
202
+ method?: 'pearson' | 'spearman';
203
+ /** corr: "lower" (default) or "full" (default with `cluster`). */
204
+ triangle?: 'lower' | 'full' | boolean;
205
+ /** corr: hierarchical clustering order + dendrogram (clustermap). */
206
+ cluster?: boolean;
207
+ dendrogram?: boolean;
208
+ /** corr: grey out cells with p ≥ alpha. */
209
+ alpha?: number;
210
+ cellSize?: number;
211
+ /** pairplot: diagonal panels ("kde" by default with hue). */
212
+ diag?: 'hist' | 'kde';
213
+ /** pairplot: above the diagonal. */
214
+ upper?: 'corr' | 'scatter' | false;
215
+ corner?: boolean;
216
+ panelSize?: number;
169
217
  marginal?: 'hist' | 'kde';
170
218
  inner?: 'box' | 'quartiles' | 'points' | 'stick' | 'none';
171
219
  split?: boolean;
@@ -181,7 +229,7 @@ export interface Spec {
181
229
  colorScale?: ColorScale;
182
230
  cmap?: ColorScale;
183
231
  colormap?: ColorScale;
184
- locale?: 'es' | 'en';
232
+ locale?: Locale;
185
233
  width?: number;
186
234
  height?: number;
187
235
  padding?: number;
@@ -299,7 +347,30 @@ export interface Spec {
299
347
  /** wordcloud: words field, or raw text to count. */
300
348
  text?: string | string[];
301
349
  textField?: string;
302
- columns?: number;
350
+ /** facet/kpi: panels or tiles per row. table: the columns. */
351
+ columns?: number | Array<string | TableColumn>;
352
+ /** table: column to sort by (field or label). */
353
+ sortBy?: string;
354
+ sortOrder?: 'asc' | 'desc';
355
+ /** table: rows per page (default: all up to 15, then 10). false shows every row. */
356
+ pageSize?: number | false;
357
+ page?: number;
358
+ /** table: keep rows containing these words (accent- and case-insensitive). */
359
+ query?: string;
360
+ /** table: browser search box (default: on above 20 rows). */
361
+ search?: boolean;
362
+ /** table: totals row. */
363
+ total?: boolean | 'sum' | 'mean' | 'min' | 'max' | Record<string, string | boolean>;
364
+ totalLabel?: string;
365
+ /** table: "#" column with the position. */
366
+ rank?: boolean;
367
+ /** table: one card per row. "auto" (default) when the columns do not fit. */
368
+ cards?: boolean | 'auto';
369
+ density?: 'compact' | 'normal' | 'comfortable';
370
+ striped?: boolean;
371
+ /** table: false disables the automatic data bar. */
372
+ bars?: boolean;
373
+ sortable?: boolean;
303
374
  [key: string]: unknown;
304
375
  }
305
376
 
@@ -330,15 +401,20 @@ export declare const schema: Record<string, unknown>;
330
401
  export declare function configure(options?: Partial<Spec> | null): Partial<Spec>;
331
402
  export interface DashboardSpec {
332
403
  title?: string; subtitle?: string; source?: string; updated?: string; note?: string;
333
- locale?: 'es' | 'en'; theme?: Spec['theme']; font?: Spec['font']; palette?: string | string[];
404
+ locale?: Locale; theme?: Spec['theme']; font?: Spec['font']; palette?: string | string[];
334
405
  data?: Row[];
335
- kpis?: Array<{ label: string; value?: number; delta?: number; period?: string; trend?: number[]; good?: 'up' | 'down'; format?: Format;
406
+ kpis?: Array<{ label: string; value?: number; delta?: number; period?: string; previous?: number; display?: string; trend?: number[]; good?: 'up' | 'down'; format?: Format;
336
407
  field?: string; agg?: 'sum' | 'mean' | 'median' | 'min' | 'max' | 'count' | 'distinct' | 'ratio'; over?: string; scale?: number;
337
408
  compare?: { field: string; current: string | number; previous: string | number }; trendBy?: string }>;
338
- charts: Array<Spec & { primary?: boolean; size?: 'full' | 'large' | 'medium' | 'small'; span?: number }>;
409
+ /** Optional when there are `kpis`. */
410
+ charts?: Array<Spec & { primary?: boolean; size?: 'full' | 'large' | 'medium' | 'small'; span?: number }>;
339
411
  filters?: Array<string | { field: string; label?: string; control?: 'buttons' | 'select'; value?: string }>;
340
412
  layout?: 'auto' | 'executive' | 'grid';
341
413
  colors?: Record<string, string>;
414
+ /** "given" keeps the charts in the order written (otherwise the main time series goes first). */
415
+ order?: 'given';
416
+ ramp?: string | string[];
417
+ textScale?: number;
342
418
  }
343
419
  /** Self-contained HTML dashboard with best-practice layout, shared colors, KPIs and filters. */
344
420
  export declare function dashboard(spec: DashboardSpec): string;
@@ -347,10 +423,16 @@ export declare function safeDashboard(spec: DashboardSpec): { ok: boolean; html:
347
423
  export declare function lintDashboard(spec: DashboardSpec): { ok: boolean; errors: Issue[]; warnings: Issue[]; suggestions: Issue[] };
348
424
  export declare function planDashboard(spec: DashboardSpec): object;
349
425
  /** Browser: draws a dashboard into an element. */
426
+ /** Browser: wires the filters and charts of a dashboard already in the DOM. */
427
+ export declare function mountDashboard(root: Element, plan: object): { plan: object; update(): void; destroy(): void };
428
+ export declare function packRows(spans: number[]): Array<Array<{ i: number; span: number }>>;
429
+ export declare function balancedRows(spans: number[], hasPrimary?: boolean): Array<Array<{ i: number; span: number }>>;
350
430
  export declare function renderDashboard(target: string | Element, spec: DashboardSpec): { update(): void; destroy(): void };
351
431
  export declare const dashboardSchema: Record<string, unknown>;
352
432
  /** Raw text → [{ text, value }] word counts without stopwords. */
353
433
  export declare function countWords(text: string | string[], opts?: { minLength?: number; stopwords?: string[] }): { text: string; value: number }[];
434
+ export declare function getGeo(geo: string | object, objectName?: string): object;
435
+ export declare function registeredGeo(): string[];
354
436
  export declare function registerGeo(name: string, geo: object, aliases?: string[]): object;
355
437
  /** Any GeoJSON / TopoJSON shape → FeatureCollection. */
356
438
  export declare function toFeatureCollection(geo: object | object[], objectName?: string): object;
@@ -361,7 +443,7 @@ export declare const ramps: Record<string, string[]>;
361
443
  export declare function themeFor(spec: Spec): Theme;
362
444
  export declare function timeValues(spec: Spec): (string | number)[];
363
445
  export declare function validatePalette(colors: string[], opts?: { mode?: 'light' | 'dark'; surface?: string; pairs?: 'adjacent' | 'all' }): { ok: boolean; checks: { name: string; status: 'pass' | 'warn' | 'fail'; detail: string }[] };
364
- export declare function bundleSource(): string | null;
446
+ export declare function bundleSource(file?: string): string | null;
365
447
  export declare function toolDefinition(opts?: { name?: string; flavor?: 'anthropic' | 'openai'; kind?: 'chart' | 'dashboard' }): Record<string, unknown>;
366
448
  export declare const themes: Record<'papel' | 'noche' | 'light' | 'dark', Theme> & Record<string, Theme>;
367
449
  export declare function getTheme(t?: Spec['theme']): Theme;
@@ -378,6 +460,8 @@ export declare const QUALITATIVE: Record<string, string>;
378
460
  export declare const TYPES: ChartType[];
379
461
  export declare function squarify<T extends { value: number }>(items: T[], x: number, y: number, w: number, h: number): (T & { x: number; y: number; w: number; h: number })[];
380
462
  export declare function makeFormat(fmt?: Format, locale?: string, values?: number[]): ((v: number) => string) & { type: string };
463
+ /** Most likely language of some texts ('es' | 'en' | 'pt' | 'fr' | 'de'), or null. */
464
+ export declare function detectLocale(texts: string | string[]): 'es' | 'en' | 'pt' | 'fr' | 'de' | null;
381
465
  export declare function formatNumber(v: number, decimals?: number, locale?: string, trim?: boolean): string;
382
466
  export declare function formatCompact(v: number, locale?: string, decimals?: number): string;
383
467
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plotagents",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "The chart protocol for AI agents: one JSON spec in, a finished accessible SVG out. Bars, lines, Gantt charts, treemaps, sankeys, maps from any GeoJSON/TopoJSON, networks and seaborn-style plots. Zero dependencies.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -25,6 +25,7 @@
25
25
  "src",
26
26
  "dist",
27
27
  "docs",
28
+ "skills",
28
29
  "bin",
29
30
  "index.d.ts",
30
31
  "schema.json",
@@ -38,7 +39,7 @@
38
39
  "bench": "node scripts/bench.js",
39
40
  "prepublishOnly": "node scripts/bundle.js && node --test test/*.test.js",
40
41
  "examples": "node examples/build.js",
41
- "site": "node scripts/build-site.js",
42
+ "site": "node scripts/build-site.js && node scripts/build-site.js --lang es",
42
43
  "schema": "node -e \"import('./src/index.js').then(m=>process.stdout.write(JSON.stringify(m.schema,null,2)))\" > schema.json"
43
44
  },
44
45
  "keywords": [