@dr2rai/raid-canvas 0.3.0 → 0.3.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/dist/RaiBridge.d.ts +10 -3
- package/dist/RaiBridge.d.ts.map +1 -1
- package/dist/RaiBridge.js +252 -22
- package/dist/RaiBridge.js.map +1 -1
- package/dist/RaidCanvas.d.ts +2 -0
- package/dist/RaidCanvas.d.ts.map +1 -1
- package/dist/RaidCanvas.js +39 -10
- package/dist/RaidCanvas.js.map +1 -1
- package/dist/X6Shapes.d.ts +7 -0
- package/dist/X6Shapes.d.ts.map +1 -1
- package/dist/X6Shapes.js +146 -22
- package/dist/X6Shapes.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/RaiBridge.ts +294 -25
- package/src/RaidCanvas.tsx +41 -7
- package/src/X6Shapes.ts +151 -22
- package/src/index.ts +1 -0
- package/src/styles/aoaim-theme.css +21 -7
- package/src/types.ts +16 -0
package/dist/types.d.ts
CHANGED
|
@@ -105,6 +105,8 @@ export interface RaidEdgeData {
|
|
|
105
105
|
readonly routing?: AimRoutingMode;
|
|
106
106
|
/** User-editable or router-computed Manhattan bend points. */
|
|
107
107
|
readonly bendPoints: readonly SvgBendPoint[];
|
|
108
|
+
/** Precomputed or live SVG path data ('M ... L ...') for standalone vector rendering. */
|
|
109
|
+
readonly pathData?: string;
|
|
108
110
|
}
|
|
109
111
|
/**
|
|
110
112
|
* Full in-memory metamodel representation corresponding to a `.raid` manifest.
|
|
@@ -120,6 +122,8 @@ export interface RaidMetamodel {
|
|
|
120
122
|
readonly nodes: readonly RaidNodeData[];
|
|
121
123
|
/** Relationships connecting projected nodes. */
|
|
122
124
|
readonly edges: readonly RaidEdgeData[];
|
|
125
|
+
/** Diagram-level edge routing mode ('manhattan', 'normal', 'smooth'). */
|
|
126
|
+
readonly routing?: AimRoutingMode;
|
|
123
127
|
/** Additional diagram-level metadata. */
|
|
124
128
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
125
129
|
}
|
|
@@ -152,6 +156,12 @@ export interface HydrationOptions {
|
|
|
152
156
|
readonly clearGraph?: boolean;
|
|
153
157
|
/** If true, automatically executes Manhattan routing if bend points are missing. Default: true. */
|
|
154
158
|
readonly autoRouteEdges?: boolean;
|
|
159
|
+
/**
|
|
160
|
+
* If true, infers docking ports (e.g. port-right, port-left) based on node geometry when ports
|
|
161
|
+
* are omitted in the SVG. When false (default), edges bind directly to node cells without ports,
|
|
162
|
+
* enabling dynamic Manhattan center-aiming routing. Default: false.
|
|
163
|
+
*/
|
|
164
|
+
readonly inferPorts?: boolean;
|
|
155
165
|
/** Default fallback dimensions when width/height are unspecified in SVG. */
|
|
156
166
|
readonly defaultNodeSize?: {
|
|
157
167
|
readonly width: number;
|
|
@@ -168,5 +178,7 @@ export interface SerializationOptions {
|
|
|
168
178
|
readonly format?: boolean;
|
|
169
179
|
/** Inject Cascais design token CSS variables into `<defs><style>`. Default: true. */
|
|
170
180
|
readonly embedStyles?: boolean;
|
|
181
|
+
/** Optional diagram-level routing mode to serialize onto root `<svg>` tag. */
|
|
182
|
+
readonly routingMode?: AimRoutingMode;
|
|
171
183
|
}
|
|
172
184
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqKH;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,4BAA4B;IAC5B,SAAS,EAAE,UAAU;IACrB,OAAO,EAAE,QAAQ;IACjB,SAAS,EAAE,UAAU;IACrB,iBAAiB,EAAE,kBAAkB;IACrC,eAAe,EAAE,gBAAgB;IACjC,cAAc,EAAE,eAAe;IAE/B,4BAA4B;IAC5B,SAAS,EAAE,UAAU;IACrB,cAAc,EAAE,eAAe;IAC/B,WAAW,EAAE,YAAY;IACzB,WAAW,EAAE,YAAY;IACzB,gBAAgB,EAAE,iBAAiB;IACnC,gBAAgB,EAAE,iBAAiB;IACnC,YAAY,EAAE,aAAa;IAC3B,UAAU,EAAE,WAAW;IAEvB,4BAA4B;IAC5B,aAAa,EAAE,sCAAsC;IACrD,aAAa,EAAE,iCAAiC;CACxC,CAAC"}
|
package/package.json
CHANGED
package/src/RaiBridge.ts
CHANGED
|
@@ -24,7 +24,13 @@ import {
|
|
|
24
24
|
type HydrationOptions,
|
|
25
25
|
type SerializationOptions,
|
|
26
26
|
} from './types.js';
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
createAimNode,
|
|
29
|
+
createAimEdge,
|
|
30
|
+
configureAimGraph,
|
|
31
|
+
CascaisPalette,
|
|
32
|
+
wrapAimText,
|
|
33
|
+
} from './X6Shapes.js';
|
|
28
34
|
|
|
29
35
|
export class RaiBridge {
|
|
30
36
|
/**
|
|
@@ -154,6 +160,34 @@ export class RaiBridge {
|
|
|
154
160
|
}
|
|
155
161
|
}
|
|
156
162
|
|
|
163
|
+
// Extract live rendered SVG path data from X6 EdgeView if available
|
|
164
|
+
let pathData: string | undefined = customData.pathData;
|
|
165
|
+
if (!pathData) {
|
|
166
|
+
const edgeView =
|
|
167
|
+
typeof (graph as unknown as { findViewByCell?: (cell: unknown) => unknown }).findViewByCell === 'function'
|
|
168
|
+
? (
|
|
169
|
+
graph as unknown as {
|
|
170
|
+
findViewByCell: (cell: unknown) => {
|
|
171
|
+
getConnectionPathData?: () => string;
|
|
172
|
+
container?: Element;
|
|
173
|
+
} | null;
|
|
174
|
+
}
|
|
175
|
+
).findViewByCell(edge)
|
|
176
|
+
: null;
|
|
177
|
+
|
|
178
|
+
if (edgeView) {
|
|
179
|
+
if (typeof edgeView.getConnectionPathData === 'function') {
|
|
180
|
+
const d = edgeView.getConnectionPathData();
|
|
181
|
+
if (d && d.trim().length > 0) pathData = d;
|
|
182
|
+
}
|
|
183
|
+
if (!pathData && edgeView.container) {
|
|
184
|
+
const pathEl = edgeView.container.querySelector('path[d]');
|
|
185
|
+
const d = pathEl?.getAttribute('d');
|
|
186
|
+
if (d && d.trim().length > 0) pathData = d;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
157
191
|
const edgeData: RaidEdgeData = {
|
|
158
192
|
id: edge.id,
|
|
159
193
|
kind: customData.kind ?? 'association',
|
|
@@ -167,16 +201,20 @@ export class RaiBridge {
|
|
|
167
201
|
...(customData.sourceCardinality !== undefined ? { sourceCardinality: customData.sourceCardinality } : {}),
|
|
168
202
|
...(customData.targetCardinality !== undefined ? { targetCardinality: customData.targetCardinality } : {}),
|
|
169
203
|
bendPoints,
|
|
204
|
+
...(pathData !== undefined ? { pathData } : {}),
|
|
170
205
|
};
|
|
171
206
|
|
|
172
207
|
edges.push(edgeData);
|
|
173
208
|
}
|
|
174
209
|
|
|
210
|
+
const diagramRouting = (graph as unknown as { _aimRoutingMode?: AimRoutingMode })._aimRoutingMode;
|
|
211
|
+
|
|
175
212
|
return {
|
|
176
213
|
diagramId: 'RaidDiagram',
|
|
177
214
|
archetype: 'InteractiveCanvas',
|
|
178
215
|
nodes,
|
|
179
216
|
edges,
|
|
217
|
+
...(diagramRouting !== undefined ? { routing: diagramRouting } : {}),
|
|
180
218
|
};
|
|
181
219
|
}
|
|
182
220
|
|
|
@@ -207,6 +245,79 @@ export class RaiBridge {
|
|
|
207
245
|
return points.map((p) => `${Math.round(p.x)},${Math.round(p.y)}`).join('; ');
|
|
208
246
|
}
|
|
209
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Computes a clean fallback SVG path connecting source and target nodes
|
|
250
|
+
* when running in headless environments (e.g. CLI, tests) without an active DOM.
|
|
251
|
+
*/
|
|
252
|
+
public computeFallbackEdgePath(
|
|
253
|
+
sourceNode: RaidNodeData,
|
|
254
|
+
targetNode: RaidNodeData,
|
|
255
|
+
bendPoints: readonly SvgBendPoint[] = [],
|
|
256
|
+
routingMode: AimRoutingMode = 'manhattan',
|
|
257
|
+
): string {
|
|
258
|
+
const scx = Math.round(sourceNode.bounds.x + sourceNode.bounds.width / 2);
|
|
259
|
+
const scy = Math.round(sourceNode.bounds.y + sourceNode.bounds.height / 2);
|
|
260
|
+
const tcx = Math.round(targetNode.bounds.x + targetNode.bounds.width / 2);
|
|
261
|
+
const tcy = Math.round(targetNode.bounds.y + targetNode.bounds.height / 2);
|
|
262
|
+
|
|
263
|
+
let sx = scx;
|
|
264
|
+
let sy = scy;
|
|
265
|
+
let tx = tcx;
|
|
266
|
+
let ty = tcy;
|
|
267
|
+
|
|
268
|
+
const dx = tcx - scx;
|
|
269
|
+
const dy = tcy - scy;
|
|
270
|
+
|
|
271
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
272
|
+
if (dx > 0) {
|
|
273
|
+
sx = Math.round(sourceNode.bounds.x + sourceNode.bounds.width);
|
|
274
|
+
sy = scy;
|
|
275
|
+
tx = Math.round(targetNode.bounds.x);
|
|
276
|
+
ty = tcy;
|
|
277
|
+
} else {
|
|
278
|
+
sx = Math.round(sourceNode.bounds.x);
|
|
279
|
+
sy = scy;
|
|
280
|
+
tx = Math.round(targetNode.bounds.x + targetNode.bounds.width);
|
|
281
|
+
ty = tcy;
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
if (dy > 0) {
|
|
285
|
+
sx = scx;
|
|
286
|
+
sy = Math.round(sourceNode.bounds.y + sourceNode.bounds.height);
|
|
287
|
+
tx = tcx;
|
|
288
|
+
ty = Math.round(targetNode.bounds.y);
|
|
289
|
+
} else {
|
|
290
|
+
sx = scx;
|
|
291
|
+
sy = Math.round(sourceNode.bounds.y);
|
|
292
|
+
tx = tcx;
|
|
293
|
+
ty = Math.round(targetNode.bounds.y + targetNode.bounds.height);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (bendPoints.length > 0) {
|
|
298
|
+
return `M ${sx} ${sy} ` + bendPoints.map((p) => `L ${Math.round(p.x)} ${Math.round(p.y)}`).join(' ') + ` L ${tx} ${ty}`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (routingMode === 'normal') {
|
|
302
|
+
return `M ${sx} ${sy} L ${tx} ${ty}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (routingMode === 'smooth') {
|
|
306
|
+
const midX = Math.round((sx + tx) / 2);
|
|
307
|
+
const midY = Math.round((sy + ty) / 2);
|
|
308
|
+
return `M ${sx} ${sy} Q ${midX} ${sy} ${midX} ${midY} T ${tx} ${ty}`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Manhattan orthogonal default
|
|
312
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
313
|
+
const midX = Math.round((sx + tx) / 2);
|
|
314
|
+
return `M ${sx} ${sy} L ${midX} ${sy} L ${midX} ${ty} L ${tx} ${ty}`;
|
|
315
|
+
} else {
|
|
316
|
+
const midY = Math.round((sy + ty) / 2);
|
|
317
|
+
return `M ${sx} ${sy} L ${sx} ${midY} L ${tx} ${midY} L ${tx} ${ty}`;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
210
321
|
// --------------------------------------------------------------------------
|
|
211
322
|
// Private Helper Implementation
|
|
212
323
|
// --------------------------------------------------------------------------
|
|
@@ -296,8 +407,8 @@ export class RaiBridge {
|
|
|
296
407
|
const sourceNode = nodes.find((n) => n.id === sourceId);
|
|
297
408
|
const targetNode = nodes.find((n) => n.id === targetId);
|
|
298
409
|
|
|
299
|
-
// Infer optimal orthogonal docking ports if
|
|
300
|
-
if (sourceNode && targetNode) {
|
|
410
|
+
// Infer optimal orthogonal docking ports only if explicitly requested in options
|
|
411
|
+
if (options.inferPorts === true && sourceNode && targetNode) {
|
|
301
412
|
const dx =
|
|
302
413
|
targetNode.bounds.x +
|
|
303
414
|
targetNode.bounds.width / 2 -
|
|
@@ -349,10 +460,7 @@ export class RaiBridge {
|
|
|
349
460
|
el.getAttribute(AimSvgContract.ATTR_ROUTING) ??
|
|
350
461
|
el.getAttribute('aim-routing') ??
|
|
351
462
|
undefined;
|
|
352
|
-
const routing =
|
|
353
|
-
rawRouting === 'normal' || rawRouting === 'smooth' || rawRouting === 'manhattan'
|
|
354
|
-
? (rawRouting as AimRoutingMode)
|
|
355
|
-
: undefined;
|
|
463
|
+
const routing = rawRouting ? this.normalizeRouting(rawRouting) : undefined;
|
|
356
464
|
|
|
357
465
|
edges.push({
|
|
358
466
|
id,
|
|
@@ -369,11 +477,18 @@ export class RaiBridge {
|
|
|
369
477
|
});
|
|
370
478
|
}
|
|
371
479
|
|
|
480
|
+
const rawDiagramRouting =
|
|
481
|
+
svgRoot.getAttribute(AimSvgContract.ATTR_ROUTING) ??
|
|
482
|
+
svgRoot.getAttribute('aim-routing') ??
|
|
483
|
+
undefined;
|
|
484
|
+
const diagramRouting = rawDiagramRouting ? this.normalizeRouting(rawDiagramRouting) : undefined;
|
|
485
|
+
|
|
372
486
|
return {
|
|
373
487
|
diagramId: svgRoot.getAttribute('id') ?? 'ImportedDiagram',
|
|
374
488
|
archetype: svgRoot.getAttribute('aim-archetype') ?? 'AOAIMDiagram',
|
|
375
489
|
nodes,
|
|
376
490
|
edges,
|
|
491
|
+
...(diagramRouting !== undefined ? { routing: diagramRouting } : {}),
|
|
377
492
|
};
|
|
378
493
|
}
|
|
379
494
|
|
|
@@ -397,7 +512,7 @@ export class RaiBridge {
|
|
|
397
512
|
}
|
|
398
513
|
|
|
399
514
|
// Check direct geometry attributes (rect, ellipse, circle)
|
|
400
|
-
const rect = el.querySelector('rect') ?? (el.tagName
|
|
515
|
+
const rect = el.querySelector?.('rect') ?? (el.tagName?.toLowerCase() === 'rect' ? el : null);
|
|
401
516
|
if (rect) {
|
|
402
517
|
if (!transform && rect.getAttribute('x')) x = parseFloat(rect.getAttribute('x')!);
|
|
403
518
|
if (!transform && rect.getAttribute('y')) y = parseFloat(rect.getAttribute('y')!);
|
|
@@ -406,7 +521,7 @@ export class RaiBridge {
|
|
|
406
521
|
}
|
|
407
522
|
|
|
408
523
|
const ellipse =
|
|
409
|
-
el.querySelector('ellipse') ?? (el.tagName
|
|
524
|
+
el.querySelector?.('ellipse') ?? (el.tagName?.toLowerCase() === 'ellipse' ? el : null);
|
|
410
525
|
if (ellipse) {
|
|
411
526
|
const rx = parseFloat(ellipse.getAttribute('rx') ?? `${width / 2}`);
|
|
412
527
|
const ry = parseFloat(ellipse.getAttribute('ry') ?? `${height / 2}`);
|
|
@@ -423,7 +538,7 @@ export class RaiBridge {
|
|
|
423
538
|
return { x, y, width, height };
|
|
424
539
|
}
|
|
425
540
|
|
|
426
|
-
|
|
541
|
+
public updateExistingSvg(
|
|
427
542
|
baseSvg: string,
|
|
428
543
|
model: RaidMetamodel,
|
|
429
544
|
_options: SerializationOptions,
|
|
@@ -435,6 +550,51 @@ export class RaiBridge {
|
|
|
435
550
|
const parser = new DOMParser();
|
|
436
551
|
const doc = parser.parseFromString(baseSvg, 'image/svg+xml');
|
|
437
552
|
|
|
553
|
+
// Ensure defs and arrow markers exist for external vector viewers (Preview, Chrome, Safari)
|
|
554
|
+
let defs = doc.querySelector('defs');
|
|
555
|
+
if (!defs) {
|
|
556
|
+
defs = doc.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
557
|
+
doc.documentElement.insertBefore(defs, doc.documentElement.firstChild);
|
|
558
|
+
}
|
|
559
|
+
if (!doc.querySelector('#arrow-classic')) {
|
|
560
|
+
const marker = doc.createElementNS('http://www.w3.org/2000/svg', 'marker');
|
|
561
|
+
marker.setAttribute('id', 'arrow-classic');
|
|
562
|
+
marker.setAttribute('viewBox', '0 0 10 10');
|
|
563
|
+
marker.setAttribute('refX', '10');
|
|
564
|
+
marker.setAttribute('refY', '5');
|
|
565
|
+
marker.setAttribute('markerWidth', '7');
|
|
566
|
+
marker.setAttribute('markerHeight', '7');
|
|
567
|
+
marker.setAttribute('orient', 'auto-start-reverse');
|
|
568
|
+
const path = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
569
|
+
path.setAttribute('d', 'M 0 0 L 10 5 L 0 10 z');
|
|
570
|
+
path.setAttribute('fill', CascaisPalette.WarmGraphite);
|
|
571
|
+
marker.appendChild(path);
|
|
572
|
+
defs.appendChild(marker);
|
|
573
|
+
}
|
|
574
|
+
if (!doc.querySelector('#arrow-hollow')) {
|
|
575
|
+
const marker = doc.createElementNS('http://www.w3.org/2000/svg', 'marker');
|
|
576
|
+
marker.setAttribute('id', 'arrow-hollow');
|
|
577
|
+
marker.setAttribute('viewBox', '0 0 12 12');
|
|
578
|
+
marker.setAttribute('refX', '12');
|
|
579
|
+
marker.setAttribute('refY', '6');
|
|
580
|
+
marker.setAttribute('markerWidth', '9');
|
|
581
|
+
marker.setAttribute('markerHeight', '9');
|
|
582
|
+
marker.setAttribute('orient', 'auto-start-reverse');
|
|
583
|
+
const polygon = doc.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
584
|
+
polygon.setAttribute('points', '0 0, 12 6, 0 12');
|
|
585
|
+
polygon.setAttribute('fill', CascaisPalette.ChalkWhite);
|
|
586
|
+
polygon.setAttribute('stroke', CascaisPalette.WarmGraphite);
|
|
587
|
+
polygon.setAttribute('stroke-width', '1.5');
|
|
588
|
+
marker.appendChild(polygon);
|
|
589
|
+
defs.appendChild(marker);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Update diagram-level routing mode on root <svg>
|
|
593
|
+
const diagramRouting = _options.routingMode ?? model.routing;
|
|
594
|
+
if (diagramRouting) {
|
|
595
|
+
doc.documentElement.setAttribute(AimSvgContract.ATTR_ROUTING, diagramRouting);
|
|
596
|
+
}
|
|
597
|
+
|
|
438
598
|
// Update node positions and transforms
|
|
439
599
|
for (const node of model.nodes) {
|
|
440
600
|
const el = doc.querySelector(`[${AimSvgContract.ATTR_ID}="${node.id}"], [id="${node.id}"]`);
|
|
@@ -465,11 +625,15 @@ export class RaiBridge {
|
|
|
465
625
|
if (edge.targetId !== undefined) {
|
|
466
626
|
el.setAttribute(AimSvgContract.ATTR_TARGET, edge.targetId);
|
|
467
627
|
}
|
|
468
|
-
if (edge.sourcePort !== undefined) {
|
|
628
|
+
if (edge.sourcePort !== undefined && edge.sourcePort !== '' && edge.sourcePort !== 'auto') {
|
|
469
629
|
el.setAttribute(AimSvgContract.ATTR_SOURCE_PORT, edge.sourcePort);
|
|
630
|
+
} else {
|
|
631
|
+
el.removeAttribute(AimSvgContract.ATTR_SOURCE_PORT);
|
|
470
632
|
}
|
|
471
|
-
if (edge.targetPort !== undefined) {
|
|
633
|
+
if (edge.targetPort !== undefined && edge.targetPort !== '' && edge.targetPort !== 'auto') {
|
|
472
634
|
el.setAttribute(AimSvgContract.ATTR_TARGET_PORT, edge.targetPort);
|
|
635
|
+
} else {
|
|
636
|
+
el.removeAttribute(AimSvgContract.ATTR_TARGET_PORT);
|
|
473
637
|
}
|
|
474
638
|
if (edge.routing !== undefined) {
|
|
475
639
|
el.setAttribute(AimSvgContract.ATTR_ROUTING, edge.routing);
|
|
@@ -483,17 +647,83 @@ export class RaiBridge {
|
|
|
483
647
|
if (edge.targetCardinality !== undefined) {
|
|
484
648
|
el.setAttribute('aim-target-cardinality', edge.targetCardinality);
|
|
485
649
|
}
|
|
650
|
+
|
|
651
|
+
// Update or inject <path class="aim-edge"> with rendered path geometry
|
|
652
|
+
let pathD = edge.pathData;
|
|
653
|
+
if (!pathD) {
|
|
654
|
+
const sourceNode = model.nodes.find((n) => n.id === edge.sourceId);
|
|
655
|
+
const targetNode = model.nodes.find((n) => n.id === edge.targetId);
|
|
656
|
+
if (sourceNode && targetNode) {
|
|
657
|
+
pathD = this.computeFallbackEdgePath(sourceNode, targetNode, edge.bendPoints, edge.routing ?? model.routing);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
let pathEl = el.querySelector('path.aim-edge') ?? el.querySelector('path');
|
|
662
|
+
if (!pathEl) {
|
|
663
|
+
pathEl = doc.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
664
|
+
pathEl.setAttribute('class', 'aim-edge');
|
|
665
|
+
el.appendChild(pathEl);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (pathD) {
|
|
669
|
+
pathEl.setAttribute('d', pathD);
|
|
670
|
+
}
|
|
671
|
+
pathEl.setAttribute('fill', 'none');
|
|
672
|
+
pathEl.setAttribute('stroke', CascaisPalette.WarmGraphite);
|
|
673
|
+
pathEl.setAttribute('stroke-width', '1.5');
|
|
674
|
+
|
|
675
|
+
if (edge.kind === 'dependency') {
|
|
676
|
+
pathEl.setAttribute('stroke-dasharray', '5,5');
|
|
677
|
+
} else {
|
|
678
|
+
pathEl.removeAttribute('stroke-dasharray');
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const markerEnd = edge.kind === 'generalization' ? 'url(#arrow-hollow)' : 'url(#arrow-classic)';
|
|
682
|
+
pathEl.setAttribute('marker-end', markerEnd);
|
|
486
683
|
}
|
|
487
684
|
}
|
|
488
685
|
|
|
489
686
|
return new XMLSerializer().serializeToString(doc);
|
|
490
687
|
}
|
|
491
688
|
|
|
492
|
-
private
|
|
689
|
+
private renderSvgText(
|
|
690
|
+
text: string,
|
|
691
|
+
cx: number,
|
|
692
|
+
cy: number,
|
|
693
|
+
fontSize: number,
|
|
694
|
+
fontWeight: string,
|
|
695
|
+
fill: string,
|
|
696
|
+
underline: boolean = false,
|
|
697
|
+
): string {
|
|
698
|
+
const wrapped = wrapAimText(text);
|
|
699
|
+
const lines = wrapped.split('\n');
|
|
700
|
+
const underlineAttr = underline ? ' text-decoration="underline"' : '';
|
|
701
|
+
const weightAttr = fontWeight !== 'normal' ? ` font-weight="${fontWeight}"` : '';
|
|
702
|
+
|
|
703
|
+
if (lines.length <= 1) {
|
|
704
|
+
return ` <text x="${cx}" y="${cy}" font-size="${fontSize}"${weightAttr} fill="${fill}" text-anchor="middle" dominant-baseline="central"${underlineAttr}>${lines[0] ?? ''}</text>\n`;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const lineHeight = fontSize * 1.25;
|
|
708
|
+
const startY = cy - ((lines.length - 1) * lineHeight) / 2;
|
|
709
|
+
|
|
710
|
+
let tspans = '';
|
|
711
|
+
lines.forEach((line, idx) => {
|
|
712
|
+
const y = Math.round(startY + idx * lineHeight);
|
|
713
|
+
tspans += `<tspan x="${cx}" y="${y}">${line}</tspan>`;
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
return ` <text font-size="${fontSize}"${weightAttr} fill="${fill}" text-anchor="middle" dominant-baseline="central"${underlineAttr}>${tspans}</text>\n`;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
public generateFreshSvg(model: RaidMetamodel, options: SerializationOptions): string {
|
|
493
720
|
const width = Math.max(800, ...model.nodes.map((n) => n.bounds.x + n.bounds.width + 100));
|
|
494
721
|
const height = Math.max(600, ...model.nodes.map((n) => n.bounds.y + n.bounds.height + 100));
|
|
495
722
|
|
|
496
|
-
|
|
723
|
+
const diagramRouting = options.routingMode ?? model.routing;
|
|
724
|
+
const routingAttr = diagramRouting ? ` ${AimSvgContract.ATTR_ROUTING}="${diagramRouting}"` : '';
|
|
725
|
+
|
|
726
|
+
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" id="${model.diagramId}" aim-archetype="${model.archetype}"${routingAttr}>\n`;
|
|
497
727
|
|
|
498
728
|
// Definitions & Markers
|
|
499
729
|
svg += ` <defs>\n`;
|
|
@@ -509,6 +739,7 @@ export class RaiBridge {
|
|
|
509
739
|
svg += ` .aim-node:hover { filter: drop-shadow(0 4px 6px rgba(0,0,0,0.1)); }\n`;
|
|
510
740
|
svg += ` .aim-edge { fill: none; stroke: ${CascaisPalette.WarmGraphite}; stroke-width: 1.5; }\n`;
|
|
511
741
|
svg += ` text { font-family: Inter, system-ui, sans-serif; }\n`;
|
|
742
|
+
svg += ` .aim-act text, .aim-obj text { text-decoration: underline; }\n`;
|
|
512
743
|
svg += ` </style>\n`;
|
|
513
744
|
}
|
|
514
745
|
svg += ` </defs>\n\n`;
|
|
@@ -522,22 +753,41 @@ export class RaiBridge {
|
|
|
522
753
|
const markerEnd = edge.kind === 'generalization' ? ' marker-end="url(#arrow-hollow)"' : ' marker-end="url(#arrow-classic)"';
|
|
523
754
|
|
|
524
755
|
// Path data construction
|
|
525
|
-
let pathD = '';
|
|
526
|
-
if (
|
|
527
|
-
const
|
|
528
|
-
|
|
756
|
+
let pathD = edge.pathData ?? '';
|
|
757
|
+
if (!pathD) {
|
|
758
|
+
const sourceNode = model.nodes.find((n) => n.id === edge.sourceId);
|
|
759
|
+
const targetNode = model.nodes.find((n) => n.id === edge.targetId);
|
|
760
|
+
if (sourceNode && targetNode) {
|
|
761
|
+
pathD = this.computeFallbackEdgePath(sourceNode, targetNode, edge.bendPoints, edge.routing ?? model.routing);
|
|
762
|
+
} else if (edge.bendPoints.length > 0) {
|
|
763
|
+
const first = edge.bendPoints[0]!;
|
|
764
|
+
pathD = `M ${first.x} ${first.y} ` + edge.bendPoints.slice(1).map((p) => `L ${p.x} ${p.y}`).join(' ');
|
|
765
|
+
}
|
|
529
766
|
}
|
|
530
767
|
|
|
531
|
-
const sourcePortAttr = edge.sourcePort ? ` ${AimSvgContract.ATTR_SOURCE_PORT}="${edge.sourcePort}"` : '';
|
|
532
|
-
const targetPortAttr = edge.targetPort ? ` ${AimSvgContract.ATTR_TARGET_PORT}="${edge.targetPort}"` : '';
|
|
768
|
+
const sourcePortAttr = edge.sourcePort && edge.sourcePort !== 'auto' ? ` ${AimSvgContract.ATTR_SOURCE_PORT}="${edge.sourcePort}"` : '';
|
|
769
|
+
const targetPortAttr = edge.targetPort && edge.targetPort !== 'auto' ? ` ${AimSvgContract.ATTR_TARGET_PORT}="${edge.targetPort}"` : '';
|
|
533
770
|
const routingAttr = edge.routing ? ` ${AimSvgContract.ATTR_ROUTING}="${edge.routing}"` : '';
|
|
534
771
|
|
|
535
772
|
svg += ` <g ${AimSvgContract.ATTR_EDGE}="true" ${AimSvgContract.ATTR_ID}="${edge.id}" ${AimSvgContract.ATTR_EDGE_KIND}="${edge.kind}" ${AimSvgContract.ATTR_SOURCE}="${edge.sourceId}" ${AimSvgContract.ATTR_TARGET}="${edge.targetId}"${sourcePortAttr}${targetPortAttr}${routingAttr} ${AimSvgContract.ATTR_BENDS}="${bendsFormatted}">\n`;
|
|
536
773
|
if (pathD) {
|
|
537
|
-
svg += ` <path d="${pathD}" class="aim-edge"${strokeDash}${markerEnd} />\n`;
|
|
774
|
+
svg += ` <path d="${pathD}" class="aim-edge" fill="none" stroke="${CascaisPalette.WarmGraphite}" stroke-width="1.5"${strokeDash}${markerEnd} />\n`;
|
|
538
775
|
}
|
|
539
776
|
if (edge.label) {
|
|
540
|
-
const midPoint =
|
|
777
|
+
const midPoint =
|
|
778
|
+
edge.bendPoints.length > 0
|
|
779
|
+
? (edge.bendPoints[Math.floor(edge.bendPoints.length / 2)] ?? { x: 50, y: 50 })
|
|
780
|
+
: (() => {
|
|
781
|
+
const s = model.nodes.find((n) => n.id === edge.sourceId);
|
|
782
|
+
const t = model.nodes.find((n) => n.id === edge.targetId);
|
|
783
|
+
if (s && t) {
|
|
784
|
+
return {
|
|
785
|
+
x: Math.round((s.bounds.x + s.bounds.width / 2 + t.bounds.x + t.bounds.width / 2) / 2),
|
|
786
|
+
y: Math.round((s.bounds.y + s.bounds.height / 2 + t.bounds.y + t.bounds.height / 2) / 2),
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return { x: 50, y: 50 };
|
|
790
|
+
})();
|
|
541
791
|
svg += ` <text x="${midPoint.x}" y="${midPoint.y - 8}" font-size="11" fill="${CascaisPalette.TextSecondary}" text-anchor="middle">${edge.label}</text>\n`;
|
|
542
792
|
}
|
|
543
793
|
svg += ` </g>\n`;
|
|
@@ -554,13 +804,24 @@ export class RaiBridge {
|
|
|
554
804
|
const rx = node.bounds.width / 2;
|
|
555
805
|
const ry = node.bounds.height / 2;
|
|
556
806
|
svg += ` <ellipse cx="${rx}" cy="${ry}" rx="${rx}" ry="${ry}" fill="${CascaisPalette.ChalkWhite}" stroke="${CascaisPalette.NetGold}" stroke-width="2" />\n`;
|
|
557
|
-
svg +=
|
|
807
|
+
svg += this.renderSvgText(node.displayName, rx, ry, 13, 'bold', CascaisPalette.TextPrimary, false);
|
|
558
808
|
} else if (node.kind === 'act') {
|
|
559
809
|
svg += ` <rect width="${node.bounds.width}" height="${node.bounds.height}" rx="12" ry="12" fill="${CascaisPalette.CanvasCream}" stroke="${CascaisPalette.HeraldicGreen}" stroke-width="2" />\n`;
|
|
560
|
-
svg +=
|
|
810
|
+
svg += this.renderSvgText(node.displayName, node.bounds.width / 2, node.bounds.height / 2, 13, '600', CascaisPalette.TextPrimary, true);
|
|
811
|
+
} else if (node.kind === 'obj') {
|
|
812
|
+
svg += ` <rect width="${node.bounds.width}" height="${node.bounds.height}" fill="${CascaisPalette.ChalkWhite}" stroke="${CascaisPalette.SilverLineDark}" stroke-width="1.5" />\n`;
|
|
813
|
+
svg += this.renderSvgText(node.displayName, node.bounds.width / 2, node.bounds.height / 2, 12, 'normal', CascaisPalette.TextPrimary, true);
|
|
814
|
+
} else if (node.kind === 'per') {
|
|
815
|
+
const isInitiating = node.stereotype?.toLowerCase().includes('initiates') ?? false;
|
|
816
|
+
const strokeColor = isInitiating ? CascaisPalette.NetGold : CascaisPalette.WarmGraphite;
|
|
817
|
+
const cx = Math.round(node.bounds.width / 2);
|
|
818
|
+
svg += ` <rect width="${node.bounds.width}" height="${node.bounds.height}" fill="none" stroke="none" />\n`;
|
|
819
|
+
svg += ` <path d="M 61 50 v -4 a 8 8 0 0 0 -8 -8 H 37 a 8 8 0 0 0 -8 8 v 4" fill="none" stroke="${strokeColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />\n`;
|
|
820
|
+
svg += ` <circle cx="45" cy="22" r="8" fill="${CascaisPalette.ChalkWhite}" stroke="${strokeColor}" stroke-width="2" />\n`;
|
|
821
|
+
svg += this.renderSvgText(node.displayName, cx, 68, 12, '500', CascaisPalette.TextPrimary, false);
|
|
561
822
|
} else {
|
|
562
823
|
svg += ` <rect width="${node.bounds.width}" height="${node.bounds.height}" fill="${CascaisPalette.ChalkWhite}" stroke="${CascaisPalette.WarmGraphite}" stroke-width="1.5" />\n`;
|
|
563
|
-
svg +=
|
|
824
|
+
svg += this.renderSvgText(node.displayName, node.bounds.width / 2, node.bounds.height / 2, 12, 'normal', CascaisPalette.TextPrimary, false);
|
|
564
825
|
}
|
|
565
826
|
|
|
566
827
|
svg += ` </g>\n`;
|
|
@@ -571,6 +832,14 @@ export class RaiBridge {
|
|
|
571
832
|
return svg;
|
|
572
833
|
}
|
|
573
834
|
|
|
835
|
+
public normalizeRouting(raw: string): AimRoutingMode {
|
|
836
|
+
const lower = raw.toLowerCase().trim();
|
|
837
|
+
if (lower === 'orthogonal' || lower === 'manhattan') return 'manhattan';
|
|
838
|
+
if (lower === 'straight' || lower === 'normal') return 'normal';
|
|
839
|
+
if (lower === 'curved' || lower === 'smooth') return 'smooth';
|
|
840
|
+
return 'manhattan';
|
|
841
|
+
}
|
|
842
|
+
|
|
574
843
|
private normalizeKind(kind: string): AimOntologyKind {
|
|
575
844
|
const lower = kind.toLowerCase();
|
|
576
845
|
if (lower === 'uc' || lower === 'usecase') return 'uc';
|