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.
Files changed (67) hide show
  1. package/AGENTS.md +29 -5
  2. package/README.md +44 -15
  3. package/bin/plotagents.js +40 -14
  4. package/dist/plotagents-geo.js +1 -1
  5. package/dist/plotagents.js +6161 -757
  6. package/docs/api.md +23 -5
  7. package/docs/charts.md +229 -10
  8. package/docs/colorscales.md +46 -0
  9. package/docs/dashboards.md +79 -0
  10. package/docs/es/agents.md +102 -0
  11. package/docs/es/api.md +172 -0
  12. package/docs/es/charts.md +436 -0
  13. package/docs/es/colorscales.md +46 -0
  14. package/docs/es/dashboards.md +79 -0
  15. package/docs/es/getting-started.md +142 -0
  16. package/docs/es/rendering.md +47 -0
  17. package/docs/es/themes.md +116 -0
  18. package/docs/getting-started.md +6 -2
  19. package/docs/themes.md +39 -9
  20. package/index.d.ts +275 -20
  21. package/package.json +11 -4
  22. package/schema.json +1382 -53
  23. package/skills/plotagents/SKILL.md +105 -0
  24. package/src/axes.js +11 -3
  25. package/src/charts/bar.js +40 -21
  26. package/src/charts/categorical.js +31 -7
  27. package/src/charts/chord.js +6 -4
  28. package/src/charts/choropleth.js +287 -37
  29. package/src/charts/corr.js +512 -0
  30. package/src/charts/dist.js +39 -21
  31. package/src/charts/donut.js +4 -2
  32. package/src/charts/gantt.js +460 -0
  33. package/src/charts/heatmap.js +2 -2
  34. package/src/charts/kpi.js +11 -8
  35. package/src/charts/line.js +187 -43
  36. package/src/charts/network.js +557 -189
  37. package/src/charts/pointmap.js +602 -0
  38. package/src/charts/radar.js +7 -1
  39. package/src/charts/regplot.js +16 -4
  40. package/src/charts/sankey.js +42 -6
  41. package/src/charts/scatter.js +103 -23
  42. package/src/charts/sunburst.js +7 -6
  43. package/src/charts/table.js +882 -0
  44. package/src/charts/treemap.js +12 -9
  45. package/src/charts/wordcloud.js +177 -0
  46. package/src/colorscales.js +193 -0
  47. package/src/dashboard.js +451 -0
  48. package/src/dashed.js +1 -1
  49. package/src/data.js +4 -2
  50. package/src/facet.js +4 -3
  51. package/src/format.js +80 -25
  52. package/src/frame.js +101 -29
  53. package/src/geo/projection.js +76 -9
  54. package/src/geo/registry.js +87 -8
  55. package/src/hierarchy.js +6 -0
  56. package/src/i18n.js +107 -0
  57. package/src/index.js +92 -14
  58. package/src/infer.js +18 -1
  59. package/src/interact.js +157 -36
  60. package/src/lint.js +353 -11
  61. package/src/runtime.js +301 -101
  62. package/src/scale.js +82 -11
  63. package/src/schema.js +235 -36
  64. package/src/stats.js +76 -17
  65. package/src/svg.js +4 -1
  66. package/src/text.js +5 -0
  67. package/src/theme.js +101 -14
