@spectratools/graphic-designer-cli 0.11.0 → 0.12.1
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 +104 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +104 -8
- package/dist/qa.d.ts +1 -1
- package/dist/qa.js +21 -0
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.js +104 -8
- package/dist/{spec.schema-CYlOLxmK.d.ts → spec.schema-BkbcnVcm.d.ts} +466 -117
- package/dist/spec.schema.d.ts +1 -1
- package/dist/spec.schema.js +21 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -125,6 +125,7 @@ Plus a **freestyle draw layer** with 8 draw command types: `rect`, `circle`, `te
|
|
|
125
125
|
- **auto** — ELK.js layout engine (layered, stress, force, radial, box algorithms)
|
|
126
126
|
- **grid** — column-based grid layout
|
|
127
127
|
- **stack** — vertical or horizontal stack
|
|
128
|
+
- **ellipse** — evenly spaced nodes on a configurable ellipse
|
|
128
129
|
- **manual** — explicit x/y coordinates
|
|
129
130
|
|
|
130
131
|
### Connection Routing Modes
|
|
@@ -138,6 +139,36 @@ Per-connection `routing` supports:
|
|
|
138
139
|
|
|
139
140
|
`layout.diagramCenter` can optionally override the center point used by `curve` and `arc` routing. When omitted, center is derived from the laid-out element centroid (fallback: canvas center).
|
|
140
141
|
|
|
142
|
+
### Hexagonal Preset via Ellipse Layout
|
|
143
|
+
|
|
144
|
+
A six-node hexagonal arrangement is just an ellipse layout with equal angular spacing and matching routing hints:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const spec = parseDesignSpec({
|
|
148
|
+
version: 2,
|
|
149
|
+
theme: 'dark',
|
|
150
|
+
elements: [
|
|
151
|
+
{ type: 'flow-node', id: 'triage', label: 'Triage', shape: 'rounded-box' },
|
|
152
|
+
{ type: 'flow-node', id: 'design', label: 'Design', shape: 'rounded-box' },
|
|
153
|
+
{ type: 'flow-node', id: 'build', label: 'Build', shape: 'rounded-box' },
|
|
154
|
+
{ type: 'flow-node', id: 'test', label: 'Test', shape: 'rounded-box' },
|
|
155
|
+
{ type: 'flow-node', id: 'deploy', label: 'Deploy', shape: 'rounded-box' },
|
|
156
|
+
{ type: 'flow-node', id: 'observe', label: 'Observe', shape: 'rounded-box' },
|
|
157
|
+
],
|
|
158
|
+
layout: {
|
|
159
|
+
mode: 'ellipse',
|
|
160
|
+
cx: 600,
|
|
161
|
+
cy: 340,
|
|
162
|
+
rx: 280,
|
|
163
|
+
ry: 180,
|
|
164
|
+
startAngle: -90,
|
|
165
|
+
diagramCenter: { x: 600, y: 340 },
|
|
166
|
+
ellipseRx: 280,
|
|
167
|
+
ellipseRy: 180,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
141
172
|
## Programmatic Usage
|
|
142
173
|
|
|
143
174
|
```ts
|
|
@@ -157,7 +188,17 @@ const spec = parseDesignSpec({
|
|
|
157
188
|
{ type: 'flow-node', id: 'b', label: 'End', shape: 'rounded-box', color: '#059669' },
|
|
158
189
|
{ type: 'connection', from: 'a', to: 'b', label: 'next', routing: 'arc' },
|
|
159
190
|
],
|
|
160
|
-
layout: {
|
|
191
|
+
layout: {
|
|
192
|
+
mode: 'ellipse',
|
|
193
|
+
cx: 600,
|
|
194
|
+
cy: 340,
|
|
195
|
+
rx: 280,
|
|
196
|
+
ry: 180,
|
|
197
|
+
startAngle: -90,
|
|
198
|
+
diagramCenter: { x: 600, y: 340 },
|
|
199
|
+
ellipseRx: 280,
|
|
200
|
+
ellipseRy: 180,
|
|
201
|
+
},
|
|
161
202
|
});
|
|
162
203
|
|
|
163
204
|
const render = await renderDesign(spec, { generatorVersion: '0.3.0' });
|
package/dist/cli.js
CHANGED
|
@@ -766,6 +766,10 @@ var drawShadowSchema = z2.object({
|
|
|
766
766
|
offsetY: z2.number().default(4)
|
|
767
767
|
}).strict();
|
|
768
768
|
var drawFontFamilySchema = z2.enum(["heading", "body", "mono"]);
|
|
769
|
+
var strokeGradientSchema = z2.object({
|
|
770
|
+
from: colorHexSchema2,
|
|
771
|
+
to: colorHexSchema2
|
|
772
|
+
}).strict();
|
|
769
773
|
var drawRectSchema = z2.object({
|
|
770
774
|
type: z2.literal("rect"),
|
|
771
775
|
x: z2.number(),
|
|
@@ -813,6 +817,7 @@ var drawLineSchema = z2.object({
|
|
|
813
817
|
x2: z2.number(),
|
|
814
818
|
y2: z2.number(),
|
|
815
819
|
color: colorHexSchema2.default("#FFFFFF"),
|
|
820
|
+
strokeGradient: strokeGradientSchema.optional(),
|
|
816
821
|
width: z2.number().min(0.5).max(32).default(2),
|
|
817
822
|
dash: z2.array(z2.number()).max(6).optional(),
|
|
818
823
|
arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
|
|
@@ -843,6 +848,7 @@ var drawBezierSchema = z2.object({
|
|
|
843
848
|
type: z2.literal("bezier"),
|
|
844
849
|
points: z2.array(drawPointSchema).min(2).max(20),
|
|
845
850
|
color: colorHexSchema2.default("#FFFFFF"),
|
|
851
|
+
strokeGradient: strokeGradientSchema.optional(),
|
|
846
852
|
width: z2.number().min(0.5).max(32).default(2),
|
|
847
853
|
dash: z2.array(z2.number()).max(6).optional(),
|
|
848
854
|
arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
|
|
@@ -1189,6 +1195,20 @@ var stackLayoutConfigSchema = z2.object({
|
|
|
1189
1195
|
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1190
1196
|
ellipseRy: z2.number().positive().optional()
|
|
1191
1197
|
}).strict();
|
|
1198
|
+
var ellipseLayoutConfigSchema = z2.object({
|
|
1199
|
+
mode: z2.literal("ellipse"),
|
|
1200
|
+
cx: z2.number().optional(),
|
|
1201
|
+
cy: z2.number().optional(),
|
|
1202
|
+
rx: z2.number().positive(),
|
|
1203
|
+
ry: z2.number().positive(),
|
|
1204
|
+
startAngle: z2.number().default(-90),
|
|
1205
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
1206
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
1207
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1208
|
+
ellipseRx: z2.number().positive().optional(),
|
|
1209
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1210
|
+
ellipseRy: z2.number().positive().optional()
|
|
1211
|
+
}).strict();
|
|
1192
1212
|
var manualPositionSchema = z2.object({
|
|
1193
1213
|
x: z2.number().int(),
|
|
1194
1214
|
y: z2.number().int(),
|
|
@@ -1209,6 +1229,7 @@ var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
|
1209
1229
|
autoLayoutConfigSchema,
|
|
1210
1230
|
gridLayoutConfigSchema,
|
|
1211
1231
|
stackLayoutConfigSchema,
|
|
1232
|
+
ellipseLayoutConfigSchema,
|
|
1212
1233
|
manualLayoutConfigSchema
|
|
1213
1234
|
]);
|
|
1214
1235
|
var constraintsSchema = z2.object({
|
|
@@ -2439,6 +2460,35 @@ async function computeElkLayout(elements, config, safeFrame) {
|
|
|
2439
2460
|
};
|
|
2440
2461
|
}
|
|
2441
2462
|
|
|
2463
|
+
// src/layout/ellipse.ts
|
|
2464
|
+
function clampDimension(estimated, max) {
|
|
2465
|
+
return Math.max(1, Math.min(max, Math.floor(estimated)));
|
|
2466
|
+
}
|
|
2467
|
+
function computeEllipseLayout(elements, config, safeFrame) {
|
|
2468
|
+
const placeable = elements.filter((element) => element.type !== "connection");
|
|
2469
|
+
const positions = /* @__PURE__ */ new Map();
|
|
2470
|
+
if (placeable.length === 0) {
|
|
2471
|
+
return { positions };
|
|
2472
|
+
}
|
|
2473
|
+
const cx = config.cx ?? safeFrame.x + safeFrame.width / 2;
|
|
2474
|
+
const cy = config.cy ?? safeFrame.y + safeFrame.height / 2;
|
|
2475
|
+
const stepDegrees = 360 / placeable.length;
|
|
2476
|
+
for (const [index, element] of placeable.entries()) {
|
|
2477
|
+
const angleRadians = (config.startAngle + index * stepDegrees) * Math.PI / 180;
|
|
2478
|
+
const centerX = cx + config.rx * Math.cos(angleRadians);
|
|
2479
|
+
const centerY = cy + config.ry * Math.sin(angleRadians);
|
|
2480
|
+
const width = clampDimension(estimateElementWidth(element), safeFrame.width);
|
|
2481
|
+
const height = clampDimension(estimateElementHeight(element), safeFrame.height);
|
|
2482
|
+
positions.set(element.id, {
|
|
2483
|
+
x: Math.round(centerX - width / 2),
|
|
2484
|
+
y: Math.round(centerY - height / 2),
|
|
2485
|
+
width,
|
|
2486
|
+
height
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
return { positions };
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2442
2492
|
// src/layout/grid.ts
|
|
2443
2493
|
function computeGridLayout(elements, config, safeFrame) {
|
|
2444
2494
|
const placeable = elements.filter((element) => element.type !== "connection");
|
|
@@ -2534,6 +2584,8 @@ async function computeLayout(elements, layout, safeFrame) {
|
|
|
2534
2584
|
return computeGridLayout(elements, layout, safeFrame);
|
|
2535
2585
|
case "stack":
|
|
2536
2586
|
return computeStackLayout(elements, layout, safeFrame);
|
|
2587
|
+
case "ellipse":
|
|
2588
|
+
return computeEllipseLayout(elements, layout, safeFrame);
|
|
2537
2589
|
case "manual":
|
|
2538
2590
|
return computeManualLayout(elements, layout, safeFrame);
|
|
2539
2591
|
default:
|
|
@@ -3894,6 +3946,24 @@ function fromPoints(points) {
|
|
|
3894
3946
|
function resolveDrawFont(theme, family) {
|
|
3895
3947
|
return resolveFont(theme.fonts[family], family);
|
|
3896
3948
|
}
|
|
3949
|
+
function createDrawStrokeGradient(ctx, start, end, strokeGradient) {
|
|
3950
|
+
const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
|
|
3951
|
+
gradient.addColorStop(0, strokeGradient.from);
|
|
3952
|
+
gradient.addColorStop(1, strokeGradient.to);
|
|
3953
|
+
return gradient;
|
|
3954
|
+
}
|
|
3955
|
+
function resolveDrawStroke(ctx, start, end, color, strokeGradient) {
|
|
3956
|
+
if (!strokeGradient) {
|
|
3957
|
+
return color;
|
|
3958
|
+
}
|
|
3959
|
+
return createDrawStrokeGradient(ctx, start, end, strokeGradient);
|
|
3960
|
+
}
|
|
3961
|
+
function resolveArrowFill(color, strokeGradient, position) {
|
|
3962
|
+
if (!strokeGradient) {
|
|
3963
|
+
return color;
|
|
3964
|
+
}
|
|
3965
|
+
return position === "start" ? strokeGradient.from : strokeGradient.to;
|
|
3966
|
+
}
|
|
3897
3967
|
function measureSpacedTextWidth(ctx, text, letterSpacing) {
|
|
3898
3968
|
const chars = [...text];
|
|
3899
3969
|
if (chars.length === 0) {
|
|
@@ -4172,18 +4242,31 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4172
4242
|
const from = { x: command.x1, y: command.y1 };
|
|
4173
4243
|
const to = { x: command.x2, y: command.y2 };
|
|
4174
4244
|
const lineAngle = angleBetween(from, to);
|
|
4245
|
+
const stroke = resolveDrawStroke(ctx, from, to, command.color, command.strokeGradient);
|
|
4175
4246
|
withOpacity(ctx, command.opacity, () => {
|
|
4176
4247
|
applyDrawShadow(ctx, command.shadow);
|
|
4177
4248
|
drawLine(ctx, from, to, {
|
|
4178
|
-
color:
|
|
4249
|
+
color: stroke,
|
|
4179
4250
|
width: command.width,
|
|
4180
4251
|
...command.dash ? { dash: command.dash } : {}
|
|
4181
4252
|
});
|
|
4182
4253
|
if (command.arrow === "end" || command.arrow === "both") {
|
|
4183
|
-
drawArrowhead(
|
|
4254
|
+
drawArrowhead(
|
|
4255
|
+
ctx,
|
|
4256
|
+
to,
|
|
4257
|
+
lineAngle,
|
|
4258
|
+
command.arrowSize,
|
|
4259
|
+
resolveArrowFill(command.color, command.strokeGradient, "end")
|
|
4260
|
+
);
|
|
4184
4261
|
}
|
|
4185
4262
|
if (command.arrow === "start" || command.arrow === "both") {
|
|
4186
|
-
drawArrowhead(
|
|
4263
|
+
drawArrowhead(
|
|
4264
|
+
ctx,
|
|
4265
|
+
from,
|
|
4266
|
+
lineAngle + Math.PI,
|
|
4267
|
+
command.arrowSize,
|
|
4268
|
+
resolveArrowFill(command.color, command.strokeGradient, "start")
|
|
4269
|
+
);
|
|
4187
4270
|
}
|
|
4188
4271
|
});
|
|
4189
4272
|
const arrowPadding = command.arrow === "none" ? 0 : command.arrowSize;
|
|
@@ -4191,7 +4274,7 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4191
4274
|
id,
|
|
4192
4275
|
kind: "draw",
|
|
4193
4276
|
bounds: expandRect(fromPoints([from, to]), Math.max(command.width / 2, arrowPadding)),
|
|
4194
|
-
foregroundColor: command.color
|
|
4277
|
+
foregroundColor: command.strokeGradient?.from ?? command.color
|
|
4195
4278
|
});
|
|
4196
4279
|
break;
|
|
4197
4280
|
}
|
|
@@ -4225,10 +4308,17 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4225
4308
|
}
|
|
4226
4309
|
case "bezier": {
|
|
4227
4310
|
const points = command.points;
|
|
4311
|
+
const stroke = resolveDrawStroke(
|
|
4312
|
+
ctx,
|
|
4313
|
+
points[0],
|
|
4314
|
+
points[points.length - 1],
|
|
4315
|
+
command.color,
|
|
4316
|
+
command.strokeGradient
|
|
4317
|
+
);
|
|
4228
4318
|
withOpacity(ctx, command.opacity, () => {
|
|
4229
4319
|
applyDrawShadow(ctx, command.shadow);
|
|
4230
4320
|
drawBezier(ctx, points, {
|
|
4231
|
-
color:
|
|
4321
|
+
color: stroke,
|
|
4232
4322
|
width: command.width,
|
|
4233
4323
|
...command.dash ? { dash: command.dash } : {}
|
|
4234
4324
|
});
|
|
@@ -4240,11 +4330,17 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4240
4330
|
points[points.length - 1],
|
|
4241
4331
|
endAngle,
|
|
4242
4332
|
command.arrowSize,
|
|
4243
|
-
command.color
|
|
4333
|
+
resolveArrowFill(command.color, command.strokeGradient, "end")
|
|
4244
4334
|
);
|
|
4245
4335
|
}
|
|
4246
4336
|
if (command.arrow === "start" || command.arrow === "both") {
|
|
4247
|
-
drawArrowhead(
|
|
4337
|
+
drawArrowhead(
|
|
4338
|
+
ctx,
|
|
4339
|
+
points[0],
|
|
4340
|
+
startAngle + Math.PI,
|
|
4341
|
+
command.arrowSize,
|
|
4342
|
+
resolveArrowFill(command.color, command.strokeGradient, "start")
|
|
4343
|
+
);
|
|
4248
4344
|
}
|
|
4249
4345
|
});
|
|
4250
4346
|
const arrowPadding = command.arrow === "none" ? 0 : command.arrowSize;
|
|
@@ -4252,7 +4348,7 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4252
4348
|
id,
|
|
4253
4349
|
kind: "draw",
|
|
4254
4350
|
bounds: expandRect(fromPoints(points), Math.max(command.width / 2, arrowPadding)),
|
|
4255
|
-
foregroundColor: command.color
|
|
4351
|
+
foregroundColor: command.strokeGradient?.from ?? command.color
|
|
4256
4352
|
});
|
|
4257
4353
|
break;
|
|
4258
4354
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Cli } from 'incur';
|
|
2
|
-
import { T as ThemeInput, D as DesignSpec, a as Rect$1, b as DrawCommand, c as Theme, d as RenderedElement, A as AnchorHint, C as ConnectionElement } from './spec.schema-
|
|
3
|
-
export { e as AutoLayoutConfig, B as BuiltInTheme, f as CardElement, g as CodeBlockElement, h as ConstraintSpec, i as DEFAULT_GENERATOR_VERSION, j as DEFAULT_RAINBOW_COLORS, k as Decorator, l as DesignCardSpec, m as DesignSafeFrame, n as DesignTheme, o as DiagramElement, p as DiagramLayout, q as DiagramSpec, r as DrawArc, s as DrawBadge, t as DrawBezier, u as DrawCircle, v as DrawFontFamily, w as DrawGradientRect, x as DrawLine, y as DrawPath, z as DrawPoint, E as DrawRect, F as DrawShadow, G as DrawText, H as DrawTextRow, I as DrawTextRowSegment, J as Element, K as
|
|
2
|
+
import { T as ThemeInput, D as DesignSpec, a as Rect$1, b as DrawCommand, c as Theme, d as RenderedElement, A as AnchorHint, C as ConnectionElement } from './spec.schema-BkbcnVcm.js';
|
|
3
|
+
export { e as AutoLayoutConfig, B as BuiltInTheme, f as CardElement, g as CodeBlockElement, h as ConstraintSpec, i as DEFAULT_GENERATOR_VERSION, j as DEFAULT_RAINBOW_COLORS, k as Decorator, l as DesignCardSpec, m as DesignSafeFrame, n as DesignTheme, o as DiagramElement, p as DiagramLayout, q as DiagramSpec, r as DrawArc, s as DrawBadge, t as DrawBezier, u as DrawCircle, v as DrawFontFamily, w as DrawGradientRect, x as DrawLine, y as DrawPath, z as DrawPoint, E as DrawRect, F as DrawShadow, G as DrawText, H as DrawTextRow, I as DrawTextRowSegment, J as Element, K as EllipseLayoutConfig, L as FlowNodeElement, M as Gradient, N as GradientOverlayDecorator, O as GradientSpec, P as GradientStop, Q as GridLayoutConfig, S as ImageElement, U as IterationMeta, V as LayoutConfig, W as LayoutSnapshot, X as ManualLayoutConfig, Y as RainbowRuleDecorator, Z as RenderDesignOptions, R as RenderMetadata, _ as RenderResult, $ as ShapeElement, a0 as StackLayoutConfig, a1 as TerminalElement, a2 as TextElement, a3 as ThemeInput, a4 as VignetteDecorator, a5 as WrittenArtifacts, a6 as builtInThemeBackgrounds, a7 as builtInThemes, a8 as computeSpecHash, a9 as connectionElementSchema, aa as defaultAutoLayout, ab as defaultCanvas, ac as defaultConstraints, ad as defaultGridLayout, ae as defaultLayout, af as defaultStackLayout, ag as defaultTheme, ah as deriveSafeFrame, ai as designSpecSchema, aj as diagramElementSchema, ak as diagramLayoutSchema, al as diagramSpecSchema, am as drawGradientRect, an as drawRainbowRule, ao as drawVignette, ap as flowNodeElementSchema, aq as inferLayout, ar as inferSidecarPath, as as parseDesignSpec, at as parseDiagramSpec, au as renderDesign, av as resolveTheme, aw as writeRenderArtifacts } from './spec.schema-BkbcnVcm.js';
|
|
4
4
|
import { SKRSContext2D } from '@napi-rs/canvas';
|
|
5
5
|
export { QaIssue, QaReferenceResult, QaReport, QaSeverity, readMetadata, runQa } from './qa.js';
|
|
6
6
|
import { Highlighter } from 'shiki';
|
package/dist/index.js
CHANGED
|
@@ -775,6 +775,10 @@ var drawShadowSchema = z2.object({
|
|
|
775
775
|
offsetY: z2.number().default(4)
|
|
776
776
|
}).strict();
|
|
777
777
|
var drawFontFamilySchema = z2.enum(["heading", "body", "mono"]);
|
|
778
|
+
var strokeGradientSchema = z2.object({
|
|
779
|
+
from: colorHexSchema2,
|
|
780
|
+
to: colorHexSchema2
|
|
781
|
+
}).strict();
|
|
778
782
|
var drawRectSchema = z2.object({
|
|
779
783
|
type: z2.literal("rect"),
|
|
780
784
|
x: z2.number(),
|
|
@@ -822,6 +826,7 @@ var drawLineSchema = z2.object({
|
|
|
822
826
|
x2: z2.number(),
|
|
823
827
|
y2: z2.number(),
|
|
824
828
|
color: colorHexSchema2.default("#FFFFFF"),
|
|
829
|
+
strokeGradient: strokeGradientSchema.optional(),
|
|
825
830
|
width: z2.number().min(0.5).max(32).default(2),
|
|
826
831
|
dash: z2.array(z2.number()).max(6).optional(),
|
|
827
832
|
arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
|
|
@@ -852,6 +857,7 @@ var drawBezierSchema = z2.object({
|
|
|
852
857
|
type: z2.literal("bezier"),
|
|
853
858
|
points: z2.array(drawPointSchema).min(2).max(20),
|
|
854
859
|
color: colorHexSchema2.default("#FFFFFF"),
|
|
860
|
+
strokeGradient: strokeGradientSchema.optional(),
|
|
855
861
|
width: z2.number().min(0.5).max(32).default(2),
|
|
856
862
|
dash: z2.array(z2.number()).max(6).optional(),
|
|
857
863
|
arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
|
|
@@ -1199,6 +1205,20 @@ var stackLayoutConfigSchema = z2.object({
|
|
|
1199
1205
|
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1200
1206
|
ellipseRy: z2.number().positive().optional()
|
|
1201
1207
|
}).strict();
|
|
1208
|
+
var ellipseLayoutConfigSchema = z2.object({
|
|
1209
|
+
mode: z2.literal("ellipse"),
|
|
1210
|
+
cx: z2.number().optional(),
|
|
1211
|
+
cy: z2.number().optional(),
|
|
1212
|
+
rx: z2.number().positive(),
|
|
1213
|
+
ry: z2.number().positive(),
|
|
1214
|
+
startAngle: z2.number().default(-90),
|
|
1215
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
1216
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
1217
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1218
|
+
ellipseRx: z2.number().positive().optional(),
|
|
1219
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
1220
|
+
ellipseRy: z2.number().positive().optional()
|
|
1221
|
+
}).strict();
|
|
1202
1222
|
var manualPositionSchema = z2.object({
|
|
1203
1223
|
x: z2.number().int(),
|
|
1204
1224
|
y: z2.number().int(),
|
|
@@ -1219,6 +1239,7 @@ var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
|
1219
1239
|
autoLayoutConfigSchema,
|
|
1220
1240
|
gridLayoutConfigSchema,
|
|
1221
1241
|
stackLayoutConfigSchema,
|
|
1242
|
+
ellipseLayoutConfigSchema,
|
|
1222
1243
|
manualLayoutConfigSchema
|
|
1223
1244
|
]);
|
|
1224
1245
|
var constraintsSchema = z2.object({
|
|
@@ -2452,6 +2473,35 @@ async function computeElkLayout(elements, config, safeFrame) {
|
|
|
2452
2473
|
};
|
|
2453
2474
|
}
|
|
2454
2475
|
|
|
2476
|
+
// src/layout/ellipse.ts
|
|
2477
|
+
function clampDimension(estimated, max) {
|
|
2478
|
+
return Math.max(1, Math.min(max, Math.floor(estimated)));
|
|
2479
|
+
}
|
|
2480
|
+
function computeEllipseLayout(elements, config, safeFrame) {
|
|
2481
|
+
const placeable = elements.filter((element) => element.type !== "connection");
|
|
2482
|
+
const positions = /* @__PURE__ */ new Map();
|
|
2483
|
+
if (placeable.length === 0) {
|
|
2484
|
+
return { positions };
|
|
2485
|
+
}
|
|
2486
|
+
const cx = config.cx ?? safeFrame.x + safeFrame.width / 2;
|
|
2487
|
+
const cy = config.cy ?? safeFrame.y + safeFrame.height / 2;
|
|
2488
|
+
const stepDegrees = 360 / placeable.length;
|
|
2489
|
+
for (const [index, element] of placeable.entries()) {
|
|
2490
|
+
const angleRadians = (config.startAngle + index * stepDegrees) * Math.PI / 180;
|
|
2491
|
+
const centerX = cx + config.rx * Math.cos(angleRadians);
|
|
2492
|
+
const centerY = cy + config.ry * Math.sin(angleRadians);
|
|
2493
|
+
const width = clampDimension(estimateElementWidth(element), safeFrame.width);
|
|
2494
|
+
const height = clampDimension(estimateElementHeight(element), safeFrame.height);
|
|
2495
|
+
positions.set(element.id, {
|
|
2496
|
+
x: Math.round(centerX - width / 2),
|
|
2497
|
+
y: Math.round(centerY - height / 2),
|
|
2498
|
+
width,
|
|
2499
|
+
height
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
return { positions };
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2455
2505
|
// src/layout/grid.ts
|
|
2456
2506
|
function computeGridLayout(elements, config, safeFrame) {
|
|
2457
2507
|
const placeable = elements.filter((element) => element.type !== "connection");
|
|
@@ -2547,6 +2597,8 @@ async function computeLayout(elements, layout, safeFrame) {
|
|
|
2547
2597
|
return computeGridLayout(elements, layout, safeFrame);
|
|
2548
2598
|
case "stack":
|
|
2549
2599
|
return computeStackLayout(elements, layout, safeFrame);
|
|
2600
|
+
case "ellipse":
|
|
2601
|
+
return computeEllipseLayout(elements, layout, safeFrame);
|
|
2550
2602
|
case "manual":
|
|
2551
2603
|
return computeManualLayout(elements, layout, safeFrame);
|
|
2552
2604
|
default:
|
|
@@ -3962,6 +4014,24 @@ function fromPoints(points) {
|
|
|
3962
4014
|
function resolveDrawFont(theme, family) {
|
|
3963
4015
|
return resolveFont(theme.fonts[family], family);
|
|
3964
4016
|
}
|
|
4017
|
+
function createDrawStrokeGradient(ctx, start, end, strokeGradient) {
|
|
4018
|
+
const gradient = ctx.createLinearGradient(start.x, start.y, end.x, end.y);
|
|
4019
|
+
gradient.addColorStop(0, strokeGradient.from);
|
|
4020
|
+
gradient.addColorStop(1, strokeGradient.to);
|
|
4021
|
+
return gradient;
|
|
4022
|
+
}
|
|
4023
|
+
function resolveDrawStroke(ctx, start, end, color, strokeGradient) {
|
|
4024
|
+
if (!strokeGradient) {
|
|
4025
|
+
return color;
|
|
4026
|
+
}
|
|
4027
|
+
return createDrawStrokeGradient(ctx, start, end, strokeGradient);
|
|
4028
|
+
}
|
|
4029
|
+
function resolveArrowFill(color, strokeGradient, position) {
|
|
4030
|
+
if (!strokeGradient) {
|
|
4031
|
+
return color;
|
|
4032
|
+
}
|
|
4033
|
+
return position === "start" ? strokeGradient.from : strokeGradient.to;
|
|
4034
|
+
}
|
|
3965
4035
|
function measureSpacedTextWidth(ctx, text, letterSpacing) {
|
|
3966
4036
|
const chars = [...text];
|
|
3967
4037
|
if (chars.length === 0) {
|
|
@@ -4240,18 +4310,31 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4240
4310
|
const from = { x: command.x1, y: command.y1 };
|
|
4241
4311
|
const to = { x: command.x2, y: command.y2 };
|
|
4242
4312
|
const lineAngle = angleBetween(from, to);
|
|
4313
|
+
const stroke = resolveDrawStroke(ctx, from, to, command.color, command.strokeGradient);
|
|
4243
4314
|
withOpacity(ctx, command.opacity, () => {
|
|
4244
4315
|
applyDrawShadow(ctx, command.shadow);
|
|
4245
4316
|
drawLine(ctx, from, to, {
|
|
4246
|
-
color:
|
|
4317
|
+
color: stroke,
|
|
4247
4318
|
width: command.width,
|
|
4248
4319
|
...command.dash ? { dash: command.dash } : {}
|
|
4249
4320
|
});
|
|
4250
4321
|
if (command.arrow === "end" || command.arrow === "both") {
|
|
4251
|
-
drawArrowhead(
|
|
4322
|
+
drawArrowhead(
|
|
4323
|
+
ctx,
|
|
4324
|
+
to,
|
|
4325
|
+
lineAngle,
|
|
4326
|
+
command.arrowSize,
|
|
4327
|
+
resolveArrowFill(command.color, command.strokeGradient, "end")
|
|
4328
|
+
);
|
|
4252
4329
|
}
|
|
4253
4330
|
if (command.arrow === "start" || command.arrow === "both") {
|
|
4254
|
-
drawArrowhead(
|
|
4331
|
+
drawArrowhead(
|
|
4332
|
+
ctx,
|
|
4333
|
+
from,
|
|
4334
|
+
lineAngle + Math.PI,
|
|
4335
|
+
command.arrowSize,
|
|
4336
|
+
resolveArrowFill(command.color, command.strokeGradient, "start")
|
|
4337
|
+
);
|
|
4255
4338
|
}
|
|
4256
4339
|
});
|
|
4257
4340
|
const arrowPadding = command.arrow === "none" ? 0 : command.arrowSize;
|
|
@@ -4259,7 +4342,7 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4259
4342
|
id,
|
|
4260
4343
|
kind: "draw",
|
|
4261
4344
|
bounds: expandRect(fromPoints([from, to]), Math.max(command.width / 2, arrowPadding)),
|
|
4262
|
-
foregroundColor: command.color
|
|
4345
|
+
foregroundColor: command.strokeGradient?.from ?? command.color
|
|
4263
4346
|
});
|
|
4264
4347
|
break;
|
|
4265
4348
|
}
|
|
@@ -4293,10 +4376,17 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4293
4376
|
}
|
|
4294
4377
|
case "bezier": {
|
|
4295
4378
|
const points = command.points;
|
|
4379
|
+
const stroke = resolveDrawStroke(
|
|
4380
|
+
ctx,
|
|
4381
|
+
points[0],
|
|
4382
|
+
points[points.length - 1],
|
|
4383
|
+
command.color,
|
|
4384
|
+
command.strokeGradient
|
|
4385
|
+
);
|
|
4296
4386
|
withOpacity(ctx, command.opacity, () => {
|
|
4297
4387
|
applyDrawShadow(ctx, command.shadow);
|
|
4298
4388
|
drawBezier(ctx, points, {
|
|
4299
|
-
color:
|
|
4389
|
+
color: stroke,
|
|
4300
4390
|
width: command.width,
|
|
4301
4391
|
...command.dash ? { dash: command.dash } : {}
|
|
4302
4392
|
});
|
|
@@ -4308,11 +4398,17 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4308
4398
|
points[points.length - 1],
|
|
4309
4399
|
endAngle,
|
|
4310
4400
|
command.arrowSize,
|
|
4311
|
-
command.color
|
|
4401
|
+
resolveArrowFill(command.color, command.strokeGradient, "end")
|
|
4312
4402
|
);
|
|
4313
4403
|
}
|
|
4314
4404
|
if (command.arrow === "start" || command.arrow === "both") {
|
|
4315
|
-
drawArrowhead(
|
|
4405
|
+
drawArrowhead(
|
|
4406
|
+
ctx,
|
|
4407
|
+
points[0],
|
|
4408
|
+
startAngle + Math.PI,
|
|
4409
|
+
command.arrowSize,
|
|
4410
|
+
resolveArrowFill(command.color, command.strokeGradient, "start")
|
|
4411
|
+
);
|
|
4316
4412
|
}
|
|
4317
4413
|
});
|
|
4318
4414
|
const arrowPadding = command.arrow === "none" ? 0 : command.arrowSize;
|
|
@@ -4320,7 +4416,7 @@ function renderDrawCommands(ctx, commands, theme) {
|
|
|
4320
4416
|
id,
|
|
4321
4417
|
kind: "draw",
|
|
4322
4418
|
bounds: expandRect(fromPoints(points), Math.max(command.width / 2, arrowPadding)),
|
|
4323
|
-
foregroundColor: command.color
|
|
4419
|
+
foregroundColor: command.strokeGradient?.from ?? command.color
|
|
4324
4420
|
});
|
|
4325
4421
|
break;
|
|
4326
4422
|
}
|
package/dist/qa.d.ts
CHANGED
package/dist/qa.js
CHANGED
|
@@ -509,6 +509,10 @@ var drawShadowSchema = z2.object({
|
|
|
509
509
|
offsetY: z2.number().default(4)
|
|
510
510
|
}).strict();
|
|
511
511
|
var drawFontFamilySchema = z2.enum(["heading", "body", "mono"]);
|
|
512
|
+
var strokeGradientSchema = z2.object({
|
|
513
|
+
from: colorHexSchema2,
|
|
514
|
+
to: colorHexSchema2
|
|
515
|
+
}).strict();
|
|
512
516
|
var drawRectSchema = z2.object({
|
|
513
517
|
type: z2.literal("rect"),
|
|
514
518
|
x: z2.number(),
|
|
@@ -556,6 +560,7 @@ var drawLineSchema = z2.object({
|
|
|
556
560
|
x2: z2.number(),
|
|
557
561
|
y2: z2.number(),
|
|
558
562
|
color: colorHexSchema2.default("#FFFFFF"),
|
|
563
|
+
strokeGradient: strokeGradientSchema.optional(),
|
|
559
564
|
width: z2.number().min(0.5).max(32).default(2),
|
|
560
565
|
dash: z2.array(z2.number()).max(6).optional(),
|
|
561
566
|
arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
|
|
@@ -586,6 +591,7 @@ var drawBezierSchema = z2.object({
|
|
|
586
591
|
type: z2.literal("bezier"),
|
|
587
592
|
points: z2.array(drawPointSchema).min(2).max(20),
|
|
588
593
|
color: colorHexSchema2.default("#FFFFFF"),
|
|
594
|
+
strokeGradient: strokeGradientSchema.optional(),
|
|
589
595
|
width: z2.number().min(0.5).max(32).default(2),
|
|
590
596
|
dash: z2.array(z2.number()).max(6).optional(),
|
|
591
597
|
arrow: z2.enum(["none", "end", "start", "both"]).default("none"),
|
|
@@ -932,6 +938,20 @@ var stackLayoutConfigSchema = z2.object({
|
|
|
932
938
|
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
933
939
|
ellipseRy: z2.number().positive().optional()
|
|
934
940
|
}).strict();
|
|
941
|
+
var ellipseLayoutConfigSchema = z2.object({
|
|
942
|
+
mode: z2.literal("ellipse"),
|
|
943
|
+
cx: z2.number().optional(),
|
|
944
|
+
cy: z2.number().optional(),
|
|
945
|
+
rx: z2.number().positive(),
|
|
946
|
+
ry: z2.number().positive(),
|
|
947
|
+
startAngle: z2.number().default(-90),
|
|
948
|
+
/** Explicit center used by curve/arc connection routing. */
|
|
949
|
+
diagramCenter: diagramCenterSchema.optional(),
|
|
950
|
+
/** Horizontal radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
951
|
+
ellipseRx: z2.number().positive().optional(),
|
|
952
|
+
/** Vertical radius for shared ellipse used by curveMode: 'ellipse'. */
|
|
953
|
+
ellipseRy: z2.number().positive().optional()
|
|
954
|
+
}).strict();
|
|
935
955
|
var manualPositionSchema = z2.object({
|
|
936
956
|
x: z2.number().int(),
|
|
937
957
|
y: z2.number().int(),
|
|
@@ -952,6 +972,7 @@ var layoutConfigSchema = z2.discriminatedUnion("mode", [
|
|
|
952
972
|
autoLayoutConfigSchema,
|
|
953
973
|
gridLayoutConfigSchema,
|
|
954
974
|
stackLayoutConfigSchema,
|
|
975
|
+
ellipseLayoutConfigSchema,
|
|
955
976
|
manualLayoutConfigSchema
|
|
956
977
|
]);
|
|
957
978
|
var constraintsSchema = z2.object({
|
package/dist/renderer.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { i as DEFAULT_GENERATOR_VERSION,
|
|
1
|
+
export { i as DEFAULT_GENERATOR_VERSION, U as IterationMeta, W as LayoutSnapshot, a as Rect, Z as RenderDesignOptions, R as RenderMetadata, _ as RenderResult, d as RenderedElement, a5 as WrittenArtifacts, a8 as computeSpecHash, ar as inferSidecarPath, au as renderDesign, aw as writeRenderArtifacts } from './spec.schema-BkbcnVcm.js';
|
|
2
2
|
import 'zod';
|
|
3
3
|
import '@napi-rs/canvas';
|