@pixerate/editor 0.163.0 → 0.165.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/dist/agent/index.cjs +54 -39
- package/dist/agent/index.js +24 -9
- package/dist/animation/index.cjs +6 -5
- package/dist/animation/index.js +5 -4
- package/dist/animation-editor/index.cjs +65 -64
- package/dist/animation-editor/index.js +8 -7
- package/dist/bridge/cli.cjs +3 -3
- package/dist/bridge/cli.js +2 -2
- package/dist/bridge/index.cjs +3 -3
- package/dist/bridge/index.js +2 -2
- package/dist/canvas/displacement.d.cts +34 -2
- package/dist/canvas/displacement.d.ts +34 -2
- package/dist/canvas/explosion-tools.d.cts +43 -0
- package/dist/canvas/explosion-tools.d.ts +43 -0
- package/dist/canvas/explosionLayout.d.cts +50 -0
- package/dist/canvas/explosionLayout.d.ts +50 -0
- package/dist/canvas/explosionState.d.cts +52 -0
- package/dist/canvas/explosionState.d.ts +52 -0
- package/dist/canvas/index.cjs +512 -168
- package/dist/canvas/index.d.cts +3 -0
- package/dist/canvas/index.d.ts +3 -0
- package/dist/canvas/index.js +434 -90
- package/dist/canvas/types.d.cts +27 -2
- package/dist/canvas/types.d.ts +27 -2
- package/dist/chart/controller.d.cts +31 -2
- package/dist/chart/controller.d.ts +31 -2
- package/dist/chart/index.cjs +504 -132
- package/dist/chart/index.d.cts +2 -0
- package/dist/chart/index.d.ts +2 -0
- package/dist/chart/index.js +386 -14
- package/dist/chart/layout-families.d.cts +7 -0
- package/dist/chart/layout-families.d.ts +7 -0
- package/dist/chart/links.d.cts +34 -0
- package/dist/chart/links.d.ts +34 -0
- package/dist/chart/mermaid.d.cts +9 -6
- package/dist/chart/mermaid.d.ts +9 -6
- package/dist/chart/schemas.d.cts +3 -0
- package/dist/chart/schemas.d.ts +3 -0
- package/dist/chart/spec.d.cts +3 -2
- package/dist/chart/spec.d.ts +3 -2
- package/dist/chart/style.d.cts +33 -0
- package/dist/chart/style.d.ts +33 -0
- package/dist/chart/types.d.cts +24 -2
- package/dist/chart/types.d.ts +24 -2
- package/dist/{chunk-TALQONZ5.js → chunk-2J5VLJHC.js} +3 -3
- package/dist/{chunk-3NEZJCBY.js → chunk-2ZRN43IN.js} +340 -201
- package/dist/{chunk-ZVNAMTFO.cjs → chunk-3FSUJIXN.cjs} +18 -174
- package/dist/{chunk-RQCMSEM5.cjs → chunk-4QUUHTGJ.cjs} +2 -2
- package/dist/{chunk-CIPIESX4.cjs → chunk-4ZC5QJE2.cjs} +3 -3
- package/dist/{chunk-S6X2QRVU.cjs → chunk-5UEFBM2M.cjs} +1 -138
- package/dist/{chunk-YSAGBIRW.cjs → chunk-64VWWR5U.cjs} +16 -16
- package/dist/{chunk-QYZGBVNC.js → chunk-7272D3K2.js} +10 -8
- package/dist/{chunk-462WGGXX.cjs → chunk-AMX23CSN.cjs} +27 -16
- package/dist/{chunk-3VLOOO3G.js → chunk-BDIM6NVW.js} +1 -1
- package/dist/{chunk-POR54B55.js → chunk-CRFSQWKQ.js} +4 -1
- package/dist/{chunk-GZ2FM3ZB.js → chunk-DKNEQX7T.js} +6 -162
- package/dist/{chunk-JCXMJUQO.cjs → chunk-EGZM52EO.cjs} +3 -3
- package/dist/{chunk-ES3UEVUT.cjs → chunk-EUIDTZNQ.cjs} +112 -110
- package/dist/{chunk-H3OEM3Q2.js → chunk-F6R67I3A.js} +1 -1
- package/dist/{chunk-5FNGLV2D.js → chunk-FEU2XVQH.js} +15 -3
- package/dist/{chunk-SZA42R4N.js → chunk-FRBRLKNQ.js} +154 -16
- package/dist/chunk-HIDFL5LQ.js +484 -0
- package/dist/{chunk-MKTDELCD.js → chunk-I5MRJW7B.js} +1 -1
- package/dist/{chunk-SDHZG5TF.js → chunk-ILP77UD6.js} +160 -2
- package/dist/{chunk-GKNEJ6EN.cjs → chunk-IMRXC7OA.cjs} +241 -103
- package/dist/chunk-KGRZMHZB.js +204 -0
- package/dist/{chunk-SET6KJOP.js → chunk-LPIKXPBM.js} +1 -1
- package/dist/{chunk-BQWGN5WH.js → chunk-M2TPMYJE.js} +1 -1
- package/dist/{chunk-GTELFJRE.cjs → chunk-M5GLQQWG.cjs} +2 -2
- package/dist/{chunk-4VU4CP7R.js → chunk-MHLU6GRJ.js} +1 -138
- package/dist/{chunk-D54JHQK2.cjs → chunk-MKXK6E4O.cjs} +51 -51
- package/dist/{chunk-DAPQGA55.js → chunk-N7NBGT2Y.js} +2 -2
- package/dist/chunk-O6OGNDND.js +164 -0
- package/dist/{chunk-22KLZZGH.cjs → chunk-ODFCS7HF.cjs} +8 -8
- package/dist/{chunk-PA3IMPPS.cjs → chunk-OVYQAYGH.cjs} +81 -81
- package/dist/{chunk-YXOW456B.js → chunk-PPEDZ5WR.js} +1 -1
- package/dist/{chunk-YGG7WDWT.cjs → chunk-Q25WAFTD.cjs} +21 -18
- package/dist/{chunk-SBQVEBSU.cjs → chunk-QO2T6KA5.cjs} +165 -67
- package/dist/{chunk-24M7VD6M.cjs → chunk-S4EIAIEW.cjs} +7 -7
- package/dist/{chunk-M4MIZPQI.cjs → chunk-SJ2CPTQC.cjs} +189 -15
- package/dist/chunk-TFF2GTGN.cjs +153 -0
- package/dist/chunk-TTCJPX4U.cjs +204 -0
- package/dist/{chunk-NZD2MZJC.cjs → chunk-TVQW26AV.cjs} +35 -23
- package/dist/{chunk-R3P2M7HU.cjs → chunk-UVXTOZ2B.cjs} +346 -207
- package/dist/{chunk-WI7QJA7A.cjs → chunk-UXETV2NC.cjs} +161 -3
- package/dist/{chunk-Z4P2RBMJ.js → chunk-V5GSY3NC.js} +101 -3
- package/dist/{chunk-QY4KUDMY.js → chunk-VRXXAXUT.js} +2 -2
- package/dist/chunk-VU6WQSMB.cjs +164 -0
- package/dist/{chunk-OXERWBKE.js → chunk-X5W645RI.js} +3 -3
- package/dist/{chunk-NHLYK3AX.js → chunk-XBQX76CT.js} +182 -8
- package/dist/{chunk-5AARIS3R.cjs → chunk-XCDGM7TD.cjs} +37 -37
- package/dist/{chunk-EVXQNQND.js → chunk-XZB3TJ6K.js} +16 -5
- package/dist/chunk-YBAPT7NL.js +153 -0
- package/dist/chunk-Z2UQYMPS.cjs +484 -0
- package/dist/color/index.cjs +5 -3
- package/dist/color/index.js +12 -10
- package/dist/diagram/index.cjs +55 -243
- package/dist/diagram/index.js +12 -200
- package/dist/export/index.cjs +7 -6
- package/dist/export/index.js +6 -5
- package/dist/extensions/index.cjs +12 -9
- package/dist/extensions/index.js +11 -8
- package/dist/gradient/index.cjs +29 -27
- package/dist/gradient/index.js +6 -4
- package/dist/image-editor/index.cjs +644 -643
- package/dist/image-editor/index.js +7 -6
- package/dist/index.cjs +21 -18
- package/dist/index.js +14 -11
- package/dist/paint-editor/index.cjs +19 -18
- package/dist/paint-editor/index.js +7 -6
- package/dist/parameters/index.cjs +2 -2
- package/dist/parameters/index.js +1 -1
- package/dist/persistence/assetWrites.d.cts +22 -0
- package/dist/persistence/assetWrites.d.ts +22 -0
- package/dist/persistence/assets.d.cts +71 -0
- package/dist/persistence/assets.d.ts +71 -0
- package/dist/persistence/documentStore.d.cts +17 -1
- package/dist/persistence/documentStore.d.ts +17 -1
- package/dist/persistence/documentTools.d.cts +5 -1
- package/dist/persistence/documentTools.d.ts +5 -1
- package/dist/persistence/editorAutosave.d.cts +7 -0
- package/dist/persistence/editorAutosave.d.ts +7 -0
- package/dist/persistence/index.cjs +141 -21
- package/dist/persistence/index.js +130 -10
- package/dist/persistence/storage.d.cts +13 -1
- package/dist/persistence/storage.d.ts +13 -1
- package/dist/spreadsheet/index.cjs +3 -3
- package/dist/spreadsheet/index.js +2 -2
- package/dist/tokens/index.cjs +11 -3
- package/dist/tokens/index.d.cts +1 -0
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.js +10 -2
- package/dist/tokens/style.d.cts +36 -0
- package/dist/tokens/style.d.ts +36 -0
- package/dist/tools/index.cjs +15 -3
- package/dist/tools/index.d.cts +2 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +14 -2
- package/dist/tools/style-tools.d.cts +19 -0
- package/dist/tools/style-tools.d.ts +19 -0
- package/dist/tools/style.d.cts +212 -0
- package/dist/tools/style.d.ts +212 -0
- package/dist/tools/view.d.cts +6 -0
- package/dist/tools/view.d.ts +6 -0
- package/dist/typography/index.cjs +8 -6
- package/dist/typography/index.js +5 -3
- package/dist/vector/index.cjs +953 -952
- package/dist/vector/index.js +7 -6
- package/dist/video-editor/index.cjs +8 -7
- package/dist/video-editor/index.js +6 -5
- package/package.json +1 -1
- package/skills/README.md +12 -0
- package/skills/pixerate-agent-tools/SKILL.md +34 -1
- package/skills/pixerate-chart/SKILL.md +41 -9
- package/skills/pixerate-image-editor/SKILL.md +2 -0
- package/skills/pixerate-paint-editor/SKILL.md +2 -0
- package/skills/pixerate-tokens/SKILL.md +5 -0
- package/dist/chunk-ERAGQL2N.cjs +0 -68
- package/dist/chunk-KMU6QJB7.js +0 -68
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSankey,
|
|
3
|
+
parseTreemap
|
|
4
|
+
} from "./chunk-KGRZMHZB.js";
|
|
1
5
|
import {
|
|
2
6
|
chartMovingAverage,
|
|
3
7
|
chartSupportsTrendlines,
|
|
@@ -7,20 +11,23 @@ import {
|
|
|
7
11
|
r2,
|
|
8
12
|
sameChartSelection,
|
|
9
13
|
summarizeChartSeries
|
|
10
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-V5GSY3NC.js";
|
|
11
15
|
import {
|
|
12
16
|
CHART_TYPE_LABELS,
|
|
13
17
|
DEFAULT_CHART_PALETTE,
|
|
14
18
|
DEFAULT_CHART_THEME,
|
|
15
19
|
chartDataFromTable,
|
|
16
20
|
chartFamily,
|
|
21
|
+
chartLinksFromTable,
|
|
17
22
|
chartSeriesColor,
|
|
18
23
|
chartSliceColor,
|
|
19
24
|
chartSpecColorsByCategory,
|
|
20
25
|
chartTreeDepth,
|
|
21
26
|
chartTreeFromRows,
|
|
27
|
+
chartTreeFromSeries,
|
|
22
28
|
chartTreeFromTable,
|
|
23
29
|
chartTreeToRows,
|
|
30
|
+
chartTreeTotal,
|
|
24
31
|
checkChartColors,
|
|
25
32
|
cloneChartSeries,
|
|
26
33
|
cloneChartSpec,
|
|
@@ -28,6 +35,7 @@ import {
|
|
|
28
35
|
defaultChartAxis,
|
|
29
36
|
describeChartErrorBars,
|
|
30
37
|
findChartTreeNode,
|
|
38
|
+
normalizeChartLinks,
|
|
31
39
|
normalizeChartReferenceBand,
|
|
32
40
|
normalizeChartReferenceLine,
|
|
33
41
|
normalizeChartSpec,
|
|
@@ -36,7 +44,7 @@ import {
|
|
|
36
44
|
parseChartValue,
|
|
37
45
|
shadeChartColor,
|
|
38
46
|
uniqueChartSeriesId
|
|
39
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-2ZRN43IN.js";
|
|
40
48
|
import {
|
|
41
49
|
parseCsv,
|
|
42
50
|
parseTsv
|
|
@@ -71,9 +79,14 @@ function splitOutside(text, sep) {
|
|
|
71
79
|
out.push(cur);
|
|
72
80
|
return out;
|
|
73
81
|
}
|
|
82
|
+
var csvField = (text) => /[",\n]|^\s|\s$/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
83
|
+
var treemapName = (name) => !name.includes('"') ? `"${name}"` : !name.includes("'") ? `'${name}'` : `"${name.replace(/"/g, "'")}"`;
|
|
84
|
+
var yamlText = (text) => /^[\w][\w .,!?()/&+-]*$/.test(text) && !/:\s/.test(text) ? text : JSON.stringify(text);
|
|
74
85
|
function chartMermaidFamily(spec) {
|
|
75
86
|
const family = chartFamily(spec);
|
|
76
|
-
if (family === "
|
|
87
|
+
if (family === "sankey" && spec.links?.length) return "sankey";
|
|
88
|
+
if (family === "treemap" || family === "sunburst") return "treemap";
|
|
89
|
+
if (family === "pie" || family === "funnel" || family === "parliament") return "pie";
|
|
77
90
|
if (family === "radar") return "radar";
|
|
78
91
|
return "xychart";
|
|
79
92
|
}
|
|
@@ -114,7 +127,27 @@ function chartToMermaid(spec, options = {}) {
|
|
|
114
127
|
const visible = spec.series.filter((s) => !s.hidden);
|
|
115
128
|
const lines = [];
|
|
116
129
|
const v = (x) => x === null || !Number.isFinite(x) ? "0" : num(x);
|
|
117
|
-
|
|
130
|
+
const front = [];
|
|
131
|
+
if (family === "sankey") {
|
|
132
|
+
if (spec.title) front.push("---", `title: ${yamlText(spec.title)}`, "---");
|
|
133
|
+
lines.push("sankey-beta");
|
|
134
|
+
for (const l of spec.links) lines.push(`${csvField(l.source)},${csvField(l.target)},${num(l.value)}`);
|
|
135
|
+
} else if (family === "treemap") {
|
|
136
|
+
lines.push("treemap-beta");
|
|
137
|
+
if (spec.title) lines.push(`title ${spec.title}`);
|
|
138
|
+
const first = visible[0] ?? spec.series[0];
|
|
139
|
+
const tree = spec.tree.length ? spec.tree : chartTreeFromSeries(spec.categories, first?.values ?? []);
|
|
140
|
+
const write = (nodes, depth) => {
|
|
141
|
+
for (const n of nodes) {
|
|
142
|
+
const pad = ind.repeat(depth);
|
|
143
|
+
if (n.children) {
|
|
144
|
+
lines.push(`${pad}${treemapName(n.name)}`);
|
|
145
|
+
write(n.children, depth + 1);
|
|
146
|
+
} else lines.push(`${pad}${treemapName(n.name)}: ${num(n.value ?? 0)}`);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
write(tree, 1);
|
|
150
|
+
} else if (family === "pie") {
|
|
118
151
|
const s = visible[0] ?? spec.series[0];
|
|
119
152
|
lines.push(`pie${spec.dataLabels ? " showData" : ""}`);
|
|
120
153
|
if (spec.title) lines.push(`${ind}title ${spec.title}`);
|
|
@@ -160,13 +193,13 @@ function chartToMermaid(spec, options = {}) {
|
|
|
160
193
|
lines.push(`${ind}${kind} ${quote(s.name)} [${s.values.map(v).join(", ")}]`);
|
|
161
194
|
}
|
|
162
195
|
}
|
|
163
|
-
let text = `${lines.join("\n")}
|
|
196
|
+
let text = `${[...front, ...lines].join("\n")}
|
|
164
197
|
`;
|
|
165
198
|
if (options.meta !== false) {
|
|
166
199
|
const meta = metaFor(spec, text);
|
|
167
200
|
if (meta) {
|
|
168
201
|
const [header, ...rest] = lines;
|
|
169
|
-
text = `${[header, `${CHART_MERMAID_META_PREFIX}${JSON.stringify({ v: 1, ...meta })}`, ...rest].join("\n")}
|
|
202
|
+
text = `${[...front, header, `${CHART_MERMAID_META_PREFIX}${JSON.stringify({ v: 1, ...meta })}`, ...rest].join("\n")}
|
|
170
203
|
`;
|
|
171
204
|
}
|
|
172
205
|
}
|
|
@@ -229,7 +262,9 @@ function chartFromMermaid(text, options = {}) {
|
|
|
229
262
|
showData = !!m[1];
|
|
230
263
|
if (m[2]) frontTitle = m[2].trim();
|
|
231
264
|
} else if (/^radar(?:-beta)?\s*$/i.test(t)) family = "radar";
|
|
232
|
-
else
|
|
265
|
+
else if (/^sankey(?:-beta)?\s*$/i.test(t)) family = "sankey";
|
|
266
|
+
else if (/^treemap(?:-beta)?\s*$/i.test(t)) family = "treemap";
|
|
267
|
+
else issues.push({ path: "header", line: i + 1, message: `Line ${i + 1}: expected a chart header (xychart-beta, pie, radar-beta, sankey-beta or treemap-beta), got "${t.slice(0, 40)}".` });
|
|
233
268
|
headerLine = i;
|
|
234
269
|
break;
|
|
235
270
|
}
|
|
@@ -245,6 +280,7 @@ function chartFromMermaid(text, options = {}) {
|
|
|
245
280
|
let graticule = "circle";
|
|
246
281
|
const counts = { bar: 0, line: 0 };
|
|
247
282
|
const lineIssue = (i, message) => issues.push({ path: `line ${i + 1}`, line: i + 1, message: `Line ${i + 1}: ${message}` });
|
|
283
|
+
const whole = family === "sankey" || family === "treemap";
|
|
248
284
|
for (let i = headerLine + 1; family && i < all.length; i++) {
|
|
249
285
|
const raw = all[i];
|
|
250
286
|
const t = raw.trim();
|
|
@@ -260,7 +296,7 @@ function chartFromMermaid(text, options = {}) {
|
|
|
260
296
|
}
|
|
261
297
|
continue;
|
|
262
298
|
}
|
|
263
|
-
if (t.startsWith("%%")) continue;
|
|
299
|
+
if (t.startsWith("%%") || whole) continue;
|
|
264
300
|
const isLast = i === all.length - 1 || all.slice(i + 1).every((l) => !l.trim());
|
|
265
301
|
let m;
|
|
266
302
|
if (m = /^title\s+(.+)$/i.exec(t)) {
|
|
@@ -362,7 +398,8 @@ function chartFromMermaid(text, options = {}) {
|
|
|
362
398
|
if (!(partial && isLast)) lineIssue(i, `could not read "${t.slice(0, 60)}"; it was skipped.`);
|
|
363
399
|
}
|
|
364
400
|
let input;
|
|
365
|
-
if (family
|
|
401
|
+
if (whole) input = readWholeMermaid(family, all, headerLine, partial, title, issues);
|
|
402
|
+
else if (family === "pie") {
|
|
366
403
|
input = {
|
|
367
404
|
type: "pie",
|
|
368
405
|
title,
|
|
@@ -398,18 +435,54 @@ function chartFromMermaid(text, options = {}) {
|
|
|
398
435
|
}
|
|
399
436
|
}
|
|
400
437
|
const { spec, issues: specIssues } = normalizeChartSpec(input);
|
|
401
|
-
if (!family && !partial && !issues.length) issues.push({ path: "header", message: "The text is empty: expected a chart header (xychart-beta, pie or
|
|
438
|
+
if (!family && !partial && !issues.length) issues.push({ path: "header", message: "The text is empty: expected a chart header (xychart-beta, pie, radar-beta, sankey-beta or treemap-beta)." });
|
|
402
439
|
return { spec, issues: [...issues, ...specIssues], family, hasMeta: !!meta };
|
|
403
440
|
}
|
|
441
|
+
function readWholeMermaid(family, all, headerLine, partial, title, issues) {
|
|
442
|
+
const body = all.slice(headerLine).map((l) => l.trim().startsWith(CHART_MERMAID_META_PREFIX.trim()) ? "%%" : l);
|
|
443
|
+
const parse = (lines) => family === "sankey" ? parseSankey(lines.join("\n")) : parseTreemap(lines.join("\n"));
|
|
444
|
+
let read = parse(body);
|
|
445
|
+
if ("error" in read && partial && body.length > 1) read = parse(body.slice(0, -1));
|
|
446
|
+
const lineOf = (n) => n === void 0 ? void 0 : n + headerLine;
|
|
447
|
+
if ("error" in read) {
|
|
448
|
+
const line = lineOf(read.line);
|
|
449
|
+
if (!(partial && body.length <= 2)) issues.push({ path: family, ...line ? { line } : {}, message: line ? `Line ${line}: ${read.error}` : read.error });
|
|
450
|
+
return { type: family === "sankey" ? "sankey" : "treemap", title };
|
|
451
|
+
}
|
|
452
|
+
if (family === "sankey") {
|
|
453
|
+
const data2 = read.data;
|
|
454
|
+
return { type: "sankey", title, links: data2.links, sankeyNodes: data2.nodes, series: [{ id: "series-1", name: "Values", values: [] }] };
|
|
455
|
+
}
|
|
456
|
+
const data = read.data;
|
|
457
|
+
let items = data.items;
|
|
458
|
+
let name = data.title ?? title;
|
|
459
|
+
if (items.length === 1 && items[0].children?.length) {
|
|
460
|
+
name ||= items[0].name;
|
|
461
|
+
items = items[0].children;
|
|
462
|
+
}
|
|
463
|
+
const toNode = (item) => item.children?.length ? { name: item.name, children: item.children.map(toNode) } : { name: item.name, value: item.value ?? 0 };
|
|
464
|
+
const tree = items.map(toNode);
|
|
465
|
+
const leaf = (n) => !n.children;
|
|
466
|
+
const flatOrOneLevel = tree.every(leaf) || tree.every((n) => n.children?.every(leaf));
|
|
467
|
+
return { type: flatOrOneLevel ? "treemap" : "sunburst", title: name, tree, categories: tree.map((n) => n.name), series: [{ id: "series-1", name: "Values", values: tree.map(chartTreeTotal) }] };
|
|
468
|
+
}
|
|
404
469
|
function findMermaidCharts(text) {
|
|
405
470
|
const lines = text.replace(/^/, "").split(/\r?\n/);
|
|
406
471
|
const found = [];
|
|
407
472
|
const familyOf = (body) => {
|
|
408
|
-
|
|
473
|
+
let rows = body.split(/\r?\n/);
|
|
474
|
+
const firstRow = rows.findIndex((l) => l.trim());
|
|
475
|
+
if (rows[firstRow]?.trim() === "---") {
|
|
476
|
+
const end = rows.findIndex((l, i) => i > firstRow && l.trim() === "---");
|
|
477
|
+
if (end > 0) rows = rows.slice(end + 1);
|
|
478
|
+
}
|
|
479
|
+
const header = rows.find((l) => l.trim() && !l.trim().startsWith("%%"));
|
|
409
480
|
if (!header) return null;
|
|
410
481
|
if (/^\s*xychart(-beta)?\b/.test(header)) return "xychart";
|
|
411
482
|
if (/^\s*pie\b/.test(header)) return "pie";
|
|
412
483
|
if (/^\s*radar(-beta)?\b/.test(header)) return "radar";
|
|
484
|
+
if (/^\s*sankey(-beta)?\s*$/.test(header)) return "sankey";
|
|
485
|
+
if (/^\s*treemap(-beta)?\s*$/.test(header)) return "treemap";
|
|
413
486
|
return null;
|
|
414
487
|
};
|
|
415
488
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -476,6 +549,14 @@ function describeChart(spec, series = spec.series.filter((s) => !s.hidden).map((
|
|
|
476
549
|
}
|
|
477
550
|
return parts.join(" ");
|
|
478
551
|
}
|
|
552
|
+
if (family === "sankey") {
|
|
553
|
+
if (!spec.links.length) return `${head} with no flows yet.`;
|
|
554
|
+
const total = spec.links.reduce((t, l) => t + l.value, 0);
|
|
555
|
+
const largest = [...spec.links].sort((a, b) => b.value - a.value)[0];
|
|
556
|
+
const sources = spec.sankeyNodes.filter((n) => !spec.links.some((l) => l.target === n));
|
|
557
|
+
const sinks = spec.sankeyNodes.filter((n) => !spec.links.some((l) => l.source === n));
|
|
558
|
+
return `${head} of ${spec.links.length} flows between ${spec.sankeyNodes.length} nodes, from ${sources.join(", ")} to ${sinks.join(", ")}. The largest flow is ${largest.source} \u2192 ${largest.target} (${fmt(largest.value)}); all flows add up to ${fmt(total)}.`;
|
|
559
|
+
}
|
|
479
560
|
if (family === "parliament") {
|
|
480
561
|
const s = spec.series.find((x) => !x.hidden);
|
|
481
562
|
const seats = spec.categories.map((c, i) => ({ c, n: Math.max(0, Math.round(s.values[i] ?? 0)) }));
|
|
@@ -910,7 +991,7 @@ var ChartController = class {
|
|
|
910
991
|
* to the categories; all-or-nothing like `update`.
|
|
911
992
|
*/
|
|
912
993
|
setData(data, options = {}) {
|
|
913
|
-
const clear = (data.categories || data.series) && this.spec.tree.length ? { tree: [] } : {};
|
|
994
|
+
const clear = (data.categories || data.series) && (this.spec.tree.length || this.spec.links.length) ? { tree: [], links: [] } : {};
|
|
914
995
|
return this.update({ ...data.categories ? { categories: data.categories } : {}, ...data.series ? { series: data.series } : {}, ...clear }, { label: options.label ?? "Edit chart data" });
|
|
915
996
|
}
|
|
916
997
|
/**
|
|
@@ -1153,7 +1234,7 @@ var ChartController = class {
|
|
|
1153
1234
|
const old = byName.get(s.name);
|
|
1154
1235
|
return { ...s, ...old?.color ? { color: old.color } : {}, ...old?.type ? { type: old.type } : {}, ...old ? { id: old.id } : {} };
|
|
1155
1236
|
});
|
|
1156
|
-
const { spec } = normalizeChartSpec({ ...this.spec, categories: data.categories, series, tree: [] });
|
|
1237
|
+
const { spec } = normalizeChartSpec({ ...this.spec, categories: data.categories, series, tree: [], links: [] });
|
|
1157
1238
|
this.commit("Import data", spec);
|
|
1158
1239
|
if (data.issues.length) {
|
|
1159
1240
|
this.issues = data.issues;
|
|
@@ -1170,8 +1251,12 @@ var ChartController = class {
|
|
|
1170
1251
|
// -------------------------------------------------------------------------
|
|
1171
1252
|
// Grouped data (tree)
|
|
1172
1253
|
// -------------------------------------------------------------------------
|
|
1173
|
-
/** A refusal while the data is a tree (cell edits would be overwritten by
|
|
1254
|
+
/** A refusal while the data is a tree or flows (cell edits would be overwritten by their totals), else null. */
|
|
1174
1255
|
treeLocked() {
|
|
1256
|
+
if (this.spec.links.length)
|
|
1257
|
+
return this.reject(
|
|
1258
|
+
"This chart's data is flows (links): its categories are the nodes and its values how much passes through each. Change it with setLinks / the flow edits (set_chart_links, add_chart_link, update_chart_link, remove_chart_link), or replace it with new data."
|
|
1259
|
+
);
|
|
1175
1260
|
if (!this.spec.tree.length) return null;
|
|
1176
1261
|
return this.reject(
|
|
1177
1262
|
"This chart's data is grouped (a tree): its categories and values are the top-level groups and their totals. Change it with setTree / the tree node edits (set_chart_tree, add_chart_tree_node, update_chart_tree_node, remove_chart_tree_node), or replace it with new data."
|
|
@@ -1182,7 +1267,7 @@ var ChartController = class {
|
|
|
1182
1267
|
const issues = [];
|
|
1183
1268
|
const next = normalizeChartTree(tree, issues);
|
|
1184
1269
|
if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
|
|
1185
|
-
return this.update({ tree: next }, { label: options.label ?? (next.length ? "Replace grouped data" : "Ungroup data") });
|
|
1270
|
+
return this.update({ tree: next, links: [] }, { label: options.label ?? (next.length ? "Replace grouped data" : "Ungroup data") });
|
|
1186
1271
|
}
|
|
1187
1272
|
/**
|
|
1188
1273
|
* Replaces the grouped data from rows of names and a value (group, …, item, value), as the
|
|
@@ -1208,7 +1293,7 @@ var ChartController = class {
|
|
|
1208
1293
|
const next = chartTreeFromTable(rows, options, issues);
|
|
1209
1294
|
if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
|
|
1210
1295
|
if (!next.length) return this.reject('There is no grouped data to import: expected rows of names followed by a value, e.g. "Europe,France,40".');
|
|
1211
|
-
return this.update({ tree: next }, { label: "Import grouped data" });
|
|
1296
|
+
return this.update({ tree: next, links: [] }, { label: "Import grouped data" });
|
|
1212
1297
|
}
|
|
1213
1298
|
nodeAt(path) {
|
|
1214
1299
|
const node = findChartTreeNode(this.spec.tree, path);
|
|
@@ -1281,6 +1366,56 @@ var ChartController = class {
|
|
|
1281
1366
|
return this.update({ tree }, { label: `Delete "${node.name}"` });
|
|
1282
1367
|
}
|
|
1283
1368
|
// -------------------------------------------------------------------------
|
|
1369
|
+
// Flows (links)
|
|
1370
|
+
// -------------------------------------------------------------------------
|
|
1371
|
+
/**
|
|
1372
|
+
* Replaces the flows (and, when given, the node order). An empty list goes back to the
|
|
1373
|
+
* table, keeping how much passed through each node as values. One undo step, all-or-nothing.
|
|
1374
|
+
*/
|
|
1375
|
+
setLinks(links, options = {}) {
|
|
1376
|
+
const issues = [];
|
|
1377
|
+
const next = normalizeChartLinks(links, issues);
|
|
1378
|
+
if (issues.length) return this.reject(issues.map((i) => i.message).join(" "), issues);
|
|
1379
|
+
const used = new Set(next.flatMap((l) => [l.source, l.target]));
|
|
1380
|
+
const sankeyNodes = options.nodes ? [...options.nodes] : this.spec.sankeyNodes.filter((n) => used.has(n));
|
|
1381
|
+
return this.update({ links: next, tree: [], sankeyNodes }, { label: options.label ?? (next.length ? "Replace flows" : "Remove flows") });
|
|
1382
|
+
}
|
|
1383
|
+
/** Replaces the data with flows from a table of source, target and value columns. */
|
|
1384
|
+
importLinksTable(rows, options = {}) {
|
|
1385
|
+
const raw = chartLinksFromTable(rows, options);
|
|
1386
|
+
if (!raw.length) return this.reject('There are no flows to import: expected rows of source, target and value, e.g. "Website,Signup,120".');
|
|
1387
|
+
return this.setLinks(raw, { label: "Import flows" });
|
|
1388
|
+
}
|
|
1389
|
+
badLink(index) {
|
|
1390
|
+
const n = this.spec.links.length;
|
|
1391
|
+
return fail(`There is no flow ${index}; ${n ? `expected 0 to ${n - 1}` : "the chart has none yet"}.`);
|
|
1392
|
+
}
|
|
1393
|
+
/** Adds a flow and returns its index. */
|
|
1394
|
+
addLink(link) {
|
|
1395
|
+
const result = this.setLinks([...this.spec.links, link], { label: `Add flow ${String(link?.source ?? "")} \u2192 ${String(link?.target ?? "")}` });
|
|
1396
|
+
if (!result.ok) return result;
|
|
1397
|
+
return { ok: true, index: this.spec.links.length - 1 };
|
|
1398
|
+
}
|
|
1399
|
+
/** Changes a flow's source, target, value or colour (`color: null` goes back to the default). */
|
|
1400
|
+
updateLink(index, patch) {
|
|
1401
|
+
const current = this.spec.links[index];
|
|
1402
|
+
if (!current) return this.badLink(index);
|
|
1403
|
+
const merged = { ...current, ...patch };
|
|
1404
|
+
if (patch.color === null) delete merged.color;
|
|
1405
|
+
return this.setLinks(
|
|
1406
|
+
this.spec.links.map((l, i) => i === index ? merged : l),
|
|
1407
|
+
{ label: "Change flow" }
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
removeLink(index) {
|
|
1411
|
+
const current = this.spec.links[index];
|
|
1412
|
+
if (!current) return this.badLink(index);
|
|
1413
|
+
return this.setLinks(
|
|
1414
|
+
this.spec.links.filter((_, i) => i !== index),
|
|
1415
|
+
{ label: `Delete flow ${current.source} \u2192 ${current.target}` }
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
// -------------------------------------------------------------------------
|
|
1284
1419
|
// Annotations
|
|
1285
1420
|
// -------------------------------------------------------------------------
|
|
1286
1421
|
/** Sets the note written under the chart ("" removes it). */
|
|
@@ -1675,6 +1810,9 @@ function updateLabel(patch) {
|
|
|
1675
1810
|
parliamentLabel: "Edit seat caption",
|
|
1676
1811
|
sunburstLabel: "Edit centre caption",
|
|
1677
1812
|
tree: "Replace grouped data",
|
|
1813
|
+
links: "Replace flows",
|
|
1814
|
+
sankeyNodes: "Reorder nodes",
|
|
1815
|
+
sankeyLinkColor: "Change flow colours",
|
|
1678
1816
|
radarTicks: "Change radar rings",
|
|
1679
1817
|
radarGraticule: "Change radar grid",
|
|
1680
1818
|
legend: "Change legend",
|