@@ -0,0 +1,46 @@
1
+ # Escalas de color
2
+
3
+ Todos los mapas de colores que trae matplotlib están disponibles por nombre, además de los tuyos. Úsalos en cualquier gráfico que asigne un color a un número: mapas de calor, coropletas, mapas de puntos, treemaps coloreados por cambio y nubes de palabras.
4
+
5
+ ```js
6
+ plot({ type: 'heatmap', data, x: 'hour', y: 'day', value: 'trips', colorScale: 'viridis' });
7
+ plot({ type: 'choropleth', geo: 'chile', data, key: 'region', value: 'change', colorScale: 'RdBu' });
8
+ plot({ ...spec, colorScale: 'magma_r' }); // _r reverses any map
9
+ plot({ ...spec, colorScale: ['#FFF7EC', '#FC8D59', '#7F0000'] }); // your own stops
10
+ plot({ ...spec, colorScale: { name: 'coolwarm', mid: 100 } }); // diverging around 100
11
+ ```
12
+
13
+ - **Los nombres no distinguen mayúsculas** (`'rdbu'` funciona) y `_r` invierte cualquier mapa.
14
+ - **Los mapas divergentes se centran solos**: en 0 cuando los datos cruzan el cero; si no, en el punto medio de los datos. Fija `mid` (o `midpoint`) para elegir el centro.
15
+ - **Tu propia escala** es una lista de dos o más colores, repartidos de forma pareja de menor a mayor, o `{ colors, reverse, mid }`.
16
+ - La interpolación ocurre en OKLab, así que los pasos se ven parejos. Las paradas se muestrearon de matplotlib 3.9 y quedan a menos de 2 ΔE (×100) del original.
17
+ - `cmap` y `colormap` son alias de `colorScale`. La opción anterior `ramp` todavía funciona.
18
+
19
+ ## Paletas categóricas
20
+
21
+ `palette` acepta una lista de colores, una paleta de plotagents o uno de los mapas cualitativos de matplotlib:
22
+
23
+ ```js
24
+ plot({ ...spec, palette: 'tab10' });
25
+ plot({ ...spec, palette: 'Set2' });
26
+ plot({ ...spec, palette: 'viridis' }); // a continuous map, sampled into distinct colors
27
+ ```
28
+
29
+ La paleta por defecto está validada para deficiencias de visión del color; `lint` revisa cualquier otra paleta que traigas (consulta [Temas](themes.md#validating-a-palette)).
30
+
31
+ ## ¿Cuál usar?
32
+
33
+ | Datos | Usa | Evita |
34
+ |---|---|---|
35
+ | Valores ordenados, de menor → mayor | `viridis`, `cividis`, `magma`, `Blues`, `YlGnBu` | `jet`, `rainbow`, `hsv` (inventan bordes donde los datos no los tienen) |
36
+ | Valores en torno a un centro con significado (0, una meta, un promedio) | `RdBu`, `coolwarm`, `PuOr`, `BrBG` | un mapa secuencial |
37
+ | Ángulos, horas del día, meses | `twilight`, `hsv` | un mapa secuencial |
38
+ | Categorías | `palette: 'tab10'`, `'Set2'`, `'Dark2'` | cualquier mapa continuo como `colorScale` |
39
+
40
+ `lint` marca los mapas arcoíris con una sugerencia y rechaza un mapa cualitativo usado como escala continua.
41
+
42
+ ## Todas las escalas
43
+
44
+ {{colorscales}}
45
+
46
+ La lista también está disponible en código: `colorScaleNames()` devuelve los nombres agrupados por tipo y `resolveColorScale(name)` devuelve `{ name, stops, kind }`.
@@ -0,0 +1,79 @@
1
+ # Dashboards
2
+
3
+ `dashboard(spec)` convierte un puñado de especificaciones de gráficos en un dashboard HTML terminado y autocontenido. Igual que en un gráfico individual, las decisiones de diseño vienen incluidas: tú dices qué va en la página y la biblioteca decide dónde y cómo.
4
+
5
+ ```js
6
+ import { dashboard } from 'plotagents';
7
+ import { writeFileSync } from 'node:fs';
8
+
9
+ writeFileSync('sales.html', dashboard({
10
+ title: 'How are southern sales doing?',
11
+ subtitle: 'Sales by month, region and channel, 2024–2025, CLP millions',
12
+ source: 'Commercial ERP',
13
+ updated: '2026-01-05',
14
+ locale: 'en',
15
+ data: sales, // shared rows
16
+ kpis: [
17
+ { label: 'Sales 2025', field: 'sales', agg: 'sum', format: 'compact',
18
+ compare: { field: 'year', current: 2025, previous: 2024 }, trendBy: 'month' },
19
+ { label: 'Orders 2025', field: 'orders', agg: 'sum', format: 'compact',
20
+ compare: { field: 'year', current: 2025, previous: 2024 } },
21
+ { label: 'Online share', value: 0.361, delta: 0.13, format: 'percent', period: 'vs 2024' },
22
+ ],
23
+ filters: ['region', 'channel'],
24
+ charts: [
25
+ { type: 'line', x: 'month', y: 'sales', series: 'region', highlight: 'Ñuble',
26
+ title: 'Ñuble grew 37% this year, faster than any other region' },
27
+ { type: 'choropleth', geo: 'chile', key: 'region', value: 'sales', fitTo: ['Biobío', 'Ñuble', 'Maule', 'Araucanía'],
28
+ title: 'Biobío makes 36% of sales' },
29
+ { type: 'bar', x: 'channel', y: 'sales', series: 'year',
30
+ title: 'Online is the only channel growing strongly' },
31
+ { type: 'treemap', path: ['region', 'channel'], value: 'sales',
32
+ title: 'Physical stores are still half the business' },
33
+ ],
34
+ }));
35
+ ```
36
+
37
+ La página funciona sin JavaScript: cada gráfico se dibuja en el servidor, así que se abre en cualquier navegador, se imprime y se convierte a PDF. Cuando la versión para navegador está disponible (lo está, en Node), la página también recibe tooltips, ampliación de mapas y filtros en vivo. En una aplicación web, `renderDashboard('#board', spec)` dibuja el mismo dashboard dentro de un elemento.
38
+
39
+ ## Lo que decide por ti
40
+
41
+ Estos valores por defecto vienen de la práctica del diseño de dashboards: *Information Dashboard Design* de Stephen Few, el patrón de lectura en Z y lo que hace difíciles de leer los dashboards de negocio.
42
+
43
+ | Regla | Lo que hace plotagents |
44
+ |---|---|
45
+ | **Una pregunta por dashboard** | El `title` es la pregunta ("¿Cómo van las ventas del sur?"). `lintDashboard` advierte sobre 9 gráficos y sugiere dividir sobre 6. |
46
+ | **Primero los números** | Los KPI van en una fila arriba, como máximo 5 (advertencia sobre eso). Cada uno tiene una etiqueta, la cifra, su cambio con una flecha **y** un signo (nunca solo color), el período de comparación y una minilínea opcional. |
47
+ | **Lectura en Z** | El gráfico principal va justo después de los KPI, a todo el ancho: el marcado con `primary: true` o, si no, la primera serie de tiempo. |
48
+ | **Filas completas, sin huecos** | Una cuadrícula de 12 columnas en filas equilibradas: 2 gráficos → 6 + 6 (8 + 4 cuando uno es ancho), 3 → 4 + 4 + 4, 4 → dos filas de dos, 5 → 3 + 2. Los paneles de una fila comparten la altura. |
49
+ | **El ancho sigue al gráfico** | Las series de tiempo, las cartas Gantt, los mapas de calor y los flujos se leen de izquierda a derecha, así que reciben paneles anchos. Las donas, los KPI y los rankings cortos reciben paneles angostos. El mapa de Chile recibe una columna angosta. |
50
+ | **Lo mismo, del mismo color** | Un solo mapa de colores para todos los gráficos: "Ñuble" tiene el mismo color en la línea, el treemap y las barras. `colors` fija colores para toda la página. |
51
+ | **Una sola voz** | Un tema, un idioma y una tipografía para todos los paneles. La fuente aparece una vez, abajo; un gráfico conserva su propia fuente solo cuando es distinta. |
52
+ | **Filtros como botones** | `filters` se convierten en botones cuando un campo tiene hasta 6 valores, y en una lista desplegable cuando tiene más. Filtran todos los gráficos que tienen el campo, recalculan los KPI y dicen qué está filtrado en cada subtítulo. Un filtro sobre el campo `time` de un gráfico lleva ese gráfico al período elegido. |
53
+ | **Teléfonos** | Una columna en orden de lectura; los KPI en dos columnas. |
54
+ | **Impresión** | Los paneles no se cortan entre páginas; la barra de filtros no queda fija en papel. |
55
+
56
+ ## Especificación
57
+
58
+ | Opción | Significado |
59
+ |---|---|
60
+ | `title`, `subtitle` | La pregunta y el alcance (medida, unidad, período). |
61
+ | `source`, `updated`, `note` | Metadatos del pie y del encabezado. |
62
+ | `locale`, `theme`, `font`, `palette` | Se aplican a todos los gráficos (`theme: "noche"` para un dashboard oscuro). |
63
+ | `data` | Filas compartidas. Los gráficos sin `data` las usan; los KPI calculados y los filtros trabajan sobre ellas. |
64
+ | `kpis` | `[{ label, value, delta, period, trend, good, format }]` (fijos) o `[{ label, field, agg, over, scale, compare, trendBy, format }]` (calculados). `agg`: `sum`, `mean`, `median`, `min`, `max`, `count`, `distinct`, `ratio` (con `over`). `compare: { field, current, previous }` muestra el período actual y su cambio. `trendBy: "month"` dibuja la minilínea. |
65
+ | `charts` | Especificaciones de gráficos, como en `plot()`. Por gráfico: `primary: true`, `size: "full" \| "large" \| "medium" \| "small"` o `span` (de 12). |
66
+ | `filters` | `["region"]` o `[{ field, label, control: "buttons" \| "select", value }]`. |
67
+ | `layout` | `"auto"` (por defecto), `"executive"` (gráfico principal a todo el ancho y luego pares) o `"grid"` (paneles iguales). |
68
+ | `colors` | `{ "Biobío": "#3A47B8" }` para todos los gráficos. |
69
+
70
+ ## Para agentes
71
+
72
+ ```js
73
+ import { safeDashboard, toolDefinition } from 'plotagents';
74
+
75
+ const tool = toolDefinition({ kind: 'dashboard' }); // JSON Schema of the dashboard spec
76
+ const { ok, html, errors, warnings, suggestions } = safeDashboard(input);
77
+ ```
78
+
79
+ `safeDashboard` nunca lanza errores. Revisa el dashboard como un todo (demasiados gráficos o KPI, varios gráficos de torta, temas mezclados, filtros sobre campos que ningún gráfico tiene, paneles sin título) y cada gráfico que contiene, y etiqueta cada problema con el número y el título del gráfico para que el modelo sepa cuál corregir. `planDashboard(spec)` devuelve las decisiones de diseño (paneles, anchos, filas, colores, filtros) sin dibujar nada.
@@ -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.
@@ -5,10 +5,14 @@ plotagents turns one JSON object into a finished, accessible SVG chart. You pass
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install github:carlosnavarreteliz/plotagents
8
+ npm install plotagents
9
9
  ```
10
10
 
11
- plotagents is not on the npm registry yet, so install it from GitHub for now. Once it is published, `npm install plotagents` will work too. After installing, `npx plotagents` runs the CLI from your project.
11
+ Or straight from GitHub, for the latest unreleased changes:
12
+
13
+ ```bash
14
+ npm install github:carlosnavarreteliz/plotagents
15
+ ```
12
16
 
13
17
  Node 18 or later, Deno and Bun work out of the box. The package has no dependencies.
14
18
 
package/docs/themes.md CHANGED
@@ -15,7 +15,34 @@ plot({ ...spec, ramp: 'teal' }); // shortcut for them
15
15
  configure({ font: 'Poppins', textScale: 1.4, theme: { extends: 'papel', marks: { lineWidth: 2.5 } } });
16
16
  ```
17
17
 
18
- Every later `plot()`, `render()` and `toHTML()` starts from these values; the spec always wins. `configure()` returns the current defaults and `configure(null)` clears them.
18
+ Every later `plot()`, `render()`, `toHTML()` and dashboard starts from these values; the spec always wins. A spec's own `theme` object is deep-merged **over** the global theme, so `configure({ theme: 'noche' })` plus `theme: { marks: { barRadius: 0 } }` stays dark. `configure()` returns the current defaults and `configure(null)` clears them.
19
+
20
+ ## Your own theme, everywhere
21
+
22
+ The default theme (`papel`) never changes unless you ask. To restyle every chart, register a theme once and make it the default:
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'` also works per chart, and `{ extends: 'acme', … }` builds on it. Colors are validated: anything that is not a CSS color (hex, `rgb()`, `hsl()` or a color name) is dropped and the base value is kept.
19
46
 
20
47
  ## Built-in themes
21
48
 
@@ -30,17 +57,20 @@ Both share the same eight hues, stepped separately for each surface and validate
30
57
 
31
58
  | Key | Type | Meaning |
32
59
  |---|---|---|
33
- | `extends` | `'papel' \| 'noche'` | Base to merge over (default `papel`, or `noche` when `mode: 'dark'`). |
60
+ | `extends` | theme name or object | Base to merge over (default `papel`, or `noche` when `mode: 'dark'`). |
34
61
  | `mode` | `'light' \| 'dark'` | Affects tooltips and text-on-fill contrast. |
35
- | `surface` | color | Chart background. Also used for 2 px gaps and rings around marks. |
36
- | `ink`, `lede`, `muted` | color | Primary text, secondary text, axis and source text. |
37
- | `grid`, `axis` | color | Gridlines and baselines (1 px, solid). |
62
+ | `surface` (alias `background`) | color | Chart background. Also used for 2 px gaps and rings around marks. |
63
+ | `ink` (alias `text`), `lede`, `muted` | color | Primary text, secondary text, axis and source text. |
64
+ | `grid` | color or `{ color, dash, width }` | Gridlines (default 1 px, solid). `dash: '2 3'` draws dotted gridlines. |
65
+ | `axis` | color or `{ color, width }` | Baselines. |
66
+ | `tooltip` | `{ bg, ink }` | Tooltip card colors in the browser (default: white card on light themes, navy on dark). |
67
+ | `radius`, `barRadius` | number | Corner radius of bars (default 4). `radius` sets it for every mark that has corners. |
38
68
  | `tint`, `tint2` | color | Quiet fills: KPI tiles, slider tracks, meters. |
39
69
  | `dim` | color | Muted marks when `highlight` is used. |
40
70
  | `other` | color | The "Other" bucket and series past the 8th. |
41
- | `categorical` | colors or palette name | Identity colors, assigned in this order. |
42
- | `sequential` | colors or ramp name | Light → dark, for heatmaps. |
43
- | `diverging` | colors or ramp name | Two poles with a neutral midpoint. |
71
+ | `categorical` | colors, palette or colormap name | Identity colors, assigned in this order. |
72
+ | `sequential` | colors, ramp or colormap name | Light → dark, for heatmaps and maps. |
73
+ | `diverging` | colors, ramp or colormap name | Two poles with a neutral midpoint. |
44
74
  | `status` | `{ good, warning, critical }` | KPI deltas and meters. Never used for series. |
45
75
  | `font` | `{ family, mono, import }` | Font stack and optional Google Fonts URL (`fontImport: true` embeds it in the SVG). |
46
76
  | `type` | `{ title, subtitle, label, axis, legend, source, titleWeight }` | Type sizes in px. Titles shrink automatically on narrow widths. |
@@ -52,7 +82,7 @@ Both share the same eight hues, stepped separately for each surface and validate
52
82
 
53
83
  | Palettes (`categorical`) | Ramps (`sequential` / `diverging`) |
54
84
  |---|---|
55
- | `cdia` (default), `cdia-noche`, `okabe-ito`, `tableau10`, `udec` | `indigo`, `magenta`, `teal`, `amber`, `indigo-magenta`, `teal-rust` |
85
+ | `cdia` (default), `cdia-noche`, `okabe-ito`, `tableau10`, `udec`, plus matplotlib's `tab10`, `tab20`, `tab20b`, `tab20c`, `Set1`, `Set2`, `Set3`, `Pastel1`, `Pastel2`, `Paired`, `Accent`, `Dark2` | `indigo`, `magenta`, `teal`, `amber`, `indigo-magenta`, `teal-rust`, plus every matplotlib colormap (see [Color scales](colorscales.md)) |
56
86
 
57
87
  ## Validating a palette
58
88