@diagrammo/dgmo 0.6.0 → 0.6.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/cli.cjs +163 -162
- package/dist/index.cjs +378 -512
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -20
- package/dist/index.d.ts +5 -20
- package/dist/index.js +378 -512
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/c4/layout.ts +7 -67
- package/src/c4/renderer.ts +122 -119
- package/src/cli.ts +6 -38
- package/src/d3.ts +55 -35
- package/src/echarts.ts +24 -24
- package/src/er/renderer.ts +15 -9
- package/src/index.ts +2 -2
- package/src/infra/compute.ts +1 -21
- package/src/infra/parser.ts +5 -32
- package/src/infra/renderer.ts +28 -164
- package/src/infra/types.ts +1 -11
- package/src/initiative-status/layout.ts +9 -6
- package/src/kanban/renderer.ts +28 -24
- package/src/org/renderer.ts +24 -23
- package/src/render.ts +2 -2
- package/src/sequence/renderer.ts +24 -19
- package/src/sitemap/layout.ts +7 -14
- package/src/sitemap/renderer.ts +30 -29
- package/src/utils/legend-constants.ts +25 -0
package/src/d3.ts
CHANGED
|
@@ -184,6 +184,19 @@ import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
|
|
|
184
184
|
import { collectIndentedValues, extractColor, parsePipeMetadata } from './utils/parsing';
|
|
185
185
|
import { matchTagBlockHeading, validateTagValues, resolveTagColor } from './utils/tag-groups';
|
|
186
186
|
import type { TagGroup } from './utils/tag-groups';
|
|
187
|
+
import {
|
|
188
|
+
LEGEND_HEIGHT as TL_LEGEND_HEIGHT,
|
|
189
|
+
LEGEND_PILL_PAD as TL_LEGEND_PILL_PAD,
|
|
190
|
+
LEGEND_PILL_FONT_SIZE as TL_LEGEND_PILL_FONT_SIZE,
|
|
191
|
+
LEGEND_PILL_FONT_W as TL_LEGEND_PILL_FONT_W,
|
|
192
|
+
LEGEND_CAPSULE_PAD as TL_LEGEND_CAPSULE_PAD,
|
|
193
|
+
LEGEND_DOT_R as TL_LEGEND_DOT_R,
|
|
194
|
+
LEGEND_ENTRY_FONT_SIZE as TL_LEGEND_ENTRY_FONT_SIZE,
|
|
195
|
+
LEGEND_ENTRY_FONT_W as TL_LEGEND_ENTRY_FONT_W,
|
|
196
|
+
LEGEND_ENTRY_DOT_GAP as TL_LEGEND_ENTRY_DOT_GAP,
|
|
197
|
+
LEGEND_ENTRY_TRAIL as TL_LEGEND_ENTRY_TRAIL,
|
|
198
|
+
LEGEND_GROUP_GAP as TL_LEGEND_GROUP_GAP,
|
|
199
|
+
} from './utils/legend-constants';
|
|
187
200
|
|
|
188
201
|
// ============================================================
|
|
189
202
|
// Shared Rendering Helpers
|
|
@@ -3047,7 +3060,7 @@ export function renderTimeline(
|
|
|
3047
3060
|
}
|
|
3048
3061
|
}
|
|
3049
3062
|
|
|
3050
|
-
// Reserve space for tag legend
|
|
3063
|
+
// Reserve space for tag legend at the bottom of chart content
|
|
3051
3064
|
const tagLegendReserve = parsed.timelineTagGroups.length > 0 ? 36 : 0;
|
|
3052
3065
|
|
|
3053
3066
|
// ================================================================
|
|
@@ -3087,9 +3100,9 @@ export function renderTimeline(
|
|
|
3087
3100
|
const scaleMargin = timelineScale ? 40 : 0;
|
|
3088
3101
|
const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
|
|
3089
3102
|
const margin = {
|
|
3090
|
-
top: 104 + markerMargin
|
|
3103
|
+
top: 104 + markerMargin,
|
|
3091
3104
|
right: 40 + scaleMargin,
|
|
3092
|
-
bottom: 40,
|
|
3105
|
+
bottom: 40 + tagLegendReserve,
|
|
3093
3106
|
left: 60 + scaleMargin,
|
|
3094
3107
|
};
|
|
3095
3108
|
const innerWidth = width - margin.left - margin.right;
|
|
@@ -3309,9 +3322,9 @@ export function renderTimeline(
|
|
|
3309
3322
|
const scaleMargin = timelineScale ? 40 : 0;
|
|
3310
3323
|
const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
|
|
3311
3324
|
const margin = {
|
|
3312
|
-
top: 104 + markerMargin
|
|
3325
|
+
top: 104 + markerMargin,
|
|
3313
3326
|
right: 200,
|
|
3314
|
-
bottom: 40,
|
|
3327
|
+
bottom: 40 + tagLegendReserve,
|
|
3315
3328
|
left: 60 + scaleMargin,
|
|
3316
3329
|
};
|
|
3317
3330
|
const innerWidth = width - margin.left - margin.right;
|
|
@@ -3590,9 +3603,9 @@ export function renderTimeline(
|
|
|
3590
3603
|
// Group-sorted doesn't need legend space (group names shown on left)
|
|
3591
3604
|
const baseTopMargin = title ? 50 : 20;
|
|
3592
3605
|
const margin = {
|
|
3593
|
-
top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin
|
|
3606
|
+
top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin,
|
|
3594
3607
|
right: 40,
|
|
3595
|
-
bottom: 40 + scaleMargin,
|
|
3608
|
+
bottom: 40 + scaleMargin + tagLegendReserve,
|
|
3596
3609
|
left: dynamicLeftMargin,
|
|
3597
3610
|
};
|
|
3598
3611
|
const innerWidth = width - margin.left - margin.right;
|
|
@@ -3869,9 +3882,9 @@ export function renderTimeline(
|
|
|
3869
3882
|
const scaleMargin = timelineScale ? 24 : 0;
|
|
3870
3883
|
const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
|
|
3871
3884
|
const margin = {
|
|
3872
|
-
top: 104 + (timelineScale ? 40 : 0) + markerMargin
|
|
3885
|
+
top: 104 + (timelineScale ? 40 : 0) + markerMargin,
|
|
3873
3886
|
right: 40,
|
|
3874
|
-
bottom: 40 + scaleMargin,
|
|
3887
|
+
bottom: 40 + scaleMargin + tagLegendReserve,
|
|
3875
3888
|
left: 60,
|
|
3876
3889
|
};
|
|
3877
3890
|
const innerWidth = width - margin.left - margin.right;
|
|
@@ -4121,23 +4134,23 @@ export function renderTimeline(
|
|
|
4121
4134
|
|
|
4122
4135
|
// ── Tag Legend (org-chart-style pills) ──
|
|
4123
4136
|
if (parsed.timelineTagGroups.length > 0) {
|
|
4124
|
-
const LG_HEIGHT =
|
|
4125
|
-
const LG_PILL_PAD =
|
|
4126
|
-
const LG_PILL_FONT_SIZE =
|
|
4127
|
-
const LG_PILL_FONT_W =
|
|
4128
|
-
const LG_CAPSULE_PAD =
|
|
4129
|
-
const LG_DOT_R =
|
|
4130
|
-
const LG_ENTRY_FONT_SIZE =
|
|
4131
|
-
const LG_ENTRY_FONT_W =
|
|
4132
|
-
const LG_ENTRY_DOT_GAP =
|
|
4133
|
-
const LG_ENTRY_TRAIL =
|
|
4134
|
-
const LG_GROUP_GAP =
|
|
4135
|
-
const LG_ICON_W = 20; // swimlane icon area (icon + surrounding space)
|
|
4137
|
+
const LG_HEIGHT = TL_LEGEND_HEIGHT;
|
|
4138
|
+
const LG_PILL_PAD = TL_LEGEND_PILL_PAD;
|
|
4139
|
+
const LG_PILL_FONT_SIZE = TL_LEGEND_PILL_FONT_SIZE;
|
|
4140
|
+
const LG_PILL_FONT_W = TL_LEGEND_PILL_FONT_W;
|
|
4141
|
+
const LG_CAPSULE_PAD = TL_LEGEND_CAPSULE_PAD;
|
|
4142
|
+
const LG_DOT_R = TL_LEGEND_DOT_R;
|
|
4143
|
+
const LG_ENTRY_FONT_SIZE = TL_LEGEND_ENTRY_FONT_SIZE;
|
|
4144
|
+
const LG_ENTRY_FONT_W = TL_LEGEND_ENTRY_FONT_W;
|
|
4145
|
+
const LG_ENTRY_DOT_GAP = TL_LEGEND_ENTRY_DOT_GAP;
|
|
4146
|
+
const LG_ENTRY_TRAIL = TL_LEGEND_ENTRY_TRAIL;
|
|
4147
|
+
const LG_GROUP_GAP = TL_LEGEND_GROUP_GAP;
|
|
4148
|
+
const LG_ICON_W = 20; // swimlane icon area (icon + surrounding space) — local
|
|
4136
4149
|
|
|
4137
4150
|
const mainSvg = d3Selection.select(container).select<SVGSVGElement>('svg');
|
|
4138
4151
|
const mainG = mainSvg.select<SVGGElement>('g');
|
|
4139
4152
|
if (!mainSvg.empty() && !mainG.empty()) {
|
|
4140
|
-
const legendY =
|
|
4153
|
+
const legendY = height - LG_HEIGHT - 4;
|
|
4141
4154
|
|
|
4142
4155
|
const groupBg = isDark
|
|
4143
4156
|
? mix(palette.surface, palette.bg, 50)
|
|
@@ -4212,6 +4225,7 @@ export function renderTimeline(
|
|
|
4212
4225
|
function drawLegend() {
|
|
4213
4226
|
// Remove previous legend
|
|
4214
4227
|
mainSvg.selectAll('.tl-tag-legend-group').remove();
|
|
4228
|
+
mainSvg.selectAll('.tl-tag-legend-container').remove();
|
|
4215
4229
|
|
|
4216
4230
|
// Effective color source: explicit color group > swimlane group
|
|
4217
4231
|
const effectiveColorKey = (currentActiveGroup ?? currentSwimlaneGroup)?.toLowerCase() ?? null;
|
|
@@ -4238,6 +4252,13 @@ export function renderTimeline(
|
|
|
4238
4252
|
|
|
4239
4253
|
let cx = (width - totalW) / 2;
|
|
4240
4254
|
|
|
4255
|
+
// Legend container for data-legend-active attribute
|
|
4256
|
+
const legendContainer = mainSvg.append('g')
|
|
4257
|
+
.attr('class', 'tl-tag-legend-container');
|
|
4258
|
+
if (currentActiveGroup) {
|
|
4259
|
+
legendContainer.attr('data-legend-active', currentActiveGroup.toLowerCase());
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4241
4262
|
for (const lg of visibleGroups) {
|
|
4242
4263
|
const groupKey = lg.group.name.toLowerCase();
|
|
4243
4264
|
const isActive = viewMode ||
|
|
@@ -4249,7 +4270,7 @@ export function renderTimeline(
|
|
|
4249
4270
|
const pillLabel = lg.group.name;
|
|
4250
4271
|
const pillWidth = pillLabel.length * LG_PILL_FONT_W + LG_PILL_PAD;
|
|
4251
4272
|
|
|
4252
|
-
const gEl =
|
|
4273
|
+
const gEl = legendContainer
|
|
4253
4274
|
.append('g')
|
|
4254
4275
|
.attr('transform', `translate(${cx}, ${legendY})`)
|
|
4255
4276
|
.attr('class', 'tl-tag-legend-group tl-tag-legend-entry')
|
|
@@ -5781,7 +5802,7 @@ export async function renderForExport(
|
|
|
5781
5802
|
hiddenAttributes?: Set<string>;
|
|
5782
5803
|
swimlaneTagGroup?: string | null;
|
|
5783
5804
|
},
|
|
5784
|
-
options?: { branding?: boolean; c4Level?: 'context' | 'containers' | 'components' | 'deployment'; c4System?: string; c4Container?: string;
|
|
5805
|
+
options?: { branding?: boolean; c4Level?: 'context' | 'containers' | 'components' | 'deployment'; c4System?: string; c4Container?: string; tagGroup?: string }
|
|
5785
5806
|
): Promise<string> {
|
|
5786
5807
|
// Flowchart and org chart use their own parser pipelines — intercept before parseVisualization()
|
|
5787
5808
|
const { parseDgmoChartType } = await import('./dgmo-router');
|
|
@@ -5801,7 +5822,7 @@ export async function renderForExport(
|
|
|
5801
5822
|
|
|
5802
5823
|
// Apply interactive collapse state when provided
|
|
5803
5824
|
const collapsedNodes = orgExportState?.collapsedNodes;
|
|
5804
|
-
const activeTagGroup = orgExportState?.activeTagGroup ?? null;
|
|
5825
|
+
const activeTagGroup = orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
|
|
5805
5826
|
const hiddenAttributes = orgExportState?.hiddenAttributes;
|
|
5806
5827
|
|
|
5807
5828
|
const { parsed: effectiveParsed, hiddenCounts } =
|
|
@@ -5841,7 +5862,7 @@ export async function renderForExport(
|
|
|
5841
5862
|
|
|
5842
5863
|
// Apply interactive collapse state when provided
|
|
5843
5864
|
const collapsedNodes = orgExportState?.collapsedNodes;
|
|
5844
|
-
const activeTagGroup = orgExportState?.activeTagGroup ?? null;
|
|
5865
|
+
const activeTagGroup = orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
|
|
5845
5866
|
const hiddenAttributes = orgExportState?.hiddenAttributes;
|
|
5846
5867
|
|
|
5847
5868
|
const { parsed: effectiveParsed, hiddenCounts } =
|
|
@@ -5881,7 +5902,7 @@ export async function renderForExport(
|
|
|
5881
5902
|
container.style.left = '-9999px';
|
|
5882
5903
|
document.body.appendChild(container);
|
|
5883
5904
|
|
|
5884
|
-
renderKanban(container, kanbanParsed, effectivePalette, theme === 'dark');
|
|
5905
|
+
renderKanban(container, kanbanParsed, effectivePalette, theme === 'dark', undefined, undefined, options?.tagGroup);
|
|
5885
5906
|
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5886
5907
|
}
|
|
5887
5908
|
|
|
@@ -5921,7 +5942,7 @@ export async function renderForExport(
|
|
|
5921
5942
|
const exportHeight = erLayout.height + PADDING * 2 + titleOffset;
|
|
5922
5943
|
const container = createExportContainer(exportWidth, exportHeight);
|
|
5923
5944
|
|
|
5924
|
-
renderERDiagram(container, erParsed, erLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
5945
|
+
renderERDiagram(container, erParsed, erLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight }, options?.tagGroup);
|
|
5925
5946
|
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5926
5947
|
}
|
|
5927
5948
|
|
|
@@ -5979,7 +6000,7 @@ export async function renderForExport(
|
|
|
5979
6000
|
? renderC4Containers
|
|
5980
6001
|
: renderC4Context;
|
|
5981
6002
|
|
|
5982
|
-
renderFn(container, c4Parsed, c4Layout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
|
|
6003
|
+
renderFn(container, c4Parsed, c4Layout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight }, options?.tagGroup);
|
|
5983
6004
|
return finalizeSvgExport(container, theme, effectivePalette, options);
|
|
5984
6005
|
}
|
|
5985
6006
|
|
|
@@ -6009,11 +6030,9 @@ export async function renderForExport(
|
|
|
6009
6030
|
const infraParsed = parseInfra(content);
|
|
6010
6031
|
if (infraParsed.error || infraParsed.nodes.length === 0) return '';
|
|
6011
6032
|
|
|
6012
|
-
const
|
|
6013
|
-
? infraParsed.scenarios.find((s) => s.name.toLowerCase() === options.scenario!.toLowerCase()) ?? null
|
|
6014
|
-
: null;
|
|
6015
|
-
const infraComputed = computeInfra(infraParsed, selectedScenario ? { scenario: selectedScenario } : {});
|
|
6033
|
+
const infraComputed = computeInfra(infraParsed);
|
|
6016
6034
|
const infraLayout = layoutInfra(infraComputed);
|
|
6035
|
+
const activeTagGroup = options?.tagGroup ?? null;
|
|
6017
6036
|
|
|
6018
6037
|
const titleOffset = infraParsed.title ? 40 : 0;
|
|
6019
6038
|
const legendGroups = computeInfraLegendGroups(infraLayout.nodes, infraParsed.tagGroups, effectivePalette);
|
|
@@ -6022,7 +6041,7 @@ export async function renderForExport(
|
|
|
6022
6041
|
const exportHeight = infraLayout.height + titleOffset + legendOffset;
|
|
6023
6042
|
const container = createExportContainer(exportWidth, exportHeight);
|
|
6024
6043
|
|
|
6025
|
-
renderInfra(container, infraLayout, effectivePalette, theme === 'dark', infraParsed.title, infraParsed.titleLineNumber, infraParsed.tagGroups,
|
|
6044
|
+
renderInfra(container, infraLayout, effectivePalette, theme === 'dark', infraParsed.title, infraParsed.titleLineNumber, infraParsed.tagGroups, activeTagGroup, false, null, null, true);
|
|
6026
6045
|
// Restore explicit pixel dimensions for resvg (renderer uses 100%/viewBox for app scaling)
|
|
6027
6046
|
const infraSvg = container.querySelector('svg');
|
|
6028
6047
|
if (infraSvg) {
|
|
@@ -6079,6 +6098,7 @@ export async function renderForExport(
|
|
|
6079
6098
|
if (seqParsed.error || seqParsed.participants.length === 0) return '';
|
|
6080
6099
|
renderSequenceDiagram(container, seqParsed, effectivePalette, isDark, undefined, {
|
|
6081
6100
|
exportWidth: EXPORT_WIDTH,
|
|
6101
|
+
activeTagGroup: options?.tagGroup,
|
|
6082
6102
|
});
|
|
6083
6103
|
} else if (parsed.type === 'wordcloud') {
|
|
6084
6104
|
await renderWordCloudAsync(container, parsed, effectivePalette, isDark, dims);
|
|
@@ -6086,7 +6106,7 @@ export async function renderForExport(
|
|
|
6086
6106
|
renderArcDiagram(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
6087
6107
|
} else if (parsed.type === 'timeline') {
|
|
6088
6108
|
renderTimeline(container, parsed, effectivePalette, isDark, undefined, dims,
|
|
6089
|
-
orgExportState?.activeTagGroup, orgExportState?.swimlaneTagGroup);
|
|
6109
|
+
orgExportState?.activeTagGroup ?? options?.tagGroup, orgExportState?.swimlaneTagGroup);
|
|
6090
6110
|
} else if (parsed.type === 'venn') {
|
|
6091
6111
|
renderVenn(container, parsed, effectivePalette, isDark, undefined, dims);
|
|
6092
6112
|
} else if (parsed.type === 'quadrant') {
|
package/src/echarts.ts
CHANGED
|
@@ -1323,7 +1323,7 @@ function makeGridAxis(
|
|
|
1323
1323
|
data?: string[],
|
|
1324
1324
|
nameGapOverride?: number,
|
|
1325
1325
|
chartWidthHint?: number,
|
|
1326
|
-
intervalOverride?:
|
|
1326
|
+
intervalOverride?: number
|
|
1327
1327
|
): Record<string, unknown> {
|
|
1328
1328
|
const defaultGap = type === 'value' ? 75 : 40;
|
|
1329
1329
|
|
|
@@ -1333,13 +1333,17 @@ function makeGridAxis(
|
|
|
1333
1333
|
if (type === 'category' && data && data.length > 0) {
|
|
1334
1334
|
const maxLabelLen = Math.max(...data.map((l) => l.length));
|
|
1335
1335
|
const count = data.length;
|
|
1336
|
+
// When interval skips labels, base sizing on visible count (≈ count / step)
|
|
1337
|
+
const step = intervalOverride != null && intervalOverride > 0 ? intervalOverride + 1 : 1;
|
|
1338
|
+
const visibleCount = Math.ceil(count / step);
|
|
1336
1339
|
// Reduce font size based on density and label length
|
|
1337
|
-
if (
|
|
1338
|
-
else if (
|
|
1340
|
+
if (visibleCount > 10 || maxLabelLen > 20) catFontSize = 10;
|
|
1341
|
+
else if (visibleCount > 5 || maxLabelLen > 14) catFontSize = 11;
|
|
1339
1342
|
else if (maxLabelLen > 8) catFontSize = 12;
|
|
1340
1343
|
|
|
1341
|
-
// Constrain labels to their allotted slot width so ECharts wraps instead of hiding
|
|
1342
|
-
|
|
1344
|
+
// Constrain labels to their allotted slot width so ECharts wraps instead of hiding.
|
|
1345
|
+
// Skip when interval > 0 — visible labels are spread out and need no constraint.
|
|
1346
|
+
if ((intervalOverride == null || intervalOverride === 0) && chartWidthHint && count > 0) {
|
|
1343
1347
|
const availPerLabel = Math.floor((chartWidthHint * 0.85) / count);
|
|
1344
1348
|
catLabelExtras = {
|
|
1345
1349
|
width: availPerLabel,
|
|
@@ -1358,6 +1362,9 @@ function makeGridAxis(
|
|
|
1358
1362
|
fontFamily: FONT_FAMILY,
|
|
1359
1363
|
...(type === 'category' && {
|
|
1360
1364
|
interval: intervalOverride ?? 0,
|
|
1365
|
+
// Prevent ECharts auto-rotation: it measures raw slot width (chartWidth/N),
|
|
1366
|
+
// which is too narrow when an interval skips most labels, and rotates to 90°.
|
|
1367
|
+
rotate: 0,
|
|
1361
1368
|
formatter: (value: string) =>
|
|
1362
1369
|
value.replace(/([a-z])([A-Z])/g, '$1\n$2'),
|
|
1363
1370
|
...catLabelExtras,
|
|
@@ -1477,24 +1484,17 @@ function buildBarOption(
|
|
|
1477
1484
|
|
|
1478
1485
|
// ── Era band helpers ──────────────────────────────────────────
|
|
1479
1486
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1487
|
+
// Returns an integer interval for ECharts axisLabel.interval.
|
|
1488
|
+
// interval: N means show label at index 0, N+1, 2*(N+1), ...
|
|
1489
|
+
// For a desired step S we return S-1.
|
|
1490
|
+
// Targets ~5 visible labels — conservative enough to prevent ECharts stagger.
|
|
1491
|
+
function buildIntervalStep(labels: string[]): number {
|
|
1484
1492
|
const count = labels.length;
|
|
1485
|
-
if (count <=
|
|
1486
|
-
const snapSteps = [1, 2,
|
|
1487
|
-
const raw = Math.ceil(count /
|
|
1493
|
+
if (count <= 6) return 0; // show all
|
|
1494
|
+
const snapSteps = [1, 2, 5, 10, 25, 50, 100];
|
|
1495
|
+
const raw = Math.ceil(count / 5); // target ~5 visible labels
|
|
1488
1496
|
const N = [...snapSteps].reverse().find((s) => s <= raw) ?? 1; // snap down
|
|
1489
|
-
|
|
1490
|
-
for (let i = 0; i < count; i += N) pinned.add(i);
|
|
1491
|
-
for (const era of eras) {
|
|
1492
|
-
const si = labels.indexOf(era.start);
|
|
1493
|
-
const ei = labels.indexOf(era.end);
|
|
1494
|
-
if (si >= 0) pinned.add(si);
|
|
1495
|
-
if (ei >= 0) pinned.add(ei);
|
|
1496
|
-
}
|
|
1497
|
-
return (index: number) => pinned.has(index);
|
|
1497
|
+
return N - 1; // ECharts shows labels at indices 0, N, 2N, ...
|
|
1498
1498
|
}
|
|
1499
1499
|
|
|
1500
1500
|
function buildMarkArea(
|
|
@@ -1548,7 +1548,7 @@ function buildLineOption(
|
|
|
1548
1548
|
const labels = parsed.data.map((d) => d.label);
|
|
1549
1549
|
const values = parsed.data.map((d) => d.value);
|
|
1550
1550
|
const eras = parsed.eras ?? [];
|
|
1551
|
-
const interval =
|
|
1551
|
+
const interval = buildIntervalStep(labels);
|
|
1552
1552
|
const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
|
|
1553
1553
|
|
|
1554
1554
|
return {
|
|
@@ -1595,7 +1595,7 @@ function buildMultiLineOption(
|
|
|
1595
1595
|
const seriesNames = parsed.seriesNames ?? [];
|
|
1596
1596
|
const labels = parsed.data.map((d) => d.label);
|
|
1597
1597
|
const eras = parsed.eras ?? [];
|
|
1598
|
-
const interval =
|
|
1598
|
+
const interval = buildIntervalStep(labels);
|
|
1599
1599
|
const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
|
|
1600
1600
|
|
|
1601
1601
|
const series = seriesNames.map((name, idx) => {
|
|
@@ -1654,7 +1654,7 @@ function buildAreaOption(
|
|
|
1654
1654
|
const labels = parsed.data.map((d) => d.label);
|
|
1655
1655
|
const values = parsed.data.map((d) => d.value);
|
|
1656
1656
|
const eras = parsed.eras ?? [];
|
|
1657
|
-
const interval =
|
|
1657
|
+
const interval = buildIntervalStep(labels);
|
|
1658
1658
|
const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
|
|
1659
1659
|
|
|
1660
1660
|
return {
|
package/src/er/renderer.ts
CHANGED
|
@@ -9,6 +9,12 @@ import type { PaletteColors } from '../palettes';
|
|
|
9
9
|
import { mix } from '../palettes/color-utils';
|
|
10
10
|
import { getSeriesColors } from '../palettes';
|
|
11
11
|
import { resolveTagColor } from '../utils/tag-groups';
|
|
12
|
+
import {
|
|
13
|
+
LEGEND_HEIGHT,
|
|
14
|
+
LEGEND_PILL_PAD,
|
|
15
|
+
LEGEND_PILL_FONT_SIZE,
|
|
16
|
+
LEGEND_GROUP_GAP,
|
|
17
|
+
} from '../utils/legend-constants';
|
|
12
18
|
import type { ParsedERDiagram, ERConstraint } from './types';
|
|
13
19
|
import type { ERLayoutResult, ERLayoutNode, ERLayoutEdge } from './layout';
|
|
14
20
|
import { parseERDiagram } from './parser';
|
|
@@ -445,17 +451,17 @@ export function renderERDiagram(
|
|
|
445
451
|
|
|
446
452
|
// ── Tag Legend ──
|
|
447
453
|
if (parsed.tagGroups.length > 0) {
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
const LEGEND_PILL_RX = 11;
|
|
451
|
-
const LEGEND_PILL_PAD = 10;
|
|
454
|
+
const LEGEND_PILL_H = LEGEND_HEIGHT - 6;
|
|
455
|
+
const LEGEND_PILL_RX = Math.floor(LEGEND_PILL_H / 2);
|
|
452
456
|
const LEGEND_GAP = 8;
|
|
453
|
-
const LEGEND_FONT_SIZE = 11;
|
|
454
|
-
const LEGEND_GROUP_GAP = 16;
|
|
455
457
|
|
|
456
458
|
const legendG = svg.append('g')
|
|
457
459
|
.attr('class', 'er-tag-legend');
|
|
458
460
|
|
|
461
|
+
if (activeTagGroup) {
|
|
462
|
+
legendG.attr('data-legend-active', activeTagGroup.toLowerCase());
|
|
463
|
+
}
|
|
464
|
+
|
|
459
465
|
let legendX = DIAGRAM_PADDING;
|
|
460
466
|
let legendY = height - DIAGRAM_PADDING;
|
|
461
467
|
|
|
@@ -469,7 +475,7 @@ export function renderERDiagram(
|
|
|
469
475
|
.attr('y', legendY + LEGEND_PILL_H / 2)
|
|
470
476
|
.attr('dominant-baseline', 'central')
|
|
471
477
|
.attr('fill', palette.textMuted)
|
|
472
|
-
.attr('font-size',
|
|
478
|
+
.attr('font-size', LEGEND_PILL_FONT_SIZE)
|
|
473
479
|
.attr('font-family', FONT_FAMILY)
|
|
474
480
|
.text(`${group.name}:`);
|
|
475
481
|
|
|
@@ -484,7 +490,7 @@ export function renderERDiagram(
|
|
|
484
490
|
|
|
485
491
|
// Estimate text width
|
|
486
492
|
const tmpText = legendG.append('text')
|
|
487
|
-
.attr('font-size',
|
|
493
|
+
.attr('font-size', LEGEND_PILL_FONT_SIZE)
|
|
488
494
|
.attr('font-family', FONT_FAMILY)
|
|
489
495
|
.text(entry.value);
|
|
490
496
|
const textW = tmpText.node()?.getComputedTextLength?.() ?? entry.value.length * 7;
|
|
@@ -509,7 +515,7 @@ export function renderERDiagram(
|
|
|
509
515
|
.attr('text-anchor', 'middle')
|
|
510
516
|
.attr('dominant-baseline', 'central')
|
|
511
517
|
.attr('fill', palette.text)
|
|
512
|
-
.attr('font-size',
|
|
518
|
+
.attr('font-size', LEGEND_PILL_FONT_SIZE)
|
|
513
519
|
.attr('font-family', FONT_FAMILY)
|
|
514
520
|
.text(entry.value);
|
|
515
521
|
|
package/src/index.ts
CHANGED
|
@@ -246,7 +246,7 @@ export { collapseSitemapTree } from './sitemap/collapse';
|
|
|
246
246
|
|
|
247
247
|
// ── Infra Chart ────────────────────────────────────────────
|
|
248
248
|
export { parseInfra } from './infra/parser';
|
|
249
|
-
export type { ParsedInfra, InfraNode, InfraEdge, InfraGroup, InfraTagGroup, InfraProperty, InfraDiagnostic,
|
|
249
|
+
export type { ParsedInfra, InfraNode, InfraEdge, InfraGroup, InfraTagGroup, InfraProperty, InfraDiagnostic, InfraComputeParams, InfraBehaviorKey } from './infra/types';
|
|
250
250
|
export { INFRA_BEHAVIOR_KEYS } from './infra/types';
|
|
251
251
|
export { computeInfra } from './infra/compute';
|
|
252
252
|
export type { ComputedInfraModel, ComputedInfraNode, ComputedInfraEdge, InfraLatencyPercentiles, InfraAvailabilityPercentiles, InfraCbState } from './infra/types';
|
|
@@ -256,7 +256,7 @@ export type { InfraRole } from './infra/roles';
|
|
|
256
256
|
export { layoutInfra } from './infra/layout';
|
|
257
257
|
export type { InfraLayoutResult, InfraLayoutNode, InfraLayoutEdge, InfraLayoutGroup } from './infra/layout';
|
|
258
258
|
export { renderInfra, parseAndLayoutInfra, computeInfraLegendGroups } from './infra/renderer';
|
|
259
|
-
export type { InfraLegendGroup
|
|
259
|
+
export type { InfraLegendGroup } from './infra/renderer';
|
|
260
260
|
export type { CollapsedSitemapResult } from './sitemap/collapse';
|
|
261
261
|
|
|
262
262
|
export { collapseOrgTree } from './org/collapse';
|
package/src/infra/compute.ts
CHANGED
|
@@ -604,28 +604,8 @@ export function computeInfra(
|
|
|
604
604
|
const defaultLatencyMs = parseFloat(parsed.options['default-latency-ms'] ?? '') || 0;
|
|
605
605
|
const defaultUptime = parseFloat(parsed.options['default-uptime'] ?? '') || 100;
|
|
606
606
|
|
|
607
|
-
// Apply scenario overrides (shallow clone nodes with modified properties)
|
|
608
|
-
let effectiveNodes = parsed.nodes;
|
|
609
|
-
if (params.scenario) {
|
|
610
|
-
const overrides = params.scenario.overrides;
|
|
611
|
-
effectiveNodes = parsed.nodes.map((node) => {
|
|
612
|
-
const nodeOverrides = overrides[node.id];
|
|
613
|
-
if (!nodeOverrides) return node;
|
|
614
|
-
const props = node.properties.map((p) => {
|
|
615
|
-
const ov = nodeOverrides[p.key];
|
|
616
|
-
return ov != null ? { ...p, value: ov } : p;
|
|
617
|
-
});
|
|
618
|
-
// Add new properties from scenario that don't exist on the node
|
|
619
|
-
for (const [key, val] of Object.entries(nodeOverrides)) {
|
|
620
|
-
if (!props.some((p) => p.key === key)) {
|
|
621
|
-
props.push({ key, value: val, lineNumber: node.lineNumber });
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
return { ...node, properties: props };
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
607
|
// Apply per-node property overrides (from interactive sliders)
|
|
628
|
-
|
|
608
|
+
let effectiveNodes = parsed.nodes;
|
|
629
609
|
if (params.propertyOverrides) {
|
|
630
610
|
const propOv = params.propertyOverrides;
|
|
631
611
|
effectiveNodes = effectiveNodes.map((node) => {
|
package/src/infra/parser.ts
CHANGED
|
@@ -110,7 +110,6 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
110
110
|
edges: [],
|
|
111
111
|
groups: [],
|
|
112
112
|
tagGroups: [],
|
|
113
|
-
scenarios: [],
|
|
114
113
|
options: {},
|
|
115
114
|
diagnostics: [],
|
|
116
115
|
error: null,
|
|
@@ -250,46 +249,20 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
250
249
|
continue;
|
|
251
250
|
}
|
|
252
251
|
|
|
253
|
-
// scenario: Name
|
|
252
|
+
// scenario: Name — deprecated, emit warning and skip block
|
|
254
253
|
if (/^scenario\s*:/i.test(trimmed)) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
currentGroup = null;
|
|
258
|
-
const scenarioName = trimmed.replace(/^scenario\s*:\s*/i, '').trim();
|
|
259
|
-
const scenario: import('./types').InfraScenario = {
|
|
260
|
-
name: scenarioName,
|
|
261
|
-
overrides: {},
|
|
262
|
-
lineNumber,
|
|
263
|
-
};
|
|
264
|
-
// Parse indented block for scenario overrides
|
|
265
|
-
let scenarioNodeId: string | null = null;
|
|
254
|
+
console.warn('[dgmo warn] scenario syntax is deprecated and will be ignored');
|
|
255
|
+
// Skip indented block
|
|
266
256
|
let si = i + 1;
|
|
267
257
|
while (si < lines.length) {
|
|
268
258
|
const sLine = lines[si];
|
|
269
259
|
const sTrimmed = sLine.trim();
|
|
270
260
|
if (!sTrimmed || sTrimmed.startsWith('#')) { si++; continue; }
|
|
271
261
|
const sIndent = sLine.length - sLine.trimStart().length;
|
|
272
|
-
if (sIndent === 0) break;
|
|
273
|
-
|
|
274
|
-
if (sIndent <= 2) {
|
|
275
|
-
// Node reference (e.g., " edge" or " API")
|
|
276
|
-
scenarioNodeId = nodeId(sTrimmed.replace(/\|.*$/, '').trim());
|
|
277
|
-
if (!scenario.overrides[scenarioNodeId]) {
|
|
278
|
-
scenario.overrides[scenarioNodeId] = {};
|
|
279
|
-
}
|
|
280
|
-
} else if (scenarioNodeId) {
|
|
281
|
-
// Property override (e.g., " rps: 10000")
|
|
282
|
-
const pm = sTrimmed.match(PROPERTY_RE);
|
|
283
|
-
if (pm) {
|
|
284
|
-
const key = pm[1].toLowerCase();
|
|
285
|
-
const val = parsePropertyValue(pm[2].trim());
|
|
286
|
-
scenario.overrides[scenarioNodeId][key] = val;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
262
|
+
if (sIndent === 0) break;
|
|
289
263
|
si++;
|
|
290
264
|
}
|
|
291
|
-
i = si - 1;
|
|
292
|
-
result.scenarios.push(scenario);
|
|
265
|
+
i = si - 1;
|
|
293
266
|
continue;
|
|
294
267
|
}
|
|
295
268
|
|