@diagrammo/dgmo 0.6.2 → 0.6.3
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/.claude/commands/dgmo.md +231 -13
- package/AGENTS.md +148 -0
- package/dist/cli.cjs +327 -153
- package/dist/index.cjs +305 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +303 -177
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/c4/layout.ts +0 -5
- package/src/c4/parser.ts +0 -16
- package/src/c4/renderer.ts +1 -5
- package/src/class/layout.ts +0 -1
- package/src/class/parser.ts +28 -0
- package/src/class/renderer.ts +5 -26
- package/src/cli.ts +563 -14
- package/src/completion.ts +58 -0
- package/src/d3.ts +58 -106
- package/src/dgmo-router.ts +0 -57
- package/src/echarts.ts +96 -55
- package/src/er/parser.ts +30 -1
- package/src/er/renderer.ts +1 -2
- package/src/graph/flowchart-parser.ts +27 -4
- package/src/graph/flowchart-renderer.ts +1 -2
- package/src/graph/state-parser.ts +0 -1
- package/src/graph/state-renderer.ts +1 -3
- package/src/index.ts +10 -0
- package/src/infra/compute.ts +0 -7
- package/src/infra/layout.ts +0 -2
- package/src/infra/parser.ts +46 -4
- package/src/infra/renderer.ts +1 -15
- package/src/initiative-status/renderer.ts +5 -25
- package/src/kanban/parser.ts +0 -2
- package/src/org/layout.ts +0 -4
- package/src/org/renderer.ts +7 -28
- package/src/sequence/parser.ts +14 -11
- package/src/sequence/renderer.ts +0 -2
- package/src/sequence/tag-resolution.ts +0 -1
- package/src/sitemap/layout.ts +1 -14
- package/src/sitemap/parser.ts +1 -2
- package/src/sitemap/renderer.ts +0 -3
- package/src/utils/arrows.ts +7 -7
- package/src/utils/export-container.ts +40 -0
package/src/echarts.ts
CHANGED
|
@@ -86,6 +86,7 @@ export interface ParsedExtendedChart {
|
|
|
86
86
|
|
|
87
87
|
import type { PaletteColors } from './palettes';
|
|
88
88
|
import { getSeriesColors, getSegmentColors } from './palettes';
|
|
89
|
+
import { mix } from './palettes/color-utils';
|
|
89
90
|
import { parseChart } from './chart';
|
|
90
91
|
import type { ParsedChart, ChartEra } from './chart';
|
|
91
92
|
import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
|
|
@@ -98,6 +99,7 @@ import { collectIndentedValues, extractColor, measureIndent, parseSeriesNames }
|
|
|
98
99
|
|
|
99
100
|
const EMPHASIS_SELF = { focus: 'self' as const, blurScope: 'global' as const };
|
|
100
101
|
const CHART_BASE: Pick<EChartsOption, 'backgroundColor' | 'animation'> = { backgroundColor: 'transparent', animation: false };
|
|
102
|
+
const CHART_BORDER_WIDTH = 2;
|
|
101
103
|
|
|
102
104
|
// ============================================================
|
|
103
105
|
// Parser
|
|
@@ -487,10 +489,12 @@ export function buildExtendedChartOption(
|
|
|
487
489
|
|
|
488
490
|
// Chord diagram
|
|
489
491
|
if (parsed.type === 'chord') {
|
|
492
|
+
const bg = isDark ? palette.surface : palette.bg;
|
|
490
493
|
return buildChordOption(
|
|
491
494
|
parsed,
|
|
492
495
|
textColor,
|
|
493
496
|
colors,
|
|
497
|
+
bg,
|
|
494
498
|
titleConfig,
|
|
495
499
|
tooltipTheme
|
|
496
500
|
);
|
|
@@ -512,6 +516,7 @@ export function buildExtendedChartOption(
|
|
|
512
516
|
|
|
513
517
|
// Scatter plot
|
|
514
518
|
if (parsed.type === 'scatter') {
|
|
519
|
+
const bg = isDark ? palette.surface : palette.bg;
|
|
515
520
|
return buildScatterOption(
|
|
516
521
|
parsed,
|
|
517
522
|
palette,
|
|
@@ -519,6 +524,7 @@ export function buildExtendedChartOption(
|
|
|
519
524
|
axisLineColor,
|
|
520
525
|
gridOpacity,
|
|
521
526
|
colors,
|
|
527
|
+
bg,
|
|
522
528
|
titleConfig,
|
|
523
529
|
tooltipTheme
|
|
524
530
|
);
|
|
@@ -526,10 +532,12 @@ export function buildExtendedChartOption(
|
|
|
526
532
|
|
|
527
533
|
// Funnel chart
|
|
528
534
|
if (parsed.type === 'funnel') {
|
|
535
|
+
const bg = isDark ? palette.surface : palette.bg;
|
|
529
536
|
return buildFunnelOption(
|
|
530
537
|
parsed,
|
|
531
538
|
textColor,
|
|
532
539
|
colors,
|
|
540
|
+
bg,
|
|
533
541
|
titleConfig,
|
|
534
542
|
tooltipTheme
|
|
535
543
|
);
|
|
@@ -539,6 +547,7 @@ export function buildExtendedChartOption(
|
|
|
539
547
|
return buildHeatmapOption(
|
|
540
548
|
parsed,
|
|
541
549
|
palette,
|
|
550
|
+
isDark,
|
|
542
551
|
textColor,
|
|
543
552
|
axisLineColor,
|
|
544
553
|
titleConfig,
|
|
@@ -616,6 +625,7 @@ function buildChordOption(
|
|
|
616
625
|
parsed: ParsedExtendedChart,
|
|
617
626
|
textColor: string,
|
|
618
627
|
colors: string[],
|
|
628
|
+
bg: string,
|
|
619
629
|
titleConfig: EChartsOption['title'],
|
|
620
630
|
tooltipTheme: Record<string, unknown>
|
|
621
631
|
): EChartsOption {
|
|
@@ -647,12 +657,13 @@ function buildChordOption(
|
|
|
647
657
|
}
|
|
648
658
|
|
|
649
659
|
// Create category data for nodes with colors
|
|
650
|
-
const categories = nodeNames.map((name, index) =>
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
660
|
+
const categories = nodeNames.map((name, index) => {
|
|
661
|
+
const stroke = colors[index % colors.length];
|
|
662
|
+
return {
|
|
663
|
+
name,
|
|
664
|
+
itemStyle: { color: mix(stroke, bg, 30), borderColor: stroke, borderWidth: CHART_BORDER_WIDTH },
|
|
665
|
+
};
|
|
666
|
+
});
|
|
656
667
|
|
|
657
668
|
return {
|
|
658
669
|
...CHART_BASE,
|
|
@@ -876,6 +887,7 @@ function buildScatterOption(
|
|
|
876
887
|
axisLineColor: string,
|
|
877
888
|
gridOpacity: number,
|
|
878
889
|
colors: string[],
|
|
890
|
+
bg: string,
|
|
879
891
|
titleConfig: EChartsOption['title'],
|
|
880
892
|
tooltipTheme: Record<string, unknown>
|
|
881
893
|
): EChartsOption {
|
|
@@ -919,7 +931,9 @@ function buildScatterOption(
|
|
|
919
931
|
const data = categoryPoints.map((p) => ({
|
|
920
932
|
name: p.name,
|
|
921
933
|
value: hasSize ? [p.x, p.y, p.size ?? 0] : [p.x, p.y],
|
|
922
|
-
...(p.color && {
|
|
934
|
+
...(p.color && {
|
|
935
|
+
itemStyle: { color: mix(p.color, bg, 30), borderColor: p.color, borderWidth: CHART_BORDER_WIDTH },
|
|
936
|
+
}),
|
|
923
937
|
}));
|
|
924
938
|
|
|
925
939
|
return {
|
|
@@ -929,23 +943,24 @@ function buildScatterOption(
|
|
|
929
943
|
...(hasSize
|
|
930
944
|
? { symbolSize: (val: number[]) => val[2] }
|
|
931
945
|
: { symbolSize: defaultSize }),
|
|
932
|
-
itemStyle: { color: catColor },
|
|
946
|
+
itemStyle: { color: mix(catColor, bg, 30), borderColor: catColor, borderWidth: CHART_BORDER_WIDTH },
|
|
933
947
|
label: labelConfig,
|
|
934
948
|
emphasis: emphasisConfig,
|
|
935
949
|
};
|
|
936
950
|
});
|
|
937
951
|
} else {
|
|
938
952
|
// Single series — per-point colors
|
|
939
|
-
const data = points.map((p, index) =>
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
?
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
953
|
+
const data = points.map((p, index) => {
|
|
954
|
+
const stroke = p.color ?? colors[index % colors.length];
|
|
955
|
+
return {
|
|
956
|
+
name: p.name,
|
|
957
|
+
value: hasSize ? [p.x, p.y, p.size ?? 0] : [p.x, p.y],
|
|
958
|
+
...(hasSize
|
|
959
|
+
? { symbolSize: p.size ?? defaultSize }
|
|
960
|
+
: { symbolSize: defaultSize }),
|
|
961
|
+
itemStyle: { color: mix(stroke, bg, 30), borderColor: stroke, borderWidth: CHART_BORDER_WIDTH },
|
|
962
|
+
};
|
|
963
|
+
});
|
|
949
964
|
|
|
950
965
|
series = [
|
|
951
966
|
{
|
|
@@ -1065,11 +1080,13 @@ function buildScatterOption(
|
|
|
1065
1080
|
function buildHeatmapOption(
|
|
1066
1081
|
parsed: ParsedExtendedChart,
|
|
1067
1082
|
palette: PaletteColors,
|
|
1083
|
+
isDark: boolean,
|
|
1068
1084
|
textColor: string,
|
|
1069
1085
|
axisLineColor: string,
|
|
1070
1086
|
titleConfig: EChartsOption['title'],
|
|
1071
1087
|
tooltipTheme: Record<string, unknown>
|
|
1072
1088
|
): EChartsOption {
|
|
1089
|
+
const bg = isDark ? palette.surface : palette.bg;
|
|
1073
1090
|
const heatmapRows = parsed.heatmapRows ?? [];
|
|
1074
1091
|
const columns = parsed.columns ?? [];
|
|
1075
1092
|
const rowLabels = heatmapRows.map((r) => r.label);
|
|
@@ -1144,10 +1161,10 @@ function buildHeatmapOption(
|
|
|
1144
1161
|
top: 'center',
|
|
1145
1162
|
inRange: {
|
|
1146
1163
|
color: [
|
|
1147
|
-
palette.primary,
|
|
1148
|
-
palette.colors.cyan,
|
|
1149
|
-
palette.colors.yellow,
|
|
1150
|
-
palette.colors.orange,
|
|
1164
|
+
mix(palette.primary, bg, 30),
|
|
1165
|
+
mix(palette.colors.cyan, bg, 30),
|
|
1166
|
+
mix(palette.colors.yellow, bg, 30),
|
|
1167
|
+
mix(palette.colors.orange, bg, 30),
|
|
1151
1168
|
],
|
|
1152
1169
|
},
|
|
1153
1170
|
textStyle: {
|
|
@@ -1158,9 +1175,13 @@ function buildHeatmapOption(
|
|
|
1158
1175
|
{
|
|
1159
1176
|
type: 'heatmap',
|
|
1160
1177
|
data,
|
|
1178
|
+
itemStyle: {
|
|
1179
|
+
borderWidth: 2,
|
|
1180
|
+
borderColor: bg,
|
|
1181
|
+
},
|
|
1161
1182
|
label: {
|
|
1162
1183
|
show: true,
|
|
1163
|
-
color:
|
|
1184
|
+
color: textColor,
|
|
1164
1185
|
fontSize: 14,
|
|
1165
1186
|
fontWeight: 'bold' as const,
|
|
1166
1187
|
},
|
|
@@ -1183,6 +1204,7 @@ function buildFunnelOption(
|
|
|
1183
1204
|
parsed: ParsedExtendedChart,
|
|
1184
1205
|
textColor: string,
|
|
1185
1206
|
colors: string[],
|
|
1207
|
+
bg: string,
|
|
1186
1208
|
titleConfig: EChartsOption['title'],
|
|
1187
1209
|
tooltipTheme: Record<string, unknown>
|
|
1188
1210
|
): EChartsOption {
|
|
@@ -1190,14 +1212,18 @@ function buildFunnelOption(
|
|
|
1190
1212
|
const sorted = [...parsed.data].sort((a, b) => b.value - a.value);
|
|
1191
1213
|
const topValue = sorted.length > 0 ? sorted[0].value : 1;
|
|
1192
1214
|
|
|
1193
|
-
const data = sorted.map((d) =>
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1215
|
+
const data = sorted.map((d) => {
|
|
1216
|
+
const stroke = d.color ?? colors[parsed.data.indexOf(d) % colors.length];
|
|
1217
|
+
return {
|
|
1218
|
+
name: d.label,
|
|
1219
|
+
value: d.value,
|
|
1220
|
+
itemStyle: {
|
|
1221
|
+
color: mix(stroke, bg, 30),
|
|
1222
|
+
borderColor: stroke,
|
|
1223
|
+
borderWidth: CHART_BORDER_WIDTH,
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
});
|
|
1201
1227
|
|
|
1202
1228
|
// Build lookup for tooltip: previous step value (in sorted order)
|
|
1203
1229
|
const prevValueMap = new Map<string, number>();
|
|
@@ -1394,12 +1420,13 @@ export function buildSimpleChartOption(
|
|
|
1394
1420
|
if (parsed.error) return {};
|
|
1395
1421
|
|
|
1396
1422
|
const { textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme } = buildChartCommons(parsed, palette, isDark);
|
|
1423
|
+
const bg = isDark ? palette.surface : palette.bg;
|
|
1397
1424
|
|
|
1398
1425
|
switch (parsed.type) {
|
|
1399
1426
|
case 'bar':
|
|
1400
|
-
return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
|
|
1427
|
+
return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth);
|
|
1401
1428
|
case 'bar-stacked':
|
|
1402
|
-
return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
|
|
1429
|
+
return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth);
|
|
1403
1430
|
case 'line':
|
|
1404
1431
|
return parsed.seriesNames
|
|
1405
1432
|
? buildMultiLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth)
|
|
@@ -1407,13 +1434,13 @@ export function buildSimpleChartOption(
|
|
|
1407
1434
|
case 'area':
|
|
1408
1435
|
return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
|
|
1409
1436
|
case 'pie':
|
|
1410
|
-
return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, false);
|
|
1437
|
+
return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme, false);
|
|
1411
1438
|
case 'doughnut':
|
|
1412
|
-
return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, true);
|
|
1439
|
+
return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme, true);
|
|
1413
1440
|
case 'radar':
|
|
1414
|
-
return buildRadarOption(parsed, palette, textColor, gridOpacity,
|
|
1441
|
+
return buildRadarOption(parsed, palette, isDark, textColor, gridOpacity, titleConfig, tooltipTheme);
|
|
1415
1442
|
case 'polar-area':
|
|
1416
|
-
return buildPolarAreaOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme);
|
|
1443
|
+
return buildPolarAreaOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme);
|
|
1417
1444
|
}
|
|
1418
1445
|
}
|
|
1419
1446
|
|
|
@@ -1439,6 +1466,7 @@ function buildBarOption(
|
|
|
1439
1466
|
splitLineColor: string,
|
|
1440
1467
|
gridOpacity: number,
|
|
1441
1468
|
colors: string[],
|
|
1469
|
+
bg: string,
|
|
1442
1470
|
titleConfig: EChartsOption['title'],
|
|
1443
1471
|
tooltipTheme: Record<string, unknown>,
|
|
1444
1472
|
chartWidth?: number
|
|
@@ -1446,10 +1474,13 @@ function buildBarOption(
|
|
|
1446
1474
|
const { xLabel, yLabel } = resolveAxisLabels(parsed);
|
|
1447
1475
|
const isHorizontal = parsed.orientation === 'horizontal';
|
|
1448
1476
|
const labels = parsed.data.map((d) => d.label);
|
|
1449
|
-
const data = parsed.data.map((d, i) =>
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1477
|
+
const data = parsed.data.map((d, i) => {
|
|
1478
|
+
const stroke = d.color ?? colors[i % colors.length];
|
|
1479
|
+
return {
|
|
1480
|
+
value: d.value,
|
|
1481
|
+
itemStyle: { color: mix(stroke, bg, 30), borderColor: stroke, borderWidth: CHART_BORDER_WIDTH },
|
|
1482
|
+
};
|
|
1483
|
+
});
|
|
1453
1484
|
|
|
1454
1485
|
// When category labels are on the y-axis (horizontal bars), they can be wide —
|
|
1455
1486
|
// compute a nameGap that clears the longest label so the ylabel doesn't overlap.
|
|
@@ -1701,15 +1732,19 @@ function buildPieOption(
|
|
|
1701
1732
|
parsed: ParsedChart,
|
|
1702
1733
|
textColor: string,
|
|
1703
1734
|
colors: string[],
|
|
1735
|
+
bg: string,
|
|
1704
1736
|
titleConfig: EChartsOption['title'],
|
|
1705
1737
|
tooltipTheme: Record<string, unknown>,
|
|
1706
1738
|
isDoughnut: boolean
|
|
1707
1739
|
): EChartsOption {
|
|
1708
|
-
const data = parsed.data.map((d, i) =>
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1740
|
+
const data = parsed.data.map((d, i) => {
|
|
1741
|
+
const stroke = d.color ?? colors[i % colors.length];
|
|
1742
|
+
return {
|
|
1743
|
+
name: d.label,
|
|
1744
|
+
value: d.value,
|
|
1745
|
+
itemStyle: { color: mix(stroke, bg, 30), borderColor: stroke, borderWidth: CHART_BORDER_WIDTH },
|
|
1746
|
+
};
|
|
1747
|
+
});
|
|
1713
1748
|
|
|
1714
1749
|
return {
|
|
1715
1750
|
...CHART_BASE,
|
|
@@ -1741,12 +1776,13 @@ function buildPieOption(
|
|
|
1741
1776
|
function buildRadarOption(
|
|
1742
1777
|
parsed: ParsedChart,
|
|
1743
1778
|
palette: PaletteColors,
|
|
1779
|
+
isDark: boolean,
|
|
1744
1780
|
textColor: string,
|
|
1745
1781
|
gridOpacity: number,
|
|
1746
|
-
colors: string[],
|
|
1747
1782
|
titleConfig: EChartsOption['title'],
|
|
1748
1783
|
tooltipTheme: Record<string, unknown>
|
|
1749
1784
|
): EChartsOption {
|
|
1785
|
+
const bg = isDark ? palette.surface : palette.bg;
|
|
1750
1786
|
const radarColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
|
|
1751
1787
|
const values = parsed.data.map((d) => d.value);
|
|
1752
1788
|
const maxValue = Math.max(...values) * 1.15;
|
|
@@ -1785,7 +1821,7 @@ function buildRadarOption(
|
|
|
1785
1821
|
{
|
|
1786
1822
|
value: values,
|
|
1787
1823
|
name: parsed.series ?? 'Value',
|
|
1788
|
-
areaStyle: { color: radarColor,
|
|
1824
|
+
areaStyle: { color: mix(radarColor, bg, 30) },
|
|
1789
1825
|
lineStyle: { color: radarColor },
|
|
1790
1826
|
itemStyle: { color: radarColor },
|
|
1791
1827
|
symbol: 'circle',
|
|
@@ -1811,14 +1847,18 @@ function buildPolarAreaOption(
|
|
|
1811
1847
|
parsed: ParsedChart,
|
|
1812
1848
|
textColor: string,
|
|
1813
1849
|
colors: string[],
|
|
1850
|
+
bg: string,
|
|
1814
1851
|
titleConfig: EChartsOption['title'],
|
|
1815
1852
|
tooltipTheme: Record<string, unknown>
|
|
1816
1853
|
): EChartsOption {
|
|
1817
|
-
const data = parsed.data.map((d, i) =>
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1854
|
+
const data = parsed.data.map((d, i) => {
|
|
1855
|
+
const stroke = d.color ?? colors[i % colors.length];
|
|
1856
|
+
return {
|
|
1857
|
+
name: d.label,
|
|
1858
|
+
value: d.value,
|
|
1859
|
+
itemStyle: { color: mix(stroke, bg, 30), borderColor: stroke, borderWidth: CHART_BORDER_WIDTH },
|
|
1860
|
+
};
|
|
1861
|
+
});
|
|
1822
1862
|
|
|
1823
1863
|
return {
|
|
1824
1864
|
...CHART_BASE,
|
|
@@ -1855,6 +1895,7 @@ function buildBarStackedOption(
|
|
|
1855
1895
|
splitLineColor: string,
|
|
1856
1896
|
gridOpacity: number,
|
|
1857
1897
|
colors: string[],
|
|
1898
|
+
bg: string,
|
|
1858
1899
|
titleConfig: EChartsOption['title'],
|
|
1859
1900
|
tooltipTheme: Record<string, unknown>,
|
|
1860
1901
|
chartWidth?: number
|
|
@@ -1874,12 +1915,12 @@ function buildBarStackedOption(
|
|
|
1874
1915
|
type: 'bar' as const,
|
|
1875
1916
|
stack: 'total',
|
|
1876
1917
|
data,
|
|
1877
|
-
itemStyle: { color },
|
|
1918
|
+
itemStyle: { color: mix(color, bg, 30), borderColor: color, borderWidth: CHART_BORDER_WIDTH },
|
|
1878
1919
|
label: {
|
|
1879
1920
|
show: true,
|
|
1880
1921
|
position: 'inside' as const,
|
|
1881
1922
|
formatter: '{c}',
|
|
1882
|
-
color:
|
|
1923
|
+
color: textColor,
|
|
1883
1924
|
fontSize: 14,
|
|
1884
1925
|
fontWeight: 'bold' as const,
|
|
1885
1926
|
fontFamily: FONT_FAMILY,
|
package/src/er/parser.ts
CHANGED
|
@@ -7,7 +7,6 @@ import type { TagGroup } from '../utils/tag-groups';
|
|
|
7
7
|
import type {
|
|
8
8
|
ParsedERDiagram,
|
|
9
9
|
ERTable,
|
|
10
|
-
ERColumn,
|
|
11
10
|
ERConstraint,
|
|
12
11
|
ERCardinality,
|
|
13
12
|
} from './types';
|
|
@@ -463,3 +462,33 @@ export function looksLikeERDiagram(content: string): boolean {
|
|
|
463
462
|
|
|
464
463
|
return false;
|
|
465
464
|
}
|
|
465
|
+
|
|
466
|
+
// ============================================================
|
|
467
|
+
// Symbol extraction (for completion API)
|
|
468
|
+
// ============================================================
|
|
469
|
+
|
|
470
|
+
import type { DiagramSymbols } from '../completion';
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Extract table names (entities) and ER keywords from document text.
|
|
474
|
+
* Used by the dgmo completion API for ghost hints and popup completions.
|
|
475
|
+
*/
|
|
476
|
+
export function extractSymbols(docText: string): DiagramSymbols {
|
|
477
|
+
const entities: string[] = [];
|
|
478
|
+
let inMetadata = true;
|
|
479
|
+
for (const rawLine of docText.split('\n')) {
|
|
480
|
+
const line = rawLine.trim();
|
|
481
|
+
if (inMetadata && /^chart\s*:/i.test(line)) continue;
|
|
482
|
+
if (inMetadata && /^[a-z-]+\s*:/i.test(line)) continue; // metadata key
|
|
483
|
+
inMetadata = false;
|
|
484
|
+
if (line.length === 0) continue;
|
|
485
|
+
if (/^\s/.test(rawLine)) continue; // indented = column definition, not table
|
|
486
|
+
const m = TABLE_DECL_RE.exec(line);
|
|
487
|
+
if (m) entities.push(m[1]!);
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
kind: 'er',
|
|
491
|
+
entities,
|
|
492
|
+
keywords: ['pk', 'fk', 'unique', 'nullable', '1', '*', '?'],
|
|
493
|
+
};
|
|
494
|
+
}
|
package/src/er/renderer.ts
CHANGED
|
@@ -17,13 +17,12 @@ import {
|
|
|
17
17
|
LEGEND_CAPSULE_PAD,
|
|
18
18
|
LEGEND_DOT_R,
|
|
19
19
|
LEGEND_ENTRY_FONT_SIZE,
|
|
20
|
-
LEGEND_ENTRY_FONT_W,
|
|
21
20
|
LEGEND_ENTRY_DOT_GAP,
|
|
22
21
|
LEGEND_ENTRY_TRAIL,
|
|
23
22
|
LEGEND_GROUP_GAP,
|
|
24
23
|
} from '../utils/legend-constants';
|
|
25
24
|
import type { ParsedERDiagram, ERConstraint } from './types';
|
|
26
|
-
import type { ERLayoutResult
|
|
25
|
+
import type { ERLayoutResult } from './layout';
|
|
27
26
|
import { parseERDiagram } from './parser';
|
|
28
27
|
import { layoutERDiagram } from './layout';
|
|
29
28
|
import { classifyEREntities, ROLE_COLORS, ROLE_LABELS, ROLE_ORDER } from './classify';
|
|
@@ -90,10 +90,6 @@ function parseNodeRef(
|
|
|
90
90
|
*/
|
|
91
91
|
function splitArrows(line: string): string[] {
|
|
92
92
|
const segments: string[] = [];
|
|
93
|
-
// Match: optional `-label(color)->` or just `->`
|
|
94
|
-
// We scan left to right looking for `->` and work backwards to find the `-` start.
|
|
95
|
-
const arrowRe = /(?:^|\s)-([^>\s(][^(>]*?)?\s*(?:\(([^)]+)\))?\s*->|(?:^|\s)->/g;
|
|
96
|
-
|
|
97
93
|
let lastIndex = 0;
|
|
98
94
|
// Simpler approach: find all `->` positions, then determine if there's a label prefix
|
|
99
95
|
const arrowPositions: { start: number; end: number; label?: string; color?: string }[] = [];
|
|
@@ -482,3 +478,30 @@ export function looksLikeFlowchart(content: string): boolean {
|
|
|
482
478
|
|
|
483
479
|
return shapeNearArrow;
|
|
484
480
|
}
|
|
481
|
+
|
|
482
|
+
// ============================================================
|
|
483
|
+
// Symbol extraction (for completion API)
|
|
484
|
+
// ============================================================
|
|
485
|
+
|
|
486
|
+
import type { DiagramSymbols } from '../completion';
|
|
487
|
+
|
|
488
|
+
// Node ID: identifier at line start followed by a shape delimiter or space (arrow line)
|
|
489
|
+
const NODE_ID_RE = /^([a-zA-Z_][\w-]*)[\s([</{]/;
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Extract node IDs (entities) from flowchart document text.
|
|
493
|
+
* Used by the dgmo completion API for ghost hints and popup completions.
|
|
494
|
+
*/
|
|
495
|
+
export function extractSymbols(docText: string): DiagramSymbols {
|
|
496
|
+
const entities: string[] = [];
|
|
497
|
+
let inMetadata = true;
|
|
498
|
+
for (const rawLine of docText.split('\n')) {
|
|
499
|
+
const line = rawLine.trim();
|
|
500
|
+
if (inMetadata && /^[a-z-]+\s*:/i.test(line)) continue;
|
|
501
|
+
inMetadata = false;
|
|
502
|
+
if (line.length === 0 || /^\s/.test(rawLine)) continue;
|
|
503
|
+
const m = NODE_ID_RE.exec(line);
|
|
504
|
+
if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
|
|
505
|
+
}
|
|
506
|
+
return { kind: 'flowchart', entities, keywords: [] };
|
|
507
|
+
}
|
|
@@ -8,7 +8,7 @@ import { FONT_FAMILY } from '../fonts';
|
|
|
8
8
|
import type { PaletteColors } from '../palettes';
|
|
9
9
|
import { mix } from '../palettes/color-utils';
|
|
10
10
|
import type { ParsedGraph, GraphShape } from './types';
|
|
11
|
-
import type { LayoutResult, LayoutNode
|
|
11
|
+
import type { LayoutResult, LayoutNode } from './layout';
|
|
12
12
|
import { parseFlowchart } from './flowchart-parser';
|
|
13
13
|
import { layoutGraph } from './layout';
|
|
14
14
|
|
|
@@ -246,7 +246,6 @@ export function renderFlowchart(
|
|
|
246
246
|
|
|
247
247
|
// Center the diagram in the area below the title
|
|
248
248
|
const scaledW = diagramW * scale;
|
|
249
|
-
const scaledH = diagramH * scale;
|
|
250
249
|
const offsetX = (width - scaledW) / 2;
|
|
251
250
|
const offsetY = titleHeight + DIAGRAM_PADDING;
|
|
252
251
|
|
|
@@ -8,7 +8,7 @@ import { FONT_FAMILY } from '../fonts';
|
|
|
8
8
|
import type { PaletteColors } from '../palettes';
|
|
9
9
|
import { mix } from '../palettes/color-utils';
|
|
10
10
|
import type { ParsedGraph } from './types';
|
|
11
|
-
import type { LayoutResult, LayoutNode
|
|
11
|
+
import type { LayoutResult, LayoutNode } from './layout';
|
|
12
12
|
import { parseState } from './state-parser';
|
|
13
13
|
import { layoutGraph } from './layout';
|
|
14
14
|
|
|
@@ -76,8 +76,6 @@ function selfLoopPath(node: LayoutNode): string {
|
|
|
76
76
|
// Main renderer
|
|
77
77
|
// ============================================================
|
|
78
78
|
|
|
79
|
-
type GSelection = d3Selection.Selection<SVGGElement, unknown, null, undefined>;
|
|
80
|
-
|
|
81
79
|
export function renderState(
|
|
82
80
|
container: HTMLDivElement,
|
|
83
81
|
graph: ParsedGraph,
|
package/src/index.ts
CHANGED
|
@@ -361,6 +361,16 @@ export type {
|
|
|
361
361
|
DecodedDiagramUrl,
|
|
362
362
|
} from './sharing';
|
|
363
363
|
|
|
364
|
+
// ============================================================
|
|
365
|
+
// Completion (symbol extraction API)
|
|
366
|
+
// ============================================================
|
|
367
|
+
|
|
368
|
+
export {
|
|
369
|
+
registerExtractor,
|
|
370
|
+
extractDiagramSymbols,
|
|
371
|
+
} from './completion';
|
|
372
|
+
export type { DiagramSymbols, ExtractFn } from './completion';
|
|
373
|
+
|
|
364
374
|
// ============================================================
|
|
365
375
|
// Branding
|
|
366
376
|
// ============================================================
|
package/src/infra/compute.ts
CHANGED
|
@@ -22,8 +22,6 @@ import type {
|
|
|
22
22
|
InfraAvailabilityPercentiles,
|
|
23
23
|
InfraProperty,
|
|
24
24
|
} from './types';
|
|
25
|
-
import { INFRA_BEHAVIOR_KEYS } from './types';
|
|
26
|
-
|
|
27
25
|
// ============================================================
|
|
28
26
|
// Helpers
|
|
29
27
|
// ============================================================
|
|
@@ -71,11 +69,6 @@ function serverlessCapacity(node: InfraNode): number {
|
|
|
71
69
|
return concurrency / (durationMs / 1000);
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
/** Backward-compatible helper used by overload detection. */
|
|
75
|
-
function getInstances(node: InfraNode): number {
|
|
76
|
-
return getInstanceRange(node).min;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
72
|
/** Compute dynamic instance count based on load and max-rps. */
|
|
80
73
|
function computeDynamicInstances(node: InfraNode, computedRps: number): number {
|
|
81
74
|
const { min, max } = getInstanceRange(node);
|
package/src/infra/layout.ts
CHANGED
package/src/infra/parser.ts
CHANGED
|
@@ -11,11 +11,8 @@ import { measureIndent } from '../utils/parsing';
|
|
|
11
11
|
import type {
|
|
12
12
|
ParsedInfra,
|
|
13
13
|
InfraNode,
|
|
14
|
-
InfraEdge,
|
|
15
14
|
InfraGroup,
|
|
16
15
|
InfraTagGroup,
|
|
17
|
-
InfraTagValue,
|
|
18
|
-
InfraProperty,
|
|
19
16
|
} from './types';
|
|
20
17
|
import { INFRA_BEHAVIOR_KEYS, EDGE_ONLY_KEYS } from './types';
|
|
21
18
|
|
|
@@ -116,7 +113,6 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
116
113
|
};
|
|
117
114
|
|
|
118
115
|
const nodeMap = new Map<string, InfraNode>();
|
|
119
|
-
const edgeNodeId = 'edge';
|
|
120
116
|
|
|
121
117
|
const setError = (line: number, message: string) => {
|
|
122
118
|
const diag = makeDgmoError(line, message);
|
|
@@ -573,3 +569,49 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
573
569
|
|
|
574
570
|
return result;
|
|
575
571
|
}
|
|
572
|
+
|
|
573
|
+
// ============================================================
|
|
574
|
+
// Symbol extraction (for completion API)
|
|
575
|
+
// ============================================================
|
|
576
|
+
|
|
577
|
+
import type { DiagramSymbols } from '../completion';
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Extract component names (entities) from infra document text.
|
|
581
|
+
* Used by the dgmo completion API for ghost hints and popup completions.
|
|
582
|
+
*/
|
|
583
|
+
export function extractSymbols(docText: string): DiagramSymbols {
|
|
584
|
+
const entities: string[] = [];
|
|
585
|
+
let inMetadata = true;
|
|
586
|
+
let inTagGroup = false;
|
|
587
|
+
for (const rawLine of docText.split('\n')) {
|
|
588
|
+
const line = rawLine.trim();
|
|
589
|
+
if (line.length === 0) continue;
|
|
590
|
+
const indented = /^\s/.test(rawLine);
|
|
591
|
+
|
|
592
|
+
// Metadata phase: skip until first non-metadata root-level line.
|
|
593
|
+
// All lines (including indented) are skipped while inMetadata = true.
|
|
594
|
+
if (inMetadata) {
|
|
595
|
+
if (!indented && !/^[a-z-]+\s*:/i.test(line)) inMetadata = false;
|
|
596
|
+
else continue;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (!indented) {
|
|
600
|
+
// Root-level: tag group declaration, group header, or component
|
|
601
|
+
if (/^tag\s*:/i.test(line)) { inTagGroup = true; continue; }
|
|
602
|
+
inTagGroup = false;
|
|
603
|
+
if (/^\[/.test(line)) continue; // group header
|
|
604
|
+
const m = COMPONENT_RE.exec(line);
|
|
605
|
+
if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
|
|
606
|
+
} else {
|
|
607
|
+
// Indented: skip tag values, connections, and properties; extract grouped components
|
|
608
|
+
if (inTagGroup) continue;
|
|
609
|
+
if (/^->/.test(line)) continue; // simple connection
|
|
610
|
+
if (/^-[^>]+-?>/.test(line)) continue; // labeled connection
|
|
611
|
+
if (/^\w[\w-]*\s*:/.test(line)) continue; // property (key: value)
|
|
612
|
+
const m = COMPONENT_RE.exec(line);
|
|
613
|
+
if (m && !entities.includes(m[1]!)) entities.push(m[1]!);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return { kind: 'infra', entities, keywords: [] };
|
|
617
|
+
}
|