@pixerate/editor 0.164.0 → 0.166.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/index.cjs +89 -87
- package/dist/canvas/index.js +11 -9
- package/dist/chart/controller.d.cts +60 -2
- package/dist/chart/controller.d.ts +60 -2
- package/dist/chart/index.cjs +657 -163
- package/dist/chart/index.d.cts +3 -0
- package/dist/chart/index.d.ts +3 -0
- package/dist/chart/index.js +512 -18
- package/dist/chart/layout-families.d.cts +14 -0
- package/dist/chart/layout-families.d.ts +14 -0
- package/dist/chart/layout-shared.d.cts +5 -0
- package/dist/chart/layout-shared.d.ts +5 -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/race.d.cts +26 -0
- package/dist/chart/race.d.ts +26 -0
- package/dist/chart/schemas.d.cts +5 -0
- package/dist/chart/schemas.d.ts +5 -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 +28 -2
- package/dist/chart/types.d.ts +28 -2
- 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-5FNGLV2D.js → chunk-5UNQDC3Q.js} +18 -3
- package/dist/{chunk-YSAGBIRW.cjs → chunk-6FYIFGBW.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-R3P2M7HU.cjs → chunk-BPX4Z3N3.cjs} +355 -207
- package/dist/{chunk-D54JHQK2.cjs → chunk-CHLV7UFP.cjs} +51 -51
- package/dist/{chunk-OXERWBKE.js → chunk-DFCAR7AW.js} +3 -3
- 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-3NEZJCBY.js → chunk-FR432ROA.js} +349 -201
- package/dist/chunk-HIDFL5LQ.js +484 -0
- package/dist/{chunk-22KLZZGH.cjs → chunk-HXOBOHH2.cjs} +8 -8
- package/dist/{chunk-MKTDELCD.js → chunk-I5MRJW7B.js} +1 -1
- package/dist/{chunk-SDHZG5TF.js → chunk-ILP77UD6.js} +160 -2
- package/dist/{chunk-Z4P2RBMJ.js → chunk-ITANXMZD.js} +177 -3
- 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-NZD2MZJC.cjs → chunk-MZFZSVUJ.cjs} +38 -23
- package/dist/{chunk-DAPQGA55.js → chunk-NQQDGGXY.js} +2 -2
- package/dist/chunk-O6OGNDND.js +164 -0
- package/dist/{chunk-PA3IMPPS.cjs → chunk-OVYQAYGH.cjs} +81 -81
- package/dist/{chunk-YXOW456B.js → chunk-PPEDZ5WR.js} +1 -1
- package/dist/{chunk-24M7VD6M.cjs → chunk-S4EIAIEW.cjs} +7 -7
- package/dist/{chunk-M4MIZPQI.cjs → chunk-SJ2CPTQC.cjs} +189 -15
- package/dist/{chunk-YGG7WDWT.cjs → chunk-SZWVMB3B.cjs} +24 -18
- package/dist/chunk-TFF2GTGN.cjs +153 -0
- package/dist/chunk-TTCJPX4U.cjs +204 -0
- package/dist/{chunk-SBQVEBSU.cjs → chunk-UUUTEVX6.cjs} +242 -68
- package/dist/{chunk-WI7QJA7A.cjs → chunk-UXETV2NC.cjs} +161 -3
- package/dist/{chunk-GKNEJ6EN.cjs → chunk-V5L2G22R.cjs} +321 -107
- package/dist/{chunk-TALQONZ5.js → chunk-VB2CY6EG.js} +3 -3
- package/dist/{chunk-QY4KUDMY.js → chunk-VRXXAXUT.js} +2 -2
- package/dist/chunk-VU6WQSMB.cjs +164 -0
- package/dist/{chunk-NHLYK3AX.js → chunk-XBQX76CT.js} +182 -8
- package/dist/{chunk-5AARIS3R.cjs → chunk-XCDGM7TD.cjs} +37 -37
- package/dist/{chunk-POR54B55.js → chunk-XNU45TYA.js} +7 -1
- package/dist/{chunk-EVXQNQND.js → chunk-XZB3TJ6K.js} +16 -5
- package/dist/chunk-YBAPT7NL.js +153 -0
- package/dist/{chunk-SZA42R4N.js → chunk-YUFTVNLX.js} +230 -16
- 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/pixerate-chart/SKILL.md +20 -2
- package/dist/chunk-ERAGQL2N.cjs +0 -68
- package/dist/chunk-KMU6QJB7.js +0 -68
package/dist/chart/index.js
CHANGED
|
@@ -2,6 +2,9 @@ import {
|
|
|
2
2
|
CATEGORY_REF,
|
|
3
3
|
CHART_UPDATE_PROPERTIES,
|
|
4
4
|
HEIGHT,
|
|
5
|
+
LINK_INDEX,
|
|
6
|
+
LINK_PROPS,
|
|
7
|
+
RACE_TIME,
|
|
5
8
|
REFERENCE_BAND_PATCH,
|
|
6
9
|
REFERENCE_BAND_PROPS,
|
|
7
10
|
REFERENCE_INDEX,
|
|
@@ -14,7 +17,7 @@ import {
|
|
|
14
17
|
TRENDLINE_PROPS,
|
|
15
18
|
VALUE,
|
|
16
19
|
WIDTH
|
|
17
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-5UNQDC3Q.js";
|
|
18
21
|
import {
|
|
19
22
|
CHART_OOXML_CONTENT_TYPE,
|
|
20
23
|
CHART_OOXML_NS,
|
|
@@ -22,7 +25,7 @@ import {
|
|
|
22
25
|
chartFormatToCode,
|
|
23
26
|
chartFromOoxml,
|
|
24
27
|
chartToOoxml
|
|
25
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-XNU45TYA.js";
|
|
26
29
|
import {
|
|
27
30
|
CHART_COLOR_SCHEMES,
|
|
28
31
|
CHART_LAYOUT_PRESETS,
|
|
@@ -44,7 +47,8 @@ import {
|
|
|
44
47
|
monochromeChartPalette,
|
|
45
48
|
sparklineToSvg,
|
|
46
49
|
summarizeChart
|
|
47
|
-
} from "../chunk-
|
|
50
|
+
} from "../chunk-YUFTVNLX.js";
|
|
51
|
+
import "../chunk-KGRZMHZB.js";
|
|
48
52
|
import {
|
|
49
53
|
CHART_DATE_STEPS,
|
|
50
54
|
MAX_CHART_DATA_LABELS,
|
|
@@ -56,6 +60,9 @@ import {
|
|
|
56
60
|
chartHistogram,
|
|
57
61
|
chartMovingAverage,
|
|
58
62
|
chartQuantile,
|
|
63
|
+
chartRaceMoment,
|
|
64
|
+
chartRaceRanking,
|
|
65
|
+
chartRaceValueAt,
|
|
59
66
|
chartScaleFromPixel,
|
|
60
67
|
chartScaleToPixel,
|
|
61
68
|
chartSerialToDate,
|
|
@@ -84,7 +91,7 @@ import {
|
|
|
84
91
|
suggestChartReferences,
|
|
85
92
|
summarizeChartSeries,
|
|
86
93
|
trendNumberText
|
|
87
|
-
} from "../chunk-
|
|
94
|
+
} from "../chunk-ITANXMZD.js";
|
|
88
95
|
import {
|
|
89
96
|
CHART_AXIS_KINDS,
|
|
90
97
|
CHART_AXIS_SCALES,
|
|
@@ -102,6 +109,7 @@ import {
|
|
|
102
109
|
CHART_PALETTE_SHADES,
|
|
103
110
|
CHART_RADAR_GRATICULES,
|
|
104
111
|
CHART_REFERENCE_AXES,
|
|
112
|
+
CHART_SANKEY_LINK_COLORS,
|
|
105
113
|
CHART_SERIES_TYPES,
|
|
106
114
|
CHART_STACKINGS,
|
|
107
115
|
CHART_TRENDLINE_TYPES,
|
|
@@ -110,8 +118,10 @@ import {
|
|
|
110
118
|
CSS_VARIABLE_CHART_THEME,
|
|
111
119
|
DEFAULT_CHART_PALETTE,
|
|
112
120
|
DEFAULT_CHART_THEME,
|
|
121
|
+
MAX_CHART_LINKS,
|
|
113
122
|
MAX_CHART_TREE_DEPTH,
|
|
114
123
|
MAX_CHART_TREE_NODES,
|
|
124
|
+
addChartLink,
|
|
115
125
|
addChartTreeRow,
|
|
116
126
|
chartColorDistance,
|
|
117
127
|
chartColorInputValue,
|
|
@@ -123,10 +133,13 @@ import {
|
|
|
123
133
|
chartErrorBarsOfType,
|
|
124
134
|
chartFamily,
|
|
125
135
|
chartLikertColor,
|
|
136
|
+
chartLinksFromTable,
|
|
126
137
|
chartLuminance,
|
|
127
138
|
chartPointColor,
|
|
128
139
|
chartRgbToHex,
|
|
129
140
|
chartRgbToOklab,
|
|
141
|
+
chartSankeyNodeValues,
|
|
142
|
+
chartSankeyNodes,
|
|
130
143
|
chartSeriesColor,
|
|
131
144
|
chartSeriesType,
|
|
132
145
|
chartSliceColor,
|
|
@@ -155,6 +168,7 @@ import {
|
|
|
155
168
|
isXyChart,
|
|
156
169
|
normalizeChartAxis,
|
|
157
170
|
normalizeChartErrorBars,
|
|
171
|
+
normalizeChartLinks,
|
|
158
172
|
normalizeChartReferenceBand,
|
|
159
173
|
normalizeChartReferenceLine,
|
|
160
174
|
normalizeChartSpec,
|
|
@@ -165,12 +179,14 @@ import {
|
|
|
165
179
|
parseChartColor,
|
|
166
180
|
parseChartValue,
|
|
167
181
|
removeChartTreeRow,
|
|
182
|
+
setChartLinkField,
|
|
168
183
|
setChartTreeRowName,
|
|
169
184
|
setChartTreeRowValue,
|
|
170
185
|
shadeChartColor,
|
|
171
186
|
simulateChartVision,
|
|
172
187
|
uniqueChartSeriesId
|
|
173
|
-
} from "../chunk-
|
|
188
|
+
} from "../chunk-FR432ROA.js";
|
|
189
|
+
import "../chunk-O6OGNDND.js";
|
|
174
190
|
import "../chunk-II2WIF5Z.js";
|
|
175
191
|
import {
|
|
176
192
|
parseCsv
|
|
@@ -188,11 +204,12 @@ import {
|
|
|
188
204
|
} from "../chunk-CIQIM2IP.js";
|
|
189
205
|
import {
|
|
190
206
|
defineEditorDocument
|
|
191
|
-
} from "../chunk-
|
|
207
|
+
} from "../chunk-XBQX76CT.js";
|
|
192
208
|
import "../chunk-HF5QZS7Z.js";
|
|
193
209
|
import "../chunk-RNSANXPT.js";
|
|
194
|
-
import "../chunk-
|
|
195
|
-
import "../chunk-
|
|
210
|
+
import "../chunk-7272D3K2.js";
|
|
211
|
+
import "../chunk-YBAPT7NL.js";
|
|
212
|
+
import "../chunk-MHLU6GRJ.js";
|
|
196
213
|
import "../chunk-6ONMQ6WF.js";
|
|
197
214
|
|
|
198
215
|
// src/chart/raster.ts
|
|
@@ -383,6 +400,7 @@ function recommendForShape(shape) {
|
|
|
383
400
|
add("area", "Area", "Shows change over time with volume.");
|
|
384
401
|
add("bar", "Column", "Compares each period.");
|
|
385
402
|
if (shape.series > 1) add("area", "Stacked area", "Shows how the parts add up over time.", "stacked");
|
|
403
|
+
if (shape.series >= 5 && shape.points >= 3) add("barrace", "Bar race", "Many series over time: an animated ranking shows who leads when.");
|
|
386
404
|
return out;
|
|
387
405
|
}
|
|
388
406
|
if (shape.series === 1) {
|
|
@@ -417,6 +435,7 @@ function recommendForShape(shape) {
|
|
|
417
435
|
add("bar", "Stacked column", "Shows how the series add up.", "stacked");
|
|
418
436
|
add("line", "Line", "Shows the series in order.");
|
|
419
437
|
if (shape.points >= 3 && shape.series <= 6) add("radar", "Radar", "Compares several measures across the series.");
|
|
438
|
+
if (shape.nonNegative && shape.series >= 5 && shape.points >= 3) add("barrace", "Bar race", "Many series across ordered categories (such as years): an animated ranking shows who leads when.");
|
|
420
439
|
if (shape.nonNegative && shape.series >= 3 && shape.series <= 7) add("likert", "Likert", "Survey answers per question (columns from most negative to most positive): shows agreement against disagreement.");
|
|
421
440
|
}
|
|
422
441
|
return out;
|
|
@@ -462,6 +481,12 @@ function createChartCommands(controller, options = {}) {
|
|
|
462
481
|
if (index < 0 || index >= categories.length || !Number.isInteger(index)) fail(`There is no category ${JSON.stringify(ref)}; expected an index from 0 to ${categories.length - 1} or one of the labels.`);
|
|
463
482
|
return index;
|
|
464
483
|
};
|
|
484
|
+
const raceFrame = (time) => {
|
|
485
|
+
const cats = controller.getSpec().categories;
|
|
486
|
+
const frame = typeof time === "number" ? time : cats.indexOf(time);
|
|
487
|
+
if (!(Number.isFinite(frame) && frame >= 0 && frame <= cats.length - 1)) fail(`There is no time point ${JSON.stringify(time)}; expected an index from 0 to ${Math.max(0, cats.length - 1)} or a category label.`);
|
|
488
|
+
return frame;
|
|
489
|
+
};
|
|
465
490
|
const object = (properties, required) => ({ type: "object", properties, ...required ? { required } : {}, additionalProperties: false });
|
|
466
491
|
return [
|
|
467
492
|
{
|
|
@@ -800,6 +825,131 @@ function createChartCommands(controller, options = {}) {
|
|
|
800
825
|
return { groups: controller.getSpec().tree.map((n) => ({ name: n.name, total: chartTreeTotal(n) })) };
|
|
801
826
|
}
|
|
802
827
|
},
|
|
828
|
+
{
|
|
829
|
+
...base,
|
|
830
|
+
id: `${prefix}setLinks`,
|
|
831
|
+
label: "Replace Flows\u2026",
|
|
832
|
+
keywords: ["chart", "sankey", "flow", "links", "funnel", "budget"],
|
|
833
|
+
description: "Replaces the data with flows between named nodes for a Sankey diagram; [] goes back to plain data.",
|
|
834
|
+
params: object(
|
|
835
|
+
{
|
|
836
|
+
links: { type: "array", items: { type: "object", properties: LINK_PROPS, required: ["source", "target", "value"], additionalProperties: false }, description: "All the flows; [] removes them." },
|
|
837
|
+
nodes: { type: "array", items: { type: "string" }, description: "Node names in stacking order." }
|
|
838
|
+
},
|
|
839
|
+
["links"]
|
|
840
|
+
),
|
|
841
|
+
run: (_ctx, params) => {
|
|
842
|
+
check(controller.setLinks(params.links, { nodes: params.nodes }));
|
|
843
|
+
return { links: controller.getSpec().links, nodes: controller.getSpec().sankeyNodes };
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
...base,
|
|
848
|
+
id: `${prefix}importLinksCsv`,
|
|
849
|
+
label: "Import Flows from CSV\u2026",
|
|
850
|
+
keywords: ["chart", "sankey", "flow", "csv", "import"],
|
|
851
|
+
description: "Replaces the data with flows from CSV / TSV rows of source, target and value.",
|
|
852
|
+
params: object(
|
|
853
|
+
{
|
|
854
|
+
csv: { type: "string", description: "CSV or TSV text." },
|
|
855
|
+
header: { type: ["boolean", "string"], enum: [true, false, "auto"], description: "The first row holds column names (default auto)." }
|
|
856
|
+
},
|
|
857
|
+
["csv"]
|
|
858
|
+
),
|
|
859
|
+
run: (_ctx, params) => {
|
|
860
|
+
check(controller.importLinksTable(parseCsv(params.csv, { delimiter: params.csv.includes(" ") ? " " : "," }), { header: params.header }));
|
|
861
|
+
return { links: controller.getSpec().links };
|
|
862
|
+
}
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
...base,
|
|
866
|
+
id: `${prefix}addLink`,
|
|
867
|
+
label: "Add Flow\u2026",
|
|
868
|
+
keywords: ["chart", "sankey", "flow", "link", "add"],
|
|
869
|
+
description: "Adds a flow between two nodes. Returns its index.",
|
|
870
|
+
params: object(LINK_PROPS, ["source", "target", "value"]),
|
|
871
|
+
run: (_ctx, params) => {
|
|
872
|
+
const result = controller.addLink(params);
|
|
873
|
+
check(result);
|
|
874
|
+
return { index: result.index };
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
...base,
|
|
879
|
+
id: `${prefix}updateLink`,
|
|
880
|
+
label: "Change Flow\u2026",
|
|
881
|
+
keywords: ["chart", "sankey", "flow", "link", "value"],
|
|
882
|
+
description: "Changes a flow by index (color null for the default).",
|
|
883
|
+
params: object({ index: LINK_INDEX, ...LINK_PROPS, color: { type: ["string", "null"], description: "CSS colour, or null for the default." } }, ["index"]),
|
|
884
|
+
run: (_ctx, params) => {
|
|
885
|
+
const { index, ...patch } = params;
|
|
886
|
+
check(controller.updateLink(index, patch));
|
|
887
|
+
return { link: controller.getSpec().links[index] };
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
...base,
|
|
892
|
+
id: `${prefix}removeLink`,
|
|
893
|
+
label: "Delete Flow",
|
|
894
|
+
keywords: ["chart", "sankey", "flow", "link", "remove"],
|
|
895
|
+
description: "Deletes a flow by index.",
|
|
896
|
+
params: object({ index: LINK_INDEX }, ["index"]),
|
|
897
|
+
run: (_ctx, params) => {
|
|
898
|
+
check(controller.removeLink(params.index));
|
|
899
|
+
return { links: controller.getSpec().links };
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
...base,
|
|
904
|
+
id: `${prefix}raceRanking`,
|
|
905
|
+
label: "Bar Race Ranking",
|
|
906
|
+
readOnly: true,
|
|
907
|
+
keywords: ["chart", "race", "ranking", "leader", "time"],
|
|
908
|
+
description: "Returns the top names of a bar race at a moment (default: the one shown).",
|
|
909
|
+
params: object({ time: RACE_TIME }),
|
|
910
|
+
run: (_ctx, params = {}) => {
|
|
911
|
+
const spec = controller.getSpec();
|
|
912
|
+
const frame = params?.time !== void 0 ? raceFrame(params.time) : controller.getState().raceFrame ?? spec.categories.length - 1;
|
|
913
|
+
return { frame, ranking: chartRaceRanking(spec, frame).slice(0, spec.raceTopN).map((e) => ({ rank: e.rank + 1, series: e.seriesId, name: e.name, value: e.value })) };
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
...base,
|
|
918
|
+
id: `${prefix}setRaceFrame`,
|
|
919
|
+
label: "Show Bar Race Moment\u2026",
|
|
920
|
+
keywords: ["chart", "race", "frame", "time", "scrub"],
|
|
921
|
+
description: "Shows the bar race at a time point (null for the last) and stops playback. View state, not an undo step.",
|
|
922
|
+
params: object({ time: { ...RACE_TIME, type: ["string", "number", "null"], description: `${RACE_TIME.description} null shows the last time point.` } }, ["time"]),
|
|
923
|
+
run: (_ctx, params) => {
|
|
924
|
+
const frame = params.time === null ? null : raceFrame(params.time);
|
|
925
|
+
check(controller.setRaceFrame(frame));
|
|
926
|
+
return { frame };
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
...base,
|
|
931
|
+
id: `${prefix}playRace`,
|
|
932
|
+
label: "Play Bar Race",
|
|
933
|
+
keywords: ["chart", "race", "play", "animate"],
|
|
934
|
+
description: "Plays the bar race from the moment shown, one time point every msPerStep ms.",
|
|
935
|
+
params: object({ msPerStep: { type: "number", minimum: 50, maximum: 1e4, description: "Milliseconds per time point (default 1000)." } }),
|
|
936
|
+
run: (_ctx, params = {}) => {
|
|
937
|
+
check(controller.playRace({ msPerStep: params?.msPerStep }));
|
|
938
|
+
return { playing: true };
|
|
939
|
+
}
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
...base,
|
|
943
|
+
id: `${prefix}pauseRace`,
|
|
944
|
+
label: "Pause Bar Race",
|
|
945
|
+
keywords: ["chart", "race", "pause", "stop"],
|
|
946
|
+
description: "Pauses bar race playback where it is.",
|
|
947
|
+
enabled: () => controller.getState().racePlaying || "The race is not playing",
|
|
948
|
+
run: () => {
|
|
949
|
+
controller.pauseRace();
|
|
950
|
+
return { frame: controller.getState().raceFrame };
|
|
951
|
+
}
|
|
952
|
+
},
|
|
803
953
|
{
|
|
804
954
|
...base,
|
|
805
955
|
id: `${prefix}applyPreset`,
|
|
@@ -851,11 +1001,11 @@ function createChartCommands(controller, options = {}) {
|
|
|
851
1001
|
id: `${prefix}importMermaid`,
|
|
852
1002
|
label: "Import Mermaid Chart\u2026",
|
|
853
1003
|
keywords: ["chart", "mermaid", "markdown", "xychart", "pie", "radar", "import"],
|
|
854
|
-
description: "Replaces the chart with Mermaid xychart-beta, pie or
|
|
1004
|
+
description: "Replaces the chart with Mermaid xychart-beta, pie, radar-beta, sankey-beta or treemap-beta text (one undo step). Unreadable lines are listed with line numbers.",
|
|
855
1005
|
params: object({ mermaid: { type: "string", description: "Mermaid text." } }, ["mermaid"]),
|
|
856
1006
|
run: (_ctx, params) => {
|
|
857
1007
|
const read = chartFromMermaid(params.mermaid);
|
|
858
|
-
if (!read.family) fail(read.issues.map((i) => i.message).join(" ") || "Expected Mermaid starting with xychart-beta, pie or
|
|
1008
|
+
if (!read.family) fail(read.issues.map((i) => i.message).join(" ") || "Expected Mermaid starting with xychart-beta, pie, radar-beta, sankey-beta or treemap-beta.");
|
|
859
1009
|
check(controller.update({ ...read.spec }, { label: "Import Mermaid chart" }));
|
|
860
1010
|
return { spec: controller.toJSON(), issues: read.issues };
|
|
861
1011
|
}
|
|
@@ -1366,6 +1516,11 @@ function planChartToolPlayback(tool, args) {
|
|
|
1366
1516
|
case "add_chart_tree_node":
|
|
1367
1517
|
case "update_chart_tree_node":
|
|
1368
1518
|
case "remove_chart_tree_node":
|
|
1519
|
+
case "set_chart_links":
|
|
1520
|
+
case "import_chart_links_csv":
|
|
1521
|
+
case "add_chart_link":
|
|
1522
|
+
case "update_chart_link":
|
|
1523
|
+
case "remove_chart_link":
|
|
1369
1524
|
case "import_chart_csv":
|
|
1370
1525
|
case "import_chart_mermaid":
|
|
1371
1526
|
case "import_chart_ooxml":
|
|
@@ -1392,6 +1547,10 @@ function planChartToolPlayback(tool, args) {
|
|
|
1392
1547
|
return click(control("series.callout"));
|
|
1393
1548
|
case "highlight_chart":
|
|
1394
1549
|
return click(control("highlight"));
|
|
1550
|
+
case "set_chart_race_frame":
|
|
1551
|
+
return click(control("raceFrame"));
|
|
1552
|
+
case "play_chart_race":
|
|
1553
|
+
return click({ kind: "action", id: "race-play" });
|
|
1395
1554
|
case "render_chart":
|
|
1396
1555
|
return [{ type: "target", ref: { kind: "action", id: "download-png" } }];
|
|
1397
1556
|
case "undo_chart":
|
|
@@ -1456,6 +1615,23 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
1456
1615
|
const depth = chartTreeDepth(spec().tree);
|
|
1457
1616
|
return [[...Array.from({ length: depth }, (_, k) => k === 0 ? "Group" : k === depth - 1 ? "Item" : `Level ${k + 1}`), "Value"], ...rows.map((r) => [...r.path, ...Array(depth - r.path.length).fill(""), r.value])];
|
|
1458
1617
|
};
|
|
1618
|
+
const raceFrameOf = (time) => {
|
|
1619
|
+
const cats = spec().categories;
|
|
1620
|
+
const frame = typeof time === "number" ? time : cats.indexOf(time);
|
|
1621
|
+
if (!(Number.isFinite(frame) && frame >= 0 && frame <= cats.length - 1))
|
|
1622
|
+
fail2(`There is no time point ${JSON.stringify(time)}; expected an index from 0 to ${Math.max(0, cats.length - 1)} or one of ${cats.slice(0, 20).map((c) => JSON.stringify(c)).join(", ")}.`);
|
|
1623
|
+
return frame;
|
|
1624
|
+
};
|
|
1625
|
+
const raceAt = (frame) => {
|
|
1626
|
+
const s = spec();
|
|
1627
|
+
const at = frame ?? s.categories.length - 1;
|
|
1628
|
+
return {
|
|
1629
|
+
frame: at,
|
|
1630
|
+
time: s.categories[Math.round(at)] ?? null,
|
|
1631
|
+
ranking: chartRaceRanking(s, at).slice(0, s.raceTopN).map((e) => ({ rank: e.rank + 1, series: e.seriesId, name: e.name, value: e.value }))
|
|
1632
|
+
};
|
|
1633
|
+
};
|
|
1634
|
+
const linksSummary = () => ({ links: spec().links, nodes: spec().sankeyNodes.map((name, i) => ({ name, value: chartSankeyNodeValues(spec().links, spec().sankeyNodes)[i] })) });
|
|
1459
1635
|
const treeSummary = () => ({ tree: spec().tree, groups: spec().tree.map((n) => ({ name: n.name, total: chartTreeTotal(n) })), depth: chartTreeDepth(spec().tree) });
|
|
1460
1636
|
const Parser = () => options.DOMParser ?? globalThis.DOMParser;
|
|
1461
1637
|
const getChart = {
|
|
@@ -1486,14 +1662,16 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
1486
1662
|
properties: {
|
|
1487
1663
|
width: WIDTH,
|
|
1488
1664
|
height: HEIGHT,
|
|
1489
|
-
format: { type: "string", enum: ["svg", "png"], description: "svg (default): the SVG document as text. png: an image to look at (only when the host can rasterize; costs image tokens)." }
|
|
1665
|
+
format: { type: "string", enum: ["svg", "png"], description: "svg (default): the SVG document as text. png: an image to look at (only when the host can rasterize; costs image tokens)." },
|
|
1666
|
+
time: { ...RACE_TIME, description: `Bar race only: the moment to draw (default: the one shown). ${RACE_TIME.description}` }
|
|
1490
1667
|
},
|
|
1491
1668
|
additionalProperties: false
|
|
1492
1669
|
},
|
|
1493
1670
|
annotations: { title: "Render the chart", ...READ },
|
|
1494
|
-
handler: async ({ width = size.width, height = size.height, format = "svg" }) => {
|
|
1671
|
+
handler: async ({ width = size.width, height = size.height, format = "svg", time }) => {
|
|
1495
1672
|
const json = controller.toJSON();
|
|
1496
|
-
const
|
|
1673
|
+
const raceFrame = time !== void 0 ? raceFrameOf(time) : controller.getState().raceFrame;
|
|
1674
|
+
const scene = layoutChart(json, { width, height, selection: null, raceFrame });
|
|
1497
1675
|
const svg = chartSceneToSvg(scene, { ariaLabel: json.title || void 0, description: describeChart(json) });
|
|
1498
1676
|
const warnings = scene.warnings;
|
|
1499
1677
|
if (format === "png") {
|
|
@@ -1566,6 +1744,14 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
1566
1744
|
return { trendline: line, coefficients: fit.coefficients, r2: fit.r2, equation: fit.equation, points: fit.points };
|
|
1567
1745
|
}
|
|
1568
1746
|
};
|
|
1747
|
+
const raceRanking = {
|
|
1748
|
+
name: "get_chart_race_ranking",
|
|
1749
|
+
title: "Read a bar race ranking",
|
|
1750
|
+
description: "Returns who leads a bar race at a moment without changing what is shown: the top raceTopN names with their rank, series id and value (eased between time points). Defaults to the moment shown. Works for any chart, reading categories as time points and series as names.",
|
|
1751
|
+
inputSchema: { type: "object", properties: { time: RACE_TIME }, additionalProperties: false },
|
|
1752
|
+
annotations: { title: "Read a bar race ranking", ...READ },
|
|
1753
|
+
handler: ({ time }) => raceAt(time !== void 0 ? raceFrameOf(time) : controller.getState().raceFrame)
|
|
1754
|
+
};
|
|
1569
1755
|
const listPresets = {
|
|
1570
1756
|
name: "list_chart_presets",
|
|
1571
1757
|
title: "List chart presets",
|
|
@@ -1580,7 +1766,7 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
1580
1766
|
const exportMermaid = {
|
|
1581
1767
|
name: "export_chart_mermaid",
|
|
1582
1768
|
title: "Export chart as Mermaid",
|
|
1583
|
-
description: 'Returns the chart as Mermaid text
|
|
1769
|
+
description: 'Returns the chart as Mermaid text for Markdown or chat: xychart-beta, pie or radar-beta, sankey-beta for Sankey diagrams with flows, treemap-beta for treemaps and sunbursts (with their levels). With meta (default true) one "%% @pixerate {\u2026}" comment keeps what Mermaid cannot express, so import_chart_mermaid gets the chart back exactly.',
|
|
1584
1770
|
inputSchema: { type: "object", properties: { meta: { type: "boolean", description: "Include the round-trip comment (default true)." } }, additionalProperties: false },
|
|
1585
1771
|
annotations: { title: "Export chart as Mermaid", ...READ },
|
|
1586
1772
|
handler: ({ meta = true }) => ({ mermaid: chartToMermaid(controller.toJSON(), { meta }) })
|
|
@@ -1767,13 +1953,13 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
1767
1953
|
const importMermaid = {
|
|
1768
1954
|
name: "import_chart_mermaid",
|
|
1769
1955
|
title: "Import a Mermaid chart",
|
|
1770
|
-
description: 'Replaces the chart with Mermaid xychart-beta, pie or
|
|
1956
|
+
description: 'Replaces the chart with Mermaid xychart-beta, pie, radar-beta, sankey-beta or treemap-beta text as one undo step (a "%% @pixerate" comment from export_chart_mermaid restores everything else). Lines that cannot be read are skipped and listed with their line numbers. Returns the spec.',
|
|
1771
1957
|
inputSchema: { type: "object", properties: { mermaid: { type: "string", description: "Mermaid text, optionally inside a ```mermaid fence." } }, required: ["mermaid"], additionalProperties: false },
|
|
1772
1958
|
annotations: { title: "Import a Mermaid chart", ...DESTRUCTIVE },
|
|
1773
1959
|
handler: ({ mermaid }) => {
|
|
1774
1960
|
const fenced = /```(?:mermaid)?\s*\n([\s\S]*?)```/.exec(mermaid);
|
|
1775
1961
|
const read = chartFromMermaid(fenced ? fenced[1] : mermaid);
|
|
1776
|
-
if (!read.family) fail2(read.issues.map((i) => i.message).join(" ") || "Expected Mermaid starting with xychart-beta, pie or
|
|
1962
|
+
if (!read.family) fail2(read.issues.map((i) => i.message).join(" ") || "Expected Mermaid starting with xychart-beta, pie, radar-beta, sankey-beta or treemap-beta.");
|
|
1777
1963
|
resolve(controller.update({ ...read.spec }, { label: "Import Mermaid chart" }));
|
|
1778
1964
|
return { spec: controller.toJSON(), issues: read.issues };
|
|
1779
1965
|
}
|
|
@@ -1982,6 +2168,105 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
1982
2168
|
return { groups: treeSummary().groups };
|
|
1983
2169
|
}
|
|
1984
2170
|
};
|
|
2171
|
+
const setLinks = {
|
|
2172
|
+
name: "set_chart_links",
|
|
2173
|
+
title: "Replace the flows",
|
|
2174
|
+
description: `Replaces the chart's data with flows between named nodes for a Sankey diagram, as one undo step: each {"source", "target", "value"} with a value above 0. Flows only go forwards (no loops) and a source-target pair appears once. The nodes become the categories and how much passes through each the first series. nodes (optional) sets the stacking order. [] goes back to plain data. Returns the flows and each node's throughput.`,
|
|
2175
|
+
inputSchema: {
|
|
2176
|
+
type: "object",
|
|
2177
|
+
properties: {
|
|
2178
|
+
links: { type: "array", items: { type: "object", properties: LINK_PROPS, required: ["source", "target", "value"], additionalProperties: false }, description: "All the flows; [] removes them." },
|
|
2179
|
+
nodes: { type: "array", items: { type: "string" }, description: "Node names in stacking order (default: as the flows first name them)." }
|
|
2180
|
+
},
|
|
2181
|
+
required: ["links"],
|
|
2182
|
+
additionalProperties: false
|
|
2183
|
+
},
|
|
2184
|
+
annotations: { title: "Replace the flows", ...WRITE_IDEMPOTENT },
|
|
2185
|
+
handler: ({ links, nodes }) => {
|
|
2186
|
+
resolve(controller.setLinks(links, { nodes }));
|
|
2187
|
+
return linksSummary();
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
const importLinksCsv = {
|
|
2191
|
+
name: "import_chart_links_csv",
|
|
2192
|
+
title: "Import flows from CSV",
|
|
2193
|
+
description: "Replaces the data with flows from CSV or TSV rows of source, target and value (as Mermaid sankey-beta and spreadsheets write them). One undo step. Returns the flows and each node's throughput.",
|
|
2194
|
+
inputSchema: {
|
|
2195
|
+
type: "object",
|
|
2196
|
+
properties: {
|
|
2197
|
+
csv: { type: "string", description: 'CSV or TSV text, e.g. "Website,Signup,120\\nSignup,Paid,30".' },
|
|
2198
|
+
header: { type: ["boolean", "string"], enum: [true, false, "auto"], description: "The first row holds column names (default auto: when its third cell is not a number)." }
|
|
2199
|
+
},
|
|
2200
|
+
required: ["csv"],
|
|
2201
|
+
additionalProperties: false
|
|
2202
|
+
},
|
|
2203
|
+
annotations: { title: "Import flows from CSV", ...WRITE },
|
|
2204
|
+
handler: ({ csv, header }) => {
|
|
2205
|
+
resolve(controller.importLinksTable(parseCsv(csv, { delimiter: csv.includes(" ") ? " " : "," }), { header }));
|
|
2206
|
+
return linksSummary();
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
const addLink = {
|
|
2210
|
+
name: "add_chart_link",
|
|
2211
|
+
title: "Add a flow",
|
|
2212
|
+
description: "Adds a flow from one node to another (new names add nodes). Refused when the pair exists already or it would close a loop. Returns its index and the throughput of its two nodes.",
|
|
2213
|
+
inputSchema: { type: "object", properties: LINK_PROPS, required: ["source", "target", "value"], additionalProperties: false },
|
|
2214
|
+
annotations: { title: "Add a flow", ...WRITE },
|
|
2215
|
+
handler: (args) => {
|
|
2216
|
+
const result = resolve(controller.addLink(args));
|
|
2217
|
+
const s = spec();
|
|
2218
|
+
const through = (n) => s.series[0]?.values[s.sankeyNodes.indexOf(n)] ?? 0;
|
|
2219
|
+
const link = s.links[result.index];
|
|
2220
|
+
return { index: result.index, link, nodes: [link.source, link.target].map((name) => ({ name, value: through(name) })) };
|
|
2221
|
+
}
|
|
2222
|
+
};
|
|
2223
|
+
const updateLink = {
|
|
2224
|
+
name: "update_chart_link",
|
|
2225
|
+
title: "Change a flow",
|
|
2226
|
+
description: "Changes a flow by index: its source, target, value or colour (null goes back to the default). Checked like a new flow. Returns the flow as stored.",
|
|
2227
|
+
inputSchema: { type: "object", properties: { index: LINK_INDEX, ...LINK_PROPS, color: { type: ["string", "null"], description: "CSS colour, or null for the default." } }, required: ["index"], additionalProperties: false },
|
|
2228
|
+
annotations: { title: "Change a flow", ...WRITE_IDEMPOTENT },
|
|
2229
|
+
handler: ({ index, ...patch }) => {
|
|
2230
|
+
if (!Object.keys(patch).length) fail2(`Nothing to change: pass source, target, value or color with the index, e.g. {"index": ${index}, "value": 40}.`);
|
|
2231
|
+
resolve(controller.updateLink(index, patch));
|
|
2232
|
+
return { index, link: spec().links[index] };
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
const removeLink = {
|
|
2236
|
+
name: "remove_chart_link",
|
|
2237
|
+
title: "Delete a flow",
|
|
2238
|
+
description: "Deletes a flow by index; the ones after it move up one, and nodes no flow uses any more go. Returns the flows left.",
|
|
2239
|
+
inputSchema: { type: "object", properties: { index: LINK_INDEX }, required: ["index"], additionalProperties: false },
|
|
2240
|
+
annotations: { title: "Delete a flow", ...DESTRUCTIVE },
|
|
2241
|
+
handler: ({ index }) => {
|
|
2242
|
+
resolve(controller.removeLink(index));
|
|
2243
|
+
return { links: spec().links };
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
const setRaceFrame = {
|
|
2247
|
+
name: "set_chart_race_frame",
|
|
2248
|
+
title: "Show a bar race moment",
|
|
2249
|
+
description: "Shows the bar race at a time point (or between two), and stops playback. This is view state like the selection: it is not an undo step and is not saved in the chart. Returns the moment and the ranking shown.",
|
|
2250
|
+
inputSchema: { type: "object", properties: { time: { ...RACE_TIME, type: ["string", "number", "null"], description: `${RACE_TIME.description} null shows the last time point.` } }, required: ["time"], additionalProperties: false },
|
|
2251
|
+
annotations: { title: "Show a bar race moment", ...WRITE_IDEMPOTENT },
|
|
2252
|
+
handler: ({ time }) => {
|
|
2253
|
+
const frame = time === null ? null : raceFrameOf(time);
|
|
2254
|
+
resolve(controller.setRaceFrame(frame));
|
|
2255
|
+
return raceAt(frame);
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
const playRace = {
|
|
2259
|
+
name: "play_chart_race",
|
|
2260
|
+
title: "Play the bar race",
|
|
2261
|
+
description: "Starts playing the bar race for the user from the moment shown (from the start when it is at the end), one time point every msPerStep ms, and returns at once. set_chart_race_frame stops it. Nothing in the chart changes.",
|
|
2262
|
+
inputSchema: { type: "object", properties: { msPerStep: { type: "number", minimum: 50, maximum: 1e4, description: "Milliseconds per time point (default 1000)." } }, additionalProperties: false },
|
|
2263
|
+
annotations: { title: "Play the bar race", ...WRITE },
|
|
2264
|
+
handler: ({ msPerStep }) => {
|
|
2265
|
+
resolve(controller.playRace({ msPerStep }));
|
|
2266
|
+
const n = spec().categories.length;
|
|
2267
|
+
return { playing: true, from: controller.getState().raceFrame ?? 0, durationMs: Math.round((n - 1 - (controller.getState().raceFrame ?? 0)) * (msPerStep ?? 1e3)) };
|
|
2268
|
+
}
|
|
2269
|
+
};
|
|
1985
2270
|
const applyPreset = {
|
|
1986
2271
|
name: "apply_chart_preset",
|
|
1987
2272
|
title: "Apply a chart style",
|
|
@@ -2147,7 +2432,7 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
2147
2432
|
return { redone, ...redone ? { label } : {} };
|
|
2148
2433
|
}
|
|
2149
2434
|
};
|
|
2150
|
-
const reads = [getChart, renderChart, checkColors, recommend, getTrendline, listPresets, exportMermaid, exportOoxml];
|
|
2435
|
+
const reads = [getChart, renderChart, checkColors, recommend, getTrendline, raceRanking, listPresets, exportMermaid, exportOoxml];
|
|
2151
2436
|
const writes = [
|
|
2152
2437
|
updateChart,
|
|
2153
2438
|
setData,
|
|
@@ -2174,6 +2459,13 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
2174
2459
|
addTreeNode,
|
|
2175
2460
|
updateTreeNode,
|
|
2176
2461
|
removeTreeNode,
|
|
2462
|
+
setLinks,
|
|
2463
|
+
importLinksCsv,
|
|
2464
|
+
addLink,
|
|
2465
|
+
updateLink,
|
|
2466
|
+
removeLink,
|
|
2467
|
+
setRaceFrame,
|
|
2468
|
+
playRace,
|
|
2177
2469
|
applyPreset,
|
|
2178
2470
|
transpose,
|
|
2179
2471
|
select,
|
|
@@ -2186,7 +2478,7 @@ function createChartToolProvider(controller, options = {}) {
|
|
|
2186
2478
|
id: options.id ?? "chart",
|
|
2187
2479
|
namespace: options.namespace,
|
|
2188
2480
|
title: "Chart editor",
|
|
2189
|
-
description: "One chart: its spec, data and values, series and categories, trendlines, reference lines and bands, callouts, highlight, grouped data and presets, CSV, Mermaid and OOXML import, rendering and colour checks.",
|
|
2481
|
+
description: "One chart: its spec, data and values, series and categories, trendlines, reference lines and bands, callouts, highlight, grouped data, flows, bar race playback and presets, CSV, Mermaid and OOXML import, rendering and colour checks.",
|
|
2190
2482
|
skill: "pixerate-chart",
|
|
2191
2483
|
tools: () => {
|
|
2192
2484
|
if (readOnlyReason()) return reads;
|
|
@@ -2669,6 +2961,194 @@ function createChartEditorDocument(controller) {
|
|
|
2669
2961
|
}
|
|
2670
2962
|
});
|
|
2671
2963
|
}
|
|
2964
|
+
|
|
2965
|
+
// src/chart/style.ts
|
|
2966
|
+
var CHART_TYPES3 = ["bar", "line", "area", "scatter", "bubble", "pie", "donut", "radar", "waterfall", "histogram", "box", "funnel", "treemap"];
|
|
2967
|
+
var COLOR = { type: "string", minLength: 1, description: 'A CSS colour (hex or rgb()), or a { "$token": ... } reference.' };
|
|
2968
|
+
function chartColors(spec) {
|
|
2969
|
+
const out = [];
|
|
2970
|
+
const visible = spec.series.map((s, i) => ({ s, i })).filter(({ s }) => !s.hidden);
|
|
2971
|
+
if (chartColorsByCategory(spec.type)) {
|
|
2972
|
+
const first = visible[0];
|
|
2973
|
+
spec.categories.forEach((name, index) => {
|
|
2974
|
+
const color = first?.s.pointColors?.[index] || chartSliceColor(spec, index);
|
|
2975
|
+
out.push({ color, label: `Slice "${name}"`, target: first ? { kind: "point", seriesId: first.s.id, index } : { kind: "category", index } });
|
|
2976
|
+
});
|
|
2977
|
+
return out;
|
|
2978
|
+
}
|
|
2979
|
+
for (const { s, i } of visible) {
|
|
2980
|
+
out.push({ color: chartSeriesColor(spec, i), label: `Series "${s.name}"`, target: { kind: "series", seriesId: s.id } });
|
|
2981
|
+
s.pointColors?.forEach((c, index) => {
|
|
2982
|
+
if (c) out.push({ color: c, label: `Point ${index + 1} of "${s.name}"`, target: { kind: "point", seriesId: s.id, index } });
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
return out;
|
|
2986
|
+
}
|
|
2987
|
+
var paletteOf = (profile) => {
|
|
2988
|
+
const palette = profile.palette;
|
|
2989
|
+
if (!palette?.length) throw new Error("the profile has no palette.");
|
|
2990
|
+
return palette;
|
|
2991
|
+
};
|
|
2992
|
+
var paletteOnly = {
|
|
2993
|
+
id: "palette-only",
|
|
2994
|
+
description: "Every series, slice and point colour comes from the house palette (or a lighter or darker shade the chart cycles to once the palette runs out).",
|
|
2995
|
+
optionsSchema: {
|
|
2996
|
+
type: "object",
|
|
2997
|
+
properties: { tolerance: { type: "number", minimum: 0, maximum: 1, description: "Largest OKLab distance still counted as the same colour (default 0.02)." } },
|
|
2998
|
+
additionalProperties: false
|
|
2999
|
+
},
|
|
3000
|
+
run(spec, profile, { tolerance = 0.02 }) {
|
|
3001
|
+
const palette = paletteOf(profile);
|
|
3002
|
+
const allowed = palette.flatMap((c) => CHART_PALETTE_SHADES.map((t) => shadeChartColor(c, t))).map((c) => ({ c, rgb: parseChartColor(c) }));
|
|
3003
|
+
return chartColors(spec).flatMap(({ color, label, target }) => {
|
|
3004
|
+
const rgb = parseChartColor(color);
|
|
3005
|
+
if (!rgb) return [];
|
|
3006
|
+
let best = { c: palette[0], d: Infinity };
|
|
3007
|
+
for (const a of allowed) {
|
|
3008
|
+
if (!a.rgb) continue;
|
|
3009
|
+
const d = chartColorDistance(rgb, a.rgb);
|
|
3010
|
+
if (d < best.d) best = { c: a.c, d };
|
|
3011
|
+
}
|
|
3012
|
+
if (best.d <= tolerance) return [];
|
|
3013
|
+
const nearest = palette.map((c) => ({ c, d: parseChartColor(c) ? chartColorDistance(rgb, parseChartColor(c)) : Infinity })).sort((a, b) => a.d - b.d)[0].c;
|
|
3014
|
+
return [{ severity: "error", message: `${label} uses ${color}, which is not in the house palette; use ${nearest} or another palette colour (${palette.join(", ")}).`, target }];
|
|
3015
|
+
});
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
var distinguishable = {
|
|
3019
|
+
id: "distinguishable",
|
|
3020
|
+
description: "Series (or slice) colours can be told apart with normal vision and with protanopia, deuteranopia and tritanopia.",
|
|
3021
|
+
optionsSchema: {
|
|
3022
|
+
type: "object",
|
|
3023
|
+
properties: { minDistance: { type: "number", minimum: 0, maximum: 1, description: "Smallest OKLab distance between two colours after simulation (default 0.08)." } },
|
|
3024
|
+
additionalProperties: false
|
|
3025
|
+
},
|
|
3026
|
+
run(spec, _profile, { minDistance = 0.08 }) {
|
|
3027
|
+
const colors = chartColors(spec).filter((c) => c.target.kind !== "point" || chartColorsByCategory(spec.type));
|
|
3028
|
+
const result = checkChartColors(colors.map((c) => c.color), { minDistance, minContrast: 0 });
|
|
3029
|
+
const findings = [];
|
|
3030
|
+
for (const [vision, pair] of Object.entries(result.closest)) {
|
|
3031
|
+
if (!pair || pair.distance >= minDistance) continue;
|
|
3032
|
+
const who = vision === "normal" ? "" : ` with ${vision}`;
|
|
3033
|
+
findings.push({
|
|
3034
|
+
severity: "warning",
|
|
3035
|
+
message: `${colors[pair.a].label} (${pair.colors[0]}) and ${colors[pair.b].label} (${pair.colors[1]}) are hard to tell apart${who} (distance ${pair.distance}, want ${minDistance}); give the second another palette colour.`,
|
|
3036
|
+
target: colors[pair.b].target
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
return findings;
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
var minContrast = {
|
|
3043
|
+
id: "min-contrast",
|
|
3044
|
+
description: "Marks stand out from the background: each series or slice colour against the profile theme's background (or surface, default white).",
|
|
3045
|
+
optionsSchema: {
|
|
3046
|
+
type: "object",
|
|
3047
|
+
properties: { ratio: { type: "number", minimum: 1, maximum: 21, description: "Smallest contrast ratio (default 3, the WCAG ratio for graphics)." } },
|
|
3048
|
+
additionalProperties: false
|
|
3049
|
+
},
|
|
3050
|
+
run(spec, profile, { ratio = 3 }) {
|
|
3051
|
+
const theme = profile.theme ?? {};
|
|
3052
|
+
const background = theme.background && theme.background !== "transparent" ? theme.background : theme.surface ?? "#ffffff";
|
|
3053
|
+
return chartColors(spec).flatMap(({ color, label, target }) => {
|
|
3054
|
+
const r = chartContrastRatio(color, background);
|
|
3055
|
+
if (r === null || r >= ratio) return [];
|
|
3056
|
+
return [{ severity: "warning", message: `${label} (${color}) has contrast ${Math.round(r * 100) / 100}:1 against the background ${background}; want at least ${ratio}:1, so use a darker or lighter palette colour.`, target }];
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
};
|
|
3060
|
+
var allowedType = {
|
|
3061
|
+
id: "allowed-type",
|
|
3062
|
+
description: "The chart is one of the chart types the house style allows.",
|
|
3063
|
+
optionsSchema: {
|
|
3064
|
+
type: "object",
|
|
3065
|
+
properties: { types: { type: "array", items: { type: "string", enum: [...CHART_TYPES3] }, minItems: 1, description: "Allowed chart types." } },
|
|
3066
|
+
required: ["types"],
|
|
3067
|
+
additionalProperties: false
|
|
3068
|
+
},
|
|
3069
|
+
run(spec, _profile, { types }) {
|
|
3070
|
+
const allowed = types;
|
|
3071
|
+
if (allowed.includes(spec.type)) return [];
|
|
3072
|
+
return [{ severity: "error", message: `This is a ${spec.type} chart; the house style allows ${allowed.join(", ")}. Change the type with update_chart.`, target: { kind: "chart" } }];
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
3075
|
+
var chartStyleDomain = {
|
|
3076
|
+
id: "chart",
|
|
3077
|
+
title: "Charts",
|
|
3078
|
+
description: "The house palette and theme for charts, which colour schemes and layouts are allowed, defaults for new charts, and the checks to run.",
|
|
3079
|
+
profileSchema: {
|
|
3080
|
+
type: "object",
|
|
3081
|
+
properties: {
|
|
3082
|
+
palette: { type: "array", items: COLOR, minItems: 1, description: "Series colours in order." },
|
|
3083
|
+
colorSchemes: { type: "array", items: { type: "string", enum: CHART_COLOR_SCHEMES.map((s) => s.id) }, description: "Colour scheme ids allowed besides the palette (empty: none)." },
|
|
3084
|
+
layouts: { type: "array", items: { type: "string", enum: CHART_LAYOUT_PRESETS.map((p) => p.id) }, description: "Layout preset ids allowed." },
|
|
3085
|
+
theme: {
|
|
3086
|
+
type: "object",
|
|
3087
|
+
properties: {
|
|
3088
|
+
background: COLOR,
|
|
3089
|
+
surface: COLOR,
|
|
3090
|
+
text: COLOR,
|
|
3091
|
+
mutedText: COLOR,
|
|
3092
|
+
grid: COLOR,
|
|
3093
|
+
axis: COLOR,
|
|
3094
|
+
positive: COLOR,
|
|
3095
|
+
negative: COLOR,
|
|
3096
|
+
fontFamily: { type: "string", minLength: 1, description: "CSS font family." },
|
|
3097
|
+
fontSize: { type: "number", minimum: 6, maximum: 48, description: "Body text size in px." },
|
|
3098
|
+
titleSize: { type: "number", minimum: 6, maximum: 72, description: "Title size in px." }
|
|
3099
|
+
},
|
|
3100
|
+
additionalProperties: false,
|
|
3101
|
+
description: "Colours and type to draw charts with."
|
|
3102
|
+
},
|
|
3103
|
+
defaults: {
|
|
3104
|
+
type: "object",
|
|
3105
|
+
properties: {
|
|
3106
|
+
dataLabels: { type: "boolean", description: "Value labels on." },
|
|
3107
|
+
points: { type: "boolean", description: "Dots on lines." },
|
|
3108
|
+
curve: { type: "string", enum: ["linear", "smooth", "step"], description: "Line interpolation." },
|
|
3109
|
+
stacking: { type: "string", enum: ["none", "stacked", "percent"], description: "Stacking." },
|
|
3110
|
+
orientation: { type: "string", enum: ["vertical", "horizontal"], description: "Bar direction." },
|
|
3111
|
+
labelPosition: { type: "string", enum: ["auto", "inside", "outside", "center"], description: "Bar label position." }
|
|
3112
|
+
},
|
|
3113
|
+
additionalProperties: false,
|
|
3114
|
+
description: "Spec values new charts start from."
|
|
3115
|
+
}
|
|
3116
|
+
},
|
|
3117
|
+
additionalProperties: false
|
|
3118
|
+
},
|
|
3119
|
+
checks: [paletteOnly, distinguishable, minContrast, allowedType]
|
|
3120
|
+
};
|
|
3121
|
+
function chartStylePatch(profile) {
|
|
3122
|
+
const p = profile;
|
|
3123
|
+
return { ...p.defaults ?? {}, ...p.palette?.length ? { palette: [...p.palette] } : {} };
|
|
3124
|
+
}
|
|
3125
|
+
function createChartStyleToolProvider(controller, styles, options = {}) {
|
|
3126
|
+
if (!styles.domains.some((d) => d.id === "chart")) throw new Error('The style resolver has no "chart" domain; add chartStyleDomain to its domains.');
|
|
3127
|
+
const check2 = {
|
|
3128
|
+
name: "check_chart_style",
|
|
3129
|
+
title: "Check chart house style",
|
|
3130
|
+
description: 'Checks the chart against the house style (see get_style with domain "chart"): runs the checks its profile turns on (palette-only, distinguishable, min-contrast, allowed-type). Returns findings with a severity, a sentence saying how to fix it, a target selection (series id or point) for update_chart_series or update_chart, and the layer that asked for it; plus the checks that ran and the ones skipped with the reason. Check after a batch of edits and again after fixing.',
|
|
3131
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
3132
|
+
annotations: { title: "Check chart house style", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
3133
|
+
handler: async () => {
|
|
3134
|
+
const { style, ...report } = await styles.check("chart", controller.getSpec());
|
|
3135
|
+
return {
|
|
3136
|
+
...report,
|
|
3137
|
+
ok: report.findings.length === 0,
|
|
3138
|
+
...style.skills.length ? {} : { note: "No house style applies to charts." },
|
|
3139
|
+
...style.issues.length ? { issues: style.issues } : {}
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
return {
|
|
3144
|
+
id: options.id ?? "chart-style",
|
|
3145
|
+
namespace: options.namespace ?? "chart",
|
|
3146
|
+
title: "Chart house style",
|
|
3147
|
+
description: "Check a chart against the house style.",
|
|
3148
|
+
skill: "pixerate-chart",
|
|
3149
|
+
tools: [check2]
|
|
3150
|
+
};
|
|
3151
|
+
}
|
|
2672
3152
|
export {
|
|
2673
3153
|
CHART_AXIS_KINDS,
|
|
2674
3154
|
CHART_AXIS_SCALES,
|
|
@@ -2695,6 +3175,7 @@ export {
|
|
|
2695
3175
|
CHART_RADAR_GRATICULES,
|
|
2696
3176
|
CHART_RASTER_MAX_SIZE,
|
|
2697
3177
|
CHART_REFERENCE_AXES,
|
|
3178
|
+
CHART_SANKEY_LINK_COLORS,
|
|
2698
3179
|
CHART_SERIES_TYPES,
|
|
2699
3180
|
CHART_STACKINGS,
|
|
2700
3181
|
CHART_TRENDLINE_TYPES,
|
|
@@ -2709,11 +3190,13 @@ export {
|
|
|
2709
3190
|
DEFAULT_CHART_THEME,
|
|
2710
3191
|
MAX_CHART_DATA_LABELS,
|
|
2711
3192
|
MAX_CHART_LINE_POINTS,
|
|
3193
|
+
MAX_CHART_LINKS,
|
|
2712
3194
|
MAX_CHART_MARKERS,
|
|
2713
3195
|
MAX_CHART_TREE_DEPTH,
|
|
2714
3196
|
MAX_CHART_TREE_NODES,
|
|
2715
3197
|
OfficeChartsError,
|
|
2716
3198
|
RENDER_CHART_INSTRUCTIONS,
|
|
3199
|
+
addChartLink,
|
|
2717
3200
|
addChartTreeRow,
|
|
2718
3201
|
analyzeChartTable,
|
|
2719
3202
|
blendChartScenes,
|
|
@@ -2739,6 +3222,7 @@ export {
|
|
|
2739
3222
|
chartHistogram,
|
|
2740
3223
|
chartHitTest,
|
|
2741
3224
|
chartLikertColor,
|
|
3225
|
+
chartLinksFromTable,
|
|
2742
3226
|
chartLuminance,
|
|
2743
3227
|
chartMermaidFamily,
|
|
2744
3228
|
chartMovingAverage,
|
|
@@ -2747,9 +3231,14 @@ export {
|
|
|
2747
3231
|
chartPointRef,
|
|
2748
3232
|
chartPresetPatch,
|
|
2749
3233
|
chartQuantile,
|
|
3234
|
+
chartRaceMoment,
|
|
3235
|
+
chartRaceRanking,
|
|
3236
|
+
chartRaceValueAt,
|
|
2750
3237
|
chartRasterSize,
|
|
2751
3238
|
chartRgbToHex,
|
|
2752
3239
|
chartRgbToOklab,
|
|
3240
|
+
chartSankeyNodeValues,
|
|
3241
|
+
chartSankeyNodes,
|
|
2753
3242
|
chartScaleFromPixel,
|
|
2754
3243
|
chartScaleToPixel,
|
|
2755
3244
|
chartSceneToDomSpec,
|
|
@@ -2760,6 +3249,8 @@ export {
|
|
|
2760
3249
|
chartSliceColor,
|
|
2761
3250
|
chartSpecColorsByCategory,
|
|
2762
3251
|
chartSquarify,
|
|
3252
|
+
chartStyleDomain,
|
|
3253
|
+
chartStylePatch,
|
|
2763
3254
|
chartSupportsTrendlines,
|
|
2764
3255
|
chartTextBaseline,
|
|
2765
3256
|
chartTextOn,
|
|
@@ -2789,6 +3280,7 @@ export {
|
|
|
2789
3280
|
createChartEditorDocument,
|
|
2790
3281
|
createChartRasterizer,
|
|
2791
3282
|
createChartSpec,
|
|
3283
|
+
createChartStyleToolProvider,
|
|
2792
3284
|
createChartToolProvider,
|
|
2793
3285
|
createInstantChartClock,
|
|
2794
3286
|
createOfficeChartToolProvider,
|
|
@@ -2827,6 +3319,7 @@ export {
|
|
|
2827
3319
|
niceStep,
|
|
2828
3320
|
normalizeChartAxis,
|
|
2829
3321
|
normalizeChartErrorBars,
|
|
3322
|
+
normalizeChartLinks,
|
|
2830
3323
|
normalizeChartReferenceBand,
|
|
2831
3324
|
normalizeChartReferenceLine,
|
|
2832
3325
|
normalizeChartSpec,
|
|
@@ -2847,6 +3340,7 @@ export {
|
|
|
2847
3340
|
removeChartTreeRow,
|
|
2848
3341
|
resolveChartSvgPaints,
|
|
2849
3342
|
sameChartSelection,
|
|
3343
|
+
setChartLinkField,
|
|
2850
3344
|
setChartTreeRowName,
|
|
2851
3345
|
setChartTreeRowValue,
|
|
2852
3346
|
shadeChartColor,
|