@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/block/renderer.ts
DELETED
|
@@ -1,484 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Block diagram — D3 SVG Renderer
|
|
3
|
-
// ============================================================
|
|
4
|
-
//
|
|
5
|
-
// Draws the deterministic grid from layout.ts. The intrinsic block is scaled to
|
|
6
|
-
// fit (never upscaled) and centered in the canvas, under the title + tag legend.
|
|
7
|
-
// Containers nest a sub-grid; a collapsed container shows the shared collapse-bar
|
|
8
|
-
// signal (org/sitemap/mindmap). Collapse/expand + click are app-side; export
|
|
9
|
-
// renders the authored state. Colours come from the tag group (cascaded in the
|
|
10
|
-
// parser), resolved here via the shared tag-color helper.
|
|
11
|
-
|
|
12
|
-
import * as d3Selection from 'd3-selection';
|
|
13
|
-
import { FONT_FAMILY } from '../fonts';
|
|
14
|
-
import { mix } from '../palettes/color-utils';
|
|
15
|
-
import { resolveTagColor, tagAttrKey } from '../utils/tag-groups';
|
|
16
|
-
import { renderIntegratedLegend } from '../utils/legend-integration';
|
|
17
|
-
import { getMaxLegendReservedHeight } from '../utils/legend-layout';
|
|
18
|
-
import { LEGEND_GROUP_GAP } from '../utils/legend-constants';
|
|
19
|
-
import type { LegendGroupData, LegendPosition } from '../utils/legend-types';
|
|
20
|
-
import {
|
|
21
|
-
TITLE_FONT_SIZE,
|
|
22
|
-
TITLE_FONT_WEIGHT,
|
|
23
|
-
TITLE_Y,
|
|
24
|
-
} from '../utils/title-constants';
|
|
25
|
-
import { measureText } from '../utils/text-measure';
|
|
26
|
-
import type { PaletteColors } from '../palettes';
|
|
27
|
-
import type { D3ExportDimensions } from '../utils/d3-types';
|
|
28
|
-
import type { ParsedBlock, BlockNode } from './types';
|
|
29
|
-
import { isBlockNode } from './types';
|
|
30
|
-
import {
|
|
31
|
-
layoutBlock,
|
|
32
|
-
type BlockLayoutItem,
|
|
33
|
-
BLOCK_BAR_H,
|
|
34
|
-
BLOCK_HEADER_H,
|
|
35
|
-
} from './layout';
|
|
36
|
-
|
|
37
|
-
const PADDING = 12;
|
|
38
|
-
const TITLE_BAND = 36;
|
|
39
|
-
// Floor for export width so a narrow block still has room for its title/legend.
|
|
40
|
-
const MIN_EXPORT_WIDTH = 220;
|
|
41
|
-
|
|
42
|
-
const LEGEND_POSITION: LegendPosition = {
|
|
43
|
-
placement: 'top-center',
|
|
44
|
-
titleRelation: 'below-title',
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export interface BlockRenderOptions {
|
|
48
|
-
/** Block ids to render folded (app runtime collapse on top of authored). */
|
|
49
|
-
collapsed?: ReadonlySet<string>;
|
|
50
|
-
/** Click handler for a container header (app collapse/expand). */
|
|
51
|
-
onToggle?: (id: string, lineNumber: number) => void;
|
|
52
|
-
exportMode?: boolean;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function renderBlockForExport(
|
|
56
|
-
container: HTMLDivElement,
|
|
57
|
-
parsed: ParsedBlock,
|
|
58
|
-
palette: PaletteColors,
|
|
59
|
-
isDark: boolean,
|
|
60
|
-
exportDims?: D3ExportDimensions,
|
|
61
|
-
options?: BlockRenderOptions
|
|
62
|
-
): void {
|
|
63
|
-
renderBlock(container, parsed, palette, isDark, exportDims, {
|
|
64
|
-
exportMode: true,
|
|
65
|
-
...options,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function renderBlock(
|
|
70
|
-
container: HTMLDivElement,
|
|
71
|
-
parsed: ParsedBlock,
|
|
72
|
-
palette: PaletteColors,
|
|
73
|
-
isDark: boolean,
|
|
74
|
-
exportDims?: D3ExportDimensions,
|
|
75
|
-
options: BlockRenderOptions = {}
|
|
76
|
-
): void {
|
|
77
|
-
if (parsed.error || parsed.top.rows.length === 0) return;
|
|
78
|
-
|
|
79
|
-
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
80
|
-
const opts = parsed.options;
|
|
81
|
-
const showTitle = !!parsed.title;
|
|
82
|
-
const titleH = showTitle ? TITLE_BAND : 0;
|
|
83
|
-
|
|
84
|
-
// The app owns the live collapsed set (seeded once from authored flags via
|
|
85
|
-
// `authoredCollapsedIds`), so when it passes one it is AUTHORITATIVE — that's
|
|
86
|
-
// what lets a user expand an authored-`collapsed` container. Headless export
|
|
87
|
-
// (no set passed) derives the folds straight from the source.
|
|
88
|
-
const collapsed: ReadonlySet<string> =
|
|
89
|
-
options.collapsed ?? authoredCollapsedIds(parsed);
|
|
90
|
-
const layout = layoutBlock(parsed.top, { collapsed });
|
|
91
|
-
|
|
92
|
-
const legend = !opts.noLegend ? buildBlockLegend(parsed) : null;
|
|
93
|
-
|
|
94
|
-
// Canvas size. Live preview fills the host container and scales the block to
|
|
95
|
-
// fit. Export sizes the canvas to the block's intrinsic dimensions (+ title,
|
|
96
|
-
// legend, padding) so the SVG carries no dead whitespace.
|
|
97
|
-
let width: number;
|
|
98
|
-
let height: number;
|
|
99
|
-
if (options.exportMode) {
|
|
100
|
-
width = Math.max(layout.width, MIN_EXPORT_WIDTH) + PADDING * 2;
|
|
101
|
-
} else {
|
|
102
|
-
width = exportDims?.width ?? container.clientWidth;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const legendReserve = legend
|
|
106
|
-
? getMaxLegendReservedHeight(
|
|
107
|
-
{ groups: legend.groups, position: LEGEND_POSITION, mode: 'preview' },
|
|
108
|
-
width
|
|
109
|
-
) + LEGEND_GROUP_GAP
|
|
110
|
-
: 0;
|
|
111
|
-
|
|
112
|
-
const areaY = titleH + legendReserve + PADDING;
|
|
113
|
-
if (options.exportMode) {
|
|
114
|
-
height = areaY + layout.height + PADDING;
|
|
115
|
-
} else {
|
|
116
|
-
height = exportDims?.height ?? container.clientHeight;
|
|
117
|
-
}
|
|
118
|
-
if (width <= 0 || height <= 0) return;
|
|
119
|
-
|
|
120
|
-
const areaX = PADDING;
|
|
121
|
-
const areaW = Math.max(1, width - PADDING * 2);
|
|
122
|
-
const areaH = Math.max(1, height - areaY - PADDING);
|
|
123
|
-
|
|
124
|
-
const scale = Math.min(1, areaW / layout.width, areaH / layout.height);
|
|
125
|
-
const offsetX = areaX + (areaW - layout.width * scale) / 2;
|
|
126
|
-
// Top-align under the title/legend (a block reads top-down); only center
|
|
127
|
-
// horizontally. Centering vertically would float a short diagram mid-canvas.
|
|
128
|
-
const offsetY = areaY;
|
|
129
|
-
|
|
130
|
-
// ── SVG root ───────────────────────────────────────────────
|
|
131
|
-
const svg = d3Selection
|
|
132
|
-
.select(container)
|
|
133
|
-
.append('svg')
|
|
134
|
-
.attr('width', width)
|
|
135
|
-
.attr('height', height)
|
|
136
|
-
.attr('viewBox', `0 0 ${width} ${height}`)
|
|
137
|
-
.attr('preserveAspectRatio', 'xMidYMin meet')
|
|
138
|
-
.attr('xmlns', 'http://www.w3.org/2000/svg')
|
|
139
|
-
.attr('class', 'dgmo-block')
|
|
140
|
-
.style('font-family', FONT_FAMILY);
|
|
141
|
-
|
|
142
|
-
// No opaque full-canvas background rect: the SVG's CSS `background` style
|
|
143
|
-
// (and resvg's background option on PNG export) supplies the fill, so
|
|
144
|
-
// transparent-theme / Obsidian embeds blend with the host instead of
|
|
145
|
-
// showing a dark box — matching every other chart type.
|
|
146
|
-
|
|
147
|
-
if (showTitle) {
|
|
148
|
-
const title = svg
|
|
149
|
-
.append('text')
|
|
150
|
-
.attr('class', 'chart-title')
|
|
151
|
-
.attr('x', width / 2)
|
|
152
|
-
.attr('y', TITLE_Y)
|
|
153
|
-
.attr('text-anchor', 'middle')
|
|
154
|
-
.attr('fill', palette.text)
|
|
155
|
-
.attr('font-family', FONT_FAMILY)
|
|
156
|
-
.attr('font-size', TITLE_FONT_SIZE)
|
|
157
|
-
.attr('font-weight', TITLE_FONT_WEIGHT)
|
|
158
|
-
.text(parsed.title);
|
|
159
|
-
if (parsed.titleLineNumber !== null) {
|
|
160
|
-
title.attr('data-line-number', parsed.titleLineNumber);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const activeGroup =
|
|
165
|
-
parsed.tagGroups.length > 0 ? parsed.tagGroups[0]!.name : null;
|
|
166
|
-
const neutral = mix(palette.text, palette.bg, 22);
|
|
167
|
-
|
|
168
|
-
const root = svg
|
|
169
|
-
.append('g')
|
|
170
|
-
.attr('class', 'dgmo-block-cells')
|
|
171
|
-
.attr('transform', `translate(${offsetX},${offsetY}) scale(${scale})`);
|
|
172
|
-
|
|
173
|
-
drawItems(root, layout.items, {
|
|
174
|
-
palette,
|
|
175
|
-
tagGroups: parsed.tagGroups,
|
|
176
|
-
activeGroup,
|
|
177
|
-
activeKey: activeGroup ? tagAttrKey(activeGroup) : null,
|
|
178
|
-
neutral,
|
|
179
|
-
solidFill: opts.solidFill,
|
|
180
|
-
onToggle: options.onToggle,
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
if (legend) {
|
|
184
|
-
const legendG = svg
|
|
185
|
-
.append('g')
|
|
186
|
-
.attr('class', 'dgmo-block-legend')
|
|
187
|
-
.attr('transform', `translate(0, ${titleH})`);
|
|
188
|
-
renderIntegratedLegend(legendG, {
|
|
189
|
-
groups: legend.groups,
|
|
190
|
-
palette: {
|
|
191
|
-
bg: palette.bg,
|
|
192
|
-
surface: palette.surface,
|
|
193
|
-
text: palette.text,
|
|
194
|
-
textMuted: palette.textMuted,
|
|
195
|
-
primary: palette.primary,
|
|
196
|
-
},
|
|
197
|
-
isDark,
|
|
198
|
-
width,
|
|
199
|
-
mode: options.exportMode ? 'export' : 'preview',
|
|
200
|
-
position: LEGEND_POSITION,
|
|
201
|
-
activeGroup: legend.activeGroup,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ============================================================
|
|
207
|
-
// Drawing
|
|
208
|
-
// ============================================================
|
|
209
|
-
|
|
210
|
-
interface DrawCtx {
|
|
211
|
-
palette: PaletteColors;
|
|
212
|
-
tagGroups: ParsedBlock['tagGroups'];
|
|
213
|
-
activeGroup: string | null;
|
|
214
|
-
/** `tagAttrKey(activeGroup)` — the `data-tag-<key>` suffix for legend hover. */
|
|
215
|
-
activeKey: string | null;
|
|
216
|
-
neutral: string;
|
|
217
|
-
/** `solid-fill` — render node fills at full saturation instead of a tint. */
|
|
218
|
-
solidFill: boolean;
|
|
219
|
-
onToggle: ((id: string, lineNumber: number) => void) | undefined;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
type GSel = d3Selection.Selection<SVGGElement, unknown, null, undefined>;
|
|
223
|
-
|
|
224
|
-
function colorOf(node: BlockNode | undefined, ctx: DrawCtx): string | null {
|
|
225
|
-
if (!node || !ctx.activeGroup) return null;
|
|
226
|
-
return resolveTagColor(node.metadata, ctx.tagGroups, ctx.activeGroup) ?? null;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function clipLabel(s: string, maxWidth: number, fs: number): string {
|
|
230
|
-
if (measureText(s, fs) <= maxWidth) return s;
|
|
231
|
-
let lo = 0;
|
|
232
|
-
let hi = s.length;
|
|
233
|
-
while (lo < hi) {
|
|
234
|
-
const mid = Math.ceil((lo + hi) / 2);
|
|
235
|
-
if (measureText(s.slice(0, mid) + '…', fs) <= maxWidth) lo = mid;
|
|
236
|
-
else hi = mid - 1;
|
|
237
|
-
}
|
|
238
|
-
return lo <= 0 ? '' : s.slice(0, lo) + '…';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
let clipCounter = 0;
|
|
242
|
-
|
|
243
|
-
function drawItems(
|
|
244
|
-
g: GSel,
|
|
245
|
-
items: BlockLayoutItem[],
|
|
246
|
-
ctx: DrawCtx,
|
|
247
|
-
path: string[] = []
|
|
248
|
-
): void {
|
|
249
|
-
const { palette, solidFill: solid } = ctx;
|
|
250
|
-
for (const it of items) {
|
|
251
|
-
if (it.type === 'empty') {
|
|
252
|
-
g.append('rect')
|
|
253
|
-
.attr('x', it.x)
|
|
254
|
-
.attr('y', it.y)
|
|
255
|
-
.attr('width', it.w)
|
|
256
|
-
.attr('height', it.h)
|
|
257
|
-
.attr('rx', 7)
|
|
258
|
-
.attr('fill', 'none')
|
|
259
|
-
.attr('stroke', ctx.neutral)
|
|
260
|
-
.attr('stroke-width', 1)
|
|
261
|
-
.attr('stroke-dasharray', '3 4')
|
|
262
|
-
.attr('opacity', 0.7);
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const node = it.node!;
|
|
267
|
-
const itemPath = [...path, node.id];
|
|
268
|
-
const color = colorOf(node, ctx);
|
|
269
|
-
const stroke = color ?? ctx.neutral;
|
|
270
|
-
|
|
271
|
-
// Every block is its own `.dgmo-block-cell` group (a FLAT sibling — children
|
|
272
|
-
// are not nested in their container's group, so opacity dimming for the
|
|
273
|
-
// editor↔diagram sync and hover spotlight never compounds). `data-block-path`
|
|
274
|
-
// = the ancestor-id chain (subtree highlight), `data-tag-<key>` = the tag
|
|
275
|
-
// value (legend hover), `data-line-number` = the source line (cursor sync).
|
|
276
|
-
const cell = g
|
|
277
|
-
.append('g')
|
|
278
|
-
.attr('class', 'dgmo-block-cell')
|
|
279
|
-
.attr('data-block-id', node.id)
|
|
280
|
-
.attr('data-block-path', itemPath.join(' / '));
|
|
281
|
-
if (it.lineNumber !== undefined)
|
|
282
|
-
cell.attr('data-line-number', it.lineNumber);
|
|
283
|
-
if (ctx.activeKey) {
|
|
284
|
-
const v = node.metadata[ctx.activeKey];
|
|
285
|
-
if (v) cell.attr(`data-tag-${ctx.activeKey}`, v);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (it.type === 'collapsed') {
|
|
289
|
-
const fill = color
|
|
290
|
-
? solid
|
|
291
|
-
? color
|
|
292
|
-
: mix(color, palette.bg, 12)
|
|
293
|
-
: palette.surface;
|
|
294
|
-
const cid = `dgmo-block-clip-${clipCounter++}`;
|
|
295
|
-
cell
|
|
296
|
-
.append('clipPath')
|
|
297
|
-
.attr('id', cid)
|
|
298
|
-
.append('rect')
|
|
299
|
-
.attr('x', it.x)
|
|
300
|
-
.attr('y', it.y)
|
|
301
|
-
.attr('width', it.w)
|
|
302
|
-
.attr('height', it.h)
|
|
303
|
-
.attr('rx', 10);
|
|
304
|
-
cell
|
|
305
|
-
.append('rect')
|
|
306
|
-
.attr('x', it.x)
|
|
307
|
-
.attr('y', it.y)
|
|
308
|
-
.attr('width', it.w)
|
|
309
|
-
.attr('height', it.h)
|
|
310
|
-
.attr('rx', 10)
|
|
311
|
-
.attr('fill', fill)
|
|
312
|
-
.attr('stroke', stroke)
|
|
313
|
-
.attr('stroke-width', 1.5);
|
|
314
|
-
cell
|
|
315
|
-
.append('text')
|
|
316
|
-
.attr('x', it.x + it.w / 2)
|
|
317
|
-
.attr('y', it.y + it.h / 2 + 4)
|
|
318
|
-
.attr('text-anchor', 'middle')
|
|
319
|
-
.attr('font-size', 12.5)
|
|
320
|
-
.attr('font-weight', 700)
|
|
321
|
-
.attr('fill', palette.text)
|
|
322
|
-
.text(clipLabel(it.label ?? '', it.w - 24, 12.5));
|
|
323
|
-
// Collapse-bar (org / sitemap / mindmap precedent).
|
|
324
|
-
cell
|
|
325
|
-
.append('rect')
|
|
326
|
-
.attr('x', it.x)
|
|
327
|
-
.attr('y', it.y + it.h - BLOCK_BAR_H)
|
|
328
|
-
.attr('width', it.w)
|
|
329
|
-
.attr('height', BLOCK_BAR_H)
|
|
330
|
-
// §3 convention: solid bar = card stroke; in solid-fill mode the stroke
|
|
331
|
-
// equals the fill, so fall back to the label color to keep the bar visible.
|
|
332
|
-
.attr('fill', solid && color ? palette.text : stroke)
|
|
333
|
-
.attr('clip-path', `url(#${cid})`);
|
|
334
|
-
bindToggle(cell, it, ctx, it.h);
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (it.type === 'container') {
|
|
339
|
-
const fill = color
|
|
340
|
-
? solid
|
|
341
|
-
? color
|
|
342
|
-
: mix(color, palette.bg, 7)
|
|
343
|
-
: palette.surface;
|
|
344
|
-
cell
|
|
345
|
-
.append('rect')
|
|
346
|
-
.attr('x', it.x)
|
|
347
|
-
.attr('y', it.y)
|
|
348
|
-
.attr('width', it.w)
|
|
349
|
-
.attr('height', it.h)
|
|
350
|
-
.attr('rx', 10)
|
|
351
|
-
.attr('fill', fill)
|
|
352
|
-
.attr('stroke', stroke)
|
|
353
|
-
.attr('stroke-width', 1.5);
|
|
354
|
-
// An expanded container's header scales (subtly) with the box size and
|
|
355
|
-
// fades as it grows — a soft section watermark on big regions, crisp on
|
|
356
|
-
// small ones (the treemap leaf-label treatment, applied to headers).
|
|
357
|
-
const sizeMetric = Math.min(it.w, it.h * 2.5);
|
|
358
|
-
const headerFs = Math.max(
|
|
359
|
-
13,
|
|
360
|
-
Math.min(20, Math.round(13 + (sizeMetric - 220) / 70))
|
|
361
|
-
);
|
|
362
|
-
const grow = (headerFs - 13) / (20 - 13); // 0 (small) → 1 (large)
|
|
363
|
-
const headerOpacity = 0.92 - grow * 0.3; // 0.92 → 0.62
|
|
364
|
-
const headerY = it.y + Math.round(headerFs * 0.7) + 7;
|
|
365
|
-
cell
|
|
366
|
-
.append('text')
|
|
367
|
-
.attr('x', it.x + it.w / 2)
|
|
368
|
-
.attr('y', headerY)
|
|
369
|
-
.attr('text-anchor', 'middle')
|
|
370
|
-
.attr('font-size', headerFs)
|
|
371
|
-
.attr('font-weight', 700)
|
|
372
|
-
.attr('fill', palette.text)
|
|
373
|
-
.attr('opacity', headerOpacity)
|
|
374
|
-
.text(clipLabel(it.label ?? '', it.w - 24, headerFs));
|
|
375
|
-
bindToggle(cell, it, ctx, BLOCK_HEADER_H);
|
|
376
|
-
// Children are flat siblings in `g` (drawn after → painted on top of the
|
|
377
|
-
// container background), each carrying this container's id in their path.
|
|
378
|
-
if (it.inner) drawItems(g, it.inner, ctx, itemPath);
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// leaf
|
|
383
|
-
const fill = color
|
|
384
|
-
? solid
|
|
385
|
-
? color
|
|
386
|
-
: mix(color, palette.bg, 14)
|
|
387
|
-
: palette.bg;
|
|
388
|
-
cell.attr('data-leaf', 'true');
|
|
389
|
-
cell
|
|
390
|
-
.append('rect')
|
|
391
|
-
.attr('x', it.x)
|
|
392
|
-
.attr('y', it.y)
|
|
393
|
-
.attr('width', it.w)
|
|
394
|
-
.attr('height', it.h)
|
|
395
|
-
.attr('rx', 8)
|
|
396
|
-
.attr('fill', fill)
|
|
397
|
-
.attr('stroke', stroke)
|
|
398
|
-
.attr('stroke-width', 1.25);
|
|
399
|
-
cell
|
|
400
|
-
.append('text')
|
|
401
|
-
.attr('x', it.x + it.w / 2)
|
|
402
|
-
.attr('y', it.y + it.h / 2 + 5)
|
|
403
|
-
.attr('text-anchor', 'middle')
|
|
404
|
-
.attr('font-size', 13)
|
|
405
|
-
.attr('font-weight', 600)
|
|
406
|
-
.attr('fill', palette.text)
|
|
407
|
-
.text(clipLabel(it.label ?? '', it.w - 16, 13));
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function bindToggle(
|
|
412
|
-
cell: GSel,
|
|
413
|
-
it: BlockLayoutItem,
|
|
414
|
-
ctx: DrawCtx,
|
|
415
|
-
hitH: number
|
|
416
|
-
): void {
|
|
417
|
-
if (!ctx.onToggle || !it.node || it.lineNumber === undefined) return;
|
|
418
|
-
const id = it.node.id;
|
|
419
|
-
const ln = it.lineNumber;
|
|
420
|
-
cell
|
|
421
|
-
.append('rect')
|
|
422
|
-
.attr('x', it.x)
|
|
423
|
-
.attr('y', it.y)
|
|
424
|
-
.attr('width', it.w)
|
|
425
|
-
.attr('height', hitH)
|
|
426
|
-
.attr('rx', 10)
|
|
427
|
-
.attr('fill', 'transparent')
|
|
428
|
-
.style('cursor', 'pointer')
|
|
429
|
-
.on('click', () => ctx.onToggle!(id, ln));
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// ============================================================
|
|
433
|
-
// Collapsed set + legend
|
|
434
|
-
// ============================================================
|
|
435
|
-
|
|
436
|
-
/** The ids of containers authored with the `collapsed` flag — the app seeds its
|
|
437
|
-
* live collapsed set from this so authored folds are the initial state yet stay
|
|
438
|
-
* user-toggleable. */
|
|
439
|
-
export function authoredCollapsedIds(parsed: ParsedBlock): Set<string> {
|
|
440
|
-
const into = new Set<string>();
|
|
441
|
-
collectCollapsed(parsed, into);
|
|
442
|
-
return into;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
function collectCollapsed(parsed: ParsedBlock, into: Set<string>): void {
|
|
446
|
-
const walk = (grid: ParsedBlock['top']): void => {
|
|
447
|
-
for (const row of grid.rows)
|
|
448
|
-
for (const c of row)
|
|
449
|
-
if (isBlockNode(c)) {
|
|
450
|
-
if (c.collapsed && c.grid) into.add(c.id);
|
|
451
|
-
if (c.grid) walk(c.grid);
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
walk(parsed.top);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
interface BlockLegend {
|
|
458
|
-
groups: LegendGroupData[];
|
|
459
|
-
activeGroup: string | null;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/** One legend group from the first tag group, listing only the values actually
|
|
463
|
-
* used in the diagram — the shared tag-group framework. */
|
|
464
|
-
function buildBlockLegend(parsed: ParsedBlock): BlockLegend | null {
|
|
465
|
-
if (parsed.tagGroups.length === 0) return null;
|
|
466
|
-
const tg = parsed.tagGroups[0]!;
|
|
467
|
-
const key = tagAttrKey(tg.name);
|
|
468
|
-
const used = new Set<string>();
|
|
469
|
-
const walk = (grid: ParsedBlock['top']): void => {
|
|
470
|
-
for (const row of grid.rows)
|
|
471
|
-
for (const c of row)
|
|
472
|
-
if (isBlockNode(c)) {
|
|
473
|
-
const v = c.metadata[key];
|
|
474
|
-
if (v) used.add(v.toLowerCase());
|
|
475
|
-
if (c.grid) walk(c.grid);
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
walk(parsed.top);
|
|
479
|
-
const entries = tg.entries
|
|
480
|
-
.filter((e) => used.has(e.value.toLowerCase()))
|
|
481
|
-
.map((e) => ({ value: e.value, color: e.color }));
|
|
482
|
-
if (entries.length === 0) return null;
|
|
483
|
-
return { groups: [{ name: tg.name, entries }], activeGroup: tg.name };
|
|
484
|
-
}
|
package/src/block/types.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import type { DgmoError } from '../diagnostics';
|
|
2
|
-
import type { TagGroup } from '../utils/tag-groups';
|
|
3
|
-
|
|
4
|
-
// ============================================================
|
|
5
|
-
// Block diagram — Parsed Types
|
|
6
|
-
// ============================================================
|
|
7
|
-
//
|
|
8
|
-
// A block diagram is an author-controlled GRID of rectangular blocks. One
|
|
9
|
-
// source line = one row; `[Label]` is a block; `_` is an empty cell. A block
|
|
10
|
-
// becomes a CONTAINER by indenting a sub-grid under it (siblings stack
|
|
11
|
-
// vertically; the horizontal grid is for the leaf blocks inside a container).
|
|
12
|
-
// Metadata (tags, `span:`, `collapsed`) goes OUTSIDE the bracket — the
|
|
13
|
-
// boxes-and-lines group idiom — and a group's tag cascades to its children.
|
|
14
|
-
// See the block § of docs/dgmo-language-spec.md for the canonical grammar.
|
|
15
|
-
|
|
16
|
-
/** A single block. Becomes a container when `grid` is present. */
|
|
17
|
-
export interface BlockNode {
|
|
18
|
-
readonly id: string;
|
|
19
|
-
readonly label: string;
|
|
20
|
-
/** Column span (≥ 1). Resolved/clamped during the layout-inference pass. */
|
|
21
|
-
span: number;
|
|
22
|
-
/** Authored `collapsed` flag — container starts folded (collapse-bar). */
|
|
23
|
-
collapsed: boolean;
|
|
24
|
-
readonly metadata: Record<string, string>;
|
|
25
|
-
readonly lineNumber: number;
|
|
26
|
-
/** Present iff this block is a container (has an indented sub-grid). */
|
|
27
|
-
grid?: BlockGrid;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** A deliberate empty cell (`_`), reserving grid space with no block. */
|
|
31
|
-
export interface EmptyCell {
|
|
32
|
-
readonly empty: true;
|
|
33
|
-
span: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type BlockCell = BlockNode | EmptyCell;
|
|
37
|
-
|
|
38
|
-
export interface BlockGrid {
|
|
39
|
-
/** Explicit `columns N`, or null until the inference pass resolves it from
|
|
40
|
-
* the widest row. After parse this is always a positive integer. */
|
|
41
|
-
cols: number | null;
|
|
42
|
-
readonly rows: BlockCell[][];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface BlockOptions {
|
|
46
|
-
/** `no-legend` — hide the tag legend. */
|
|
47
|
-
noLegend: boolean;
|
|
48
|
-
/** `solid-fill` — fill nodes at full intent saturation instead of a tint. */
|
|
49
|
-
solidFill: boolean;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface ParsedBlock {
|
|
53
|
-
readonly type: 'block';
|
|
54
|
-
readonly title: string | null;
|
|
55
|
-
readonly titleLineNumber: number | null;
|
|
56
|
-
/** The top-level grid (rows of cells; containers nest their own grids). */
|
|
57
|
-
readonly top: BlockGrid;
|
|
58
|
-
readonly tagGroups: TagGroup[];
|
|
59
|
-
readonly options: BlockOptions;
|
|
60
|
-
readonly diagnostics: DgmoError[];
|
|
61
|
-
readonly error: string | null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Type guard — narrow a cell to a real block. */
|
|
65
|
-
export function isBlockNode(cell: BlockCell): cell is BlockNode {
|
|
66
|
-
return (cell as EmptyCell).empty !== true;
|
|
67
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Boxes and Lines — Collapse/Expand Transform
|
|
3
|
-
// ============================================================
|
|
4
|
-
|
|
5
|
-
import type { ParsedBoxesAndLines, BLGroup, BLEdge } from './types';
|
|
6
|
-
|
|
7
|
-
export interface BLCollapseResult {
|
|
8
|
-
parsed: ParsedBoxesAndLines;
|
|
9
|
-
collapsedChildCounts: Map<string, number>;
|
|
10
|
-
originalGroups: readonly BLGroup[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Pure transform: returns a new ParsedBoxesAndLines with collapsed groups
|
|
15
|
-
* removed from the diagram content.
|
|
16
|
-
*
|
|
17
|
-
* - Children of collapsed groups removed from nodes
|
|
18
|
-
* - Edges redirected: endpoints in collapsed groups → group ID
|
|
19
|
-
* - Internal edges (both in same collapsed group) dropped
|
|
20
|
-
* - Duplicate edges (same source, target, label) deduplicated
|
|
21
|
-
* - Collapsed groups removed from groups[] (layout handles as nodes)
|
|
22
|
-
*/
|
|
23
|
-
export function collapseBoxesAndLines(
|
|
24
|
-
parsed: ParsedBoxesAndLines,
|
|
25
|
-
collapsedGroups: Set<string>
|
|
26
|
-
): BLCollapseResult {
|
|
27
|
-
const originalGroups = parsed.groups;
|
|
28
|
-
|
|
29
|
-
if (collapsedGroups.size === 0) {
|
|
30
|
-
return { parsed, collapsedChildCounts: new Map(), originalGroups };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Build group lookup by label
|
|
34
|
-
const groupByLabel = new Map<string, BLGroup>();
|
|
35
|
-
for (const group of parsed.groups) {
|
|
36
|
-
groupByLabel.set(group.label, group);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Build node → collapsed group lookup
|
|
40
|
-
const nodeToGroup = new Map<string, string>();
|
|
41
|
-
const collapsedChildCounts = new Map<string, number>();
|
|
42
|
-
|
|
43
|
-
// Recursively collect all descendants of a group (including sub-group children)
|
|
44
|
-
function collectDescendants(groupLabel: string): string[] {
|
|
45
|
-
const group = groupByLabel.get(groupLabel);
|
|
46
|
-
if (!group) return [];
|
|
47
|
-
const descendants: string[] = [];
|
|
48
|
-
for (const child of group.children) {
|
|
49
|
-
descendants.push(child);
|
|
50
|
-
// If child is itself a group, collect its descendants too
|
|
51
|
-
if (groupByLabel.has(child)) {
|
|
52
|
-
descendants.push(...collectDescendants(child));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return descendants;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
for (const groupLabel of collapsedGroups) {
|
|
59
|
-
const group = groupByLabel.get(groupLabel);
|
|
60
|
-
if (!group) continue;
|
|
61
|
-
const groupId = `__group_${groupLabel}`;
|
|
62
|
-
|
|
63
|
-
const allDescendants = collectDescendants(groupLabel);
|
|
64
|
-
for (const child of allDescendants) {
|
|
65
|
-
nodeToGroup.set(child, groupId);
|
|
66
|
-
}
|
|
67
|
-
collapsedChildCounts.set(groupLabel, group.children.length);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Filter nodes: remove children of collapsed groups
|
|
71
|
-
const nodes = parsed.nodes.filter((n) => !nodeToGroup.has(n.label));
|
|
72
|
-
|
|
73
|
-
// Remap and deduplicate edges
|
|
74
|
-
const edgeKeys = new Set<string>();
|
|
75
|
-
const edges: BLEdge[] = [];
|
|
76
|
-
for (const edge of parsed.edges) {
|
|
77
|
-
const src = nodeToGroup.get(edge.source) ?? edge.source;
|
|
78
|
-
const tgt = nodeToGroup.get(edge.target) ?? edge.target;
|
|
79
|
-
if (src === tgt) continue;
|
|
80
|
-
const key = `${src}|${tgt}|${edge.label ?? ''}`;
|
|
81
|
-
if (edgeKeys.has(key)) continue;
|
|
82
|
-
edgeKeys.add(key);
|
|
83
|
-
edges.push({ ...edge, source: src, target: tgt });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Keep only groups that are not collapsed and not inside a collapsed group
|
|
87
|
-
const groups = parsed.groups.filter(
|
|
88
|
-
(g) => !collapsedGroups.has(g.label) && !nodeToGroup.has(g.label)
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
parsed: { ...parsed, nodes, edges, groups },
|
|
93
|
-
collapsedChildCounts,
|
|
94
|
-
originalGroups,
|
|
95
|
-
};
|
|
96
|
-
}
|