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.
- package/AGENTS.md +5 -1
- package/README.md +18 -1
- package/bin/plotagents.js +3 -1
- package/dist/plotagents-geo.js +1 -1
- package/dist/plotagents.js +1857 -110
- package/docs/charts.md +86 -1
- 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/index.d.ts +92 -8
- package/package.json +3 -2
- package/schema.json +359 -9
- package/skills/plotagents/SKILL.md +105 -0
- package/src/charts/bar.js +13 -5
- package/src/charts/chord.js +3 -1
- package/src/charts/choropleth.js +3 -1
- package/src/charts/corr.js +512 -0
- package/src/charts/dist.js +2 -1
- package/src/charts/donut.js +3 -1
- package/src/charts/kpi.js +2 -2
- package/src/charts/line.js +19 -7
- package/src/charts/network.js +13 -8
- package/src/charts/pointmap.js +9 -6
- package/src/charts/regplot.js +9 -3
- package/src/charts/sankey.js +1 -1
- package/src/charts/scatter.js +4 -2
- package/src/charts/sunburst.js +2 -2
- package/src/charts/table.js +882 -0
- package/src/charts/treemap.js +2 -2
- package/src/charts/wordcloud.js +17 -3
- package/src/colorscales.js +12 -1
- package/src/dashboard.js +21 -8
- package/src/data.js +2 -0
- package/src/facet.js +4 -3
- package/src/format.js +36 -13
- package/src/frame.js +8 -5
- package/src/geo/projection.js +4 -2
- package/src/hierarchy.js +6 -0
- package/src/i18n.js +107 -0
- package/src/index.js +17 -4
- package/src/infer.js +9 -1
- package/src/lint.js +66 -4
- package/src/runtime.js +94 -6
- package/src/scale.js +3 -2
- package/src/schema.js +54 -11
- package/src/stats.js +5 -3
- package/src/svg.js +4 -1
- 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?:
|
|
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
|
|
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?:
|
|
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
|
-
|
|
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.
|
|
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": [
|