sketchmark 0.2.0 → 0.2.2
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/animation/index.d.ts +2 -1
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/ast/types.d.ts +0 -4
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/index.cjs +351 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +351 -136
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/renderer/canvas/index.d.ts.map +1 -1
- package/dist/renderer/canvas/roughChartCanvas.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/renderer/svg/roughChartSVG.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +351 -136
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -259,10 +259,6 @@ function propsToStyle(p) {
|
|
|
259
259
|
s.color = p.color;
|
|
260
260
|
if (p.opacity)
|
|
261
261
|
s.opacity = parseFloat(p.opacity);
|
|
262
|
-
if (p.radius)
|
|
263
|
-
s.radius = parseFloat(p.radius);
|
|
264
|
-
if (p.shadow)
|
|
265
|
-
s.shadow = p.shadow === "true";
|
|
266
262
|
if (p["font-size"])
|
|
267
263
|
s.fontSize = parseFloat(p["font-size"]);
|
|
268
264
|
if (p["font-weight"])
|
|
@@ -279,8 +275,9 @@ function propsToStyle(p) {
|
|
|
279
275
|
s.letterSpacing = parseFloat(p["letter-spacing"]);
|
|
280
276
|
if (p.font)
|
|
281
277
|
s.font = p.font;
|
|
282
|
-
|
|
283
|
-
|
|
278
|
+
const dashVal = p["dash"] || p["stroke-dash"];
|
|
279
|
+
if (dashVal) {
|
|
280
|
+
const parts = dashVal
|
|
284
281
|
.split(",")
|
|
285
282
|
.map(Number)
|
|
286
283
|
.filter((n) => !isNaN(n));
|
|
@@ -1487,13 +1484,22 @@ function sizeNode(n) {
|
|
|
1487
1484
|
n.h = n.h || 50;
|
|
1488
1485
|
break;
|
|
1489
1486
|
case "text": {
|
|
1490
|
-
// read fontSize from style if set, otherwise use default
|
|
1491
1487
|
const fontSize = Number(n.style?.fontSize ?? 13);
|
|
1492
1488
|
const charWidth = fontSize * 0.55;
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1489
|
+
const pad = Number(n.style?.padding ?? 8) * 2;
|
|
1490
|
+
if (n.width) {
|
|
1491
|
+
// User set width → word-wrap within it
|
|
1492
|
+
const approxLines = Math.ceil((n.label.length * charWidth) / (n.width - pad));
|
|
1493
|
+
n.w = n.width;
|
|
1494
|
+
n.h = n.height ?? Math.max(24, approxLines * fontSize * 1.5 + pad);
|
|
1495
|
+
}
|
|
1496
|
+
else {
|
|
1497
|
+
// Auto-size to content
|
|
1498
|
+
const lines = n.label.split("\\n");
|
|
1499
|
+
const longest = lines.reduce((a, b) => (a.length > b.length ? a : b), "");
|
|
1500
|
+
n.w = Math.max(MIN_W, Math.round(longest.length * charWidth + pad));
|
|
1501
|
+
n.h = n.height ?? Math.max(24, lines.length * fontSize * 1.5 + pad);
|
|
1502
|
+
}
|
|
1497
1503
|
break;
|
|
1498
1504
|
}
|
|
1499
1505
|
default:
|
|
@@ -1710,17 +1716,16 @@ function place(g, nm, gm, tm, ntm, cm, mdm) {
|
|
|
1710
1716
|
if (layout === "row") {
|
|
1711
1717
|
const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
|
|
1712
1718
|
const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
|
|
1713
|
-
const maxH = Math.max(...hs);
|
|
1714
1719
|
const { start, gaps } = distribute(ws, contentW, gap, justify);
|
|
1715
1720
|
let x = contentX + start;
|
|
1716
1721
|
for (let i = 0; i < kids.length; i++) {
|
|
1717
1722
|
let y;
|
|
1718
1723
|
switch (align) {
|
|
1719
1724
|
case "center":
|
|
1720
|
-
y = contentY + (
|
|
1725
|
+
y = contentY + (contentH - hs[i]) / 2;
|
|
1721
1726
|
break;
|
|
1722
1727
|
case "end":
|
|
1723
|
-
y = contentY +
|
|
1728
|
+
y = contentY + contentH - hs[i];
|
|
1724
1729
|
break;
|
|
1725
1730
|
default:
|
|
1726
1731
|
y = contentY;
|
|
@@ -1741,17 +1746,16 @@ function place(g, nm, gm, tm, ntm, cm, mdm) {
|
|
|
1741
1746
|
// column (default)
|
|
1742
1747
|
const ws = kids.map((r) => iW(r, nm, gm, tm, ntm, cm, mdm));
|
|
1743
1748
|
const hs = kids.map((r) => iH(r, nm, gm, tm, ntm, cm, mdm));
|
|
1744
|
-
const maxW = Math.max(...ws);
|
|
1745
1749
|
const { start, gaps } = distribute(hs, contentH, gap, justify);
|
|
1746
1750
|
let y = contentY + start;
|
|
1747
1751
|
for (let i = 0; i < kids.length; i++) {
|
|
1748
1752
|
let x;
|
|
1749
1753
|
switch (align) {
|
|
1750
1754
|
case "center":
|
|
1751
|
-
x = contentX + (
|
|
1755
|
+
x = contentX + (contentW - ws[i]) / 2;
|
|
1752
1756
|
break;
|
|
1753
1757
|
case "end":
|
|
1754
|
-
x = contentX +
|
|
1758
|
+
x = contentX + contentW - ws[i];
|
|
1755
1759
|
break;
|
|
1756
1760
|
default:
|
|
1757
1761
|
x = contentX;
|
|
@@ -2175,13 +2179,13 @@ function mkG(id, cls) {
|
|
|
2175
2179
|
g.setAttribute('class', cls);
|
|
2176
2180
|
return g;
|
|
2177
2181
|
}
|
|
2178
|
-
function mkT(txt, x, y, sz = 10, wt = 400, col = '#4a2e10', anchor = 'middle') {
|
|
2182
|
+
function mkT(txt, x, y, sz = 10, wt = 400, col = '#4a2e10', anchor = 'middle', font = 'system-ui, sans-serif') {
|
|
2179
2183
|
const t = se$1('text');
|
|
2180
2184
|
t.setAttribute('x', String(x));
|
|
2181
2185
|
t.setAttribute('y', String(y));
|
|
2182
2186
|
t.setAttribute('text-anchor', anchor);
|
|
2183
2187
|
t.setAttribute('dominant-baseline', 'middle');
|
|
2184
|
-
t.setAttribute('font-family',
|
|
2188
|
+
t.setAttribute('font-family', font);
|
|
2185
2189
|
t.setAttribute('font-size', String(sz));
|
|
2186
2190
|
t.setAttribute('font-weight', String(wt));
|
|
2187
2191
|
t.setAttribute('fill', col);
|
|
@@ -2197,7 +2201,7 @@ function hashStr$4(s) {
|
|
|
2197
2201
|
}
|
|
2198
2202
|
const BASE = { roughness: 1.2, bowing: 0.7 };
|
|
2199
2203
|
// ── Axes ───────────────────────────────────────────────────
|
|
2200
|
-
function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol) {
|
|
2204
|
+
function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol, font = 'system-ui, sans-serif') {
|
|
2201
2205
|
// Y axis
|
|
2202
2206
|
g.appendChild(rc.line(px, py, px, py + ph, {
|
|
2203
2207
|
roughness: 0.4, seed: hashStr$4(c.id + 'ya'), stroke: labelCol, strokeWidth: 1,
|
|
@@ -2216,7 +2220,7 @@ function drawAxes$1(rc, g, c, px, py, pw, ph, allY, labelCol) {
|
|
|
2216
2220
|
g.appendChild(rc.line(px - 3, ty, px, ty, {
|
|
2217
2221
|
roughness: 0.2, seed: hashStr$4(c.id + 'yt' + tick), stroke: labelCol, strokeWidth: 0.7,
|
|
2218
2222
|
}));
|
|
2219
|
-
g.appendChild(mkT(fmtNum$1(tick), px - 5, ty, 9, 400, labelCol, 'end'));
|
|
2223
|
+
g.appendChild(mkT(fmtNum$1(tick), px - 5, ty, 9, 400, labelCol, 'end', font));
|
|
2220
2224
|
}
|
|
2221
2225
|
}
|
|
2222
2226
|
function fmtNum$1(v) {
|
|
@@ -2225,7 +2229,7 @@ function fmtNum$1(v) {
|
|
|
2225
2229
|
return String(v);
|
|
2226
2230
|
}
|
|
2227
2231
|
// ── Legend row ─────────────────────────────────────────────
|
|
2228
|
-
function legend(g, labels, colors, x, y, labelCol) {
|
|
2232
|
+
function legend(g, labels, colors, x, y, labelCol, font = 'system-ui, sans-serif') {
|
|
2229
2233
|
labels.forEach((lbl, i) => {
|
|
2230
2234
|
const dot = se$1('rect');
|
|
2231
2235
|
dot.setAttribute('x', String(x));
|
|
@@ -2235,7 +2239,7 @@ function legend(g, labels, colors, x, y, labelCol) {
|
|
|
2235
2239
|
dot.setAttribute('fill', colors[i % colors.length]);
|
|
2236
2240
|
dot.setAttribute('rx', '1');
|
|
2237
2241
|
g.appendChild(dot);
|
|
2238
|
-
g.appendChild(mkT(lbl, x + 12, y + i * 14 + 4, 9, 400, labelCol, 'start'));
|
|
2242
|
+
g.appendChild(mkT(lbl, x + 12, y + i * 14 + 4, 9, 400, labelCol, 'start', font));
|
|
2239
2243
|
});
|
|
2240
2244
|
}
|
|
2241
2245
|
// ── Public entry ───────────────────────────────────────────
|
|
@@ -2246,6 +2250,11 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2246
2250
|
const bgFill = String(s.fill ?? palette.nodeFill);
|
|
2247
2251
|
const bgStroke = String(s.stroke ?? (isDark ? '#5a4a30' : '#c8b898'));
|
|
2248
2252
|
const lc = String(s.color ?? palette.titleText);
|
|
2253
|
+
const cFont = String(s.font ? `${s.font}, system-ui, sans-serif` : 'system-ui, sans-serif');
|
|
2254
|
+
const cFontSize = Number(s.fontSize ?? 12);
|
|
2255
|
+
const cFontWeight = s.fontWeight ?? 600;
|
|
2256
|
+
if (s.opacity != null)
|
|
2257
|
+
cg.setAttribute('opacity', String(s.opacity));
|
|
2249
2258
|
// Background box
|
|
2250
2259
|
cg.appendChild(rc.rectangle(c.x, c.y, c.w, c.h, {
|
|
2251
2260
|
...BASE, seed: hashStr$4(c.id),
|
|
@@ -2255,7 +2264,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2255
2264
|
}));
|
|
2256
2265
|
// Title
|
|
2257
2266
|
if (c.title) {
|
|
2258
|
-
cg.appendChild(mkT(c.title, c.x + c.w / 2, c.y + 14,
|
|
2267
|
+
cg.appendChild(mkT(c.title, c.x + c.w / 2, c.y + 14, cFontSize, cFontWeight, lc, 'middle', cFont));
|
|
2259
2268
|
}
|
|
2260
2269
|
const { px, py, pw, ph, cx, cy } = chartLayout(c);
|
|
2261
2270
|
// ── Pie / Donut ──────────────────────────────────────────
|
|
@@ -2281,7 +2290,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2281
2290
|
angle += sweep;
|
|
2282
2291
|
}
|
|
2283
2292
|
// Mini legend on left
|
|
2284
|
-
legend(cg, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc);
|
|
2293
|
+
legend(cg, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc, cFont);
|
|
2285
2294
|
return cg;
|
|
2286
2295
|
}
|
|
2287
2296
|
// ── Scatter ───────────────────────────────────────────────
|
|
@@ -2302,7 +2311,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2302
2311
|
strokeWidth: 1.2,
|
|
2303
2312
|
}));
|
|
2304
2313
|
});
|
|
2305
|
-
legend(cg, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc);
|
|
2314
|
+
legend(cg, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc, cFont);
|
|
2306
2315
|
return cg;
|
|
2307
2316
|
}
|
|
2308
2317
|
// ── Bar / Line / Area ─────────────────────────────────────
|
|
@@ -2311,10 +2320,10 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2311
2320
|
const toY = makeValueToY(allY, py, ph);
|
|
2312
2321
|
const baseline = toY(0);
|
|
2313
2322
|
const n = labels.length;
|
|
2314
|
-
drawAxes$1(rc, cg, c, px, py, pw, ph, allY, lc);
|
|
2323
|
+
drawAxes$1(rc, cg, c, px, py, pw, ph, allY, lc, cFont);
|
|
2315
2324
|
// X labels
|
|
2316
2325
|
labels.forEach((lbl, i) => {
|
|
2317
|
-
cg.appendChild(mkT(lbl, px + (i + 0.5) * (pw / n), py + ph + 14, 9, 400, lc));
|
|
2326
|
+
cg.appendChild(mkT(lbl, px + (i + 0.5) * (pw / n), py + ph + 14, 9, 400, lc, 'middle', cFont));
|
|
2318
2327
|
});
|
|
2319
2328
|
if (c.chartType === 'bar') {
|
|
2320
2329
|
const groupW = pw / n;
|
|
@@ -2385,7 +2394,7 @@ function renderRoughChartSVG(rc, c, palette, isDark) {
|
|
|
2385
2394
|
}
|
|
2386
2395
|
// Multi-series legend
|
|
2387
2396
|
if (series.length > 1) {
|
|
2388
|
-
legend(cg, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc);
|
|
2397
|
+
legend(cg, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc, cFont);
|
|
2389
2398
|
}
|
|
2390
2399
|
return cg;
|
|
2391
2400
|
}
|
|
@@ -4851,6 +4860,14 @@ function hashStr$3(s) {
|
|
|
4851
4860
|
return h;
|
|
4852
4861
|
}
|
|
4853
4862
|
const BASE_ROUGH = { roughness: 1.3, bowing: 0.7 };
|
|
4863
|
+
/** Darken a CSS hex colour by `amount` (0–1). Falls back to input for non-hex. */
|
|
4864
|
+
function darkenHex$1(hex, amount = 0.12) {
|
|
4865
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
4866
|
+
if (!m)
|
|
4867
|
+
return hex;
|
|
4868
|
+
const d = (v) => Math.max(0, Math.round(parseInt(v, 16) * (1 - amount)));
|
|
4869
|
+
return `#${d(m[1]).toString(16).padStart(2, "0")}${d(m[2]).toString(16).padStart(2, "0")}${d(m[3]).toString(16).padStart(2, "0")}`;
|
|
4870
|
+
}
|
|
4854
4871
|
// ── Small helper: load + resolve font from style or fall back ─────────────
|
|
4855
4872
|
function resolveStyleFont$1(style, fallback) {
|
|
4856
4873
|
const raw = String(style["font"] ?? "");
|
|
@@ -5021,6 +5038,7 @@ function renderShape$1(rc, n, palette) {
|
|
|
5021
5038
|
fillStyle: "solid",
|
|
5022
5039
|
stroke,
|
|
5023
5040
|
strokeWidth: Number(s.strokeWidth ?? 1.9),
|
|
5041
|
+
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
5024
5042
|
};
|
|
5025
5043
|
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
5026
5044
|
const hw = n.w / 2 - 2;
|
|
@@ -5203,6 +5221,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5203
5221
|
continue;
|
|
5204
5222
|
const gs = g.style ?? {};
|
|
5205
5223
|
const gg = mkGroup(`group-${g.id}`, "gg");
|
|
5224
|
+
if (gs.opacity != null)
|
|
5225
|
+
gg.setAttribute("opacity", String(gs.opacity));
|
|
5206
5226
|
gg.appendChild(rc.rectangle(g.x, g.y, g.w, g.h, {
|
|
5207
5227
|
...BASE_ROUGH,
|
|
5208
5228
|
roughness: 1.7,
|
|
@@ -5215,14 +5235,26 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5215
5235
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
5216
5236
|
}));
|
|
5217
5237
|
// ── Group label typography ──────────────────────────
|
|
5218
|
-
// supports: font, font-size, letter-spacing
|
|
5219
|
-
// always left-anchored (single line)
|
|
5220
5238
|
const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
|
|
5221
5239
|
const gFontSize = Number(gs.fontSize ?? 12);
|
|
5240
|
+
const gFontWeight = gs.fontWeight ?? 500;
|
|
5222
5241
|
const gFont = resolveStyleFont$1(gs, diagramFont);
|
|
5223
5242
|
const gLetterSpacing = gs.letterSpacing;
|
|
5243
|
+
const gPad = Number(gs.padding ?? 14);
|
|
5244
|
+
const gTextAlign = String(gs.textAlign ?? "left");
|
|
5245
|
+
const gAnchorMap = {
|
|
5246
|
+
left: "start",
|
|
5247
|
+
center: "middle",
|
|
5248
|
+
right: "end",
|
|
5249
|
+
};
|
|
5250
|
+
const gAnchor = gAnchorMap[gTextAlign] ?? "start";
|
|
5251
|
+
const gTextX = gTextAlign === "right"
|
|
5252
|
+
? g.x + g.w - gPad
|
|
5253
|
+
: gTextAlign === "center"
|
|
5254
|
+
? g.x + g.w / 2
|
|
5255
|
+
: g.x + gPad;
|
|
5224
5256
|
if (g.label) {
|
|
5225
|
-
gg.appendChild(mkText(g.label,
|
|
5257
|
+
gg.appendChild(mkText(g.label, gTextX, g.y + gPad, gFontSize, gFontWeight, gLabelColor, gAnchor, gFont, gLetterSpacing));
|
|
5226
5258
|
}
|
|
5227
5259
|
GL.appendChild(gg);
|
|
5228
5260
|
}
|
|
@@ -5243,6 +5275,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5243
5275
|
const [x1, y1] = getConnPoint$1(src, dstCX, dstCY);
|
|
5244
5276
|
const [x2, y2] = getConnPoint$1(dst, srcCX, srcCY);
|
|
5245
5277
|
const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
|
|
5278
|
+
if (e.style?.opacity != null)
|
|
5279
|
+
eg.setAttribute("opacity", String(e.style.opacity));
|
|
5246
5280
|
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
5247
5281
|
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
5248
5282
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
@@ -5283,7 +5317,9 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5283
5317
|
const eFontSize = Number(e.style?.fontSize ?? 11);
|
|
5284
5318
|
const eFont = resolveStyleFont$1(e.style ?? {}, diagramFont);
|
|
5285
5319
|
const eLetterSpacing = e.style?.letterSpacing;
|
|
5286
|
-
|
|
5320
|
+
const eFontWeight = e.style?.fontWeight ?? 400;
|
|
5321
|
+
const eLabelColor = String(e.style?.color ?? palette.edgeLabelText);
|
|
5322
|
+
eg.appendChild(mkText(e.label, mx, my, eFontSize, eFontWeight, eLabelColor, "middle", eFont, eLetterSpacing));
|
|
5287
5323
|
}
|
|
5288
5324
|
EL.appendChild(eg);
|
|
5289
5325
|
}
|
|
@@ -5292,6 +5328,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5292
5328
|
const NL = mkGroup("node-layer");
|
|
5293
5329
|
for (const n of sg.nodes) {
|
|
5294
5330
|
const ng = mkGroup(`node-${n.id}`, "ng");
|
|
5331
|
+
if (n.style?.opacity != null)
|
|
5332
|
+
ng.setAttribute("opacity", String(n.style.opacity));
|
|
5295
5333
|
renderShape$1(rc, n, palette).forEach((s) => ng.appendChild(s));
|
|
5296
5334
|
// ── Node / text typography ─────────────────────────
|
|
5297
5335
|
// supports: font, font-size, letter-spacing, text-align, line-height
|
|
@@ -5310,18 +5348,19 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5310
5348
|
// line-height is a multiplier (e.g. 1.4 = 140% of font-size)
|
|
5311
5349
|
const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
|
|
5312
5350
|
const letterSpacing = n.style?.letterSpacing;
|
|
5351
|
+
const pad = Number(n.style?.padding ?? 8);
|
|
5313
5352
|
// x shifts for left / right alignment
|
|
5314
5353
|
const textX = textAlign === "left"
|
|
5315
|
-
? n.x +
|
|
5354
|
+
? n.x + pad
|
|
5316
5355
|
: textAlign === "right"
|
|
5317
|
-
? n.x + n.w -
|
|
5356
|
+
? n.x + n.w - pad
|
|
5318
5357
|
: n.x + n.w / 2;
|
|
5319
5358
|
const lines = n.shape === 'text' && !n.label.includes('\n')
|
|
5320
|
-
? wrapText$1(n.label, n.w -
|
|
5359
|
+
? wrapText$1(n.label, n.w - pad * 2, fontSize)
|
|
5321
5360
|
: n.label.split('\n');
|
|
5322
5361
|
const verticalAlign = String(n.style?.verticalAlign ?? "middle");
|
|
5323
|
-
const nodeBodyTop = n.y +
|
|
5324
|
-
const nodeBodyBottom = n.y + n.h -
|
|
5362
|
+
const nodeBodyTop = n.y + pad;
|
|
5363
|
+
const nodeBodyBottom = n.y + n.h - pad;
|
|
5325
5364
|
const nodeBodyMid = n.y + n.h / 2;
|
|
5326
5365
|
const blockH = (lines.length - 1) * lineHeight;
|
|
5327
5366
|
const textCY = verticalAlign === "top"
|
|
@@ -5353,15 +5392,19 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5353
5392
|
const fill = String(gs.fill ?? palette.tableFill);
|
|
5354
5393
|
const strk = String(gs.stroke ?? palette.tableStroke);
|
|
5355
5394
|
const textCol = String(gs.color ?? palette.tableText);
|
|
5356
|
-
const hdrFill = palette.tableHeaderFill;
|
|
5395
|
+
const hdrFill = gs.fill ? darkenHex$1(fill, 0.08) : palette.tableHeaderFill;
|
|
5357
5396
|
const hdrText = String(gs.color ?? palette.tableHeaderText);
|
|
5358
5397
|
const divCol = palette.tableDivider;
|
|
5359
5398
|
const pad = t.labelH;
|
|
5399
|
+
const tStrokeWidth = Number(gs.strokeWidth ?? 1.5);
|
|
5400
|
+
const tFontWeight = gs.fontWeight ?? 500;
|
|
5360
5401
|
// ── Table-level font (applies to label + all cells) ─
|
|
5361
5402
|
// supports: font, font-size, letter-spacing
|
|
5362
5403
|
const tFontSize = Number(gs.fontSize ?? 12);
|
|
5363
5404
|
const tFont = resolveStyleFont$1(gs, diagramFont);
|
|
5364
5405
|
const tLetterSpacing = gs.letterSpacing;
|
|
5406
|
+
if (gs.opacity != null)
|
|
5407
|
+
tg.setAttribute("opacity", String(gs.opacity));
|
|
5365
5408
|
// outer border
|
|
5366
5409
|
tg.appendChild(rc.rectangle(t.x, t.y, t.w, t.h, {
|
|
5367
5410
|
...BASE_ROUGH,
|
|
@@ -5369,7 +5412,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5369
5412
|
fill,
|
|
5370
5413
|
fillStyle: "solid",
|
|
5371
5414
|
stroke: strk,
|
|
5372
|
-
strokeWidth:
|
|
5415
|
+
strokeWidth: tStrokeWidth,
|
|
5416
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
5373
5417
|
}));
|
|
5374
5418
|
// label strip separator
|
|
5375
5419
|
tg.appendChild(rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
|
|
@@ -5378,8 +5422,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5378
5422
|
stroke: strk,
|
|
5379
5423
|
strokeWidth: 1,
|
|
5380
5424
|
}));
|
|
5381
|
-
// ── Table label: font, font-size, letter-spacing (always left) ──
|
|
5382
|
-
tg.appendChild(mkText(t.label, t.x + 10, t.y + pad / 2, tFontSize,
|
|
5425
|
+
// ── Table label: font, font-size, font-weight, letter-spacing (always left) ──
|
|
5426
|
+
tg.appendChild(mkText(t.label, t.x + 10, t.y + pad / 2, tFontSize, tFontWeight, textCol, "start", tFont, tLetterSpacing));
|
|
5383
5427
|
// rows
|
|
5384
5428
|
let rowY = t.y + pad;
|
|
5385
5429
|
for (const row of t.rows) {
|
|
@@ -5408,7 +5452,7 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5408
5452
|
right: "end",
|
|
5409
5453
|
};
|
|
5410
5454
|
const cellAnchor = cellAnchorMap[cellAlignProp] ?? "middle";
|
|
5411
|
-
const cellFw = row.kind === "header" ? 600 : 400;
|
|
5455
|
+
const cellFw = row.kind === "header" ? 600 : (gs.fontWeight ?? 400);
|
|
5412
5456
|
const cellColor = row.kind === "header" ? hdrText : textCol;
|
|
5413
5457
|
let cx = t.x;
|
|
5414
5458
|
row.cells.forEach((cell, i) => {
|
|
@@ -5447,27 +5491,31 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5447
5491
|
const gs = n.style ?? {};
|
|
5448
5492
|
const fill = String(gs.fill ?? palette.noteFill);
|
|
5449
5493
|
const strk = String(gs.stroke ?? palette.noteStroke);
|
|
5494
|
+
const nStrokeWidth = Number(gs.strokeWidth ?? 1.2);
|
|
5450
5495
|
const fold = 14;
|
|
5451
5496
|
const { x, y, w, h } = n;
|
|
5497
|
+
if (gs.opacity != null)
|
|
5498
|
+
ng.setAttribute("opacity", String(gs.opacity));
|
|
5452
5499
|
// ── Note typography ─────────────────────────────────
|
|
5453
|
-
// supports: font, font-size, letter-spacing, text-align, line-height
|
|
5454
5500
|
const nFontSize = Number(gs.fontSize ?? 12);
|
|
5501
|
+
const nFontWeight = gs.fontWeight ?? 400;
|
|
5455
5502
|
const nFont = resolveStyleFont$1(gs, diagramFont);
|
|
5456
5503
|
const nLetterSpacing = gs.letterSpacing;
|
|
5457
5504
|
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
5458
5505
|
const nTextAlign = String(gs.textAlign ?? "left");
|
|
5506
|
+
const nPad = Number(gs.padding ?? 12);
|
|
5459
5507
|
const nAnchorMap = {
|
|
5460
5508
|
left: "start",
|
|
5461
5509
|
center: "middle",
|
|
5462
5510
|
right: "end",
|
|
5463
5511
|
};
|
|
5464
5512
|
const nAnchor = nAnchorMap[nTextAlign] ?? "start";
|
|
5465
|
-
// x position for the text block (pad from left, with alignment)
|
|
5466
5513
|
const nTextX = nTextAlign === "right"
|
|
5467
|
-
? x + w - fold -
|
|
5514
|
+
? x + w - fold - nPad
|
|
5468
5515
|
: nTextAlign === "center"
|
|
5469
5516
|
? x + (w - fold) / 2
|
|
5470
|
-
: x +
|
|
5517
|
+
: x + nPad;
|
|
5518
|
+
const nFoldPad = fold + nPad; // text starts below fold + user padding
|
|
5471
5519
|
ng.appendChild(rc.polygon([
|
|
5472
5520
|
[x, y],
|
|
5473
5521
|
[x + w - fold, y],
|
|
@@ -5480,7 +5528,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5480
5528
|
fill,
|
|
5481
5529
|
fillStyle: "solid",
|
|
5482
5530
|
stroke: strk,
|
|
5483
|
-
strokeWidth:
|
|
5531
|
+
strokeWidth: nStrokeWidth,
|
|
5532
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
5484
5533
|
}));
|
|
5485
5534
|
ng.appendChild(rc.polygon([
|
|
5486
5535
|
[x + w - fold, y],
|
|
@@ -5492,11 +5541,11 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5492
5541
|
fill: palette.noteFold,
|
|
5493
5542
|
fillStyle: "solid",
|
|
5494
5543
|
stroke: strk,
|
|
5495
|
-
strokeWidth: 0.8,
|
|
5544
|
+
strokeWidth: Math.min(nStrokeWidth, 0.8),
|
|
5496
5545
|
}));
|
|
5497
5546
|
const nVerticalAlign = String(gs.verticalAlign ?? "top");
|
|
5498
|
-
const bodyTop = y +
|
|
5499
|
-
const bodyBottom = y + h -
|
|
5547
|
+
const bodyTop = y + nFoldPad;
|
|
5548
|
+
const bodyBottom = y + h - nPad;
|
|
5500
5549
|
const bodyMid = (bodyTop + bodyBottom) / 2;
|
|
5501
5550
|
const blockH = (n.lines.length - 1) * nLineHeight;
|
|
5502
5551
|
const blockCY = nVerticalAlign === "bottom"
|
|
@@ -5504,13 +5553,11 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5504
5553
|
: nVerticalAlign === "middle"
|
|
5505
5554
|
? bodyMid
|
|
5506
5555
|
: bodyTop + blockH / 2;
|
|
5507
|
-
// multiline: use mkMultilineText so line-height is respected
|
|
5508
5556
|
if (n.lines.length > 1) {
|
|
5509
|
-
|
|
5510
|
-
ng.appendChild(mkMultilineText(n.lines, nTextX, blockCY, nFontSize, 400, String(gs.color ?? palette.noteText), nAnchor, nLineHeight, nFont, nLetterSpacing));
|
|
5557
|
+
ng.appendChild(mkMultilineText(n.lines, nTextX, blockCY, nFontSize, nFontWeight, String(gs.color ?? palette.noteText), nAnchor, nLineHeight, nFont, nLetterSpacing));
|
|
5511
5558
|
}
|
|
5512
5559
|
else {
|
|
5513
|
-
ng.appendChild(mkText(n.lines[0] ?? "", nTextX, blockCY, nFontSize,
|
|
5560
|
+
ng.appendChild(mkText(n.lines[0] ?? "", nTextX, blockCY, nFontSize, nFontWeight, String(gs.color ?? palette.noteText), nAnchor, nFont, nLetterSpacing));
|
|
5514
5561
|
}
|
|
5515
5562
|
NoteL.appendChild(ng);
|
|
5516
5563
|
}
|
|
@@ -5519,13 +5566,27 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5519
5566
|
const MDL = mkGroup('markdown-layer');
|
|
5520
5567
|
for (const m of sg.markdowns) {
|
|
5521
5568
|
const mg = mkGroup(`markdown-${m.id}`, 'mdg');
|
|
5522
|
-
const
|
|
5523
|
-
const
|
|
5524
|
-
const
|
|
5569
|
+
const gs = m.style ?? {};
|
|
5570
|
+
const mFont = resolveStyleFont$1(gs, diagramFont);
|
|
5571
|
+
const baseColor = String(gs.color ?? palette.nodeText);
|
|
5572
|
+
const textAlign = String(gs.textAlign ?? 'left');
|
|
5525
5573
|
const anchor = textAlign === 'right' ? 'end'
|
|
5526
5574
|
: textAlign === 'center' ? 'middle'
|
|
5527
5575
|
: 'start';
|
|
5528
|
-
const PAD = Number(
|
|
5576
|
+
const PAD = Number(gs.padding ?? 16);
|
|
5577
|
+
const mLetterSpacing = gs.letterSpacing;
|
|
5578
|
+
if (gs.opacity != null)
|
|
5579
|
+
mg.setAttribute('opacity', String(gs.opacity));
|
|
5580
|
+
// Background + border
|
|
5581
|
+
if (gs.fill || gs.stroke) {
|
|
5582
|
+
mg.appendChild(rc.rectangle(m.x, m.y, m.w, m.h, {
|
|
5583
|
+
...BASE_ROUGH, seed: hashStr$3(m.id),
|
|
5584
|
+
fill: String(gs.fill ?? 'none'), fillStyle: 'solid',
|
|
5585
|
+
stroke: String(gs.stroke ?? 'none'),
|
|
5586
|
+
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
5587
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
5588
|
+
}));
|
|
5589
|
+
}
|
|
5529
5590
|
const textX = textAlign === 'right' ? m.x + m.w - PAD
|
|
5530
5591
|
: textAlign === 'center' ? m.x + m.w / 2
|
|
5531
5592
|
: m.x + PAD;
|
|
@@ -5548,6 +5609,8 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
5548
5609
|
t.setAttribute('fill', baseColor);
|
|
5549
5610
|
t.setAttribute('pointer-events', 'none');
|
|
5550
5611
|
t.setAttribute('user-select', 'none');
|
|
5612
|
+
if (mLetterSpacing != null)
|
|
5613
|
+
t.setAttribute('letter-spacing', String(mLetterSpacing));
|
|
5551
5614
|
for (const run of line.runs) {
|
|
5552
5615
|
const span = se('tspan');
|
|
5553
5616
|
span.textContent = run.text;
|
|
@@ -5637,7 +5700,7 @@ function drawPieArc(rc, ctx, cx, cy, r, ir, startAngle, endAngle, color, seed) {
|
|
|
5637
5700
|
});
|
|
5638
5701
|
}
|
|
5639
5702
|
// ── Axes ───────────────────────────────────────────────────
|
|
5640
|
-
function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R) {
|
|
5703
|
+
function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R, font = 'system-ui, sans-serif') {
|
|
5641
5704
|
const toY = makeValueToY(allY, py, ph);
|
|
5642
5705
|
const baseline = toY(0);
|
|
5643
5706
|
// Y axis
|
|
@@ -5651,7 +5714,7 @@ function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R) {
|
|
|
5651
5714
|
continue;
|
|
5652
5715
|
rc.line(px - 3, ty, px, ty, { roughness: 0.2, seed: hashStr$2(c.id + 'yt' + tick), stroke: labelCol, strokeWidth: 0.7 });
|
|
5653
5716
|
ctx.save();
|
|
5654
|
-
ctx.font =
|
|
5717
|
+
ctx.font = `400 9px ${font}`;
|
|
5655
5718
|
ctx.fillStyle = labelCol;
|
|
5656
5719
|
ctx.textAlign = 'right';
|
|
5657
5720
|
ctx.textBaseline = 'middle';
|
|
@@ -5660,9 +5723,9 @@ function drawAxes(rc, ctx, c, px, py, pw, ph, allY, labelCol, R) {
|
|
|
5660
5723
|
}
|
|
5661
5724
|
}
|
|
5662
5725
|
// ── Legend ─────────────────────────────────────────────────
|
|
5663
|
-
function drawLegend(ctx, labels, colors, x, y, labelCol) {
|
|
5726
|
+
function drawLegend(ctx, labels, colors, x, y, labelCol, font = 'system-ui, sans-serif') {
|
|
5664
5727
|
ctx.save();
|
|
5665
|
-
ctx.font =
|
|
5728
|
+
ctx.font = `400 9px ${font}`;
|
|
5666
5729
|
ctx.textAlign = 'left';
|
|
5667
5730
|
ctx.textBaseline = 'middle';
|
|
5668
5731
|
labels.forEach((lbl, i) => {
|
|
@@ -5680,6 +5743,11 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5680
5743
|
const bgFill = String(s.fill ?? pal.nodeFill);
|
|
5681
5744
|
const bgStroke = String(s.stroke ?? (pal.nodeStroke === 'none' ? '#c8b898' : pal.nodeStroke));
|
|
5682
5745
|
const lc = String(s.color ?? pal.labelText);
|
|
5746
|
+
const cFont = String(s.font ? `${s.font}, system-ui, sans-serif` : 'system-ui, sans-serif');
|
|
5747
|
+
const cFontSize = Number(s.fontSize ?? 12);
|
|
5748
|
+
const cFontWeight = s.fontWeight ?? 600;
|
|
5749
|
+
if (s.opacity != null)
|
|
5750
|
+
ctx.globalAlpha = Number(s.opacity);
|
|
5683
5751
|
// Background
|
|
5684
5752
|
rc.rectangle(c.x, c.y, c.w, c.h, {
|
|
5685
5753
|
...R, seed: hashStr$2(c.id),
|
|
@@ -5692,7 +5760,7 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5692
5760
|
// Title
|
|
5693
5761
|
if (c.title) {
|
|
5694
5762
|
ctx.save();
|
|
5695
|
-
ctx.font =
|
|
5763
|
+
ctx.font = `${cFontWeight} ${cFontSize}px ${cFont}`;
|
|
5696
5764
|
ctx.fillStyle = lc;
|
|
5697
5765
|
ctx.textAlign = 'center';
|
|
5698
5766
|
ctx.textBaseline = 'middle';
|
|
@@ -5713,7 +5781,8 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5713
5781
|
drawPieArc(rc, ctx, cx, cy, r, ir, angle, angle + sweep, seg.color, hashStr$2(c.id + seg.label + i));
|
|
5714
5782
|
angle += sweep;
|
|
5715
5783
|
});
|
|
5716
|
-
drawLegend(ctx, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc);
|
|
5784
|
+
drawLegend(ctx, segments.map(s => `${s.label} ${Math.round(s.value / total * 100)}%`), segments.map(s => s.color), legendX, legendY, lc, cFont);
|
|
5785
|
+
ctx.globalAlpha = 1;
|
|
5717
5786
|
return;
|
|
5718
5787
|
}
|
|
5719
5788
|
// ── Scatter ───────────────────────────────────────────────
|
|
@@ -5733,7 +5802,8 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5733
5802
|
strokeWidth: 1.2,
|
|
5734
5803
|
});
|
|
5735
5804
|
});
|
|
5736
|
-
drawLegend(ctx, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc);
|
|
5805
|
+
drawLegend(ctx, pts.map(p => p.label), CHART_COLORS, c.x + 8, c.y + (c.title ? 28 : 12), lc, cFont);
|
|
5806
|
+
ctx.globalAlpha = 1;
|
|
5737
5807
|
return;
|
|
5738
5808
|
}
|
|
5739
5809
|
// ── Bar / Line / Area ─────────────────────────────────────
|
|
@@ -5742,10 +5812,10 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5742
5812
|
const toY = makeValueToY(allY, py, ph);
|
|
5743
5813
|
const baseline = toY(0);
|
|
5744
5814
|
const n = labels.length;
|
|
5745
|
-
drawAxes(rc, ctx, c, px, py, pw, ph, allY, lc, R);
|
|
5815
|
+
drawAxes(rc, ctx, c, px, py, pw, ph, allY, lc, R, cFont);
|
|
5746
5816
|
// X labels
|
|
5747
5817
|
ctx.save();
|
|
5748
|
-
ctx.font =
|
|
5818
|
+
ctx.font = `400 9px ${cFont}`;
|
|
5749
5819
|
ctx.fillStyle = lc;
|
|
5750
5820
|
ctx.textAlign = 'center';
|
|
5751
5821
|
ctx.textBaseline = 'top';
|
|
@@ -5822,8 +5892,9 @@ function drawRoughChartCanvas(rc, ctx, c, pal, R) {
|
|
|
5822
5892
|
}
|
|
5823
5893
|
// Multi-series legend
|
|
5824
5894
|
if (series.length > 1) {
|
|
5825
|
-
drawLegend(ctx, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc);
|
|
5895
|
+
drawLegend(ctx, series.map(s => s.name), series.map(s => s.color), px, py - 2, lc, cFont);
|
|
5826
5896
|
}
|
|
5897
|
+
ctx.globalAlpha = 1;
|
|
5827
5898
|
}
|
|
5828
5899
|
|
|
5829
5900
|
// ============================================================
|
|
@@ -5836,6 +5907,14 @@ function hashStr$1(s) {
|
|
|
5836
5907
|
h = ((h * 33) ^ s.charCodeAt(i)) & 0xffff;
|
|
5837
5908
|
return h;
|
|
5838
5909
|
}
|
|
5910
|
+
/** Darken a CSS hex colour by `amount` (0–1). Falls back to input for non-hex. */
|
|
5911
|
+
function darkenHex(hex, amount = 0.12) {
|
|
5912
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
|
|
5913
|
+
if (!m)
|
|
5914
|
+
return hex;
|
|
5915
|
+
const d = (v) => Math.max(0, Math.round(parseInt(v, 16) * (1 - amount)));
|
|
5916
|
+
return `#${d(m[1]).toString(16).padStart(2, "0")}${d(m[2]).toString(16).padStart(2, "0")}${d(m[3]).toString(16).padStart(2, "0")}`;
|
|
5917
|
+
}
|
|
5839
5918
|
// ── Small helper: load + resolve font from a style map ────────────────────
|
|
5840
5919
|
function resolveStyleFont(style, fallback) {
|
|
5841
5920
|
const raw = String(style['font'] ?? '');
|
|
@@ -5953,6 +6032,7 @@ function renderShape(rc, ctx, n, palette, R) {
|
|
|
5953
6032
|
...R, seed: hashStr$1(n.id),
|
|
5954
6033
|
fill, fillStyle: 'solid',
|
|
5955
6034
|
stroke, strokeWidth: Number(s.strokeWidth ?? 1.9),
|
|
6035
|
+
...(s.strokeDash ? { strokeLineDash: s.strokeDash } : {}),
|
|
5956
6036
|
};
|
|
5957
6037
|
const cx = n.x + n.w / 2, cy = n.y + n.h / 2;
|
|
5958
6038
|
const hw = n.w / 2 - 2;
|
|
@@ -6088,6 +6168,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6088
6168
|
if (!g.w)
|
|
6089
6169
|
continue;
|
|
6090
6170
|
const gs = g.style ?? {};
|
|
6171
|
+
if (gs.opacity != null)
|
|
6172
|
+
ctx.globalAlpha = Number(gs.opacity);
|
|
6091
6173
|
rc.rectangle(g.x, g.y, g.w, g.h, {
|
|
6092
6174
|
...R, roughness: 1.7, bowing: 0.4, seed: hashStr$1(g.id),
|
|
6093
6175
|
fill: String(gs.fill ?? palette.groupFill),
|
|
@@ -6096,16 +6178,21 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6096
6178
|
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
6097
6179
|
strokeLineDash: gs.strokeDash ?? palette.groupDash,
|
|
6098
6180
|
});
|
|
6099
|
-
// ── Group label ──────────────────────────────────────
|
|
6100
|
-
// Only render when label has content — empty label = no reserved space
|
|
6101
|
-
// supports: font, font-size, letter-spacing (always left-anchored)
|
|
6102
6181
|
if (g.label) {
|
|
6103
6182
|
const gFontSize = Number(gs.fontSize ?? 12);
|
|
6183
|
+
const gFontWeight = gs.fontWeight ?? 500;
|
|
6104
6184
|
const gFont = resolveStyleFont(gs, diagramFont);
|
|
6105
6185
|
const gLetterSpacing = gs.letterSpacing;
|
|
6106
6186
|
const gLabelColor = gs.color ? String(gs.color) : palette.groupLabel;
|
|
6107
|
-
|
|
6187
|
+
const gPad = Number(gs.padding ?? 14);
|
|
6188
|
+
const gTextAlign = String(gs.textAlign ?? 'left');
|
|
6189
|
+
const gTextX = gTextAlign === 'right' ? g.x + g.w - gPad
|
|
6190
|
+
: gTextAlign === 'center' ? g.x + g.w / 2
|
|
6191
|
+
: g.x + gPad;
|
|
6192
|
+
drawText(ctx, g.label, gTextX, g.y + gPad + 2, gFontSize, gFontWeight, gLabelColor, gTextAlign, gFont, gLetterSpacing);
|
|
6108
6193
|
}
|
|
6194
|
+
if (gs.opacity != null)
|
|
6195
|
+
ctx.globalAlpha = 1;
|
|
6109
6196
|
}
|
|
6110
6197
|
// ── Edges ─────────────────────────────────────────────────
|
|
6111
6198
|
for (const e of sg.edges) {
|
|
@@ -6117,6 +6204,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6117
6204
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
6118
6205
|
const [x1, y1] = getConnPoint(src, dstCX, dstCY);
|
|
6119
6206
|
const [x2, y2] = getConnPoint(dst, srcCX, srcCY);
|
|
6207
|
+
if (e.style?.opacity != null)
|
|
6208
|
+
ctx.globalAlpha = Number(e.style.opacity);
|
|
6120
6209
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
6121
6210
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
6122
6211
|
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
@@ -6145,17 +6234,22 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6145
6234
|
const eFontSize = Number(e.style?.fontSize ?? 11);
|
|
6146
6235
|
const eFont = resolveStyleFont(e.style ?? {}, diagramFont);
|
|
6147
6236
|
const eLetterSpacing = e.style?.letterSpacing;
|
|
6237
|
+
const eFontWeight = e.style?.fontWeight ?? 400;
|
|
6238
|
+
const eLabelColor = String(e.style?.color ?? palette.edgeLabelText);
|
|
6148
6239
|
ctx.save();
|
|
6149
|
-
ctx.font =
|
|
6240
|
+
ctx.font = `${eFontWeight} ${eFontSize}px ${eFont}`;
|
|
6150
6241
|
const tw = ctx.measureText(e.label).width + 12;
|
|
6151
6242
|
ctx.restore();
|
|
6152
6243
|
ctx.fillStyle = palette.edgeLabelBg;
|
|
6153
6244
|
ctx.fillRect(mx - tw / 2, my - 8, tw, 15);
|
|
6154
|
-
drawText(ctx, e.label, mx, my + 3, eFontSize,
|
|
6245
|
+
drawText(ctx, e.label, mx, my + 3, eFontSize, eFontWeight, eLabelColor, 'center', eFont, eLetterSpacing);
|
|
6155
6246
|
}
|
|
6247
|
+
ctx.globalAlpha = 1;
|
|
6156
6248
|
}
|
|
6157
6249
|
// ── Nodes ─────────────────────────────────────────────────
|
|
6158
6250
|
for (const n of sg.nodes) {
|
|
6251
|
+
if (n.style?.opacity != null)
|
|
6252
|
+
ctx.globalAlpha = Number(n.style.opacity);
|
|
6159
6253
|
renderShape(rc, ctx, n, palette, R);
|
|
6160
6254
|
// ── Node / text typography ─────────────────────────
|
|
6161
6255
|
// supports: font, font-size, letter-spacing, text-align,
|
|
@@ -6169,18 +6263,19 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6169
6263
|
const lineHeight = Number(n.style?.lineHeight ?? 1.3) * fontSize;
|
|
6170
6264
|
const letterSpacing = n.style?.letterSpacing;
|
|
6171
6265
|
const vertAlign = String(n.style?.verticalAlign ?? 'middle');
|
|
6266
|
+
const pad = Number(n.style?.padding ?? 8);
|
|
6172
6267
|
// x shifts for left/right alignment
|
|
6173
|
-
const textX = textAlign === 'left' ? n.x +
|
|
6174
|
-
: textAlign === 'right' ? n.x + n.w -
|
|
6268
|
+
const textX = textAlign === 'left' ? n.x + pad
|
|
6269
|
+
: textAlign === 'right' ? n.x + n.w - pad
|
|
6175
6270
|
: n.x + n.w / 2;
|
|
6176
6271
|
// word-wrap for text shape; explicit \n for all others
|
|
6177
6272
|
const rawLines = n.label.split('\n');
|
|
6178
6273
|
const lines = n.shape === 'text' && rawLines.length === 1
|
|
6179
|
-
? wrapText(n.label, n.w -
|
|
6274
|
+
? wrapText(n.label, n.w - pad * 2, fontSize)
|
|
6180
6275
|
: rawLines;
|
|
6181
6276
|
// vertical-align: compute textCY from top/middle/bottom
|
|
6182
|
-
const nodeBodyTop = n.y +
|
|
6183
|
-
const nodeBodyBottom = n.y + n.h -
|
|
6277
|
+
const nodeBodyTop = n.y + pad;
|
|
6278
|
+
const nodeBodyBottom = n.y + n.h - pad;
|
|
6184
6279
|
const blockH = (lines.length - 1) * lineHeight;
|
|
6185
6280
|
const textCY = vertAlign === 'top' ? nodeBodyTop + blockH / 2
|
|
6186
6281
|
: vertAlign === 'bottom' ? nodeBodyBottom - blockH / 2
|
|
@@ -6191,6 +6286,8 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6191
6286
|
else {
|
|
6192
6287
|
drawText(ctx, lines[0] ?? '', textX, textCY, fontSize, fontWeight, textColor, textAlign, nodeFont, letterSpacing);
|
|
6193
6288
|
}
|
|
6289
|
+
if (n.style?.opacity != null)
|
|
6290
|
+
ctx.globalAlpha = 1;
|
|
6194
6291
|
}
|
|
6195
6292
|
// ── Tables ────────────────────────────────────────────────
|
|
6196
6293
|
for (const t of sg.tables) {
|
|
@@ -6200,25 +6297,28 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6200
6297
|
const textCol = String(gs.color ?? palette.tableText);
|
|
6201
6298
|
const pad = t.labelH;
|
|
6202
6299
|
// ── Table-level font ────────────────────────────────
|
|
6203
|
-
// supports: font, font-size, letter-spacing
|
|
6204
|
-
// cells also support text-align
|
|
6205
6300
|
const tFontSize = Number(gs.fontSize ?? 12);
|
|
6206
6301
|
const tFont = resolveStyleFont(gs, diagramFont);
|
|
6207
6302
|
const tLetterSpacing = gs.letterSpacing;
|
|
6303
|
+
const tStrokeWidth = Number(gs.strokeWidth ?? 1.5);
|
|
6304
|
+
const tFontWeight = gs.fontWeight ?? 500;
|
|
6305
|
+
if (gs.opacity != null)
|
|
6306
|
+
ctx.globalAlpha = Number(gs.opacity);
|
|
6208
6307
|
rc.rectangle(t.x, t.y, t.w, t.h, {
|
|
6209
6308
|
...R, seed: hashStr$1(t.id),
|
|
6210
|
-
fill, fillStyle: 'solid', stroke: strk, strokeWidth:
|
|
6309
|
+
fill, fillStyle: 'solid', stroke: strk, strokeWidth: tStrokeWidth,
|
|
6310
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
6211
6311
|
});
|
|
6212
6312
|
rc.line(t.x, t.y + pad, t.x + t.w, t.y + pad, {
|
|
6213
6313
|
roughness: 0.6, seed: hashStr$1(t.id + 'l'), stroke: strk, strokeWidth: 1,
|
|
6214
6314
|
});
|
|
6215
6315
|
// ── Table label: always left-anchored ───────────────
|
|
6216
|
-
drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize,
|
|
6316
|
+
drawText(ctx, t.label, t.x + 10, t.y + pad / 2, tFontSize, tFontWeight, textCol, 'left', tFont, tLetterSpacing);
|
|
6217
6317
|
let rowY = t.y + pad;
|
|
6218
6318
|
for (const row of t.rows) {
|
|
6219
6319
|
const rh = row.kind === 'header' ? t.headerH : t.rowH;
|
|
6220
6320
|
if (row.kind === 'header') {
|
|
6221
|
-
ctx.fillStyle = palette.tableHeaderFill;
|
|
6321
|
+
ctx.fillStyle = gs.fill ? darkenHex(fill, 0.08) : palette.tableHeaderFill;
|
|
6222
6322
|
ctx.fillRect(t.x + 1, rowY + 1, t.w - 2, rh - 1);
|
|
6223
6323
|
}
|
|
6224
6324
|
rc.line(t.x, rowY + rh, t.x + t.w, rowY + rh, {
|
|
@@ -6231,7 +6331,7 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6231
6331
|
const cellAlignProp = (row.kind === 'header'
|
|
6232
6332
|
? 'center'
|
|
6233
6333
|
: String(gs.textAlign ?? 'center'));
|
|
6234
|
-
const cellFw = row.kind === 'header' ? 600 : 400;
|
|
6334
|
+
const cellFw = row.kind === 'header' ? 600 : (gs.fontWeight ?? 400);
|
|
6235
6335
|
const cellColor = row.kind === 'header'
|
|
6236
6336
|
? String(gs.color ?? palette.tableHeaderText)
|
|
6237
6337
|
: textCol;
|
|
@@ -6252,63 +6352,87 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6252
6352
|
});
|
|
6253
6353
|
rowY += rh;
|
|
6254
6354
|
}
|
|
6355
|
+
ctx.globalAlpha = 1;
|
|
6255
6356
|
}
|
|
6256
6357
|
// ── Notes ─────────────────────────────────────────────────
|
|
6257
6358
|
for (const n of sg.notes) {
|
|
6258
6359
|
const gs = n.style ?? {};
|
|
6259
6360
|
const fill = String(gs.fill ?? palette.noteFill);
|
|
6260
6361
|
const strk = String(gs.stroke ?? palette.noteStroke);
|
|
6362
|
+
const nStrokeWidth = Number(gs.strokeWidth ?? 1.2);
|
|
6261
6363
|
const fold = 14;
|
|
6262
6364
|
const { x, y, w, h } = n;
|
|
6365
|
+
if (gs.opacity != null)
|
|
6366
|
+
ctx.globalAlpha = Number(gs.opacity);
|
|
6263
6367
|
rc.polygon([
|
|
6264
6368
|
[x, y],
|
|
6265
6369
|
[x + w - fold, y],
|
|
6266
6370
|
[x + w, y + fold],
|
|
6267
6371
|
[x + w, y + h],
|
|
6268
6372
|
[x, y + h],
|
|
6269
|
-
], { ...R, seed: hashStr$1(n.id), fill, fillStyle: 'solid', stroke: strk,
|
|
6373
|
+
], { ...R, seed: hashStr$1(n.id), fill, fillStyle: 'solid', stroke: strk,
|
|
6374
|
+
strokeWidth: nStrokeWidth,
|
|
6375
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
6376
|
+
});
|
|
6270
6377
|
rc.polygon([
|
|
6271
6378
|
[x + w - fold, y],
|
|
6272
6379
|
[x + w, y + fold],
|
|
6273
6380
|
[x + w - fold, y + fold],
|
|
6274
6381
|
], { roughness: 0.4, seed: hashStr$1(n.id + 'f'),
|
|
6275
|
-
fill: palette.noteFold, fillStyle: 'solid', stroke: strk,
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
// vertical-align, line-height
|
|
6382
|
+
fill: palette.noteFold, fillStyle: 'solid', stroke: strk,
|
|
6383
|
+
strokeWidth: Math.min(nStrokeWidth, 0.8),
|
|
6384
|
+
});
|
|
6279
6385
|
const nFontSize = Number(gs.fontSize ?? 12);
|
|
6386
|
+
const nFontWeight = gs.fontWeight ?? 400;
|
|
6280
6387
|
const nFont = resolveStyleFont(gs, diagramFont);
|
|
6281
6388
|
const nLetterSpacing = gs.letterSpacing;
|
|
6282
6389
|
const nLineHeight = Number(gs.lineHeight ?? 1.4) * nFontSize;
|
|
6283
6390
|
const nTextAlign = String(gs.textAlign ?? 'left');
|
|
6284
6391
|
const nVertAlign = String(gs.verticalAlign ?? 'top');
|
|
6285
6392
|
const nColor = String(gs.color ?? palette.noteText);
|
|
6286
|
-
const
|
|
6393
|
+
const nPad = Number(gs.padding ?? 12);
|
|
6394
|
+
const nTextX = nTextAlign === 'right' ? x + w - fold - nPad
|
|
6287
6395
|
: nTextAlign === 'center' ? x + (w - fold) / 2
|
|
6288
|
-
: x +
|
|
6289
|
-
|
|
6290
|
-
const bodyTop = y +
|
|
6291
|
-
const bodyBottom = y + h -
|
|
6396
|
+
: x + nPad;
|
|
6397
|
+
const nFoldPad = fold + nPad;
|
|
6398
|
+
const bodyTop = y + nFoldPad;
|
|
6399
|
+
const bodyBottom = y + h - nPad;
|
|
6292
6400
|
const blockH = (n.lines.length - 1) * nLineHeight;
|
|
6293
6401
|
const blockCY = nVertAlign === 'bottom' ? bodyBottom - blockH / 2
|
|
6294
6402
|
: nVertAlign === 'middle' ? (bodyTop + bodyBottom) / 2
|
|
6295
|
-
: bodyTop + blockH / 2;
|
|
6403
|
+
: bodyTop + blockH / 2;
|
|
6296
6404
|
if (n.lines.length > 1) {
|
|
6297
|
-
drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize,
|
|
6405
|
+
drawMultilineText(ctx, n.lines, nTextX, blockCY, nFontSize, nFontWeight, nColor, nTextAlign, nLineHeight, nFont, nLetterSpacing);
|
|
6298
6406
|
}
|
|
6299
6407
|
else {
|
|
6300
|
-
drawText(ctx, n.lines[0] ?? '', nTextX, blockCY, nFontSize,
|
|
6408
|
+
drawText(ctx, n.lines[0] ?? '', nTextX, blockCY, nFontSize, nFontWeight, nColor, nTextAlign, nFont, nLetterSpacing);
|
|
6301
6409
|
}
|
|
6410
|
+
if (gs.opacity != null)
|
|
6411
|
+
ctx.globalAlpha = 1;
|
|
6302
6412
|
}
|
|
6303
6413
|
// ── Markdown blocks ────────────────────────────────────────
|
|
6304
6414
|
// Renders prose with Markdown headings and bold/italic inline spans.
|
|
6305
6415
|
// Canvas has no native bold-within-a-run, so each run is drawn
|
|
6306
6416
|
// individually with its own ctx.font setting.
|
|
6307
6417
|
for (const m of (sg.markdowns ?? [])) {
|
|
6308
|
-
const
|
|
6309
|
-
const
|
|
6310
|
-
const
|
|
6311
|
-
const
|
|
6418
|
+
const gs = m.style ?? {};
|
|
6419
|
+
const mFont = resolveStyleFont(gs, diagramFont);
|
|
6420
|
+
const baseColor = String(gs.color ?? palette.nodeText);
|
|
6421
|
+
const textAlign = String(gs.textAlign ?? 'left');
|
|
6422
|
+
const PAD = Number(gs.padding ?? 16);
|
|
6423
|
+
const mLetterSpacing = gs.letterSpacing;
|
|
6424
|
+
if (gs.opacity != null)
|
|
6425
|
+
ctx.globalAlpha = Number(gs.opacity);
|
|
6426
|
+
// Background + border
|
|
6427
|
+
if (gs.fill || gs.stroke) {
|
|
6428
|
+
rc.rectangle(m.x, m.y, m.w, m.h, {
|
|
6429
|
+
...R, seed: hashStr$1(m.id),
|
|
6430
|
+
fill: String(gs.fill ?? 'none'), fillStyle: 'solid',
|
|
6431
|
+
stroke: String(gs.stroke ?? 'none'),
|
|
6432
|
+
strokeWidth: Number(gs.strokeWidth ?? 1.2),
|
|
6433
|
+
...(gs.strokeDash ? { strokeLineDash: gs.strokeDash } : {}),
|
|
6434
|
+
});
|
|
6435
|
+
}
|
|
6312
6436
|
const anchorX = textAlign === 'right' ? m.x + m.w - PAD
|
|
6313
6437
|
: textAlign === 'center' ? m.x + m.w / 2
|
|
6314
6438
|
: m.x + PAD;
|
|
@@ -6326,14 +6450,29 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6326
6450
|
ctx.save();
|
|
6327
6451
|
ctx.textBaseline = 'middle';
|
|
6328
6452
|
ctx.fillStyle = baseColor;
|
|
6453
|
+
const ls = mLetterSpacing ?? 0;
|
|
6454
|
+
// measure run width including letter-spacing
|
|
6455
|
+
const runW = (run) => {
|
|
6456
|
+
return ctx.measureText(run.text).width + ls * run.text.length;
|
|
6457
|
+
};
|
|
6458
|
+
const drawRun = (run, rx) => {
|
|
6459
|
+
if (ls) {
|
|
6460
|
+
for (const ch of run.text) {
|
|
6461
|
+
ctx.fillText(ch, rx, lineY);
|
|
6462
|
+
rx += ctx.measureText(ch).width + ls;
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6465
|
+
else {
|
|
6466
|
+
ctx.fillText(run.text, rx, lineY);
|
|
6467
|
+
}
|
|
6468
|
+
};
|
|
6329
6469
|
if (textAlign === 'center' || textAlign === 'right') {
|
|
6330
|
-
// Measure full line width first
|
|
6331
6470
|
let totalW = 0;
|
|
6332
6471
|
for (const run of line.runs) {
|
|
6333
6472
|
const runStyle = run.italic ? 'italic ' : '';
|
|
6334
6473
|
const runWeight = run.bold ? 700 : fontWeight;
|
|
6335
6474
|
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
6336
|
-
totalW +=
|
|
6475
|
+
totalW += runW(run);
|
|
6337
6476
|
}
|
|
6338
6477
|
let runX = textAlign === 'center' ? anchorX - totalW / 2 : anchorX - totalW;
|
|
6339
6478
|
ctx.textAlign = 'left';
|
|
@@ -6341,25 +6480,25 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
6341
6480
|
const runStyle = run.italic ? 'italic ' : '';
|
|
6342
6481
|
const runWeight = run.bold ? 700 : fontWeight;
|
|
6343
6482
|
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
6344
|
-
|
|
6345
|
-
runX +=
|
|
6483
|
+
drawRun(run, runX);
|
|
6484
|
+
runX += runW(run);
|
|
6346
6485
|
}
|
|
6347
6486
|
}
|
|
6348
6487
|
else {
|
|
6349
|
-
// left-aligned — draw runs left to right from anchorX
|
|
6350
6488
|
let runX = anchorX;
|
|
6351
6489
|
ctx.textAlign = 'left';
|
|
6352
6490
|
for (const run of line.runs) {
|
|
6353
6491
|
const runStyle = run.italic ? 'italic ' : '';
|
|
6354
6492
|
const runWeight = run.bold ? 700 : fontWeight;
|
|
6355
6493
|
ctx.font = `${runStyle}${runWeight} ${fontSize}px ${mFont}`;
|
|
6356
|
-
|
|
6357
|
-
runX +=
|
|
6494
|
+
drawRun(run, runX);
|
|
6495
|
+
runX += runW(run);
|
|
6358
6496
|
}
|
|
6359
6497
|
}
|
|
6360
6498
|
ctx.restore();
|
|
6361
6499
|
y += LINE_SPACING[line.kind];
|
|
6362
6500
|
}
|
|
6501
|
+
ctx.globalAlpha = 1;
|
|
6363
6502
|
}
|
|
6364
6503
|
// ── Charts ────────────────────────────────────────────────
|
|
6365
6504
|
for (const c of sg.charts) {
|
|
@@ -6397,6 +6536,7 @@ const getEdgeEl = (svg, f, t) => getEl(svg, `edge-${f}-${t}`);
|
|
|
6397
6536
|
const getTableEl = (svg, id) => getEl(svg, `table-${id}`);
|
|
6398
6537
|
const getNoteEl = (svg, id) => getEl(svg, `note-${id}`);
|
|
6399
6538
|
const getChartEl = (svg, id) => getEl(svg, `chart-${id}`);
|
|
6539
|
+
const getMarkdownEl = (svg, id) => getEl(svg, `markdown-${id}`);
|
|
6400
6540
|
function resolveEl(svg, target) {
|
|
6401
6541
|
// check edge first — target contains connector like "a-->b"
|
|
6402
6542
|
const edge = parseEdgeTarget(target);
|
|
@@ -6408,6 +6548,7 @@ function resolveEl(svg, target) {
|
|
|
6408
6548
|
getTableEl(svg, target) ??
|
|
6409
6549
|
getNoteEl(svg, target) ??
|
|
6410
6550
|
getChartEl(svg, target) ??
|
|
6551
|
+
getMarkdownEl(svg, target) ??
|
|
6411
6552
|
null);
|
|
6412
6553
|
}
|
|
6413
6554
|
function pathLength(p) {
|
|
@@ -6418,6 +6559,15 @@ function pathLength(p) {
|
|
|
6418
6559
|
return 200;
|
|
6419
6560
|
}
|
|
6420
6561
|
}
|
|
6562
|
+
function clearDashOverridesAfter(el, delayMs) {
|
|
6563
|
+
setTimeout(() => {
|
|
6564
|
+
el.querySelectorAll('path').forEach(p => {
|
|
6565
|
+
p.style.strokeDasharray = '';
|
|
6566
|
+
p.style.strokeDashoffset = '';
|
|
6567
|
+
p.style.transition = '';
|
|
6568
|
+
});
|
|
6569
|
+
}, delayMs);
|
|
6570
|
+
}
|
|
6421
6571
|
// ── Arrow connector parser ────────────────────────────────
|
|
6422
6572
|
const ARROW_CONNECTORS = ["<-->", "<->", "-->", "<--", "->", "<-", "---", "--"];
|
|
6423
6573
|
function parseEdgeTarget(target) {
|
|
@@ -6525,32 +6675,39 @@ function clearEdgeDrawStyles(el) {
|
|
|
6525
6675
|
});
|
|
6526
6676
|
}
|
|
6527
6677
|
function animateEdgeDraw(el, conn) {
|
|
6528
|
-
const paths = Array.from(el.querySelectorAll(
|
|
6678
|
+
const paths = Array.from(el.querySelectorAll('path'));
|
|
6529
6679
|
if (!paths.length)
|
|
6530
6680
|
return;
|
|
6531
6681
|
const linePath = paths[0];
|
|
6532
6682
|
const headPaths = paths.slice(1);
|
|
6533
6683
|
const STROKE_DUR = 360;
|
|
6534
6684
|
const len = pathLength(linePath);
|
|
6535
|
-
const reversed = conn.startsWith(
|
|
6685
|
+
const reversed = conn.startsWith('<') && !conn.includes('>');
|
|
6536
6686
|
linePath.style.strokeDasharray = `${len}`;
|
|
6537
6687
|
linePath.style.strokeDashoffset = reversed ? `${-len}` : `${len}`;
|
|
6538
|
-
linePath.style.transition =
|
|
6539
|
-
headPaths.forEach(
|
|
6540
|
-
p.style.opacity =
|
|
6541
|
-
p.style.transition =
|
|
6688
|
+
linePath.style.transition = 'none';
|
|
6689
|
+
headPaths.forEach(p => {
|
|
6690
|
+
p.style.opacity = '0';
|
|
6691
|
+
p.style.transition = 'none';
|
|
6542
6692
|
});
|
|
6543
|
-
el.classList.remove(
|
|
6544
|
-
el.classList.add(
|
|
6545
|
-
el.style.opacity =
|
|
6693
|
+
el.classList.remove('draw-hidden');
|
|
6694
|
+
el.classList.add('draw-reveal');
|
|
6695
|
+
el.style.opacity = '1';
|
|
6546
6696
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
6547
6697
|
linePath.style.transition = `stroke-dashoffset ${STROKE_DUR}ms cubic-bezier(.4,0,.2,1)`;
|
|
6548
|
-
linePath.style.strokeDashoffset =
|
|
6698
|
+
linePath.style.strokeDashoffset = '0';
|
|
6549
6699
|
setTimeout(() => {
|
|
6550
|
-
headPaths.forEach(
|
|
6551
|
-
p.style.transition =
|
|
6552
|
-
p.style.opacity =
|
|
6700
|
+
headPaths.forEach(p => {
|
|
6701
|
+
p.style.transition = 'opacity 120ms ease';
|
|
6702
|
+
p.style.opacity = '1';
|
|
6553
6703
|
});
|
|
6704
|
+
// ── ADD: clear inline dash overrides so SVG attribute
|
|
6705
|
+
// (stroke-dasharray="6,5" for dashed arrows) takes over again
|
|
6706
|
+
setTimeout(() => {
|
|
6707
|
+
linePath.style.strokeDasharray = '';
|
|
6708
|
+
linePath.style.strokeDashoffset = '';
|
|
6709
|
+
linePath.style.transition = '';
|
|
6710
|
+
}, 160);
|
|
6554
6711
|
}, STROKE_DUR - 40);
|
|
6555
6712
|
}));
|
|
6556
6713
|
}
|
|
@@ -6574,6 +6731,7 @@ class AnimationController {
|
|
|
6574
6731
|
this.drawTargetTables = new Set();
|
|
6575
6732
|
this.drawTargetNotes = new Set();
|
|
6576
6733
|
this.drawTargetCharts = new Set();
|
|
6734
|
+
this.drawTargetMarkdowns = new Set();
|
|
6577
6735
|
for (const s of steps) {
|
|
6578
6736
|
if (s.action !== "draw" || parseEdgeTarget(s.target))
|
|
6579
6737
|
continue;
|
|
@@ -6594,6 +6752,10 @@ class AnimationController {
|
|
|
6594
6752
|
this.drawTargetCharts.add(`chart-${s.target}`);
|
|
6595
6753
|
this.drawTargetNodes.delete(`node-${s.target}`);
|
|
6596
6754
|
}
|
|
6755
|
+
if (svg.querySelector(`#markdown-${s.target}`)) {
|
|
6756
|
+
this.drawTargetMarkdowns.add(`markdown-${s.target}`);
|
|
6757
|
+
this.drawTargetNodes.delete(`node-${s.target}`);
|
|
6758
|
+
}
|
|
6597
6759
|
}
|
|
6598
6760
|
this._clearAll();
|
|
6599
6761
|
}
|
|
@@ -6765,7 +6927,24 @@ class AnimationController {
|
|
|
6765
6927
|
});
|
|
6766
6928
|
}
|
|
6767
6929
|
});
|
|
6768
|
-
|
|
6930
|
+
// Markdown
|
|
6931
|
+
this.svg.querySelectorAll(".mdg").forEach((el) => {
|
|
6932
|
+
clearDrawStyles(el);
|
|
6933
|
+
el.style.transition = "none";
|
|
6934
|
+
el.style.opacity = "";
|
|
6935
|
+
if (this.drawTargetMarkdowns.has(el.id)) {
|
|
6936
|
+
el.classList.add("gg-hidden");
|
|
6937
|
+
}
|
|
6938
|
+
else {
|
|
6939
|
+
el.classList.remove("gg-hidden");
|
|
6940
|
+
requestAnimationFrame(() => {
|
|
6941
|
+
el.style.transition = "";
|
|
6942
|
+
});
|
|
6943
|
+
}
|
|
6944
|
+
});
|
|
6945
|
+
this.svg
|
|
6946
|
+
.querySelectorAll(".tg, .ntg, .cg, .mdg")
|
|
6947
|
+
.forEach((el) => {
|
|
6769
6948
|
el.style.transform = "";
|
|
6770
6949
|
el.style.transition = "";
|
|
6771
6950
|
el.style.opacity = "";
|
|
@@ -6943,6 +7122,9 @@ class AnimationController {
|
|
|
6943
7122
|
if (!firstPath?.style.strokeDasharray)
|
|
6944
7123
|
prepareForDraw(groupEl);
|
|
6945
7124
|
animateShapeDraw(groupEl, 550, 40);
|
|
7125
|
+
const pathCount = groupEl.querySelectorAll('path').length;
|
|
7126
|
+
const totalMs = pathCount * 40 + 550 + 120; // stagger + duration + buffer
|
|
7127
|
+
clearDashOverridesAfter(groupEl, totalMs);
|
|
6946
7128
|
}
|
|
6947
7129
|
return;
|
|
6948
7130
|
}
|
|
@@ -6963,6 +7145,8 @@ class AnimationController {
|
|
|
6963
7145
|
tableEl.classList.remove("gg-hidden");
|
|
6964
7146
|
prepareForDraw(tableEl);
|
|
6965
7147
|
animateShapeDraw(tableEl, 500, 40);
|
|
7148
|
+
const tablePathCount = tableEl.querySelectorAll('path').length;
|
|
7149
|
+
clearDashOverridesAfter(tableEl, tablePathCount * 40 + 500 + 120);
|
|
6966
7150
|
}
|
|
6967
7151
|
return;
|
|
6968
7152
|
}
|
|
@@ -6983,6 +7167,8 @@ class AnimationController {
|
|
|
6983
7167
|
noteEl.classList.remove("gg-hidden");
|
|
6984
7168
|
prepareForDraw(noteEl);
|
|
6985
7169
|
animateShapeDraw(noteEl, 420, 55);
|
|
7170
|
+
const notePathCount = noteEl.querySelectorAll('path').length;
|
|
7171
|
+
clearDashOverridesAfter(noteEl, notePathCount * 55 + 420 + 120);
|
|
6986
7172
|
}
|
|
6987
7173
|
return;
|
|
6988
7174
|
}
|
|
@@ -7010,6 +7196,28 @@ class AnimationController {
|
|
|
7010
7196
|
}
|
|
7011
7197
|
return;
|
|
7012
7198
|
}
|
|
7199
|
+
// ── Markdown ──────────────────────────────────────────
|
|
7200
|
+
const markdownEl = getMarkdownEl(this.svg, target);
|
|
7201
|
+
if (markdownEl) {
|
|
7202
|
+
if (silent) {
|
|
7203
|
+
markdownEl.style.transition = "none";
|
|
7204
|
+
markdownEl.style.opacity = "";
|
|
7205
|
+
markdownEl.classList.remove("gg-hidden");
|
|
7206
|
+
markdownEl.style.opacity = "1";
|
|
7207
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
7208
|
+
markdownEl.style.transition = "";
|
|
7209
|
+
}));
|
|
7210
|
+
}
|
|
7211
|
+
else {
|
|
7212
|
+
markdownEl.style.opacity = "0";
|
|
7213
|
+
markdownEl.classList.remove("gg-hidden");
|
|
7214
|
+
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
7215
|
+
markdownEl.style.transition = "opacity 500ms ease";
|
|
7216
|
+
markdownEl.style.opacity = "1";
|
|
7217
|
+
}));
|
|
7218
|
+
}
|
|
7219
|
+
return;
|
|
7220
|
+
}
|
|
7013
7221
|
// ── Node draw ──────────────────────────────────────
|
|
7014
7222
|
const nodeEl = getNodeEl(this.svg, target);
|
|
7015
7223
|
if (!nodeEl)
|
|
@@ -7023,14 +7231,16 @@ class AnimationController {
|
|
|
7023
7231
|
if (!firstPath?.style.strokeDasharray)
|
|
7024
7232
|
prepareForDraw(nodeEl);
|
|
7025
7233
|
animateShapeDraw(nodeEl, 420, 55);
|
|
7234
|
+
const nodePathCount = nodeEl.querySelectorAll('path').length;
|
|
7235
|
+
clearDashOverridesAfter(nodeEl, nodePathCount * 55 + 420 + 120);
|
|
7026
7236
|
}
|
|
7027
7237
|
}
|
|
7028
7238
|
// ── erase ─────────────────────────────────────────────────
|
|
7029
7239
|
_doErase(target) {
|
|
7030
7240
|
const el = resolveEl(this.svg, target); // handles edges too now
|
|
7031
7241
|
if (el) {
|
|
7032
|
-
el.style.transition =
|
|
7033
|
-
el.style.opacity =
|
|
7242
|
+
el.style.transition = "opacity 0.4s";
|
|
7243
|
+
el.style.opacity = "0";
|
|
7034
7244
|
}
|
|
7035
7245
|
}
|
|
7036
7246
|
// ── show / hide ───────────────────────────────────────────
|
|
@@ -7058,10 +7268,10 @@ class AnimationController {
|
|
|
7058
7268
|
return;
|
|
7059
7269
|
// edge — color stroke
|
|
7060
7270
|
if (parseEdgeTarget(target)) {
|
|
7061
|
-
el.querySelectorAll(
|
|
7271
|
+
el.querySelectorAll("path, line, polyline").forEach((p) => {
|
|
7062
7272
|
p.style.stroke = color;
|
|
7063
7273
|
});
|
|
7064
|
-
el.querySelectorAll(
|
|
7274
|
+
el.querySelectorAll("polygon").forEach((p) => {
|
|
7065
7275
|
p.style.fill = color;
|
|
7066
7276
|
p.style.stroke = color;
|
|
7067
7277
|
});
|
|
@@ -7069,22 +7279,24 @@ class AnimationController {
|
|
|
7069
7279
|
}
|
|
7070
7280
|
// everything else — color fill
|
|
7071
7281
|
let hit = false;
|
|
7072
|
-
el.querySelectorAll(
|
|
7073
|
-
const attrFill = c.getAttribute(
|
|
7074
|
-
if (attrFill ===
|
|
7282
|
+
el.querySelectorAll("path, rect, ellipse, polygon").forEach((c) => {
|
|
7283
|
+
const attrFill = c.getAttribute("fill");
|
|
7284
|
+
if (attrFill === "none")
|
|
7075
7285
|
return;
|
|
7076
|
-
if (attrFill === null && c.tagName ===
|
|
7286
|
+
if (attrFill === null && c.tagName === "path")
|
|
7077
7287
|
return;
|
|
7078
7288
|
c.style.fill = color;
|
|
7079
7289
|
hit = true;
|
|
7080
7290
|
});
|
|
7081
7291
|
if (!hit) {
|
|
7082
|
-
el.querySelectorAll(
|
|
7292
|
+
el.querySelectorAll("text").forEach((t) => {
|
|
7293
|
+
t.style.fill = color;
|
|
7294
|
+
});
|
|
7083
7295
|
}
|
|
7084
7296
|
}
|
|
7085
7297
|
}
|
|
7086
7298
|
const ANIMATION_CSS = `
|
|
7087
|
-
.ng, .gg, .tg, .ntg, .cg, .eg {
|
|
7299
|
+
.ng, .gg, .tg, .ntg, .cg, .eg, .mdg {
|
|
7088
7300
|
transform-box: fill-box;
|
|
7089
7301
|
transform-origin: center;
|
|
7090
7302
|
transition: filter 0.3s, opacity 0.35s;
|
|
@@ -7095,9 +7307,10 @@ const ANIMATION_CSS = `
|
|
|
7095
7307
|
.tg.hl path, .tg.hl rect,
|
|
7096
7308
|
.ntg.hl path, .ntg.hl polygon,
|
|
7097
7309
|
.cg.hl path, .cg.hl rect,
|
|
7310
|
+
.mdg.hl text,
|
|
7098
7311
|
.eg.hl path, .eg.hl line, .eg.hl polygon { stroke-width: 2.8 !important; }
|
|
7099
7312
|
|
|
7100
|
-
.ng.hl, .tg.hl, .ntg.hl, .cg.hl, .eg.hl {
|
|
7313
|
+
.ng.hl, .tg.hl, .ntg.hl, .cg.hl, .mdg.hl, .eg.hl {
|
|
7101
7314
|
animation: ng-pulse 1.4s ease-in-out infinite;
|
|
7102
7315
|
}
|
|
7103
7316
|
@keyframes ng-pulse {
|
|
@@ -7106,7 +7319,8 @@ const ANIMATION_CSS = `
|
|
|
7106
7319
|
}
|
|
7107
7320
|
|
|
7108
7321
|
/* fade */
|
|
7109
|
-
.ng.faded, .gg.faded, .tg.faded, .ntg.faded,
|
|
7322
|
+
.ng.faded, .gg.faded, .tg.faded, .ntg.faded,
|
|
7323
|
+
.cg.faded, .eg.faded, .mdg.faded { opacity: 0.22; }
|
|
7110
7324
|
|
|
7111
7325
|
.ng.hidden { opacity: 0; pointer-events: none; }
|
|
7112
7326
|
.eg.draw-hidden { opacity: 0; }
|
|
@@ -7115,6 +7329,7 @@ const ANIMATION_CSS = `
|
|
|
7115
7329
|
.tg.gg-hidden { opacity: 0; }
|
|
7116
7330
|
.ntg.gg-hidden { opacity: 0; }
|
|
7117
7331
|
.cg.gg-hidden { opacity: 0; }
|
|
7332
|
+
.mdg.gg-hidden { opacity: 0; }
|
|
7118
7333
|
`;
|
|
7119
7334
|
|
|
7120
7335
|
// ============================================================
|