@diagrammo/dgmo 0.47.0 → 0.48.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/advanced.js +11 -11
- package/dist/auto.js +16 -16
- package/dist/auto.mjs +41 -12
- package/dist/block.js +4 -4
- package/dist/{chunk-FNB3PNGF.js → chunk-7DTWEFUE.js} +3 -3
- package/dist/{chunk-2DXMZRWJ.js → chunk-B65G5DNL.js} +8 -1
- package/dist/{chunk-RZCF2VVM.js → chunk-B7JUCQUA.js} +3 -1
- package/dist/{chunk-DU7W62N5.js → chunk-FANX6UJR.js} +3 -3
- package/dist/{chunk-6FZYCKXB.js → chunk-HSH73DP6.js} +2 -2
- package/dist/{chunk-P4RH4OSP.js → chunk-ILKORZ3H.js} +2 -2
- package/dist/{chunk-Y5QGVTVC.js → chunk-JTSMEYIZ.js} +7 -3
- package/dist/{chunk-RCERWOJR.js → chunk-ODZVHU3R.js} +1 -2
- package/dist/{chunk-DZAQBW35.js → chunk-SGZYXELT.js} +3 -7
- package/dist/{chunk-CUVHQHZ7.js → chunk-THXCFRTU.js} +3 -3
- package/dist/{chunk-IH6ZMVQ5.js → chunk-UC7TFHUM.js} +29 -11
- package/dist/{chunk-EMS2SK26.js → chunk-ZBWMSD5N.js} +3 -1
- package/dist/cli.cjs +15 -15
- package/dist/{dgmo-router-DZCIDUO5.js → dgmo-router-X35VLZLM.js} +3 -3
- package/dist/element.js +16 -16
- package/dist/element.mjs +41 -12
- package/dist/{flowchart-renderer-ZAEOL4DX.js → flowchart-renderer-SNDSII4D.js} +2 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -4
- package/dist/{parser-FUSKFGZN.js → parser-2EWECDSA.js} +2 -2
- package/dist/{renderer-UWA5XS74.js → renderer-4JHRUBE6.js} +3 -3
- package/dist/{renderer-WZRREQ2P.js → renderer-5MYBMBYO.js} +2 -2
- package/dist/{renderer-TOCOVLHS.js → renderer-B2OMQIQG.js} +3 -3
- package/dist/{renderer-2XQQQ6QR.js → renderer-N43WR5GV.js} +3 -3
- package/dist/{renderer-T7YKC5VP.js → renderer-ZRNG54CW.js} +3 -3
- package/dist/{state-renderer-RFYHSP4K.js → state-renderer-B4DQBM6M.js} +2 -2
- package/package.json +23 -71
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -5719
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.cjs +0 -526
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -160
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/dist/themes-1-CuKpeH.d.cts +0 -908
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/parser.ts +0 -427
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/layout.ts +0 -2441
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/calculator.ts +0 -979
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/time-ticks.ts +0 -253
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
package/src/er/layout.ts
DELETED
|
@@ -1,530 +0,0 @@
|
|
|
1
|
-
import dagre from '@dagrejs/dagre';
|
|
2
|
-
import { measureText } from '../utils/text-measure';
|
|
3
|
-
import {
|
|
4
|
-
resolveNotes,
|
|
5
|
-
buildPlacedNotes,
|
|
6
|
-
noteCanvasShift,
|
|
7
|
-
type PlacedNote,
|
|
8
|
-
} from '../utils/notes';
|
|
9
|
-
import type { ParsedERDiagram, ERTable, ERRelationship } from './types';
|
|
10
|
-
|
|
11
|
-
// ============================================================
|
|
12
|
-
// Layout types
|
|
13
|
-
// ============================================================
|
|
14
|
-
|
|
15
|
-
export interface ERLayoutNode extends ERTable {
|
|
16
|
-
readonly x: number;
|
|
17
|
-
readonly y: number;
|
|
18
|
-
readonly width: number;
|
|
19
|
-
readonly height: number;
|
|
20
|
-
readonly headerHeight: number;
|
|
21
|
-
readonly columnsHeight: number;
|
|
22
|
-
/** A note floated beside this table (never moves the box). */
|
|
23
|
-
readonly note?: PlacedNote;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface ERLayoutOptions {
|
|
27
|
-
/** 1-based source lines of notes the user collapsed (corner badge). */
|
|
28
|
-
collapsedNotes?: ReadonlySet<number>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ERLayoutEdge {
|
|
32
|
-
readonly source: string;
|
|
33
|
-
readonly target: string;
|
|
34
|
-
readonly cardinality: { readonly from: string; readonly to: string };
|
|
35
|
-
readonly points: ReadonlyArray<{ readonly x: number; readonly y: number }>;
|
|
36
|
-
readonly label?: string;
|
|
37
|
-
readonly lineNumber: number;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface ERLayoutResult {
|
|
41
|
-
readonly nodes: readonly ERLayoutNode[];
|
|
42
|
-
readonly edges: readonly ERLayoutEdge[];
|
|
43
|
-
readonly width: number;
|
|
44
|
-
readonly height: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ============================================================
|
|
48
|
-
// Sizing constants
|
|
49
|
-
// ============================================================
|
|
50
|
-
|
|
51
|
-
const MIN_WIDTH = 140;
|
|
52
|
-
const PADDING_X = 24;
|
|
53
|
-
const HEADER_BASE = 36;
|
|
54
|
-
// Font sizes mirror the renderer: table name at TABLE_FONT_SIZE, columns at
|
|
55
|
-
// COLUMN_FONT_SIZE. Keep these in sync with renderer.ts.
|
|
56
|
-
const TABLE_FONT_SIZE = 13;
|
|
57
|
-
const COLUMN_FONT_SIZE = 11;
|
|
58
|
-
const EDGE_LABEL_FONT_SIZE = 11;
|
|
59
|
-
// Renderer offsets column text by 16px when a constraint icon is present.
|
|
60
|
-
const CONSTRAINT_ICON_WIDTH = 16;
|
|
61
|
-
const MEMBER_LINE_HEIGHT = 18;
|
|
62
|
-
const COMPARTMENT_PADDING_Y = 8;
|
|
63
|
-
const SEPARATOR_HEIGHT = 1;
|
|
64
|
-
|
|
65
|
-
const HALF_MARGIN = 30;
|
|
66
|
-
const COMP_GAP = 60; // gap between packed components
|
|
67
|
-
|
|
68
|
-
// ============================================================
|
|
69
|
-
// Node sizing
|
|
70
|
-
// ============================================================
|
|
71
|
-
|
|
72
|
-
function computeNodeDimensions(table: ERTable): {
|
|
73
|
-
width: number;
|
|
74
|
-
height: number;
|
|
75
|
-
headerHeight: number;
|
|
76
|
-
columnsHeight: number;
|
|
77
|
-
} {
|
|
78
|
-
// Width: max rendered pixel width of table name and column rows. Measure
|
|
79
|
-
// each at the font size the renderer draws it with; reserve the constraint
|
|
80
|
-
// icon's horizontal offset when a column carries one.
|
|
81
|
-
let maxTextWidth = measureText(table.name, TABLE_FONT_SIZE);
|
|
82
|
-
for (const col of table.columns) {
|
|
83
|
-
let colText = col.name;
|
|
84
|
-
if (col.type) colText += `: ${col.type}`;
|
|
85
|
-
let colWidth = measureText(colText, COLUMN_FONT_SIZE);
|
|
86
|
-
if (col.constraints.length > 0) colWidth += CONSTRAINT_ICON_WIDTH;
|
|
87
|
-
maxTextWidth = Math.max(maxTextWidth, colWidth);
|
|
88
|
-
}
|
|
89
|
-
const width = Math.max(MIN_WIDTH, maxTextWidth + PADDING_X);
|
|
90
|
-
const headerHeight = HEADER_BASE;
|
|
91
|
-
|
|
92
|
-
let columnsHeight = 0;
|
|
93
|
-
if (table.columns.length > 0) {
|
|
94
|
-
columnsHeight =
|
|
95
|
-
COMPARTMENT_PADDING_Y * 2 +
|
|
96
|
-
table.columns.length * MEMBER_LINE_HEIGHT +
|
|
97
|
-
SEPARATOR_HEIGHT;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const height = headerHeight + columnsHeight + (columnsHeight === 0 ? 4 : 0);
|
|
101
|
-
return { width, height, headerHeight, columnsHeight };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ============================================================
|
|
105
|
-
// Connected component detection (undirected BFS)
|
|
106
|
-
// ============================================================
|
|
107
|
-
|
|
108
|
-
function findConnectedComponents(
|
|
109
|
-
tableIds: string[],
|
|
110
|
-
relationships: readonly ERRelationship[]
|
|
111
|
-
): string[][] {
|
|
112
|
-
const adj = new Map<string, Set<string>>();
|
|
113
|
-
for (const id of tableIds) adj.set(id, new Set());
|
|
114
|
-
for (const rel of relationships) {
|
|
115
|
-
adj.get(rel.source)?.add(rel.target);
|
|
116
|
-
adj.get(rel.target)?.add(rel.source);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const visited = new Set<string>();
|
|
120
|
-
const components: string[][] = [];
|
|
121
|
-
|
|
122
|
-
for (const id of tableIds) {
|
|
123
|
-
if (visited.has(id)) continue;
|
|
124
|
-
const comp: string[] = [];
|
|
125
|
-
const queue = [id];
|
|
126
|
-
while (queue.length > 0) {
|
|
127
|
-
const cur = queue.shift()!;
|
|
128
|
-
if (visited.has(cur)) continue;
|
|
129
|
-
visited.add(cur);
|
|
130
|
-
comp.push(cur);
|
|
131
|
-
for (const nb of adj.get(cur) ?? []) {
|
|
132
|
-
if (!visited.has(nb)) queue.push(nb);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
components.push(comp);
|
|
136
|
-
}
|
|
137
|
-
return components;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ============================================================
|
|
141
|
-
// Per-component layout (independent dagre run)
|
|
142
|
-
// ============================================================
|
|
143
|
-
|
|
144
|
-
interface ComponentLayout {
|
|
145
|
-
/** Node center positions relative to this component's top-left origin */
|
|
146
|
-
nodePositions: Map<
|
|
147
|
-
string,
|
|
148
|
-
{
|
|
149
|
-
x: number;
|
|
150
|
-
y: number;
|
|
151
|
-
width: number;
|
|
152
|
-
height: number;
|
|
153
|
-
headerHeight: number;
|
|
154
|
-
columnsHeight: number;
|
|
155
|
-
}
|
|
156
|
-
>;
|
|
157
|
-
/** Edge waypoints keyed by relationship lineNumber, relative to origin */
|
|
158
|
-
edgePoints: Map<number, { x: number; y: number }[]>;
|
|
159
|
-
width: number;
|
|
160
|
-
height: number;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function layoutComponent(
|
|
164
|
-
tables: ERTable[],
|
|
165
|
-
rels: ERRelationship[],
|
|
166
|
-
dimMap: Map<
|
|
167
|
-
string,
|
|
168
|
-
{
|
|
169
|
-
width: number;
|
|
170
|
-
height: number;
|
|
171
|
-
headerHeight: number;
|
|
172
|
-
columnsHeight: number;
|
|
173
|
-
}
|
|
174
|
-
>
|
|
175
|
-
): ComponentLayout {
|
|
176
|
-
const nodePositions = new Map<
|
|
177
|
-
string,
|
|
178
|
-
{
|
|
179
|
-
x: number;
|
|
180
|
-
y: number;
|
|
181
|
-
width: number;
|
|
182
|
-
height: number;
|
|
183
|
-
headerHeight: number;
|
|
184
|
-
columnsHeight: number;
|
|
185
|
-
}
|
|
186
|
-
>();
|
|
187
|
-
const edgePoints = new Map<number, { x: number; y: number }[]>();
|
|
188
|
-
|
|
189
|
-
if (tables.length === 1) {
|
|
190
|
-
// Single node — skip dagre
|
|
191
|
-
// In-bounds by length-1 guard above.
|
|
192
|
-
const onlyTable = tables[0]!;
|
|
193
|
-
const dims = dimMap.get(onlyTable.id)!;
|
|
194
|
-
nodePositions.set(onlyTable.id, {
|
|
195
|
-
x: dims.width / 2,
|
|
196
|
-
y: dims.height / 2,
|
|
197
|
-
...dims,
|
|
198
|
-
});
|
|
199
|
-
return {
|
|
200
|
-
nodePositions,
|
|
201
|
-
edgePoints,
|
|
202
|
-
width: dims.width,
|
|
203
|
-
height: dims.height,
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const g = new dagre.graphlib.Graph({ multigraph: true });
|
|
208
|
-
g.setGraph({ rankdir: 'LR', nodesep: 40, ranksep: 80, edgesep: 20 });
|
|
209
|
-
g.setDefaultEdgeLabel(() => ({}));
|
|
210
|
-
|
|
211
|
-
for (const table of tables) {
|
|
212
|
-
const dims = dimMap.get(table.id)!;
|
|
213
|
-
g.setNode(table.id, { width: dims.width, height: dims.height });
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Use lineNumber as edge name to support multigraph (multiple edges between same pair)
|
|
217
|
-
for (const rel of rels) {
|
|
218
|
-
g.setEdge(
|
|
219
|
-
rel.source,
|
|
220
|
-
rel.target,
|
|
221
|
-
{ label: rel.label ?? '' },
|
|
222
|
-
String(rel.lineNumber)
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
dagre.layout(g);
|
|
227
|
-
|
|
228
|
-
// Compute bounding box (dagre coordinates)
|
|
229
|
-
let minX = Infinity,
|
|
230
|
-
minY = Infinity,
|
|
231
|
-
maxX = -Infinity,
|
|
232
|
-
maxY = -Infinity;
|
|
233
|
-
|
|
234
|
-
for (const table of tables) {
|
|
235
|
-
const pos = g.node(table.id);
|
|
236
|
-
const dims = dimMap.get(table.id)!;
|
|
237
|
-
minX = Math.min(minX, pos.x - dims.width / 2);
|
|
238
|
-
minY = Math.min(minY, pos.y - dims.height / 2);
|
|
239
|
-
maxX = Math.max(maxX, pos.x + dims.width / 2);
|
|
240
|
-
maxY = Math.max(maxY, pos.y + dims.height / 2);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
for (const rel of rels) {
|
|
244
|
-
const ed = g.edge(rel.source, rel.target, String(rel.lineNumber));
|
|
245
|
-
for (const pt of ed?.points ?? []) {
|
|
246
|
-
minX = Math.min(minX, pt.x);
|
|
247
|
-
minY = Math.min(minY, pt.y);
|
|
248
|
-
maxX = Math.max(maxX, pt.x);
|
|
249
|
-
maxY = Math.max(maxY, pt.y);
|
|
250
|
-
}
|
|
251
|
-
if (rel.label && (ed?.points ?? []).length > 0) {
|
|
252
|
-
const pts = ed!.points;
|
|
253
|
-
const mid = pts[Math.floor(pts.length / 2)];
|
|
254
|
-
const hw = (measureText(rel.label, EDGE_LABEL_FONT_SIZE) + 8) / 2;
|
|
255
|
-
minX = Math.min(minX, mid.x - hw);
|
|
256
|
-
maxX = Math.max(maxX, mid.x + hw);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Normalize positions to component origin (0, 0)
|
|
261
|
-
for (const table of tables) {
|
|
262
|
-
const pos = g.node(table.id);
|
|
263
|
-
const dims = dimMap.get(table.id)!;
|
|
264
|
-
nodePositions.set(table.id, {
|
|
265
|
-
x: pos.x - minX,
|
|
266
|
-
y: pos.y - minY,
|
|
267
|
-
...dims,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
for (const rel of rels) {
|
|
271
|
-
const ed = g.edge(rel.source, rel.target, String(rel.lineNumber));
|
|
272
|
-
edgePoints.set(
|
|
273
|
-
rel.lineNumber,
|
|
274
|
-
(ed?.points ?? []).map((pt: { x: number; y: number }) => ({
|
|
275
|
-
x: pt.x - minX,
|
|
276
|
-
y: pt.y - minY,
|
|
277
|
-
}))
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return {
|
|
282
|
-
nodePositions,
|
|
283
|
-
edgePoints,
|
|
284
|
-
width: Math.max(0, maxX - minX),
|
|
285
|
-
height: Math.max(0, maxY - minY),
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// ============================================================
|
|
290
|
-
// Row packing (Next Fit Decreasing Height)
|
|
291
|
-
// ============================================================
|
|
292
|
-
|
|
293
|
-
interface PackedComponent {
|
|
294
|
-
compIds: string[];
|
|
295
|
-
compLayout: ComponentLayout;
|
|
296
|
-
offsetX: number;
|
|
297
|
-
offsetY: number;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function packComponents(
|
|
301
|
-
items: Array<{ compIds: string[]; compLayout: ComponentLayout }>
|
|
302
|
-
): PackedComponent[] {
|
|
303
|
-
if (items.length === 0) return [];
|
|
304
|
-
|
|
305
|
-
// Sort: multi-node components (have relationships, more interesting) first,
|
|
306
|
-
// then isolated single-node components. Within each group, sort by height descending.
|
|
307
|
-
const sorted = [...items].sort((a, b) => {
|
|
308
|
-
const aConnected = a.compIds.length > 1 ? 1 : 0;
|
|
309
|
-
const bConnected = b.compIds.length > 1 ? 1 : 0;
|
|
310
|
-
if (aConnected !== bConnected) return bConnected - aConnected;
|
|
311
|
-
return b.compLayout.height - a.compLayout.height;
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// Target width: sqrt of total content area × aspect factor (~1.5 = slightly landscape)
|
|
315
|
-
const totalArea = items.reduce(
|
|
316
|
-
(s, c) =>
|
|
317
|
-
s +
|
|
318
|
-
(c.compLayout.width || MIN_WIDTH) * (c.compLayout.height || HEADER_BASE),
|
|
319
|
-
0
|
|
320
|
-
);
|
|
321
|
-
const targetW = Math.max(
|
|
322
|
-
Math.sqrt(totalArea) * 1.5,
|
|
323
|
-
// In-bounds: sorted.length === items.length and items.length === 0 returned above.
|
|
324
|
-
sorted[0]!.compLayout.width // at least as wide as the widest component
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
const placements: PackedComponent[] = [];
|
|
328
|
-
let curX = 0;
|
|
329
|
-
let curY = 0;
|
|
330
|
-
let rowH = 0;
|
|
331
|
-
|
|
332
|
-
for (const item of sorted) {
|
|
333
|
-
const w = item.compLayout.width || MIN_WIDTH;
|
|
334
|
-
const h = item.compLayout.height || HEADER_BASE;
|
|
335
|
-
|
|
336
|
-
// Wrap to next row if this item doesn't fit (but always place if row is empty)
|
|
337
|
-
if (curX > 0 && curX + w > targetW) {
|
|
338
|
-
curY += rowH + COMP_GAP;
|
|
339
|
-
curX = 0;
|
|
340
|
-
rowH = 0;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
placements.push({
|
|
344
|
-
compIds: item.compIds,
|
|
345
|
-
compLayout: item.compLayout,
|
|
346
|
-
offsetX: curX,
|
|
347
|
-
offsetY: curY,
|
|
348
|
-
});
|
|
349
|
-
curX += w + COMP_GAP;
|
|
350
|
-
rowH = Math.max(rowH, h);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return placements;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// ============================================================
|
|
357
|
-
// Layout engine
|
|
358
|
-
// ============================================================
|
|
359
|
-
|
|
360
|
-
export function layoutERDiagram(
|
|
361
|
-
parsed: ParsedERDiagram,
|
|
362
|
-
options?: ERLayoutOptions
|
|
363
|
-
): ERLayoutResult {
|
|
364
|
-
if (parsed.tables.length === 0) {
|
|
365
|
-
return { nodes: [], edges: [], width: 0, height: 0 };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// ── 1. Node dimensions ──────────────────────────────────────────────────────
|
|
369
|
-
const dimMap = new Map<
|
|
370
|
-
string,
|
|
371
|
-
{
|
|
372
|
-
width: number;
|
|
373
|
-
height: number;
|
|
374
|
-
headerHeight: number;
|
|
375
|
-
columnsHeight: number;
|
|
376
|
-
}
|
|
377
|
-
>();
|
|
378
|
-
for (const table of parsed.tables) {
|
|
379
|
-
dimMap.set(table.id, computeNodeDimensions(table));
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// ── 2. Find connected components ────────────────────────────────────────────
|
|
383
|
-
const compIdSets = findConnectedComponents(
|
|
384
|
-
parsed.tables.map((t) => t.id),
|
|
385
|
-
parsed.relationships
|
|
386
|
-
);
|
|
387
|
-
|
|
388
|
-
// ── 3. Layout each component independently ──────────────────────────────────
|
|
389
|
-
const tableById = new Map(parsed.tables.map((t) => [t.id, t]));
|
|
390
|
-
|
|
391
|
-
const componentItems = compIdSets.map((ids) => {
|
|
392
|
-
const tables = ids.map((id) => tableById.get(id)!);
|
|
393
|
-
const rels = parsed.relationships.filter((r) => ids.includes(r.source));
|
|
394
|
-
return { compIds: ids, compLayout: layoutComponent(tables, rels, dimMap) };
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
// ── 4. Pack component bounding boxes into rows ───────────────────────────────
|
|
398
|
-
const packed = packComponents(componentItems);
|
|
399
|
-
|
|
400
|
-
// Build lookup: tableId → packed placement
|
|
401
|
-
const placementByTableId = new Map<string, PackedComponent>();
|
|
402
|
-
for (const p of packed) {
|
|
403
|
-
for (const id of p.compIds) placementByTableId.set(id, p);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Build lookup: lineNumber → packed placement (for edges)
|
|
407
|
-
const placementByRelLine = new Map<number, PackedComponent>();
|
|
408
|
-
for (const p of packed) {
|
|
409
|
-
for (const lineNum of p.compLayout.edgePoints.keys()) {
|
|
410
|
-
placementByRelLine.set(lineNum, p);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// ── 5. Assemble final layout ─────────────────────────────────────────────────
|
|
415
|
-
const layoutNodes: ERLayoutNode[] = parsed.tables.map((table) => {
|
|
416
|
-
const p = placementByTableId.get(table.id)!;
|
|
417
|
-
const pos = p.compLayout.nodePositions.get(table.id)!;
|
|
418
|
-
return {
|
|
419
|
-
...table,
|
|
420
|
-
x: pos.x + p.offsetX + HALF_MARGIN,
|
|
421
|
-
y: pos.y + p.offsetY + HALF_MARGIN,
|
|
422
|
-
width: pos.width,
|
|
423
|
-
height: pos.height,
|
|
424
|
-
headerHeight: pos.headerHeight,
|
|
425
|
-
columnsHeight: pos.columnsHeight,
|
|
426
|
-
};
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
const layoutEdges: ERLayoutEdge[] = parsed.relationships.map((rel) => {
|
|
430
|
-
const p = placementByRelLine.get(rel.lineNumber);
|
|
431
|
-
const pts = p?.compLayout.edgePoints.get(rel.lineNumber) ?? [];
|
|
432
|
-
return {
|
|
433
|
-
source: rel.source,
|
|
434
|
-
target: rel.target,
|
|
435
|
-
cardinality: rel.cardinality,
|
|
436
|
-
points: pts.map((pt) => ({
|
|
437
|
-
x: pt.x + (p?.offsetX ?? 0) + HALF_MARGIN,
|
|
438
|
-
y: pt.y + (p?.offsetY ?? 0) + HALF_MARGIN,
|
|
439
|
-
})),
|
|
440
|
-
...(rel.label !== undefined && { label: rel.label }),
|
|
441
|
-
lineNumber: rel.lineNumber,
|
|
442
|
-
};
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// ── 6. Notes ─────────────────────────────────────────────────────────────────
|
|
446
|
-
// Resolve note anchors (no diagnostics — the parser already emitted them).
|
|
447
|
-
// `no-notes` drops the footprint so layout matches an un-annotated diagram.
|
|
448
|
-
// The note floats beside its table WITHOUT moving it (ER nodes are center
|
|
449
|
-
// -positioned → default side right).
|
|
450
|
-
const notesSuppressed = parsed.options?.['no-notes'] === 'on';
|
|
451
|
-
const noteByNode =
|
|
452
|
-
notesSuppressed || !parsed.notes
|
|
453
|
-
? new Map()
|
|
454
|
-
: resolveNotes(
|
|
455
|
-
parsed.notes,
|
|
456
|
-
parsed.tables.map((t) => ({ id: t.id, label: t.name }))
|
|
457
|
-
);
|
|
458
|
-
const placedNotes = buildPlacedNotes(
|
|
459
|
-
layoutNodes.map((n) => ({
|
|
460
|
-
id: n.id,
|
|
461
|
-
x: n.x,
|
|
462
|
-
y: n.y,
|
|
463
|
-
width: n.width,
|
|
464
|
-
height: n.height,
|
|
465
|
-
})),
|
|
466
|
-
noteByNode,
|
|
467
|
-
'TB',
|
|
468
|
-
options?.collapsedNotes
|
|
469
|
-
);
|
|
470
|
-
const notedNodes: ERLayoutNode[] = layoutNodes.map((n) => {
|
|
471
|
-
const note = placedNotes.get(n.id);
|
|
472
|
-
return note ? { ...n, note } : n;
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
// ── 7. Content bbox (incl. floated notes) + off-canvas shift ─────────────────
|
|
476
|
-
let bbMinX = Infinity;
|
|
477
|
-
let bbMinY = Infinity;
|
|
478
|
-
let bbMaxX = -Infinity;
|
|
479
|
-
let bbMaxY = -Infinity;
|
|
480
|
-
const extend = (l: number, t: number, r: number, b: number): void => {
|
|
481
|
-
if (l < bbMinX) bbMinX = l;
|
|
482
|
-
if (t < bbMinY) bbMinY = t;
|
|
483
|
-
if (r > bbMaxX) bbMaxX = r;
|
|
484
|
-
if (b > bbMaxY) bbMaxY = b;
|
|
485
|
-
};
|
|
486
|
-
for (const node of notedNodes) {
|
|
487
|
-
extend(
|
|
488
|
-
node.x - node.width / 2,
|
|
489
|
-
node.y - node.height / 2,
|
|
490
|
-
node.x + node.width / 2,
|
|
491
|
-
node.y + node.height / 2
|
|
492
|
-
);
|
|
493
|
-
if (node.note && !node.note.collapsed) {
|
|
494
|
-
extend(
|
|
495
|
-
node.x + node.note.x,
|
|
496
|
-
node.y + node.note.y,
|
|
497
|
-
node.x + node.note.x + node.note.width,
|
|
498
|
-
node.y + node.note.y + node.note.height
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
for (const edge of layoutEdges) {
|
|
503
|
-
for (const pt of edge.points) extend(pt.x, pt.y, pt.x, pt.y);
|
|
504
|
-
}
|
|
505
|
-
if (!Number.isFinite(bbMinX)) {
|
|
506
|
-
bbMinX = 0;
|
|
507
|
-
bbMinY = 0;
|
|
508
|
-
bbMaxX = 0;
|
|
509
|
-
bbMaxY = 0;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const { shiftX, shiftY } = noteCanvasShift(bbMinX, bbMinY);
|
|
513
|
-
const shifted = shiftX !== 0 || shiftY !== 0;
|
|
514
|
-
const finalNodes = shifted
|
|
515
|
-
? notedNodes.map((n) => ({ ...n, x: n.x + shiftX, y: n.y + shiftY }))
|
|
516
|
-
: notedNodes;
|
|
517
|
-
const finalEdges = shifted
|
|
518
|
-
? layoutEdges.map((e) => ({
|
|
519
|
-
...e,
|
|
520
|
-
points: e.points.map((pt) => ({ x: pt.x + shiftX, y: pt.y + shiftY })),
|
|
521
|
-
}))
|
|
522
|
-
: layoutEdges;
|
|
523
|
-
|
|
524
|
-
return {
|
|
525
|
-
nodes: finalNodes,
|
|
526
|
-
edges: finalEdges,
|
|
527
|
-
width: bbMaxX + shiftX + HALF_MARGIN,
|
|
528
|
-
height: bbMaxY + shiftY + HALF_MARGIN,
|
|
529
|
-
};
|
|
530
|
-
}
|