@spectratools/graphic-designer-cli 0.10.0 → 0.12.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 +42 -1
- package/dist/cli.js +262 -124
- package/dist/index.d.ts +44 -4
- package/dist/index.js +266 -74
- package/dist/qa.d.ts +1 -1
- package/dist/qa.js +50 -7
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +262 -124
- package/dist/{spec.schema-B6sXTTou.d.ts → spec.schema-BkbcnVcm.d.ts} +1790 -1267
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +50 -7
- package/package.json +1 -1
package/dist/renderer.js
CHANGED
|
@@ -1005,6 +1005,35 @@ async function computeElkLayout(elements, config, safeFrame) {
|
|
|
1005
1005
|
};
|
|
1006
1006
|
}
|
|
1007
1007
|
|
|
1008
|
+
// src/layout/ellipse.ts
|
|
1009
|
+
function clampDimension(estimated, max) {
|
|
1010
|
+
return Math.max(1, Math.min(max, Math.floor(estimated)));
|
|
1011
|
+
}
|
|
1012
|
+
function computeEllipseLayout(elements, config, safeFrame) {
|
|
1013
|
+
const placeable = elements.filter((element) => element.type !== "connection");
|
|
1014
|
+
const positions = /* @__PURE__ */ new Map();
|
|
1015
|
+
if (placeable.length === 0) {
|
|
1016
|
+
return { positions };
|
|
1017
|
+
}
|
|
1018
|
+
const cx = config.cx ?? safeFrame.x + safeFrame.width / 2;
|
|
1019
|
+
const cy = config.cy ?? safeFrame.y + safeFrame.height / 2;
|
|
1020
|
+
const stepDegrees = 360 / placeable.length;
|
|
1021
|
+
for (const [index, element] of placeable.entries()) {
|
|
1022
|
+
const angleRadians = (config.startAngle + index * stepDegrees) * Math.PI / 180;
|
|
1023
|
+
const centerX = cx + config.rx * Math.cos(angleRadians);
|
|
1024
|
+
const centerY = cy + config.ry * Math.sin(angleRadians);
|
|
1025
|
+
const width = clampDimension(estimateElementWidth(element), safeFrame.width);
|
|
1026
|
+
const height = clampDimension(estimateElementHeight(element), safeFrame.height);
|
|
1027
|
+
positions.set(element.id, {
|
|
1028
|
+
x: Math.round(centerX - width / 2),
|
|
1029
|
+
y: Math.round(centerY - height / 2),
|
|
1030
|
+
width,
|
|
1031
|
+
height
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
return { positions };
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1008
1037
|
// src/layout/grid.ts
|
|
1009
1038
|
function computeGridLayout(elements, config, safeFrame) {
|
|
1010
1039
|
const placeable = elements.filter((element) => element.type !== "connection");
|
|
@@ -1100,6 +1129,8 @@ async function computeLayout(elements, layout, safeFrame) {
|
|
|
1100
1129
|
return computeGridLayout(elements, layout, safeFrame);
|
|
1101
1130
|
case "stack":
|
|
1102
1131
|
return computeStackLayout(elements, layout, safeFrame);
|
|
1132
|
+
case "ellipse":
|
|
1133
|
+
return computeEllipseLayout(elements, layout, safeFrame);
|
|
1103
1134
|
case "manual":
|
|
1104
1135
|
return computeManualLayout(elements, layout, safeFrame);
|
|
1105
1136
|
default:
|
|
@@ -1366,12 +1397,12 @@ var MACOS_DOTS = [
|
|
|
1366
1397
|
{ fill: "#27C93F", stroke: "#1AAB29" }
|
|
1367
1398
|
];
|
|
1368
1399
|
function drawMacosDots(ctx, x, y) {
|
|
1369
|
-
for (const [index,
|
|
1400
|
+
for (const [index, dot] of MACOS_DOTS.entries()) {
|
|
1370
1401
|
ctx.beginPath();
|
|
1371
1402
|
ctx.arc(x + index * DOT_SPACING, y, DOT_RADIUS, 0, Math.PI * 2);
|
|
1372
1403
|
ctx.closePath();
|
|
1373
|
-
ctx.fillStyle =
|
|
1374
|
-
ctx.strokeStyle =
|
|
1404
|
+
ctx.fillStyle = dot.fill;
|
|
1405
|
+
ctx.strokeStyle = dot.stroke;
|
|
1375
1406
|
ctx.lineWidth = DOT_STROKE_WIDTH;
|
|
1376
1407
|
ctx.fill();
|
|
1377
1408
|
ctx.stroke();
|
|
@@ -2090,56 +2121,71 @@ function curveRoute(fromBounds, toBounds, diagramCenter, tension, fromAnchor, to
|
|
|
2090
2121
|
const cp2 = { x: p3.x + n3.x * offset, y: p3.y + n3.y * offset };
|
|
2091
2122
|
return [p0, cp1, cp2, p3];
|
|
2092
2123
|
}
|
|
2093
|
-
function
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2124
|
+
function inferEllipseParams(nodeBounds, explicitCenter, explicitRx, explicitRy) {
|
|
2125
|
+
if (nodeBounds.length === 0) {
|
|
2126
|
+
return {
|
|
2127
|
+
cx: explicitCenter?.x ?? 0,
|
|
2128
|
+
cy: explicitCenter?.y ?? 0,
|
|
2129
|
+
rx: explicitRx ?? 1,
|
|
2130
|
+
ry: explicitRy ?? 1
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
const centers = nodeBounds.map(rectCenter);
|
|
2134
|
+
const cx = explicitCenter?.x ?? centers.reduce((sum, c) => sum + c.x, 0) / centers.length;
|
|
2135
|
+
const cy = explicitCenter?.y ?? centers.reduce((sum, c) => sum + c.y, 0) / centers.length;
|
|
2136
|
+
const rx = explicitRx ?? Math.max(1, ...centers.map((c) => Math.abs(c.x - cx)));
|
|
2137
|
+
const ry = explicitRy ?? Math.max(1, ...centers.map((c) => Math.abs(c.y - cy)));
|
|
2138
|
+
return { cx, cy, rx, ry };
|
|
2101
2139
|
}
|
|
2102
|
-
function
|
|
2140
|
+
function ellipseRoute(fromBounds, toBounds, ellipse, fromAnchor, toAnchor) {
|
|
2103
2141
|
const fromCenter = rectCenter(fromBounds);
|
|
2104
2142
|
const toCenter = rectCenter(toBounds);
|
|
2105
|
-
const
|
|
2106
|
-
const
|
|
2107
|
-
const
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
const
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
const
|
|
2129
|
-
const
|
|
2130
|
-
const
|
|
2131
|
-
const
|
|
2132
|
-
const
|
|
2133
|
-
const
|
|
2134
|
-
const
|
|
2135
|
-
const
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
+
const p0 = resolveAnchor(fromBounds, fromAnchor, toCenter);
|
|
2144
|
+
const p3 = resolveAnchor(toBounds, toAnchor, fromCenter);
|
|
2145
|
+
const theta1 = Math.atan2(
|
|
2146
|
+
(fromCenter.y - ellipse.cy) / ellipse.ry,
|
|
2147
|
+
(fromCenter.x - ellipse.cx) / ellipse.rx
|
|
2148
|
+
);
|
|
2149
|
+
const theta2 = Math.atan2(
|
|
2150
|
+
(toCenter.y - ellipse.cy) / ellipse.ry,
|
|
2151
|
+
(toCenter.x - ellipse.cx) / ellipse.rx
|
|
2152
|
+
);
|
|
2153
|
+
let angularSpan = theta2 - theta1;
|
|
2154
|
+
while (angularSpan > Math.PI) angularSpan -= 2 * Math.PI;
|
|
2155
|
+
while (angularSpan <= -Math.PI) angularSpan += 2 * Math.PI;
|
|
2156
|
+
const absSpan = Math.abs(angularSpan);
|
|
2157
|
+
const kappa = absSpan < 1e-6 ? 0 : 4 / 3 * Math.tan(absSpan / 4);
|
|
2158
|
+
const tangent1 = {
|
|
2159
|
+
x: -ellipse.rx * Math.sin(theta1),
|
|
2160
|
+
y: ellipse.ry * Math.cos(theta1)
|
|
2161
|
+
};
|
|
2162
|
+
const tangent2 = {
|
|
2163
|
+
x: -ellipse.rx * Math.sin(theta2),
|
|
2164
|
+
y: ellipse.ry * Math.cos(theta2)
|
|
2165
|
+
};
|
|
2166
|
+
const len1 = Math.hypot(tangent1.x, tangent1.y) || 1;
|
|
2167
|
+
const len2 = Math.hypot(tangent2.x, tangent2.y) || 1;
|
|
2168
|
+
const norm1 = { x: tangent1.x / len1, y: tangent1.y / len1 };
|
|
2169
|
+
const norm2 = { x: tangent2.x / len2, y: tangent2.y / len2 };
|
|
2170
|
+
const chordLength = Math.hypot(p3.x - p0.x, p3.y - p0.y);
|
|
2171
|
+
const cpDistance = chordLength * kappa * 0.5;
|
|
2172
|
+
const sign = angularSpan >= 0 ? 1 : -1;
|
|
2173
|
+
const cp1 = {
|
|
2174
|
+
x: p0.x + norm1.x * cpDistance * sign,
|
|
2175
|
+
y: p0.y + norm1.y * cpDistance * sign
|
|
2176
|
+
};
|
|
2177
|
+
const cp2 = {
|
|
2178
|
+
x: p3.x - norm2.x * cpDistance * sign,
|
|
2179
|
+
y: p3.y - norm2.y * cpDistance * sign
|
|
2180
|
+
};
|
|
2181
|
+
return [p0, cp1, cp2, p3];
|
|
2182
|
+
}
|
|
2183
|
+
function straightRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
|
|
2184
|
+
const fromC = rectCenter(fromBounds);
|
|
2185
|
+
const toC = rectCenter(toBounds);
|
|
2186
|
+
const p0 = resolveAnchor(fromBounds, fromAnchor, toC);
|
|
2187
|
+
const p3 = resolveAnchor(toBounds, toAnchor, fromC);
|
|
2188
|
+
return [p0, p3];
|
|
2143
2189
|
}
|
|
2144
2190
|
function orthogonalRoute(fromBounds, toBounds, fromAnchor, toAnchor) {
|
|
2145
2191
|
const fromC = rectCenter(fromBounds);
|
|
@@ -2185,15 +2231,6 @@ function findBoundaryIntersection(p0, cp1, cp2, p3, targetRect, searchFromEnd) {
|
|
|
2185
2231
|
}
|
|
2186
2232
|
return void 0;
|
|
2187
2233
|
}
|
|
2188
|
-
function pointAlongArc(route, t) {
|
|
2189
|
-
const [first, second] = route;
|
|
2190
|
-
if (t <= 0.5) {
|
|
2191
|
-
const localT2 = Math.max(0, Math.min(1, t * 2));
|
|
2192
|
-
return bezierPointAt(first[0], first[1], first[2], first[3], localT2);
|
|
2193
|
-
}
|
|
2194
|
-
const localT = Math.max(0, Math.min(1, (t - 0.5) * 2));
|
|
2195
|
-
return bezierPointAt(second[0], second[1], second[2], second[3], localT);
|
|
2196
|
-
}
|
|
2197
2234
|
function computeDiagramCenter(nodeBounds, canvasCenter) {
|
|
2198
2235
|
if (nodeBounds.length === 0) {
|
|
2199
2236
|
return canvasCenter ?? { x: 0, y: 0 };
|
|
@@ -2316,8 +2353,19 @@ function polylineBounds(points) {
|
|
|
2316
2353
|
};
|
|
2317
2354
|
}
|
|
2318
2355
|
function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, options) {
|
|
2319
|
-
|
|
2320
|
-
|
|
2356
|
+
let routing = conn.routing ?? "auto";
|
|
2357
|
+
let curveMode = conn.curveMode ?? "normal";
|
|
2358
|
+
if (conn.strokeStyle !== void 0) {
|
|
2359
|
+
console.warn("connection.strokeStyle is deprecated, use style instead");
|
|
2360
|
+
}
|
|
2361
|
+
if (routing === "arc") {
|
|
2362
|
+
console.warn(
|
|
2363
|
+
"connection routing: 'arc' is deprecated. Use routing: 'curve' with curveMode: 'ellipse' instead."
|
|
2364
|
+
);
|
|
2365
|
+
routing = "curve";
|
|
2366
|
+
curveMode = "ellipse";
|
|
2367
|
+
}
|
|
2368
|
+
const strokeStyle = conn.style ?? conn.strokeStyle ?? "solid";
|
|
2321
2369
|
const strokeWidth = conn.width ?? conn.strokeWidth ?? 2;
|
|
2322
2370
|
const tension = conn.tension ?? 0.35;
|
|
2323
2371
|
const dash = dashFromStyle(strokeStyle);
|
|
@@ -2339,14 +2387,29 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
2339
2387
|
ctx.globalAlpha = conn.opacity;
|
|
2340
2388
|
const arrowPlacement = conn.arrowPlacement ?? "endpoint";
|
|
2341
2389
|
if (routing === "curve") {
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2390
|
+
let p0;
|
|
2391
|
+
let cp1;
|
|
2392
|
+
let cp2;
|
|
2393
|
+
let p3;
|
|
2394
|
+
if (curveMode === "ellipse") {
|
|
2395
|
+
const ellipse = options?.ellipseParams ?? inferEllipseParams([fromBounds, toBounds]);
|
|
2396
|
+
[p0, cp1, cp2, p3] = ellipseRoute(
|
|
2397
|
+
fromBounds,
|
|
2398
|
+
toBounds,
|
|
2399
|
+
ellipse,
|
|
2400
|
+
conn.fromAnchor,
|
|
2401
|
+
conn.toAnchor
|
|
2402
|
+
);
|
|
2403
|
+
} else {
|
|
2404
|
+
[p0, cp1, cp2, p3] = curveRoute(
|
|
2405
|
+
fromBounds,
|
|
2406
|
+
toBounds,
|
|
2407
|
+
diagramCenter,
|
|
2408
|
+
tension,
|
|
2409
|
+
conn.fromAnchor,
|
|
2410
|
+
conn.toAnchor
|
|
2411
|
+
);
|
|
2412
|
+
}
|
|
2350
2413
|
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
2351
2414
|
ctx.strokeStyle = stroke;
|
|
2352
2415
|
ctx.lineWidth = style.width;
|
|
@@ -2379,51 +2442,22 @@ function renderConnection(ctx, conn, fromBounds, toBounds, theme, edgeRoute, opt
|
|
|
2379
2442
|
}
|
|
2380
2443
|
}
|
|
2381
2444
|
}
|
|
2382
|
-
} else if (routing === "
|
|
2383
|
-
const [
|
|
2384
|
-
fromBounds,
|
|
2385
|
-
toBounds,
|
|
2386
|
-
diagramCenter,
|
|
2387
|
-
tension,
|
|
2388
|
-
conn.fromAnchor,
|
|
2389
|
-
conn.toAnchor
|
|
2390
|
-
);
|
|
2391
|
-
const [p0, cp1, cp2, pMid] = first;
|
|
2392
|
-
const [, cp3, cp4, p3] = second;
|
|
2445
|
+
} else if (routing === "straight") {
|
|
2446
|
+
const [p0, p3] = straightRoute(fromBounds, toBounds, conn.fromAnchor, conn.toAnchor);
|
|
2393
2447
|
const stroke = resolveConnectionStroke(ctx, p0, p3, conn.fromColor, style.color, conn.toColor);
|
|
2394
2448
|
ctx.strokeStyle = stroke;
|
|
2395
2449
|
ctx.lineWidth = style.width;
|
|
2396
2450
|
ctx.setLineDash(style.dash ?? []);
|
|
2397
2451
|
ctx.beginPath();
|
|
2398
2452
|
ctx.moveTo(p0.x, p0.y);
|
|
2399
|
-
ctx.
|
|
2400
|
-
ctx.bezierCurveTo(cp3.x, cp3.y, cp4.x, cp4.y, p3.x, p3.y);
|
|
2453
|
+
ctx.lineTo(p3.x, p3.y);
|
|
2401
2454
|
ctx.stroke();
|
|
2402
|
-
linePoints = [p0,
|
|
2455
|
+
linePoints = [p0, p3];
|
|
2403
2456
|
startPoint = p0;
|
|
2404
2457
|
endPoint = p3;
|
|
2405
|
-
startAngle = Math.atan2(p0.y -
|
|
2406
|
-
endAngle = Math.atan2(p3.y -
|
|
2407
|
-
labelPoint =
|
|
2408
|
-
if (arrowPlacement === "boundary") {
|
|
2409
|
-
if (conn.arrow === "end" || conn.arrow === "both") {
|
|
2410
|
-
const [, s_cp3, s_cp4, s_p3] = second;
|
|
2411
|
-
const tEnd = findBoundaryIntersection(pMid, s_cp3, s_cp4, s_p3, toBounds, true);
|
|
2412
|
-
if (tEnd !== void 0) {
|
|
2413
|
-
endPoint = bezierPointAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
|
|
2414
|
-
const tangent = bezierTangentAt(pMid, s_cp3, s_cp4, s_p3, tEnd);
|
|
2415
|
-
endAngle = Math.atan2(tangent.y, tangent.x);
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
if (conn.arrow === "start" || conn.arrow === "both") {
|
|
2419
|
-
const tStart = findBoundaryIntersection(p0, cp1, cp2, pMid, fromBounds, false);
|
|
2420
|
-
if (tStart !== void 0) {
|
|
2421
|
-
startPoint = bezierPointAt(p0, cp1, cp2, pMid, tStart);
|
|
2422
|
-
const tangent = bezierTangentAt(p0, cp1, cp2, pMid, tStart);
|
|
2423
|
-
startAngle = Math.atan2(tangent.y, tangent.x) + Math.PI;
|
|
2424
|
-
}
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2458
|
+
startAngle = Math.atan2(p0.y - p3.y, p0.x - p3.x);
|
|
2459
|
+
endAngle = Math.atan2(p3.y - p0.y, p3.x - p0.x);
|
|
2460
|
+
labelPoint = pointAlongPolyline(linePoints, labelT);
|
|
2427
2461
|
} else {
|
|
2428
2462
|
const hasAnchorHints = conn.fromAnchor !== void 0 || conn.toAnchor !== void 0;
|
|
2429
2463
|
const useElkRoute = routing === "auto" && !hasAnchorHints && (edgeRoute?.points.length ?? 0) >= 2;
|
|
@@ -2689,6 +2723,24 @@ function fromPoints(points) {
|
|
|
2689
2723
|
function resolveDrawFont(theme, family) {
|
|
2690
2724
|
return resolveFont(theme.fonts[family], family);
|
|
2691
2725
|
}
|
|
2726
|
+
function createDrawStrokeGradient(ctx, start, end, strokeGradient) {
|
|
2727
|
+
const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
|
|
2728
|
+
gradient.addColorStop(0, strokeGradient.from);
|
|
2729
|
+
gradient.addColorStop(1, strokeGradient.to);
|
|
2730
|
+
return gradient;
|
|
2731
|
+
}
|
|
2732
|
+
function resolveDrawStroke(ctx, start, end, color, strokeGradient) {
|
|
2733
|
+
if (!strokeGradient) {
|
|
2734
|
+
return color;
|
|
2735
|
+
}
|
|
2736
|
+
return createDrawStrokeGradient(ctx, start, end, strokeGradient);
|
|
2737
|
+
}
|
|
2738
|
+
function resolveArrowFill(color, strokeGradient, position) {
|
|
2739
|
+
if (!strokeGradient) {
|
|
2740
|
+
return color;
|
|
2741
|
+
}
|
|
2742
|
+
return position === "start" ? strokeGradient.from : strokeGradient.to;
|
|
2743
|
+
}
|
|
2692
2744
|
function measureSpacedTextWidth(ctx, text, letterSpacing) {
|
|
2693
2745
|
const chars = [...text];
|
|
2694
2746
|
if (chars.length === 0) {
|
|
@@ -2967,18 +3019,31 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
2967
3019
|
const from = { x: command.x1, y: command.y1 };
|
|
2968
3020
|
const to = { x: command.x2, y: command.y2 };
|
|
2969
3021
|
const lineAngle = angleBetween(from, to);
|
|
3022
|
+
const stroke = resolveDrawStroke(ctx, from, to, command.color, command.strokeGradient);
|
|
2970
3023
|
withOpacity(ctx, command.opacity, () => {
|
|
2971
3024
|
applyDrawShadow(ctx, command.shadow);
|
|
2972
3025
|
drawLine(ctx, from, to, {
|
|
2973
|
-
color:
|
|
3026
|
+
color: stroke,
|
|
2974
3027
|
width: command.width,
|
|
2975
3028
|
...command.dash ? { dash: command.dash } : {}
|
|
2976
3029
|
});
|
|
2977
3030
|
if (command.arrow === "end" || command.arrow === "both") {
|
|
2978
|
-
drawArrowhead(
|
|
3031
|
+
drawArrowhead(
|
|
3032
|
+
ctx,
|
|
3033
|
+
to,
|
|
3034
|
+
lineAngle,
|
|
3035
|
+
command.arrowSize,
|
|
3036
|
+
resolveArrowFill(command.color, command.strokeGradient, "end")
|
|
3037
|
+
);
|
|
2979
3038
|
}
|
|
2980
3039
|
if (command.arrow === "start" || command.arrow === "both") {
|
|
2981
|
-
drawArrowhead(
|
|
3040
|
+
drawArrowhead(
|
|
3041
|
+
ctx,
|
|
3042
|
+
from,
|
|
3043
|
+
lineAngle + Math.PI,
|
|
3044
|
+
command.arrowSize,
|
|
3045
|
+
resolveArrowFill(command.color, command.strokeGradient, "start")
|
|
3046
|
+
);
|
|
2982
3047
|
}
|
|
2983
3048
|
});
|
|
2984
3049
|
const arrowPadding = command.arrow === "none" ? 0 : command.arrowSize;
|
|
@@ -2986,7 +3051,7 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
2986
3051
|
id,
|
|
2987
3052
|
kind: "draw",
|
|
2988
3053
|
bounds: expandRect(fromPoints([from, to]), Math.max(command.width / 2, arrowPadding)),
|
|
2989
|
-
foregroundColor: command.color
|
|
3054
|
+
foregroundColor: command.strokeGradient?.from ?? command.color
|
|
2990
3055
|
});
|
|
2991
3056
|
break;
|
|
2992
3057
|
}
|
|
@@ -3020,10 +3085,17 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
3020
3085
|
}
|
|
3021
3086
|
case "bezier": {
|
|
3022
3087
|
const points = command.points;
|
|
3088
|
+
const stroke = resolveDrawStroke(
|
|
3089
|
+
ctx,
|
|
3090
|
+
points[0],
|
|
3091
|
+
points[points.length - 1],
|
|
3092
|
+
command.color,
|
|
3093
|
+
command.strokeGradient
|
|
3094
|
+
);
|
|
3023
3095
|
withOpacity(ctx, command.opacity, () => {
|
|
3024
3096
|
applyDrawShadow(ctx, command.shadow);
|
|
3025
3097
|
drawBezier(ctx, points, {
|
|
3026
|
-
color:
|
|
3098
|
+
color: stroke,
|
|
3027
3099
|
width: command.width,
|
|
3028
3100
|
...command.dash ? { dash: command.dash } : {}
|
|
3029
3101
|
});
|
|
@@ -3035,11 +3107,17 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
3035
3107
|
points[points.length - 1],
|
|
3036
3108
|
endAngle,
|
|
3037
3109
|
command.arrowSize,
|
|
3038
|
-
command.color
|
|
3110
|
+
resolveArrowFill(command.color, command.strokeGradient, "end")
|
|
3039
3111
|
);
|
|
3040
3112
|
}
|
|
3041
3113
|
if (command.arrow === "start" || command.arrow === "both") {
|
|
3042
|
-
drawArrowhead(
|
|
3114
|
+
drawArrowhead(
|
|
3115
|
+
ctx,
|
|
3116
|
+
points[0],
|
|
3117
|
+
startAngle + Math.PI,
|
|
3118
|
+
command.arrowSize,
|
|
3119
|
+
resolveArrowFill(command.color, command.strokeGradient, "start")
|
|
3120
|
+
);
|
|
3043
3121
|
}
|
|
3044
3122
|
});
|
|
3045
3123
|
const arrowPadding = command.arrow === "none" ? 0 : command.arrowSize;
|
|
@@ -3047,7 +3125,7 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
3047
3125
|
id,
|
|
3048
3126
|
kind: "draw",
|
|
3049
3127
|
bounds: expandRect(fromPoints(points), Math.max(command.width / 2, arrowPadding)),
|
|
3050
|
-
foregroundColor: command.color
|
|
3128
|
+
foregroundColor: command.strokeGradient?.from ?? command.color
|
|
3051
3129
|
});
|
|
3052
3130
|
break;
|
|
3053
3131
|
}
|
|
@@ -3547,6 +3625,10 @@ var drawShadowSchema = z2.object({
|
|
|
3547
3625
|
offsetY: z2.number().default(4)
|
|
3548
3626
|
}).strict();
|
|
3549
3627
|
var drawFontFamilySchema = z2.enum(["heading", "body", "mono"]);
|
|
3628
|
+
var strokeGradientSchema = z2.object({
|
|
3629
|
+
from: colorHexSchema2,
|
|
3630
|
+
to: colorHexSchema2
|
|
3631
|
+
}).strict();
|
|
3550
3632
|
var drawRectSchema = z2.object({
|
|
3551
3633
|
type: z2.literal("rect"),
|
|
3552
3634
|
x: z2.number(),
|
|
@@ -3594,6 +3676,7 @@ var drawLineSchema = z2.object({
|
|
|
3594
3676
|
x2: z2.number(),
|
|
3595
3677
|
y2: z2.number(),
|
|
3596
3678
|
color: colorHexSchema2.default("#FFFFFF"),
|
|
3679
|
+
strokeGradient: strokeGradientSchema.optional(),
|
|
3597
3680
|
width: z2.number().min(0.5).max(32).default(2),
|
|
3598
3681
|
dash: z2.array(z2.number()).max(6).optional(),
|
|
3599
3682
|
arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
|
|
@@ -3624,6 +3707,7 @@ var drawBezierSchema = z2.object({
|
|
|
3624
3707
|
type: z2.literal("bezier"),
|
|
3625
3708
|
points: z2.array(drawPointSchema).min(2).max(20),
|
|
3626
3709
|
color: colorHexSchema2.default("#FFFFFF"),
|
|
3710
|
+
strokeGradient: strokeGradientSchema.optional(),
|
|
3627
3711
|
width: z2.number().min(0.5).max(32).default(2),
|
|
3628
3712
|
dash: z2.array(z2.number()).max(6).optional(),
|
|
3629
3713
|
arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
|
|
@@ -3831,7 +3915,8 @@ var connectionElementSchema = z2.object({
|
|
|
3831
3915
|
from: z2.string().min(1).max(120),
|
|
3832
3916
|
to: z2.string().min(1).max(120),
|
|
3833
3917
|
style: z2.enum(["solid", "dashed", "dotted"]).default("solid"),
|
|
3834
|
-
|
|
3918
|
+
/** @deprecated Use `style` instead. */
|
|
3919
|
+
strokeStyle: z2.enum(["solid", "dashed", "dotted"]).optional(),
|
|
3835
3920
|
arrow: z2.enum(["end", "start", "both", "none"]).default("end"),
|
|
3836
3921
|
label: z2.string().min(1).max(200).optional(),
|
|
3837
3922
|
labelPosition: z2.enum(["start", "middle", "end"]).default("middle"),
|
|
@@ -3843,7 +3928,8 @@ var connectionElementSchema = z2.object({
|
|
|
3843
3928
|
arrowSize: z2.number().min(4).max(32).optional(),
|
|
3844
3929
|
arrowPlacement: z2.enum(["endpoint", "boundary"]).default("endpoint"),
|
|
3845
3930
|
opacity: z2.number().min(0).max(1).default(1),
|
|
3846
|
-
routing: z2.enum(["auto", "orthogonal", "curve", "arc"]).default("auto"),
|
|
3931
|
+
routing: z2.enum(["auto", "orthogonal", "curve", "arc", "straight"]).default("auto"),
|
|
3932
|
+
curveMode: z2.enum(["normal", "ellipse"]).default("normal"),
|
|
3847
3933
|
tension: z2.number().min(0.1).max(0.8).default(0.35),
|
|
3848
3934
|
fromAnchor: anchorHintSchema.optional(),
|
|
3849
3935
|
toAnchor: anchorHintSchema.optional()
|
|
@@ -3936,7 +4022,11 @@ var autoLayoutConfigSchema = z2.object({
|
|
|
3936
4022
|
/** Sort strategy for radial layout node ordering. Only relevant when algorithm is 'radial'. */
|
|
3937
4023
|
radialSortBy: z2.enum(["id", "connections"]).optional(),
|
|
3938
4024
|
/** Explicit center used by curve/arc connection routing. */
|
|
3939
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
4025
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
4026
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4027
|
+
ellipseRx: z2.number().positive().optional(),
|
|
4028
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4029
|
+
ellipseRy: z2.number().positive().optional()
|
|
3940
4030
|
}).strict();
|
|
3941
4031
|
var gridLayoutConfigSchema = z2.object({
|
|
3942
4032
|
mode: z2.literal("grid"),
|
|
@@ -3946,7 +4036,11 @@ var gridLayoutConfigSchema = z2.object({
|
|
|
3946
4036
|
cardMaxHeight: z2.number().int().min(32).max(4096).optional(),
|
|
3947
4037
|
equalHeight: z2.boolean().default(false),
|
|
3948
4038
|
/** Explicit center used by curve/arc connection routing. */
|
|
3949
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
4039
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
4040
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4041
|
+
ellipseRx: z2.number().positive().optional(),
|
|
4042
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4043
|
+
ellipseRy: z2.number().positive().optional()
|
|
3950
4044
|
}).strict();
|
|
3951
4045
|
var stackLayoutConfigSchema = z2.object({
|
|
3952
4046
|
mode: z2.literal("stack"),
|
|
@@ -3954,7 +4048,25 @@ var stackLayoutConfigSchema = z2.object({
|
|
|
3954
4048
|
gap: z2.number().int().min(0).max(256).default(24),
|
|
3955
4049
|
alignment: z2.enum(["start", "center", "end", "stretch"]).default("stretch"),
|
|
3956
4050
|
/** Explicit center used by curve/arc connection routing. */
|
|
3957
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
4051
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
4052
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4053
|
+
ellipseRx: z2.number().positive().optional(),
|
|
4054
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4055
|
+
ellipseRy: z2.number().positive().optional()
|
|
4056
|
+
}).strict();
|
|
4057
|
+
var ellipseLayoutConfigSchema = z2.object({
|
|
4058
|
+
mode: z2.literal("ellipse"),
|
|
4059
|
+
cx: z2.number().optional(),
|
|
4060
|
+
cy: z2.number().optional(),
|
|
4061
|
+
rx: z2.number().positive(),
|
|
4062
|
+
ry: z2.number().positive(),
|
|
4063
|
+
startAngle: z2.number().default(-90),
|
|
4064
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
4065
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
4066
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4067
|
+
ellipseRx: z2.number().positive().optional(),
|
|
4068
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4069
|
+
ellipseRy: z2.number().positive().optional()
|
|
3958
4070
|
}).strict();
|
|
3959
4071
|
var manualPositionSchema = z2.object({
|
|
3960
4072
|
x: z2.number().int(),
|
|
@@ -3966,12 +4078,17 @@ var manualLayoutConfigSchema = z2.object({
|
|
|
3966
4078
|
mode: z2.literal("manual"),
|
|
3967
4079
|
positions: z2.record(z2.string().min(1), manualPositionSchema).default({}),
|
|
3968
4080
|
/** Explicit center used by curve/arc connection routing. */
|
|
3969
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
4081
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
4082
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4083
|
+
ellipseRx: z2.number().positive().optional(),
|
|
4084
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4085
|
+
ellipseRy: z2.number().positive().optional()
|
|
3970
4086
|
}).strict();
|
|
3971
4087
|
var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
3972
4088
|
autoLayoutConfigSchema,
|
|
3973
4089
|
gridLayoutConfigSchema,
|
|
3974
4090
|
stackLayoutConfigSchema,
|
|
4091
|
+
ellipseLayoutConfigSchema,
|
|
3975
4092
|
manualLayoutConfigSchema
|
|
3976
4093
|
]);
|
|
3977
4094
|
var constraintsSchema = z2.object({
|
|
@@ -4031,7 +4148,11 @@ var diagramElementSchema = z2.discriminatedUnion("type", [
|
|
|
4031
4148
|
var diagramLayoutSchema = z2.object({
|
|
4032
4149
|
mode: z2.enum(["manual", "auto"]).default("manual"),
|
|
4033
4150
|
positions: z2.record(z2.string(), diagramPositionSchema).optional(),
|
|
4034
|
-
diagramCenter: diagramCenterSchema.optional()
|
|
4151
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
4152
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4153
|
+
ellipseRx: z2.number().positive().optional(),
|
|
4154
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
4155
|
+
ellipseRy: z2.number().positive().optional()
|
|
4035
4156
|
}).strict();
|
|
4036
4157
|
var diagramSpecSchema = z2.object({
|
|
4037
4158
|
version: z2.literal(1),
|
|
@@ -4414,10 +4535,19 @@ async function renderDesign(input, options = {}) {
|
|
|
4414
4535
|
break;
|
|
4415
4536
|
}
|
|
4416
4537
|
}
|
|
4417
|
-
const
|
|
4418
|
-
|
|
4419
|
-
|
|
4538
|
+
const nodeBounds = spec.elements.filter((element) => element.type !== "connection").map((element) => elementRects.get(element.id)).filter((rect) => rect != null);
|
|
4539
|
+
const diagramCenter = spec.layout.diagramCenter ?? computeDiagramCenter(nodeBounds, { x: spec.canvas.width / 2, y: spec.canvas.height / 2 });
|
|
4540
|
+
const layoutEllipseRx = "ellipseRx" in spec.layout ? spec.layout.ellipseRx : void 0;
|
|
4541
|
+
const layoutEllipseRy = "ellipseRy" in spec.layout ? spec.layout.ellipseRy : void 0;
|
|
4542
|
+
const hasEllipseConnections = spec.elements.some(
|
|
4543
|
+
(el) => el.type === "connection" && (el.curveMode === "ellipse" || el.routing === "arc")
|
|
4420
4544
|
);
|
|
4545
|
+
const ellipseParams = hasEllipseConnections ? inferEllipseParams(
|
|
4546
|
+
nodeBounds,
|
|
4547
|
+
spec.layout.diagramCenter ?? diagramCenter,
|
|
4548
|
+
layoutEllipseRx,
|
|
4549
|
+
layoutEllipseRy
|
|
4550
|
+
) : void 0;
|
|
4421
4551
|
for (const element of spec.elements) {
|
|
4422
4552
|
if (element.type !== "connection") {
|
|
4423
4553
|
continue;
|
|
@@ -4431,7 +4561,15 @@ async function renderDesign(input, options = {}) {
|
|
|
4431
4561
|
}
|
|
4432
4562
|
const edgeRoute = edgeRoutes?.get(`${element.from}-${element.to}`);
|
|
4433
4563
|
elements.push(
|
|
4434
|
-
...renderConnection(
|
|
4564
|
+
...renderConnection(
|
|
4565
|
+
ctx,
|
|
4566
|
+
element,
|
|
4567
|
+
fromRect,
|
|
4568
|
+
toRect,
|
|
4569
|
+
theme,
|
|
4570
|
+
edgeRoute,
|
|
4571
|
+
ellipseParams ? { diagramCenter, ellipseParams } : { diagramCenter }
|
|
4572
|
+
)
|
|
4435
4573
|
);
|
|
4436
4574
|
}
|
|
4437
4575
|
if (footerRect && spec.footer) {
|