sketchmark 1.3.6 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -18
- package/dist/animation/index.d.ts +1 -1
- package/dist/animation/index.d.ts.map +1 -1
- package/dist/ast/types.d.ts +4 -0
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/index.cjs +340 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +340 -49
- 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/shared.d.ts +16 -0
- package/dist/renderer/shared.d.ts.map +1 -1
- package/dist/renderer/svg/index.d.ts.map +1 -1
- package/dist/scene/index.d.ts +4 -2
- package/dist/scene/index.d.ts.map +1 -1
- package/dist/sketchmark.iife.js +340 -49
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { AnimationController, ANIMATION_CSS } from "./animation";
|
|
|
12
12
|
export type { AnimationEvent, AnimationEventType } from "./animation";
|
|
13
13
|
export { exportSVG, exportPNG, exportCanvasPNG, exportHTML, exportGIF, exportMP4, getSVGBlob, svgToPNGDataURL, } from "./export";
|
|
14
14
|
export type { ExportFormat, ExportOptions } from "./export";
|
|
15
|
-
export type { NodeShape, EdgeConnector, EdgeAnchor, LayoutType, AlignItems, JustifyContent, AnimationAction, AnimationTrigger, StyleProps, StepPace, ASTNode, ASTEdge, ASTGroup, ASTStep, ASTBeat, ASTStepItem, ASTChart, ASTTable, GroupChildRef, RootItemRef, ASTMarkdown, } from "./ast/types";
|
|
15
|
+
export type { NodeShape, EdgeConnector, EdgeAnchor, EdgeRoute, EdgePoint, LayoutType, AlignItems, JustifyContent, AnimationAction, AnimationTrigger, StyleProps, StepPace, ASTNode, ASTEdge, ASTGroup, ASTStep, ASTBeat, ASTStepItem, ASTChart, ASTTable, GroupChildRef, RootItemRef, ASTMarkdown, } from "./ast/types";
|
|
16
16
|
export { hashStr, clamp, lerp, parseHex, sleep, throttle, debounce, EventEmitter, } from "./utils";
|
|
17
17
|
export { render } from "./render";
|
|
18
18
|
export type { RenderOptions, DiagramInstance } from "./render";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC1E,YAAY,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtE,OAAO,EACL,SAAS,EACT,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG5D,YAAY,EACV,SAAS,EACT,aAAa,EACb,UAAU,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAQ,EACR,OAAO,EACP,OAAO,EACP,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,OAAO,EACP,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,sBAAsB,EACtB,iCAAiC,GAClC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC1E,YAAY,EACV,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,cAAc,EACd,eAAe,EACf,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtE,OAAO,EACL,SAAS,EACT,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAG5D,YAAY,EACV,SAAS,EACT,aAAa,EACb,UAAU,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAQ,EACR,OAAO,EACP,OAAO,EACP,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,WAAW,EACX,WAAW,GACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,OAAO,EACP,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,YAAY,GACb,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAG/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,sBAAsB,EACtB,iCAAiC,GAClC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EACV,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -357,6 +357,39 @@ function isValueToken(t) {
|
|
|
357
357
|
function isPropKeyToken(t) {
|
|
358
358
|
return !!t && (t.type === "IDENT" || t.type === "KEYWORD");
|
|
359
359
|
}
|
|
360
|
+
const NUMBER_RE = /[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?/g;
|
|
361
|
+
function parseEdgeWaypoints(value, token) {
|
|
362
|
+
if (!value)
|
|
363
|
+
return undefined;
|
|
364
|
+
const numbers = (value.match(NUMBER_RE) ?? []).map((part) => Number(part));
|
|
365
|
+
if (!numbers.length)
|
|
366
|
+
return undefined;
|
|
367
|
+
if (numbers.length % 2 !== 0) {
|
|
368
|
+
throw new ParseError(`Edge via must contain x,y coordinate pairs`, token.line, token.col);
|
|
369
|
+
}
|
|
370
|
+
const points = [];
|
|
371
|
+
for (let index = 0; index < numbers.length; index += 2) {
|
|
372
|
+
const x = numbers[index];
|
|
373
|
+
const y = numbers[index + 1];
|
|
374
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
375
|
+
throw new ParseError(`Edge via contains a non-numeric coordinate`, token.line, token.col);
|
|
376
|
+
}
|
|
377
|
+
points.push([x, y]);
|
|
378
|
+
}
|
|
379
|
+
return points.length ? points : undefined;
|
|
380
|
+
}
|
|
381
|
+
function normalizeEdgeRoute(value, token) {
|
|
382
|
+
if (!value)
|
|
383
|
+
return undefined;
|
|
384
|
+
const normalized = value.toLowerCase();
|
|
385
|
+
if (normalized === "straight" || normalized === "polyline" || normalized === "orthogonal") {
|
|
386
|
+
return normalized;
|
|
387
|
+
}
|
|
388
|
+
if (normalized === "ortho" || normalized === "elbow") {
|
|
389
|
+
return "orthogonal";
|
|
390
|
+
}
|
|
391
|
+
throw new ParseError(`Unsupported edge route "${value}"; use straight, orthogonal, or polyline`, token.line, token.col);
|
|
392
|
+
}
|
|
360
393
|
function parse(src, options = {}) {
|
|
361
394
|
resetUid();
|
|
362
395
|
const preparedSource = applyPluginPreprocessors(src, options.plugins);
|
|
@@ -715,28 +748,63 @@ function parse(src, options = {}) {
|
|
|
715
748
|
height: props.height !== undefined ? parseFloat(props.height) : undefined,
|
|
716
749
|
};
|
|
717
750
|
}
|
|
718
|
-
function
|
|
719
|
-
const toTok = rest.shift();
|
|
720
|
-
if (!toTok)
|
|
721
|
-
throw new ParseError("Expected edge target", 0, 0);
|
|
751
|
+
function parseEdgeProps(toks) {
|
|
722
752
|
const props = {};
|
|
723
753
|
let j = 0;
|
|
724
|
-
while (j <
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
j += 3;
|
|
754
|
+
while (j < toks.length) {
|
|
755
|
+
const key = toks[j];
|
|
756
|
+
const eq = toks[j + 1];
|
|
757
|
+
if (!isPropKeyToken(key) || eq?.type !== "EQUALS") {
|
|
758
|
+
j++;
|
|
759
|
+
continue;
|
|
731
760
|
}
|
|
732
|
-
|
|
761
|
+
const value = toks[j + 2];
|
|
762
|
+
if (!value) {
|
|
733
763
|
j++;
|
|
764
|
+
continue;
|
|
734
765
|
}
|
|
766
|
+
if (value.type === "LBRACKET") {
|
|
767
|
+
const parts = [];
|
|
768
|
+
let depth = 1;
|
|
769
|
+
j += 3;
|
|
770
|
+
while (j < toks.length && depth > 0) {
|
|
771
|
+
const tok = toks[j];
|
|
772
|
+
if (tok.type === "LBRACKET") {
|
|
773
|
+
depth++;
|
|
774
|
+
}
|
|
775
|
+
else if (tok.type === "RBRACKET") {
|
|
776
|
+
depth--;
|
|
777
|
+
if (depth === 0) {
|
|
778
|
+
j++;
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (depth > 0)
|
|
783
|
+
parts.push(tok.value);
|
|
784
|
+
j++;
|
|
785
|
+
}
|
|
786
|
+
if (depth > 0) {
|
|
787
|
+
throw new ParseError(`Unterminated edge property list; expected ']'`, key.line, key.col);
|
|
788
|
+
}
|
|
789
|
+
props[key.value] = parts.join(" ");
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
props[key.value] = value.value;
|
|
793
|
+
j += 3;
|
|
735
794
|
}
|
|
795
|
+
return props;
|
|
796
|
+
}
|
|
797
|
+
function parseEdge(fromId, connector, rest) {
|
|
798
|
+
const toTok = rest.shift();
|
|
799
|
+
if (!toTok)
|
|
800
|
+
throw new ParseError("Expected edge target", 0, 0);
|
|
801
|
+
const props = parseEdgeProps(rest);
|
|
736
802
|
const dashed = connector.includes("--") ||
|
|
737
803
|
connector.includes(".-") ||
|
|
738
804
|
connector.includes("-.");
|
|
739
805
|
const bidirectional = connector.includes("<") && connector.includes(">");
|
|
806
|
+
const via = parseEdgeWaypoints(props.via, toTok);
|
|
807
|
+
const route = normalizeEdgeRoute(props.route, toTok) ?? (via?.length ? "polyline" : undefined);
|
|
740
808
|
return {
|
|
741
809
|
kind: "edge",
|
|
742
810
|
id: uid("edge"),
|
|
@@ -748,6 +816,8 @@ function parse(src, options = {}) {
|
|
|
748
816
|
labelDy: props["label-dy"] !== undefined ? parseFloat(props["label-dy"]) : undefined,
|
|
749
817
|
fromAnchor: props["anchor-from"],
|
|
750
818
|
toAnchor: props["anchor-to"],
|
|
819
|
+
route,
|
|
820
|
+
via,
|
|
751
821
|
dashed,
|
|
752
822
|
bidirectional,
|
|
753
823
|
style: propsToStyle(props),
|
|
@@ -3681,6 +3751,8 @@ function buildSceneGraph(ast) {
|
|
|
3681
3751
|
labelDy: e.labelDy,
|
|
3682
3752
|
fromAnchor: e.fromAnchor,
|
|
3683
3753
|
toAnchor: e.toAnchor,
|
|
3754
|
+
route: e.route,
|
|
3755
|
+
via: e.via,
|
|
3684
3756
|
dashed: e.dashed ?? false,
|
|
3685
3757
|
bidirectional: e.bidirectional ?? false,
|
|
3686
3758
|
style: e.style ?? {},
|
|
@@ -4282,6 +4354,151 @@ function getConnPoint(src, dstCX, dstCY, anchor) {
|
|
|
4282
4354
|
return anchoredConnPoint(src, anchor, dstCX, dstCY);
|
|
4283
4355
|
}
|
|
4284
4356
|
// ── Group depth (for paint order) ────────────────────────────────────────
|
|
4357
|
+
function segmentLength(a, b) {
|
|
4358
|
+
return Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
4359
|
+
}
|
|
4360
|
+
function compactPolylinePoints(points) {
|
|
4361
|
+
const compacted = [];
|
|
4362
|
+
for (const point of points) {
|
|
4363
|
+
const previous = compacted[compacted.length - 1];
|
|
4364
|
+
if (!previous || segmentLength(previous, point) > 0.01) {
|
|
4365
|
+
compacted.push(point);
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
return compacted;
|
|
4369
|
+
}
|
|
4370
|
+
function polylinePathData(points) {
|
|
4371
|
+
return points
|
|
4372
|
+
.map(([x, y], index) => `${index === 0 ? "M" : "L"} ${x} ${y}`)
|
|
4373
|
+
.join(" ");
|
|
4374
|
+
}
|
|
4375
|
+
function polylineEndpointDirection(points, end) {
|
|
4376
|
+
const step = end === "start" ? 1 : -1;
|
|
4377
|
+
let index = end === "start" ? 0 : points.length - 1;
|
|
4378
|
+
while (index + step >= 0 && index + step < points.length) {
|
|
4379
|
+
const from = points[index];
|
|
4380
|
+
const to = points[index + step];
|
|
4381
|
+
const dx = to[0] - from[0];
|
|
4382
|
+
const dy = to[1] - from[1];
|
|
4383
|
+
const len = Math.hypot(dx, dy);
|
|
4384
|
+
if (len > 0.01) {
|
|
4385
|
+
return end === "start" ? [dx / len, dy / len] : [-dx / len, -dy / len];
|
|
4386
|
+
}
|
|
4387
|
+
index += step;
|
|
4388
|
+
}
|
|
4389
|
+
return [1, 0];
|
|
4390
|
+
}
|
|
4391
|
+
function insetPolylineEndpoints(points, arrowAt, inset) {
|
|
4392
|
+
const next = points.map((point) => [point[0], point[1]]);
|
|
4393
|
+
if (next.length < 2)
|
|
4394
|
+
return next;
|
|
4395
|
+
if (arrowAt === "start" || arrowAt === "both") {
|
|
4396
|
+
const [dx, dy] = polylineEndpointDirection(next, "start");
|
|
4397
|
+
next[0] = [next[0][0] + dx * inset, next[0][1] + dy * inset];
|
|
4398
|
+
}
|
|
4399
|
+
if (arrowAt === "end" || arrowAt === "both") {
|
|
4400
|
+
const [dx, dy] = polylineEndpointDirection(next, "end");
|
|
4401
|
+
const last = next.length - 1;
|
|
4402
|
+
next[last] = [next[last][0] - dx * inset, next[last][1] - dy * inset];
|
|
4403
|
+
}
|
|
4404
|
+
return compactPolylinePoints(next);
|
|
4405
|
+
}
|
|
4406
|
+
function polylineLabelPosition(points, offset, dx = 0, dy = 0) {
|
|
4407
|
+
if (points.length < 2) {
|
|
4408
|
+
const [x, y] = points[0] ?? [0, 0];
|
|
4409
|
+
return { x: x + dx, y: y + dy };
|
|
4410
|
+
}
|
|
4411
|
+
const lengths = points.slice(1).map((point, index) => segmentLength(points[index], point));
|
|
4412
|
+
const total = lengths.reduce((sum, value) => sum + value, 0);
|
|
4413
|
+
if (total <= 0.01) {
|
|
4414
|
+
const [x, y] = points[0];
|
|
4415
|
+
return { x: x + dx, y: y + dy };
|
|
4416
|
+
}
|
|
4417
|
+
let travelled = 0;
|
|
4418
|
+
const target = total / 2;
|
|
4419
|
+
for (let index = 0; index < lengths.length; index += 1) {
|
|
4420
|
+
const length = lengths[index];
|
|
4421
|
+
if (travelled + length >= target) {
|
|
4422
|
+
const from = points[index];
|
|
4423
|
+
const to = points[index + 1];
|
|
4424
|
+
const t = length > 0 ? (target - travelled) / length : 0;
|
|
4425
|
+
const ux = (to[0] - from[0]) / length;
|
|
4426
|
+
const uy = (to[1] - from[1]) / length;
|
|
4427
|
+
return {
|
|
4428
|
+
x: from[0] + (to[0] - from[0]) * t - uy * offset + dx,
|
|
4429
|
+
y: from[1] + (to[1] - from[1]) * t + ux * offset + dy,
|
|
4430
|
+
};
|
|
4431
|
+
}
|
|
4432
|
+
travelled += length;
|
|
4433
|
+
}
|
|
4434
|
+
const [x, y] = points[points.length - 1];
|
|
4435
|
+
return { x: x + dx, y: y + dy };
|
|
4436
|
+
}
|
|
4437
|
+
function rectBoundaryPoint(entity, point, direction) {
|
|
4438
|
+
const [px, py] = point;
|
|
4439
|
+
const [dx, dy] = direction;
|
|
4440
|
+
const candidates = [];
|
|
4441
|
+
const minX = entity.x;
|
|
4442
|
+
const maxX = entity.x + entity.w;
|
|
4443
|
+
const minY = entity.y;
|
|
4444
|
+
const maxY = entity.y + entity.h;
|
|
4445
|
+
const epsilon = 0.01;
|
|
4446
|
+
if (Math.abs(dx) > epsilon) {
|
|
4447
|
+
candidates.push((minX - px) / dx, (maxX - px) / dx);
|
|
4448
|
+
}
|
|
4449
|
+
if (Math.abs(dy) > epsilon) {
|
|
4450
|
+
candidates.push((minY - py) / dy, (maxY - py) / dy);
|
|
4451
|
+
}
|
|
4452
|
+
const valid = candidates
|
|
4453
|
+
.filter((t) => t >= -epsilon)
|
|
4454
|
+
.map((t) => ({
|
|
4455
|
+
t: Math.max(0, t),
|
|
4456
|
+
x: px + dx * t,
|
|
4457
|
+
y: py + dy * t,
|
|
4458
|
+
}))
|
|
4459
|
+
.filter(({ x, y }) => x >= minX - epsilon &&
|
|
4460
|
+
x <= maxX + epsilon &&
|
|
4461
|
+
y >= minY - epsilon &&
|
|
4462
|
+
y <= maxY + epsilon)
|
|
4463
|
+
.sort((a, b) => a.t - b.t);
|
|
4464
|
+
const hit = valid[0];
|
|
4465
|
+
return hit ? [hit.x, hit.y] : point;
|
|
4466
|
+
}
|
|
4467
|
+
function ellipseBoundaryPoint(entity, point, direction) {
|
|
4468
|
+
const [px, py] = point;
|
|
4469
|
+
const [dx, dy] = direction;
|
|
4470
|
+
const cx = entity.x + entity.w / 2;
|
|
4471
|
+
const cy = entity.y + entity.h / 2;
|
|
4472
|
+
const rx = Math.max(1, entity.w * 0.44);
|
|
4473
|
+
const ry = Math.max(1, entity.h * 0.44);
|
|
4474
|
+
const x0 = px - cx;
|
|
4475
|
+
const y0 = py - cy;
|
|
4476
|
+
const a = (dx * dx) / (rx * rx) + (dy * dy) / (ry * ry);
|
|
4477
|
+
const b = 2 * ((x0 * dx) / (rx * rx) + (y0 * dy) / (ry * ry));
|
|
4478
|
+
const c = (x0 * x0) / (rx * rx) + (y0 * y0) / (ry * ry) - 1;
|
|
4479
|
+
const disc = b * b - 4 * a * c;
|
|
4480
|
+
if (a <= 0 || disc < 0)
|
|
4481
|
+
return point;
|
|
4482
|
+
const sqrt = Math.sqrt(disc);
|
|
4483
|
+
const hits = [(-b - sqrt) / (2 * a), (-b + sqrt) / (2 * a)]
|
|
4484
|
+
.filter((t) => t >= -0.01)
|
|
4485
|
+
.sort((left, right) => left - right);
|
|
4486
|
+
const t = Math.max(0, hits[0] ?? 0);
|
|
4487
|
+
return [px + dx * t, py + dy * t];
|
|
4488
|
+
}
|
|
4489
|
+
function polylineArrowTipPoint(entity, points, end) {
|
|
4490
|
+
const point = end === "start" ? points[0] : points[points.length - 1];
|
|
4491
|
+
if (!point)
|
|
4492
|
+
return [0, 0];
|
|
4493
|
+
const [dx, dy] = polylineEndpointDirection(points, end);
|
|
4494
|
+
const outward = end === "start" ? [dx, dy] : [-dx, -dy];
|
|
4495
|
+
if (Math.hypot(outward[0], outward[1]) <= 0.01)
|
|
4496
|
+
return point;
|
|
4497
|
+
if (entity.shape === "circle") {
|
|
4498
|
+
return ellipseBoundaryPoint(entity, point, outward);
|
|
4499
|
+
}
|
|
4500
|
+
return rectBoundaryPoint(entity, point, outward);
|
|
4501
|
+
}
|
|
4285
4502
|
function groupDepth(g, gm) {
|
|
4286
4503
|
let d = 0;
|
|
4287
4504
|
let cur = g;
|
|
@@ -5242,6 +5459,31 @@ function rectConnPoint(rx, ry, rw, rh, ox, oy) {
|
|
|
5242
5459
|
const t = Math.min(tx, ty);
|
|
5243
5460
|
return [cx + t * dx, cy + t * dy];
|
|
5244
5461
|
}
|
|
5462
|
+
function distance$1(a, b) {
|
|
5463
|
+
return Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
5464
|
+
}
|
|
5465
|
+
function compactEdgePoints(points) {
|
|
5466
|
+
const compacted = [];
|
|
5467
|
+
for (const point of points) {
|
|
5468
|
+
const previous = compacted[compacted.length - 1];
|
|
5469
|
+
if (!previous || distance$1(previous, point) > 0.01) {
|
|
5470
|
+
compacted.push(point);
|
|
5471
|
+
}
|
|
5472
|
+
}
|
|
5473
|
+
return compacted;
|
|
5474
|
+
}
|
|
5475
|
+
function orthogonalEdgePoints(start, end) {
|
|
5476
|
+
if (Math.abs(start[0] - end[0]) < 0.01 || Math.abs(start[1] - end[1]) < 0.01) {
|
|
5477
|
+
return [start, end];
|
|
5478
|
+
}
|
|
5479
|
+
const midX = (start[0] + end[0]) / 2;
|
|
5480
|
+
return compactEdgePoints([
|
|
5481
|
+
start,
|
|
5482
|
+
[midX, start[1]],
|
|
5483
|
+
[midX, end[1]],
|
|
5484
|
+
end,
|
|
5485
|
+
]);
|
|
5486
|
+
}
|
|
5245
5487
|
function routeEdges(sg) {
|
|
5246
5488
|
const nm = nodeMap(sg);
|
|
5247
5489
|
const tm = tableMap(sg);
|
|
@@ -5271,10 +5513,17 @@ function routeEdges(sg) {
|
|
|
5271
5513
|
}
|
|
5272
5514
|
const dstCX = dst.x + dst.w / 2, dstCY = dst.y + dst.h / 2;
|
|
5273
5515
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
5274
|
-
e.
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5516
|
+
const start = anchoredConnPoint(src, e.fromAnchor, dstCX, dstCY);
|
|
5517
|
+
const end = anchoredConnPoint(dst, e.toAnchor, srcCX, srcCY);
|
|
5518
|
+
if (e.via?.length) {
|
|
5519
|
+
e.points = compactEdgePoints([start, ...e.via, end]);
|
|
5520
|
+
}
|
|
5521
|
+
else if (e.route === "orthogonal") {
|
|
5522
|
+
e.points = orthogonalEdgePoints(start, end);
|
|
5523
|
+
}
|
|
5524
|
+
else {
|
|
5525
|
+
e.points = [start, end];
|
|
5526
|
+
}
|
|
5278
5527
|
}
|
|
5279
5528
|
}
|
|
5280
5529
|
function computeBounds(sg, margin) {
|
|
@@ -5284,6 +5533,7 @@ function computeBounds(sg, margin) {
|
|
|
5284
5533
|
...sg.tables.map((t) => t.x + t.w),
|
|
5285
5534
|
...sg.charts.map((c) => c.x + c.w),
|
|
5286
5535
|
...sg.markdowns.map((m) => m.x + m.w),
|
|
5536
|
+
...sg.edges.flatMap((e) => (e.points ?? []).map(([x]) => x)),
|
|
5287
5537
|
];
|
|
5288
5538
|
const allY = [
|
|
5289
5539
|
...sg.nodes.map((n) => n.y + n.h),
|
|
@@ -5291,6 +5541,7 @@ function computeBounds(sg, margin) {
|
|
|
5291
5541
|
...sg.tables.map((t) => t.y + t.h),
|
|
5292
5542
|
...sg.charts.map((c) => c.y + c.h),
|
|
5293
5543
|
...sg.markdowns.map((m) => m.y + m.h),
|
|
5544
|
+
...sg.edges.flatMap((e) => (e.points ?? []).map(([, y]) => y)),
|
|
5294
5545
|
];
|
|
5295
5546
|
const autoWidth = (allX.length ? Math.max(...allX) : 400) + margin;
|
|
5296
5547
|
const autoHeight = (allY.length ? Math.max(...allY) : 300) + margin;
|
|
@@ -8278,6 +8529,36 @@ function setParentGroupData(el, groupId) {
|
|
|
8278
8529
|
if (groupId)
|
|
8279
8530
|
el.dataset.parentGroup = groupId;
|
|
8280
8531
|
}
|
|
8532
|
+
function resolveEdgeEndpointKind(id, nm, tm, gm, cm) {
|
|
8533
|
+
if (nm.has(id))
|
|
8534
|
+
return "node";
|
|
8535
|
+
if (gm.has(id))
|
|
8536
|
+
return "group";
|
|
8537
|
+
if (tm.has(id))
|
|
8538
|
+
return "table";
|
|
8539
|
+
if (cm.has(id))
|
|
8540
|
+
return "chart";
|
|
8541
|
+
return null;
|
|
8542
|
+
}
|
|
8543
|
+
function collectEdgeGroupLineage(endpointId, endpointKind, parentGroups) {
|
|
8544
|
+
const lineage = [];
|
|
8545
|
+
let groupId = endpointKind === "group"
|
|
8546
|
+
? endpointId
|
|
8547
|
+
: parentGroups.get(`${endpointKind}:${endpointId}`);
|
|
8548
|
+
while (groupId) {
|
|
8549
|
+
lineage.push(groupId);
|
|
8550
|
+
groupId = parentGroups.get(`group:${groupId}`);
|
|
8551
|
+
}
|
|
8552
|
+
return lineage;
|
|
8553
|
+
}
|
|
8554
|
+
function resolveEdgeParentGroupId(fromId, toId, nm, tm, gm, cm, parentGroups) {
|
|
8555
|
+
const fromKind = resolveEdgeEndpointKind(fromId, nm, tm, gm, cm);
|
|
8556
|
+
const toKind = resolveEdgeEndpointKind(toId, nm, tm, gm, cm);
|
|
8557
|
+
if (!fromKind || !toKind)
|
|
8558
|
+
return undefined;
|
|
8559
|
+
const toLineage = new Set(collectEdgeGroupLineage(toId, toKind, parentGroups));
|
|
8560
|
+
return collectEdgeGroupLineage(fromId, fromKind, parentGroups).find((groupId) => toLineage.has(groupId));
|
|
8561
|
+
}
|
|
8281
8562
|
// ── Node shapes ───────────────────────────────────────────────────────────
|
|
8282
8563
|
function renderShape$1(rc, n, palette) {
|
|
8283
8564
|
const s = n.style ?? {};
|
|
@@ -8434,19 +8715,16 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8434
8715
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
8435
8716
|
const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
|
|
8436
8717
|
const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
|
|
8718
|
+
const points = compactPolylinePoints(e.points?.length && e.points.length >= 2 ? e.points : [[x1, y1], [x2, y2]]);
|
|
8437
8719
|
const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
|
|
8720
|
+
setParentGroupData(eg, resolveEdgeParentGroupId(e.from, e.to, nm, tm, gmMap, cm, parentGroups));
|
|
8438
8721
|
if (e.style?.opacity != null)
|
|
8439
8722
|
eg.setAttribute("opacity", String(e.style.opacity));
|
|
8440
|
-
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
8441
|
-
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
8442
8723
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
8443
8724
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
8444
8725
|
const HEAD = EDGE.headInset;
|
|
8445
|
-
const
|
|
8446
|
-
const
|
|
8447
|
-
const sx2 = arrowAt === "end" || arrowAt === "both" ? x2 - nx * HEAD : x2;
|
|
8448
|
-
const sy2 = arrowAt === "end" || arrowAt === "both" ? y2 - ny * HEAD : y2;
|
|
8449
|
-
const shaft = rc.line(sx1, sy1, sx2, sy2, {
|
|
8726
|
+
const shaftPoints = insetPolylineEndpoints(points, arrowAt, HEAD);
|
|
8727
|
+
const shaft = rc.path(polylinePathData(shaftPoints), {
|
|
8450
8728
|
...BASE_ROUGH,
|
|
8451
8729
|
roughness: 0.9,
|
|
8452
8730
|
seed: hashStr$3(e.from + e.to),
|
|
@@ -8457,18 +8735,21 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8457
8735
|
shaft.setAttribute("data-edge-role", "shaft");
|
|
8458
8736
|
eg.appendChild(shaft);
|
|
8459
8737
|
if (arrowAt === "end" || arrowAt === "both") {
|
|
8460
|
-
const
|
|
8738
|
+
const [endDx, endDy] = polylineEndpointDirection(points, "end");
|
|
8739
|
+
const [endX, endY] = polylineArrowTipPoint(dst, points, "end");
|
|
8740
|
+
const endHead = arrowHead(rc, endX, endY, Math.atan2(endDy, endDx), ecol, hashStr$3(e.to));
|
|
8461
8741
|
endHead.setAttribute("data-edge-role", "head");
|
|
8462
8742
|
eg.appendChild(endHead);
|
|
8463
8743
|
}
|
|
8464
8744
|
if (arrowAt === "start" || arrowAt === "both") {
|
|
8465
|
-
const
|
|
8745
|
+
const [startDx, startDy] = polylineEndpointDirection(points, "start");
|
|
8746
|
+
const [startX, startY] = polylineArrowTipPoint(src, points, "start");
|
|
8747
|
+
const startHead = arrowHead(rc, startX, startY, Math.atan2(-startDy, -startDx), ecol, hashStr$3(e.from + "back"));
|
|
8466
8748
|
startHead.setAttribute("data-edge-role", "head");
|
|
8467
8749
|
eg.appendChild(startHead);
|
|
8468
8750
|
}
|
|
8469
8751
|
if (e.label) {
|
|
8470
|
-
const
|
|
8471
|
-
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset + (e.labelDy ?? 0);
|
|
8752
|
+
const { x: mx, y: my } = polylineLabelPosition(points, EDGE.labelOffset, e.labelDx ?? 0, e.labelDy ?? 0);
|
|
8472
8753
|
const tw = Math.max(e.label.length * 7 + 12, 36);
|
|
8473
8754
|
const bg = se("rect");
|
|
8474
8755
|
bg.setAttribute("x", String(mx - tw / 2));
|
|
@@ -9180,31 +9461,31 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
9180
9461
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
9181
9462
|
const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
|
|
9182
9463
|
const [x2, y2] = getConnPoint(dst, srcCX, srcCY, e.toAnchor);
|
|
9464
|
+
const points = compactPolylinePoints(e.points?.length && e.points.length >= 2 ? e.points : [[x1, y1], [x2, y2]]);
|
|
9183
9465
|
if (e.style?.opacity != null)
|
|
9184
9466
|
ctx.globalAlpha = Number(e.style.opacity);
|
|
9185
9467
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
9186
9468
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
9187
|
-
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
9188
|
-
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
9189
9469
|
const HEAD = EDGE.headInset;
|
|
9190
|
-
const
|
|
9191
|
-
|
|
9192
|
-
const sx2 = arrowAt === 'end' || arrowAt === 'both' ? x2 - nx * HEAD : x2;
|
|
9193
|
-
const sy2 = arrowAt === 'end' || arrowAt === 'both' ? y2 - ny * HEAD : y2;
|
|
9194
|
-
rc.line(sx1, sy1, sx2, sy2, {
|
|
9470
|
+
const shaftPoints = insetPolylineEndpoints(points, arrowAt, HEAD);
|
|
9471
|
+
rc.path(polylinePathData(shaftPoints), {
|
|
9195
9472
|
...R, roughness: 0.9, seed: hashStr$3(e.from + e.to),
|
|
9196
9473
|
stroke: ecol,
|
|
9197
9474
|
strokeWidth: Number(e.style?.strokeWidth ?? 1.6),
|
|
9198
9475
|
...(dashed ? { strokeLineDash: EDGE.dashPattern } : {}),
|
|
9199
9476
|
});
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9477
|
+
if (arrowAt === 'end' || arrowAt === 'both') {
|
|
9478
|
+
const [endDx, endDy] = polylineEndpointDirection(points, 'end');
|
|
9479
|
+
const [endX, endY] = polylineArrowTipPoint(dst, points, 'end');
|
|
9480
|
+
drawArrowHead(rc, endX, endY, Math.atan2(endDy, endDx), ecol, hashStr$3(e.to));
|
|
9481
|
+
}
|
|
9482
|
+
if (arrowAt === 'start' || arrowAt === 'both') {
|
|
9483
|
+
const [startDx, startDy] = polylineEndpointDirection(points, 'start');
|
|
9484
|
+
const [startX, startY] = polylineArrowTipPoint(src, points, 'start');
|
|
9485
|
+
drawArrowHead(rc, startX, startY, Math.atan2(-startDy, -startDx), ecol, hashStr$3(e.from + 'back'));
|
|
9486
|
+
}
|
|
9205
9487
|
if (e.label) {
|
|
9206
|
-
const
|
|
9207
|
-
const my = (y1 + y2) / 2 + nx * EDGE.labelOffset + (e.labelDy ?? 0);
|
|
9488
|
+
const { x: mx, y: my } = polylineLabelPosition(points, EDGE.labelOffset, e.labelDx ?? 0, e.labelDy ?? 0);
|
|
9208
9489
|
// ── Edge label: font, font-size, letter-spacing ──
|
|
9209
9490
|
// always center-anchored (single line)
|
|
9210
9491
|
const eFontSize = Number(e.style?.fontSize ?? EDGE.labelFontSize);
|
|
@@ -9481,7 +9762,7 @@ const getTableEl = (svg, id) => getEl(svg, `table-${id}`);
|
|
|
9481
9762
|
const getNoteEl = (svg, id) => getEl(svg, `note-${id}`);
|
|
9482
9763
|
const getChartEl = (svg, id) => getEl(svg, `chart-${id}`);
|
|
9483
9764
|
const getMarkdownEl = (svg, id) => getEl(svg, `markdown-${id}`);
|
|
9484
|
-
const POSITIONABLE_SELECTOR = ".ng, .gg, .tg, .ntg, .cg, .mdg";
|
|
9765
|
+
const POSITIONABLE_SELECTOR = ".ng, .gg, .tg, .ntg, .cg, .eg, .mdg";
|
|
9485
9766
|
function resolveNonEdgeDrawEl(svg, target) {
|
|
9486
9767
|
return (getGroupEl(svg, target) ??
|
|
9487
9768
|
getTableEl(svg, target) ??
|
|
@@ -9901,7 +10182,7 @@ function animateShapeDraw(el, strokeDur = ANIMATION.nodeStrokeDur, stag = ANIMAT
|
|
|
9901
10182
|
}));
|
|
9902
10183
|
}
|
|
9903
10184
|
// ── Edge draw helpers ─────────────────────────────────────
|
|
9904
|
-
const EDGE_SHAFT_SELECTOR = '[data-edge-role="shaft"] path';
|
|
10185
|
+
const EDGE_SHAFT_SELECTOR = '[data-edge-role="shaft"] path, path[data-edge-role="shaft"]';
|
|
9905
10186
|
const EDGE_DECOR_SELECTOR = '[data-edge-role="head"], [data-edge-role="label"], [data-edge-role="label-bg"]';
|
|
9906
10187
|
function edgeShaftPaths(el) {
|
|
9907
10188
|
return Array.from(el.querySelectorAll(EDGE_SHAFT_SELECTOR));
|
|
@@ -10075,8 +10356,16 @@ class AnimationController {
|
|
|
10075
10356
|
_buildDrawStepIndex() {
|
|
10076
10357
|
const drawStepIndexByElementId = new Map();
|
|
10077
10358
|
forEachPlaybackStep(this.steps, (step, stepIndex) => {
|
|
10078
|
-
if (step.action !== "draw"
|
|
10359
|
+
if (step.action !== "draw")
|
|
10360
|
+
return;
|
|
10361
|
+
const edge = parseEdgeTarget(step.target);
|
|
10362
|
+
if (edge) {
|
|
10363
|
+
const edgeEl = getEdgeEl(this.svg, edge.from, edge.to);
|
|
10364
|
+
if (edgeEl && !drawStepIndexByElementId.has(edgeEl.id)) {
|
|
10365
|
+
drawStepIndexByElementId.set(edgeEl.id, stepIndex);
|
|
10366
|
+
}
|
|
10079
10367
|
return;
|
|
10368
|
+
}
|
|
10080
10369
|
const el = resolveNonEdgeDrawEl(this.svg, step.target);
|
|
10081
10370
|
if (el && !drawStepIndexByElementId.has(el.id)) {
|
|
10082
10371
|
drawStepIndexByElementId.set(el.id, stepIndex);
|
|
@@ -10776,6 +11065,7 @@ class AnimationController {
|
|
|
10776
11065
|
const el = getEdgeEl(this.svg, edge.from, edge.to);
|
|
10777
11066
|
if (!el)
|
|
10778
11067
|
return;
|
|
11068
|
+
showDrawEl(el);
|
|
10779
11069
|
if (silent) {
|
|
10780
11070
|
revealEdgeInstant(el);
|
|
10781
11071
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
|
@@ -11389,11 +11679,12 @@ const ANIMATION_CSS = `
|
|
|
11389
11679
|
.cg.faded, .eg.faded, .mdg.faded { opacity: 0.22; }
|
|
11390
11680
|
|
|
11391
11681
|
.ng.hidden { opacity: 0; pointer-events: none; }
|
|
11392
|
-
.gg.gg-hidden { opacity: 0; }
|
|
11393
|
-
.tg.gg-hidden { opacity: 0; }
|
|
11394
|
-
.ntg.gg-hidden { opacity: 0; }
|
|
11395
|
-
.cg.gg-hidden { opacity: 0; }
|
|
11396
|
-
.
|
|
11682
|
+
.gg.gg-hidden { opacity: 0; }
|
|
11683
|
+
.tg.gg-hidden { opacity: 0; }
|
|
11684
|
+
.ntg.gg-hidden { opacity: 0; }
|
|
11685
|
+
.cg.gg-hidden { opacity: 0; }
|
|
11686
|
+
.eg.gg-hidden { opacity: 0; }
|
|
11687
|
+
.mdg.gg-hidden { opacity: 0; }
|
|
11397
11688
|
|
|
11398
11689
|
/* narration caption */
|
|
11399
11690
|
.skm-caption { pointer-events: none; user-select: none; }
|