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
package/src/charts/network.js
CHANGED
|
@@ -276,13 +276,16 @@ function spanningTree(nodes, edges, byId, root) {
|
|
|
276
276
|
// Hojas numeradas en orden DFS; cada padre queda en el promedio de sus hijos.
|
|
277
277
|
const pos = new Map();
|
|
278
278
|
let leaf = 0;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
279
|
+
// Iterativo: una cadena larga (miles de niveles) desbordaría la pila.
|
|
280
|
+
const stack = [[root.id, 0]];
|
|
281
|
+
while (stack.length) {
|
|
282
|
+
const top = stack[stack.length - 1];
|
|
283
|
+
const ch = children.get(top[0]);
|
|
284
|
+
if (!ch.length) { pos.set(top[0], leaf++); stack.pop(); continue; }
|
|
285
|
+
if (top[1] < ch.length) { stack.push([ch[top[1]++], 0]); continue; }
|
|
286
|
+
pos.set(top[0], ch.reduce((a, c) => a + pos.get(c), 0) / ch.length);
|
|
287
|
+
stack.pop();
|
|
288
|
+
}
|
|
286
289
|
const maxDepth = Math.max(0, ...depth.values());
|
|
287
290
|
const rest = nodes.filter((n) => !depth.has(n.id)).sort(byGroup);
|
|
288
291
|
return { depth, pos, leaves: leaf, maxDepth, rest, treeEdges, children };
|
|
@@ -504,7 +507,9 @@ export function network(spec, theme) {
|
|
|
504
507
|
|
|
505
508
|
// Foco (clic en un nodo): va al centro y se destacan sus vecinos, con nombre.
|
|
506
509
|
const focusNode = spec.focus !== undefined && spec.focus !== null ? byId.get(String([].concat(spec.focus)[0])) ?? null : null;
|
|
507
|
-
|
|
510
|
+
// Un `highlight` que no existe en la red se ignora (si no, todo quedaría atenuado).
|
|
511
|
+
const hlIds = spec.highlight === undefined || spec.highlight === null ? [] : [].concat(spec.highlight).map(String).filter((id) => byId.has(id));
|
|
512
|
+
const hl = focusNode ? new Set([focusNode.id]) : hlIds.length ? new Set(hlIds) : null;
|
|
508
513
|
const near = new Set();
|
|
509
514
|
if (hl) { for (const id of hl) near.add(id); for (const e of edges) { if (hl.has(e.s)) near.add(e.t); if (hl.has(e.t)) near.add(e.s); } }
|
|
510
515
|
const on = (n) => !hl || near.has(n.id);
|
package/src/charts/pointmap.js
CHANGED
|
@@ -23,6 +23,9 @@ const MAX_LAT = 85.0511287798;
|
|
|
23
23
|
const TILE = 256;
|
|
24
24
|
const MAX_TILES = 48;
|
|
25
25
|
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
26
|
+
// Mínimo y máximo sin `...` (con cientos de miles de puntos desborda la pila).
|
|
27
|
+
const lo = (a) => a.reduce((m, v) => (v < m ? v : m), Infinity);
|
|
28
|
+
const hi = (a) => a.reduce((m, v) => (v > m ? v : m), -Infinity);
|
|
26
29
|
|
|
27
30
|
// ── Web Mercator (EPSG:3857) en coordenadas normalizadas [0, 1] ─────────────
|
|
28
31
|
/** Longitud → x de Web Mercator en [0, 1] (0 = 180° O). */
|
|
@@ -235,10 +238,10 @@ export function pointmap(spec, theme) {
|
|
|
235
238
|
let unwrapped = false;
|
|
236
239
|
{
|
|
237
240
|
const lons = pts.map((p) => p.lon);
|
|
238
|
-
const span =
|
|
241
|
+
const span = hi(lons) - lo(lons);
|
|
239
242
|
if (span > 180) {
|
|
240
243
|
const alt = lons.map((l) => (l < 0 ? l + 360 : l));
|
|
241
|
-
if (
|
|
244
|
+
if (hi(alt) - lo(alt) < span) { for (const p of pts) if (p.lon < 0) p.lon += 360; unwrapped = true; }
|
|
242
245
|
}
|
|
243
246
|
}
|
|
244
247
|
for (const p of pts) { p.mx = mercX(p.lon); p.my = mercY(p.lat); }
|
|
@@ -267,7 +270,7 @@ export function pointmap(spec, theme) {
|
|
|
267
270
|
colorOf = (p) => cmap.get(p.k) || theme.other;
|
|
268
271
|
if (keys.length > 1) legend = keys.map((k) => ({ label: k, color: cmap.get(k) }));
|
|
269
272
|
} else if (valueF && values.length) {
|
|
270
|
-
const vmin =
|
|
273
|
+
const vmin = lo(values), vmax = hi(values);
|
|
271
274
|
const mid = spec.midpoint ?? (spec.diverging ? 0 : undefined);
|
|
272
275
|
let stops, at;
|
|
273
276
|
if (mid !== undefined) {
|
|
@@ -295,7 +298,7 @@ export function pointmap(spec, theme) {
|
|
|
295
298
|
let area = frame.layout(legend);
|
|
296
299
|
|
|
297
300
|
// Radios: área proporcional a `size` (raíz cuadrada), o uno fijo según la densidad.
|
|
298
|
-
const maxS = Math.max(
|
|
301
|
+
const maxS = sizes.reduce((m, v) => Math.max(m, Math.abs(v)), 0);
|
|
299
302
|
const wk = Math.max(0.6, Math.min(1.2, area.w / 672));
|
|
300
303
|
const rMax = sizeF && maxS ? spec.maxRadius ?? Math.round((n <= 40 ? 22 : n <= 150 ? 15 : n <= 600 ? 10 : 6) * wk) : 0;
|
|
301
304
|
const rBase = spec.radius ?? (dense ? 2.2 : n > 500 ? 3 : n > 120 ? 4 : 5.5);
|
|
@@ -513,8 +516,8 @@ function describePointmap({ pts, n, provider, world, hueF, keys, sizeF, valueF,
|
|
|
513
516
|
const en = String(locale).startsWith('en');
|
|
514
517
|
const W = en ? 'W' : 'O';
|
|
515
518
|
const lats = pts.map((p) => p.lat), lons = pts.map((p) => (p.lon > 180 ? p.lon - 360 : p.lon));
|
|
516
|
-
const la0 =
|
|
517
|
-
const lo0 =
|
|
519
|
+
const la0 = lo(lats), la1 = hi(lats);
|
|
520
|
+
const lo0 = lo(lons), lo1 = hi(lons);
|
|
518
521
|
const base = provider ? provider.name || provider.attribution : world ? (en ? 'a world outline' : 'un contorno del mundo') : '';
|
|
519
522
|
const where = n === 1 || (la1 - la0 < 0.05 && lo1 - lo0 < 0.05)
|
|
520
523
|
? (en ? ` at ${deg(la0, 'N', 'S', locale)}, ${deg(lo0, 'E', W, locale)}` : ` en ${deg(la0, 'N', 'S', locale)}, ${deg(lo0, 'E', W, locale)}`)
|
package/src/charts/regplot.js
CHANGED
|
@@ -56,7 +56,7 @@ function fitGroup(gp, spec, { xLog, xMin, xMax, steps = 90 }) {
|
|
|
56
56
|
const pts = lw.map(([t, v]) => [spec.logx ? 10 ** t : t, v]);
|
|
57
57
|
return { line: pts, band: null, r2: null, n: gp.length };
|
|
58
58
|
}
|
|
59
|
-
const fit = polyfit(xs, ys, { order: spec.order ?? 1, level: (spec.ci
|
|
59
|
+
const fit = polyfit(xs, ys, { order: spec.order ?? 1, level: typeof spec.ci === 'number' ? (spec.ci > 1 ? spec.ci / 100 : spec.ci) : 0.95 });
|
|
60
60
|
const line = grid.map((v) => [v, fit.predict(fx(v))]);
|
|
61
61
|
const band = spec.ci === false || spec.ci === null || gp.length < (spec.order ?? 1) + 3 ? null : grid.map((v) => [v, ...fit.band(fx(v))]);
|
|
62
62
|
return { line, band, r2: fit.r2, n: fit.n, fit };
|
|
@@ -123,7 +123,7 @@ export function regplot(spec, theme) {
|
|
|
123
123
|
});
|
|
124
124
|
if (spec.scatter !== false) {
|
|
125
125
|
const order = [...pts].sort((a, b) => (focusKey(a.k) ? 1 : 0) - (focusKey(b.k) ? 1 : 0));
|
|
126
|
-
const dots = order.map((p) => ({
|
|
126
|
+
const dots = order.filter((p) => visible(p, xS, yS, inner)).map((p) => ({
|
|
127
127
|
class: 'pa-m', cx: xS(p.x), cy: yS(p.y), r, fill: colorOf(p.k), fillOpacity: K === 1 ? (M.dotOpacity ?? 0.85) * 0.75 : M.dotOpacity ?? 0.85,
|
|
128
128
|
stroke: pts.length > 400 ? 'none' : theme.surface, strokeWidth: 1.25,
|
|
129
129
|
'data-tip': tip(p.name || p.k || '', `${humanize(P.x)}: ${fx(p.x)}`, `${humanize(P.y)}: ${fy(p.y)}`, p.name && p.k ? p.k : ''),
|
|
@@ -351,7 +351,7 @@ export function jointplot(spec, theme) {
|
|
|
351
351
|
body.push(el('path', { d: L(f.line), fill: 'none', stroke: K === 1 ? theme.ink : colorOf(k), strokeWidth: M.lineWidth ?? 2, opacity: 0.9 }));
|
|
352
352
|
});
|
|
353
353
|
}
|
|
354
|
-
body.push(g({}, dotLayer(pts.map((p) => ({ class: 'pa-m', cx: xS(p.x), cy: yS(p.y), r, fill: colorOf(p.k), fillOpacity: M.dotOpacity ?? 0.85, stroke: pts.length > 400 ? 'none' : theme.surface, strokeWidth: 1.25, 'data-tip': tip(p.name || p.k, `${humanize(P.x)}: ${fx(p.x)}`, `${humanize(P.y)}: ${fy(p.y)}`, p.name && p.k ? p.k : '') })))));
|
|
354
|
+
body.push(g({}, dotLayer(pts.filter((p) => visible(p, xS, yS, inner)).map((p) => ({ class: 'pa-m', cx: xS(p.x), cy: yS(p.y), r, fill: colorOf(p.k), fillOpacity: M.dotOpacity ?? 0.85, stroke: pts.length > 400 ? 'none' : theme.surface, strokeWidth: 1.25, 'data-tip': tip(p.name || p.k, `${humanize(P.x)}: ${fx(p.x)}`, `${humanize(P.y)}: ${fy(p.y)}`, p.name && p.k ? p.k : '') })))));
|
|
355
355
|
body.push(pointLabels(pts, spec, xS, yS, r, inner, theme, P));
|
|
356
356
|
body.push(gridX(xAx.ticks, xS, inner, theme, tickFmt(fx, xAx), { lines: false }));
|
|
357
357
|
body.push(yTickLabels(yAx.ticks, yS, inner, theme, yt));
|
|
@@ -363,6 +363,12 @@ export function jointplot(spec, theme) {
|
|
|
363
363
|
return frame.finish(body, { desc, type: 'jointplot' });
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
/** Con xDomain/yDomain, los puntos fuera del rango no se dibujan fuera del área. */
|
|
367
|
+
function visible(p, xS, yS, inner) {
|
|
368
|
+
const cx = xS(p.x), cy = yS(p.y);
|
|
369
|
+
return cx >= inner.x - 1 && cx <= inner.x + inner.w + 1 && cy >= inner.y - 1 && cy <= inner.y + inner.h + 1;
|
|
370
|
+
}
|
|
371
|
+
|
|
366
372
|
function hashStr(s) {
|
|
367
373
|
let h = 0;
|
|
368
374
|
for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
|
package/src/charts/sankey.js
CHANGED
|
@@ -120,7 +120,7 @@ export function sankey(spec, theme) {
|
|
|
120
120
|
const x0 = l.sn.x + NW, x1 = l.tn.x, xm = (x0 + x1) / 2, w = l.v * ky;
|
|
121
121
|
const d = `M${x0},${l.y0}C${xm},${l.y0} ${xm},${l.y1} ${x1},${l.y1}L${x1},${l.y1 + w}C${xm},${l.y1 + w} ${xm},${l.y0 + w} ${x0},${l.y0 + w}Z`;
|
|
122
122
|
const share = l.sn.value ? Math.round((l.v / l.sn.value) * 100) : 0;
|
|
123
|
-
linkG.push(el('path', { class: 'pa-m', d, fill: linkColor(l), fillOpacity: theme.link, 'data-tip': `${l.s} → ${l.t}\n${fmt(l.v)} · ${share}% de ${l.s}` }));
|
|
123
|
+
linkG.push(el('path', { class: 'pa-m', d, fill: linkColor(l), fillOpacity: theme.link, 'data-tip': `${l.s} → ${l.t}\n${fmt(l.v)} · ${share}% ${en ? 'of' : 'de'} ${l.s}` }));
|
|
124
124
|
}
|
|
125
125
|
body.push(g({ class: 'pa-links' }, linkG));
|
|
126
126
|
for (const [ci, c] of cols.entries()) for (const n of c) {
|
package/src/charts/scatter.js
CHANGED
|
@@ -138,7 +138,9 @@ export function scatter(spec, theme) {
|
|
|
138
138
|
// Tooltips: la capa densa guarda a lo más MAX_TIPS; no se arman los que se descartan.
|
|
139
139
|
const MAX_TIPS = 20000;
|
|
140
140
|
const hx = humanize(x), hy = humanize(y), hs = size ? humanize(size) : '';
|
|
141
|
-
|
|
141
|
+
// Con xDomain/yDomain, los puntos fuera del rango no se dibujan fuera del área.
|
|
142
|
+
const vis = (p) => { const cx = xS(p.x), cy = yS(p.y); return cx >= inner.x - 1 && cx <= inner.x + inner.w + 1 && cy >= inner.y - 1 && cy <= inner.y + inner.h + 1; };
|
|
143
|
+
const circles = sorted.filter(vis).map((p, i) => {
|
|
142
144
|
let t = '';
|
|
143
145
|
if (i < MAX_TIPS || sorted.length <= 1500) {
|
|
144
146
|
const lines = [p.name || '', `${hx}: ${fx(p.x)}`, `${hy}: ${fy(p.y)}`];
|
|
@@ -195,7 +197,7 @@ export function scatter(spec, theme) {
|
|
|
195
197
|
}
|
|
196
198
|
const placed = [];
|
|
197
199
|
for (const p of chosen) {
|
|
198
|
-
if (!p.name) continue;
|
|
200
|
+
if (!p.name || !vis(p)) continue;
|
|
199
201
|
const w = measure(p.name, 12, 500);
|
|
200
202
|
const r = rOf(p);
|
|
201
203
|
let lx = xS(p.x) + r + 4, ly = yS(p.y) + 4, anchor = 'start';
|
package/src/charts/sunburst.js
CHANGED
|
@@ -7,7 +7,7 @@ import { makeFormat } from '../format.js';
|
|
|
7
7
|
import { textOn } from '../theme.js';
|
|
8
8
|
import { humanize } from '../data.js';
|
|
9
9
|
import { createFrame } from '../frame.js';
|
|
10
|
-
import { buildHierarchy,
|
|
10
|
+
import { buildHierarchy, focusNode, assignColors, shareFmt, breadcrumb, height, SEP } from '../hierarchy.js';
|
|
11
11
|
|
|
12
12
|
const TAU = Math.PI * 2;
|
|
13
13
|
const f = (n) => n.toFixed(2);
|
|
@@ -24,7 +24,7 @@ export function sunburst(spec, theme) {
|
|
|
24
24
|
const locale = spec.locale || 'es';
|
|
25
25
|
const en = String(locale).startsWith('en');
|
|
26
26
|
const { root, fields, valueField } = buildHierarchy(spec);
|
|
27
|
-
const focus =
|
|
27
|
+
const focus = focusNode(root, spec.focus);
|
|
28
28
|
const levels = Math.max(1, height(focus));
|
|
29
29
|
assignColors(root, theme, spec); // colores estables al navegar
|
|
30
30
|
const vals = [];
|