@bendyline/squisq 2.3.3 → 2.4.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/{Doc-BrgZC7SE.d.ts → Doc-DLpyOAXJ.d.ts} +87 -2
- package/dist/{ImageEditDoc-rum0Xb9P.d.ts → ImageEditDoc-Cu30xb9b.d.ts} +1 -1
- package/dist/{chunk-2ZWIXGAC.js → chunk-2S74DPJH.js} +4 -0
- package/dist/{chunk-RS5AP3J4.js → chunk-AE3IUKMM.js} +1 -1
- package/dist/{chunk-QWCFK5FN.js → chunk-CUYHFOFL.js} +5 -1
- package/dist/{chunk-G3JOS25T.js → chunk-D4LPP3P5.js} +43 -14
- package/dist/{chunk-GQNH7PLN.js → chunk-F2IEPSMA.js} +1320 -8
- package/dist/{chunk-IZLKI3IL.js → chunk-KPBZZVGY.js} +29 -5
- package/dist/{chunk-BCCXTMN5.js → chunk-O7JILDEF.js} +7 -0
- package/dist/{chunk-SPTY4C6F.js → chunk-REDUXXDJ.js} +1 -1
- package/dist/{chunk-ABP4LAJS.js → chunk-ZHLNKB2I.js} +1 -1
- package/dist/chunk-ZYO3DBTA.js +983 -0
- package/dist/doc/index.d.ts +103 -6
- package/dist/doc/index.js +8 -6
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +6 -6
- package/dist/index.js +29 -11
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +2 -2
- package/dist/markdown/index.d.ts +118 -1
- package/dist/markdown/index.js +21 -5
- package/dist/{materializePageSection-CrZWYnXM.d.ts → materializePageSection-CgNs5Qmw.d.ts} +2 -2
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +4 -4
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +2 -2
- package/dist/{themeLibrary-DJt89gyP.d.ts → themeLibrary-RWsNtlOu.d.ts} +1 -1
- package/dist/transform/index.d.ts +2 -2
- package/dist/transform/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-UA5DTYAY.js +0 -400
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
parseAsciiTimeline,
|
|
22
22
|
pickAutoTemplate,
|
|
23
23
|
profileBlockContents
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-CUYHFOFL.js";
|
|
25
25
|
import {
|
|
26
26
|
DEFAULT_THEME,
|
|
27
27
|
FRONTMATTER_CUSTOM_TEMPLATES_KEY,
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
extractPlainText,
|
|
58
58
|
readFrontmatterThemeId,
|
|
59
59
|
walkMarkdownTree
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-O7JILDEF.js";
|
|
61
61
|
import {
|
|
62
62
|
resolveBlockTransition
|
|
63
63
|
} from "./chunk-4VOD55SX.js";
|
|
@@ -423,6 +423,16 @@ var TEMPLATE_ALIASES = Object.freeze({
|
|
|
423
423
|
function resolveTemplateName(name) {
|
|
424
424
|
return TEMPLATE_ALIASES[name] ?? name;
|
|
425
425
|
}
|
|
426
|
+
var TABLE_FED_TEMPLATES = /* @__PURE__ */ new Set([
|
|
427
|
+
"dataTable",
|
|
428
|
+
"barChart",
|
|
429
|
+
"columnChart",
|
|
430
|
+
"pieChart",
|
|
431
|
+
"donutChart",
|
|
432
|
+
"lineChart",
|
|
433
|
+
"areaChart",
|
|
434
|
+
"scatterChart"
|
|
435
|
+
]);
|
|
426
436
|
var CONTAINER_TEMPLATES = /* @__PURE__ */ new Set(["diagram", "drawing", "layout"]);
|
|
427
437
|
function isContainerTemplate(name) {
|
|
428
438
|
return !!name && CONTAINER_TEMPLATES.has(resolveTemplateName(name));
|
|
@@ -949,7 +959,7 @@ function sectionHeader(input, context) {
|
|
|
949
959
|
|
|
950
960
|
// src/doc/templates/contentBlock.ts
|
|
951
961
|
function completeBodyText(context) {
|
|
952
|
-
return (context.block?.contents ?? []).map((node) => extractPlainText(node)).join("\n").trim();
|
|
962
|
+
return (context.block?.contents ?? []).filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((node) => extractPlainText(node)).join("\n").trim();
|
|
953
963
|
}
|
|
954
964
|
function bodyFontSize(body, context) {
|
|
955
965
|
const base = body.length > 1800 ? 18 : body.length > 1100 ? 21 : body.length > 650 ? 24 : 28;
|
|
@@ -2739,6 +2749,32 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
|
|
|
2739
2749
|
if (tableData) return { ...tableData, ...headingText ? { title: headingText } : {} };
|
|
2740
2750
|
return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
|
|
2741
2751
|
}
|
|
2752
|
+
case "barChart":
|
|
2753
|
+
case "columnChart":
|
|
2754
|
+
case "pieChart":
|
|
2755
|
+
case "donutChart":
|
|
2756
|
+
case "lineChart":
|
|
2757
|
+
case "areaChart":
|
|
2758
|
+
case "scatterChart": {
|
|
2759
|
+
const tableData = extractTableFromContents(contents);
|
|
2760
|
+
if (tableData) {
|
|
2761
|
+
return {
|
|
2762
|
+
headers: tableData.headers,
|
|
2763
|
+
rows: tableData.rows,
|
|
2764
|
+
...headingText ? { title: headingText } : {}
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
return placeholders && !bodyText3.trim() ? {
|
|
2768
|
+
headers: ["Quarter", "Revenue", "Costs"],
|
|
2769
|
+
rows: [
|
|
2770
|
+
["Q1", "40", "28"],
|
|
2771
|
+
["Q2", "55", "31"],
|
|
2772
|
+
["Q3", "48", "30"],
|
|
2773
|
+
["Q4", "62", "35"]
|
|
2774
|
+
],
|
|
2775
|
+
...headingText ? { title: headingText } : {}
|
|
2776
|
+
} : null;
|
|
2777
|
+
}
|
|
2742
2778
|
case "imageWithCaption": {
|
|
2743
2779
|
const img = extractFirstImage(contents);
|
|
2744
2780
|
if (!img) return placeholders ? { caption: headingText } : null;
|
|
@@ -5585,6 +5621,1019 @@ function makeCustomTemplateFn(def) {
|
|
|
5585
5621
|
};
|
|
5586
5622
|
}
|
|
5587
5623
|
|
|
5624
|
+
// src/doc/templates/chart/parse.ts
|
|
5625
|
+
var NUMERIC_COLUMN_THRESHOLD = 0.6;
|
|
5626
|
+
function parseCellNumber(raw) {
|
|
5627
|
+
if (typeof raw !== "string") return null;
|
|
5628
|
+
let text = raw.trim();
|
|
5629
|
+
if (!text) return null;
|
|
5630
|
+
let negative = false;
|
|
5631
|
+
const accounting = /^\((.*)\)$/.exec(text);
|
|
5632
|
+
if (accounting) {
|
|
5633
|
+
negative = true;
|
|
5634
|
+
text = accounting[1].trim();
|
|
5635
|
+
}
|
|
5636
|
+
text = text.replace(/^([+-]?)\s*[$€£¥]\s*/, "$1");
|
|
5637
|
+
text = text.replace(/%$/, "").trim();
|
|
5638
|
+
text = text.replace(/,/g, "");
|
|
5639
|
+
if (/^[+-]?\d{1,3}(?: \d{3})+(?:\.\d+)?$/.test(text)) {
|
|
5640
|
+
text = text.replace(/ /g, "");
|
|
5641
|
+
}
|
|
5642
|
+
if (!/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(text)) return null;
|
|
5643
|
+
const value = Number(text);
|
|
5644
|
+
if (!Number.isFinite(value)) return null;
|
|
5645
|
+
return negative ? -value : value;
|
|
5646
|
+
}
|
|
5647
|
+
function isNumericColumn(rows, colIndex) {
|
|
5648
|
+
let nonEmpty = 0;
|
|
5649
|
+
let numeric = 0;
|
|
5650
|
+
for (const row of rows) {
|
|
5651
|
+
const cell = (row[colIndex] ?? "").trim();
|
|
5652
|
+
if (!cell) continue;
|
|
5653
|
+
nonEmpty += 1;
|
|
5654
|
+
if (parseCellNumber(cell) !== null) numeric += 1;
|
|
5655
|
+
}
|
|
5656
|
+
return numeric > 0 && numeric / Math.max(1, nonEmpty) >= NUMERIC_COLUMN_THRESHOLD;
|
|
5657
|
+
}
|
|
5658
|
+
function resolveColumnRef(ref, headers) {
|
|
5659
|
+
const text = String(ref).trim();
|
|
5660
|
+
if (!text) return null;
|
|
5661
|
+
const lower = text.toLowerCase();
|
|
5662
|
+
const byName = headers.findIndex((h) => h.trim().toLowerCase() === lower);
|
|
5663
|
+
if (byName >= 0) return byName;
|
|
5664
|
+
if (/^\d+$/.test(text)) {
|
|
5665
|
+
const index = Number(text);
|
|
5666
|
+
if (index < headers.length) return index;
|
|
5667
|
+
}
|
|
5668
|
+
return null;
|
|
5669
|
+
}
|
|
5670
|
+
function buildChartData(table, options = {}) {
|
|
5671
|
+
const headers = Array.isArray(table.headers) ? table.headers : [];
|
|
5672
|
+
const rows = Array.isArray(table.rows) ? table.rows : [];
|
|
5673
|
+
if (headers.length === 0 || rows.length === 0) return null;
|
|
5674
|
+
const warnings = [];
|
|
5675
|
+
let labelIndex = 0;
|
|
5676
|
+
if (options.labelColumn !== void 0 && String(options.labelColumn).trim() !== "") {
|
|
5677
|
+
const resolved = resolveColumnRef(String(options.labelColumn), headers);
|
|
5678
|
+
if (resolved === null) {
|
|
5679
|
+
warnings.push(
|
|
5680
|
+
`labelColumn "${options.labelColumn}" matches no column; using the first column`
|
|
5681
|
+
);
|
|
5682
|
+
} else {
|
|
5683
|
+
labelIndex = resolved;
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
let valueIndexes = [];
|
|
5687
|
+
const requested = (options.valueColumns ?? []).map((ref) => String(ref)).filter((ref) => ref.trim() !== "");
|
|
5688
|
+
if (requested.length > 0) {
|
|
5689
|
+
for (const ref of requested) {
|
|
5690
|
+
const resolved = resolveColumnRef(ref, headers);
|
|
5691
|
+
if (resolved === null) {
|
|
5692
|
+
warnings.push(`valueColumns entry "${ref}" matches no column; skipped`);
|
|
5693
|
+
} else if (!valueIndexes.includes(resolved)) {
|
|
5694
|
+
valueIndexes.push(resolved);
|
|
5695
|
+
}
|
|
5696
|
+
}
|
|
5697
|
+
} else {
|
|
5698
|
+
valueIndexes = headers.map((_, index) => index).filter((index) => index !== labelIndex && isNumericColumn(rows, index));
|
|
5699
|
+
}
|
|
5700
|
+
if (valueIndexes.length === 0) return null;
|
|
5701
|
+
return {
|
|
5702
|
+
labels: rows.map((row) => (row[labelIndex] ?? "").trim()),
|
|
5703
|
+
labelHeader: headers[labelIndex] ?? "",
|
|
5704
|
+
labelNumbers: rows.map((row) => parseCellNumber(row[labelIndex] ?? "")),
|
|
5705
|
+
series: valueIndexes.map((index) => ({
|
|
5706
|
+
name: (headers[index] ?? `Column ${index + 1}`).trim() || `Column ${index + 1}`,
|
|
5707
|
+
values: rows.map((row) => parseCellNumber(row[index] ?? ""))
|
|
5708
|
+
})),
|
|
5709
|
+
warnings
|
|
5710
|
+
};
|
|
5711
|
+
}
|
|
5712
|
+
|
|
5713
|
+
// src/doc/templates/chart/scale.ts
|
|
5714
|
+
function niceNum(range, round4) {
|
|
5715
|
+
const exponent = Math.floor(Math.log10(range));
|
|
5716
|
+
const fraction = range / 10 ** exponent;
|
|
5717
|
+
let niceFraction;
|
|
5718
|
+
if (round4) {
|
|
5719
|
+
if (fraction < 1.5) niceFraction = 1;
|
|
5720
|
+
else if (fraction < 3) niceFraction = 2;
|
|
5721
|
+
else if (fraction < 7) niceFraction = 5;
|
|
5722
|
+
else niceFraction = 10;
|
|
5723
|
+
} else {
|
|
5724
|
+
if (fraction <= 1) niceFraction = 1;
|
|
5725
|
+
else if (fraction <= 2) niceFraction = 2;
|
|
5726
|
+
else if (fraction <= 5) niceFraction = 5;
|
|
5727
|
+
else niceFraction = 10;
|
|
5728
|
+
}
|
|
5729
|
+
return niceFraction * 10 ** exponent;
|
|
5730
|
+
}
|
|
5731
|
+
function niceTicks(min, max, targetCount = 5) {
|
|
5732
|
+
let lo = Number.isFinite(min) ? min : 0;
|
|
5733
|
+
let hi = Number.isFinite(max) ? max : 0;
|
|
5734
|
+
if (lo > hi) [lo, hi] = [hi, lo];
|
|
5735
|
+
if (lo === hi) {
|
|
5736
|
+
const pad = lo === 0 ? 1 : niceNum(Math.abs(lo), true);
|
|
5737
|
+
lo -= pad;
|
|
5738
|
+
hi += pad;
|
|
5739
|
+
if (min >= 0 && lo < 0) lo = 0;
|
|
5740
|
+
}
|
|
5741
|
+
const count = Math.max(2, targetCount);
|
|
5742
|
+
const range = niceNum(hi - lo, false);
|
|
5743
|
+
const step = niceNum(range / (count - 1), true);
|
|
5744
|
+
const niceMin = Math.floor(lo / step) * step;
|
|
5745
|
+
const niceMax = Math.ceil(hi / step) * step;
|
|
5746
|
+
const ticks = [];
|
|
5747
|
+
for (let tick = niceMin; tick <= niceMax + step * 1e-6; tick += step) {
|
|
5748
|
+
ticks.push(Math.abs(tick) < step * 1e-6 ? 0 : Number(tick.toPrecision(12)));
|
|
5749
|
+
}
|
|
5750
|
+
return { ticks, niceMin, niceMax, step };
|
|
5751
|
+
}
|
|
5752
|
+
function formatChartValue(value, unit) {
|
|
5753
|
+
if (!Number.isFinite(value)) return "\u2014";
|
|
5754
|
+
const sign = value < 0 ? "-" : "";
|
|
5755
|
+
const abs = Math.abs(value);
|
|
5756
|
+
let text;
|
|
5757
|
+
if (abs >= 1e6) {
|
|
5758
|
+
text = `${sign}${trimTrailingZero((abs / 1e6).toFixed(1))}M`;
|
|
5759
|
+
} else if (abs >= 1e3) {
|
|
5760
|
+
text = `${sign}${trimTrailingZero((abs / 1e3).toFixed(abs >= 1e4 ? 0 : 1))}K`;
|
|
5761
|
+
} else {
|
|
5762
|
+
text = `${sign}${abs.toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
|
|
5763
|
+
}
|
|
5764
|
+
return unit ? `${text} ${unit}` : text;
|
|
5765
|
+
}
|
|
5766
|
+
function trimTrailingZero(text) {
|
|
5767
|
+
return text.replace(/\.0$/, "");
|
|
5768
|
+
}
|
|
5769
|
+
|
|
5770
|
+
// src/doc/templates/chart/geometry.ts
|
|
5771
|
+
function round3(value) {
|
|
5772
|
+
return Math.round(value * 100) / 100;
|
|
5773
|
+
}
|
|
5774
|
+
function pointOnCircle(cx, cy, r, deg) {
|
|
5775
|
+
const rad = (deg - 90) * Math.PI / 180;
|
|
5776
|
+
return { x: round3(cx + r * Math.cos(rad)), y: round3(cy + r * Math.sin(rad)) };
|
|
5777
|
+
}
|
|
5778
|
+
function arcPath(cx, cy, rOuter, rInner, startDeg, endDeg) {
|
|
5779
|
+
let sweep = endDeg - startDeg;
|
|
5780
|
+
if (sweep <= 0) return "";
|
|
5781
|
+
if (sweep >= 359.9) sweep = 359.9;
|
|
5782
|
+
const end = startDeg + sweep;
|
|
5783
|
+
const largeArc = sweep > 180 ? 1 : 0;
|
|
5784
|
+
const o1 = pointOnCircle(cx, cy, rOuter, startDeg);
|
|
5785
|
+
const o2 = pointOnCircle(cx, cy, rOuter, end);
|
|
5786
|
+
if (rInner <= 0) {
|
|
5787
|
+
return [
|
|
5788
|
+
`M ${round3(cx)} ${round3(cy)}`,
|
|
5789
|
+
`L ${o1.x} ${o1.y}`,
|
|
5790
|
+
`A ${round3(rOuter)} ${round3(rOuter)} 0 ${largeArc} 1 ${o2.x} ${o2.y}`,
|
|
5791
|
+
"Z"
|
|
5792
|
+
].join(" ");
|
|
5793
|
+
}
|
|
5794
|
+
const i1 = pointOnCircle(cx, cy, rInner, startDeg);
|
|
5795
|
+
const i2 = pointOnCircle(cx, cy, rInner, end);
|
|
5796
|
+
return [
|
|
5797
|
+
`M ${o1.x} ${o1.y}`,
|
|
5798
|
+
`A ${round3(rOuter)} ${round3(rOuter)} 0 ${largeArc} 1 ${o2.x} ${o2.y}`,
|
|
5799
|
+
`L ${i2.x} ${i2.y}`,
|
|
5800
|
+
`A ${round3(rInner)} ${round3(rInner)} 0 ${largeArc} 0 ${i1.x} ${i1.y}`,
|
|
5801
|
+
"Z"
|
|
5802
|
+
].join(" ");
|
|
5803
|
+
}
|
|
5804
|
+
function polylinePath(points) {
|
|
5805
|
+
if (points.length === 0) return "";
|
|
5806
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"} ${round3(p.x)} ${round3(p.y)}`).join(" ");
|
|
5807
|
+
}
|
|
5808
|
+
function areaPath(points, baselineY) {
|
|
5809
|
+
if (points.length === 0) return "";
|
|
5810
|
+
const open = polylinePath(points);
|
|
5811
|
+
const first = points[0];
|
|
5812
|
+
const last = points[points.length - 1];
|
|
5813
|
+
return `${open} L ${round3(last.x)} ${round3(baselineY)} L ${round3(first.x)} ${round3(baselineY)} Z`;
|
|
5814
|
+
}
|
|
5815
|
+
|
|
5816
|
+
// src/doc/templates/chart/layout.ts
|
|
5817
|
+
function computeChartFrame(options) {
|
|
5818
|
+
const W = options.viewportWidth;
|
|
5819
|
+
const H = options.viewportHeight;
|
|
5820
|
+
const marginX = W * 0.06;
|
|
5821
|
+
const marginY = H * 0.06;
|
|
5822
|
+
const innerWidth = W - marginX * 2;
|
|
5823
|
+
let cursor = marginY;
|
|
5824
|
+
let titleBand;
|
|
5825
|
+
if (options.hasTitle) {
|
|
5826
|
+
titleBand = { x: marginX, y: cursor, width: innerWidth, height: options.titleFontSize * 2.2 };
|
|
5827
|
+
cursor = titleBand.y + titleBand.height;
|
|
5828
|
+
}
|
|
5829
|
+
let tableBand;
|
|
5830
|
+
let tableHeight = 0;
|
|
5831
|
+
if (options.tableRowCount > 0) {
|
|
5832
|
+
tableHeight = Math.min(H * 0.4, (options.tableRowCount + 1) * options.tableFontSize * 2.4);
|
|
5833
|
+
}
|
|
5834
|
+
const legendHeight = options.legendRows > 0 ? options.legendRows * options.legendRowHeight : 0;
|
|
5835
|
+
const chartArea = {
|
|
5836
|
+
x: marginX,
|
|
5837
|
+
y: cursor,
|
|
5838
|
+
width: innerWidth,
|
|
5839
|
+
height: Math.max(40, H - marginY - cursor - legendHeight - tableHeight)
|
|
5840
|
+
};
|
|
5841
|
+
cursor = chartArea.y + chartArea.height;
|
|
5842
|
+
let legendBand;
|
|
5843
|
+
if (legendHeight > 0) {
|
|
5844
|
+
legendBand = { x: marginX, y: cursor, width: innerWidth, height: legendHeight };
|
|
5845
|
+
cursor += legendHeight;
|
|
5846
|
+
}
|
|
5847
|
+
if (tableHeight > 0) {
|
|
5848
|
+
tableBand = { x: marginX, y: cursor, width: innerWidth, height: tableHeight };
|
|
5849
|
+
}
|
|
5850
|
+
const plot = {
|
|
5851
|
+
x: chartArea.x + options.leftGutter,
|
|
5852
|
+
y: chartArea.y,
|
|
5853
|
+
width: Math.max(20, chartArea.width - options.leftGutter),
|
|
5854
|
+
height: Math.max(20, chartArea.height - options.bottomGutter)
|
|
5855
|
+
};
|
|
5856
|
+
return { titleBand, chartArea, plot, legendBand, tableBand };
|
|
5857
|
+
}
|
|
5858
|
+
function stackSeries(series) {
|
|
5859
|
+
const rowCount = series.reduce((max, s) => Math.max(max, s.values.length), 0);
|
|
5860
|
+
const positive = new Array(rowCount).fill(0);
|
|
5861
|
+
const negative = new Array(rowCount).fill(0);
|
|
5862
|
+
return series.map(
|
|
5863
|
+
(s) => Array.from({ length: rowCount }, (_, row) => {
|
|
5864
|
+
const value = s.values[row];
|
|
5865
|
+
if (value === null || value === void 0 || !Number.isFinite(value)) {
|
|
5866
|
+
return { y0: positive[row], y1: positive[row] };
|
|
5867
|
+
}
|
|
5868
|
+
if (value >= 0) {
|
|
5869
|
+
const y0 = positive[row];
|
|
5870
|
+
positive[row] += value;
|
|
5871
|
+
return { y0, y1: positive[row] };
|
|
5872
|
+
}
|
|
5873
|
+
const y1 = negative[row];
|
|
5874
|
+
negative[row] += value;
|
|
5875
|
+
return { y0: negative[row], y1 };
|
|
5876
|
+
})
|
|
5877
|
+
);
|
|
5878
|
+
}
|
|
5879
|
+
function layoutLegend(labels, fontSize, maxWidth) {
|
|
5880
|
+
const swatchSize = fontSize * 0.9;
|
|
5881
|
+
const gap = fontSize * 1.6;
|
|
5882
|
+
const rowHeight = fontSize * 2;
|
|
5883
|
+
const widths = labels.map(
|
|
5884
|
+
(label) => swatchSize + fontSize * 0.5 + label.length * fontSize * 0.58
|
|
5885
|
+
);
|
|
5886
|
+
const rows = [];
|
|
5887
|
+
let current = [];
|
|
5888
|
+
let currentWidth = 0;
|
|
5889
|
+
widths.forEach((width, index) => {
|
|
5890
|
+
const extra = current.length > 0 ? gap : 0;
|
|
5891
|
+
if (current.length > 0 && currentWidth + extra + width > maxWidth) {
|
|
5892
|
+
rows.push(current);
|
|
5893
|
+
current = [];
|
|
5894
|
+
currentWidth = 0;
|
|
5895
|
+
}
|
|
5896
|
+
current.push(index);
|
|
5897
|
+
currentWidth += (current.length > 1 ? gap : 0) + width;
|
|
5898
|
+
});
|
|
5899
|
+
if (current.length > 0) rows.push(current);
|
|
5900
|
+
const items = [];
|
|
5901
|
+
rows.forEach((rowIndexes, rowIndex) => {
|
|
5902
|
+
const rowWidth = rowIndexes.reduce(
|
|
5903
|
+
(sum, index, i) => sum + widths[index] + (i > 0 ? gap : 0),
|
|
5904
|
+
0
|
|
5905
|
+
);
|
|
5906
|
+
let x = Math.max(0, (maxWidth - rowWidth) / 2);
|
|
5907
|
+
rowIndexes.forEach((index) => {
|
|
5908
|
+
items.push({ label: labels[index], index, x, y: rowIndex * rowHeight, swatchSize });
|
|
5909
|
+
x += widths[index] + gap;
|
|
5910
|
+
});
|
|
5911
|
+
});
|
|
5912
|
+
return { items, rows: rows.length, rowHeight };
|
|
5913
|
+
}
|
|
5914
|
+
function categoryLabelStep(categoryCount, axisLength, fontSize) {
|
|
5915
|
+
if (categoryCount <= 1) return 1;
|
|
5916
|
+
const minPitch = fontSize * 6;
|
|
5917
|
+
const pitch = axisLength / categoryCount;
|
|
5918
|
+
return Math.max(1, Math.ceil(minPitch / Math.max(1, pitch)));
|
|
5919
|
+
}
|
|
5920
|
+
|
|
5921
|
+
// src/doc/templates/chart/render.ts
|
|
5922
|
+
var TEMPLATE_ID = {
|
|
5923
|
+
bar: "barChart",
|
|
5924
|
+
column: "columnChart",
|
|
5925
|
+
pie: "pieChart",
|
|
5926
|
+
donut: "donutChart",
|
|
5927
|
+
line: "lineChart",
|
|
5928
|
+
area: "areaChart",
|
|
5929
|
+
scatter: "scatterChart"
|
|
5930
|
+
};
|
|
5931
|
+
function renderChart(kind, input, context) {
|
|
5932
|
+
const table = resolveTable(input, context);
|
|
5933
|
+
const data = table ? buildChartData(table, input) : null;
|
|
5934
|
+
if (!table || !data) return contentFallback(input, context);
|
|
5935
|
+
const { theme, viewport } = context;
|
|
5936
|
+
const templateId = TEMPLATE_ID[kind];
|
|
5937
|
+
const title = (input.title ?? "").trim();
|
|
5938
|
+
const titleFontSize = themedFontSize(44, context, true);
|
|
5939
|
+
const axisFontSize = themedFontSize(20, context, false);
|
|
5940
|
+
const labelFontSize = themedFontSize(22, context, false);
|
|
5941
|
+
const legendFontSize = themedFontSize(22, context, false);
|
|
5942
|
+
const tableFontSize = themedFontSize(20, context, false);
|
|
5943
|
+
const bodyFont = getThemeFont(context, "body");
|
|
5944
|
+
const isRadial = kind === "pie" || kind === "donut";
|
|
5945
|
+
const legendLabels = resolveLegendLabels(kind, input, data);
|
|
5946
|
+
const colors = seriesColors(
|
|
5947
|
+
context,
|
|
5948
|
+
input.colorScheme,
|
|
5949
|
+
isRadial ? data.labels.length : data.series.length
|
|
5950
|
+
);
|
|
5951
|
+
if (isRadial && !data.series[0].values.some((v) => v !== null && v > 0)) {
|
|
5952
|
+
return contentFallback(input, context);
|
|
5953
|
+
}
|
|
5954
|
+
const innerWidth = viewport.width * 0.88;
|
|
5955
|
+
const legend = legendLabels.length > 0 ? layoutLegend(legendLabels, legendFontSize, innerWidth) : null;
|
|
5956
|
+
const gutters = computeGutters(kind, data, input, context, axisFontSize, labelFontSize);
|
|
5957
|
+
const frame = computeChartFrame({
|
|
5958
|
+
viewportWidth: viewport.width,
|
|
5959
|
+
viewportHeight: viewport.height,
|
|
5960
|
+
hasTitle: title.length > 0,
|
|
5961
|
+
titleFontSize,
|
|
5962
|
+
leftGutter: gutters.left,
|
|
5963
|
+
bottomGutter: gutters.bottom,
|
|
5964
|
+
legendRows: legend?.rows ?? 0,
|
|
5965
|
+
legendRowHeight: legendFontSize * 2,
|
|
5966
|
+
tableRowCount: input.showTable === true ? table.rows.length : 0,
|
|
5967
|
+
tableFontSize
|
|
5968
|
+
});
|
|
5969
|
+
const entranceHint = getTemplateHint(context, templateId, "entrance", "staggered");
|
|
5970
|
+
const entrance = (index) => ({
|
|
5971
|
+
type: "fadeIn",
|
|
5972
|
+
duration: 0.6,
|
|
5973
|
+
delay: entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08)
|
|
5974
|
+
});
|
|
5975
|
+
const paint = {
|
|
5976
|
+
kind,
|
|
5977
|
+
input,
|
|
5978
|
+
context,
|
|
5979
|
+
data,
|
|
5980
|
+
frame,
|
|
5981
|
+
colors,
|
|
5982
|
+
axisFontSize,
|
|
5983
|
+
labelFontSize,
|
|
5984
|
+
bodyFont,
|
|
5985
|
+
gridColor: withAlpha(theme.colors.text, 0.12),
|
|
5986
|
+
axisTextColor: theme.colors.textMuted,
|
|
5987
|
+
entrance
|
|
5988
|
+
};
|
|
5989
|
+
const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
|
|
5990
|
+
if (title && frame.titleBand) {
|
|
5991
|
+
layers.push({
|
|
5992
|
+
type: "text",
|
|
5993
|
+
id: "title",
|
|
5994
|
+
content: {
|
|
5995
|
+
text: title,
|
|
5996
|
+
style: {
|
|
5997
|
+
fontSize: titleFontSize,
|
|
5998
|
+
fontFamily: getThemeFont(context, "title"),
|
|
5999
|
+
fontWeight: "bold",
|
|
6000
|
+
color: theme.colors.text,
|
|
6001
|
+
textAlign: "center",
|
|
6002
|
+
shadow: shouldUseShadow(context)
|
|
6003
|
+
}
|
|
6004
|
+
},
|
|
6005
|
+
position: {
|
|
6006
|
+
x: frame.titleBand.x + frame.titleBand.width / 2,
|
|
6007
|
+
y: frame.titleBand.y + frame.titleBand.height / 2,
|
|
6008
|
+
width: frame.titleBand.width,
|
|
6009
|
+
anchor: "center"
|
|
6010
|
+
},
|
|
6011
|
+
animation: { type: "fadeIn", duration: 0.8 }
|
|
6012
|
+
});
|
|
6013
|
+
}
|
|
6014
|
+
switch (kind) {
|
|
6015
|
+
case "bar":
|
|
6016
|
+
layers.push(...paintBars(paint));
|
|
6017
|
+
break;
|
|
6018
|
+
case "column":
|
|
6019
|
+
layers.push(...paintColumns(paint));
|
|
6020
|
+
break;
|
|
6021
|
+
case "pie":
|
|
6022
|
+
case "donut":
|
|
6023
|
+
layers.push(...paintRadial(paint));
|
|
6024
|
+
break;
|
|
6025
|
+
case "line":
|
|
6026
|
+
case "area":
|
|
6027
|
+
layers.push(...paintLines(paint));
|
|
6028
|
+
break;
|
|
6029
|
+
case "scatter":
|
|
6030
|
+
layers.push(...paintScatter(paint));
|
|
6031
|
+
break;
|
|
6032
|
+
}
|
|
6033
|
+
if (legend && frame.legendBand) {
|
|
6034
|
+
layers.push(...paintLegend(paint, legend, frame.legendBand, legendFontSize));
|
|
6035
|
+
}
|
|
6036
|
+
if (input.showTable === true && frame.tableBand) {
|
|
6037
|
+
layers.push(tableLayer(table, frame.tableBand, tableFontSize, input, context));
|
|
6038
|
+
}
|
|
6039
|
+
return layers;
|
|
6040
|
+
}
|
|
6041
|
+
function resolveTable(input, context) {
|
|
6042
|
+
if (Array.isArray(input.headers) && input.headers.length > 0 && Array.isArray(input.rows) && input.rows.length > 0) {
|
|
6043
|
+
return { headers: input.headers, rows: input.rows };
|
|
6044
|
+
}
|
|
6045
|
+
const contents = input.contents ?? context.block?.contents;
|
|
6046
|
+
return extractTableFromContents(contents);
|
|
6047
|
+
}
|
|
6048
|
+
function contentFallback(input, context) {
|
|
6049
|
+
const fallbackInput = {
|
|
6050
|
+
...input,
|
|
6051
|
+
template: "content",
|
|
6052
|
+
title: input.title ?? context.block?.title ?? ""
|
|
6053
|
+
};
|
|
6054
|
+
return contentBlock(fallbackInput, context);
|
|
6055
|
+
}
|
|
6056
|
+
function resolveLegendLabels(kind, input, data) {
|
|
6057
|
+
if (input.showLegend === false) return [];
|
|
6058
|
+
if (kind === "pie" || kind === "donut") return data.labels;
|
|
6059
|
+
if (data.series.length > 1 || input.showLegend === true) {
|
|
6060
|
+
return data.series.map((s) => s.name);
|
|
6061
|
+
}
|
|
6062
|
+
return [];
|
|
6063
|
+
}
|
|
6064
|
+
function seriesColors(context, schemeName, count) {
|
|
6065
|
+
const schemes = context.theme.colorSchemes;
|
|
6066
|
+
const names = Object.keys(schemes);
|
|
6067
|
+
const primary = resolveColorScheme(context, schemeName).accent;
|
|
6068
|
+
const palette = [primary];
|
|
6069
|
+
const start = schemeName && names.includes(schemeName) ? names.indexOf(schemeName) : 0;
|
|
6070
|
+
for (let i = 1; i < names.length && palette.length < count; i++) {
|
|
6071
|
+
const accent = schemes[names[(start + i) % names.length]].accent;
|
|
6072
|
+
if (!palette.some((c) => c.toLowerCase() === accent.toLowerCase())) palette.push(accent);
|
|
6073
|
+
}
|
|
6074
|
+
const distinct = palette.length;
|
|
6075
|
+
for (let i = 0; palette.length < count; i++) {
|
|
6076
|
+
palette.push(withAlpha(palette[i % distinct], 0.55));
|
|
6077
|
+
}
|
|
6078
|
+
return palette.slice(0, Math.max(1, count));
|
|
6079
|
+
}
|
|
6080
|
+
function computeGutters(kind, data, input, context, axisFontSize, labelFontSize) {
|
|
6081
|
+
if (kind === "pie" || kind === "donut") return { left: 0, bottom: 0 };
|
|
6082
|
+
if (kind === "bar") {
|
|
6083
|
+
const longest = data.labels.reduce((max, label) => Math.max(max, label.length), 0);
|
|
6084
|
+
const left = Math.min(
|
|
6085
|
+
context.viewport.width * 0.24,
|
|
6086
|
+
Math.max(labelFontSize * 3, longest * labelFontSize * 0.58 + 16)
|
|
6087
|
+
);
|
|
6088
|
+
return { left, bottom: axisFontSize * 2.4 };
|
|
6089
|
+
}
|
|
6090
|
+
const ticks = valueTicks(kind, data, input);
|
|
6091
|
+
const longestTick = ticks.ticks.reduce(
|
|
6092
|
+
(max, tick) => Math.max(max, formatChartValue(tick, input.unit).length),
|
|
6093
|
+
0
|
|
6094
|
+
);
|
|
6095
|
+
return {
|
|
6096
|
+
left: Math.max(axisFontSize * 2.5, longestTick * axisFontSize * 0.6 + 16),
|
|
6097
|
+
bottom: labelFontSize * 2.6
|
|
6098
|
+
};
|
|
6099
|
+
}
|
|
6100
|
+
function valueTicks(kind, data, input) {
|
|
6101
|
+
let values;
|
|
6102
|
+
if ((kind === "bar" || kind === "column") && input.stacked === true) {
|
|
6103
|
+
const extents = stackSeries(data.series).flat();
|
|
6104
|
+
values = extents.flatMap((e) => [e.y0, e.y1]);
|
|
6105
|
+
} else {
|
|
6106
|
+
values = data.series.flatMap((s) => s.values.filter((v) => v !== null));
|
|
6107
|
+
}
|
|
6108
|
+
if (values.length === 0) values = [0, 1];
|
|
6109
|
+
let min = Math.min(...values);
|
|
6110
|
+
let max = Math.max(...values);
|
|
6111
|
+
if (kind !== "line" && kind !== "scatter") {
|
|
6112
|
+
min = Math.min(0, min);
|
|
6113
|
+
max = Math.max(0, max);
|
|
6114
|
+
}
|
|
6115
|
+
return niceTicks(min, max, 5);
|
|
6116
|
+
}
|
|
6117
|
+
function paintValueAxisY(paint, scale, yFor) {
|
|
6118
|
+
const { frame, gridColor, axisTextColor, axisFontSize, bodyFont, input } = paint;
|
|
6119
|
+
const layers = [];
|
|
6120
|
+
scale.ticks.forEach((tick, index) => {
|
|
6121
|
+
const y = yFor(tick);
|
|
6122
|
+
layers.push({
|
|
6123
|
+
type: "shape",
|
|
6124
|
+
id: `grid-${index}`,
|
|
6125
|
+
content: {
|
|
6126
|
+
shape: "rect",
|
|
6127
|
+
fill: tick === 0 ? withAlpha(paint.context.theme.colors.text, 0.35) : gridColor
|
|
6128
|
+
},
|
|
6129
|
+
position: {
|
|
6130
|
+
x: frame.plot.x,
|
|
6131
|
+
y: y - (tick === 0 ? 1 : 0.5),
|
|
6132
|
+
width: frame.plot.width,
|
|
6133
|
+
height: tick === 0 ? 2 : 1
|
|
6134
|
+
}
|
|
6135
|
+
});
|
|
6136
|
+
layers.push({
|
|
6137
|
+
type: "text",
|
|
6138
|
+
id: `tick-${index}`,
|
|
6139
|
+
content: {
|
|
6140
|
+
text: formatChartValue(tick, input.unit),
|
|
6141
|
+
style: {
|
|
6142
|
+
fontSize: axisFontSize,
|
|
6143
|
+
fontFamily: bodyFont,
|
|
6144
|
+
color: axisTextColor,
|
|
6145
|
+
textAlign: "center"
|
|
6146
|
+
}
|
|
6147
|
+
},
|
|
6148
|
+
position: {
|
|
6149
|
+
x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
|
|
6150
|
+
y,
|
|
6151
|
+
anchor: "center"
|
|
6152
|
+
}
|
|
6153
|
+
});
|
|
6154
|
+
});
|
|
6155
|
+
return layers;
|
|
6156
|
+
}
|
|
6157
|
+
function paintCategoryAxisX(paint, xForCenter) {
|
|
6158
|
+
const { frame, data, labelFontSize, axisTextColor, bodyFont } = paint;
|
|
6159
|
+
const step = categoryLabelStep(data.labels.length, frame.plot.width, labelFontSize);
|
|
6160
|
+
const layers = [];
|
|
6161
|
+
data.labels.forEach((label, index) => {
|
|
6162
|
+
if (index % step !== 0 || !label) return;
|
|
6163
|
+
layers.push({
|
|
6164
|
+
type: "text",
|
|
6165
|
+
id: `cat-${index}`,
|
|
6166
|
+
content: {
|
|
6167
|
+
text: label,
|
|
6168
|
+
style: {
|
|
6169
|
+
fontSize: labelFontSize,
|
|
6170
|
+
fontFamily: bodyFont,
|
|
6171
|
+
color: axisTextColor,
|
|
6172
|
+
textAlign: "center"
|
|
6173
|
+
}
|
|
6174
|
+
},
|
|
6175
|
+
position: {
|
|
6176
|
+
x: xForCenter(index),
|
|
6177
|
+
y: frame.plot.y + frame.plot.height + labelFontSize * 1.2,
|
|
6178
|
+
anchor: "center"
|
|
6179
|
+
}
|
|
6180
|
+
});
|
|
6181
|
+
});
|
|
6182
|
+
return layers;
|
|
6183
|
+
}
|
|
6184
|
+
function paintColumns(paint) {
|
|
6185
|
+
const { frame, data, colors, input, entrance } = paint;
|
|
6186
|
+
const scale = valueTicks("column", data, input);
|
|
6187
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6188
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6189
|
+
const n = data.labels.length;
|
|
6190
|
+
const band = frame.plot.width / Math.max(1, n);
|
|
6191
|
+
const innerPad = band * 0.15;
|
|
6192
|
+
layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
|
|
6193
|
+
const stacked = input.stacked === true && data.series.length > 1;
|
|
6194
|
+
const extents = stacked ? stackSeries(data.series) : null;
|
|
6195
|
+
const groupWidth = band - innerPad * 2;
|
|
6196
|
+
const barWidth = stacked ? groupWidth : Math.max(2, (groupWidth - 4 * (data.series.length - 1)) / data.series.length);
|
|
6197
|
+
data.series.forEach((series, s) => {
|
|
6198
|
+
series.values.forEach((value, i) => {
|
|
6199
|
+
if (i >= n) return;
|
|
6200
|
+
const extent = extents ? extents[s][i] : value === null ? null : { y0: Math.min(0, value), y1: Math.max(0, value) };
|
|
6201
|
+
if (!extent || extent.y0 === extent.y1) return;
|
|
6202
|
+
const top = yFor(extent.y1);
|
|
6203
|
+
const height = Math.max(1, yFor(extent.y0) - top);
|
|
6204
|
+
const x = stacked ? frame.plot.x + i * band + innerPad : frame.plot.x + i * band + innerPad + s * (barWidth + 4);
|
|
6205
|
+
layers.push({
|
|
6206
|
+
type: "shape",
|
|
6207
|
+
id: `mark-${s}-${i}`,
|
|
6208
|
+
content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
|
|
6209
|
+
position: { x, y: top, width: barWidth, height },
|
|
6210
|
+
animation: entrance(i + s)
|
|
6211
|
+
});
|
|
6212
|
+
if (input.showValues === true && value !== null && !stacked) {
|
|
6213
|
+
layers.push(
|
|
6214
|
+
valueLabel(
|
|
6215
|
+
paint,
|
|
6216
|
+
`val-${s}-${i}`,
|
|
6217
|
+
value,
|
|
6218
|
+
x + barWidth / 2,
|
|
6219
|
+
value >= 0 ? top - paint.axisFontSize * 0.9 : top + height + paint.axisFontSize * 0.9
|
|
6220
|
+
)
|
|
6221
|
+
);
|
|
6222
|
+
}
|
|
6223
|
+
});
|
|
6224
|
+
});
|
|
6225
|
+
return layers;
|
|
6226
|
+
}
|
|
6227
|
+
function paintBars(paint) {
|
|
6228
|
+
const {
|
|
6229
|
+
frame,
|
|
6230
|
+
data,
|
|
6231
|
+
colors,
|
|
6232
|
+
input,
|
|
6233
|
+
entrance,
|
|
6234
|
+
labelFontSize,
|
|
6235
|
+
axisFontSize,
|
|
6236
|
+
bodyFont,
|
|
6237
|
+
axisTextColor,
|
|
6238
|
+
gridColor
|
|
6239
|
+
} = paint;
|
|
6240
|
+
const scale = valueTicks("bar", data, input);
|
|
6241
|
+
const labelGap = axisFontSize * 0.6;
|
|
6242
|
+
const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * 0.58;
|
|
6243
|
+
const showValueLabels = input.showValues === true && !(input.stacked === true);
|
|
6244
|
+
const reservedForLabels = showValueLabels ? data.series.flatMap((series) => series.values).filter((v) => v !== null).reduce((max, v) => Math.max(max, labelWidthPx(v)), 0) + labelGap : 0;
|
|
6245
|
+
const axisWidth = Math.max(20, frame.plot.width - reservedForLabels);
|
|
6246
|
+
const xFor = (v) => frame.plot.x + axisWidth * ((v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6247
|
+
const layers = [];
|
|
6248
|
+
scale.ticks.forEach((tick, index) => {
|
|
6249
|
+
const x = xFor(tick);
|
|
6250
|
+
layers.push({
|
|
6251
|
+
type: "shape",
|
|
6252
|
+
id: `grid-${index}`,
|
|
6253
|
+
content: {
|
|
6254
|
+
shape: "rect",
|
|
6255
|
+
fill: tick === 0 ? withAlpha(paint.context.theme.colors.text, 0.35) : gridColor
|
|
6256
|
+
},
|
|
6257
|
+
position: {
|
|
6258
|
+
x: x - (tick === 0 ? 1 : 0.5),
|
|
6259
|
+
y: frame.plot.y,
|
|
6260
|
+
width: tick === 0 ? 2 : 1,
|
|
6261
|
+
height: frame.plot.height
|
|
6262
|
+
}
|
|
6263
|
+
});
|
|
6264
|
+
layers.push({
|
|
6265
|
+
type: "text",
|
|
6266
|
+
id: `tick-${index}`,
|
|
6267
|
+
content: {
|
|
6268
|
+
text: formatChartValue(tick, input.unit),
|
|
6269
|
+
style: {
|
|
6270
|
+
fontSize: axisFontSize,
|
|
6271
|
+
fontFamily: bodyFont,
|
|
6272
|
+
color: axisTextColor,
|
|
6273
|
+
textAlign: "center"
|
|
6274
|
+
}
|
|
6275
|
+
},
|
|
6276
|
+
position: { x, y: frame.plot.y + frame.plot.height + axisFontSize * 1.2, anchor: "center" }
|
|
6277
|
+
});
|
|
6278
|
+
});
|
|
6279
|
+
const n = data.labels.length;
|
|
6280
|
+
const band = frame.plot.height / Math.max(1, n);
|
|
6281
|
+
const innerPad = band * 0.15;
|
|
6282
|
+
const stacked = input.stacked === true && data.series.length > 1;
|
|
6283
|
+
const extents = stacked ? stackSeries(data.series) : null;
|
|
6284
|
+
const groupHeight = band - innerPad * 2;
|
|
6285
|
+
const barHeight = stacked ? groupHeight : Math.max(2, (groupHeight - 4 * (data.series.length - 1)) / data.series.length);
|
|
6286
|
+
data.labels.forEach((label, i) => {
|
|
6287
|
+
if (!label) return;
|
|
6288
|
+
layers.push({
|
|
6289
|
+
type: "text",
|
|
6290
|
+
id: `cat-${i}`,
|
|
6291
|
+
content: {
|
|
6292
|
+
text: label,
|
|
6293
|
+
style: {
|
|
6294
|
+
fontSize: labelFontSize,
|
|
6295
|
+
fontFamily: bodyFont,
|
|
6296
|
+
color: axisTextColor,
|
|
6297
|
+
textAlign: "center"
|
|
6298
|
+
}
|
|
6299
|
+
},
|
|
6300
|
+
position: {
|
|
6301
|
+
x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
|
|
6302
|
+
y: frame.plot.y + (i + 0.5) * band,
|
|
6303
|
+
anchor: "center"
|
|
6304
|
+
}
|
|
6305
|
+
});
|
|
6306
|
+
});
|
|
6307
|
+
data.series.forEach((series, s) => {
|
|
6308
|
+
series.values.forEach((value, i) => {
|
|
6309
|
+
if (i >= n) return;
|
|
6310
|
+
const extent = extents ? extents[s][i] : value === null ? null : { y0: Math.min(0, value), y1: Math.max(0, value) };
|
|
6311
|
+
if (!extent || extent.y0 === extent.y1) return;
|
|
6312
|
+
const left = xFor(extent.y0);
|
|
6313
|
+
const width = Math.max(1, xFor(extent.y1) - left);
|
|
6314
|
+
const y = stacked ? frame.plot.y + i * band + innerPad : frame.plot.y + i * band + innerPad + s * (barHeight + 4);
|
|
6315
|
+
layers.push({
|
|
6316
|
+
type: "shape",
|
|
6317
|
+
id: `mark-${s}-${i}`,
|
|
6318
|
+
content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
|
|
6319
|
+
position: { x: left, y, width, height: barHeight },
|
|
6320
|
+
animation: entrance(i + s)
|
|
6321
|
+
});
|
|
6322
|
+
if (showValueLabels && value !== null) {
|
|
6323
|
+
const half = labelWidthPx(value) / 2;
|
|
6324
|
+
const labelX = value >= 0 ? left + width + labelGap + half : left - labelGap - half;
|
|
6325
|
+
layers.push(valueLabel(paint, `val-${s}-${i}`, value, labelX, y + barHeight / 2));
|
|
6326
|
+
}
|
|
6327
|
+
});
|
|
6328
|
+
});
|
|
6329
|
+
return layers;
|
|
6330
|
+
}
|
|
6331
|
+
function paintRadial(paint) {
|
|
6332
|
+
const { kind, frame, data, colors, input, entrance, context } = paint;
|
|
6333
|
+
const slices = data.labels.map((label, index) => ({ label, value: data.series[0].values[index], index })).filter(
|
|
6334
|
+
(s) => s.value !== null && Number.isFinite(s.value) && s.value > 0
|
|
6335
|
+
);
|
|
6336
|
+
const total = slices.reduce((sum, s) => sum + s.value, 0);
|
|
6337
|
+
const cx = frame.chartArea.x + frame.chartArea.width / 2;
|
|
6338
|
+
const cy = frame.chartArea.y + frame.chartArea.height / 2;
|
|
6339
|
+
const rOuter = Math.min(frame.chartArea.width, frame.chartArea.height) * 0.42;
|
|
6340
|
+
const rInner = kind === "donut" ? rOuter * 0.55 : 0;
|
|
6341
|
+
const layers = [];
|
|
6342
|
+
let startDeg = 0;
|
|
6343
|
+
slices.forEach((slice, order) => {
|
|
6344
|
+
const sweep = slice.value / total * 360;
|
|
6345
|
+
layers.push({
|
|
6346
|
+
type: "path",
|
|
6347
|
+
id: `mark-${slice.index}`,
|
|
6348
|
+
content: {
|
|
6349
|
+
d: arcPath(cx, cy, rOuter, rInner, startDeg, startDeg + sweep),
|
|
6350
|
+
fill: colors[slice.index % colors.length],
|
|
6351
|
+
stroke: context.theme.colors.background,
|
|
6352
|
+
strokeWidth: 2
|
|
6353
|
+
},
|
|
6354
|
+
position: { x: cx - rOuter, y: cy - rOuter, width: rOuter * 2, height: rOuter * 2 },
|
|
6355
|
+
animation: entrance(order)
|
|
6356
|
+
});
|
|
6357
|
+
if (input.showValues === true && sweep >= 14) {
|
|
6358
|
+
const mid = startDeg + sweep / 2;
|
|
6359
|
+
const rad = (mid - 90) * Math.PI / 180;
|
|
6360
|
+
const rLabel = rInner > 0 ? (rInner + rOuter) / 2 : rOuter * 0.68;
|
|
6361
|
+
const pctText = `${Math.round(slice.value / total * 100)}%`;
|
|
6362
|
+
layers.push({
|
|
6363
|
+
type: "text",
|
|
6364
|
+
id: `val-${slice.index}`,
|
|
6365
|
+
content: {
|
|
6366
|
+
text: pctText,
|
|
6367
|
+
style: {
|
|
6368
|
+
fontSize: paint.axisFontSize,
|
|
6369
|
+
fontFamily: paint.bodyFont,
|
|
6370
|
+
fontWeight: "bold",
|
|
6371
|
+
color: pickContrastingText(colors[slice.index % colors.length]),
|
|
6372
|
+
textAlign: "center"
|
|
6373
|
+
}
|
|
6374
|
+
},
|
|
6375
|
+
position: {
|
|
6376
|
+
x: cx + rLabel * Math.cos(rad),
|
|
6377
|
+
y: cy + rLabel * Math.sin(rad),
|
|
6378
|
+
anchor: "center"
|
|
6379
|
+
},
|
|
6380
|
+
animation: entrance(order)
|
|
6381
|
+
});
|
|
6382
|
+
}
|
|
6383
|
+
startDeg += sweep;
|
|
6384
|
+
});
|
|
6385
|
+
return layers;
|
|
6386
|
+
}
|
|
6387
|
+
function paintLines(paint) {
|
|
6388
|
+
const { kind, frame, data, colors, input, entrance } = paint;
|
|
6389
|
+
const scale = valueTicks(kind, data, input);
|
|
6390
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6391
|
+
const n = data.labels.length;
|
|
6392
|
+
const band = frame.plot.width / Math.max(1, n);
|
|
6393
|
+
const xForCenter = (i) => frame.plot.x + (i + 0.5) * band;
|
|
6394
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6395
|
+
layers.push(...paintCategoryAxisX(paint, xForCenter));
|
|
6396
|
+
const baselineY = yFor(Math.max(scale.niceMin, Math.min(scale.niceMax, 0)));
|
|
6397
|
+
const showPoints = n <= 30;
|
|
6398
|
+
data.series.forEach((series, s) => {
|
|
6399
|
+
const segments = [];
|
|
6400
|
+
let current = [];
|
|
6401
|
+
series.values.forEach((value, i) => {
|
|
6402
|
+
if (i >= n) return;
|
|
6403
|
+
if (value === null) {
|
|
6404
|
+
if (current.length > 0) segments.push(current);
|
|
6405
|
+
current = [];
|
|
6406
|
+
return;
|
|
6407
|
+
}
|
|
6408
|
+
current.push({ x: xForCenter(i), y: yFor(value) });
|
|
6409
|
+
});
|
|
6410
|
+
if (current.length > 0) segments.push(current);
|
|
6411
|
+
segments.forEach((segment, seg) => {
|
|
6412
|
+
if (kind === "area" && segment.length > 1) {
|
|
6413
|
+
layers.push({
|
|
6414
|
+
type: "path",
|
|
6415
|
+
id: `area-${s}-${seg}`,
|
|
6416
|
+
content: {
|
|
6417
|
+
d: areaPath(segment, baselineY),
|
|
6418
|
+
fill: colors[s],
|
|
6419
|
+
fillOpacity: 0.25,
|
|
6420
|
+
stroke: "none",
|
|
6421
|
+
strokeWidth: 0
|
|
6422
|
+
},
|
|
6423
|
+
position: {
|
|
6424
|
+
x: frame.plot.x,
|
|
6425
|
+
y: frame.plot.y,
|
|
6426
|
+
width: frame.plot.width,
|
|
6427
|
+
height: frame.plot.height
|
|
6428
|
+
},
|
|
6429
|
+
animation: entrance(s)
|
|
6430
|
+
});
|
|
6431
|
+
}
|
|
6432
|
+
if (segment.length > 1) {
|
|
6433
|
+
layers.push({
|
|
6434
|
+
type: "path",
|
|
6435
|
+
id: `line-${s}-${seg}`,
|
|
6436
|
+
content: { d: polylinePath(segment), stroke: colors[s], strokeWidth: 4, fill: "none" },
|
|
6437
|
+
position: {
|
|
6438
|
+
x: frame.plot.x,
|
|
6439
|
+
y: frame.plot.y,
|
|
6440
|
+
width: frame.plot.width,
|
|
6441
|
+
height: frame.plot.height
|
|
6442
|
+
},
|
|
6443
|
+
animation: entrance(s)
|
|
6444
|
+
});
|
|
6445
|
+
}
|
|
6446
|
+
});
|
|
6447
|
+
if (showPoints) {
|
|
6448
|
+
series.values.forEach((value, i) => {
|
|
6449
|
+
if (i >= n || value === null) return;
|
|
6450
|
+
const r = 5;
|
|
6451
|
+
layers.push({
|
|
6452
|
+
type: "shape",
|
|
6453
|
+
id: `pt-${s}-${i}`,
|
|
6454
|
+
content: { shape: "circle", fill: colors[s] },
|
|
6455
|
+
position: { x: xForCenter(i) - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
|
|
6456
|
+
animation: entrance(s)
|
|
6457
|
+
});
|
|
6458
|
+
if (input.showValues === true) {
|
|
6459
|
+
layers.push(
|
|
6460
|
+
valueLabel(
|
|
6461
|
+
paint,
|
|
6462
|
+
`val-${s}-${i}`,
|
|
6463
|
+
value,
|
|
6464
|
+
xForCenter(i),
|
|
6465
|
+
yFor(value) - paint.axisFontSize
|
|
6466
|
+
)
|
|
6467
|
+
);
|
|
6468
|
+
}
|
|
6469
|
+
});
|
|
6470
|
+
}
|
|
6471
|
+
});
|
|
6472
|
+
return layers;
|
|
6473
|
+
}
|
|
6474
|
+
function paintScatter(paint) {
|
|
6475
|
+
const { frame, data, colors, input, entrance } = paint;
|
|
6476
|
+
const scale = valueTicks("scatter", data, input);
|
|
6477
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6478
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6479
|
+
const numericCount = data.labelNumbers.filter((v) => v !== null).length;
|
|
6480
|
+
const numericX = data.labels.length > 0 && numericCount / data.labels.length >= 0.6;
|
|
6481
|
+
let xFor;
|
|
6482
|
+
if (numericX) {
|
|
6483
|
+
const xs = data.labelNumbers.filter((v) => v !== null);
|
|
6484
|
+
const xScale = niceTicks(Math.min(...xs), Math.max(...xs), 5);
|
|
6485
|
+
const span = xScale.niceMax - xScale.niceMin;
|
|
6486
|
+
xFor = (i) => {
|
|
6487
|
+
const v = data.labelNumbers[i];
|
|
6488
|
+
return v === null ? null : frame.plot.x + frame.plot.width * ((v - xScale.niceMin) / span);
|
|
6489
|
+
};
|
|
6490
|
+
xScale.ticks.forEach((tick, index) => {
|
|
6491
|
+
const x = frame.plot.x + frame.plot.width * ((tick - xScale.niceMin) / span);
|
|
6492
|
+
layers.push({
|
|
6493
|
+
type: "shape",
|
|
6494
|
+
id: `xgrid-${index}`,
|
|
6495
|
+
content: { shape: "rect", fill: paint.gridColor },
|
|
6496
|
+
position: { x: x - 0.5, y: frame.plot.y, width: 1, height: frame.plot.height }
|
|
6497
|
+
});
|
|
6498
|
+
layers.push({
|
|
6499
|
+
type: "text",
|
|
6500
|
+
id: `xtick-${index}`,
|
|
6501
|
+
content: {
|
|
6502
|
+
text: formatChartValue(tick),
|
|
6503
|
+
style: {
|
|
6504
|
+
fontSize: paint.axisFontSize,
|
|
6505
|
+
fontFamily: paint.bodyFont,
|
|
6506
|
+
color: paint.axisTextColor,
|
|
6507
|
+
textAlign: "center"
|
|
6508
|
+
}
|
|
6509
|
+
},
|
|
6510
|
+
position: {
|
|
6511
|
+
x,
|
|
6512
|
+
y: frame.plot.y + frame.plot.height + paint.axisFontSize * 1.2,
|
|
6513
|
+
anchor: "center"
|
|
6514
|
+
}
|
|
6515
|
+
});
|
|
6516
|
+
});
|
|
6517
|
+
} else {
|
|
6518
|
+
const band = frame.plot.width / Math.max(1, data.labels.length);
|
|
6519
|
+
xFor = (i) => frame.plot.x + (i + 0.5) * band;
|
|
6520
|
+
layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
|
|
6521
|
+
}
|
|
6522
|
+
const r = 7;
|
|
6523
|
+
data.series.forEach((series, s) => {
|
|
6524
|
+
series.values.forEach((value, i) => {
|
|
6525
|
+
if (value === null) return;
|
|
6526
|
+
const x = xFor(i);
|
|
6527
|
+
if (x === null) return;
|
|
6528
|
+
layers.push({
|
|
6529
|
+
type: "shape",
|
|
6530
|
+
id: `mark-${s}-${i}`,
|
|
6531
|
+
content: { shape: "circle", fill: colors[s], fillOpacity: 0.85 },
|
|
6532
|
+
position: { x: x - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
|
|
6533
|
+
animation: entrance(Math.floor(i / 3) + s)
|
|
6534
|
+
});
|
|
6535
|
+
});
|
|
6536
|
+
});
|
|
6537
|
+
return layers;
|
|
6538
|
+
}
|
|
6539
|
+
function paintLegend(paint, legend, band, fontSize) {
|
|
6540
|
+
const layers = [];
|
|
6541
|
+
legend.items.forEach((item, index) => {
|
|
6542
|
+
const color = paint.colors[item.index % paint.colors.length];
|
|
6543
|
+
const rowCenterY = band.y + item.y + legend.rowHeight / 2;
|
|
6544
|
+
layers.push({
|
|
6545
|
+
type: "shape",
|
|
6546
|
+
id: `legend-swatch-${index}`,
|
|
6547
|
+
content: { shape: "rect", fill: color, borderRadius: 3 },
|
|
6548
|
+
position: {
|
|
6549
|
+
x: band.x + item.x,
|
|
6550
|
+
y: rowCenterY - item.swatchSize / 2,
|
|
6551
|
+
width: item.swatchSize,
|
|
6552
|
+
height: item.swatchSize
|
|
6553
|
+
}
|
|
6554
|
+
});
|
|
6555
|
+
layers.push({
|
|
6556
|
+
type: "text",
|
|
6557
|
+
id: `legend-label-${index}`,
|
|
6558
|
+
content: {
|
|
6559
|
+
text: item.label,
|
|
6560
|
+
style: { fontSize, fontFamily: paint.bodyFont, color: paint.context.theme.colors.text }
|
|
6561
|
+
},
|
|
6562
|
+
position: {
|
|
6563
|
+
x: band.x + item.x + item.swatchSize + fontSize * 0.5,
|
|
6564
|
+
y: rowCenterY - fontSize * 0.62
|
|
6565
|
+
}
|
|
6566
|
+
});
|
|
6567
|
+
});
|
|
6568
|
+
return layers;
|
|
6569
|
+
}
|
|
6570
|
+
function valueLabel(paint, id, value, x, y) {
|
|
6571
|
+
return {
|
|
6572
|
+
type: "text",
|
|
6573
|
+
id,
|
|
6574
|
+
content: {
|
|
6575
|
+
text: formatChartValue(value, paint.input.unit),
|
|
6576
|
+
style: {
|
|
6577
|
+
fontSize: paint.axisFontSize,
|
|
6578
|
+
fontFamily: paint.bodyFont,
|
|
6579
|
+
fontWeight: "bold",
|
|
6580
|
+
color: paint.context.theme.colors.text,
|
|
6581
|
+
textAlign: "center"
|
|
6582
|
+
}
|
|
6583
|
+
},
|
|
6584
|
+
position: { x, y, anchor: "center" }
|
|
6585
|
+
};
|
|
6586
|
+
}
|
|
6587
|
+
function tableLayer(table, band, fontSize, input, context) {
|
|
6588
|
+
const { theme } = context;
|
|
6589
|
+
const colors = resolveColorScheme(context, input.colorScheme);
|
|
6590
|
+
return {
|
|
6591
|
+
type: "table",
|
|
6592
|
+
id: "table",
|
|
6593
|
+
content: {
|
|
6594
|
+
headers: table.headers,
|
|
6595
|
+
rows: table.rows,
|
|
6596
|
+
align: table.align,
|
|
6597
|
+
style: {
|
|
6598
|
+
headerBackground: colors.accent,
|
|
6599
|
+
headerColor: pickContrastingText(colors.accent),
|
|
6600
|
+
cellBackground: withAlpha(theme.colors.text, 0.04),
|
|
6601
|
+
cellColor: theme.colors.text,
|
|
6602
|
+
borderColor: withAlpha(theme.colors.text, 0.15),
|
|
6603
|
+
fontSize,
|
|
6604
|
+
fontFamily: getThemeFont(context, "body"),
|
|
6605
|
+
headerFontFamily: getThemeFont(context, "title"),
|
|
6606
|
+
borderRadius: 8
|
|
6607
|
+
}
|
|
6608
|
+
},
|
|
6609
|
+
position: { x: band.x, y: band.y, width: band.width, height: band.height },
|
|
6610
|
+
animation: { type: "fadeIn", duration: 1, delay: 0.4 }
|
|
6611
|
+
};
|
|
6612
|
+
}
|
|
6613
|
+
|
|
6614
|
+
// src/doc/templates/chartBlock.ts
|
|
6615
|
+
function barChart(input, context) {
|
|
6616
|
+
return renderChart("bar", input, context);
|
|
6617
|
+
}
|
|
6618
|
+
function columnChart(input, context) {
|
|
6619
|
+
return renderChart("column", input, context);
|
|
6620
|
+
}
|
|
6621
|
+
function pieChart(input, context) {
|
|
6622
|
+
return renderChart("pie", input, context);
|
|
6623
|
+
}
|
|
6624
|
+
function donutChart(input, context) {
|
|
6625
|
+
return renderChart("donut", input, context);
|
|
6626
|
+
}
|
|
6627
|
+
function lineChart(input, context) {
|
|
6628
|
+
return renderChart("line", input, context);
|
|
6629
|
+
}
|
|
6630
|
+
function areaChart(input, context) {
|
|
6631
|
+
return renderChart("area", input, context);
|
|
6632
|
+
}
|
|
6633
|
+
function scatterChart(input, context) {
|
|
6634
|
+
return renderChart("scatter", input, context);
|
|
6635
|
+
}
|
|
6636
|
+
|
|
5588
6637
|
// src/doc/templates/registry.ts
|
|
5589
6638
|
var templateRegistry = {
|
|
5590
6639
|
title: titleBlock,
|
|
@@ -5608,6 +6657,13 @@ var templateRegistry = {
|
|
|
5608
6657
|
videoWithCaption,
|
|
5609
6658
|
videoPullQuote,
|
|
5610
6659
|
dataTable,
|
|
6660
|
+
barChart,
|
|
6661
|
+
columnChart,
|
|
6662
|
+
pieChart,
|
|
6663
|
+
donutChart,
|
|
6664
|
+
lineChart,
|
|
6665
|
+
areaChart,
|
|
6666
|
+
scatterChart,
|
|
5611
6667
|
diagram: diagramBlock,
|
|
5612
6668
|
tree: treeBlock,
|
|
5613
6669
|
timeline: timelineBlock,
|
|
@@ -5723,6 +6779,49 @@ var CAPTION_POSITION_VALUES = ["bottom", "top", "center"];
|
|
|
5723
6779
|
var MAP_STYLE_VALUES = ["terrain", "satellite", "road", "toner", "watercolor"];
|
|
5724
6780
|
var DATE_EVENT_MOOD_VALUES = ["neutral", "somber", "celebratory"];
|
|
5725
6781
|
var COLOR_SCHEME_HINT = "theme color-scheme name \u2014 e.g. 'blue', 'green'";
|
|
6782
|
+
function CHART_DESCRIPTORS(extra = []) {
|
|
6783
|
+
return [
|
|
6784
|
+
{ key: "title", description: "Optional title above the chart" },
|
|
6785
|
+
{
|
|
6786
|
+
key: "labelColumn",
|
|
6787
|
+
description: "Category/label column",
|
|
6788
|
+
valueHint: "header name or 0-based index"
|
|
6789
|
+
},
|
|
6790
|
+
{
|
|
6791
|
+
key: "valueColumns",
|
|
6792
|
+
description: "Value columns to plot",
|
|
6793
|
+
coerce: "stringList",
|
|
6794
|
+
valueHint: "comma-separated header names or 0-based indexes"
|
|
6795
|
+
},
|
|
6796
|
+
{
|
|
6797
|
+
key: "showTable",
|
|
6798
|
+
description: "Render the source table beneath the chart",
|
|
6799
|
+
coerce: "boolean"
|
|
6800
|
+
},
|
|
6801
|
+
{ key: "showLegend", description: "Show a series legend", coerce: "boolean" },
|
|
6802
|
+
{ key: "showValues", description: "Print numeric value labels on marks", coerce: "boolean" },
|
|
6803
|
+
{ key: "unit", description: 'Unit suffix for tick/value labels (e.g. "km")' },
|
|
6804
|
+
{
|
|
6805
|
+
key: "colorScheme",
|
|
6806
|
+
description: "Color scheme seeding the series color rotation",
|
|
6807
|
+
valueHint: COLOR_SCHEME_HINT
|
|
6808
|
+
},
|
|
6809
|
+
// `headers`/`rows` are normally derived from the block's markdown table;
|
|
6810
|
+
// declared so data-fence-supplied values aren't flagged as unknown.
|
|
6811
|
+
{
|
|
6812
|
+
key: "headers",
|
|
6813
|
+
description: "Header cells (normally derived from the table)",
|
|
6814
|
+
coerce: "stringList"
|
|
6815
|
+
},
|
|
6816
|
+
{ key: "rows", description: "Table rows (supply via a data fence)" },
|
|
6817
|
+
...extra
|
|
6818
|
+
];
|
|
6819
|
+
}
|
|
6820
|
+
var STACKED_DESCRIPTOR = {
|
|
6821
|
+
key: "stacked",
|
|
6822
|
+
description: "Stack series instead of grouping them side by side",
|
|
6823
|
+
coerce: "boolean"
|
|
6824
|
+
};
|
|
5726
6825
|
var BASE_INPUT_DESCRIPTORS = [
|
|
5727
6826
|
{
|
|
5728
6827
|
key: "imageTreatment",
|
|
@@ -5844,6 +6943,13 @@ var TEMPLATE_INPUT_DESCRIPTORS = {
|
|
|
5844
6943
|
{ key: "align", description: "Per-column alignment (supply via a data fence)" },
|
|
5845
6944
|
{ key: "colorScheme", description: "Header color scheme", valueHint: COLOR_SCHEME_HINT }
|
|
5846
6945
|
],
|
|
6946
|
+
barChart: CHART_DESCRIPTORS([STACKED_DESCRIPTOR]),
|
|
6947
|
+
columnChart: CHART_DESCRIPTORS([STACKED_DESCRIPTOR]),
|
|
6948
|
+
pieChart: CHART_DESCRIPTORS(),
|
|
6949
|
+
donutChart: CHART_DESCRIPTORS(),
|
|
6950
|
+
lineChart: CHART_DESCRIPTORS(),
|
|
6951
|
+
areaChart: CHART_DESCRIPTORS(),
|
|
6952
|
+
scatterChart: CHART_DESCRIPTORS(),
|
|
5847
6953
|
tree: [
|
|
5848
6954
|
{ key: "title", description: "Optional title above the tree" },
|
|
5849
6955
|
{ key: "colorScheme", description: "Folder-row color scheme", valueHint: COLOR_SCHEME_HINT },
|
|
@@ -6315,6 +7421,55 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
|
|
|
6315
7421
|
unconsumedMedia: "reserved-slot",
|
|
6316
7422
|
variants: DENSE_VARIANTS
|
|
6317
7423
|
},
|
|
7424
|
+
barChart: {
|
|
7425
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7426
|
+
noMedia: "template-default",
|
|
7427
|
+
ownership: "supplemental",
|
|
7428
|
+
unconsumedMedia: "reserved-slot",
|
|
7429
|
+
variants: DENSE_VARIANTS
|
|
7430
|
+
},
|
|
7431
|
+
columnChart: {
|
|
7432
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7433
|
+
noMedia: "template-default",
|
|
7434
|
+
ownership: "supplemental",
|
|
7435
|
+
unconsumedMedia: "reserved-slot",
|
|
7436
|
+
variants: DENSE_VARIANTS
|
|
7437
|
+
},
|
|
7438
|
+
pieChart: {
|
|
7439
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7440
|
+
noMedia: "template-default",
|
|
7441
|
+
ownership: "supplemental",
|
|
7442
|
+
unconsumedMedia: "reserved-slot",
|
|
7443
|
+
variants: DENSE_VARIANTS
|
|
7444
|
+
},
|
|
7445
|
+
donutChart: {
|
|
7446
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7447
|
+
noMedia: "template-default",
|
|
7448
|
+
ownership: "supplemental",
|
|
7449
|
+
unconsumedMedia: "reserved-slot",
|
|
7450
|
+
variants: DENSE_VARIANTS
|
|
7451
|
+
},
|
|
7452
|
+
lineChart: {
|
|
7453
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7454
|
+
noMedia: "template-default",
|
|
7455
|
+
ownership: "supplemental",
|
|
7456
|
+
unconsumedMedia: "reserved-slot",
|
|
7457
|
+
variants: DENSE_VARIANTS
|
|
7458
|
+
},
|
|
7459
|
+
areaChart: {
|
|
7460
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7461
|
+
noMedia: "template-default",
|
|
7462
|
+
ownership: "supplemental",
|
|
7463
|
+
unconsumedMedia: "reserved-slot",
|
|
7464
|
+
variants: DENSE_VARIANTS
|
|
7465
|
+
},
|
|
7466
|
+
scatterChart: {
|
|
7467
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7468
|
+
noMedia: "template-default",
|
|
7469
|
+
ownership: "supplemental",
|
|
7470
|
+
unconsumedMedia: "reserved-slot",
|
|
7471
|
+
variants: DENSE_VARIANTS
|
|
7472
|
+
},
|
|
6318
7473
|
diagram: {
|
|
6319
7474
|
summary: "The node-and-edge canvas is intrinsic media and retains its spatial coordinate system.",
|
|
6320
7475
|
noMedia: "intrinsic-visual",
|
|
@@ -7300,6 +8455,34 @@ var TEMPLATE_METADATA = {
|
|
|
7300
8455
|
label: "Data Table",
|
|
7301
8456
|
description: "Renders tabular data in a clean, styled table with a header row."
|
|
7302
8457
|
},
|
|
8458
|
+
barChart: {
|
|
8459
|
+
label: "Bar Chart",
|
|
8460
|
+
description: "Horizontal bars from the block's markdown table \u2014 one bar per row."
|
|
8461
|
+
},
|
|
8462
|
+
columnChart: {
|
|
8463
|
+
label: "Column Chart",
|
|
8464
|
+
description: "Vertical columns from the block's markdown table \u2014 one column per row."
|
|
8465
|
+
},
|
|
8466
|
+
pieChart: {
|
|
8467
|
+
label: "Pie Chart",
|
|
8468
|
+
description: "A pie of the block's markdown table \u2014 each row becomes a labeled slice."
|
|
8469
|
+
},
|
|
8470
|
+
donutChart: {
|
|
8471
|
+
label: "Donut Chart",
|
|
8472
|
+
description: "A donut of the block's markdown table \u2014 each row becomes a labeled slice."
|
|
8473
|
+
},
|
|
8474
|
+
lineChart: {
|
|
8475
|
+
label: "Line Chart",
|
|
8476
|
+
description: "Lines from the block's markdown table \u2014 one line per value column."
|
|
8477
|
+
},
|
|
8478
|
+
areaChart: {
|
|
8479
|
+
label: "Area Chart",
|
|
8480
|
+
description: "Filled area lines from the block's markdown table \u2014 one area per value column."
|
|
8481
|
+
},
|
|
8482
|
+
scatterChart: {
|
|
8483
|
+
label: "Scatter Chart",
|
|
8484
|
+
description: "An x/y scatter plot from the block's markdown table's numeric columns."
|
|
8485
|
+
},
|
|
7303
8486
|
diagram: {
|
|
7304
8487
|
label: "Diagram",
|
|
7305
8488
|
description: "Renders sub-headings as connected nodes \u2014 edit visually as a node-and-edge diagram."
|
|
@@ -7445,6 +8628,48 @@ var TEMPLATE_AUTHORING_METADATA = {
|
|
|
7445
8628
|
safeForContentFirst: false,
|
|
7446
8629
|
placement: "heading"
|
|
7447
8630
|
},
|
|
8631
|
+
barChart: {
|
|
8632
|
+
role: "data",
|
|
8633
|
+
bodyPolicy: "structured",
|
|
8634
|
+
safeForContentFirst: false,
|
|
8635
|
+
placement: "heading"
|
|
8636
|
+
},
|
|
8637
|
+
columnChart: {
|
|
8638
|
+
role: "data",
|
|
8639
|
+
bodyPolicy: "structured",
|
|
8640
|
+
safeForContentFirst: false,
|
|
8641
|
+
placement: "heading"
|
|
8642
|
+
},
|
|
8643
|
+
pieChart: {
|
|
8644
|
+
role: "data",
|
|
8645
|
+
bodyPolicy: "structured",
|
|
8646
|
+
safeForContentFirst: false,
|
|
8647
|
+
placement: "heading"
|
|
8648
|
+
},
|
|
8649
|
+
donutChart: {
|
|
8650
|
+
role: "data",
|
|
8651
|
+
bodyPolicy: "structured",
|
|
8652
|
+
safeForContentFirst: false,
|
|
8653
|
+
placement: "heading"
|
|
8654
|
+
},
|
|
8655
|
+
lineChart: {
|
|
8656
|
+
role: "data",
|
|
8657
|
+
bodyPolicy: "structured",
|
|
8658
|
+
safeForContentFirst: false,
|
|
8659
|
+
placement: "heading"
|
|
8660
|
+
},
|
|
8661
|
+
areaChart: {
|
|
8662
|
+
role: "data",
|
|
8663
|
+
bodyPolicy: "structured",
|
|
8664
|
+
safeForContentFirst: false,
|
|
8665
|
+
placement: "heading"
|
|
8666
|
+
},
|
|
8667
|
+
scatterChart: {
|
|
8668
|
+
role: "data",
|
|
8669
|
+
bodyPolicy: "structured",
|
|
8670
|
+
safeForContentFirst: false,
|
|
8671
|
+
placement: "heading"
|
|
8672
|
+
},
|
|
7448
8673
|
diagram: {
|
|
7449
8674
|
role: "spatial",
|
|
7450
8675
|
bodyPolicy: "structured",
|
|
@@ -8309,6 +9534,18 @@ function time(raw) {
|
|
|
8309
9534
|
if (raw == null) return null;
|
|
8310
9535
|
return parseTimeSeconds(raw);
|
|
8311
9536
|
}
|
|
9537
|
+
function videoPlacement(params) {
|
|
9538
|
+
const explicit = params.placement ?? params.presentation;
|
|
9539
|
+
if (explicit === "content") return "content";
|
|
9540
|
+
if (explicit === "pip" || explicit === "picture-in-picture") return "picture-in-picture";
|
|
9541
|
+
if (explicit === "overlay" || explicit === "full-frame") return "overlay";
|
|
9542
|
+
if (params.pip === "" || params.pip === "true") return "picture-in-picture";
|
|
9543
|
+
if (params.overlay === "" || params.overlay === "true") return "overlay";
|
|
9544
|
+
return void 0;
|
|
9545
|
+
}
|
|
9546
|
+
function lockToBlock(raw) {
|
|
9547
|
+
return raw !== "false" && raw !== "0";
|
|
9548
|
+
}
|
|
8312
9549
|
function toMediaClip(parsed, id) {
|
|
8313
9550
|
const { template, params } = parsed;
|
|
8314
9551
|
if (!template || !MEDIA_TEMPLATES.has(template)) return null;
|
|
@@ -8323,13 +9560,78 @@ function toMediaClip(parsed, id) {
|
|
|
8323
9560
|
startAt: time(params.startAt ?? params.startTime) ?? 0,
|
|
8324
9561
|
anchor
|
|
8325
9562
|
};
|
|
9563
|
+
const placement = kind === "video" ? videoPlacement(params) : void 0;
|
|
9564
|
+
if (placement && placement !== "content") clip.placement = placement;
|
|
9565
|
+
if (kind === "video" && placement && placement !== "content" && params.lockToBlock != null) {
|
|
9566
|
+
const locked = lockToBlock(params.lockToBlock);
|
|
9567
|
+
clip.lockToBlock = locked;
|
|
9568
|
+
clip.anchor = locked ? "block" : "document";
|
|
9569
|
+
if (locked) clip.startAt = 0;
|
|
9570
|
+
}
|
|
8326
9571
|
const clipStart = time(params.clipStart);
|
|
8327
9572
|
const clipEnd = time(params.clipEnd);
|
|
8328
9573
|
if (clipStart != null) clip.clipStart = clipStart;
|
|
8329
9574
|
if (clipEnd != null) clip.clipEnd = clipEnd;
|
|
9575
|
+
if (clip.lockToBlock === true) delete clip.clipEnd;
|
|
8330
9576
|
if (params.spillover === "true" || params.spillover === "") clip.spillover = true;
|
|
8331
9577
|
return clip;
|
|
8332
9578
|
}
|
|
9579
|
+
function firstVideoElement(children) {
|
|
9580
|
+
const visit = (nodes) => {
|
|
9581
|
+
for (const child of nodes) {
|
|
9582
|
+
if (child.type !== "htmlElement") continue;
|
|
9583
|
+
if (child.tagName === "video") return child;
|
|
9584
|
+
const nested = visit(child.children);
|
|
9585
|
+
if (nested) return nested;
|
|
9586
|
+
}
|
|
9587
|
+
return null;
|
|
9588
|
+
};
|
|
9589
|
+
return visit(children);
|
|
9590
|
+
}
|
|
9591
|
+
function standaloneHtmlVideo(node) {
|
|
9592
|
+
if (node.type === "htmlBlock") {
|
|
9593
|
+
if (!/^\s*<video\b/i.test(node.rawHtml)) return null;
|
|
9594
|
+
const element = firstVideoElement(node.htmlChildren);
|
|
9595
|
+
return element ? { element, raw: node.rawHtml.trim() } : null;
|
|
9596
|
+
}
|
|
9597
|
+
if (node.type !== "paragraph" || !node.children.every((child) => child.type === "htmlInline")) {
|
|
9598
|
+
return null;
|
|
9599
|
+
}
|
|
9600
|
+
const raw = node.children.map((child) => child.type === "htmlInline" ? child.rawHtml : "").join("");
|
|
9601
|
+
if (!/^\s*<video\b[\s\S]*<\/video>\s*$/i.test(raw)) return null;
|
|
9602
|
+
for (const child of node.children) {
|
|
9603
|
+
if (child.type !== "htmlInline") continue;
|
|
9604
|
+
const element = firstVideoElement(child.htmlChildren);
|
|
9605
|
+
if (element) return { element, raw: raw.trim() };
|
|
9606
|
+
}
|
|
9607
|
+
return null;
|
|
9608
|
+
}
|
|
9609
|
+
function htmlVideoClip(node, id) {
|
|
9610
|
+
const authored = standaloneHtmlVideo(node);
|
|
9611
|
+
if (!authored) return null;
|
|
9612
|
+
const { element } = authored;
|
|
9613
|
+
const placement = videoPlacement({
|
|
9614
|
+
placement: element.attributes["data-squisq-video-placement"] ?? ""
|
|
9615
|
+
});
|
|
9616
|
+
if (!placement || placement === "content") return null;
|
|
9617
|
+
const src = element.attributes.src;
|
|
9618
|
+
if (!src) return null;
|
|
9619
|
+
const locked = lockToBlock(element.attributes["data-squisq-video-lock-to-block"]);
|
|
9620
|
+
const clip = {
|
|
9621
|
+
id,
|
|
9622
|
+
src,
|
|
9623
|
+
kind: "video",
|
|
9624
|
+
placement,
|
|
9625
|
+
lockToBlock: locked,
|
|
9626
|
+
startAt: locked ? 0 : time(element.attributes["data-squisq-video-start-at"]) ?? 0,
|
|
9627
|
+
anchor: locked ? "block" : "document"
|
|
9628
|
+
};
|
|
9629
|
+
const clipStart = time(element.attributes["data-squisq-video-clip-start"]);
|
|
9630
|
+
const clipEnd = time(element.attributes["data-squisq-video-clip-end"]);
|
|
9631
|
+
if (clipStart != null) clip.clipStart = clipStart;
|
|
9632
|
+
if (!locked && clipEnd != null) clip.clipEnd = clipEnd;
|
|
9633
|
+
return clip;
|
|
9634
|
+
}
|
|
8333
9635
|
function extractMediaFromContents(contents, makeId, blockId) {
|
|
8334
9636
|
const media = [];
|
|
8335
9637
|
const documentMedia = [];
|
|
@@ -8338,17 +9640,19 @@ function extractMediaFromContents(contents, makeId, blockId) {
|
|
|
8338
9640
|
let originalIndex = 0;
|
|
8339
9641
|
for (const node of contents ?? []) {
|
|
8340
9642
|
const parsed = parseStandaloneAnnotation(node);
|
|
8341
|
-
const clip = parsed ? toMediaClip(parsed, "") :
|
|
8342
|
-
if (
|
|
9643
|
+
const clip = parsed ? toMediaClip(parsed, "") : htmlVideoClip(node, "");
|
|
9644
|
+
if (clip) {
|
|
8343
9645
|
clip.id = makeId(clip.src, index++);
|
|
8344
9646
|
const line = node.position?.start.line;
|
|
8345
9647
|
if (line != null) clip.sourceLine = line;
|
|
8346
9648
|
if (blockId !== void 0) {
|
|
8347
|
-
const
|
|
9649
|
+
const authoredHtml = standaloneHtmlVideo(node);
|
|
9650
|
+
const rawText = node.type === "paragraph" && node.children?.[0]?.type === "text" ? node.children[0].value.trim() : authoredHtml?.raw;
|
|
8348
9651
|
clip.origin = {
|
|
8349
9652
|
blockId,
|
|
8350
9653
|
index: originalIndex,
|
|
8351
|
-
...rawText !== void 0 ? { raw: rawText } : {}
|
|
9654
|
+
...rawText !== void 0 ? { raw: rawText } : {},
|
|
9655
|
+
...authoredHtml ? { format: "html" } : {}
|
|
8352
9656
|
};
|
|
8353
9657
|
}
|
|
8354
9658
|
(clip.anchor === "document" ? documentMedia : media).push(clip);
|
|
@@ -8685,6 +9989,12 @@ function markdownToDoc(markdownDoc, options) {
|
|
|
8685
9989
|
}
|
|
8686
9990
|
currentTime += block.duration;
|
|
8687
9991
|
}
|
|
9992
|
+
const blockById = new Map(allBlocks.map((block) => [block.id, block]));
|
|
9993
|
+
for (const clip of documentMedia) {
|
|
9994
|
+
if (clip.lockToBlock === false && clip.origin?.format === "html" && !/\bdata-squisq-video-start-at\s*=/i.test(clip.origin.raw ?? "")) {
|
|
9995
|
+
clip.startAt = blockById.get(clip.origin.blockId)?.startTime ?? 0;
|
|
9996
|
+
}
|
|
9997
|
+
}
|
|
8688
9998
|
const captions = phrases.length > 0 ? {
|
|
8689
9999
|
phrases,
|
|
8690
10000
|
...options?.captionsGeneratedAt ? { generatedAt: options.captionsGeneratedAt } : {},
|
|
@@ -8770,7 +10080,7 @@ function applyStructuredData(block, diagnostics) {
|
|
|
8770
10080
|
}
|
|
8771
10081
|
data = { ...data, ...result.data };
|
|
8772
10082
|
}
|
|
8773
|
-
if (block.template
|
|
10083
|
+
if (TABLE_FED_TEMPLATES.has(resolveTemplateName(block.template ?? ""))) {
|
|
8774
10084
|
const provided = (key) => data && key in data || block.templateOverrides && key in block.templateOverrides;
|
|
8775
10085
|
if (!provided("headers") && !provided("rows")) {
|
|
8776
10086
|
const table = findFirstTable(block.contents);
|
|
@@ -9296,6 +10606,7 @@ export {
|
|
|
9296
10606
|
wrapWithPersistentLayers,
|
|
9297
10607
|
TEMPLATE_ALIASES,
|
|
9298
10608
|
resolveTemplateName,
|
|
10609
|
+
TABLE_FED_TEMPLATES,
|
|
9299
10610
|
CONTAINER_TEMPLATES,
|
|
9300
10611
|
isContainerTemplate,
|
|
9301
10612
|
resolveColorScheme,
|
|
@@ -9359,6 +10670,7 @@ export {
|
|
|
9359
10670
|
videoWithCaption,
|
|
9360
10671
|
videoPullQuote,
|
|
9361
10672
|
dataTable,
|
|
10673
|
+
buildChartData,
|
|
9362
10674
|
DIAGRAM_LABEL_LINE_HEIGHT,
|
|
9363
10675
|
DIAGRAM_LABEL_HORIZONTAL_PADDING,
|
|
9364
10676
|
DIAGRAM_LABEL_VERTICAL_PADDING,
|