@diagrammo/dgmo 0.8.9 → 0.8.11
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/AGENTS.md +3 -0
- package/dist/cli.cjs +245 -672
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.d.cts +2 -3
- package/dist/editor.d.ts +2 -3
- package/dist/editor.js.map +1 -1
- package/dist/index.cjs +1623 -800
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +153 -1
- package/dist/index.d.ts +153 -1
- package/dist/index.js +1619 -802
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +28 -2
- package/gallery/fixtures/sitemap-full.dgmo +1 -0
- package/package.json +14 -17
- package/src/boxes-and-lines/layout.ts +48 -8
- package/src/boxes-and-lines/parser.ts +59 -13
- package/src/boxes-and-lines/renderer.ts +34 -138
- package/src/c4/layout.ts +31 -10
- package/src/c4/renderer.ts +25 -138
- package/src/class/renderer.ts +185 -186
- package/src/d3.ts +194 -222
- package/src/echarts.ts +56 -57
- package/src/editor/index.ts +1 -2
- package/src/er/renderer.ts +52 -245
- package/src/gantt/renderer.ts +140 -182
- package/src/gantt/resolver.ts +19 -14
- package/src/index.ts +23 -1
- package/src/infra/renderer.ts +91 -244
- package/src/kanban/renderer.ts +29 -133
- package/src/label-layout.ts +286 -0
- package/src/org/renderer.ts +103 -170
- package/src/render.ts +39 -9
- package/src/sequence/parser.ts +4 -0
- package/src/sequence/renderer.ts +47 -154
- package/src/sitemap/layout.ts +180 -38
- package/src/sitemap/parser.ts +64 -23
- package/src/sitemap/renderer.ts +73 -161
- package/src/utils/arrows.ts +1 -1
- package/src/utils/legend-constants.ts +6 -0
- package/src/utils/legend-d3.ts +400 -0
- package/src/utils/legend-layout.ts +491 -0
- package/src/utils/legend-svg.ts +28 -2
- package/src/utils/legend-types.ts +166 -0
- package/src/utils/parsing.ts +1 -1
- package/src/utils/tag-groups.ts +1 -1
package/src/c4/layout.ts
CHANGED
|
@@ -18,6 +18,27 @@ import {
|
|
|
18
18
|
measureLegendText,
|
|
19
19
|
} from '../utils/legend-constants';
|
|
20
20
|
|
|
21
|
+
/** dagre node label shape after layout(). */
|
|
22
|
+
interface DagreNodeLabel {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** dagre edge label shape after layout(). */
|
|
31
|
+
interface DagreEdgeLabel {
|
|
32
|
+
points: { x: number; y: number }[];
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
const gNode = (g: any, name: string): DagreNodeLabel => g.node(name);
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const gEdge = (g: any, v: string, w: string): DagreEdgeLabel | undefined =>
|
|
40
|
+
g.edge(v, w);
|
|
41
|
+
|
|
21
42
|
// ============================================================
|
|
22
43
|
// Types
|
|
23
44
|
// ============================================================
|
|
@@ -213,7 +234,7 @@ function computeEdgePenalty(
|
|
|
213
234
|
* closer to their neighbors, producing cleaner visual layouts.
|
|
214
235
|
*/
|
|
215
236
|
function reduceCrossings(
|
|
216
|
-
g: dagre.graphlib.Graph
|
|
237
|
+
g: InstanceType<typeof dagre.graphlib.Graph>,
|
|
217
238
|
edgeList: { source: string; target: string }[],
|
|
218
239
|
nodeGroupMap?: Map<string, string>
|
|
219
240
|
): void {
|
|
@@ -229,7 +250,7 @@ function reduceCrossings(
|
|
|
229
250
|
// Build geometry map for edge-node collision scoring
|
|
230
251
|
const nodeGeometry = new Map<string, NodeGeometry>();
|
|
231
252
|
for (const name of g.nodes()) {
|
|
232
|
-
const pos = g
|
|
253
|
+
const pos = gNode(g, name);
|
|
233
254
|
if (pos)
|
|
234
255
|
nodeGeometry.set(name, {
|
|
235
256
|
y: pos.y,
|
|
@@ -241,7 +262,7 @@ function reduceCrossings(
|
|
|
241
262
|
// Group nodes by rank
|
|
242
263
|
const rankMap = new Map<number, string[]>();
|
|
243
264
|
for (const name of g.nodes()) {
|
|
244
|
-
const pos = g
|
|
265
|
+
const pos = gNode(g, name);
|
|
245
266
|
if (!pos) continue;
|
|
246
267
|
const rankY = Math.round(pos.y);
|
|
247
268
|
if (!rankMap.has(rankY)) rankMap.set(rankY, []);
|
|
@@ -250,7 +271,7 @@ function reduceCrossings(
|
|
|
250
271
|
|
|
251
272
|
// Sort each rank by current x position
|
|
252
273
|
for (const [, rankNodes] of rankMap) {
|
|
253
|
-
rankNodes.sort((a, b) => g
|
|
274
|
+
rankNodes.sort((a, b) => gNode(g, a).x - gNode(g, b).x);
|
|
254
275
|
}
|
|
255
276
|
|
|
256
277
|
let anyMoved = false;
|
|
@@ -285,13 +306,13 @@ function reduceCrossings(
|
|
|
285
306
|
|
|
286
307
|
// Collect the x-slots for this partition (sorted)
|
|
287
308
|
const xSlots = partition
|
|
288
|
-
.map((name) => g
|
|
309
|
+
.map((name) => gNode(g, name).x)
|
|
289
310
|
.sort((a, b) => a - b);
|
|
290
311
|
|
|
291
312
|
// Build position map snapshot
|
|
292
313
|
const basePositions = new Map<string, number>();
|
|
293
314
|
for (const name of g.nodes()) {
|
|
294
|
-
const pos = g
|
|
315
|
+
const pos = gNode(g, name);
|
|
295
316
|
if (pos) basePositions.set(name, pos.x);
|
|
296
317
|
}
|
|
297
318
|
|
|
@@ -379,7 +400,7 @@ function reduceCrossings(
|
|
|
379
400
|
// Apply best permutation if it differs from current
|
|
380
401
|
if (bestPerm.some((name, i) => name !== partition[i])) {
|
|
381
402
|
for (let i = 0; i < bestPerm.length; i++) {
|
|
382
|
-
g
|
|
403
|
+
gNode(g, bestPerm[i]!).x = xSlots[i]!;
|
|
383
404
|
// Update in the original rankNodes too
|
|
384
405
|
const rankIdx = rankNodes.indexOf(partition[i]!);
|
|
385
406
|
if (rankIdx >= 0) rankNodes[rankIdx] = bestPerm[i]!;
|
|
@@ -392,10 +413,10 @@ function reduceCrossings(
|
|
|
392
413
|
// Recompute edge waypoints if any positions changed
|
|
393
414
|
if (anyMoved) {
|
|
394
415
|
for (const edge of edgeList) {
|
|
395
|
-
const edgeData = g
|
|
416
|
+
const edgeData = gEdge(g, edge.source, edge.target);
|
|
396
417
|
if (!edgeData) continue;
|
|
397
|
-
const srcPos = g
|
|
398
|
-
const tgtPos = g
|
|
418
|
+
const srcPos = gNode(g, edge.source);
|
|
419
|
+
const tgtPos = gNode(g, edge.target);
|
|
399
420
|
if (!srcPos || !tgtPos) continue;
|
|
400
421
|
|
|
401
422
|
const srcBottom = { x: srcPos.x, y: srcPos.y + srcPos.height / 2 };
|
package/src/c4/renderer.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { PaletteColors } from '../palettes';
|
|
|
9
9
|
import { mix } from '../palettes/color-utils';
|
|
10
10
|
import { renderInlineText } from '../utils/inline-markdown';
|
|
11
11
|
import type { ParsedC4 } from './types';
|
|
12
|
-
import type { C4LayoutResult, C4LayoutEdge
|
|
12
|
+
import type { C4LayoutResult, C4LayoutEdge } from './layout';
|
|
13
13
|
import { parseC4 } from './parser';
|
|
14
14
|
import {
|
|
15
15
|
layoutC4Context,
|
|
@@ -18,18 +18,9 @@ import {
|
|
|
18
18
|
layoutC4Deployment,
|
|
19
19
|
collectCardMetadata,
|
|
20
20
|
} from './layout';
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
LEGEND_PILL_PAD,
|
|
25
|
-
LEGEND_DOT_R,
|
|
26
|
-
LEGEND_ENTRY_FONT_SIZE,
|
|
27
|
-
LEGEND_ENTRY_DOT_GAP,
|
|
28
|
-
LEGEND_ENTRY_TRAIL,
|
|
29
|
-
LEGEND_CAPSULE_PAD,
|
|
30
|
-
LEGEND_GROUP_GAP,
|
|
31
|
-
measureLegendText,
|
|
32
|
-
} from '../utils/legend-constants';
|
|
21
|
+
import { LEGEND_HEIGHT } from '../utils/legend-constants';
|
|
22
|
+
import { renderLegendD3 } from '../utils/legend-d3';
|
|
23
|
+
import type { LegendConfig, LegendState } from '../utils/legend-types';
|
|
33
24
|
import { TITLE_FONT_SIZE, TITLE_FONT_WEIGHT } from '../utils/title-constants';
|
|
34
25
|
|
|
35
26
|
// ============================================================
|
|
@@ -1251,133 +1242,29 @@ function renderLegend(
|
|
|
1251
1242
|
palette: PaletteColors,
|
|
1252
1243
|
isDark: boolean,
|
|
1253
1244
|
activeTagGroup?: string | null,
|
|
1254
|
-
/** When set, center groups horizontally across this width (fixed overlay mode). */
|
|
1255
1245
|
fixedWidth?: number | null
|
|
1256
1246
|
): void {
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
fixedPositions.set(g.name, cx);
|
|
1279
|
-
cx += effectiveW(g) + LEGEND_GROUP_GAP;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
for (const group of visibleGroups) {
|
|
1284
|
-
const isActive =
|
|
1285
|
-
activeTagGroup != null &&
|
|
1286
|
-
group.name.toLowerCase() === (activeTagGroup ?? '').toLowerCase();
|
|
1287
|
-
|
|
1288
|
-
const groupBg = isDark
|
|
1289
|
-
? mix(palette.surface, palette.bg, 50)
|
|
1290
|
-
: mix(palette.surface, palette.bg, 30);
|
|
1291
|
-
|
|
1292
|
-
const pillLabel = group.name;
|
|
1293
|
-
const pillWidth = pillWidthOf(group);
|
|
1294
|
-
|
|
1295
|
-
const gX = fixedPositions?.get(group.name) ?? group.x;
|
|
1296
|
-
const gY = fixedPositions != null ? 0 : group.y;
|
|
1297
|
-
|
|
1298
|
-
const gEl = parent
|
|
1299
|
-
.append('g')
|
|
1300
|
-
.attr('transform', `translate(${gX}, ${gY})`)
|
|
1301
|
-
.attr('class', 'c4-legend-group')
|
|
1302
|
-
.attr('data-legend-group', group.name.toLowerCase())
|
|
1303
|
-
.style('cursor', 'pointer');
|
|
1304
|
-
|
|
1305
|
-
if (isActive) {
|
|
1306
|
-
gEl
|
|
1307
|
-
.append('rect')
|
|
1308
|
-
.attr('width', group.width)
|
|
1309
|
-
.attr('height', LEGEND_HEIGHT)
|
|
1310
|
-
.attr('rx', LEGEND_HEIGHT / 2)
|
|
1311
|
-
.attr('fill', groupBg);
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
const pillX = isActive ? LEGEND_CAPSULE_PAD : 0;
|
|
1315
|
-
const pillY = LEGEND_CAPSULE_PAD;
|
|
1316
|
-
const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
|
|
1317
|
-
|
|
1318
|
-
gEl
|
|
1319
|
-
.append('rect')
|
|
1320
|
-
.attr('x', pillX)
|
|
1321
|
-
.attr('y', pillY)
|
|
1322
|
-
.attr('width', pillWidth)
|
|
1323
|
-
.attr('height', pillH)
|
|
1324
|
-
.attr('rx', pillH / 2)
|
|
1325
|
-
.attr('fill', isActive ? palette.bg : groupBg);
|
|
1326
|
-
|
|
1327
|
-
if (isActive) {
|
|
1328
|
-
gEl
|
|
1329
|
-
.append('rect')
|
|
1330
|
-
.attr('x', pillX)
|
|
1331
|
-
.attr('y', pillY)
|
|
1332
|
-
.attr('width', pillWidth)
|
|
1333
|
-
.attr('height', pillH)
|
|
1334
|
-
.attr('rx', pillH / 2)
|
|
1335
|
-
.attr('fill', 'none')
|
|
1336
|
-
.attr('stroke', mix(palette.textMuted, palette.bg, 50))
|
|
1337
|
-
.attr('stroke-width', 0.75);
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
gEl
|
|
1341
|
-
.append('text')
|
|
1342
|
-
.attr('x', pillX + pillWidth / 2)
|
|
1343
|
-
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2)
|
|
1344
|
-
.attr('font-size', LEGEND_PILL_FONT_SIZE)
|
|
1345
|
-
.attr('font-weight', '500')
|
|
1346
|
-
.attr('fill', isActive ? palette.text : palette.textMuted)
|
|
1347
|
-
.attr('text-anchor', 'middle')
|
|
1348
|
-
.text(pillLabel);
|
|
1349
|
-
|
|
1350
|
-
if (isActive) {
|
|
1351
|
-
let entryX = pillX + pillWidth + 4;
|
|
1352
|
-
for (const entry of group.entries) {
|
|
1353
|
-
const entryG = gEl
|
|
1354
|
-
.append('g')
|
|
1355
|
-
.attr('data-legend-entry', entry.value.toLowerCase())
|
|
1356
|
-
.style('cursor', 'pointer');
|
|
1357
|
-
|
|
1358
|
-
entryG
|
|
1359
|
-
.append('circle')
|
|
1360
|
-
.attr('cx', entryX + LEGEND_DOT_R)
|
|
1361
|
-
.attr('cy', LEGEND_HEIGHT / 2)
|
|
1362
|
-
.attr('r', LEGEND_DOT_R)
|
|
1363
|
-
.attr('fill', entry.color);
|
|
1364
|
-
|
|
1365
|
-
const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
|
|
1366
|
-
entryG
|
|
1367
|
-
.append('text')
|
|
1368
|
-
.attr('x', textX)
|
|
1369
|
-
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1)
|
|
1370
|
-
.attr('font-size', LEGEND_ENTRY_FONT_SIZE)
|
|
1371
|
-
.attr('fill', palette.textMuted)
|
|
1372
|
-
.text(entry.value);
|
|
1373
|
-
|
|
1374
|
-
entryX =
|
|
1375
|
-
textX +
|
|
1376
|
-
measureLegendText(entry.value, LEGEND_ENTRY_FONT_SIZE) +
|
|
1377
|
-
LEGEND_ENTRY_TRAIL;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1247
|
+
const groups = layout.legend.map((g) => ({
|
|
1248
|
+
name: g.name,
|
|
1249
|
+
entries: g.entries.map((e) => ({ value: e.value, color: e.color })),
|
|
1250
|
+
}));
|
|
1251
|
+
const legendConfig: LegendConfig = {
|
|
1252
|
+
groups,
|
|
1253
|
+
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
1254
|
+
mode: 'fixed',
|
|
1255
|
+
};
|
|
1256
|
+
const legendState: LegendState = { activeGroup: activeTagGroup ?? null };
|
|
1257
|
+
const containerWidth = fixedWidth ?? layout.width;
|
|
1258
|
+
renderLegendD3(
|
|
1259
|
+
parent,
|
|
1260
|
+
legendConfig,
|
|
1261
|
+
legendState,
|
|
1262
|
+
palette,
|
|
1263
|
+
isDark,
|
|
1264
|
+
undefined,
|
|
1265
|
+
containerWidth
|
|
1266
|
+
);
|
|
1267
|
+
parent.selectAll('[data-legend-group]').classed('c4-legend-group', true);
|
|
1381
1268
|
}
|
|
1382
1269
|
|
|
1383
1270
|
// ============================================================
|