@diagrammo/dgmo 0.8.22 → 0.8.23
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/cli.cjs +111 -109
- package/dist/editor.cjs +3 -0
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +3 -0
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +3 -0
- package/dist/highlight.cjs.map +1 -1
- package/dist/highlight.js +3 -0
- package/dist/highlight.js.map +1 -1
- package/dist/index.cjs +1010 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +97 -11
- package/dist/index.d.ts +97 -11
- package/dist/index.js +1001 -213
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +380 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +179 -0
- package/dist/internal.d.ts +179 -0
- package/dist/internal.js +337 -0
- package/dist/internal.js.map +1 -0
- package/docs/guide/chart-cycle.md +156 -0
- package/docs/guide/chart-journey-map.md +179 -0
- package/docs/guide/chart-pyramid.md +111 -0
- package/docs/guide/registry.json +5 -0
- package/docs/language-reference.md +62 -1
- package/gallery/fixtures/pyramid/dikw.dgmo +17 -0
- package/gallery/fixtures/pyramid/inverted-funnel.dgmo +16 -0
- package/gallery/fixtures/pyramid/minimal.dgmo +5 -0
- package/package.json +11 -1
- package/src/cli.ts +5 -35
- package/src/completion.ts +9 -44
- package/src/cycle/layout.ts +19 -28
- package/src/cycle/renderer.ts +59 -32
- package/src/cycle/types.ts +21 -0
- package/src/d3.ts +21 -1
- package/src/dgmo-router.ts +73 -3
- package/src/echarts.ts +1 -1
- package/src/editor/keywords.ts +3 -0
- package/src/index.ts +13 -2
- package/src/infra/parser.ts +2 -2
- package/src/internal.ts +16 -0
- package/src/journey-map/renderer.ts +112 -47
- package/src/org/collapse.ts +81 -0
- package/src/org/renderer.ts +212 -4
- package/src/pyramid/parser.ts +172 -0
- package/src/pyramid/renderer.ts +684 -0
- package/src/pyramid/types.ts +28 -0
- package/src/render.ts +2 -8
- package/src/sequence/parser.ts +62 -20
- package/src/sequence/renderer.ts +2 -2
- package/src/tech-radar/interactive.ts +54 -0
- package/src/utils/parsing.ts +1 -0
package/src/render.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { renderForExport } from './d3';
|
|
2
2
|
import { renderExtendedChartForExport } from './echarts';
|
|
3
|
-
import {
|
|
4
|
-
parseDgmoChartType,
|
|
5
|
-
getRenderCategory,
|
|
6
|
-
parseDgmo,
|
|
7
|
-
} from './dgmo-router';
|
|
3
|
+
import { getRenderCategory, parseDgmo } from './dgmo-router';
|
|
8
4
|
import type { DgmoError } from './diagnostics';
|
|
9
5
|
import { getPalette } from './palettes/registry';
|
|
10
6
|
import type { CompactViewState } from './sharing';
|
|
@@ -84,9 +80,7 @@ export async function render(
|
|
|
84
80
|
const paletteColors =
|
|
85
81
|
getPalette(paletteName)[theme === 'dark' ? 'dark' : 'light'];
|
|
86
82
|
|
|
87
|
-
const { diagnostics } = parseDgmo(content);
|
|
88
|
-
|
|
89
|
-
const chartType = parseDgmoChartType(content);
|
|
83
|
+
const { diagnostics, chartType } = parseDgmo(content);
|
|
90
84
|
const category = chartType ? getRenderCategory(chartType) : null;
|
|
91
85
|
|
|
92
86
|
// Build viewState from legendState (backwards compat) or use provided viewState
|
package/src/sequence/parser.ts
CHANGED
|
@@ -236,6 +236,8 @@ type NoteParseResult =
|
|
|
236
236
|
function parseNoteLine(
|
|
237
237
|
trimmed: string,
|
|
238
238
|
participants: SequenceParticipant[],
|
|
239
|
+
participantIds: Set<string>,
|
|
240
|
+
sortedParticipantsCache: SequenceParticipant[],
|
|
239
241
|
lastMsgFrom: string | null
|
|
240
242
|
): NoteParseResult {
|
|
241
243
|
const lower = trimmed.toLowerCase();
|
|
@@ -256,7 +258,7 @@ function parseNoteLine(
|
|
|
256
258
|
if (!lastMsgFrom) return { kind: 'skip' };
|
|
257
259
|
participantId = lastMsgFrom;
|
|
258
260
|
}
|
|
259
|
-
if (
|
|
261
|
+
if (participantIds.has(participantId)) {
|
|
260
262
|
return { kind: 'multi-head', position, participantId };
|
|
261
263
|
}
|
|
262
264
|
// Participant not found — fall through to bare-note handler for proper resolution
|
|
@@ -284,13 +286,17 @@ function parseNoteLine(
|
|
|
284
286
|
if (!afterPos) {
|
|
285
287
|
// Just `note left` or `note right` — multi-line head
|
|
286
288
|
if (!lastMsgFrom) return { kind: 'skip' };
|
|
287
|
-
if (!
|
|
288
|
-
return { kind: 'skip' };
|
|
289
|
+
if (!participantIds.has(lastMsgFrom)) return { kind: 'skip' };
|
|
289
290
|
return { kind: 'multi-head', position, participantId: lastMsgFrom };
|
|
290
291
|
}
|
|
291
292
|
|
|
292
293
|
// Try to match a known participant at the start of afterPos
|
|
293
|
-
const resolved = resolveParticipantAndText(
|
|
294
|
+
const resolved = resolveParticipantAndText(
|
|
295
|
+
afterPos,
|
|
296
|
+
participants,
|
|
297
|
+
participantIds,
|
|
298
|
+
sortedParticipantsCache
|
|
299
|
+
);
|
|
294
300
|
if (resolved) {
|
|
295
301
|
if (resolved.text) {
|
|
296
302
|
return {
|
|
@@ -316,8 +322,7 @@ function parseNoteLine(
|
|
|
316
322
|
|
|
317
323
|
// Without `of`, treat remaining text as note content on the last-msg sender
|
|
318
324
|
if (!lastMsgFrom) return { kind: 'skip' };
|
|
319
|
-
if (!
|
|
320
|
-
return { kind: 'skip' };
|
|
325
|
+
if (!participantIds.has(lastMsgFrom)) return { kind: 'skip' };
|
|
321
326
|
return {
|
|
322
327
|
kind: 'single',
|
|
323
328
|
position,
|
|
@@ -328,8 +333,7 @@ function parseNoteLine(
|
|
|
328
333
|
|
|
329
334
|
// Plain `note text` — default position, last msg sender
|
|
330
335
|
if (!lastMsgFrom) return { kind: 'skip' };
|
|
331
|
-
if (!
|
|
332
|
-
return { kind: 'skip' };
|
|
336
|
+
if (!participantIds.has(lastMsgFrom)) return { kind: 'skip' };
|
|
333
337
|
return {
|
|
334
338
|
kind: 'single',
|
|
335
339
|
position: 'right',
|
|
@@ -348,7 +352,9 @@ function parseNoteLine(
|
|
|
348
352
|
*/
|
|
349
353
|
function resolveParticipantAndText(
|
|
350
354
|
input: string,
|
|
351
|
-
participants: SequenceParticipant[]
|
|
355
|
+
participants: SequenceParticipant[],
|
|
356
|
+
participantIds: Set<string>,
|
|
357
|
+
sortedParticipantsCache: SequenceParticipant[]
|
|
352
358
|
): { participantId: string; text: string } | null {
|
|
353
359
|
// Handle quoted participant: `"Auth Service" text`
|
|
354
360
|
if (input.startsWith('"') || input.startsWith("'")) {
|
|
@@ -356,7 +362,7 @@ function resolveParticipantAndText(
|
|
|
356
362
|
const endQuote = input.indexOf(quote, 1);
|
|
357
363
|
if (endQuote > 0) {
|
|
358
364
|
const name = input.substring(1, endQuote);
|
|
359
|
-
if (
|
|
365
|
+
if (participantIds.has(name)) {
|
|
360
366
|
const text = input.substring(endQuote + 1).trim();
|
|
361
367
|
return { participantId: name, text };
|
|
362
368
|
}
|
|
@@ -364,8 +370,8 @@ function resolveParticipantAndText(
|
|
|
364
370
|
return null;
|
|
365
371
|
}
|
|
366
372
|
|
|
367
|
-
//
|
|
368
|
-
const sorted =
|
|
373
|
+
// Use pre-sorted participants (longest first) for greedy matching
|
|
374
|
+
const sorted = sortedParticipantsCache;
|
|
369
375
|
for (const p of sorted) {
|
|
370
376
|
if (input.startsWith(p.id)) {
|
|
371
377
|
const remaining = input.substring(p.id.length);
|
|
@@ -443,6 +449,25 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
443
449
|
// Group parsing state — tracks the active [Group] heading
|
|
444
450
|
let activeGroup: SequenceGroup | null = null;
|
|
445
451
|
|
|
452
|
+
// Fast lookup set for participant existence checks (mirrors result.participants)
|
|
453
|
+
const participantIds = new Set<string>();
|
|
454
|
+
|
|
455
|
+
// Cache sorted participants (longest ID first) for greedy name matching in notes.
|
|
456
|
+
// Invalidated whenever a new participant is added.
|
|
457
|
+
let sortedParticipantsCache: SequenceParticipant[] = [];
|
|
458
|
+
let sortedCacheDirty = true;
|
|
459
|
+
|
|
460
|
+
/** Get sorted participants, rebuilding cache only when dirty. */
|
|
461
|
+
const getSortedParticipants = (): SequenceParticipant[] => {
|
|
462
|
+
if (sortedCacheDirty) {
|
|
463
|
+
sortedParticipantsCache = [...result.participants].sort(
|
|
464
|
+
(a, b) => b.id.length - a.id.length
|
|
465
|
+
);
|
|
466
|
+
sortedCacheDirty = false;
|
|
467
|
+
}
|
|
468
|
+
return sortedParticipantsCache;
|
|
469
|
+
};
|
|
470
|
+
|
|
446
471
|
// Track participant → group name for duplicate membership detection
|
|
447
472
|
const participantGroupMap = new Map<string, string>();
|
|
448
473
|
|
|
@@ -774,7 +799,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
774
799
|
const position = posMatch ? parseInt(posMatch[1], 10) : undefined;
|
|
775
800
|
|
|
776
801
|
// Avoid duplicate participant declarations
|
|
777
|
-
if (!
|
|
802
|
+
if (!participantIds.has(id)) {
|
|
803
|
+
participantIds.add(id);
|
|
804
|
+
sortedCacheDirty = true;
|
|
778
805
|
result.participants.push({
|
|
779
806
|
id,
|
|
780
807
|
label: alias || id,
|
|
@@ -808,7 +835,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
808
835
|
const id = posOnlyMatch[1];
|
|
809
836
|
const position = parseInt(posOnlyMatch[2], 10);
|
|
810
837
|
|
|
811
|
-
if (!
|
|
838
|
+
if (!participantIds.has(id)) {
|
|
839
|
+
participantIds.add(id);
|
|
840
|
+
sortedCacheDirty = true;
|
|
812
841
|
result.participants.push({
|
|
813
842
|
id,
|
|
814
843
|
label: id,
|
|
@@ -846,7 +875,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
846
875
|
`'${id}(${color})' syntax is no longer supported — use 'tag:' groups for coloring`
|
|
847
876
|
);
|
|
848
877
|
contentStarted = true;
|
|
849
|
-
if (!
|
|
878
|
+
if (!participantIds.has(id)) {
|
|
879
|
+
participantIds.add(id);
|
|
880
|
+
sortedCacheDirty = true;
|
|
850
881
|
result.participants.push({
|
|
851
882
|
id,
|
|
852
883
|
label: id,
|
|
@@ -882,7 +913,8 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
882
913
|
) {
|
|
883
914
|
contentStarted = true;
|
|
884
915
|
const id = bareCore;
|
|
885
|
-
if (!
|
|
916
|
+
if (!participantIds.has(id)) {
|
|
917
|
+
participantIds.add(id);
|
|
886
918
|
result.participants.push({
|
|
887
919
|
id,
|
|
888
920
|
label: id,
|
|
@@ -965,7 +997,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
965
997
|
currentContainer().push(msg);
|
|
966
998
|
|
|
967
999
|
// Auto-register participants
|
|
968
|
-
if (!
|
|
1000
|
+
if (!participantIds.has(from)) {
|
|
1001
|
+
participantIds.add(from);
|
|
1002
|
+
sortedCacheDirty = true;
|
|
969
1003
|
result.participants.push({
|
|
970
1004
|
id: from,
|
|
971
1005
|
label: from,
|
|
@@ -973,7 +1007,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
973
1007
|
lineNumber,
|
|
974
1008
|
});
|
|
975
1009
|
}
|
|
976
|
-
if (!
|
|
1010
|
+
if (!participantIds.has(to)) {
|
|
1011
|
+
participantIds.add(to);
|
|
1012
|
+
sortedCacheDirty = true;
|
|
977
1013
|
result.participants.push({
|
|
978
1014
|
id: to,
|
|
979
1015
|
label: to,
|
|
@@ -1050,7 +1086,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
1050
1086
|
result.messages.push(msg);
|
|
1051
1087
|
currentContainer().push(msg);
|
|
1052
1088
|
|
|
1053
|
-
if (!
|
|
1089
|
+
if (!participantIds.has(from)) {
|
|
1090
|
+
participantIds.add(from);
|
|
1091
|
+
sortedCacheDirty = true;
|
|
1054
1092
|
result.participants.push({
|
|
1055
1093
|
id: from,
|
|
1056
1094
|
label: from,
|
|
@@ -1058,7 +1096,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
1058
1096
|
lineNumber,
|
|
1059
1097
|
});
|
|
1060
1098
|
}
|
|
1061
|
-
if (!
|
|
1099
|
+
if (!participantIds.has(to)) {
|
|
1100
|
+
participantIds.add(to);
|
|
1101
|
+
sortedCacheDirty = true;
|
|
1062
1102
|
result.participants.push({
|
|
1063
1103
|
id: to,
|
|
1064
1104
|
label: to,
|
|
@@ -1182,6 +1222,8 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
|
|
|
1182
1222
|
const noteParsed = parseNoteLine(
|
|
1183
1223
|
trimmed,
|
|
1184
1224
|
result.participants,
|
|
1225
|
+
participantIds,
|
|
1226
|
+
getSortedParticipants(),
|
|
1185
1227
|
lastMsgFrom
|
|
1186
1228
|
);
|
|
1187
1229
|
if (noteParsed) {
|
package/src/sequence/renderer.ts
CHANGED
|
@@ -2127,7 +2127,7 @@ export function renderSequenceDiagram(
|
|
|
2127
2127
|
firstBranchStep = Math.min(firstBranchStep, first);
|
|
2128
2128
|
}
|
|
2129
2129
|
if (firstBranchStep < Infinity) {
|
|
2130
|
-
const dividerY = stepY(firstBranchStep) -
|
|
2130
|
+
const dividerY = stepY(firstBranchStep) - BLOCK_HEADER_SPACE;
|
|
2131
2131
|
deferredLines.push({
|
|
2132
2132
|
x1: frameX,
|
|
2133
2133
|
y1: dividerY,
|
|
@@ -2156,7 +2156,7 @@ export function renderSequenceDiagram(
|
|
|
2156
2156
|
firstElseStep = Math.min(firstElseStep, first);
|
|
2157
2157
|
}
|
|
2158
2158
|
if (firstElseStep < Infinity) {
|
|
2159
|
-
const dividerY = stepY(firstElseStep) -
|
|
2159
|
+
const dividerY = stepY(firstElseStep) - BLOCK_HEADER_SPACE;
|
|
2160
2160
|
deferredLines.push({
|
|
2161
2161
|
x1: frameX,
|
|
2162
2162
|
y1: dividerY,
|
|
@@ -229,12 +229,46 @@ function renderQuarterCircle(
|
|
|
229
229
|
const fillColor =
|
|
230
230
|
ri % 2 === 0 ? palette.bg : mix(palette.bg, palette.border, 0.15);
|
|
231
231
|
|
|
232
|
+
const ringName = parsed.rings[ri].name;
|
|
233
|
+
|
|
234
|
+
// Background ring arc
|
|
232
235
|
svg
|
|
233
236
|
.append('path')
|
|
234
237
|
.attr('d', arcGen(innerR, outerR))
|
|
235
238
|
.attr('fill', fillColor)
|
|
236
239
|
.attr('stroke', mutedColor)
|
|
237
240
|
.attr('stroke-width', 0.5);
|
|
241
|
+
|
|
242
|
+
// Transparent hover overlay for ring interaction
|
|
243
|
+
svg
|
|
244
|
+
.append('path')
|
|
245
|
+
.attr('d', arcGen(innerR, outerR))
|
|
246
|
+
.attr('fill', 'transparent')
|
|
247
|
+
.attr('data-ring-arc', ringName)
|
|
248
|
+
.style('cursor', 'pointer')
|
|
249
|
+
.on('mouseenter', () => {
|
|
250
|
+
// Tint the hovered ring arc
|
|
251
|
+
d3Selection
|
|
252
|
+
.select(rootContainer)
|
|
253
|
+
.selectAll<SVGPathElement, unknown>('[data-ring-arc]')
|
|
254
|
+
.each(function () {
|
|
255
|
+
const el = d3Selection.select(this);
|
|
256
|
+
const isMatch = this.getAttribute('data-ring-arc') === ringName;
|
|
257
|
+
el.attr('fill', isMatch ? qColor : 'transparent').attr(
|
|
258
|
+
'opacity',
|
|
259
|
+
isMatch ? 0.15 : 1
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
dimExceptRing(rootContainer, ringName);
|
|
263
|
+
})
|
|
264
|
+
.on('mouseleave', () => {
|
|
265
|
+
d3Selection
|
|
266
|
+
.select(rootContainer)
|
|
267
|
+
.selectAll<SVGPathElement, unknown>('[data-ring-arc]')
|
|
268
|
+
.attr('fill', 'transparent')
|
|
269
|
+
.attr('opacity', 1);
|
|
270
|
+
clearDim(rootContainer);
|
|
271
|
+
});
|
|
238
272
|
}
|
|
239
273
|
|
|
240
274
|
// Ring labels removed — the side panel ring headers serve this purpose
|
|
@@ -346,10 +380,28 @@ function dimExcept(root: HTMLElement, lineNum: string): void {
|
|
|
346
380
|
});
|
|
347
381
|
}
|
|
348
382
|
|
|
383
|
+
function dimExceptRing(root: HTMLElement, ringName: string): void {
|
|
384
|
+
// Dim blips not in the hovered ring (SVG + HTML)
|
|
385
|
+
root.querySelectorAll<HTMLElement>('[data-line-number]').forEach((el) => {
|
|
386
|
+
el.style.opacity =
|
|
387
|
+
el.getAttribute('data-ring') === ringName ? '1' : String(DIM_OPACITY);
|
|
388
|
+
});
|
|
389
|
+
// Dim ring groups not matching
|
|
390
|
+
root.querySelectorAll<HTMLElement>('[data-ring-group]').forEach((el) => {
|
|
391
|
+
el.style.opacity =
|
|
392
|
+
el.getAttribute('data-ring-group') === ringName
|
|
393
|
+
? '1'
|
|
394
|
+
: String(DIM_OPACITY);
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
349
398
|
function clearDim(root: HTMLElement): void {
|
|
350
399
|
root.querySelectorAll<HTMLElement>('[data-line-number]').forEach((el) => {
|
|
351
400
|
el.style.opacity = '1';
|
|
352
401
|
});
|
|
402
|
+
root.querySelectorAll<HTMLElement>('[data-ring-group]').forEach((el) => {
|
|
403
|
+
el.style.opacity = '1';
|
|
404
|
+
});
|
|
353
405
|
}
|
|
354
406
|
|
|
355
407
|
// ============================================================
|
|
@@ -383,11 +435,13 @@ function renderHtmlPanel(
|
|
|
383
435
|
|
|
384
436
|
// Ring group container
|
|
385
437
|
const ringGroup = document.createElement('div');
|
|
438
|
+
ringGroup.setAttribute('data-ring-group', ringName);
|
|
386
439
|
ringGroup.style.cssText = `
|
|
387
440
|
background: ${palette.surface};
|
|
388
441
|
border-radius: 8px;
|
|
389
442
|
padding: 10px;
|
|
390
443
|
margin-bottom: 12px;
|
|
444
|
+
transition: opacity 0.15s;
|
|
391
445
|
`;
|
|
392
446
|
|
|
393
447
|
// Ring header inside the group
|