sketchmark 1.3.7 → 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 +21 -8
- package/dist/ast/types.d.ts +4 -0
- package/dist/ast/types.d.ts.map +1 -1
- package/dist/index.cjs +292 -42
- 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 +292 -42
- 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 +292 -42
- 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;
|
|
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;
|
|
734
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;
|
|
@@ -8464,20 +8715,16 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8464
8715
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
8465
8716
|
const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
|
|
8466
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]]);
|
|
8467
8719
|
const eg = mkGroup(`edge-${e.from}-${e.to}`, "eg");
|
|
8468
8720
|
setParentGroupData(eg, resolveEdgeParentGroupId(e.from, e.to, nm, tm, gmMap, cm, parentGroups));
|
|
8469
8721
|
if (e.style?.opacity != null)
|
|
8470
8722
|
eg.setAttribute("opacity", String(e.style.opacity));
|
|
8471
|
-
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
8472
|
-
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
8473
8723
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
8474
8724
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
8475
8725
|
const HEAD = EDGE.headInset;
|
|
8476
|
-
const
|
|
8477
|
-
const
|
|
8478
|
-
const sx2 = arrowAt === "end" || arrowAt === "both" ? x2 - nx * HEAD : x2;
|
|
8479
|
-
const sy2 = arrowAt === "end" || arrowAt === "both" ? y2 - ny * HEAD : y2;
|
|
8480
|
-
const shaft = rc.line(sx1, sy1, sx2, sy2, {
|
|
8726
|
+
const shaftPoints = insetPolylineEndpoints(points, arrowAt, HEAD);
|
|
8727
|
+
const shaft = rc.path(polylinePathData(shaftPoints), {
|
|
8481
8728
|
...BASE_ROUGH,
|
|
8482
8729
|
roughness: 0.9,
|
|
8483
8730
|
seed: hashStr$3(e.from + e.to),
|
|
@@ -8488,18 +8735,21 @@ function renderToSVG(sg, container, options = {}) {
|
|
|
8488
8735
|
shaft.setAttribute("data-edge-role", "shaft");
|
|
8489
8736
|
eg.appendChild(shaft);
|
|
8490
8737
|
if (arrowAt === "end" || arrowAt === "both") {
|
|
8491
|
-
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));
|
|
8492
8741
|
endHead.setAttribute("data-edge-role", "head");
|
|
8493
8742
|
eg.appendChild(endHead);
|
|
8494
8743
|
}
|
|
8495
8744
|
if (arrowAt === "start" || arrowAt === "both") {
|
|
8496
|
-
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"));
|
|
8497
8748
|
startHead.setAttribute("data-edge-role", "head");
|
|
8498
8749
|
eg.appendChild(startHead);
|
|
8499
8750
|
}
|
|
8500
8751
|
if (e.label) {
|
|
8501
|
-
const
|
|
8502
|
-
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);
|
|
8503
8753
|
const tw = Math.max(e.label.length * 7 + 12, 36);
|
|
8504
8754
|
const bg = se("rect");
|
|
8505
8755
|
bg.setAttribute("x", String(mx - tw / 2));
|
|
@@ -9211,31 +9461,31 @@ function renderToCanvas(sg, canvas, options = {}) {
|
|
|
9211
9461
|
const srcCX = src.x + src.w / 2, srcCY = src.y + src.h / 2;
|
|
9212
9462
|
const [x1, y1] = getConnPoint(src, dstCX, dstCY, e.fromAnchor);
|
|
9213
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]]);
|
|
9214
9465
|
if (e.style?.opacity != null)
|
|
9215
9466
|
ctx.globalAlpha = Number(e.style.opacity);
|
|
9216
9467
|
const ecol = String(e.style?.stroke ?? palette.edgeStroke);
|
|
9217
9468
|
const { arrowAt, dashed } = connMeta(e.connector);
|
|
9218
|
-
const len = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) || 1;
|
|
9219
|
-
const nx = (x2 - x1) / len, ny = (y2 - y1) / len;
|
|
9220
9469
|
const HEAD = EDGE.headInset;
|
|
9221
|
-
const
|
|
9222
|
-
|
|
9223
|
-
const sx2 = arrowAt === 'end' || arrowAt === 'both' ? x2 - nx * HEAD : x2;
|
|
9224
|
-
const sy2 = arrowAt === 'end' || arrowAt === 'both' ? y2 - ny * HEAD : y2;
|
|
9225
|
-
rc.line(sx1, sy1, sx2, sy2, {
|
|
9470
|
+
const shaftPoints = insetPolylineEndpoints(points, arrowAt, HEAD);
|
|
9471
|
+
rc.path(polylinePathData(shaftPoints), {
|
|
9226
9472
|
...R, roughness: 0.9, seed: hashStr$3(e.from + e.to),
|
|
9227
9473
|
stroke: ecol,
|
|
9228
9474
|
strokeWidth: Number(e.style?.strokeWidth ?? 1.6),
|
|
9229
9475
|
...(dashed ? { strokeLineDash: EDGE.dashPattern } : {}),
|
|
9230
9476
|
});
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
|
|
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
|
+
}
|
|
9236
9487
|
if (e.label) {
|
|
9237
|
-
const
|
|
9238
|
-
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);
|
|
9239
9489
|
// ── Edge label: font, font-size, letter-spacing ──
|
|
9240
9490
|
// always center-anchored (single line)
|
|
9241
9491
|
const eFontSize = Number(e.style?.fontSize ?? EDGE.labelFontSize);
|
|
@@ -9932,7 +10182,7 @@ function animateShapeDraw(el, strokeDur = ANIMATION.nodeStrokeDur, stag = ANIMAT
|
|
|
9932
10182
|
}));
|
|
9933
10183
|
}
|
|
9934
10184
|
// ── Edge draw helpers ─────────────────────────────────────
|
|
9935
|
-
const EDGE_SHAFT_SELECTOR = '[data-edge-role="shaft"] path';
|
|
10185
|
+
const EDGE_SHAFT_SELECTOR = '[data-edge-role="shaft"] path, path[data-edge-role="shaft"]';
|
|
9936
10186
|
const EDGE_DECOR_SELECTOR = '[data-edge-role="head"], [data-edge-role="label"], [data-edge-role="label-bg"]';
|
|
9937
10187
|
function edgeShaftPaths(el) {
|
|
9938
10188
|
return Array.from(el.querySelectorAll(EDGE_SHAFT_SELECTOR));
|