@spectratools/graphic-designer-cli 0.4.0 → 0.7.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 +32 -2
- package/dist/cli.js +728 -61
- package/dist/index.d.ts +115 -5
- package/dist/index.js +751 -61
- package/dist/qa.d.ts +14 -3
- package/dist/qa.js +263 -12
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +466 -54
- package/dist/{spec.schema-BUTof436.d.ts → spec.schema-BeFz_nk1.d.ts} +1820 -193
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +96 -9
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -115,9 +115,9 @@ function drawRoundedRect(ctx, rect, radius, fill, stroke) {
|
|
|
115
115
|
roundRectPath(ctx, rect, radius);
|
|
116
116
|
fillAndStroke(ctx, fill, stroke);
|
|
117
117
|
}
|
|
118
|
-
function drawCircle(ctx,
|
|
118
|
+
function drawCircle(ctx, center, radius, fill, stroke) {
|
|
119
119
|
ctx.beginPath();
|
|
120
|
-
ctx.arc(
|
|
120
|
+
ctx.arc(center.x, center.y, Math.max(0, radius), 0, Math.PI * 2);
|
|
121
121
|
ctx.closePath();
|
|
122
122
|
fillAndStroke(ctx, fill, stroke);
|
|
123
123
|
}
|
|
@@ -339,6 +339,10 @@ function relativeLuminance(hexColor) {
|
|
|
339
339
|
const b = srgbToLinear(rgb.b);
|
|
340
340
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
341
341
|
}
|
|
342
|
+
function withAlpha(hexColor, opacity) {
|
|
343
|
+
const rgb = parseHexColor(hexColor);
|
|
344
|
+
return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
|
|
345
|
+
}
|
|
342
346
|
function blendColorWithOpacity(foreground, background, opacity) {
|
|
343
347
|
const fg = parseHexColor(foreground);
|
|
344
348
|
const bg = parseHexColor(background);
|
|
@@ -441,15 +445,34 @@ function renderFlowNode(ctx, node, bounds, theme) {
|
|
|
441
445
|
const badgeBackground = node.badgeBackground ?? borderColor ?? theme.accent;
|
|
442
446
|
ctx.save();
|
|
443
447
|
ctx.lineWidth = borderWidth;
|
|
448
|
+
if (node.shadow) {
|
|
449
|
+
const shadowColor = node.shadow.color ?? borderColor ?? theme.accent;
|
|
450
|
+
ctx.shadowColor = withAlpha(shadowColor, node.shadow.opacity);
|
|
451
|
+
ctx.shadowBlur = node.shadow.blur;
|
|
452
|
+
ctx.shadowOffsetX = node.shadow.offsetX;
|
|
453
|
+
ctx.shadowOffsetY = node.shadow.offsetY;
|
|
454
|
+
}
|
|
444
455
|
if (fillOpacity < 1) {
|
|
445
456
|
ctx.globalAlpha = node.opacity * fillOpacity;
|
|
446
457
|
drawNodeShape(ctx, node.shape, bounds, fillColor, void 0, cornerRadius);
|
|
458
|
+
if (node.shadow) {
|
|
459
|
+
ctx.shadowColor = "transparent";
|
|
460
|
+
ctx.shadowBlur = 0;
|
|
461
|
+
ctx.shadowOffsetX = 0;
|
|
462
|
+
ctx.shadowOffsetY = 0;
|
|
463
|
+
}
|
|
447
464
|
ctx.globalAlpha = node.opacity;
|
|
448
465
|
drawNodeShape(ctx, node.shape, bounds, "rgba(0,0,0,0)", borderColor, cornerRadius);
|
|
449
466
|
} else {
|
|
450
467
|
ctx.globalAlpha = node.opacity;
|
|
451
468
|
drawNodeShape(ctx, node.shape, bounds, fillColor, borderColor, cornerRadius);
|
|
452
469
|
}
|
|
470
|
+
if (node.shadow) {
|
|
471
|
+
ctx.shadowColor = "transparent";
|
|
472
|
+
ctx.shadowBlur = 0;
|
|
473
|
+
ctx.shadowOffsetX = 0;
|
|
474
|
+
ctx.shadowOffsetY = 0;
|
|
475
|
+
}
|
|
453
476
|
const headingFont = resolveFont(theme.fonts.heading, "heading");
|
|
454
477
|
const bodyFont = resolveFont(theme.fonts.body, "body");
|
|
455
478
|
const monoFont = resolveFont(theme.fonts.mono, "mono");
|
|
@@ -1121,7 +1144,7 @@ function parseHexColor2(color) {
|
|
|
1121
1144
|
a: normalized.length === 8 ? parseChannel2(6) / 255 : 1
|
|
1122
1145
|
};
|
|
1123
1146
|
}
|
|
1124
|
-
function
|
|
1147
|
+
function withAlpha2(color, alpha) {
|
|
1125
1148
|
const parsed = parseHexColor2(color);
|
|
1126
1149
|
const effectiveAlpha = clamp01(parsed.a * alpha);
|
|
1127
1150
|
return `rgba(${parsed.r}, ${parsed.g}, ${parsed.b}, ${effectiveAlpha})`;
|
|
@@ -1178,9 +1201,9 @@ function drawVignette(ctx, width, height, intensity = 0.3, color = "#000000") {
|
|
|
1178
1201
|
centerY,
|
|
1179
1202
|
outerRadius
|
|
1180
1203
|
);
|
|
1181
|
-
vignette.addColorStop(0,
|
|
1182
|
-
vignette.addColorStop(0.6,
|
|
1183
|
-
vignette.addColorStop(1,
|
|
1204
|
+
vignette.addColorStop(0, withAlpha2(color, 0));
|
|
1205
|
+
vignette.addColorStop(0.6, withAlpha2(color, 0));
|
|
1206
|
+
vignette.addColorStop(1, withAlpha2(color, clamp01(intensity)));
|
|
1184
1207
|
ctx.save();
|
|
1185
1208
|
ctx.fillStyle = vignette;
|
|
1186
1209
|
ctx.fillRect(0, 0, width, height);
|
|
@@ -1311,12 +1334,12 @@ var MACOS_DOTS = [
|
|
|
1311
1334
|
{ fill: "#27C93F", stroke: "#1AAB29" }
|
|
1312
1335
|
];
|
|
1313
1336
|
function drawMacosDots(ctx, x, y) {
|
|
1314
|
-
for (const [index,
|
|
1337
|
+
for (const [index, dot2] of MACOS_DOTS.entries()) {
|
|
1315
1338
|
ctx.beginPath();
|
|
1316
1339
|
ctx.arc(x + index * DOT_SPACING, y, DOT_RADIUS, 0, Math.PI * 2);
|
|
1317
1340
|
ctx.closePath();
|
|
1318
|
-
ctx.fillStyle =
|
|
1319
|
-
ctx.strokeStyle =
|
|
1341
|
+
ctx.fillStyle = dot2.fill;
|
|
1342
|
+
ctx.strokeStyle = dot2.stroke;
|
|
1320
1343
|
ctx.lineWidth = DOT_STROKE_WIDTH;
|
|
1321
1344
|
ctx.fill();
|
|
1322
1345
|
ctx.stroke();
|
|
@@ -1965,25 +1988,203 @@ function drawOrthogonalPath(ctx, from, to, style) {
|
|
|
1965
1988
|
}
|
|
1966
1989
|
|
|
1967
1990
|
// src/renderers/connection.ts
|
|
1968
|
-
|
|
1991
|
+
var ELLIPSE_KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
|
|
1992
|
+
function rectCenter(rect) {
|
|
1969
1993
|
return {
|
|
1970
1994
|
x: rect.x + rect.width / 2,
|
|
1971
1995
|
y: rect.y + rect.height / 2
|
|
1972
1996
|
};
|
|
1973
1997
|
}
|
|
1974
|
-
function edgeAnchor(
|
|
1975
|
-
const c =
|
|
1998
|
+
function edgeAnchor(bounds, target) {
|
|
1999
|
+
const c = rectCenter(bounds);
|
|
1976
2000
|
const dx = target.x - c.x;
|
|
1977
2001
|
const dy = target.y - c.y;
|
|
1978
|
-
if (
|
|
1979
|
-
return {
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2002
|
+
if (dx === 0 && dy === 0) {
|
|
2003
|
+
return { x: c.x, y: c.y - bounds.height / 2 };
|
|
2004
|
+
}
|
|
2005
|
+
const hw = bounds.width / 2;
|
|
2006
|
+
const hh = bounds.height / 2;
|
|
2007
|
+
const absDx = Math.abs(dx);
|
|
2008
|
+
const absDy = Math.abs(dy);
|
|
2009
|
+
const t = absDx * hh > absDy * hw ? hw / absDx : hh / absDy;
|
|
2010
|
+
return { x: c.x + dx * t, y: c.y + dy * t };
|
|
2011
|
+
}
|
|
2012
|
+
function resolveAnchor(bounds, anchor, fallbackTarget) {
|
|
2013
|
+
if (!anchor) return edgeAnchor(bounds, fallbackTarget);
|
|
2014
|
+
if (typeof anchor === "string") {
|
|
2015
|
+
const c2 = rectCenter(bounds);
|
|
2016
|
+
switch (anchor) {
|
|
2017
|
+
case "top":
|
|
2018
|
+
return { x: c2.x, y: bounds.y };
|
|
2019
|
+
case "bottom":
|
|
2020
|
+
return { x: c2.x, y: bounds.y + bounds.height };
|
|
2021
|
+
case "left":
|
|
2022
|
+
return { x: bounds.x, y: c2.y };
|
|
2023
|
+
case "right":
|
|
2024
|
+
return { x: bounds.x + bounds.width, y: c2.y };
|
|
2025
|
+
case "center":
|
|
2026
|
+
return c2;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
const c = rectCenter(bounds);
|
|
2030
|
+
return {
|
|
2031
|
+
x: c.x + anchor.x * (bounds.width / 2),
|
|
2032
|
+
y: c.y + anchor.y * (bounds.height / 2)
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
function anchorNormal(anchor, point, diagramCenter) {
|
|
2036
|
+
if (typeof anchor === "string") {
|
|
2037
|
+
switch (anchor) {
|
|
2038
|
+
case "top":
|
|
2039
|
+
return { x: 0, y: -1 };
|
|
2040
|
+
case "bottom":
|
|
2041
|
+
return { x: 0, y: 1 };
|
|
2042
|
+
case "left":
|
|
2043
|
+
return { x: -1, y: 0 };
|
|
2044
|
+
case "right":
|
|
2045
|
+
return { x: 1, y: 0 };
|
|
2046
|
+
case "center":
|
|
2047
|
+
return outwardNormal(point, diagramCenter);
|
|
2048
|
+
}
|
|
1983
2049
|
}
|
|
2050
|
+
return outwardNormal(point, diagramCenter);
|
|
2051
|
+
}
|
|
2052
|
+
function outwardNormal(point, diagramCenter) {
|
|
2053
|
+
const dx = point.x - diagramCenter.x;
|
|
2054
|
+
const dy = point.y - diagramCenter.y;
|
|
2055
|
+
const len = Math.hypot(dx, dy) || 1;
|
|
2056
|
+
return { x: dx / len, y: dy / len };
|
|
2057
|
+
}
|
|
2058
|
+
function curveRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, toAnchor) {
|
|
2059
|
+
const fromCenter = rectCenter(fromBounds);
|
|
2060
|
+
const toCenter = rectCenter(toBounds);
|
|
2061
|
+
const p0 = resolveAnchor(fromBounds, fromAnchor, toCenter);
|
|
2062
|
+
const p3 = resolveAnchor(toBounds, toAnchor, fromCenter);
|
|
2063
|
+
const dist = Math.hypot(p3.x - p0.x, p3.y - p0.y);
|
|
2064
|
+
const offset = dist * tension;
|
|
2065
|
+
const n0 = anchorNormal(fromAnchor, p0, diagramCenter);
|
|
2066
|
+
const n3 = anchorNormal(toAnchor, p3, diagramCenter);
|
|
2067
|
+
const cp1 = { x: p0.x + n0.x * offset, y: p0.y + n0.y * offset };
|
|
2068
|
+
const cp2 = { x: p3.x + n3.x * offset, y: p3.y + n3.y * offset };
|
|
2069
|
+
return [p0, cp1, cp2, p3];
|
|
2070
|
+
}
|
|
2071
|
+
function dot(a, b) {
|
|
2072
|
+
return a.x * b.x + a.y * b.y;
|
|
2073
|
+
}
|
|
2074
|
+
function localToWorld(origin, axisX, axisY, local) {
|
|
2075
|
+
return {
|
|
2076
|
+
x: origin.x + axisX.x * local.x + axisY.x * local.y,
|
|
2077
|
+
y: origin.y + axisX.y * local.x + axisY.y * local.y
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
function arcRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, toAnchor) {
|
|
2081
|
+
const fromCenter = rectCenter(fromBounds);
|
|
2082
|
+
const toCenter = rectCenter(toBounds);
|
|
2083
|
+
const start = resolveAnchor(fromBounds, fromAnchor, toCenter);
|
|
2084
|
+
const end = resolveAnchor(toBounds, toAnchor, fromCenter);
|
|
2085
|
+
const chord = { x: end.x - start.x, y: end.y - start.y };
|
|
2086
|
+
const chordLength = Math.hypot(chord.x, chord.y);
|
|
2087
|
+
if (chordLength < 1e-6) {
|
|
2088
|
+
const mid = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
|
2089
|
+
return [
|
|
2090
|
+
[start, start, mid, mid],
|
|
2091
|
+
[mid, mid, end, end]
|
|
2092
|
+
];
|
|
2093
|
+
}
|
|
2094
|
+
const axisX = { x: chord.x / chordLength, y: chord.y / chordLength };
|
|
2095
|
+
let axisY = { x: -axisX.y, y: axisX.x };
|
|
2096
|
+
const midpoint = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
|
|
2097
|
+
const outwardHint = outwardNormal(midpoint, diagramCenter);
|
|
2098
|
+
if (dot(axisY, outwardHint) < 0) {
|
|
2099
|
+
axisY = { x: -axisY.x, y: -axisY.y };
|
|
2100
|
+
}
|
|
2101
|
+
const semiMajor = chordLength / 2;
|
|
2102
|
+
const semiMinor = Math.max(12, chordLength * tension * 0.75);
|
|
2103
|
+
const p0Local = { x: -semiMajor, y: 0 };
|
|
2104
|
+
const cp1Local = { x: -semiMajor, y: ELLIPSE_KAPPA * semiMinor };
|
|
2105
|
+
const cp2Local = { x: -ELLIPSE_KAPPA * semiMajor, y: semiMinor };
|
|
2106
|
+
const pMidLocal = { x: 0, y: semiMinor };
|
|
2107
|
+
const cp3Local = { x: ELLIPSE_KAPPA * semiMajor, y: semiMinor };
|
|
2108
|
+
const cp4Local = { x: semiMajor, y: ELLIPSE_KAPPA * semiMinor };
|
|
2109
|
+
const p3Local = { x: semiMajor, y: 0 };
|
|
2110
|
+
const p0 = localToWorld(midpoint, axisX, axisY, p0Local);
|
|
2111
|
+
const cp1 = localToWorld(midpoint, axisX, axisY, cp1Local);
|
|
2112
|
+
const cp2 = localToWorld(midpoint, axisX, axisY, cp2Local);
|
|
2113
|
+
const pMid = localToWorld(midpoint, axisX, axisY, pMidLocal);
|
|
2114
|
+
const cp3 = localToWorld(midpoint, axisX, axisY, cp3Local);
|
|
2115
|
+
const cp4 = localToWorld(midpoint, axisX, axisY, cp4Local);
|
|
2116
|
+
const p3 = localToWorld(midpoint, axisX, axisY, p3Local);
|
|
2117
|
+
return [
|
|
2118
|
+
[p0, cp1, cp2, pMid],
|
|
2119
|
+
[pMid, cp3, cp4, p3]
|
|
2120
|
+
];
|
|
2121
|
+
}
|
|
2122
|
+
function orthogonalRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
|
|
2123
|
+
const fromC = rectCenter(fromBounds);
|
|
2124
|
+
const toC = rectCenter(toBounds);
|
|
2125
|
+
const p0 = resolveAnchor(fromBounds, fromAnchor, toC);
|
|
2126
|
+
const p3 = resolveAnchor(toBounds, toAnchor, fromC);
|
|
2127
|
+
const midX = (p0.x + p3.x) / 2;
|
|
2128
|
+
return [p0, { x: midX, y: p0.y }, { x: midX, y: p3.y }, p3];
|
|
2129
|
+
}
|
|
2130
|
+
function bezierPointAt(p0, cp1, cp2, p3, t) {
|
|
2131
|
+
const mt = 1 - t;
|
|
2132
|
+
return {
|
|
2133
|
+
x: mt * mt * mt * p0.x + 3 * mt * mt * t * cp1.x + 3 * mt * t * t * cp2.x + t * t * t * p3.x,
|
|
2134
|
+
y: mt * mt * mt * p0.y + 3 * mt * mt * t * cp1.y + 3 * mt * t * t * cp2.y + t * t * t * p3.y
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
function bezierTangentAt(p0, cp1, cp2, p3, t) {
|
|
2138
|
+
const mt = 1 - t;
|
|
1984
2139
|
return {
|
|
1985
|
-
x:
|
|
1986
|
-
y:
|
|
2140
|
+
x: 3 * mt * mt * (cp1.x - p0.x) + 6 * mt * t * (cp2.x - cp1.x) + 3 * t * t * (p3.x - cp2.x),
|
|
2141
|
+
y: 3 * mt * mt * (cp1.y - p0.y) + 6 * mt * t * (cp2.y - cp1.y) + 3 * t * t * (p3.y - cp2.y)
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
function isInsideRect(point, rect) {
|
|
2145
|
+
return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
|
2146
|
+
}
|
|
2147
|
+
function findBoundaryIntersection(p0, cp1, cp2, p3, targetRect, searchFromEnd) {
|
|
2148
|
+
const step = 5e-3;
|
|
2149
|
+
if (searchFromEnd) {
|
|
2150
|
+
for (let t = 0.95; t >= 0.5; t -= step) {
|
|
2151
|
+
const pt = bezierPointAt(p0, cp1, cp2, p3, t);
|
|
2152
|
+
if (!isInsideRect(pt, targetRect)) {
|
|
2153
|
+
return t;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
} else {
|
|
2157
|
+
for (let t = 0.05; t <= 0.5; t += step) {
|
|
2158
|
+
const pt = bezierPointAt(p0, cp1, cp2, p3, t);
|
|
2159
|
+
if (!isInsideRect(pt, targetRect)) {
|
|
2160
|
+
return t;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return void 0;
|
|
2165
|
+
}
|
|
2166
|
+
function pointAlongArc(route, t) {
|
|
2167
|
+
const [first, second] = route;
|
|
2168
|
+
if (t <= 0.5) {
|
|
2169
|
+
const localT2 = Math.max(0, Math.min(1, t * 2));
|
|
2170
|
+
return bezierPointAt(first[0], first[1], first[2], first[3], localT2);
|
|
2171
|
+
}
|
|
2172
|
+
const localT = Math.max(0, Math.min(1, (t - 0.5) * 2));
|
|
2173
|
+
return bezierPointAt(second[0], second[1], second[2], second[3], localT);
|
|
2174
|
+
}
|
|
2175
|
+
function computeDiagramCenter(nodeBounds, canvasCenter) {
|
|
2176
|
+
if (nodeBounds.length === 0) {
|
|
2177
|
+
return canvasCenter ?? { x: 0, y: 0 };
|
|
2178
|
+
}
|
|
2179
|
+
let totalX = 0;
|
|
2180
|
+
let totalY = 0;
|
|
2181
|
+
for (const bounds of nodeBounds) {
|
|
2182
|
+
totalX += bounds.x + bounds.width / 2;
|
|
2183
|
+
totalY += bounds.y + bounds.height / 2;
|
|
2184
|
+
}
|
|
2185
|
+
return {
|
|
2186
|
+
x: totalX / nodeBounds.length,
|
|
2187
|
+
y: totalY / nodeBounds.length
|
|
1987
2188
|
};
|
|
1988
2189
|
}
|
|
1989
2190
|
function dashFromStyle(style) {
|
|
@@ -2067,51 +2268,148 @@ function polylineBounds(points) {
|
|
|
2067
2268
|
height: Math.max(1, maxY - minY)
|
|
2068
2269
|
};
|
|
2069
2270
|
}
|
|
2070
|
-
function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute) {
|
|
2071
|
-
const
|
|
2072
|
-
const
|
|
2073
|
-
const
|
|
2074
|
-
const
|
|
2075
|
-
const dash = dashFromStyle(
|
|
2271
|
+
function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, options) {
|
|
2272
|
+
const routing = conn.routing ?? "auto";
|
|
2273
|
+
const strokeStyle = conn.strokeStyle ?? conn.style ?? "solid";
|
|
2274
|
+
const strokeWidth = conn.width ?? conn.strokeWidth ?? 2;
|
|
2275
|
+
const tension = conn.tension ?? 0.35;
|
|
2276
|
+
const dash = dashFromStyle(strokeStyle);
|
|
2076
2277
|
const style = {
|
|
2077
2278
|
color: conn.color ?? theme.borderMuted,
|
|
2078
|
-
width:
|
|
2279
|
+
width: strokeWidth,
|
|
2079
2280
|
headSize: conn.arrowSize ?? 10,
|
|
2080
2281
|
...dash ? { dash } : {}
|
|
2081
2282
|
};
|
|
2082
|
-
const
|
|
2083
|
-
const
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
let
|
|
2087
|
-
let
|
|
2283
|
+
const labelT = conn.labelPosition === "start" ? 0.2 : conn.labelPosition === "end" ? 0.8 : 0.5;
|
|
2284
|
+
const diagramCenter = options?.diagramCenter ?? computeDiagramCenter([fromBounds, toBounds]);
|
|
2285
|
+
let linePoints;
|
|
2286
|
+
let startPoint;
|
|
2287
|
+
let endPoint;
|
|
2288
|
+
let startAngle;
|
|
2289
|
+
let endAngle;
|
|
2290
|
+
let labelPoint;
|
|
2291
|
+
ctx.save();
|
|
2292
|
+
ctx.globalAlpha = conn.opacity;
|
|
2293
|
+
const arrowPlacement = conn.arrowPlacement ?? "endpoint";
|
|
2294
|
+
if (routing === "curve") {
|
|
2295
|
+
const [p0, cp1, cp2, p3] = curveRoute(
|
|
2296
|
+
fromBounds,
|
|
2297
|
+
toBounds,
|
|
2298
|
+
diagramCenter,
|
|
2299
|
+
tension,
|
|
2300
|
+
conn.fromAnchor,
|
|
2301
|
+
conn.toAnchor
|
|
2302
|
+
);
|
|
2303
|
+
ctx.strokeStyle = style.color;
|
|
2304
|
+
ctx.lineWidth = style.width;
|
|
2305
|
+
ctx.setLineDash(style.dash ?? []);
|
|
2306
|
+
ctx.beginPath();
|
|
2307
|
+
ctx.moveTo(p0.x, p0.y);
|
|
2308
|
+
ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, p3.x, p3.y);
|
|
2309
|
+
ctx.stroke();
|
|
2310
|
+
linePoints = [p0, cp1, cp2, p3];
|
|
2311
|
+
startPoint = p0;
|
|
2312
|
+
endPoint = p3;
|
|
2313
|
+
startAngle = Math.atan2(p0.y - cp1.y, p0.x - cp1.x);
|
|
2314
|
+
endAngle = Math.atan2(p3.y - cp2.y, p3.x - cp2.x);
|
|
2315
|
+
labelPoint = bezierPointAt(p0, cp1, cp2, p3, labelT);
|
|
2316
|
+
if (arrowPlacement === "boundary") {
|
|
2317
|
+
if (conn.arrow === "end" || conn.arrow === "both") {
|
|
2318
|
+
const tEnd = findBoundaryIntersection(p0, cp1, cp2, p3, toBounds, true);
|
|
2319
|
+
if (tEnd !== void 0) {
|
|
2320
|
+
endPoint = bezierPointAt(p0, cp1, cp2, p3, tEnd);
|
|
2321
|
+
const tangent = bezierTangentAt(p0, cp1, cp2, p3, tEnd);
|
|
2322
|
+
endAngle = Math.atan2(tangent.y, tangent.x);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
if (conn.arrow === "start" || conn.arrow === "both") {
|
|
2326
|
+
const tStart = findBoundaryIntersection(p0, cp1, cp2, p3, fromBounds, false);
|
|
2327
|
+
if (tStart !== void 0) {
|
|
2328
|
+
startPoint = bezierPointAt(p0, cp1, cp2, p3, tStart);
|
|
2329
|
+
const tangent = bezierTangentAt(p0, cp1, cp2, p3, tStart);
|
|
2330
|
+
startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
} else if (routing === "arc") {
|
|
2335
|
+
const [first, second] = arcRoute(
|
|
2336
|
+
fromBounds,
|
|
2337
|
+
toBounds,
|
|
2338
|
+
diagramCenter,
|
|
2339
|
+
tension,
|
|
2340
|
+
conn.fromAnchor,
|
|
2341
|
+
conn.toAnchor
|
|
2342
|
+
);
|
|
2343
|
+
const [p0, cp1, cp2, pMid] = first;
|
|
2344
|
+
const [, cp3, cp4, p3] = second;
|
|
2345
|
+
ctx.strokeStyle = style.color;
|
|
2346
|
+
ctx.lineWidth = style.width;
|
|
2347
|
+
ctx.setLineDash(style.dash ?? []);
|
|
2348
|
+
ctx.beginPath();
|
|
2349
|
+
ctx.moveTo(p0.x, p0.y);
|
|
2350
|
+
ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, pMid.x, pMid.y);
|
|
2351
|
+
ctx.bezierCurveTo(cp3.x, cp3.y, cp4.x, cp4.y, p3.x, p3.y);
|
|
2352
|
+
ctx.stroke();
|
|
2353
|
+
linePoints = [p0, cp1, cp2, pMid, cp3, cp4, p3];
|
|
2354
|
+
startPoint = p0;
|
|
2355
|
+
endPoint = p3;
|
|
2356
|
+
startAngle = Math.atan2(p0.y - cp1.y, p0.x - cp1.x);
|
|
2357
|
+
endAngle = Math.atan2(p3.y - cp4.y, p3.x - cp4.x);
|
|
2358
|
+
labelPoint = pointAlongArc([first, second], labelT);
|
|
2359
|
+
if (arrowPlacement === "boundary") {
|
|
2360
|
+
if (conn.arrow === "end" || conn.arrow === "both") {
|
|
2361
|
+
const [, s_cp3, s_cp4, s_p3] = second;
|
|
2362
|
+
const tEnd = findBoundaryIntersection(pMid, s_cp3, s_cp4, s_p3, toBounds, true);
|
|
2363
|
+
if (tEnd !== void 0) {
|
|
2364
|
+
endPoint = bezierPointAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
|
|
2365
|
+
const tangent = bezierTangentAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
|
|
2366
|
+
endAngle = Math.atan2(tangent.y, tangent.x);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
if (conn.arrow === "start" || conn.arrow === "both") {
|
|
2370
|
+
const tStart = findBoundaryIntersection(p0, cp1, cp2, pMid, fromBounds, false);
|
|
2371
|
+
if (tStart !== void 0) {
|
|
2372
|
+
startPoint = bezierPointAt(p0, cp1, cp2, pMid, tStart);
|
|
2373
|
+
const tangent = bezierTangentAt(p0, cp1, cp2, pMid, tStart);
|
|
2374
|
+
startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
} else {
|
|
2379
|
+
const hasAnchorHints = conn.fromAnchor !== void 0 || conn.toAnchor !== void 0;
|
|
2380
|
+
const useElkRoute = routing === "auto" && !hasAnchorHints && (edgeRoute?.points.length ?? 0) >= 2;
|
|
2381
|
+
linePoints = useElkRoute ? edgeRoute?.points ?? orthogonalRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor) : orthogonalRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor);
|
|
2382
|
+
startPoint = linePoints[0];
|
|
2383
|
+
const startSegment = linePoints[1] ?? linePoints[0];
|
|
2384
|
+
const endStart = linePoints[linePoints.length - 2] ?? linePoints[0];
|
|
2385
|
+
endPoint = linePoints[linePoints.length - 1] ?? linePoints[0];
|
|
2386
|
+
startAngle = Math.atan2(startSegment.y - linePoints[0].y, startSegment.x - linePoints[0].x) + Math.PI;
|
|
2387
|
+
endAngle = Math.atan2(endPoint.y - endStart.y, endPoint.x - endStart.x);
|
|
2388
|
+
if (useElkRoute) {
|
|
2389
|
+
drawCubicInterpolatedPath(ctx, linePoints, style);
|
|
2390
|
+
} else {
|
|
2391
|
+
drawOrthogonalPath(ctx, startPoint, endPoint, style);
|
|
2392
|
+
}
|
|
2393
|
+
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
2394
|
+
}
|
|
2088
2395
|
if (!Number.isFinite(startAngle)) {
|
|
2089
2396
|
startAngle = 0;
|
|
2090
2397
|
}
|
|
2091
2398
|
if (!Number.isFinite(endAngle)) {
|
|
2092
2399
|
endAngle = 0;
|
|
2093
2400
|
}
|
|
2094
|
-
const t = conn.labelPosition === "start" ? 0.2 : conn.labelPosition === "end" ? 0.8 : 0.5;
|
|
2095
|
-
const labelPoint = pointAlongPolyline(points, t);
|
|
2096
|
-
ctx.save();
|
|
2097
|
-
ctx.globalAlpha = conn.opacity;
|
|
2098
|
-
if (edgeRoute && edgeRoute.points.length >= 2) {
|
|
2099
|
-
drawCubicInterpolatedPath(ctx, points, style);
|
|
2100
|
-
} else {
|
|
2101
|
-
drawOrthogonalPath(ctx, points[0], points[points.length - 1], style);
|
|
2102
|
-
}
|
|
2103
2401
|
if (conn.arrow === "start" || conn.arrow === "both") {
|
|
2104
|
-
drawArrowhead(ctx,
|
|
2402
|
+
drawArrowhead(ctx, startPoint, startAngle, style.headSize, style.color);
|
|
2105
2403
|
}
|
|
2106
2404
|
if (conn.arrow === "end" || conn.arrow === "both") {
|
|
2107
|
-
drawArrowhead(ctx,
|
|
2405
|
+
drawArrowhead(ctx, endPoint, endAngle, style.headSize, style.color);
|
|
2108
2406
|
}
|
|
2109
2407
|
ctx.restore();
|
|
2110
2408
|
const elements = [
|
|
2111
2409
|
{
|
|
2112
2410
|
id: `connection-${conn.from}-${conn.to}`,
|
|
2113
2411
|
kind: "connection",
|
|
2114
|
-
bounds: polylineBounds(
|
|
2412
|
+
bounds: polylineBounds(linePoints),
|
|
2115
2413
|
foregroundColor: style.color
|
|
2116
2414
|
}
|
|
2117
2415
|
];
|
|
@@ -2737,6 +3035,36 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
2737
3035
|
});
|
|
2738
3036
|
break;
|
|
2739
3037
|
}
|
|
3038
|
+
case "grid": {
|
|
3039
|
+
const canvasWidth = ctx.canvas.width;
|
|
3040
|
+
const canvasHeight = ctx.canvas.height;
|
|
3041
|
+
withOpacity(ctx, command.opacity, () => {
|
|
3042
|
+
ctx.strokeStyle = command.color;
|
|
3043
|
+
ctx.lineWidth = command.width;
|
|
3044
|
+
const startX = command.offsetX % command.spacing;
|
|
3045
|
+
for (let x = startX; x <= canvasWidth; x += command.spacing) {
|
|
3046
|
+
ctx.beginPath();
|
|
3047
|
+
ctx.moveTo(x, 0);
|
|
3048
|
+
ctx.lineTo(x, canvasHeight);
|
|
3049
|
+
ctx.stroke();
|
|
3050
|
+
}
|
|
3051
|
+
const startY = command.offsetY % command.spacing;
|
|
3052
|
+
for (let y = startY; y <= canvasHeight; y += command.spacing) {
|
|
3053
|
+
ctx.beginPath();
|
|
3054
|
+
ctx.moveTo(0, y);
|
|
3055
|
+
ctx.lineTo(canvasWidth, y);
|
|
3056
|
+
ctx.stroke();
|
|
3057
|
+
}
|
|
3058
|
+
});
|
|
3059
|
+
rendered.push({
|
|
3060
|
+
id,
|
|
3061
|
+
kind: "draw",
|
|
3062
|
+
bounds: { x: 0, y: 0, width: canvasWidth, height: canvasHeight },
|
|
3063
|
+
foregroundColor: command.color,
|
|
3064
|
+
allowOverlap: true
|
|
3065
|
+
});
|
|
3066
|
+
break;
|
|
3067
|
+
}
|
|
2740
3068
|
}
|
|
2741
3069
|
}
|
|
2742
3070
|
return rendered;
|
|
@@ -3118,6 +3446,15 @@ var drawGradientRectSchema = z2.object({
|
|
|
3118
3446
|
radius: z2.number().min(0).max(256).default(0),
|
|
3119
3447
|
opacity: z2.number().min(0).max(1).default(1)
|
|
3120
3448
|
}).strict();
|
|
3449
|
+
var drawGridSchema = z2.object({
|
|
3450
|
+
type: z2.literal("grid"),
|
|
3451
|
+
spacing: z2.number().min(5).max(200).default(40),
|
|
3452
|
+
color: colorHexSchema2.default("#1E2D4A"),
|
|
3453
|
+
width: z2.number().min(0.1).max(4).default(0.5),
|
|
3454
|
+
opacity: z2.number().min(0).max(1).default(0.2),
|
|
3455
|
+
offsetX: z2.number().default(0),
|
|
3456
|
+
offsetY: z2.number().default(0)
|
|
3457
|
+
}).strict();
|
|
3121
3458
|
var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
3122
3459
|
drawRectSchema,
|
|
3123
3460
|
drawCircleSchema,
|
|
@@ -3126,7 +3463,8 @@ var drawCommandSchema = z2.discriminatedUnion("type", [
|
|
|
3126
3463
|
drawBezierSchema,
|
|
3127
3464
|
drawPathSchema,
|
|
3128
3465
|
drawBadgeSchema,
|
|
3129
|
-
drawGradientRectSchema
|
|
3466
|
+
drawGradientRectSchema,
|
|
3467
|
+
drawGridSchema
|
|
3130
3468
|
]);
|
|
3131
3469
|
var defaultCanvas = {
|
|
3132
3470
|
width: 1200,
|
|
@@ -3190,10 +3528,26 @@ var cardElementSchema = z2.object({
|
|
|
3190
3528
|
tone: z2.enum(["neutral", "accent", "success", "warning", "error"]).default("neutral"),
|
|
3191
3529
|
icon: z2.string().min(1).max(64).optional()
|
|
3192
3530
|
}).strict();
|
|
3531
|
+
var flowNodeShadowSchema = z2.object({
|
|
3532
|
+
color: colorHexSchema2.optional(),
|
|
3533
|
+
blur: z2.number().min(0).max(64).default(8),
|
|
3534
|
+
offsetX: z2.number().min(-32).max(32).default(0),
|
|
3535
|
+
offsetY: z2.number().min(-32).max(32).default(0),
|
|
3536
|
+
opacity: z2.number().min(0).max(1).default(0.3)
|
|
3537
|
+
}).strict();
|
|
3193
3538
|
var flowNodeElementSchema = z2.object({
|
|
3194
3539
|
type: z2.literal("flow-node"),
|
|
3195
3540
|
id: z2.string().min(1).max(120),
|
|
3196
|
-
shape: z2.enum([
|
|
3541
|
+
shape: z2.enum([
|
|
3542
|
+
"box",
|
|
3543
|
+
"rounded-box",
|
|
3544
|
+
"diamond",
|
|
3545
|
+
"circle",
|
|
3546
|
+
"pill",
|
|
3547
|
+
"cylinder",
|
|
3548
|
+
"parallelogram",
|
|
3549
|
+
"hexagon"
|
|
3550
|
+
]).default("rounded-box"),
|
|
3197
3551
|
label: z2.string().min(1).max(200),
|
|
3198
3552
|
sublabel: z2.string().min(1).max(300).optional(),
|
|
3199
3553
|
sublabelColor: colorHexSchema2.optional(),
|
|
@@ -3213,20 +3567,35 @@ var flowNodeElementSchema = z2.object({
|
|
|
3213
3567
|
badgeText: z2.string().min(1).max(32).optional(),
|
|
3214
3568
|
badgeColor: colorHexSchema2.optional(),
|
|
3215
3569
|
badgeBackground: colorHexSchema2.optional(),
|
|
3216
|
-
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top")
|
|
3570
|
+
badgePosition: z2.enum(["top", "inside-top"]).default("inside-top"),
|
|
3571
|
+
shadow: flowNodeShadowSchema.optional()
|
|
3217
3572
|
}).strict();
|
|
3573
|
+
var anchorHintSchema = z2.union([
|
|
3574
|
+
z2.enum(["top", "bottom", "left", "right", "center"]),
|
|
3575
|
+
z2.object({
|
|
3576
|
+
x: z2.number().min(-1).max(1),
|
|
3577
|
+
y: z2.number().min(-1).max(1)
|
|
3578
|
+
}).strict()
|
|
3579
|
+
]);
|
|
3218
3580
|
var connectionElementSchema = z2.object({
|
|
3219
3581
|
type: z2.literal("connection"),
|
|
3220
3582
|
from: z2.string().min(1).max(120),
|
|
3221
3583
|
to: z2.string().min(1).max(120),
|
|
3222
3584
|
style: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
3585
|
+
strokeStyle: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
3223
3586
|
arrow: z2.enum(["end", "start", "both", "none"]).default("end"),
|
|
3224
3587
|
label: z2.string().min(1).max(200).optional(),
|
|
3225
3588
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
3226
3589
|
color: colorHexSchema2.optional(),
|
|
3227
|
-
width: z2.number().min(0.5).max(
|
|
3590
|
+
width: z2.number().min(0.5).max(10).optional(),
|
|
3591
|
+
strokeWidth: z2.number().min(0.5).max(10).default(2),
|
|
3228
3592
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
3229
|
-
|
|
3593
|
+
arrowPlacement: z2.enum(["endpoint", "boundary"]).default("endpoint"),
|
|
3594
|
+
opacity: z2.number().min(0).max(1).default(1),
|
|
3595
|
+
routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
|
|
3596
|
+
tension: z2.number().min(0.1).max(0.8).default(0.35),
|
|
3597
|
+
fromAnchor: anchorHintSchema.optional(),
|
|
3598
|
+
toAnchor: anchorHintSchema.optional()
|
|
3230
3599
|
}).strict();
|
|
3231
3600
|
var codeBlockStyleSchema = z2.object({
|
|
3232
3601
|
paddingVertical: z2.number().min(0).max(128).default(56),
|
|
@@ -3295,6 +3664,10 @@ var elementSchema = z2.discriminatedUnion("type", [
|
|
|
3295
3664
|
shapeElementSchema,
|
|
3296
3665
|
imageElementSchema
|
|
3297
3666
|
]);
|
|
3667
|
+
var diagramCenterSchema = z2.object({
|
|
3668
|
+
x: z2.number(),
|
|
3669
|
+
y: z2.number()
|
|
3670
|
+
}).strict();
|
|
3298
3671
|
var autoLayoutConfigSchema = z2.object({
|
|
3299
3672
|
mode: z2.literal("auto"),
|
|
3300
3673
|
algorithm: z2.enum(["layered", "stress", "force", "radial", "box"]).default("layered"),
|
|
@@ -3310,7 +3683,9 @@ var autoLayoutConfigSchema = z2.object({
|
|
|
3310
3683
|
/** Compaction strategy for radial layout. Only relevant when algorithm is 'radial'. */
|
|
3311
3684
|
radialCompaction: z2.enum(["none", "radial", "wedge"]).optional(),
|
|
3312
3685
|
/** Sort strategy for radial layout node ordering. Only relevant when algorithm is 'radial'. */
|
|
3313
|
-
radialSortBy: z2.enum(["id", "connections"]).optional()
|
|
3686
|
+
radialSortBy: z2.enum(["id", "connections"]).optional(),
|
|
3687
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
3688
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3314
3689
|
}).strict();
|
|
3315
3690
|
var gridLayoutConfigSchema = z2.object({
|
|
3316
3691
|
mode: z2.literal("grid"),
|
|
@@ -3318,13 +3693,17 @@ var gridLayoutConfigSchema = z2.object({
|
|
|
3318
3693
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
3319
3694
|
cardMinHeight: z2.number().int().min(32).max(4096).optional(),
|
|
3320
3695
|
cardMaxHeight: z2.number().int().min(32).max(4096).optional(),
|
|
3321
|
-
equalHeight: z2.boolean().default(false)
|
|
3696
|
+
equalHeight: z2.boolean().default(false),
|
|
3697
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
3698
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3322
3699
|
}).strict();
|
|
3323
3700
|
var stackLayoutConfigSchema = z2.object({
|
|
3324
3701
|
mode: z2.literal("stack"),
|
|
3325
3702
|
direction: z2.enum(["vertical", "horizontal"]).default("vertical"),
|
|
3326
3703
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
3327
|
-
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch")
|
|
3704
|
+
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch"),
|
|
3705
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
3706
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3328
3707
|
}).strict();
|
|
3329
3708
|
var manualPositionSchema = z2.object({
|
|
3330
3709
|
x: z2.number().int(),
|
|
@@ -3334,7 +3713,9 @@ var manualPositionSchema = z2.object({
|
|
|
3334
3713
|
}).strict();
|
|
3335
3714
|
var manualLayoutConfigSchema = z2.object({
|
|
3336
3715
|
mode: z2.literal("manual"),
|
|
3337
|
-
positions: z2.record(z2.string().min(1), manualPositionSchema).default({})
|
|
3716
|
+
positions: z2.record(z2.string().min(1), manualPositionSchema).default({}),
|
|
3717
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
3718
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3338
3719
|
}).strict();
|
|
3339
3720
|
var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
3340
3721
|
autoLayoutConfigSchema,
|
|
@@ -3386,6 +3767,31 @@ var canvasSchema = z2.object({
|
|
|
3386
3767
|
padding: z2.number().int().min(0).max(256).default(defaultCanvas.padding)
|
|
3387
3768
|
}).strict();
|
|
3388
3769
|
var themeInputSchema = z2.union([builtInThemeSchema, themeSchema]);
|
|
3770
|
+
var diagramPositionSchema = z2.object({
|
|
3771
|
+
x: z2.number(),
|
|
3772
|
+
y: z2.number(),
|
|
3773
|
+
width: z2.number().positive(),
|
|
3774
|
+
height: z2.number().positive()
|
|
3775
|
+
}).strict();
|
|
3776
|
+
var diagramElementSchema = z2.discriminatedUnion("type", [
|
|
3777
|
+
flowNodeElementSchema,
|
|
3778
|
+
connectionElementSchema
|
|
3779
|
+
]);
|
|
3780
|
+
var diagramLayoutSchema = z2.object({
|
|
3781
|
+
mode: z2.enum(["manual", "auto"]).default("manual"),
|
|
3782
|
+
positions: z2.record(z2.string(), diagramPositionSchema).optional(),
|
|
3783
|
+
diagramCenter: diagramCenterSchema.optional()
|
|
3784
|
+
}).strict();
|
|
3785
|
+
var diagramSpecSchema = z2.object({
|
|
3786
|
+
version: z2.literal(1),
|
|
3787
|
+
canvas: z2.object({
|
|
3788
|
+
width: z2.number().int().min(320).max(4096).default(1200),
|
|
3789
|
+
height: z2.number().int().min(180).max(4096).default(675)
|
|
3790
|
+
}).default({ width: 1200, height: 675 }),
|
|
3791
|
+
theme: themeSchema.optional(),
|
|
3792
|
+
elements: z2.array(diagramElementSchema).min(1),
|
|
3793
|
+
layout: diagramLayoutSchema.default({ mode: "manual" })
|
|
3794
|
+
}).strict();
|
|
3389
3795
|
var designSpecSchema = z2.object({
|
|
3390
3796
|
version: z2.literal(2).default(2),
|
|
3391
3797
|
canvas: canvasSchema.default(defaultCanvas),
|
|
@@ -3745,6 +4151,10 @@ async function renderDesign(input, options = {}) {
|
|
|
3745
4151
|
break;
|
|
3746
4152
|
}
|
|
3747
4153
|
}
|
|
4154
|
+
const diagramCenter = spec.layout.diagramCenter ?? computeDiagramCenter(
|
|
4155
|
+
spec.elements.filter((element) => element.type !== "connection").map((element) => elementRects.get(element.id)).filter((rect) => rect != null),
|
|
4156
|
+
{ x: spec.canvas.width / 2, y: spec.canvas.height / 2 }
|
|
4157
|
+
);
|
|
3748
4158
|
for (const element of spec.elements) {
|
|
3749
4159
|
if (element.type !== "connection") {
|
|
3750
4160
|
continue;
|
|
@@ -3757,7 +4167,9 @@ async function renderDesign(input, options = {}) {
|
|
|
3757
4167
|
);
|
|
3758
4168
|
}
|
|
3759
4169
|
const edgeRoute = edgeRoutes?.get(`${element.from}-${element.to}`);
|
|
3760
|
-
elements.push(
|
|
4170
|
+
elements.push(
|
|
4171
|
+
...renderConnection(ctx, element, fromRect, toRect, theme, edgeRoute, { diagramCenter })
|
|
4172
|
+
);
|
|
3761
4173
|
}
|
|
3762
4174
|
if (footerRect && spec.footer) {
|
|
3763
4175
|
const footerText = spec.footer.tagline ? `${spec.footer.text} \u2022 ${spec.footer.tagline}` : spec.footer.text;
|