@classic-homes/slides 0.5.12 → 0.7.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/README.md +1 -1
- package/dist/{chunk-25NZMEQG.js → chunk-HI5ZCAHO.js} +599 -34
- package/dist/chunk-HI5ZCAHO.js.map +1 -0
- package/dist/{chunk-42YDHMNB.js → chunk-ORNJ4CTQ.js} +18 -5
- package/dist/{chunk-42YDHMNB.js.map → chunk-ORNJ4CTQ.js.map} +1 -1
- package/dist/{chunk-6CYZI27I.js → chunk-UKRX7A2V.js} +2 -2
- package/dist/cli.js +4 -4
- package/dist/{echarts-render-2QVSBZ6V.js → echarts-render-GDCT5QAF.js} +17 -3
- package/dist/echarts-render-GDCT5QAF.js.map +1 -0
- package/dist/{images-8N30SsCW.d.ts → images-rkJZtnjl.d.ts} +32 -3
- package/dist/index.d.ts +29 -8
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/{mcp-P3CPIO3H.js → mcp-MG2E7J3I.js} +4 -4
- package/dist/node.d.ts +2 -2
- package/dist/node.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-25NZMEQG.js.map +0 -1
- package/dist/echarts-render-2QVSBZ6V.js.map +0 -1
- /package/dist/{chunk-6CYZI27I.js.map → chunk-UKRX7A2V.js.map} +0 -0
- /package/dist/{mcp-P3CPIO3H.js.map → mcp-MG2E7J3I.js.map} +0 -0
|
@@ -241,15 +241,40 @@ var CHART_TYPES = [
|
|
|
241
241
|
"funnel",
|
|
242
242
|
"gauge",
|
|
243
243
|
"scatter",
|
|
244
|
-
"radar"
|
|
244
|
+
"radar",
|
|
245
|
+
"percent-column",
|
|
246
|
+
"percent-bar",
|
|
247
|
+
"combo",
|
|
248
|
+
"waterfall",
|
|
249
|
+
"heatmap",
|
|
250
|
+
"dumbbell",
|
|
251
|
+
"bullet",
|
|
252
|
+
"treemap",
|
|
253
|
+
"ring",
|
|
254
|
+
"sankey",
|
|
255
|
+
"pictogram"
|
|
256
|
+
];
|
|
257
|
+
var SINGLE_SERIES_CHARTS = [
|
|
258
|
+
"pie",
|
|
259
|
+
"donut",
|
|
260
|
+
"funnel",
|
|
261
|
+
"gauge",
|
|
262
|
+
"waterfall",
|
|
263
|
+
"treemap",
|
|
264
|
+
"ring",
|
|
265
|
+
"pictogram"
|
|
245
266
|
];
|
|
246
267
|
var ChartSeries = z3.object({
|
|
247
268
|
name: z3.string().min(1).max(40),
|
|
248
|
-
values: z3.array(z3.number()).min(1).max(40)
|
|
269
|
+
values: z3.array(z3.number()).min(1).max(40),
|
|
270
|
+
as: z3.enum(["bar", "line"]).optional().describe(
|
|
271
|
+
"combo only: draw this series as bars or a line. Default: first series bars, the rest lines."
|
|
272
|
+
),
|
|
273
|
+
axis: z3.enum(["primary", "secondary"]).optional().describe("combo only: secondary puts this series on a right-hand axis with its own scale.")
|
|
249
274
|
});
|
|
250
275
|
var ChartSpec = z3.object({
|
|
251
276
|
type: z3.enum(CHART_TYPES).describe(
|
|
252
|
-
"column/bar compare categories; stacked-* show parts of a whole per category; line/area show change over time; pie/donut show shares (one series); funnel shows drop-off; gauge shows one value against a max; scatter pairs two series; radar compares profiles."
|
|
277
|
+
"column/bar compare categories; stacked-* show parts of a whole per category; percent-column/percent-bar show each category as 100%; line/area show change over time; combo mixes bars and lines (optionally on two axes); pie/donut show shares (one series); treemap shows shares of many categories; funnel shows drop-off; waterfall walks from a start to an end through gains and losses; gauge shows one value against a max; ring shows each label as a progress ring against max; bullet compares actual (series 1) with target (series 2); dumbbell compares two or more points per category (before/after); heatmap colours a rows \xD7 columns grid (labels are rows, series are columns); sankey shows flows (labels are sources, series names are destinations); pictogram counts in house icons; scatter pairs two series; radar compares profiles."
|
|
253
278
|
),
|
|
254
279
|
labels: z3.array(z3.string().max(40)).min(1).max(40).describe("Category labels (x-axis, slices, stages). One per value."),
|
|
255
280
|
series: z3.array(ChartSeries).min(1).max(6).describe(
|
|
@@ -261,7 +286,14 @@ var ChartSpec = z3.object({
|
|
|
261
286
|
legend: z3.enum(["auto", "show", "hide"]).default("auto"),
|
|
262
287
|
labelsOnData: z3.boolean().default(true).describe("Print values on bars, slices and points."),
|
|
263
288
|
highlight: z3.string().optional().describe("A label to emphasise; other categories are muted."),
|
|
264
|
-
max: z3.number().optional().describe("Axis or
|
|
289
|
+
max: z3.number().optional().describe("Axis, gauge, ring or bullet maximum."),
|
|
290
|
+
secondaryFormat: z3.enum(["number", "percent", "currency", "compact"]).optional().describe("combo only: how the secondary axis values are shown (default: format)."),
|
|
291
|
+
totals: z3.array(z3.string().max(40)).max(40).optional().describe(
|
|
292
|
+
'waterfall only: labels drawn as running-total bars (e.g. "Subtotal", "End"); their values are ignored.'
|
|
293
|
+
),
|
|
294
|
+
perIcon: z3.number().positive().optional().describe(
|
|
295
|
+
"pictogram only: what one house icon stands for (default: a round step, \u2264 20 icons)."
|
|
296
|
+
)
|
|
265
297
|
}).strict();
|
|
266
298
|
function chartIssues(spec) {
|
|
267
299
|
const out = [];
|
|
@@ -275,8 +307,69 @@ function chartIssues(spec) {
|
|
|
275
307
|
if (spec.type === "scatter" && spec.series.length < 2)
|
|
276
308
|
out.push("Scatter needs two series (x and y).");
|
|
277
309
|
if (spec.type === "gauge" && spec.max === void 0) out.push("Gauge needs a max.");
|
|
310
|
+
if (spec.type === "ring" && ringMax(spec) === void 0) {
|
|
311
|
+
out.push("Ring needs a max (or format percent, which defaults to 100).");
|
|
312
|
+
}
|
|
313
|
+
if (spec.type === "dumbbell" && spec.series.length < 2) {
|
|
314
|
+
out.push("Dumbbell needs at least two series (e.g. before and after).");
|
|
315
|
+
}
|
|
316
|
+
if (["treemap", "ring", "pictogram", "sankey"].includes(spec.type)) {
|
|
317
|
+
const used = spec.type === "sankey" ? spec.series : spec.series.slice(0, 1);
|
|
318
|
+
if (used.some((s) => s.values.some((v) => v < 0))) {
|
|
319
|
+
out.push(`${spec.type} values can't be negative.`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (spec.type === "waterfall") {
|
|
323
|
+
for (const t of spec.totals ?? []) {
|
|
324
|
+
if (!spec.labels.includes(t)) out.push(`Total "${t}" isn't one of the labels.`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (spec.type === "sankey") out.push(...sankeyIssues(spec));
|
|
328
|
+
if (spec.type === "pictogram" && spec.perIcon !== void 0) {
|
|
329
|
+
const most = Math.round(Math.max(...spec.series[0].values) / spec.perIcon);
|
|
330
|
+
if (most > 60)
|
|
331
|
+
out.push(`perIcon ${spec.perIcon} draws ${most} icons in a row; use a larger perIcon.`);
|
|
332
|
+
}
|
|
278
333
|
return out;
|
|
279
334
|
}
|
|
335
|
+
function ringMax(spec) {
|
|
336
|
+
return spec.max ?? (spec.format === "percent" ? 100 : void 0);
|
|
337
|
+
}
|
|
338
|
+
function sankeyIssues(spec) {
|
|
339
|
+
const out = [];
|
|
340
|
+
const seen = /* @__PURE__ */ new Set();
|
|
341
|
+
for (const l of spec.labels) {
|
|
342
|
+
if (seen.has(l)) out.push(`Source "${l}" appears twice; combine the rows.`);
|
|
343
|
+
seen.add(l);
|
|
344
|
+
}
|
|
345
|
+
const edges = sankeyLinks(spec);
|
|
346
|
+
if (!edges.length) out.push("Sankey needs at least one flow above zero.");
|
|
347
|
+
for (const e of edges) {
|
|
348
|
+
if (e.source === e.target) out.push(`"${e.source}" can't flow to itself.`);
|
|
349
|
+
}
|
|
350
|
+
const next = /* @__PURE__ */ new Map();
|
|
351
|
+
for (const e of edges) {
|
|
352
|
+
if (e.source !== e.target) next.set(e.source, [...next.get(e.source) ?? [], e.target]);
|
|
353
|
+
}
|
|
354
|
+
const state = /* @__PURE__ */ new Map();
|
|
355
|
+
const cyclic = (n) => {
|
|
356
|
+
if (state.get(n) === "open") return true;
|
|
357
|
+
if (state.get(n) === "done") return false;
|
|
358
|
+
state.set(n, "open");
|
|
359
|
+
const hit = (next.get(n) ?? []).some(cyclic);
|
|
360
|
+
state.set(n, "done");
|
|
361
|
+
return hit;
|
|
362
|
+
};
|
|
363
|
+
if (edges.some((e) => cyclic(e.source))) {
|
|
364
|
+
out.push("Flows loop back on themselves; a sankey must only flow one way.");
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
function sankeyLinks(spec) {
|
|
369
|
+
return spec.labels.flatMap(
|
|
370
|
+
(source, i) => spec.series.filter((s) => (s.values[i] ?? 0) > 0).map((s) => ({ source, target: s.name, value: s.values[i] }))
|
|
371
|
+
);
|
|
372
|
+
}
|
|
280
373
|
function chartWarnings(spec) {
|
|
281
374
|
const out = [];
|
|
282
375
|
if (["pie", "donut"].includes(spec.type) && spec.labels.length > 7) {
|
|
@@ -284,10 +377,49 @@ function chartWarnings(spec) {
|
|
|
284
377
|
`${spec.labels.length} slices is hard to read; group small ones into "Other" or use a bar chart.`
|
|
285
378
|
);
|
|
286
379
|
}
|
|
287
|
-
if (
|
|
380
|
+
if (SINGLE_SERIES_CHARTS.includes(spec.type) && spec.series.length > 1) {
|
|
288
381
|
out.push(`${spec.type} charts only show the first series.`);
|
|
289
382
|
}
|
|
290
|
-
if (spec.
|
|
383
|
+
if (spec.type === "bullet" && spec.series.length > 2) {
|
|
384
|
+
out.push("Bullet charts show series 1 as actual and series 2 as target; the rest are ignored.");
|
|
385
|
+
}
|
|
386
|
+
if (spec.type === "ring" && spec.labels.length > 4) {
|
|
387
|
+
out.push("More than 4 rings gets small; show the rest in a table or bar chart.");
|
|
388
|
+
}
|
|
389
|
+
if (spec.type === "treemap" && spec.labels.length > 20) {
|
|
390
|
+
out.push('More than 20 boxes is hard to label; group small ones into "Other".');
|
|
391
|
+
}
|
|
392
|
+
if (spec.type === "heatmap" && spec.series.length > 12) {
|
|
393
|
+
out.push("More than 12 columns crowds the heatmap.");
|
|
394
|
+
}
|
|
395
|
+
if (spec.type === "sankey" && (/* @__PURE__ */ new Set([...spec.labels, ...spec.series.map((s) => s.name)])).size > 12) {
|
|
396
|
+
out.push("More than 12 nodes makes a sankey hard to follow.");
|
|
397
|
+
}
|
|
398
|
+
if (spec.type === "pictogram" && spec.labels.length > 8) {
|
|
399
|
+
out.push("More than 8 rows of icons gets cramped; use a bar chart.");
|
|
400
|
+
}
|
|
401
|
+
if (spec.type !== "combo" && spec.series.some((s) => s.as || s.axis)) {
|
|
402
|
+
out.push('Series "as" and "axis" only apply to combo charts.');
|
|
403
|
+
}
|
|
404
|
+
if (spec.type !== "combo" && spec.secondaryFormat) {
|
|
405
|
+
out.push("secondaryFormat only applies to combo charts.");
|
|
406
|
+
}
|
|
407
|
+
if (spec.type !== "waterfall" && spec.totals?.length) {
|
|
408
|
+
out.push("totals only apply to waterfall charts.");
|
|
409
|
+
}
|
|
410
|
+
if (spec.type !== "pictogram" && spec.perIcon !== void 0) {
|
|
411
|
+
out.push("perIcon only applies to pictogram charts.");
|
|
412
|
+
}
|
|
413
|
+
if (spec.labels.length > 16 && [
|
|
414
|
+
"column",
|
|
415
|
+
"bar",
|
|
416
|
+
"stacked-column",
|
|
417
|
+
"stacked-bar",
|
|
418
|
+
"percent-column",
|
|
419
|
+
"percent-bar",
|
|
420
|
+
"combo",
|
|
421
|
+
"waterfall"
|
|
422
|
+
].includes(spec.type)) {
|
|
291
423
|
out.push("More than 16 categories crowds the axis; consider a line chart or fewer categories.");
|
|
292
424
|
}
|
|
293
425
|
return out;
|
|
@@ -320,6 +452,35 @@ function chartColors(palette) {
|
|
|
320
452
|
var MUTED = "#c9ccc6";
|
|
321
453
|
var TEXT = "#4f4f4f";
|
|
322
454
|
var AXIS = "#d1d5db";
|
|
455
|
+
var TRACK = "#e5e7eb";
|
|
456
|
+
var SPLIT = "#eef0ec";
|
|
457
|
+
var HOUSE = "path://M12 2L1.5 11H5v11h5.5v-7h3v7H19V11h3.5z";
|
|
458
|
+
function mix(a, b, t) {
|
|
459
|
+
const ch = (hex, i) => parseInt(hex.slice(1 + i * 2, 3 + i * 2), 16);
|
|
460
|
+
const out = [0, 1, 2].map((i) => Math.round(ch(a, i) + (ch(b, i) - ch(a, i)) * t));
|
|
461
|
+
return `#${out.map((n) => n.toString(16).padStart(2, "0")).join("")}`;
|
|
462
|
+
}
|
|
463
|
+
function niceCeil(v, steps = [1, 2, 2.5, 5, 10]) {
|
|
464
|
+
if (v <= 0) return 1;
|
|
465
|
+
const pow = 10 ** Math.floor(Math.log10(v));
|
|
466
|
+
return (steps.find((m) => m * pow >= v) ?? 10) * pow;
|
|
467
|
+
}
|
|
468
|
+
function toPercent(spec) {
|
|
469
|
+
const totals = spec.labels.map(
|
|
470
|
+
(_, i) => spec.series.reduce((sum, s) => sum + Math.abs(s.values[i] ?? 0), 0)
|
|
471
|
+
);
|
|
472
|
+
return {
|
|
473
|
+
...spec,
|
|
474
|
+
type: spec.type === "percent-bar" ? "stacked-bar" : "stacked-column",
|
|
475
|
+
format: "percent",
|
|
476
|
+
unit: void 0,
|
|
477
|
+
max: 100,
|
|
478
|
+
series: spec.series.map((s) => ({
|
|
479
|
+
...s,
|
|
480
|
+
values: s.values.map((v, i) => totals[i] ? Math.abs(v) / totals[i] * 100 : 0)
|
|
481
|
+
}))
|
|
482
|
+
};
|
|
483
|
+
}
|
|
323
484
|
function chartOption(spec, palette) {
|
|
324
485
|
const colors3 = chartColors(palette);
|
|
325
486
|
const fmt = (v) => formatValue(v, spec);
|
|
@@ -339,6 +500,363 @@ function chartOption(spec, palette) {
|
|
|
339
500
|
fontSize: 13,
|
|
340
501
|
formatter: (p) => fmt(p.value)
|
|
341
502
|
};
|
|
503
|
+
const categoryAxis = (gap) => ({
|
|
504
|
+
type: "category",
|
|
505
|
+
data: spec.labels,
|
|
506
|
+
axisLine: { lineStyle: { color: AXIS } },
|
|
507
|
+
axisTick: { show: false },
|
|
508
|
+
axisLabel: { color: TEXT, fontSize: 14, interval: 0, hideOverlap: true },
|
|
509
|
+
boundaryGap: gap
|
|
510
|
+
});
|
|
511
|
+
const valueAxis = (s = spec) => ({
|
|
512
|
+
type: "value",
|
|
513
|
+
max: spec.max,
|
|
514
|
+
axisLine: { show: false },
|
|
515
|
+
splitLine: { lineStyle: { color: SPLIT } },
|
|
516
|
+
axisLabel: {
|
|
517
|
+
color: "#7a7a7a",
|
|
518
|
+
fontSize: 13,
|
|
519
|
+
formatter: (v) => formatValue(v, { ...s, unit: void 0 })
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
const rowAxis = () => ({ ...categoryAxis(true), inverse: true });
|
|
523
|
+
if (spec.type === "percent-column" || spec.type === "percent-bar") {
|
|
524
|
+
return chartOption(toPercent(spec), palette);
|
|
525
|
+
}
|
|
526
|
+
if (spec.type === "waterfall") {
|
|
527
|
+
const totals = new Set(spec.totals ?? []);
|
|
528
|
+
let running = 0;
|
|
529
|
+
const steps = spec.labels.map((name, i) => {
|
|
530
|
+
if (totals.has(name)) return { name, base: 0, delta: running, total: true };
|
|
531
|
+
const delta = first.values[i] ?? 0;
|
|
532
|
+
const step = { name, base: running, delta, total: false };
|
|
533
|
+
running += delta;
|
|
534
|
+
return step;
|
|
535
|
+
});
|
|
536
|
+
const stack = { type: "bar", stack: "waterfall", stackStrategy: "all", barMaxWidth: 56 };
|
|
537
|
+
return {
|
|
538
|
+
...base,
|
|
539
|
+
legend: { show: false },
|
|
540
|
+
grid: { left: 16, right: 24, top: 28, bottom: 12, containLabel: true },
|
|
541
|
+
xAxis: categoryAxis(true),
|
|
542
|
+
yAxis: valueAxis(),
|
|
543
|
+
series: [
|
|
544
|
+
// Invisible spacer lifts each step to the running total.
|
|
545
|
+
{
|
|
546
|
+
...stack,
|
|
547
|
+
silent: true,
|
|
548
|
+
itemStyle: { color: "transparent" },
|
|
549
|
+
data: steps.map((s) => s.base)
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
...stack,
|
|
553
|
+
data: steps.map((s) => ({
|
|
554
|
+
value: s.delta,
|
|
555
|
+
itemStyle: {
|
|
556
|
+
color: muted(s.name, s.total ? colors3[2] : s.delta >= 0 ? colors3[1] : colors3[0]),
|
|
557
|
+
borderRadius: 3
|
|
558
|
+
},
|
|
559
|
+
label: { position: s.delta < 0 ? "bottom" : "top" }
|
|
560
|
+
})),
|
|
561
|
+
label: {
|
|
562
|
+
...labelStyle,
|
|
563
|
+
formatter: (p) => {
|
|
564
|
+
const s = steps[p.dataIndex];
|
|
565
|
+
if (s.total || p.dataIndex === 0) return fmt(s.delta);
|
|
566
|
+
return `${s.delta < 0 ? "\u2212" : "+"}${fmt(Math.abs(s.delta))}`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
]
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
if (spec.type === "heatmap") {
|
|
574
|
+
const all = spec.series.flatMap((s) => s.values);
|
|
575
|
+
const lo = Math.min(...all);
|
|
576
|
+
const span = (spec.max ?? Math.max(...all)) - lo || 1;
|
|
577
|
+
const light = mix(colors3[1], "#ffffff", 0.88);
|
|
578
|
+
return {
|
|
579
|
+
...base,
|
|
580
|
+
legend: { show: false },
|
|
581
|
+
// ECharts heatmaps need a visualMap; ours is hidden (cells carry their values).
|
|
582
|
+
visualMap: {
|
|
583
|
+
show: false,
|
|
584
|
+
min: lo,
|
|
585
|
+
max: lo + span,
|
|
586
|
+
dimension: 2,
|
|
587
|
+
inRange: { color: [light, colors3[1], colors3[2]] }
|
|
588
|
+
},
|
|
589
|
+
grid: { left: 16, right: 16, top: 36, bottom: 8, containLabel: true },
|
|
590
|
+
xAxis: {
|
|
591
|
+
type: "category",
|
|
592
|
+
data: spec.series.map((s) => s.name),
|
|
593
|
+
position: "top",
|
|
594
|
+
axisLine: { show: false },
|
|
595
|
+
axisTick: { show: false },
|
|
596
|
+
axisLabel: { color: TEXT, fontSize: 14, interval: 0 }
|
|
597
|
+
},
|
|
598
|
+
yAxis: { ...rowAxis(), axisLine: { show: false } },
|
|
599
|
+
series: [
|
|
600
|
+
{
|
|
601
|
+
type: "heatmap",
|
|
602
|
+
itemStyle: { borderColor: "#fff", borderWidth: 3 },
|
|
603
|
+
data: spec.labels.flatMap(
|
|
604
|
+
(_, y) => spec.series.map((s, x) => {
|
|
605
|
+
const v = s.values[y] ?? 0;
|
|
606
|
+
const t = Math.min(1, Math.max(0, (v - lo) / span));
|
|
607
|
+
return {
|
|
608
|
+
value: [x, y, v],
|
|
609
|
+
label: { color: t > 0.3 ? "#fff" : TEXT }
|
|
610
|
+
};
|
|
611
|
+
})
|
|
612
|
+
),
|
|
613
|
+
label: {
|
|
614
|
+
show: spec.labelsOnData,
|
|
615
|
+
fontSize: 14,
|
|
616
|
+
formatter: (p) => fmt(p.value[2])
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
]
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
if (spec.type === "dumbbell") {
|
|
623
|
+
const lows = spec.labels.map((_, i) => Math.min(...spec.series.map((s) => s.values[i] ?? 0)));
|
|
624
|
+
const highs = spec.labels.map((_, i) => Math.max(...spec.series.map((s) => s.values[i] ?? 0)));
|
|
625
|
+
const bar = { type: "bar", stack: "span", stackStrategy: "all", silent: true, barWidth: 4 };
|
|
626
|
+
return {
|
|
627
|
+
...base,
|
|
628
|
+
legend: spec.legend === "hide" ? { show: false } : { ...base.legend, show: true, data: spec.series.map((s) => s.name) },
|
|
629
|
+
grid: { left: 16, right: 90, top: 48, bottom: 12, containLabel: true },
|
|
630
|
+
xAxis: { ...valueAxis(), scale: true },
|
|
631
|
+
yAxis: rowAxis(),
|
|
632
|
+
series: [
|
|
633
|
+
// The connector: an invisible spacer to the low dot, then a thin bar to the high one.
|
|
634
|
+
{ ...bar, itemStyle: { color: "transparent" }, data: lows },
|
|
635
|
+
{ ...bar, itemStyle: { color: AXIS }, data: highs.map((h, i) => h - lows[i]) },
|
|
636
|
+
...spec.series.map((s, si) => ({
|
|
637
|
+
type: "scatter",
|
|
638
|
+
name: s.name,
|
|
639
|
+
symbolSize: 18,
|
|
640
|
+
z: 3,
|
|
641
|
+
itemStyle: { color: colors3[si % colors3.length], opacity: 1 },
|
|
642
|
+
data: s.values.map((v, i) => ({
|
|
643
|
+
value: [v, i],
|
|
644
|
+
itemStyle: { color: muted(spec.labels[i], colors3[si % colors3.length]), opacity: 1 },
|
|
645
|
+
label: {
|
|
646
|
+
position: lows[i] === highs[i] ? "top" : v === lows[i] ? "left" : v === highs[i] ? "right" : "top"
|
|
647
|
+
}
|
|
648
|
+
})),
|
|
649
|
+
label: {
|
|
650
|
+
...labelStyle,
|
|
651
|
+
distance: 10,
|
|
652
|
+
formatter: (p) => fmt(p.value[0])
|
|
653
|
+
}
|
|
654
|
+
}))
|
|
655
|
+
]
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
if (spec.type === "bullet") {
|
|
659
|
+
const [actual, target] = spec.series;
|
|
660
|
+
const top = spec.max ?? niceCeil(Math.max(...actual.values, ...target?.values ?? []) * 1.05);
|
|
661
|
+
const showTarget = Boolean(target) && spec.legend !== "hide";
|
|
662
|
+
return {
|
|
663
|
+
...base,
|
|
664
|
+
legend: showTarget || spec.legend === "show" ? { ...base.legend, show: true, data: spec.series.slice(0, 2).map((s) => s.name) } : { show: false },
|
|
665
|
+
grid: { left: 16, right: 40, top: showTarget ? 48 : 20, bottom: 12, containLabel: true },
|
|
666
|
+
xAxis: { ...valueAxis(), max: top },
|
|
667
|
+
yAxis: rowAxis(),
|
|
668
|
+
series: [
|
|
669
|
+
{
|
|
670
|
+
type: "bar",
|
|
671
|
+
name: actual.name,
|
|
672
|
+
barWidth: 16,
|
|
673
|
+
showBackground: true,
|
|
674
|
+
backgroundStyle: { color: TRACK, borderRadius: 3 },
|
|
675
|
+
data: actual.values.map((v, i) => ({
|
|
676
|
+
value: v,
|
|
677
|
+
itemStyle: { color: muted(spec.labels[i], colors3[0]), borderRadius: 3 }
|
|
678
|
+
})),
|
|
679
|
+
label: { ...labelStyle, position: "right", distance: 10 }
|
|
680
|
+
},
|
|
681
|
+
...target ? [
|
|
682
|
+
{
|
|
683
|
+
type: "scatter",
|
|
684
|
+
name: target.name,
|
|
685
|
+
symbol: "rect",
|
|
686
|
+
symbolSize: [5, 34],
|
|
687
|
+
z: 3,
|
|
688
|
+
itemStyle: { color: colors3[2] },
|
|
689
|
+
data: target.values.map((v, i) => [v, i])
|
|
690
|
+
}
|
|
691
|
+
] : []
|
|
692
|
+
]
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
if (spec.type === "treemap") {
|
|
696
|
+
return {
|
|
697
|
+
...base,
|
|
698
|
+
legend: { show: false },
|
|
699
|
+
series: [
|
|
700
|
+
{
|
|
701
|
+
type: "treemap",
|
|
702
|
+
left: 0,
|
|
703
|
+
right: 0,
|
|
704
|
+
top: 0,
|
|
705
|
+
bottom: 0,
|
|
706
|
+
roam: false,
|
|
707
|
+
nodeClick: false,
|
|
708
|
+
breadcrumb: { show: false },
|
|
709
|
+
itemStyle: { borderColor: "#fff", borderWidth: 3, gapWidth: 3 },
|
|
710
|
+
data: spec.labels.map((name, i) => ({
|
|
711
|
+
name,
|
|
712
|
+
value: first.values[i],
|
|
713
|
+
itemStyle: { color: muted(name, colors3[i % colors3.length]) }
|
|
714
|
+
})),
|
|
715
|
+
label: {
|
|
716
|
+
show: true,
|
|
717
|
+
color: "#fff",
|
|
718
|
+
fontSize: 15,
|
|
719
|
+
overflow: "truncate",
|
|
720
|
+
formatter: (p) => spec.labelsOnData ? `${p.name}
|
|
721
|
+
${fmt(p.value)}` : p.name
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
]
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
if (spec.type === "ring") {
|
|
728
|
+
const top = ringMax(spec) ?? 100;
|
|
729
|
+
const n = Math.min(spec.labels.length, 4);
|
|
730
|
+
const outer = [72, 64, 46, 34][n - 1];
|
|
731
|
+
const size = [44, 40, 30, 24][n - 1];
|
|
732
|
+
const plain2 = (s) => s.replace(/[{}|]/g, "");
|
|
733
|
+
return {
|
|
734
|
+
...base,
|
|
735
|
+
legend: { show: false },
|
|
736
|
+
series: spec.labels.slice(0, n).map((name, i) => {
|
|
737
|
+
const v = first.values[i] ?? 0;
|
|
738
|
+
return {
|
|
739
|
+
type: "pie",
|
|
740
|
+
silent: true,
|
|
741
|
+
startAngle: 90,
|
|
742
|
+
radius: [`${outer - 12}%`, `${outer}%`],
|
|
743
|
+
center: [`${(i + 0.5) / n * 100}%`, "50%"],
|
|
744
|
+
labelLine: { show: false },
|
|
745
|
+
data: [
|
|
746
|
+
{
|
|
747
|
+
value: Math.min(Math.max(v, 0), top),
|
|
748
|
+
name,
|
|
749
|
+
itemStyle: { color: muted(name, colors3[i % colors3.length]) },
|
|
750
|
+
label: {
|
|
751
|
+
show: true,
|
|
752
|
+
position: "center",
|
|
753
|
+
formatter: `{v|${plain2(fmt(v))}}
|
|
754
|
+
{l|${plain2(name)}}`,
|
|
755
|
+
rich: {
|
|
756
|
+
v: { fontSize: size, fontWeight: 600, color: colors3[2], lineHeight: size * 1.2 },
|
|
757
|
+
l: { fontSize: 15, color: TEXT, lineHeight: 22 }
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
value: Math.max(0, top - v),
|
|
763
|
+
name: "",
|
|
764
|
+
itemStyle: { color: TRACK },
|
|
765
|
+
label: { show: false }
|
|
766
|
+
}
|
|
767
|
+
]
|
|
768
|
+
};
|
|
769
|
+
})
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
if (spec.type === "sankey") {
|
|
773
|
+
const nodes = [.../* @__PURE__ */ new Set([...spec.labels, ...spec.series.map((s) => s.name)])];
|
|
774
|
+
return {
|
|
775
|
+
...base,
|
|
776
|
+
legend: { show: false },
|
|
777
|
+
series: [
|
|
778
|
+
{
|
|
779
|
+
type: "sankey",
|
|
780
|
+
left: 16,
|
|
781
|
+
right: 150,
|
|
782
|
+
top: 16,
|
|
783
|
+
bottom: 16,
|
|
784
|
+
nodeWidth: 18,
|
|
785
|
+
nodeGap: 14,
|
|
786
|
+
draggable: false,
|
|
787
|
+
emphasis: { disabled: true },
|
|
788
|
+
data: nodes.map((name, i) => ({
|
|
789
|
+
name,
|
|
790
|
+
itemStyle: { color: muted(name, colors3[i % colors3.length]), borderWidth: 0 }
|
|
791
|
+
})),
|
|
792
|
+
links: sankeyLinks(spec),
|
|
793
|
+
lineStyle: { color: "source", opacity: 0.3, curveness: 0.5 },
|
|
794
|
+
label: {
|
|
795
|
+
color: TEXT,
|
|
796
|
+
fontSize: 14,
|
|
797
|
+
formatter: (p) => spec.labelsOnData ? `${p.name} ${fmt(p.value)}` : p.name
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
]
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
if (spec.type === "pictogram") {
|
|
804
|
+
const per = spec.perIcon ?? Math.max(1, niceCeil(Math.max(...first.values) / 20, [1, 2, 5, 10]));
|
|
805
|
+
const icons = first.values.map((v) => Math.round(v / per));
|
|
806
|
+
const slots = Math.max(1, ...icons);
|
|
807
|
+
const glyph = {
|
|
808
|
+
type: "pictorialBar",
|
|
809
|
+
symbol: HOUSE,
|
|
810
|
+
symbolRepeat: slots,
|
|
811
|
+
symbolBoundingData: slots,
|
|
812
|
+
// Fixed size keeps houses square; ECharts spaces them across each slot.
|
|
813
|
+
symbolSize: Math.round(Math.min(40, 260 / spec.labels.length)),
|
|
814
|
+
symbolClip: true,
|
|
815
|
+
barCategoryGap: "25%"
|
|
816
|
+
};
|
|
817
|
+
return {
|
|
818
|
+
...base,
|
|
819
|
+
legend: { show: false },
|
|
820
|
+
grid: { left: 16, right: 16, top: 36, bottom: 8, containLabel: true },
|
|
821
|
+
xAxis: { type: "value", max: slots, show: false },
|
|
822
|
+
yAxis: [
|
|
823
|
+
{
|
|
824
|
+
...rowAxis(),
|
|
825
|
+
axisLine: { show: false },
|
|
826
|
+
name: `\u2302 = ${formatValue(per, { ...spec, decimals: Math.min(3, (String(per).split(".")[1] ?? "").length) })}`,
|
|
827
|
+
nameLocation: "start",
|
|
828
|
+
nameTextStyle: { color: "#7a7a7a", fontSize: 13, align: "left" }
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
...rowAxis(),
|
|
832
|
+
position: "right",
|
|
833
|
+
axisLine: { show: false },
|
|
834
|
+
data: first.values.map(fmt),
|
|
835
|
+
axisLabel: {
|
|
836
|
+
show: spec.labelsOnData,
|
|
837
|
+
color: colors3[2],
|
|
838
|
+
fontSize: 15,
|
|
839
|
+
fontWeight: 600
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
],
|
|
843
|
+
series: [
|
|
844
|
+
// Faded full row behind the filled icons.
|
|
845
|
+
{ ...glyph, silent: true, itemStyle: { color: TRACK }, data: icons.map(() => slots) },
|
|
846
|
+
{
|
|
847
|
+
...glyph,
|
|
848
|
+
barGap: "-100%",
|
|
849
|
+
z: 3,
|
|
850
|
+
data: icons.map((v, i) => ({
|
|
851
|
+
value: v,
|
|
852
|
+
// Clipping a full row trims the last icon's edge; partial rows clip between icons.
|
|
853
|
+
symbolClip: v < slots,
|
|
854
|
+
itemStyle: { color: muted(spec.labels[i], colors3[0]) }
|
|
855
|
+
}))
|
|
856
|
+
}
|
|
857
|
+
]
|
|
858
|
+
};
|
|
859
|
+
}
|
|
342
860
|
if (spec.type === "pie" || spec.type === "donut") {
|
|
343
861
|
return {
|
|
344
862
|
...base,
|
|
@@ -492,37 +1010,32 @@ ${fmt(p.value)}` : p.name
|
|
|
492
1010
|
}
|
|
493
1011
|
const horizontal = spec.type === "bar" || spec.type === "stacked-bar";
|
|
494
1012
|
const stacked = spec.type.startsWith("stacked");
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
max: spec.max,
|
|
507
|
-
axisLine: { show: false },
|
|
508
|
-
splitLine: { lineStyle: { color: "#eef0ec" } },
|
|
509
|
-
axisLabel: {
|
|
510
|
-
color: "#7a7a7a",
|
|
511
|
-
fontSize: 13,
|
|
512
|
-
formatter: (v) => formatValue(v, { ...spec, unit: void 0 })
|
|
513
|
-
}
|
|
514
|
-
};
|
|
1013
|
+
const combo = spec.type === "combo";
|
|
1014
|
+
const lineAt = (si) => combo ? (spec.series[si].as ?? (si === 0 ? "bar" : "line")) === "line" : spec.type === "line" || spec.type === "area";
|
|
1015
|
+
const secondaryAt = (si) => combo && spec.series[si].axis === "secondary";
|
|
1016
|
+
const hasSecondary = spec.series.some((_, si) => secondaryAt(si));
|
|
1017
|
+
const secondary = { ...spec, format: spec.secondaryFormat ?? spec.format };
|
|
1018
|
+
const category = categoryAxis(!spec.series.every((_, si) => lineAt(si)));
|
|
1019
|
+
const value = valueAxis();
|
|
1020
|
+
const yAxis = horizontal ? { ...category, inverse: true } : hasSecondary ? [
|
|
1021
|
+
value,
|
|
1022
|
+
{ ...valueAxis(secondary), max: void 0, scale: true, splitLine: { show: false } }
|
|
1023
|
+
] : value;
|
|
515
1024
|
return {
|
|
516
1025
|
...base,
|
|
517
1026
|
grid: { left: 16, right: 24, top: showLegend ? 48 : 20, bottom: 12, containLabel: true },
|
|
518
1027
|
xAxis: horizontal ? value : category,
|
|
519
|
-
yAxis
|
|
1028
|
+
yAxis,
|
|
520
1029
|
series: spec.series.map((s, si) => {
|
|
521
1030
|
const color = colors3[si % colors3.length];
|
|
1031
|
+
const isLine = lineAt(si);
|
|
1032
|
+
const own = secondaryAt(si) ? secondary : spec;
|
|
522
1033
|
return {
|
|
523
1034
|
type: isLine ? "line" : "bar",
|
|
524
1035
|
name: s.name,
|
|
525
1036
|
stack: stacked ? "total" : void 0,
|
|
1037
|
+
yAxisIndex: secondaryAt(si) ? 1 : 0,
|
|
1038
|
+
z: isLine ? 3 : 2,
|
|
526
1039
|
smooth: false,
|
|
527
1040
|
symbolSize: 8,
|
|
528
1041
|
lineStyle: isLine ? { width: 3 } : void 0,
|
|
@@ -538,9 +1051,12 @@ ${fmt(p.value)}` : p.name
|
|
|
538
1051
|
})),
|
|
539
1052
|
label: {
|
|
540
1053
|
...labelStyle,
|
|
1054
|
+
// Combo line labels sit over bars; a backing keeps them legible.
|
|
1055
|
+
...combo && isLine ? { backgroundColor: "#fff", padding: [2, 5], borderRadius: 3 } : {},
|
|
1056
|
+
formatter: (p) => formatValue(p.value, own),
|
|
541
1057
|
show: spec.labelsOnData && (!stacked || spec.series.length <= 3),
|
|
542
1058
|
position: stacked ? "inside" : horizontal ? "right" : "top",
|
|
543
|
-
color: stacked ? "#fff" : TEXT
|
|
1059
|
+
color: stacked ? "#fff" : combo && isLine ? color : TEXT
|
|
544
1060
|
}
|
|
545
1061
|
};
|
|
546
1062
|
})
|
|
@@ -548,7 +1064,7 @@ ${fmt(p.value)}` : p.name
|
|
|
548
1064
|
}
|
|
549
1065
|
var renderer;
|
|
550
1066
|
function loadChartRenderer() {
|
|
551
|
-
renderer ??= import("./echarts-render-
|
|
1067
|
+
renderer ??= import("./echarts-render-GDCT5QAF.js").then((m) => m.createRenderer());
|
|
552
1068
|
return renderer;
|
|
553
1069
|
}
|
|
554
1070
|
async function renderChartSvg(spec, opts = {}) {
|
|
@@ -563,10 +1079,23 @@ async function renderChartSvg(spec, opts = {}) {
|
|
|
563
1079
|
return svg.replace(/^<svg([^>]*?) width="\d+" height="\d+"/, "<svg$1").replace(/^<svg/, `<svg role="img" aria-label="${label}" preserveAspectRatio="xMidYMid meet"`);
|
|
564
1080
|
}
|
|
565
1081
|
function describeChart(spec) {
|
|
566
|
-
const
|
|
567
|
-
|
|
1082
|
+
const kind = `${spec.type.replace("-", " ")} chart.`;
|
|
1083
|
+
const fmt = (v) => formatValue(v, spec);
|
|
1084
|
+
if (spec.type === "sankey") {
|
|
1085
|
+
const flows = sankeyLinks(spec).map((l) => `${l.source} to ${l.target} ${fmt(l.value)}`);
|
|
1086
|
+
return `${kind} ${flows.join(", ")}.`;
|
|
1087
|
+
}
|
|
1088
|
+
if (spec.type === "bullet") {
|
|
1089
|
+
const [actual, target] = spec.series;
|
|
1090
|
+
const rows = spec.labels.map(
|
|
1091
|
+
(l, i) => `${l} ${fmt(actual.values[i] ?? 0)}${target ? ` against a target of ${fmt(target.values[i] ?? 0)}` : ""}`
|
|
1092
|
+
);
|
|
1093
|
+
return `${kind} ${rows.join(", ")}.`;
|
|
1094
|
+
}
|
|
1095
|
+
const parts = spec.series.slice(0, SINGLE_SERIES_CHARTS.includes(spec.type) ? 1 : 6).map(
|
|
1096
|
+
(s) => `${s.name}: ${spec.labels.map((l, i) => `${l} ${fmt(s.values[i] ?? 0)}`).join(", ")}`
|
|
568
1097
|
);
|
|
569
|
-
return `${
|
|
1098
|
+
return `${kind} ${parts.join(". ")}.`;
|
|
570
1099
|
}
|
|
571
1100
|
|
|
572
1101
|
// src/inline.ts
|
|
@@ -1074,7 +1603,7 @@ import { z as z5 } from "zod";
|
|
|
1074
1603
|
var CHART_SIZE = { wide: { width: 1040, height: 500 }, side: { width: 720, height: 500 } };
|
|
1075
1604
|
var chart = defineLayout({
|
|
1076
1605
|
name: "chart",
|
|
1077
|
-
description: "A branded chart (column, bar, stacked, line, area, pie, donut, funnel, gauge, scatter, radar) with an optional takeaway column.",
|
|
1606
|
+
description: "A branded chart (column, bar, stacked, 100% stacked, line, area, combo, pie, donut, treemap, funnel, waterfall, gauge, ring, bullet, dumbbell, heatmap, sankey, pictogram, scatter, radar) with an optional takeaway column.",
|
|
1078
1607
|
whenToUse: "Numbers that tell a story: sales by quarter, timelines, shares, survey scores. State the takeaway in the title.",
|
|
1079
1608
|
shape: {
|
|
1080
1609
|
eyebrow: Text.optional(),
|
|
@@ -3661,6 +4190,40 @@ ${source}${fields}<script>${RUNTIME_JS}</script>
|
|
|
3661
4190
|
`;
|
|
3662
4191
|
return { html, deck, slideCount: sections.length, slideMap, warnings: result.issues, brand };
|
|
3663
4192
|
}
|
|
4193
|
+
var NO_ASSETS = {
|
|
4194
|
+
image: () => "",
|
|
4195
|
+
qr: () => "",
|
|
4196
|
+
chart: () => "",
|
|
4197
|
+
logo: () => ""
|
|
4198
|
+
};
|
|
4199
|
+
function speakerNotes(input, options = {}) {
|
|
4200
|
+
const result = validateDeck(input, { brands: options.brands });
|
|
4201
|
+
if (!result.ok || !result.deck) throw new DeckError(result.issues);
|
|
4202
|
+
const deck = result.deck;
|
|
4203
|
+
const ctx = {
|
|
4204
|
+
deck,
|
|
4205
|
+
...NO_ASSETS,
|
|
4206
|
+
brandName: resolveBrand(deck.brand, options.brands).name
|
|
4207
|
+
};
|
|
4208
|
+
const out = [];
|
|
4209
|
+
deck.slides.forEach((slide, i) => {
|
|
4210
|
+
if (slide.hidden && !options.includeHidden) return;
|
|
4211
|
+
const rendered = getLayout(slide.layout).render(slide, ctx);
|
|
4212
|
+
const list = Array.isArray(rendered) ? rendered : [rendered];
|
|
4213
|
+
list.forEach((r, j) => {
|
|
4214
|
+
const notes = (r.notes ?? slide.notes)?.trim();
|
|
4215
|
+
out.push({
|
|
4216
|
+
number: out.length + 1,
|
|
4217
|
+
deckIndex: i,
|
|
4218
|
+
...slide.id ? { slideId: slide.id } : {},
|
|
4219
|
+
...list.length > 1 ? { part: { index: j + 1, count: list.length } } : {},
|
|
4220
|
+
...r.time ?? slide.time ? { time: r.time ?? slide.time } : {},
|
|
4221
|
+
html: notes ? prose(notes) : ""
|
|
4222
|
+
});
|
|
4223
|
+
});
|
|
4224
|
+
});
|
|
4225
|
+
return out;
|
|
4226
|
+
}
|
|
3664
4227
|
function escapedImageBytes(value) {
|
|
3665
4228
|
let bytes = 0;
|
|
3666
4229
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -3939,6 +4502,7 @@ export {
|
|
|
3939
4502
|
LOGO_SVG,
|
|
3940
4503
|
logoLockup,
|
|
3941
4504
|
CHART_TYPES,
|
|
4505
|
+
SINGLE_SERIES_CHARTS,
|
|
3942
4506
|
ChartSpec,
|
|
3943
4507
|
formatValue,
|
|
3944
4508
|
chartOption,
|
|
@@ -3981,6 +4545,7 @@ export {
|
|
|
3981
4545
|
fieldMarkers,
|
|
3982
4546
|
collectImageSources,
|
|
3983
4547
|
renderDeck,
|
|
4548
|
+
speakerNotes,
|
|
3984
4549
|
extractDeckSource,
|
|
3985
4550
|
parseDeckSource,
|
|
3986
4551
|
stringifyDeck,
|
|
@@ -3988,4 +4553,4 @@ export {
|
|
|
3988
4553
|
inlineDeckImages,
|
|
3989
4554
|
externalizeDeckImages
|
|
3990
4555
|
};
|
|
3991
|
-
//# sourceMappingURL=chunk-
|
|
4556
|
+
//# sourceMappingURL=chunk-HI5ZCAHO.js.map
|