@bendyline/squisq 2.3.3 → 2.4.1
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-CSW6K2UF.d.ts} +222 -9
- package/dist/{ImageEditDoc-rum0Xb9P.d.ts → ImageEditDoc-x9yoTb4l.d.ts} +1 -1
- package/dist/{chunk-GQNH7PLN.js → chunk-2HY2ZA7U.js} +1470 -77
- package/dist/{chunk-G3JOS25T.js → chunk-7TJJA2RI.js} +46 -15
- package/dist/{chunk-RS5AP3J4.js → chunk-AE3IUKMM.js} +1 -1
- package/dist/{chunk-IZLKI3IL.js → chunk-AQF5ZYDI.js} +76 -16
- package/dist/{chunk-QWCFK5FN.js → chunk-CUYHFOFL.js} +5 -1
- package/dist/{chunk-2VCNTDNZ.js → chunk-D6YTLQCL.js} +71 -0
- package/dist/{chunk-32R2RZAO.js → chunk-DAMIRKTO.js} +1 -1
- package/dist/{chunk-ABP4LAJS.js → chunk-EMXYZLRH.js} +1 -1
- package/dist/{chunk-2ZWIXGAC.js → chunk-GAZKTT4R.js} +28 -4
- package/dist/{chunk-SPTY4C6F.js → chunk-GODLNXO4.js} +45 -3
- package/dist/chunk-JUAC2QWP.js +983 -0
- package/dist/{chunk-BCCXTMN5.js → chunk-O7JILDEF.js} +7 -0
- package/dist/{chunk-C33GTPUZ.js → chunk-SBAX4ZPO.js} +438 -0
- package/dist/doc/index.d.ts +113 -6
- package/dist/doc/index.js +14 -11
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +3 -3
- package/dist/index.d.ts +7 -7
- package/dist/index.js +41 -17
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +4 -4
- package/dist/markdown/index.d.ts +118 -1
- package/dist/markdown/index.js +22 -6
- package/dist/{materializePageSection-CrZWYnXM.d.ts → materializePageSection-_rrDuOYU.d.ts} +2 -2
- package/dist/narration/index.d.ts +1 -1
- package/dist/narration/index.js +6 -6
- package/dist/recommend/index.js +2 -2
- package/dist/schemas/index.d.ts +32 -5
- package/dist/schemas/index.js +9 -3
- package/dist/storage/index.d.ts +2 -3
- package/dist/{themeLibrary-DJt89gyP.d.ts → themeLibrary-DlJzXsin.d.ts} +1 -1
- package/dist/timing/index.d.ts +1 -2
- package/dist/transform/index.d.ts +2 -2
- package/dist/transform/index.js +2 -2
- package/package.json +2 -2
- 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,
|
|
@@ -30,9 +30,10 @@ import {
|
|
|
30
30
|
pickContrastingText,
|
|
31
31
|
relativeLuminance,
|
|
32
32
|
resolveFontFamily,
|
|
33
|
+
validateCustomTemplateDefinition,
|
|
33
34
|
validateTheme,
|
|
34
35
|
withAlpha
|
|
35
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-SBAX4ZPO.js";
|
|
36
37
|
import {
|
|
37
38
|
VIEWPORT_PRESETS,
|
|
38
39
|
cloneData,
|
|
@@ -45,19 +46,22 @@ import {
|
|
|
45
46
|
} from "./chunk-BAOV476U.js";
|
|
46
47
|
import {
|
|
47
48
|
KNOWN_BLOCK_META_KEYS,
|
|
49
|
+
TABLE_FED_TEMPLATES,
|
|
48
50
|
coerceAnnotationValues,
|
|
51
|
+
isContainerTemplate,
|
|
49
52
|
matchTrailingTemplateAnnotation,
|
|
50
53
|
parseNumber,
|
|
51
54
|
parseTimeSeconds,
|
|
55
|
+
resolveTemplateName,
|
|
52
56
|
sanitizeUrl,
|
|
53
57
|
splitKeyValueToken,
|
|
54
58
|
tokenizeAttrTokens
|
|
55
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-D6YTLQCL.js";
|
|
56
60
|
import {
|
|
57
61
|
extractPlainText,
|
|
58
62
|
readFrontmatterThemeId,
|
|
59
63
|
walkMarkdownTree
|
|
60
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-O7JILDEF.js";
|
|
61
65
|
import {
|
|
62
66
|
resolveBlockTransition
|
|
63
67
|
} from "./chunk-4VOD55SX.js";
|
|
@@ -411,23 +415,6 @@ function wrapWithPersistentLayers(layers, block, bottomLayers, topLayers) {
|
|
|
411
415
|
];
|
|
412
416
|
}
|
|
413
417
|
|
|
414
|
-
// src/doc/templates/templateNames.ts
|
|
415
|
-
var TEMPLATE_ALIASES = Object.freeze({
|
|
416
|
-
titleBlock: "title",
|
|
417
|
-
quoteBlock: "quote",
|
|
418
|
-
mapBlock: "map",
|
|
419
|
-
listBlock: "list",
|
|
420
|
-
diagramBlock: "diagram",
|
|
421
|
-
diagramNode: "diagram"
|
|
422
|
-
});
|
|
423
|
-
function resolveTemplateName(name) {
|
|
424
|
-
return TEMPLATE_ALIASES[name] ?? name;
|
|
425
|
-
}
|
|
426
|
-
var CONTAINER_TEMPLATES = /* @__PURE__ */ new Set(["diagram", "drawing", "layout"]);
|
|
427
|
-
function isContainerTemplate(name) {
|
|
428
|
-
return !!name && CONTAINER_TEMPLATES.has(resolveTemplateName(name));
|
|
429
|
-
}
|
|
430
|
-
|
|
431
418
|
// src/doc/utils/themeUtils.ts
|
|
432
419
|
var FALLBACK_COLOR_SCHEME = {
|
|
433
420
|
bg: "#1a365d",
|
|
@@ -949,7 +936,7 @@ function sectionHeader(input, context) {
|
|
|
949
936
|
|
|
950
937
|
// src/doc/templates/contentBlock.ts
|
|
951
938
|
function completeBodyText(context) {
|
|
952
|
-
return (context.block?.contents ?? []).map((node) => extractPlainText(node)).join("\n").trim();
|
|
939
|
+
return (context.block?.contents ?? []).filter((node) => !(node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")).map((node) => extractPlainText(node)).join("\n").trim();
|
|
953
940
|
}
|
|
954
941
|
function bodyFontSize(body, context) {
|
|
955
942
|
const base = body.length > 1800 ? 18 : body.length > 1100 ? 21 : body.length > 650 ? 24 : 28;
|
|
@@ -2739,6 +2726,32 @@ function deriveTemplateInputs(templateName, headingText, contents, options = {})
|
|
|
2739
2726
|
if (tableData) return { ...tableData, ...headingText ? { title: headingText } : {} };
|
|
2740
2727
|
return placeholders ? { headers: ["Column"], rows: [["Data"]] } : null;
|
|
2741
2728
|
}
|
|
2729
|
+
case "barChart":
|
|
2730
|
+
case "columnChart":
|
|
2731
|
+
case "pieChart":
|
|
2732
|
+
case "donutChart":
|
|
2733
|
+
case "lineChart":
|
|
2734
|
+
case "areaChart":
|
|
2735
|
+
case "scatterChart": {
|
|
2736
|
+
const tableData = extractTableFromContents(contents);
|
|
2737
|
+
if (tableData) {
|
|
2738
|
+
return {
|
|
2739
|
+
headers: tableData.headers,
|
|
2740
|
+
rows: tableData.rows,
|
|
2741
|
+
...headingText ? { title: headingText } : {}
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
return placeholders && !bodyText3.trim() ? {
|
|
2745
|
+
headers: ["Quarter", "Revenue", "Costs"],
|
|
2746
|
+
rows: [
|
|
2747
|
+
["Q1", "40", "28"],
|
|
2748
|
+
["Q2", "55", "31"],
|
|
2749
|
+
["Q3", "48", "30"],
|
|
2750
|
+
["Q4", "62", "35"]
|
|
2751
|
+
],
|
|
2752
|
+
...headingText ? { title: headingText } : {}
|
|
2753
|
+
} : null;
|
|
2754
|
+
}
|
|
2742
2755
|
case "imageWithCaption": {
|
|
2743
2756
|
const img = extractFirstImage(contents);
|
|
2744
2757
|
if (!img) return placeholders ? { caption: headingText } : null;
|
|
@@ -5585,6 +5598,1019 @@ function makeCustomTemplateFn(def) {
|
|
|
5585
5598
|
};
|
|
5586
5599
|
}
|
|
5587
5600
|
|
|
5601
|
+
// src/doc/templates/chart/parse.ts
|
|
5602
|
+
var NUMERIC_COLUMN_THRESHOLD = 0.6;
|
|
5603
|
+
function parseCellNumber(raw) {
|
|
5604
|
+
if (typeof raw !== "string") return null;
|
|
5605
|
+
let text = raw.trim();
|
|
5606
|
+
if (!text) return null;
|
|
5607
|
+
let negative = false;
|
|
5608
|
+
const accounting = /^\((.*)\)$/.exec(text);
|
|
5609
|
+
if (accounting) {
|
|
5610
|
+
negative = true;
|
|
5611
|
+
text = accounting[1].trim();
|
|
5612
|
+
}
|
|
5613
|
+
text = text.replace(/^([+-]?)\s*[$€£¥]\s*/, "$1");
|
|
5614
|
+
text = text.replace(/%$/, "").trim();
|
|
5615
|
+
text = text.replace(/,/g, "");
|
|
5616
|
+
if (/^[+-]?\d{1,3}(?: \d{3})+(?:\.\d+)?$/.test(text)) {
|
|
5617
|
+
text = text.replace(/ /g, "");
|
|
5618
|
+
}
|
|
5619
|
+
if (!/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(text)) return null;
|
|
5620
|
+
const value = Number(text);
|
|
5621
|
+
if (!Number.isFinite(value)) return null;
|
|
5622
|
+
return negative ? -value : value;
|
|
5623
|
+
}
|
|
5624
|
+
function isNumericColumn(rows, colIndex) {
|
|
5625
|
+
let nonEmpty = 0;
|
|
5626
|
+
let numeric = 0;
|
|
5627
|
+
for (const row of rows) {
|
|
5628
|
+
const cell = (row[colIndex] ?? "").trim();
|
|
5629
|
+
if (!cell) continue;
|
|
5630
|
+
nonEmpty += 1;
|
|
5631
|
+
if (parseCellNumber(cell) !== null) numeric += 1;
|
|
5632
|
+
}
|
|
5633
|
+
return numeric > 0 && numeric / Math.max(1, nonEmpty) >= NUMERIC_COLUMN_THRESHOLD;
|
|
5634
|
+
}
|
|
5635
|
+
function resolveColumnRef(ref, headers) {
|
|
5636
|
+
const text = String(ref).trim();
|
|
5637
|
+
if (!text) return null;
|
|
5638
|
+
const lower = text.toLowerCase();
|
|
5639
|
+
const byName = headers.findIndex((h) => h.trim().toLowerCase() === lower);
|
|
5640
|
+
if (byName >= 0) return byName;
|
|
5641
|
+
if (/^\d+$/.test(text)) {
|
|
5642
|
+
const index = Number(text);
|
|
5643
|
+
if (index < headers.length) return index;
|
|
5644
|
+
}
|
|
5645
|
+
return null;
|
|
5646
|
+
}
|
|
5647
|
+
function buildChartData(table, options = {}) {
|
|
5648
|
+
const headers = Array.isArray(table.headers) ? table.headers : [];
|
|
5649
|
+
const rows = Array.isArray(table.rows) ? table.rows : [];
|
|
5650
|
+
if (headers.length === 0 || rows.length === 0) return null;
|
|
5651
|
+
const warnings = [];
|
|
5652
|
+
let labelIndex = 0;
|
|
5653
|
+
if (options.labelColumn !== void 0 && String(options.labelColumn).trim() !== "") {
|
|
5654
|
+
const resolved = resolveColumnRef(String(options.labelColumn), headers);
|
|
5655
|
+
if (resolved === null) {
|
|
5656
|
+
warnings.push(
|
|
5657
|
+
`labelColumn "${options.labelColumn}" matches no column; using the first column`
|
|
5658
|
+
);
|
|
5659
|
+
} else {
|
|
5660
|
+
labelIndex = resolved;
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
let valueIndexes = [];
|
|
5664
|
+
const requested = (options.valueColumns ?? []).map((ref) => String(ref)).filter((ref) => ref.trim() !== "");
|
|
5665
|
+
if (requested.length > 0) {
|
|
5666
|
+
for (const ref of requested) {
|
|
5667
|
+
const resolved = resolveColumnRef(ref, headers);
|
|
5668
|
+
if (resolved === null) {
|
|
5669
|
+
warnings.push(`valueColumns entry "${ref}" matches no column; skipped`);
|
|
5670
|
+
} else if (!valueIndexes.includes(resolved)) {
|
|
5671
|
+
valueIndexes.push(resolved);
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
} else {
|
|
5675
|
+
valueIndexes = headers.map((_, index) => index).filter((index) => index !== labelIndex && isNumericColumn(rows, index));
|
|
5676
|
+
}
|
|
5677
|
+
if (valueIndexes.length === 0) return null;
|
|
5678
|
+
return {
|
|
5679
|
+
labels: rows.map((row) => (row[labelIndex] ?? "").trim()),
|
|
5680
|
+
labelHeader: headers[labelIndex] ?? "",
|
|
5681
|
+
labelNumbers: rows.map((row) => parseCellNumber(row[labelIndex] ?? "")),
|
|
5682
|
+
series: valueIndexes.map((index) => ({
|
|
5683
|
+
name: (headers[index] ?? `Column ${index + 1}`).trim() || `Column ${index + 1}`,
|
|
5684
|
+
values: rows.map((row) => parseCellNumber(row[index] ?? ""))
|
|
5685
|
+
})),
|
|
5686
|
+
warnings
|
|
5687
|
+
};
|
|
5688
|
+
}
|
|
5689
|
+
|
|
5690
|
+
// src/doc/templates/chart/scale.ts
|
|
5691
|
+
function niceNum(range, round4) {
|
|
5692
|
+
const exponent = Math.floor(Math.log10(range));
|
|
5693
|
+
const fraction = range / 10 ** exponent;
|
|
5694
|
+
let niceFraction;
|
|
5695
|
+
if (round4) {
|
|
5696
|
+
if (fraction < 1.5) niceFraction = 1;
|
|
5697
|
+
else if (fraction < 3) niceFraction = 2;
|
|
5698
|
+
else if (fraction < 7) niceFraction = 5;
|
|
5699
|
+
else niceFraction = 10;
|
|
5700
|
+
} else {
|
|
5701
|
+
if (fraction <= 1) niceFraction = 1;
|
|
5702
|
+
else if (fraction <= 2) niceFraction = 2;
|
|
5703
|
+
else if (fraction <= 5) niceFraction = 5;
|
|
5704
|
+
else niceFraction = 10;
|
|
5705
|
+
}
|
|
5706
|
+
return niceFraction * 10 ** exponent;
|
|
5707
|
+
}
|
|
5708
|
+
function niceTicks(min, max, targetCount = 5) {
|
|
5709
|
+
let lo = Number.isFinite(min) ? min : 0;
|
|
5710
|
+
let hi = Number.isFinite(max) ? max : 0;
|
|
5711
|
+
if (lo > hi) [lo, hi] = [hi, lo];
|
|
5712
|
+
if (lo === hi) {
|
|
5713
|
+
const pad = lo === 0 ? 1 : niceNum(Math.abs(lo), true);
|
|
5714
|
+
lo -= pad;
|
|
5715
|
+
hi += pad;
|
|
5716
|
+
if (min >= 0 && lo < 0) lo = 0;
|
|
5717
|
+
}
|
|
5718
|
+
const count = Math.max(2, targetCount);
|
|
5719
|
+
const range = niceNum(hi - lo, false);
|
|
5720
|
+
const step = niceNum(range / (count - 1), true);
|
|
5721
|
+
const niceMin = Math.floor(lo / step) * step;
|
|
5722
|
+
const niceMax = Math.ceil(hi / step) * step;
|
|
5723
|
+
const ticks = [];
|
|
5724
|
+
for (let tick = niceMin; tick <= niceMax + step * 1e-6; tick += step) {
|
|
5725
|
+
ticks.push(Math.abs(tick) < step * 1e-6 ? 0 : Number(tick.toPrecision(12)));
|
|
5726
|
+
}
|
|
5727
|
+
return { ticks, niceMin, niceMax, step };
|
|
5728
|
+
}
|
|
5729
|
+
function formatChartValue(value, unit) {
|
|
5730
|
+
if (!Number.isFinite(value)) return "\u2014";
|
|
5731
|
+
const sign = value < 0 ? "-" : "";
|
|
5732
|
+
const abs = Math.abs(value);
|
|
5733
|
+
let text;
|
|
5734
|
+
if (abs >= 1e6) {
|
|
5735
|
+
text = `${sign}${trimTrailingZero((abs / 1e6).toFixed(1))}M`;
|
|
5736
|
+
} else if (abs >= 1e3) {
|
|
5737
|
+
text = `${sign}${trimTrailingZero((abs / 1e3).toFixed(abs >= 1e4 ? 0 : 1))}K`;
|
|
5738
|
+
} else {
|
|
5739
|
+
text = `${sign}${abs.toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
|
|
5740
|
+
}
|
|
5741
|
+
return unit ? `${text} ${unit}` : text;
|
|
5742
|
+
}
|
|
5743
|
+
function trimTrailingZero(text) {
|
|
5744
|
+
return text.replace(/\.0$/, "");
|
|
5745
|
+
}
|
|
5746
|
+
|
|
5747
|
+
// src/doc/templates/chart/geometry.ts
|
|
5748
|
+
function round3(value) {
|
|
5749
|
+
return Math.round(value * 100) / 100;
|
|
5750
|
+
}
|
|
5751
|
+
function pointOnCircle(cx, cy, r, deg) {
|
|
5752
|
+
const rad = (deg - 90) * Math.PI / 180;
|
|
5753
|
+
return { x: round3(cx + r * Math.cos(rad)), y: round3(cy + r * Math.sin(rad)) };
|
|
5754
|
+
}
|
|
5755
|
+
function arcPath(cx, cy, rOuter, rInner, startDeg, endDeg) {
|
|
5756
|
+
let sweep = endDeg - startDeg;
|
|
5757
|
+
if (sweep <= 0) return "";
|
|
5758
|
+
if (sweep >= 359.9) sweep = 359.9;
|
|
5759
|
+
const end = startDeg + sweep;
|
|
5760
|
+
const largeArc = sweep > 180 ? 1 : 0;
|
|
5761
|
+
const o1 = pointOnCircle(cx, cy, rOuter, startDeg);
|
|
5762
|
+
const o2 = pointOnCircle(cx, cy, rOuter, end);
|
|
5763
|
+
if (rInner <= 0) {
|
|
5764
|
+
return [
|
|
5765
|
+
`M ${round3(cx)} ${round3(cy)}`,
|
|
5766
|
+
`L ${o1.x} ${o1.y}`,
|
|
5767
|
+
`A ${round3(rOuter)} ${round3(rOuter)} 0 ${largeArc} 1 ${o2.x} ${o2.y}`,
|
|
5768
|
+
"Z"
|
|
5769
|
+
].join(" ");
|
|
5770
|
+
}
|
|
5771
|
+
const i1 = pointOnCircle(cx, cy, rInner, startDeg);
|
|
5772
|
+
const i2 = pointOnCircle(cx, cy, rInner, end);
|
|
5773
|
+
return [
|
|
5774
|
+
`M ${o1.x} ${o1.y}`,
|
|
5775
|
+
`A ${round3(rOuter)} ${round3(rOuter)} 0 ${largeArc} 1 ${o2.x} ${o2.y}`,
|
|
5776
|
+
`L ${i2.x} ${i2.y}`,
|
|
5777
|
+
`A ${round3(rInner)} ${round3(rInner)} 0 ${largeArc} 0 ${i1.x} ${i1.y}`,
|
|
5778
|
+
"Z"
|
|
5779
|
+
].join(" ");
|
|
5780
|
+
}
|
|
5781
|
+
function polylinePath(points) {
|
|
5782
|
+
if (points.length === 0) return "";
|
|
5783
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"} ${round3(p.x)} ${round3(p.y)}`).join(" ");
|
|
5784
|
+
}
|
|
5785
|
+
function areaPath(points, baselineY) {
|
|
5786
|
+
if (points.length === 0) return "";
|
|
5787
|
+
const open = polylinePath(points);
|
|
5788
|
+
const first = points[0];
|
|
5789
|
+
const last = points[points.length - 1];
|
|
5790
|
+
return `${open} L ${round3(last.x)} ${round3(baselineY)} L ${round3(first.x)} ${round3(baselineY)} Z`;
|
|
5791
|
+
}
|
|
5792
|
+
|
|
5793
|
+
// src/doc/templates/chart/layout.ts
|
|
5794
|
+
function computeChartFrame(options) {
|
|
5795
|
+
const W = options.viewportWidth;
|
|
5796
|
+
const H = options.viewportHeight;
|
|
5797
|
+
const marginX = W * 0.06;
|
|
5798
|
+
const marginY = H * 0.06;
|
|
5799
|
+
const innerWidth = W - marginX * 2;
|
|
5800
|
+
let cursor = marginY;
|
|
5801
|
+
let titleBand;
|
|
5802
|
+
if (options.hasTitle) {
|
|
5803
|
+
titleBand = { x: marginX, y: cursor, width: innerWidth, height: options.titleFontSize * 2.2 };
|
|
5804
|
+
cursor = titleBand.y + titleBand.height;
|
|
5805
|
+
}
|
|
5806
|
+
let tableBand;
|
|
5807
|
+
let tableHeight = 0;
|
|
5808
|
+
if (options.tableRowCount > 0) {
|
|
5809
|
+
tableHeight = Math.min(H * 0.4, (options.tableRowCount + 1) * options.tableFontSize * 2.4);
|
|
5810
|
+
}
|
|
5811
|
+
const legendHeight = options.legendRows > 0 ? options.legendRows * options.legendRowHeight : 0;
|
|
5812
|
+
const chartArea = {
|
|
5813
|
+
x: marginX,
|
|
5814
|
+
y: cursor,
|
|
5815
|
+
width: innerWidth,
|
|
5816
|
+
height: Math.max(40, H - marginY - cursor - legendHeight - tableHeight)
|
|
5817
|
+
};
|
|
5818
|
+
cursor = chartArea.y + chartArea.height;
|
|
5819
|
+
let legendBand;
|
|
5820
|
+
if (legendHeight > 0) {
|
|
5821
|
+
legendBand = { x: marginX, y: cursor, width: innerWidth, height: legendHeight };
|
|
5822
|
+
cursor += legendHeight;
|
|
5823
|
+
}
|
|
5824
|
+
if (tableHeight > 0) {
|
|
5825
|
+
tableBand = { x: marginX, y: cursor, width: innerWidth, height: tableHeight };
|
|
5826
|
+
}
|
|
5827
|
+
const plot = {
|
|
5828
|
+
x: chartArea.x + options.leftGutter,
|
|
5829
|
+
y: chartArea.y,
|
|
5830
|
+
width: Math.max(20, chartArea.width - options.leftGutter),
|
|
5831
|
+
height: Math.max(20, chartArea.height - options.bottomGutter)
|
|
5832
|
+
};
|
|
5833
|
+
return { titleBand, chartArea, plot, legendBand, tableBand };
|
|
5834
|
+
}
|
|
5835
|
+
function stackSeries(series) {
|
|
5836
|
+
const rowCount = series.reduce((max, s) => Math.max(max, s.values.length), 0);
|
|
5837
|
+
const positive = new Array(rowCount).fill(0);
|
|
5838
|
+
const negative = new Array(rowCount).fill(0);
|
|
5839
|
+
return series.map(
|
|
5840
|
+
(s) => Array.from({ length: rowCount }, (_, row) => {
|
|
5841
|
+
const value = s.values[row];
|
|
5842
|
+
if (value === null || value === void 0 || !Number.isFinite(value)) {
|
|
5843
|
+
return { y0: positive[row], y1: positive[row] };
|
|
5844
|
+
}
|
|
5845
|
+
if (value >= 0) {
|
|
5846
|
+
const y0 = positive[row];
|
|
5847
|
+
positive[row] += value;
|
|
5848
|
+
return { y0, y1: positive[row] };
|
|
5849
|
+
}
|
|
5850
|
+
const y1 = negative[row];
|
|
5851
|
+
negative[row] += value;
|
|
5852
|
+
return { y0: negative[row], y1 };
|
|
5853
|
+
})
|
|
5854
|
+
);
|
|
5855
|
+
}
|
|
5856
|
+
function layoutLegend(labels, fontSize, maxWidth) {
|
|
5857
|
+
const swatchSize = fontSize * 0.9;
|
|
5858
|
+
const gap = fontSize * 1.6;
|
|
5859
|
+
const rowHeight = fontSize * 2;
|
|
5860
|
+
const widths = labels.map(
|
|
5861
|
+
(label) => swatchSize + fontSize * 0.5 + label.length * fontSize * 0.58
|
|
5862
|
+
);
|
|
5863
|
+
const rows = [];
|
|
5864
|
+
let current = [];
|
|
5865
|
+
let currentWidth = 0;
|
|
5866
|
+
widths.forEach((width, index) => {
|
|
5867
|
+
const extra = current.length > 0 ? gap : 0;
|
|
5868
|
+
if (current.length > 0 && currentWidth + extra + width > maxWidth) {
|
|
5869
|
+
rows.push(current);
|
|
5870
|
+
current = [];
|
|
5871
|
+
currentWidth = 0;
|
|
5872
|
+
}
|
|
5873
|
+
current.push(index);
|
|
5874
|
+
currentWidth += (current.length > 1 ? gap : 0) + width;
|
|
5875
|
+
});
|
|
5876
|
+
if (current.length > 0) rows.push(current);
|
|
5877
|
+
const items = [];
|
|
5878
|
+
rows.forEach((rowIndexes, rowIndex) => {
|
|
5879
|
+
const rowWidth = rowIndexes.reduce(
|
|
5880
|
+
(sum, index, i) => sum + widths[index] + (i > 0 ? gap : 0),
|
|
5881
|
+
0
|
|
5882
|
+
);
|
|
5883
|
+
let x = Math.max(0, (maxWidth - rowWidth) / 2);
|
|
5884
|
+
rowIndexes.forEach((index) => {
|
|
5885
|
+
items.push({ label: labels[index], index, x, y: rowIndex * rowHeight, swatchSize });
|
|
5886
|
+
x += widths[index] + gap;
|
|
5887
|
+
});
|
|
5888
|
+
});
|
|
5889
|
+
return { items, rows: rows.length, rowHeight };
|
|
5890
|
+
}
|
|
5891
|
+
function categoryLabelStep(categoryCount, axisLength, fontSize) {
|
|
5892
|
+
if (categoryCount <= 1) return 1;
|
|
5893
|
+
const minPitch = fontSize * 6;
|
|
5894
|
+
const pitch = axisLength / categoryCount;
|
|
5895
|
+
return Math.max(1, Math.ceil(minPitch / Math.max(1, pitch)));
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
// src/doc/templates/chart/render.ts
|
|
5899
|
+
var TEMPLATE_ID = {
|
|
5900
|
+
bar: "barChart",
|
|
5901
|
+
column: "columnChart",
|
|
5902
|
+
pie: "pieChart",
|
|
5903
|
+
donut: "donutChart",
|
|
5904
|
+
line: "lineChart",
|
|
5905
|
+
area: "areaChart",
|
|
5906
|
+
scatter: "scatterChart"
|
|
5907
|
+
};
|
|
5908
|
+
function renderChart(kind, input, context) {
|
|
5909
|
+
const table = resolveTable(input, context);
|
|
5910
|
+
const data = table ? buildChartData(table, input) : null;
|
|
5911
|
+
if (!table || !data) return contentFallback(input, context);
|
|
5912
|
+
const { theme, viewport } = context;
|
|
5913
|
+
const templateId = TEMPLATE_ID[kind];
|
|
5914
|
+
const title = (input.title ?? "").trim();
|
|
5915
|
+
const titleFontSize = themedFontSize(44, context, true);
|
|
5916
|
+
const axisFontSize = themedFontSize(20, context, false);
|
|
5917
|
+
const labelFontSize = themedFontSize(22, context, false);
|
|
5918
|
+
const legendFontSize = themedFontSize(22, context, false);
|
|
5919
|
+
const tableFontSize = themedFontSize(20, context, false);
|
|
5920
|
+
const bodyFont = getThemeFont(context, "body");
|
|
5921
|
+
const isRadial = kind === "pie" || kind === "donut";
|
|
5922
|
+
const legendLabels = resolveLegendLabels(kind, input, data);
|
|
5923
|
+
const colors = seriesColors(
|
|
5924
|
+
context,
|
|
5925
|
+
input.colorScheme,
|
|
5926
|
+
isRadial ? data.labels.length : data.series.length
|
|
5927
|
+
);
|
|
5928
|
+
if (isRadial && !data.series[0].values.some((v) => v !== null && v > 0)) {
|
|
5929
|
+
return contentFallback(input, context);
|
|
5930
|
+
}
|
|
5931
|
+
const innerWidth = viewport.width * 0.88;
|
|
5932
|
+
const legend = legendLabels.length > 0 ? layoutLegend(legendLabels, legendFontSize, innerWidth) : null;
|
|
5933
|
+
const gutters = computeGutters(kind, data, input, context, axisFontSize, labelFontSize);
|
|
5934
|
+
const frame = computeChartFrame({
|
|
5935
|
+
viewportWidth: viewport.width,
|
|
5936
|
+
viewportHeight: viewport.height,
|
|
5937
|
+
hasTitle: title.length > 0,
|
|
5938
|
+
titleFontSize,
|
|
5939
|
+
leftGutter: gutters.left,
|
|
5940
|
+
bottomGutter: gutters.bottom,
|
|
5941
|
+
legendRows: legend?.rows ?? 0,
|
|
5942
|
+
legendRowHeight: legendFontSize * 2,
|
|
5943
|
+
tableRowCount: input.showTable === true ? table.rows.length : 0,
|
|
5944
|
+
tableFontSize
|
|
5945
|
+
});
|
|
5946
|
+
const entranceHint = getTemplateHint(context, templateId, "entrance", "staggered");
|
|
5947
|
+
const entrance = (index) => ({
|
|
5948
|
+
type: "fadeIn",
|
|
5949
|
+
duration: 0.6,
|
|
5950
|
+
delay: entranceHint === "subtle" ? 0.1 : Math.min(1.2, 0.2 + index * 0.08)
|
|
5951
|
+
});
|
|
5952
|
+
const paint = {
|
|
5953
|
+
kind,
|
|
5954
|
+
input,
|
|
5955
|
+
context,
|
|
5956
|
+
data,
|
|
5957
|
+
frame,
|
|
5958
|
+
colors,
|
|
5959
|
+
axisFontSize,
|
|
5960
|
+
labelFontSize,
|
|
5961
|
+
bodyFont,
|
|
5962
|
+
gridColor: withAlpha(theme.colors.text, 0.12),
|
|
5963
|
+
axisTextColor: theme.colors.textMuted,
|
|
5964
|
+
entrance
|
|
5965
|
+
};
|
|
5966
|
+
const layers = [createBackgroundLayer("bg", themedSurfaceGradient(context, 170))];
|
|
5967
|
+
if (title && frame.titleBand) {
|
|
5968
|
+
layers.push({
|
|
5969
|
+
type: "text",
|
|
5970
|
+
id: "title",
|
|
5971
|
+
content: {
|
|
5972
|
+
text: title,
|
|
5973
|
+
style: {
|
|
5974
|
+
fontSize: titleFontSize,
|
|
5975
|
+
fontFamily: getThemeFont(context, "title"),
|
|
5976
|
+
fontWeight: "bold",
|
|
5977
|
+
color: theme.colors.text,
|
|
5978
|
+
textAlign: "center",
|
|
5979
|
+
shadow: shouldUseShadow(context)
|
|
5980
|
+
}
|
|
5981
|
+
},
|
|
5982
|
+
position: {
|
|
5983
|
+
x: frame.titleBand.x + frame.titleBand.width / 2,
|
|
5984
|
+
y: frame.titleBand.y + frame.titleBand.height / 2,
|
|
5985
|
+
width: frame.titleBand.width,
|
|
5986
|
+
anchor: "center"
|
|
5987
|
+
},
|
|
5988
|
+
animation: { type: "fadeIn", duration: 0.8 }
|
|
5989
|
+
});
|
|
5990
|
+
}
|
|
5991
|
+
switch (kind) {
|
|
5992
|
+
case "bar":
|
|
5993
|
+
layers.push(...paintBars(paint));
|
|
5994
|
+
break;
|
|
5995
|
+
case "column":
|
|
5996
|
+
layers.push(...paintColumns(paint));
|
|
5997
|
+
break;
|
|
5998
|
+
case "pie":
|
|
5999
|
+
case "donut":
|
|
6000
|
+
layers.push(...paintRadial(paint));
|
|
6001
|
+
break;
|
|
6002
|
+
case "line":
|
|
6003
|
+
case "area":
|
|
6004
|
+
layers.push(...paintLines(paint));
|
|
6005
|
+
break;
|
|
6006
|
+
case "scatter":
|
|
6007
|
+
layers.push(...paintScatter(paint));
|
|
6008
|
+
break;
|
|
6009
|
+
}
|
|
6010
|
+
if (legend && frame.legendBand) {
|
|
6011
|
+
layers.push(...paintLegend(paint, legend, frame.legendBand, legendFontSize));
|
|
6012
|
+
}
|
|
6013
|
+
if (input.showTable === true && frame.tableBand) {
|
|
6014
|
+
layers.push(tableLayer(table, frame.tableBand, tableFontSize, input, context));
|
|
6015
|
+
}
|
|
6016
|
+
return layers;
|
|
6017
|
+
}
|
|
6018
|
+
function resolveTable(input, context) {
|
|
6019
|
+
if (Array.isArray(input.headers) && input.headers.length > 0 && Array.isArray(input.rows) && input.rows.length > 0) {
|
|
6020
|
+
return { headers: input.headers, rows: input.rows };
|
|
6021
|
+
}
|
|
6022
|
+
const contents = input.contents ?? context.block?.contents;
|
|
6023
|
+
return extractTableFromContents(contents);
|
|
6024
|
+
}
|
|
6025
|
+
function contentFallback(input, context) {
|
|
6026
|
+
const fallbackInput = {
|
|
6027
|
+
...input,
|
|
6028
|
+
template: "content",
|
|
6029
|
+
title: input.title ?? context.block?.title ?? ""
|
|
6030
|
+
};
|
|
6031
|
+
return contentBlock(fallbackInput, context);
|
|
6032
|
+
}
|
|
6033
|
+
function resolveLegendLabels(kind, input, data) {
|
|
6034
|
+
if (input.showLegend === false) return [];
|
|
6035
|
+
if (kind === "pie" || kind === "donut") return data.labels;
|
|
6036
|
+
if (data.series.length > 1 || input.showLegend === true) {
|
|
6037
|
+
return data.series.map((s) => s.name);
|
|
6038
|
+
}
|
|
6039
|
+
return [];
|
|
6040
|
+
}
|
|
6041
|
+
function seriesColors(context, schemeName, count) {
|
|
6042
|
+
const schemes = context.theme.colorSchemes;
|
|
6043
|
+
const names = Object.keys(schemes);
|
|
6044
|
+
const primary = resolveColorScheme(context, schemeName).accent;
|
|
6045
|
+
const palette = [primary];
|
|
6046
|
+
const start = schemeName && names.includes(schemeName) ? names.indexOf(schemeName) : 0;
|
|
6047
|
+
for (let i = 1; i < names.length && palette.length < count; i++) {
|
|
6048
|
+
const accent = schemes[names[(start + i) % names.length]].accent;
|
|
6049
|
+
if (!palette.some((c) => c.toLowerCase() === accent.toLowerCase())) palette.push(accent);
|
|
6050
|
+
}
|
|
6051
|
+
const distinct = palette.length;
|
|
6052
|
+
for (let i = 0; palette.length < count; i++) {
|
|
6053
|
+
palette.push(withAlpha(palette[i % distinct], 0.55));
|
|
6054
|
+
}
|
|
6055
|
+
return palette.slice(0, Math.max(1, count));
|
|
6056
|
+
}
|
|
6057
|
+
function computeGutters(kind, data, input, context, axisFontSize, labelFontSize) {
|
|
6058
|
+
if (kind === "pie" || kind === "donut") return { left: 0, bottom: 0 };
|
|
6059
|
+
if (kind === "bar") {
|
|
6060
|
+
const longest = data.labels.reduce((max, label) => Math.max(max, label.length), 0);
|
|
6061
|
+
const left = Math.min(
|
|
6062
|
+
context.viewport.width * 0.24,
|
|
6063
|
+
Math.max(labelFontSize * 3, longest * labelFontSize * 0.58 + 16)
|
|
6064
|
+
);
|
|
6065
|
+
return { left, bottom: axisFontSize * 2.4 };
|
|
6066
|
+
}
|
|
6067
|
+
const ticks = valueTicks(kind, data, input);
|
|
6068
|
+
const longestTick = ticks.ticks.reduce(
|
|
6069
|
+
(max, tick) => Math.max(max, formatChartValue(tick, input.unit).length),
|
|
6070
|
+
0
|
|
6071
|
+
);
|
|
6072
|
+
return {
|
|
6073
|
+
left: Math.max(axisFontSize * 2.5, longestTick * axisFontSize * 0.6 + 16),
|
|
6074
|
+
bottom: labelFontSize * 2.6
|
|
6075
|
+
};
|
|
6076
|
+
}
|
|
6077
|
+
function valueTicks(kind, data, input) {
|
|
6078
|
+
let values;
|
|
6079
|
+
if ((kind === "bar" || kind === "column") && input.stacked === true) {
|
|
6080
|
+
const extents = stackSeries(data.series).flat();
|
|
6081
|
+
values = extents.flatMap((e) => [e.y0, e.y1]);
|
|
6082
|
+
} else {
|
|
6083
|
+
values = data.series.flatMap((s) => s.values.filter((v) => v !== null));
|
|
6084
|
+
}
|
|
6085
|
+
if (values.length === 0) values = [0, 1];
|
|
6086
|
+
let min = Math.min(...values);
|
|
6087
|
+
let max = Math.max(...values);
|
|
6088
|
+
if (kind !== "line" && kind !== "scatter") {
|
|
6089
|
+
min = Math.min(0, min);
|
|
6090
|
+
max = Math.max(0, max);
|
|
6091
|
+
}
|
|
6092
|
+
return niceTicks(min, max, 5);
|
|
6093
|
+
}
|
|
6094
|
+
function paintValueAxisY(paint, scale, yFor) {
|
|
6095
|
+
const { frame, gridColor, axisTextColor, axisFontSize, bodyFont, input } = paint;
|
|
6096
|
+
const layers = [];
|
|
6097
|
+
scale.ticks.forEach((tick, index) => {
|
|
6098
|
+
const y = yFor(tick);
|
|
6099
|
+
layers.push({
|
|
6100
|
+
type: "shape",
|
|
6101
|
+
id: `grid-${index}`,
|
|
6102
|
+
content: {
|
|
6103
|
+
shape: "rect",
|
|
6104
|
+
fill: tick === 0 ? withAlpha(paint.context.theme.colors.text, 0.35) : gridColor
|
|
6105
|
+
},
|
|
6106
|
+
position: {
|
|
6107
|
+
x: frame.plot.x,
|
|
6108
|
+
y: y - (tick === 0 ? 1 : 0.5),
|
|
6109
|
+
width: frame.plot.width,
|
|
6110
|
+
height: tick === 0 ? 2 : 1
|
|
6111
|
+
}
|
|
6112
|
+
});
|
|
6113
|
+
layers.push({
|
|
6114
|
+
type: "text",
|
|
6115
|
+
id: `tick-${index}`,
|
|
6116
|
+
content: {
|
|
6117
|
+
text: formatChartValue(tick, input.unit),
|
|
6118
|
+
style: {
|
|
6119
|
+
fontSize: axisFontSize,
|
|
6120
|
+
fontFamily: bodyFont,
|
|
6121
|
+
color: axisTextColor,
|
|
6122
|
+
textAlign: "center"
|
|
6123
|
+
}
|
|
6124
|
+
},
|
|
6125
|
+
position: {
|
|
6126
|
+
x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
|
|
6127
|
+
y,
|
|
6128
|
+
anchor: "center"
|
|
6129
|
+
}
|
|
6130
|
+
});
|
|
6131
|
+
});
|
|
6132
|
+
return layers;
|
|
6133
|
+
}
|
|
6134
|
+
function paintCategoryAxisX(paint, xForCenter) {
|
|
6135
|
+
const { frame, data, labelFontSize, axisTextColor, bodyFont } = paint;
|
|
6136
|
+
const step = categoryLabelStep(data.labels.length, frame.plot.width, labelFontSize);
|
|
6137
|
+
const layers = [];
|
|
6138
|
+
data.labels.forEach((label, index) => {
|
|
6139
|
+
if (index % step !== 0 || !label) return;
|
|
6140
|
+
layers.push({
|
|
6141
|
+
type: "text",
|
|
6142
|
+
id: `cat-${index}`,
|
|
6143
|
+
content: {
|
|
6144
|
+
text: label,
|
|
6145
|
+
style: {
|
|
6146
|
+
fontSize: labelFontSize,
|
|
6147
|
+
fontFamily: bodyFont,
|
|
6148
|
+
color: axisTextColor,
|
|
6149
|
+
textAlign: "center"
|
|
6150
|
+
}
|
|
6151
|
+
},
|
|
6152
|
+
position: {
|
|
6153
|
+
x: xForCenter(index),
|
|
6154
|
+
y: frame.plot.y + frame.plot.height + labelFontSize * 1.2,
|
|
6155
|
+
anchor: "center"
|
|
6156
|
+
}
|
|
6157
|
+
});
|
|
6158
|
+
});
|
|
6159
|
+
return layers;
|
|
6160
|
+
}
|
|
6161
|
+
function paintColumns(paint) {
|
|
6162
|
+
const { frame, data, colors, input, entrance } = paint;
|
|
6163
|
+
const scale = valueTicks("column", data, input);
|
|
6164
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6165
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6166
|
+
const n = data.labels.length;
|
|
6167
|
+
const band = frame.plot.width / Math.max(1, n);
|
|
6168
|
+
const innerPad = band * 0.15;
|
|
6169
|
+
layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
|
|
6170
|
+
const stacked = input.stacked === true && data.series.length > 1;
|
|
6171
|
+
const extents = stacked ? stackSeries(data.series) : null;
|
|
6172
|
+
const groupWidth = band - innerPad * 2;
|
|
6173
|
+
const barWidth = stacked ? groupWidth : Math.max(2, (groupWidth - 4 * (data.series.length - 1)) / data.series.length);
|
|
6174
|
+
data.series.forEach((series, s) => {
|
|
6175
|
+
series.values.forEach((value, i) => {
|
|
6176
|
+
if (i >= n) return;
|
|
6177
|
+
const extent = extents ? extents[s][i] : value === null ? null : { y0: Math.min(0, value), y1: Math.max(0, value) };
|
|
6178
|
+
if (!extent || extent.y0 === extent.y1) return;
|
|
6179
|
+
const top = yFor(extent.y1);
|
|
6180
|
+
const height = Math.max(1, yFor(extent.y0) - top);
|
|
6181
|
+
const x = stacked ? frame.plot.x + i * band + innerPad : frame.plot.x + i * band + innerPad + s * (barWidth + 4);
|
|
6182
|
+
layers.push({
|
|
6183
|
+
type: "shape",
|
|
6184
|
+
id: `mark-${s}-${i}`,
|
|
6185
|
+
content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
|
|
6186
|
+
position: { x, y: top, width: barWidth, height },
|
|
6187
|
+
animation: entrance(i + s)
|
|
6188
|
+
});
|
|
6189
|
+
if (input.showValues === true && value !== null && !stacked) {
|
|
6190
|
+
layers.push(
|
|
6191
|
+
valueLabel(
|
|
6192
|
+
paint,
|
|
6193
|
+
`val-${s}-${i}`,
|
|
6194
|
+
value,
|
|
6195
|
+
x + barWidth / 2,
|
|
6196
|
+
value >= 0 ? top - paint.axisFontSize * 0.9 : top + height + paint.axisFontSize * 0.9
|
|
6197
|
+
)
|
|
6198
|
+
);
|
|
6199
|
+
}
|
|
6200
|
+
});
|
|
6201
|
+
});
|
|
6202
|
+
return layers;
|
|
6203
|
+
}
|
|
6204
|
+
function paintBars(paint) {
|
|
6205
|
+
const {
|
|
6206
|
+
frame,
|
|
6207
|
+
data,
|
|
6208
|
+
colors,
|
|
6209
|
+
input,
|
|
6210
|
+
entrance,
|
|
6211
|
+
labelFontSize,
|
|
6212
|
+
axisFontSize,
|
|
6213
|
+
bodyFont,
|
|
6214
|
+
axisTextColor,
|
|
6215
|
+
gridColor
|
|
6216
|
+
} = paint;
|
|
6217
|
+
const scale = valueTicks("bar", data, input);
|
|
6218
|
+
const labelGap = axisFontSize * 0.6;
|
|
6219
|
+
const labelWidthPx = (value) => formatChartValue(value, input.unit).length * axisFontSize * 0.58;
|
|
6220
|
+
const showValueLabels = input.showValues === true && !(input.stacked === true);
|
|
6221
|
+
const reservedForLabels = showValueLabels ? data.series.flatMap((series) => series.values).filter((v) => v !== null).reduce((max, v) => Math.max(max, labelWidthPx(v)), 0) + labelGap : 0;
|
|
6222
|
+
const axisWidth = Math.max(20, frame.plot.width - reservedForLabels);
|
|
6223
|
+
const xFor = (v) => frame.plot.x + axisWidth * ((v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6224
|
+
const layers = [];
|
|
6225
|
+
scale.ticks.forEach((tick, index) => {
|
|
6226
|
+
const x = xFor(tick);
|
|
6227
|
+
layers.push({
|
|
6228
|
+
type: "shape",
|
|
6229
|
+
id: `grid-${index}`,
|
|
6230
|
+
content: {
|
|
6231
|
+
shape: "rect",
|
|
6232
|
+
fill: tick === 0 ? withAlpha(paint.context.theme.colors.text, 0.35) : gridColor
|
|
6233
|
+
},
|
|
6234
|
+
position: {
|
|
6235
|
+
x: x - (tick === 0 ? 1 : 0.5),
|
|
6236
|
+
y: frame.plot.y,
|
|
6237
|
+
width: tick === 0 ? 2 : 1,
|
|
6238
|
+
height: frame.plot.height
|
|
6239
|
+
}
|
|
6240
|
+
});
|
|
6241
|
+
layers.push({
|
|
6242
|
+
type: "text",
|
|
6243
|
+
id: `tick-${index}`,
|
|
6244
|
+
content: {
|
|
6245
|
+
text: formatChartValue(tick, input.unit),
|
|
6246
|
+
style: {
|
|
6247
|
+
fontSize: axisFontSize,
|
|
6248
|
+
fontFamily: bodyFont,
|
|
6249
|
+
color: axisTextColor,
|
|
6250
|
+
textAlign: "center"
|
|
6251
|
+
}
|
|
6252
|
+
},
|
|
6253
|
+
position: { x, y: frame.plot.y + frame.plot.height + axisFontSize * 1.2, anchor: "center" }
|
|
6254
|
+
});
|
|
6255
|
+
});
|
|
6256
|
+
const n = data.labels.length;
|
|
6257
|
+
const band = frame.plot.height / Math.max(1, n);
|
|
6258
|
+
const innerPad = band * 0.15;
|
|
6259
|
+
const stacked = input.stacked === true && data.series.length > 1;
|
|
6260
|
+
const extents = stacked ? stackSeries(data.series) : null;
|
|
6261
|
+
const groupHeight = band - innerPad * 2;
|
|
6262
|
+
const barHeight = stacked ? groupHeight : Math.max(2, (groupHeight - 4 * (data.series.length - 1)) / data.series.length);
|
|
6263
|
+
data.labels.forEach((label, i) => {
|
|
6264
|
+
if (!label) return;
|
|
6265
|
+
layers.push({
|
|
6266
|
+
type: "text",
|
|
6267
|
+
id: `cat-${i}`,
|
|
6268
|
+
content: {
|
|
6269
|
+
text: label,
|
|
6270
|
+
style: {
|
|
6271
|
+
fontSize: labelFontSize,
|
|
6272
|
+
fontFamily: bodyFont,
|
|
6273
|
+
color: axisTextColor,
|
|
6274
|
+
textAlign: "center"
|
|
6275
|
+
}
|
|
6276
|
+
},
|
|
6277
|
+
position: {
|
|
6278
|
+
x: frame.chartArea.x + (frame.plot.x - frame.chartArea.x) / 2,
|
|
6279
|
+
y: frame.plot.y + (i + 0.5) * band,
|
|
6280
|
+
anchor: "center"
|
|
6281
|
+
}
|
|
6282
|
+
});
|
|
6283
|
+
});
|
|
6284
|
+
data.series.forEach((series, s) => {
|
|
6285
|
+
series.values.forEach((value, i) => {
|
|
6286
|
+
if (i >= n) return;
|
|
6287
|
+
const extent = extents ? extents[s][i] : value === null ? null : { y0: Math.min(0, value), y1: Math.max(0, value) };
|
|
6288
|
+
if (!extent || extent.y0 === extent.y1) return;
|
|
6289
|
+
const left = xFor(extent.y0);
|
|
6290
|
+
const width = Math.max(1, xFor(extent.y1) - left);
|
|
6291
|
+
const y = stacked ? frame.plot.y + i * band + innerPad : frame.plot.y + i * band + innerPad + s * (barHeight + 4);
|
|
6292
|
+
layers.push({
|
|
6293
|
+
type: "shape",
|
|
6294
|
+
id: `mark-${s}-${i}`,
|
|
6295
|
+
content: { shape: "rect", fill: colors[s], borderRadius: stacked ? 0 : 3 },
|
|
6296
|
+
position: { x: left, y, width, height: barHeight },
|
|
6297
|
+
animation: entrance(i + s)
|
|
6298
|
+
});
|
|
6299
|
+
if (showValueLabels && value !== null) {
|
|
6300
|
+
const half = labelWidthPx(value) / 2;
|
|
6301
|
+
const labelX = value >= 0 ? left + width + labelGap + half : left - labelGap - half;
|
|
6302
|
+
layers.push(valueLabel(paint, `val-${s}-${i}`, value, labelX, y + barHeight / 2));
|
|
6303
|
+
}
|
|
6304
|
+
});
|
|
6305
|
+
});
|
|
6306
|
+
return layers;
|
|
6307
|
+
}
|
|
6308
|
+
function paintRadial(paint) {
|
|
6309
|
+
const { kind, frame, data, colors, input, entrance, context } = paint;
|
|
6310
|
+
const slices = data.labels.map((label, index) => ({ label, value: data.series[0].values[index], index })).filter(
|
|
6311
|
+
(s) => s.value !== null && Number.isFinite(s.value) && s.value > 0
|
|
6312
|
+
);
|
|
6313
|
+
const total = slices.reduce((sum, s) => sum + s.value, 0);
|
|
6314
|
+
const cx = frame.chartArea.x + frame.chartArea.width / 2;
|
|
6315
|
+
const cy = frame.chartArea.y + frame.chartArea.height / 2;
|
|
6316
|
+
const rOuter = Math.min(frame.chartArea.width, frame.chartArea.height) * 0.42;
|
|
6317
|
+
const rInner = kind === "donut" ? rOuter * 0.55 : 0;
|
|
6318
|
+
const layers = [];
|
|
6319
|
+
let startDeg = 0;
|
|
6320
|
+
slices.forEach((slice, order) => {
|
|
6321
|
+
const sweep = slice.value / total * 360;
|
|
6322
|
+
layers.push({
|
|
6323
|
+
type: "path",
|
|
6324
|
+
id: `mark-${slice.index}`,
|
|
6325
|
+
content: {
|
|
6326
|
+
d: arcPath(cx, cy, rOuter, rInner, startDeg, startDeg + sweep),
|
|
6327
|
+
fill: colors[slice.index % colors.length],
|
|
6328
|
+
stroke: context.theme.colors.background,
|
|
6329
|
+
strokeWidth: 2
|
|
6330
|
+
},
|
|
6331
|
+
position: { x: cx - rOuter, y: cy - rOuter, width: rOuter * 2, height: rOuter * 2 },
|
|
6332
|
+
animation: entrance(order)
|
|
6333
|
+
});
|
|
6334
|
+
if (input.showValues === true && sweep >= 14) {
|
|
6335
|
+
const mid = startDeg + sweep / 2;
|
|
6336
|
+
const rad = (mid - 90) * Math.PI / 180;
|
|
6337
|
+
const rLabel = rInner > 0 ? (rInner + rOuter) / 2 : rOuter * 0.68;
|
|
6338
|
+
const pctText = `${Math.round(slice.value / total * 100)}%`;
|
|
6339
|
+
layers.push({
|
|
6340
|
+
type: "text",
|
|
6341
|
+
id: `val-${slice.index}`,
|
|
6342
|
+
content: {
|
|
6343
|
+
text: pctText,
|
|
6344
|
+
style: {
|
|
6345
|
+
fontSize: paint.axisFontSize,
|
|
6346
|
+
fontFamily: paint.bodyFont,
|
|
6347
|
+
fontWeight: "bold",
|
|
6348
|
+
color: pickContrastingText(colors[slice.index % colors.length]),
|
|
6349
|
+
textAlign: "center"
|
|
6350
|
+
}
|
|
6351
|
+
},
|
|
6352
|
+
position: {
|
|
6353
|
+
x: cx + rLabel * Math.cos(rad),
|
|
6354
|
+
y: cy + rLabel * Math.sin(rad),
|
|
6355
|
+
anchor: "center"
|
|
6356
|
+
},
|
|
6357
|
+
animation: entrance(order)
|
|
6358
|
+
});
|
|
6359
|
+
}
|
|
6360
|
+
startDeg += sweep;
|
|
6361
|
+
});
|
|
6362
|
+
return layers;
|
|
6363
|
+
}
|
|
6364
|
+
function paintLines(paint) {
|
|
6365
|
+
const { kind, frame, data, colors, input, entrance } = paint;
|
|
6366
|
+
const scale = valueTicks(kind, data, input);
|
|
6367
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6368
|
+
const n = data.labels.length;
|
|
6369
|
+
const band = frame.plot.width / Math.max(1, n);
|
|
6370
|
+
const xForCenter = (i) => frame.plot.x + (i + 0.5) * band;
|
|
6371
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6372
|
+
layers.push(...paintCategoryAxisX(paint, xForCenter));
|
|
6373
|
+
const baselineY = yFor(Math.max(scale.niceMin, Math.min(scale.niceMax, 0)));
|
|
6374
|
+
const showPoints = n <= 30;
|
|
6375
|
+
data.series.forEach((series, s) => {
|
|
6376
|
+
const segments = [];
|
|
6377
|
+
let current = [];
|
|
6378
|
+
series.values.forEach((value, i) => {
|
|
6379
|
+
if (i >= n) return;
|
|
6380
|
+
if (value === null) {
|
|
6381
|
+
if (current.length > 0) segments.push(current);
|
|
6382
|
+
current = [];
|
|
6383
|
+
return;
|
|
6384
|
+
}
|
|
6385
|
+
current.push({ x: xForCenter(i), y: yFor(value) });
|
|
6386
|
+
});
|
|
6387
|
+
if (current.length > 0) segments.push(current);
|
|
6388
|
+
segments.forEach((segment, seg) => {
|
|
6389
|
+
if (kind === "area" && segment.length > 1) {
|
|
6390
|
+
layers.push({
|
|
6391
|
+
type: "path",
|
|
6392
|
+
id: `area-${s}-${seg}`,
|
|
6393
|
+
content: {
|
|
6394
|
+
d: areaPath(segment, baselineY),
|
|
6395
|
+
fill: colors[s],
|
|
6396
|
+
fillOpacity: 0.25,
|
|
6397
|
+
stroke: "none",
|
|
6398
|
+
strokeWidth: 0
|
|
6399
|
+
},
|
|
6400
|
+
position: {
|
|
6401
|
+
x: frame.plot.x,
|
|
6402
|
+
y: frame.plot.y,
|
|
6403
|
+
width: frame.plot.width,
|
|
6404
|
+
height: frame.plot.height
|
|
6405
|
+
},
|
|
6406
|
+
animation: entrance(s)
|
|
6407
|
+
});
|
|
6408
|
+
}
|
|
6409
|
+
if (segment.length > 1) {
|
|
6410
|
+
layers.push({
|
|
6411
|
+
type: "path",
|
|
6412
|
+
id: `line-${s}-${seg}`,
|
|
6413
|
+
content: { d: polylinePath(segment), stroke: colors[s], strokeWidth: 4, fill: "none" },
|
|
6414
|
+
position: {
|
|
6415
|
+
x: frame.plot.x,
|
|
6416
|
+
y: frame.plot.y,
|
|
6417
|
+
width: frame.plot.width,
|
|
6418
|
+
height: frame.plot.height
|
|
6419
|
+
},
|
|
6420
|
+
animation: entrance(s)
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
6423
|
+
});
|
|
6424
|
+
if (showPoints) {
|
|
6425
|
+
series.values.forEach((value, i) => {
|
|
6426
|
+
if (i >= n || value === null) return;
|
|
6427
|
+
const r = 5;
|
|
6428
|
+
layers.push({
|
|
6429
|
+
type: "shape",
|
|
6430
|
+
id: `pt-${s}-${i}`,
|
|
6431
|
+
content: { shape: "circle", fill: colors[s] },
|
|
6432
|
+
position: { x: xForCenter(i) - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
|
|
6433
|
+
animation: entrance(s)
|
|
6434
|
+
});
|
|
6435
|
+
if (input.showValues === true) {
|
|
6436
|
+
layers.push(
|
|
6437
|
+
valueLabel(
|
|
6438
|
+
paint,
|
|
6439
|
+
`val-${s}-${i}`,
|
|
6440
|
+
value,
|
|
6441
|
+
xForCenter(i),
|
|
6442
|
+
yFor(value) - paint.axisFontSize
|
|
6443
|
+
)
|
|
6444
|
+
);
|
|
6445
|
+
}
|
|
6446
|
+
});
|
|
6447
|
+
}
|
|
6448
|
+
});
|
|
6449
|
+
return layers;
|
|
6450
|
+
}
|
|
6451
|
+
function paintScatter(paint) {
|
|
6452
|
+
const { frame, data, colors, input, entrance } = paint;
|
|
6453
|
+
const scale = valueTicks("scatter", data, input);
|
|
6454
|
+
const yFor = (v) => frame.plot.y + frame.plot.height * (1 - (v - scale.niceMin) / (scale.niceMax - scale.niceMin));
|
|
6455
|
+
const layers = paintValueAxisY(paint, scale, yFor);
|
|
6456
|
+
const numericCount = data.labelNumbers.filter((v) => v !== null).length;
|
|
6457
|
+
const numericX = data.labels.length > 0 && numericCount / data.labels.length >= 0.6;
|
|
6458
|
+
let xFor;
|
|
6459
|
+
if (numericX) {
|
|
6460
|
+
const xs = data.labelNumbers.filter((v) => v !== null);
|
|
6461
|
+
const xScale = niceTicks(Math.min(...xs), Math.max(...xs), 5);
|
|
6462
|
+
const span = xScale.niceMax - xScale.niceMin;
|
|
6463
|
+
xFor = (i) => {
|
|
6464
|
+
const v = data.labelNumbers[i];
|
|
6465
|
+
return v === null ? null : frame.plot.x + frame.plot.width * ((v - xScale.niceMin) / span);
|
|
6466
|
+
};
|
|
6467
|
+
xScale.ticks.forEach((tick, index) => {
|
|
6468
|
+
const x = frame.plot.x + frame.plot.width * ((tick - xScale.niceMin) / span);
|
|
6469
|
+
layers.push({
|
|
6470
|
+
type: "shape",
|
|
6471
|
+
id: `xgrid-${index}`,
|
|
6472
|
+
content: { shape: "rect", fill: paint.gridColor },
|
|
6473
|
+
position: { x: x - 0.5, y: frame.plot.y, width: 1, height: frame.plot.height }
|
|
6474
|
+
});
|
|
6475
|
+
layers.push({
|
|
6476
|
+
type: "text",
|
|
6477
|
+
id: `xtick-${index}`,
|
|
6478
|
+
content: {
|
|
6479
|
+
text: formatChartValue(tick),
|
|
6480
|
+
style: {
|
|
6481
|
+
fontSize: paint.axisFontSize,
|
|
6482
|
+
fontFamily: paint.bodyFont,
|
|
6483
|
+
color: paint.axisTextColor,
|
|
6484
|
+
textAlign: "center"
|
|
6485
|
+
}
|
|
6486
|
+
},
|
|
6487
|
+
position: {
|
|
6488
|
+
x,
|
|
6489
|
+
y: frame.plot.y + frame.plot.height + paint.axisFontSize * 1.2,
|
|
6490
|
+
anchor: "center"
|
|
6491
|
+
}
|
|
6492
|
+
});
|
|
6493
|
+
});
|
|
6494
|
+
} else {
|
|
6495
|
+
const band = frame.plot.width / Math.max(1, data.labels.length);
|
|
6496
|
+
xFor = (i) => frame.plot.x + (i + 0.5) * band;
|
|
6497
|
+
layers.push(...paintCategoryAxisX(paint, (i) => frame.plot.x + (i + 0.5) * band));
|
|
6498
|
+
}
|
|
6499
|
+
const r = 7;
|
|
6500
|
+
data.series.forEach((series, s) => {
|
|
6501
|
+
series.values.forEach((value, i) => {
|
|
6502
|
+
if (value === null) return;
|
|
6503
|
+
const x = xFor(i);
|
|
6504
|
+
if (x === null) return;
|
|
6505
|
+
layers.push({
|
|
6506
|
+
type: "shape",
|
|
6507
|
+
id: `mark-${s}-${i}`,
|
|
6508
|
+
content: { shape: "circle", fill: colors[s], fillOpacity: 0.85 },
|
|
6509
|
+
position: { x: x - r, y: yFor(value) - r, width: r * 2, height: r * 2 },
|
|
6510
|
+
animation: entrance(Math.floor(i / 3) + s)
|
|
6511
|
+
});
|
|
6512
|
+
});
|
|
6513
|
+
});
|
|
6514
|
+
return layers;
|
|
6515
|
+
}
|
|
6516
|
+
function paintLegend(paint, legend, band, fontSize) {
|
|
6517
|
+
const layers = [];
|
|
6518
|
+
legend.items.forEach((item, index) => {
|
|
6519
|
+
const color = paint.colors[item.index % paint.colors.length];
|
|
6520
|
+
const rowCenterY = band.y + item.y + legend.rowHeight / 2;
|
|
6521
|
+
layers.push({
|
|
6522
|
+
type: "shape",
|
|
6523
|
+
id: `legend-swatch-${index}`,
|
|
6524
|
+
content: { shape: "rect", fill: color, borderRadius: 3 },
|
|
6525
|
+
position: {
|
|
6526
|
+
x: band.x + item.x,
|
|
6527
|
+
y: rowCenterY - item.swatchSize / 2,
|
|
6528
|
+
width: item.swatchSize,
|
|
6529
|
+
height: item.swatchSize
|
|
6530
|
+
}
|
|
6531
|
+
});
|
|
6532
|
+
layers.push({
|
|
6533
|
+
type: "text",
|
|
6534
|
+
id: `legend-label-${index}`,
|
|
6535
|
+
content: {
|
|
6536
|
+
text: item.label,
|
|
6537
|
+
style: { fontSize, fontFamily: paint.bodyFont, color: paint.context.theme.colors.text }
|
|
6538
|
+
},
|
|
6539
|
+
position: {
|
|
6540
|
+
x: band.x + item.x + item.swatchSize + fontSize * 0.5,
|
|
6541
|
+
y: rowCenterY - fontSize * 0.62
|
|
6542
|
+
}
|
|
6543
|
+
});
|
|
6544
|
+
});
|
|
6545
|
+
return layers;
|
|
6546
|
+
}
|
|
6547
|
+
function valueLabel(paint, id, value, x, y) {
|
|
6548
|
+
return {
|
|
6549
|
+
type: "text",
|
|
6550
|
+
id,
|
|
6551
|
+
content: {
|
|
6552
|
+
text: formatChartValue(value, paint.input.unit),
|
|
6553
|
+
style: {
|
|
6554
|
+
fontSize: paint.axisFontSize,
|
|
6555
|
+
fontFamily: paint.bodyFont,
|
|
6556
|
+
fontWeight: "bold",
|
|
6557
|
+
color: paint.context.theme.colors.text,
|
|
6558
|
+
textAlign: "center"
|
|
6559
|
+
}
|
|
6560
|
+
},
|
|
6561
|
+
position: { x, y, anchor: "center" }
|
|
6562
|
+
};
|
|
6563
|
+
}
|
|
6564
|
+
function tableLayer(table, band, fontSize, input, context) {
|
|
6565
|
+
const { theme } = context;
|
|
6566
|
+
const colors = resolveColorScheme(context, input.colorScheme);
|
|
6567
|
+
return {
|
|
6568
|
+
type: "table",
|
|
6569
|
+
id: "table",
|
|
6570
|
+
content: {
|
|
6571
|
+
headers: table.headers,
|
|
6572
|
+
rows: table.rows,
|
|
6573
|
+
align: table.align,
|
|
6574
|
+
style: {
|
|
6575
|
+
headerBackground: colors.accent,
|
|
6576
|
+
headerColor: pickContrastingText(colors.accent),
|
|
6577
|
+
cellBackground: withAlpha(theme.colors.text, 0.04),
|
|
6578
|
+
cellColor: theme.colors.text,
|
|
6579
|
+
borderColor: withAlpha(theme.colors.text, 0.15),
|
|
6580
|
+
fontSize,
|
|
6581
|
+
fontFamily: getThemeFont(context, "body"),
|
|
6582
|
+
headerFontFamily: getThemeFont(context, "title"),
|
|
6583
|
+
borderRadius: 8
|
|
6584
|
+
}
|
|
6585
|
+
},
|
|
6586
|
+
position: { x: band.x, y: band.y, width: band.width, height: band.height },
|
|
6587
|
+
animation: { type: "fadeIn", duration: 1, delay: 0.4 }
|
|
6588
|
+
};
|
|
6589
|
+
}
|
|
6590
|
+
|
|
6591
|
+
// src/doc/templates/chartBlock.ts
|
|
6592
|
+
function barChart(input, context) {
|
|
6593
|
+
return renderChart("bar", input, context);
|
|
6594
|
+
}
|
|
6595
|
+
function columnChart(input, context) {
|
|
6596
|
+
return renderChart("column", input, context);
|
|
6597
|
+
}
|
|
6598
|
+
function pieChart(input, context) {
|
|
6599
|
+
return renderChart("pie", input, context);
|
|
6600
|
+
}
|
|
6601
|
+
function donutChart(input, context) {
|
|
6602
|
+
return renderChart("donut", input, context);
|
|
6603
|
+
}
|
|
6604
|
+
function lineChart(input, context) {
|
|
6605
|
+
return renderChart("line", input, context);
|
|
6606
|
+
}
|
|
6607
|
+
function areaChart(input, context) {
|
|
6608
|
+
return renderChart("area", input, context);
|
|
6609
|
+
}
|
|
6610
|
+
function scatterChart(input, context) {
|
|
6611
|
+
return renderChart("scatter", input, context);
|
|
6612
|
+
}
|
|
6613
|
+
|
|
5588
6614
|
// src/doc/templates/registry.ts
|
|
5589
6615
|
var templateRegistry = {
|
|
5590
6616
|
title: titleBlock,
|
|
@@ -5608,6 +6634,13 @@ var templateRegistry = {
|
|
|
5608
6634
|
videoWithCaption,
|
|
5609
6635
|
videoPullQuote,
|
|
5610
6636
|
dataTable,
|
|
6637
|
+
barChart,
|
|
6638
|
+
columnChart,
|
|
6639
|
+
pieChart,
|
|
6640
|
+
donutChart,
|
|
6641
|
+
lineChart,
|
|
6642
|
+
areaChart,
|
|
6643
|
+
scatterChart,
|
|
5611
6644
|
diagram: diagramBlock,
|
|
5612
6645
|
tree: treeBlock,
|
|
5613
6646
|
timeline: timelineBlock,
|
|
@@ -5723,6 +6756,49 @@ var CAPTION_POSITION_VALUES = ["bottom", "top", "center"];
|
|
|
5723
6756
|
var MAP_STYLE_VALUES = ["terrain", "satellite", "road", "toner", "watercolor"];
|
|
5724
6757
|
var DATE_EVENT_MOOD_VALUES = ["neutral", "somber", "celebratory"];
|
|
5725
6758
|
var COLOR_SCHEME_HINT = "theme color-scheme name \u2014 e.g. 'blue', 'green'";
|
|
6759
|
+
function CHART_DESCRIPTORS(extra = []) {
|
|
6760
|
+
return [
|
|
6761
|
+
{ key: "title", description: "Optional title above the chart" },
|
|
6762
|
+
{
|
|
6763
|
+
key: "labelColumn",
|
|
6764
|
+
description: "Category/label column",
|
|
6765
|
+
valueHint: "header name or 0-based index"
|
|
6766
|
+
},
|
|
6767
|
+
{
|
|
6768
|
+
key: "valueColumns",
|
|
6769
|
+
description: "Value columns to plot",
|
|
6770
|
+
coerce: "stringList",
|
|
6771
|
+
valueHint: "comma-separated header names or 0-based indexes"
|
|
6772
|
+
},
|
|
6773
|
+
{
|
|
6774
|
+
key: "showTable",
|
|
6775
|
+
description: "Render the source table beneath the chart",
|
|
6776
|
+
coerce: "boolean"
|
|
6777
|
+
},
|
|
6778
|
+
{ key: "showLegend", description: "Show a series legend", coerce: "boolean" },
|
|
6779
|
+
{ key: "showValues", description: "Print numeric value labels on marks", coerce: "boolean" },
|
|
6780
|
+
{ key: "unit", description: 'Unit suffix for tick/value labels (e.g. "km")' },
|
|
6781
|
+
{
|
|
6782
|
+
key: "colorScheme",
|
|
6783
|
+
description: "Color scheme seeding the series color rotation",
|
|
6784
|
+
valueHint: COLOR_SCHEME_HINT
|
|
6785
|
+
},
|
|
6786
|
+
// `headers`/`rows` are normally derived from the block's markdown table;
|
|
6787
|
+
// declared so data-fence-supplied values aren't flagged as unknown.
|
|
6788
|
+
{
|
|
6789
|
+
key: "headers",
|
|
6790
|
+
description: "Header cells (normally derived from the table)",
|
|
6791
|
+
coerce: "stringList"
|
|
6792
|
+
},
|
|
6793
|
+
{ key: "rows", description: "Table rows (supply via a data fence)" },
|
|
6794
|
+
...extra
|
|
6795
|
+
];
|
|
6796
|
+
}
|
|
6797
|
+
var STACKED_DESCRIPTOR = {
|
|
6798
|
+
key: "stacked",
|
|
6799
|
+
description: "Stack series instead of grouping them side by side",
|
|
6800
|
+
coerce: "boolean"
|
|
6801
|
+
};
|
|
5726
6802
|
var BASE_INPUT_DESCRIPTORS = [
|
|
5727
6803
|
{
|
|
5728
6804
|
key: "imageTreatment",
|
|
@@ -5844,6 +6920,13 @@ var TEMPLATE_INPUT_DESCRIPTORS = {
|
|
|
5844
6920
|
{ key: "align", description: "Per-column alignment (supply via a data fence)" },
|
|
5845
6921
|
{ key: "colorScheme", description: "Header color scheme", valueHint: COLOR_SCHEME_HINT }
|
|
5846
6922
|
],
|
|
6923
|
+
barChart: CHART_DESCRIPTORS([STACKED_DESCRIPTOR]),
|
|
6924
|
+
columnChart: CHART_DESCRIPTORS([STACKED_DESCRIPTOR]),
|
|
6925
|
+
pieChart: CHART_DESCRIPTORS(),
|
|
6926
|
+
donutChart: CHART_DESCRIPTORS(),
|
|
6927
|
+
lineChart: CHART_DESCRIPTORS(),
|
|
6928
|
+
areaChart: CHART_DESCRIPTORS(),
|
|
6929
|
+
scatterChart: CHART_DESCRIPTORS(),
|
|
5847
6930
|
tree: [
|
|
5848
6931
|
{ key: "title", description: "Optional title above the tree" },
|
|
5849
6932
|
{ key: "colorScheme", description: "Folder-row color scheme", valueHint: COLOR_SCHEME_HINT },
|
|
@@ -6315,6 +7398,55 @@ var BLOCK_MEDIA_LAYOUT_POLICIES = {
|
|
|
6315
7398
|
unconsumedMedia: "reserved-slot",
|
|
6316
7399
|
variants: DENSE_VARIANTS
|
|
6317
7400
|
},
|
|
7401
|
+
barChart: {
|
|
7402
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7403
|
+
noMedia: "template-default",
|
|
7404
|
+
ownership: "supplemental",
|
|
7405
|
+
unconsumedMedia: "reserved-slot",
|
|
7406
|
+
variants: DENSE_VARIANTS
|
|
7407
|
+
},
|
|
7408
|
+
columnChart: {
|
|
7409
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7410
|
+
noMedia: "template-default",
|
|
7411
|
+
ownership: "supplemental",
|
|
7412
|
+
unconsumedMedia: "reserved-slot",
|
|
7413
|
+
variants: DENSE_VARIANTS
|
|
7414
|
+
},
|
|
7415
|
+
pieChart: {
|
|
7416
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7417
|
+
noMedia: "template-default",
|
|
7418
|
+
ownership: "supplemental",
|
|
7419
|
+
unconsumedMedia: "reserved-slot",
|
|
7420
|
+
variants: DENSE_VARIANTS
|
|
7421
|
+
},
|
|
7422
|
+
donutChart: {
|
|
7423
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7424
|
+
noMedia: "template-default",
|
|
7425
|
+
ownership: "supplemental",
|
|
7426
|
+
unconsumedMedia: "reserved-slot",
|
|
7427
|
+
variants: DENSE_VARIANTS
|
|
7428
|
+
},
|
|
7429
|
+
lineChart: {
|
|
7430
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7431
|
+
noMedia: "template-default",
|
|
7432
|
+
ownership: "supplemental",
|
|
7433
|
+
unconsumedMedia: "reserved-slot",
|
|
7434
|
+
variants: DENSE_VARIANTS
|
|
7435
|
+
},
|
|
7436
|
+
areaChart: {
|
|
7437
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7438
|
+
noMedia: "template-default",
|
|
7439
|
+
ownership: "supplemental",
|
|
7440
|
+
unconsumedMedia: "reserved-slot",
|
|
7441
|
+
variants: DENSE_VARIANTS
|
|
7442
|
+
},
|
|
7443
|
+
scatterChart: {
|
|
7444
|
+
summary: "The chart remains wide; supplemental media stacks below the dense data region.",
|
|
7445
|
+
noMedia: "template-default",
|
|
7446
|
+
ownership: "supplemental",
|
|
7447
|
+
unconsumedMedia: "reserved-slot",
|
|
7448
|
+
variants: DENSE_VARIANTS
|
|
7449
|
+
},
|
|
6318
7450
|
diagram: {
|
|
6319
7451
|
summary: "The node-and-edge canvas is intrinsic media and retains its spatial coordinate system.",
|
|
6320
7452
|
noMedia: "intrinsic-visual",
|
|
@@ -7300,6 +8432,34 @@ var TEMPLATE_METADATA = {
|
|
|
7300
8432
|
label: "Data Table",
|
|
7301
8433
|
description: "Renders tabular data in a clean, styled table with a header row."
|
|
7302
8434
|
},
|
|
8435
|
+
barChart: {
|
|
8436
|
+
label: "Bar Chart",
|
|
8437
|
+
description: "Horizontal bars from the block's markdown table \u2014 one bar per row."
|
|
8438
|
+
},
|
|
8439
|
+
columnChart: {
|
|
8440
|
+
label: "Column Chart",
|
|
8441
|
+
description: "Vertical columns from the block's markdown table \u2014 one column per row."
|
|
8442
|
+
},
|
|
8443
|
+
pieChart: {
|
|
8444
|
+
label: "Pie Chart",
|
|
8445
|
+
description: "A pie of the block's markdown table \u2014 each row becomes a labeled slice."
|
|
8446
|
+
},
|
|
8447
|
+
donutChart: {
|
|
8448
|
+
label: "Donut Chart",
|
|
8449
|
+
description: "A donut of the block's markdown table \u2014 each row becomes a labeled slice."
|
|
8450
|
+
},
|
|
8451
|
+
lineChart: {
|
|
8452
|
+
label: "Line Chart",
|
|
8453
|
+
description: "Lines from the block's markdown table \u2014 one line per value column."
|
|
8454
|
+
},
|
|
8455
|
+
areaChart: {
|
|
8456
|
+
label: "Area Chart",
|
|
8457
|
+
description: "Filled area lines from the block's markdown table \u2014 one area per value column."
|
|
8458
|
+
},
|
|
8459
|
+
scatterChart: {
|
|
8460
|
+
label: "Scatter Chart",
|
|
8461
|
+
description: "An x/y scatter plot from the block's markdown table's numeric columns."
|
|
8462
|
+
},
|
|
7303
8463
|
diagram: {
|
|
7304
8464
|
label: "Diagram",
|
|
7305
8465
|
description: "Renders sub-headings as connected nodes \u2014 edit visually as a node-and-edge diagram."
|
|
@@ -7445,6 +8605,48 @@ var TEMPLATE_AUTHORING_METADATA = {
|
|
|
7445
8605
|
safeForContentFirst: false,
|
|
7446
8606
|
placement: "heading"
|
|
7447
8607
|
},
|
|
8608
|
+
barChart: {
|
|
8609
|
+
role: "data",
|
|
8610
|
+
bodyPolicy: "structured",
|
|
8611
|
+
safeForContentFirst: false,
|
|
8612
|
+
placement: "heading"
|
|
8613
|
+
},
|
|
8614
|
+
columnChart: {
|
|
8615
|
+
role: "data",
|
|
8616
|
+
bodyPolicy: "structured",
|
|
8617
|
+
safeForContentFirst: false,
|
|
8618
|
+
placement: "heading"
|
|
8619
|
+
},
|
|
8620
|
+
pieChart: {
|
|
8621
|
+
role: "data",
|
|
8622
|
+
bodyPolicy: "structured",
|
|
8623
|
+
safeForContentFirst: false,
|
|
8624
|
+
placement: "heading"
|
|
8625
|
+
},
|
|
8626
|
+
donutChart: {
|
|
8627
|
+
role: "data",
|
|
8628
|
+
bodyPolicy: "structured",
|
|
8629
|
+
safeForContentFirst: false,
|
|
8630
|
+
placement: "heading"
|
|
8631
|
+
},
|
|
8632
|
+
lineChart: {
|
|
8633
|
+
role: "data",
|
|
8634
|
+
bodyPolicy: "structured",
|
|
8635
|
+
safeForContentFirst: false,
|
|
8636
|
+
placement: "heading"
|
|
8637
|
+
},
|
|
8638
|
+
areaChart: {
|
|
8639
|
+
role: "data",
|
|
8640
|
+
bodyPolicy: "structured",
|
|
8641
|
+
safeForContentFirst: false,
|
|
8642
|
+
placement: "heading"
|
|
8643
|
+
},
|
|
8644
|
+
scatterChart: {
|
|
8645
|
+
role: "data",
|
|
8646
|
+
bodyPolicy: "structured",
|
|
8647
|
+
safeForContentFirst: false,
|
|
8648
|
+
placement: "heading"
|
|
8649
|
+
},
|
|
7448
8650
|
diagram: {
|
|
7449
8651
|
role: "spatial",
|
|
7450
8652
|
bodyPolicy: "structured",
|
|
@@ -7694,7 +8896,8 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
7694
8896
|
audioSegments,
|
|
7695
8897
|
customTemplates,
|
|
7696
8898
|
failureMode = "fallback",
|
|
7697
|
-
onDiagnostic
|
|
8899
|
+
onDiagnostic,
|
|
8900
|
+
splitLongBlocks = true
|
|
7698
8901
|
} = opts;
|
|
7699
8902
|
const totalBlocks = blocks.length;
|
|
7700
8903
|
const registry = customTemplates && customTemplates.length > 0 ? buildRegistry(customTemplates) : templateRegistry;
|
|
@@ -7885,27 +9088,29 @@ function expandDocBlocks(blocks, options = {}) {
|
|
|
7885
9088
|
}
|
|
7886
9089
|
}
|
|
7887
9090
|
const MAX_BLOCK_DURATION = 20;
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
9091
|
+
if (splitLongBlocks) {
|
|
9092
|
+
for (let i = 0; i < segmentExpandedBlocks.length; i++) {
|
|
9093
|
+
const block = segmentExpandedBlocks[i];
|
|
9094
|
+
if (block.duration > MAX_BLOCK_DURATION) {
|
|
9095
|
+
const numParts = Math.ceil(block.duration / MAX_BLOCK_DURATION);
|
|
9096
|
+
const partDuration = block.duration / numParts;
|
|
9097
|
+
if (partDuration >= MIN_TRANSITION_GAP) {
|
|
9098
|
+
block.duration = partDuration;
|
|
9099
|
+
for (let p = 1; p < numParts; p++) {
|
|
9100
|
+
const splitBlock = {
|
|
9101
|
+
id: `${block.id}-split-${p}`,
|
|
9102
|
+
startTime: block.startTime + p * partDuration,
|
|
9103
|
+
duration: partDuration,
|
|
9104
|
+
audioSegment: block.audioSegment,
|
|
9105
|
+
layers: (block.layers ?? []).map((layer) => ({
|
|
9106
|
+
...cloneData(layer),
|
|
9107
|
+
id: `${layer.id}-split-${p}`
|
|
9108
|
+
})),
|
|
9109
|
+
transition: { type: "dissolve", duration: 1 },
|
|
9110
|
+
template: block.template
|
|
9111
|
+
};
|
|
9112
|
+
expandedBlocks.push(splitBlock);
|
|
9113
|
+
}
|
|
7909
9114
|
}
|
|
7910
9115
|
}
|
|
7911
9116
|
}
|
|
@@ -8005,16 +9210,15 @@ function readCustomTemplatesFromFrontmatter(frontmatter) {
|
|
|
8005
9210
|
for (const entry of candidates) {
|
|
8006
9211
|
if (!entry || typeof entry !== "object") continue;
|
|
8007
9212
|
const e = entry;
|
|
8008
|
-
|
|
8009
|
-
if (!Array.isArray(e.layers)) continue;
|
|
8010
|
-
const def = {
|
|
9213
|
+
const candidate = {
|
|
8011
9214
|
name: e.name,
|
|
8012
9215
|
label: e.label,
|
|
8013
|
-
viewport:
|
|
9216
|
+
viewport: e.viewport ?? { ...DEFAULT_VIEWPORT },
|
|
8014
9217
|
layers: e.layers
|
|
8015
9218
|
};
|
|
8016
|
-
if (
|
|
8017
|
-
|
|
9219
|
+
if (e.description !== void 0) candidate.description = e.description;
|
|
9220
|
+
const result = validateCustomTemplateDefinition(candidate);
|
|
9221
|
+
if (result.template) out.push(result.template);
|
|
8018
9222
|
}
|
|
8019
9223
|
return out.length > 0 ? out : void 0;
|
|
8020
9224
|
}
|
|
@@ -8030,15 +9234,6 @@ function writeCustomTemplatesToFrontmatter(templates, options) {
|
|
|
8030
9234
|
}
|
|
8031
9235
|
return JSON.stringify(map, null, options?.pretty ? 2 : void 0);
|
|
8032
9236
|
}
|
|
8033
|
-
function readViewport(raw) {
|
|
8034
|
-
if (raw && typeof raw === "object") {
|
|
8035
|
-
const v = raw;
|
|
8036
|
-
const w = typeof v.width === "number" ? v.width : DEFAULT_VIEWPORT.width;
|
|
8037
|
-
const h = typeof v.height === "number" ? v.height : DEFAULT_VIEWPORT.height;
|
|
8038
|
-
return { width: w, height: h };
|
|
8039
|
-
}
|
|
8040
|
-
return DEFAULT_VIEWPORT;
|
|
8041
|
-
}
|
|
8042
9237
|
function expandCompactMap(map) {
|
|
8043
9238
|
return Object.entries(map).map(([name, raw]) => {
|
|
8044
9239
|
const e = raw && typeof raw === "object" ? raw : {};
|
|
@@ -8281,14 +9476,8 @@ function extractTableData(table) {
|
|
|
8281
9476
|
}
|
|
8282
9477
|
|
|
8283
9478
|
// src/doc/standaloneAnnotation.ts
|
|
8284
|
-
function
|
|
8285
|
-
|
|
8286
|
-
const children = node.children ?? [];
|
|
8287
|
-
if (children.length !== 1 || children[0].type !== "text") return null;
|
|
8288
|
-
const text = children[0].value.trim();
|
|
8289
|
-
const match = matchTrailingTemplateAnnotation(text);
|
|
8290
|
-
if (!match || match.index !== 0) return null;
|
|
8291
|
-
const tokens = tokenizeAttrTokens(match.inner.trim());
|
|
9479
|
+
function parseAnnotationInner(inner) {
|
|
9480
|
+
const tokens = tokenizeAttrTokens(inner.trim());
|
|
8292
9481
|
const firstIsParam = tokens.length > 0 && tokens[0].indexOf("=") > 0;
|
|
8293
9482
|
const template = firstIsParam || tokens.length === 0 ? void 0 : tokens[0];
|
|
8294
9483
|
const startIdx = firstIsParam ? 0 : 1;
|
|
@@ -8299,6 +9488,15 @@ function parseStandaloneAnnotation(node) {
|
|
|
8299
9488
|
}
|
|
8300
9489
|
return { template, params };
|
|
8301
9490
|
}
|
|
9491
|
+
function parseStandaloneAnnotation(node) {
|
|
9492
|
+
if (node.type !== "paragraph") return null;
|
|
9493
|
+
const children = node.children ?? [];
|
|
9494
|
+
if (children.length !== 1 || children[0].type !== "text") return null;
|
|
9495
|
+
const text = children[0].value.trim();
|
|
9496
|
+
const match = matchTrailingTemplateAnnotation(text);
|
|
9497
|
+
if (!match || match.index !== 0) return null;
|
|
9498
|
+
return parseAnnotationInner(match.inner);
|
|
9499
|
+
}
|
|
8302
9500
|
|
|
8303
9501
|
// src/doc/mediaAnnotations.ts
|
|
8304
9502
|
var MEDIA_TEMPLATES = /* @__PURE__ */ new Set(["audio", "video", "media"]);
|
|
@@ -8309,6 +9507,27 @@ function time(raw) {
|
|
|
8309
9507
|
if (raw == null) return null;
|
|
8310
9508
|
return parseTimeSeconds(raw);
|
|
8311
9509
|
}
|
|
9510
|
+
function videoPlacement(params) {
|
|
9511
|
+
const explicit = params.placement ?? params.presentation;
|
|
9512
|
+
if (explicit === "content") return "content";
|
|
9513
|
+
if (explicit === "pip" || explicit === "picture-in-picture") return "picture-in-picture";
|
|
9514
|
+
if (explicit === "overlay" || explicit === "full-frame") return "overlay";
|
|
9515
|
+
if (params.pip === "" || params.pip === "true") return "picture-in-picture";
|
|
9516
|
+
if (params.overlay === "" || params.overlay === "true") return "overlay";
|
|
9517
|
+
return void 0;
|
|
9518
|
+
}
|
|
9519
|
+
function lockToBlock(raw) {
|
|
9520
|
+
return raw !== "false" && raw !== "0";
|
|
9521
|
+
}
|
|
9522
|
+
function videoPipSize(raw) {
|
|
9523
|
+
return raw === "small" || raw === "large" ? raw : void 0;
|
|
9524
|
+
}
|
|
9525
|
+
function videoPipShape(raw) {
|
|
9526
|
+
return raw === "square" || raw === "wide" ? raw : void 0;
|
|
9527
|
+
}
|
|
9528
|
+
function videoPipPosition(raw) {
|
|
9529
|
+
return raw === "top-left" || raw === "top-right" || raw === "bottom-left" || raw === "bottom-right" ? raw : void 0;
|
|
9530
|
+
}
|
|
8312
9531
|
function toMediaClip(parsed, id) {
|
|
8313
9532
|
const { template, params } = parsed;
|
|
8314
9533
|
if (!template || !MEDIA_TEMPLATES.has(template)) return null;
|
|
@@ -8323,13 +9542,90 @@ function toMediaClip(parsed, id) {
|
|
|
8323
9542
|
startAt: time(params.startAt ?? params.startTime) ?? 0,
|
|
8324
9543
|
anchor
|
|
8325
9544
|
};
|
|
9545
|
+
const placement = kind === "video" ? videoPlacement(params) : void 0;
|
|
9546
|
+
if (placement && placement !== "content") clip.placement = placement;
|
|
9547
|
+
const pipSize = videoPipSize(params.pipSize);
|
|
9548
|
+
const pipShape = videoPipShape(params.pipShape);
|
|
9549
|
+
const pipPosition = videoPipPosition(params.pipPosition);
|
|
9550
|
+
if (pipSize) clip.pipSize = pipSize;
|
|
9551
|
+
if (pipShape) clip.pipShape = pipShape;
|
|
9552
|
+
if (pipPosition) clip.pipPosition = pipPosition;
|
|
9553
|
+
if (kind === "video" && placement && placement !== "content" && params.lockToBlock != null) {
|
|
9554
|
+
const locked = lockToBlock(params.lockToBlock);
|
|
9555
|
+
clip.lockToBlock = locked;
|
|
9556
|
+
clip.anchor = locked ? "block" : "document";
|
|
9557
|
+
if (locked) clip.startAt = 0;
|
|
9558
|
+
}
|
|
8326
9559
|
const clipStart = time(params.clipStart);
|
|
8327
9560
|
const clipEnd = time(params.clipEnd);
|
|
8328
9561
|
if (clipStart != null) clip.clipStart = clipStart;
|
|
8329
9562
|
if (clipEnd != null) clip.clipEnd = clipEnd;
|
|
9563
|
+
if (clip.lockToBlock === true) delete clip.clipEnd;
|
|
8330
9564
|
if (params.spillover === "true" || params.spillover === "") clip.spillover = true;
|
|
8331
9565
|
return clip;
|
|
8332
9566
|
}
|
|
9567
|
+
function firstVideoElement(children) {
|
|
9568
|
+
const visit = (nodes) => {
|
|
9569
|
+
for (const child of nodes) {
|
|
9570
|
+
if (child.type !== "htmlElement") continue;
|
|
9571
|
+
if (child.tagName === "video") return child;
|
|
9572
|
+
const nested = visit(child.children);
|
|
9573
|
+
if (nested) return nested;
|
|
9574
|
+
}
|
|
9575
|
+
return null;
|
|
9576
|
+
};
|
|
9577
|
+
return visit(children);
|
|
9578
|
+
}
|
|
9579
|
+
function standaloneHtmlVideo(node) {
|
|
9580
|
+
if (node.type === "htmlBlock") {
|
|
9581
|
+
if (!/^\s*<video\b/i.test(node.rawHtml)) return null;
|
|
9582
|
+
const element = firstVideoElement(node.htmlChildren);
|
|
9583
|
+
return element ? { element, raw: node.rawHtml.trim() } : null;
|
|
9584
|
+
}
|
|
9585
|
+
if (node.type !== "paragraph" || !node.children.every((child) => child.type === "htmlInline")) {
|
|
9586
|
+
return null;
|
|
9587
|
+
}
|
|
9588
|
+
const raw = node.children.map((child) => child.type === "htmlInline" ? child.rawHtml : "").join("");
|
|
9589
|
+
if (!/^\s*<video\b[\s\S]*<\/video>\s*$/i.test(raw)) return null;
|
|
9590
|
+
for (const child of node.children) {
|
|
9591
|
+
if (child.type !== "htmlInline") continue;
|
|
9592
|
+
const element = firstVideoElement(child.htmlChildren);
|
|
9593
|
+
if (element) return { element, raw: raw.trim() };
|
|
9594
|
+
}
|
|
9595
|
+
return null;
|
|
9596
|
+
}
|
|
9597
|
+
function htmlVideoClip(node, id) {
|
|
9598
|
+
const authored = standaloneHtmlVideo(node);
|
|
9599
|
+
if (!authored) return null;
|
|
9600
|
+
const { element } = authored;
|
|
9601
|
+
const placement = videoPlacement({
|
|
9602
|
+
placement: element.attributes["data-squisq-video-placement"] ?? ""
|
|
9603
|
+
});
|
|
9604
|
+
if (!placement || placement === "content") return null;
|
|
9605
|
+
const src = element.attributes.src;
|
|
9606
|
+
if (!src) return null;
|
|
9607
|
+
const locked = lockToBlock(element.attributes["data-squisq-video-lock-to-block"]);
|
|
9608
|
+
const clip = {
|
|
9609
|
+
id,
|
|
9610
|
+
src,
|
|
9611
|
+
kind: "video",
|
|
9612
|
+
placement,
|
|
9613
|
+
lockToBlock: locked,
|
|
9614
|
+
startAt: locked ? 0 : time(element.attributes["data-squisq-video-start-at"]) ?? 0,
|
|
9615
|
+
anchor: locked ? "block" : "document"
|
|
9616
|
+
};
|
|
9617
|
+
const pipSize = videoPipSize(element.attributes["data-squisq-video-pip-size"]);
|
|
9618
|
+
const pipShape = videoPipShape(element.attributes["data-squisq-video-pip-shape"]);
|
|
9619
|
+
const pipPosition = videoPipPosition(element.attributes["data-squisq-video-pip-position"]);
|
|
9620
|
+
if (pipSize) clip.pipSize = pipSize;
|
|
9621
|
+
if (pipShape) clip.pipShape = pipShape;
|
|
9622
|
+
if (pipPosition) clip.pipPosition = pipPosition;
|
|
9623
|
+
const clipStart = time(element.attributes["data-squisq-video-clip-start"]);
|
|
9624
|
+
const clipEnd = time(element.attributes["data-squisq-video-clip-end"]);
|
|
9625
|
+
if (clipStart != null) clip.clipStart = clipStart;
|
|
9626
|
+
if (!locked && clipEnd != null) clip.clipEnd = clipEnd;
|
|
9627
|
+
return clip;
|
|
9628
|
+
}
|
|
8333
9629
|
function extractMediaFromContents(contents, makeId, blockId) {
|
|
8334
9630
|
const media = [];
|
|
8335
9631
|
const documentMedia = [];
|
|
@@ -8338,17 +9634,19 @@ function extractMediaFromContents(contents, makeId, blockId) {
|
|
|
8338
9634
|
let originalIndex = 0;
|
|
8339
9635
|
for (const node of contents ?? []) {
|
|
8340
9636
|
const parsed = parseStandaloneAnnotation(node);
|
|
8341
|
-
const clip = parsed ? toMediaClip(parsed, "") :
|
|
8342
|
-
if (
|
|
9637
|
+
const clip = parsed ? toMediaClip(parsed, "") : htmlVideoClip(node, "");
|
|
9638
|
+
if (clip) {
|
|
8343
9639
|
clip.id = makeId(clip.src, index++);
|
|
8344
9640
|
const line = node.position?.start.line;
|
|
8345
9641
|
if (line != null) clip.sourceLine = line;
|
|
8346
9642
|
if (blockId !== void 0) {
|
|
8347
|
-
const
|
|
9643
|
+
const authoredHtml = standaloneHtmlVideo(node);
|
|
9644
|
+
const rawText = node.type === "paragraph" && node.children?.[0]?.type === "text" ? node.children[0].value.trim() : authoredHtml?.raw;
|
|
8348
9645
|
clip.origin = {
|
|
8349
9646
|
blockId,
|
|
8350
9647
|
index: originalIndex,
|
|
8351
|
-
...rawText !== void 0 ? { raw: rawText } : {}
|
|
9648
|
+
...rawText !== void 0 ? { raw: rawText } : {},
|
|
9649
|
+
...authoredHtml ? { format: "html" } : {}
|
|
8352
9650
|
};
|
|
8353
9651
|
}
|
|
8354
9652
|
(clip.anchor === "document" ? documentMedia : media).push(clip);
|
|
@@ -8412,6 +9710,78 @@ function extractTemplateBlocksFromContents(contents, options) {
|
|
|
8412
9710
|
return { leading, blocks };
|
|
8413
9711
|
}
|
|
8414
9712
|
|
|
9713
|
+
// src/doc/promoteBodyAnnotation.ts
|
|
9714
|
+
function lastTextChildValue(node) {
|
|
9715
|
+
if (node.type !== "paragraph") return null;
|
|
9716
|
+
const children = node.children;
|
|
9717
|
+
if (children.length === 0) return null;
|
|
9718
|
+
const last = children[children.length - 1];
|
|
9719
|
+
return last.type === "text" ? last.value : null;
|
|
9720
|
+
}
|
|
9721
|
+
function isBodyAnnotationNode(node) {
|
|
9722
|
+
if (parseStandaloneAnnotation(node)?.template) return true;
|
|
9723
|
+
const value = lastTextChildValue(node);
|
|
9724
|
+
if (value == null) return false;
|
|
9725
|
+
const match = matchTrailingTemplateAnnotation(value);
|
|
9726
|
+
return !!match && match.index > 0 && !!parseAnnotationInner(match.inner).template;
|
|
9727
|
+
}
|
|
9728
|
+
function stripTrailingToken(contents, index, cut) {
|
|
9729
|
+
const out = [...contents];
|
|
9730
|
+
const para = out[index];
|
|
9731
|
+
if (para.type !== "paragraph") return out;
|
|
9732
|
+
const children = [...para.children];
|
|
9733
|
+
const last = children[children.length - 1];
|
|
9734
|
+
if (last.type !== "text") return out;
|
|
9735
|
+
const kept = last.value.slice(0, cut);
|
|
9736
|
+
if (kept.length === 0 && children.length === 1) {
|
|
9737
|
+
out.splice(index, 1);
|
|
9738
|
+
} else {
|
|
9739
|
+
children[children.length - 1] = { ...last, value: kept };
|
|
9740
|
+
out[index] = { ...para, children };
|
|
9741
|
+
}
|
|
9742
|
+
return out;
|
|
9743
|
+
}
|
|
9744
|
+
function detectPromotableBodyAnnotation(contents) {
|
|
9745
|
+
if (!contents || contents.length === 0) return null;
|
|
9746
|
+
const lastIndex = contents.length - 1;
|
|
9747
|
+
const last = contents[lastIndex];
|
|
9748
|
+
let data = null;
|
|
9749
|
+
let strippedContents = null;
|
|
9750
|
+
const whole = parseStandaloneAnnotation(last);
|
|
9751
|
+
if (whole?.template && last.type === "paragraph") {
|
|
9752
|
+
const first = last.children[0];
|
|
9753
|
+
data = {
|
|
9754
|
+
template: whole.template,
|
|
9755
|
+
params: whole.params,
|
|
9756
|
+
origin: {
|
|
9757
|
+
kind: "paragraph",
|
|
9758
|
+
index: lastIndex,
|
|
9759
|
+
raw: first && first.type === "text" ? first.value.trim() : ""
|
|
9760
|
+
}
|
|
9761
|
+
};
|
|
9762
|
+
strippedContents = contents.slice(0, lastIndex);
|
|
9763
|
+
} else {
|
|
9764
|
+
const value = lastTextChildValue(last);
|
|
9765
|
+
const match = value != null ? matchTrailingTemplateAnnotation(value) : null;
|
|
9766
|
+
if (value != null && match && match.index > 0) {
|
|
9767
|
+
const parsed = parseAnnotationInner(match.inner);
|
|
9768
|
+
if (parsed.template) {
|
|
9769
|
+
data = {
|
|
9770
|
+
template: parsed.template,
|
|
9771
|
+
params: parsed.params,
|
|
9772
|
+
origin: { kind: "trailing", index: lastIndex, suffix: value.slice(match.index) }
|
|
9773
|
+
};
|
|
9774
|
+
strippedContents = stripTrailingToken(contents, lastIndex, match.index);
|
|
9775
|
+
}
|
|
9776
|
+
}
|
|
9777
|
+
}
|
|
9778
|
+
if (!data || !strippedContents) return null;
|
|
9779
|
+
for (let i = 0; i < lastIndex; i++) {
|
|
9780
|
+
if (isBodyAnnotationNode(contents[i])) return null;
|
|
9781
|
+
}
|
|
9782
|
+
return { data, strippedContents };
|
|
9783
|
+
}
|
|
9784
|
+
|
|
8415
9785
|
// src/doc/markdownToDoc.ts
|
|
8416
9786
|
var SLUG_NON_WORD_RE = /[^\w\s-]/g;
|
|
8417
9787
|
var SLUG_SPACES_RE = /[\s_]+/g;
|
|
@@ -8457,6 +9827,9 @@ function pinnedHeadingMeta(heading) {
|
|
|
8457
9827
|
return { ...fromAnnotation, ...fromPandoc };
|
|
8458
9828
|
}
|
|
8459
9829
|
function pinnedMeta(block) {
|
|
9830
|
+
if (block.promotedBodyAnnotation?.params) {
|
|
9831
|
+
return coerceAnnotationValues(block.promotedBodyAnnotation.params).blockMeta;
|
|
9832
|
+
}
|
|
8460
9833
|
if (block.sourceHeading) return pinnedHeadingMeta(block.sourceHeading);
|
|
8461
9834
|
if (block.sourceAnnotation?.params) {
|
|
8462
9835
|
return coerceAnnotationValues(block.sourceAnnotation.params).blockMeta;
|
|
@@ -8466,6 +9839,22 @@ function pinnedMeta(block) {
|
|
|
8466
9839
|
function getPinnedBlockMeta(block) {
|
|
8467
9840
|
return pinnedMeta(block);
|
|
8468
9841
|
}
|
|
9842
|
+
function promoteBodyAnnotations(blocks) {
|
|
9843
|
+
for (const block of blocks) {
|
|
9844
|
+
if (!block.sourceHeading || block.sourceHeading.templateAnnotation?.template) continue;
|
|
9845
|
+
const found = detectPromotableBodyAnnotation(block.contents);
|
|
9846
|
+
if (!found) continue;
|
|
9847
|
+
const { template, params } = found.data;
|
|
9848
|
+
if (isMediaAnnotationName(template) || !hasTemplate(template)) continue;
|
|
9849
|
+
block.template = resolveTemplateName(template);
|
|
9850
|
+
if (Object.keys(params).length > 0) {
|
|
9851
|
+
block.templateOverrides = params;
|
|
9852
|
+
applyBlockMeta2(block, coerceAnnotationValues(params).blockMeta);
|
|
9853
|
+
}
|
|
9854
|
+
block.contents = found.strippedContents;
|
|
9855
|
+
block.promotedBodyAnnotation = found.data;
|
|
9856
|
+
}
|
|
9857
|
+
}
|
|
8469
9858
|
function expandStandaloneAnnotations(blocks, makeId, defaultDuration) {
|
|
8470
9859
|
const out = [];
|
|
8471
9860
|
for (const block of blocks) {
|
|
@@ -8602,6 +9991,7 @@ function markdownToDoc(markdownDoc, options) {
|
|
|
8602
9991
|
if (rootBlocks.length > 0 && !rootBlocks[0].sourceHeading && (rootBlocks[0].contents?.length ?? 0) === 0 && (rootBlocks[0].children?.length ?? 0) === 0 && !rootBlocks[0].media) {
|
|
8603
9992
|
rootBlocks.shift();
|
|
8604
9993
|
}
|
|
9994
|
+
promoteBodyAnnotations(flattenBlocks(rootBlocks));
|
|
8605
9995
|
const makeStandaloneId = (parsed) => {
|
|
8606
9996
|
const base = parsed.params.title ?? parsed.template ?? "block";
|
|
8607
9997
|
return idGenerator ? idGenerator.fromText(base) : slugify(base);
|
|
@@ -8685,6 +10075,12 @@ function markdownToDoc(markdownDoc, options) {
|
|
|
8685
10075
|
}
|
|
8686
10076
|
currentTime += block.duration;
|
|
8687
10077
|
}
|
|
10078
|
+
const blockById = new Map(allBlocks.map((block) => [block.id, block]));
|
|
10079
|
+
for (const clip of documentMedia) {
|
|
10080
|
+
if (clip.lockToBlock === false && clip.origin?.format === "html" && !/\bdata-squisq-video-start-at\s*=/i.test(clip.origin.raw ?? "")) {
|
|
10081
|
+
clip.startAt = blockById.get(clip.origin.blockId)?.startTime ?? 0;
|
|
10082
|
+
}
|
|
10083
|
+
}
|
|
8688
10084
|
const captions = phrases.length > 0 ? {
|
|
8689
10085
|
phrases,
|
|
8690
10086
|
...options?.captionsGeneratedAt ? { generatedAt: options.captionsGeneratedAt } : {},
|
|
@@ -8770,7 +10166,7 @@ function applyStructuredData(block, diagnostics) {
|
|
|
8770
10166
|
}
|
|
8771
10167
|
data = { ...data, ...result.data };
|
|
8772
10168
|
}
|
|
8773
|
-
if (block.template
|
|
10169
|
+
if (TABLE_FED_TEMPLATES.has(resolveTemplateName(block.template ?? ""))) {
|
|
8774
10170
|
const provided = (key) => data && key in data || block.templateOverrides && key in block.templateOverrides;
|
|
8775
10171
|
if (!provided("headers") && !provided("rows")) {
|
|
8776
10172
|
const table = findFirstTable(block.contents);
|
|
@@ -8855,7 +10251,7 @@ function frontmatterDisablesAutoTemplates(markdownDoc) {
|
|
|
8855
10251
|
}
|
|
8856
10252
|
function applyAutoTemplates(blocks, resolvedDefault, state) {
|
|
8857
10253
|
for (const block of blocks) {
|
|
8858
|
-
const annotated = !!block.sourceHeading?.templateAnnotation?.template;
|
|
10254
|
+
const annotated = !!block.sourceHeading?.templateAnnotation?.template || !!block.promotedBodyAnnotation;
|
|
8859
10255
|
if (block.sourceHeading && !annotated && block.template === resolvedDefault) {
|
|
8860
10256
|
const profile = profileBlockContents(block.contents ?? []);
|
|
8861
10257
|
const picked = pickAutoTemplate(profile, state.featureIndex);
|
|
@@ -9294,10 +10690,6 @@ export {
|
|
|
9294
10690
|
getPersistentLayersFromTheme,
|
|
9295
10691
|
resolvePersistentLayers,
|
|
9296
10692
|
wrapWithPersistentLayers,
|
|
9297
|
-
TEMPLATE_ALIASES,
|
|
9298
|
-
resolveTemplateName,
|
|
9299
|
-
CONTAINER_TEMPLATES,
|
|
9300
|
-
isContainerTemplate,
|
|
9301
10693
|
resolveColorScheme,
|
|
9302
10694
|
getThemeFont,
|
|
9303
10695
|
themedFontSize,
|
|
@@ -9359,6 +10751,7 @@ export {
|
|
|
9359
10751
|
videoWithCaption,
|
|
9360
10752
|
videoPullQuote,
|
|
9361
10753
|
dataTable,
|
|
10754
|
+
buildChartData,
|
|
9362
10755
|
DIAGRAM_LABEL_LINE_HEIGHT,
|
|
9363
10756
|
DIAGRAM_LABEL_HORIZONTAL_PADDING,
|
|
9364
10757
|
DIAGRAM_LABEL_VERTICAL_PADDING,
|