@diagrammo/dgmo 0.47.0 → 0.48.0
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/auto.js +1 -1
- package/dist/auto.mjs +1 -1
- package/dist/element.js +1 -1
- package/dist/element.mjs +1 -1
- package/package.json +19 -70
- 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/mindmap/layout.ts
DELETED
|
@@ -1,649 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Mindmap Two-Sided Horizontal Tree Layout
|
|
3
|
-
// ============================================================
|
|
4
|
-
//
|
|
5
|
-
// Classic mindmap layout: root centered, children branch left and right.
|
|
6
|
-
// Each side is a recursive vertical stacking — no D3 tree layout needed.
|
|
7
|
-
// Nodes at the same depth share the same X column. Hierarchy reads
|
|
8
|
-
// left→right (right branches) or right→left (left branches).
|
|
9
|
-
|
|
10
|
-
import type { MindmapNode } from './types';
|
|
11
|
-
import type {
|
|
12
|
-
MindmapLayoutNode,
|
|
13
|
-
MindmapLayoutEdge,
|
|
14
|
-
MindmapLayoutResult,
|
|
15
|
-
} from './types';
|
|
16
|
-
import type { ParsedMindmap } from './types';
|
|
17
|
-
import type { PaletteColors } from '../palettes';
|
|
18
|
-
import type { TagGroup } from '../utils/tag-groups';
|
|
19
|
-
import { resolveTagColor, injectDefaultTagMetadata } from '../utils/tag-groups';
|
|
20
|
-
import { computeNodeText } from './text-wrap';
|
|
21
|
-
import { ScaleContext } from '../utils/scaling';
|
|
22
|
-
|
|
23
|
-
// ============================================================
|
|
24
|
-
// Constants
|
|
25
|
-
// ============================================================
|
|
26
|
-
|
|
27
|
-
const ROOT_WIDTH = 180;
|
|
28
|
-
const DEPTH1_WIDTH = 150;
|
|
29
|
-
const LEAF_WIDTH = 120;
|
|
30
|
-
|
|
31
|
-
const SINGLE_LABEL_HEIGHT = 28;
|
|
32
|
-
const LABEL_LINE_HEIGHT = 18; // per line when multi-line
|
|
33
|
-
const DESC_LINE_HEIGHT = 14; // per description line
|
|
34
|
-
const NODE_V_PAD = 10;
|
|
35
|
-
|
|
36
|
-
const H_GAP = 40; // horizontal gap between parent edge and child edge
|
|
37
|
-
const V_GAP = 12; // vertical gap between sibling nodes
|
|
38
|
-
const MARGIN = 40;
|
|
39
|
-
const MULTI_ROOT_GAP = 60; // horizontal gap between independent root trees
|
|
40
|
-
|
|
41
|
-
interface ScaledConstants {
|
|
42
|
-
rootWidth: number;
|
|
43
|
-
depth1Width: number;
|
|
44
|
-
leafWidth: number;
|
|
45
|
-
hGap: number;
|
|
46
|
-
vGap: number;
|
|
47
|
-
margin: number;
|
|
48
|
-
multiRootGap: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ============================================================
|
|
52
|
-
// Direction — which side a subtree grows toward
|
|
53
|
-
// ============================================================
|
|
54
|
-
|
|
55
|
-
type Direction = 'right' | 'left';
|
|
56
|
-
|
|
57
|
-
// ============================================================
|
|
58
|
-
// Internal positioned node (intermediate before final output)
|
|
59
|
-
// ============================================================
|
|
60
|
-
|
|
61
|
-
interface PositionedNode {
|
|
62
|
-
node: MindmapNode;
|
|
63
|
-
x: number; // top-left x
|
|
64
|
-
y: number; // top-left y
|
|
65
|
-
width: number;
|
|
66
|
-
height: number;
|
|
67
|
-
depth: number;
|
|
68
|
-
direction: Direction;
|
|
69
|
-
subtreeHeight: number; // total height of this node's subtree
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ============================================================
|
|
73
|
-
// Main entry point
|
|
74
|
-
// ============================================================
|
|
75
|
-
|
|
76
|
-
export function layoutMindmap(
|
|
77
|
-
parsed: ParsedMindmap,
|
|
78
|
-
_palette: PaletteColors,
|
|
79
|
-
options?: {
|
|
80
|
-
interactive?: boolean;
|
|
81
|
-
hiddenCounts?: Map<string, number>;
|
|
82
|
-
activeTagGroup?: string | null;
|
|
83
|
-
hideDescriptions?: boolean;
|
|
84
|
-
ctx?: ScaleContext;
|
|
85
|
-
}
|
|
86
|
-
): MindmapLayoutResult {
|
|
87
|
-
const roots = parsed.roots;
|
|
88
|
-
if (roots.length === 0) {
|
|
89
|
-
return { nodes: [], edges: [], width: 0, height: 0 };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const hiddenCounts = options?.hiddenCounts ?? new Map<string, number>();
|
|
93
|
-
const activeTagGroup = options?.activeTagGroup ?? null;
|
|
94
|
-
const hideDescriptions = options?.hideDescriptions ?? false;
|
|
95
|
-
const ctx = options?.ctx ?? ScaleContext.identity();
|
|
96
|
-
|
|
97
|
-
const sc: ScaledConstants = {
|
|
98
|
-
rootWidth: ctx.structural(ROOT_WIDTH),
|
|
99
|
-
depth1Width: ctx.structural(DEPTH1_WIDTH),
|
|
100
|
-
leafWidth: ctx.structural(LEAF_WIDTH),
|
|
101
|
-
hGap: ctx.aesthetic(H_GAP),
|
|
102
|
-
vGap: ctx.aesthetic(V_GAP),
|
|
103
|
-
margin: ctx.aesthetic(MARGIN),
|
|
104
|
-
multiRootGap: ctx.aesthetic(MULTI_ROOT_GAP),
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
populateDepthCache(roots);
|
|
108
|
-
|
|
109
|
-
const allNodes: MindmapNode[] = [];
|
|
110
|
-
const collectAll = (nodes: readonly MindmapNode[]) => {
|
|
111
|
-
for (const n of nodes) {
|
|
112
|
-
allNodes.push(n);
|
|
113
|
-
collectAll(n.children);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
collectAll(roots);
|
|
117
|
-
injectDefaultTagMetadata(allNodes, parsed.tagGroups);
|
|
118
|
-
|
|
119
|
-
const tagGroups = parsed.tagGroups;
|
|
120
|
-
|
|
121
|
-
if (roots.length === 1) {
|
|
122
|
-
return layoutSingleRoot(
|
|
123
|
-
roots[0]!,
|
|
124
|
-
hiddenCounts,
|
|
125
|
-
hideDescriptions,
|
|
126
|
-
tagGroups,
|
|
127
|
-
activeTagGroup,
|
|
128
|
-
sc
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
return layoutMultiRoot(
|
|
132
|
-
roots,
|
|
133
|
-
parsed,
|
|
134
|
-
hiddenCounts,
|
|
135
|
-
hideDescriptions,
|
|
136
|
-
tagGroups,
|
|
137
|
-
activeTagGroup,
|
|
138
|
-
sc
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// ============================================================
|
|
143
|
-
// Single root — two-sided horizontal tree
|
|
144
|
-
// ============================================================
|
|
145
|
-
|
|
146
|
-
function layoutSingleRoot(
|
|
147
|
-
root: MindmapNode,
|
|
148
|
-
hiddenCounts: Map<string, number>,
|
|
149
|
-
hideDescriptions: boolean,
|
|
150
|
-
tagGroups: readonly TagGroup[] = [],
|
|
151
|
-
activeTagGroup: string | null = null,
|
|
152
|
-
sc: ScaledConstants
|
|
153
|
-
): MindmapLayoutResult {
|
|
154
|
-
const positioned: PositionedNode[] = [];
|
|
155
|
-
const rootW = nodeWidthS(0, sc);
|
|
156
|
-
const rootH = nodeHeightS(root, hideDescriptions, sc);
|
|
157
|
-
|
|
158
|
-
const children = root.children;
|
|
159
|
-
const { right: rightChildren, left: leftChildren } = balancedSplitS(
|
|
160
|
-
children,
|
|
161
|
-
hideDescriptions,
|
|
162
|
-
sc
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
const rightHeight = computeGroupHeightS(
|
|
166
|
-
rightChildren,
|
|
167
|
-
1,
|
|
168
|
-
hideDescriptions,
|
|
169
|
-
sc
|
|
170
|
-
);
|
|
171
|
-
const leftHeight = computeGroupHeightS(leftChildren, 1, hideDescriptions, sc);
|
|
172
|
-
const maxSideHeight = Math.max(rightHeight, leftHeight, rootH);
|
|
173
|
-
|
|
174
|
-
const rootX = 0;
|
|
175
|
-
const rootY = maxSideHeight / 2 - rootH / 2;
|
|
176
|
-
|
|
177
|
-
positioned.push({
|
|
178
|
-
node: root,
|
|
179
|
-
x: rootX,
|
|
180
|
-
y: rootY,
|
|
181
|
-
width: rootW,
|
|
182
|
-
height: rootH,
|
|
183
|
-
depth: 0,
|
|
184
|
-
direction: 'right',
|
|
185
|
-
subtreeHeight: maxSideHeight,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const rootCenterY = rootY + rootH / 2;
|
|
189
|
-
const rightStartX = rootX + rootW + sc.hGap;
|
|
190
|
-
layoutSideS(
|
|
191
|
-
rightChildren,
|
|
192
|
-
rightStartX,
|
|
193
|
-
rootCenterY,
|
|
194
|
-
1,
|
|
195
|
-
'right',
|
|
196
|
-
hideDescriptions,
|
|
197
|
-
positioned,
|
|
198
|
-
sc
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
const leftStartX = rootX - sc.hGap;
|
|
202
|
-
layoutSideS(
|
|
203
|
-
leftChildren,
|
|
204
|
-
leftStartX,
|
|
205
|
-
rootCenterY,
|
|
206
|
-
1,
|
|
207
|
-
'left',
|
|
208
|
-
hideDescriptions,
|
|
209
|
-
positioned,
|
|
210
|
-
sc
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
return finalizeS(
|
|
214
|
-
positioned,
|
|
215
|
-
hiddenCounts,
|
|
216
|
-
hideDescriptions,
|
|
217
|
-
root,
|
|
218
|
-
tagGroups,
|
|
219
|
-
activeTagGroup,
|
|
220
|
-
sc
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// ============================================================
|
|
225
|
-
// Multi-root — each root gets its own two-sided tree, arranged horizontally
|
|
226
|
-
// ============================================================
|
|
227
|
-
|
|
228
|
-
function layoutMultiRoot(
|
|
229
|
-
roots: readonly MindmapNode[],
|
|
230
|
-
_parsed: ParsedMindmap,
|
|
231
|
-
hiddenCounts: Map<string, number>,
|
|
232
|
-
hideDescriptions: boolean,
|
|
233
|
-
tagGroups: readonly TagGroup[],
|
|
234
|
-
activeTagGroup: string | null,
|
|
235
|
-
sc: ScaledConstants
|
|
236
|
-
): MindmapLayoutResult {
|
|
237
|
-
const subResults: MindmapLayoutResult[] = [];
|
|
238
|
-
for (const r of roots) {
|
|
239
|
-
subResults.push(
|
|
240
|
-
layoutSingleRoot(
|
|
241
|
-
r,
|
|
242
|
-
hiddenCounts,
|
|
243
|
-
hideDescriptions,
|
|
244
|
-
tagGroups,
|
|
245
|
-
activeTagGroup,
|
|
246
|
-
sc
|
|
247
|
-
)
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const totalWidth =
|
|
252
|
-
subResults.reduce((sum, r) => sum + r.width, 0) +
|
|
253
|
-
sc.multiRootGap * (subResults.length - 1);
|
|
254
|
-
const maxHeight = Math.max(...subResults.map((r) => r.height));
|
|
255
|
-
|
|
256
|
-
const allNodes: MindmapLayoutNode[] = [];
|
|
257
|
-
const allEdges: MindmapLayoutEdge[] = [];
|
|
258
|
-
let xOffset = 0;
|
|
259
|
-
|
|
260
|
-
for (const sub of subResults) {
|
|
261
|
-
const yOffset = (maxHeight - sub.height) / 2;
|
|
262
|
-
for (const n of sub.nodes) {
|
|
263
|
-
allNodes.push({ ...n, x: n.x + xOffset, y: n.y + yOffset });
|
|
264
|
-
}
|
|
265
|
-
for (const e of sub.edges) {
|
|
266
|
-
allEdges.push({
|
|
267
|
-
sourceId: e.sourceId,
|
|
268
|
-
targetId: e.targetId,
|
|
269
|
-
path: offsetPath(e.path, xOffset, yOffset),
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
xOffset += sub.width + sc.multiRootGap;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
nodes: allNodes,
|
|
277
|
-
edges: allEdges,
|
|
278
|
-
width: totalWidth,
|
|
279
|
-
height: maxHeight,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// ============================================================
|
|
284
|
-
// Recursive side layout
|
|
285
|
-
// ============================================================
|
|
286
|
-
|
|
287
|
-
function layoutSideS(
|
|
288
|
-
children: readonly MindmapNode[],
|
|
289
|
-
startX: number,
|
|
290
|
-
parentCenterY: number,
|
|
291
|
-
depth: number,
|
|
292
|
-
direction: Direction,
|
|
293
|
-
hideDescriptions: boolean,
|
|
294
|
-
positioned: PositionedNode[],
|
|
295
|
-
sc: ScaledConstants
|
|
296
|
-
): void {
|
|
297
|
-
if (children.length === 0) return;
|
|
298
|
-
|
|
299
|
-
const groupHeight = computeGroupHeightS(
|
|
300
|
-
children,
|
|
301
|
-
depth,
|
|
302
|
-
hideDescriptions,
|
|
303
|
-
sc
|
|
304
|
-
);
|
|
305
|
-
let currentY = parentCenterY - groupHeight / 2;
|
|
306
|
-
|
|
307
|
-
for (const child of children) {
|
|
308
|
-
const w = nodeWidthS(depth, sc);
|
|
309
|
-
const h = nodeHeightS(child, hideDescriptions, sc);
|
|
310
|
-
const subtreeH = computeSubtreeHeightS(child, depth, hideDescriptions, sc);
|
|
311
|
-
|
|
312
|
-
const nodeY = currentY + subtreeH / 2 - h / 2;
|
|
313
|
-
const nodeX = direction === 'right' ? startX : startX - w;
|
|
314
|
-
|
|
315
|
-
positioned.push({
|
|
316
|
-
node: child,
|
|
317
|
-
x: nodeX,
|
|
318
|
-
y: nodeY,
|
|
319
|
-
width: w,
|
|
320
|
-
height: h,
|
|
321
|
-
depth,
|
|
322
|
-
direction,
|
|
323
|
-
subtreeHeight: subtreeH,
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
if (child.children.length > 0) {
|
|
327
|
-
const childCenterY = nodeY + h / 2;
|
|
328
|
-
const nextX =
|
|
329
|
-
direction === 'right' ? nodeX + w + sc.hGap : nodeX - sc.hGap;
|
|
330
|
-
layoutSideS(
|
|
331
|
-
child.children,
|
|
332
|
-
nextX,
|
|
333
|
-
childCenterY,
|
|
334
|
-
depth + 1,
|
|
335
|
-
direction,
|
|
336
|
-
hideDescriptions,
|
|
337
|
-
positioned,
|
|
338
|
-
sc
|
|
339
|
-
);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
currentY += subtreeH + sc.vGap;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// ============================================================
|
|
347
|
-
// Height computation
|
|
348
|
-
// ============================================================
|
|
349
|
-
|
|
350
|
-
function computeGroupHeightS(
|
|
351
|
-
children: readonly MindmapNode[],
|
|
352
|
-
depth: number,
|
|
353
|
-
hideDescriptions: boolean,
|
|
354
|
-
sc: ScaledConstants
|
|
355
|
-
): number {
|
|
356
|
-
if (children.length === 0) return 0;
|
|
357
|
-
let total = 0;
|
|
358
|
-
for (const child of children) {
|
|
359
|
-
total += computeSubtreeHeightS(child, depth, hideDescriptions, sc);
|
|
360
|
-
}
|
|
361
|
-
total += sc.vGap * (children.length - 1);
|
|
362
|
-
return total;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function computeSubtreeHeightS(
|
|
366
|
-
node: MindmapNode,
|
|
367
|
-
depth: number,
|
|
368
|
-
hideDescriptions: boolean,
|
|
369
|
-
sc: ScaledConstants
|
|
370
|
-
): number {
|
|
371
|
-
const h = nodeHeightS(node, hideDescriptions, sc);
|
|
372
|
-
if (node.children.length === 0) return h;
|
|
373
|
-
const childrenHeight = computeGroupHeightS(
|
|
374
|
-
node.children,
|
|
375
|
-
depth + 1,
|
|
376
|
-
hideDescriptions,
|
|
377
|
-
sc
|
|
378
|
-
);
|
|
379
|
-
return Math.max(h, childrenHeight);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// ============================================================
|
|
383
|
-
// Finalization — normalize coordinates, generate output
|
|
384
|
-
// ============================================================
|
|
385
|
-
|
|
386
|
-
function resolveNodeColor(
|
|
387
|
-
node: MindmapNode,
|
|
388
|
-
tagGroups: readonly TagGroup[],
|
|
389
|
-
activeGroupName: string | null
|
|
390
|
-
): string | undefined {
|
|
391
|
-
// Explicit inline (color) always wins
|
|
392
|
-
if (node.color) return node.color;
|
|
393
|
-
return resolveTagColor(node.metadata, [...tagGroups], activeGroupName);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function finalizeS(
|
|
397
|
-
positioned: PositionedNode[],
|
|
398
|
-
hiddenCounts: Map<string, number>,
|
|
399
|
-
_hideDescriptions: boolean,
|
|
400
|
-
_rootMindmapNode: MindmapNode,
|
|
401
|
-
tagGroups: readonly TagGroup[] = [],
|
|
402
|
-
activeTagGroup: string | null = null,
|
|
403
|
-
sc: ScaledConstants
|
|
404
|
-
): MindmapLayoutResult {
|
|
405
|
-
let minX = Infinity,
|
|
406
|
-
minY = Infinity,
|
|
407
|
-
maxX = -Infinity,
|
|
408
|
-
maxY = -Infinity;
|
|
409
|
-
for (const p of positioned) {
|
|
410
|
-
minX = Math.min(minX, p.x);
|
|
411
|
-
minY = Math.min(minY, p.y);
|
|
412
|
-
maxX = Math.max(maxX, p.x + p.width);
|
|
413
|
-
maxY = Math.max(maxY, p.y + p.height);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
const offsetX = -minX + sc.margin;
|
|
417
|
-
const offsetY = -minY + sc.margin;
|
|
418
|
-
const totalWidth = maxX - minX + sc.margin * 2;
|
|
419
|
-
const totalHeight = maxY - minY + sc.margin * 2;
|
|
420
|
-
|
|
421
|
-
// Build node index for edge generation
|
|
422
|
-
const nodeMap = new Map<string, PositionedNode>();
|
|
423
|
-
for (const p of positioned) {
|
|
424
|
-
nodeMap.set(p.node.id, p);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
const nodes: MindmapLayoutNode[] = [];
|
|
428
|
-
const edges: MindmapLayoutEdge[] = [];
|
|
429
|
-
|
|
430
|
-
for (const p of positioned) {
|
|
431
|
-
const resolvedColor = resolveNodeColor(p.node, tagGroups, activeTagGroup);
|
|
432
|
-
const hiddenCount = hiddenCounts.get(p.node.id);
|
|
433
|
-
nodes.push({
|
|
434
|
-
id: p.node.id,
|
|
435
|
-
label: p.node.label,
|
|
436
|
-
...(p.node.description !== undefined && {
|
|
437
|
-
description: [...p.node.description],
|
|
438
|
-
}),
|
|
439
|
-
metadata: { ...p.node.metadata },
|
|
440
|
-
lineNumber: p.node.lineNumber,
|
|
441
|
-
...(resolvedColor !== undefined && { color: resolvedColor }),
|
|
442
|
-
x: p.x + offsetX,
|
|
443
|
-
y: p.y + offsetY,
|
|
444
|
-
width: p.width,
|
|
445
|
-
height: p.height,
|
|
446
|
-
depth: p.depth,
|
|
447
|
-
angle: 0,
|
|
448
|
-
radius: 0,
|
|
449
|
-
...(hiddenCount !== undefined && { hiddenCount }),
|
|
450
|
-
hasChildren: hiddenCounts.has(p.node.id) || p.node.children.length > 0,
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Generate bus-style edges: one trunk + vertical bar + individual drops per parent.
|
|
455
|
-
// This prevents overlapping semi-transparent segments.
|
|
456
|
-
const childrenByParent = new Map<string, PositionedNode[]>();
|
|
457
|
-
for (const p of positioned) {
|
|
458
|
-
if (p.node.parentId) {
|
|
459
|
-
const arr = childrenByParent.get(p.node.parentId) ?? [];
|
|
460
|
-
arr.push(p);
|
|
461
|
-
childrenByParent.set(p.node.parentId, arr);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Split children by direction so left and right get separate bus edges
|
|
466
|
-
const groupedEdges: [string, PositionedNode[]][] = [];
|
|
467
|
-
for (const [parentId, children] of childrenByParent) {
|
|
468
|
-
const rightKids = children.filter((c) => c.direction === 'right');
|
|
469
|
-
const leftKids = children.filter((c) => c.direction === 'left');
|
|
470
|
-
if (rightKids.length > 0) groupedEdges.push([parentId, rightKids]);
|
|
471
|
-
if (leftKids.length > 0) groupedEdges.push([parentId, leftKids]);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
for (const [parentId, children] of groupedEdges) {
|
|
475
|
-
const parent = nodeMap.get(parentId)!;
|
|
476
|
-
const px = parent.x + offsetX;
|
|
477
|
-
const py = parent.y + offsetY;
|
|
478
|
-
// In-bounds: groupedEdges only contains non-empty children arrays.
|
|
479
|
-
const isLeft = children[0]!.direction === 'left';
|
|
480
|
-
|
|
481
|
-
// Parent connection point
|
|
482
|
-
const srcX = isLeft ? px : px + parent.width;
|
|
483
|
-
const srcY = py + parent.height / 2;
|
|
484
|
-
|
|
485
|
-
// Midpoint X between parent edge and children column
|
|
486
|
-
// In-bounds: children is non-empty (see above).
|
|
487
|
-
const firstChild = children[0]!;
|
|
488
|
-
const childEdgeX = isLeft
|
|
489
|
-
? firstChild.x + offsetX + firstChild.width
|
|
490
|
-
: firstChild.x + offsetX;
|
|
491
|
-
const midX = (srcX + childEdgeX) / 2;
|
|
492
|
-
|
|
493
|
-
if (children.length === 1) {
|
|
494
|
-
// Single child — simple elbow, no bus needed
|
|
495
|
-
// In-bounds: children.length === 1.
|
|
496
|
-
const c = children[0]!;
|
|
497
|
-
const tgtX = isLeft ? c.x + offsetX + c.width : c.x + offsetX;
|
|
498
|
-
const tgtY = c.y + offsetY + c.height / 2;
|
|
499
|
-
edges.push({
|
|
500
|
-
sourceId: parentId,
|
|
501
|
-
targetId: c.node.id,
|
|
502
|
-
path: `M ${srcX} ${srcY} L ${midX} ${srcY} L ${midX} ${tgtY} L ${tgtX} ${tgtY}`,
|
|
503
|
-
});
|
|
504
|
-
} else {
|
|
505
|
-
// Bus pattern: trunk → vertical bar → drops
|
|
506
|
-
// 1. Trunk: parent edge → midX
|
|
507
|
-
edges.push({
|
|
508
|
-
sourceId: parentId,
|
|
509
|
-
targetId: parentId, // self-ref = trunk
|
|
510
|
-
path: `M ${srcX} ${srcY} L ${midX} ${srcY}`,
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
// 2. Vertical bar from topmost child to bottommost child at midX
|
|
514
|
-
const childYs = children.map((c) => c.y + offsetY + c.height / 2);
|
|
515
|
-
const minChildY = Math.min(...childYs);
|
|
516
|
-
const maxChildY = Math.max(...childYs);
|
|
517
|
-
edges.push({
|
|
518
|
-
sourceId: parentId,
|
|
519
|
-
targetId: parentId, // self-ref = bar
|
|
520
|
-
path: `M ${midX} ${minChildY} L ${midX} ${maxChildY}`,
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
// 3. Individual horizontal drops from midX to each child
|
|
524
|
-
for (let i = 0; i < children.length; i++) {
|
|
525
|
-
// In-bounds by loop guard (i < children.length); childYs derived from children.
|
|
526
|
-
const c = children[i]!;
|
|
527
|
-
const tgtX = isLeft ? c.x + offsetX + c.width : c.x + offsetX;
|
|
528
|
-
const tgtY = childYs[i]!;
|
|
529
|
-
edges.push({
|
|
530
|
-
sourceId: parentId,
|
|
531
|
-
targetId: c.node.id,
|
|
532
|
-
path: `M ${midX} ${tgtY} L ${tgtX} ${tgtY}`,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
return { nodes, edges, width: totalWidth, height: totalHeight };
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Split children into right and left groups, balancing by subtree height.
|
|
543
|
-
* Preserves source order on each side. Alternating assignment to the
|
|
544
|
-
* lighter side keeps both sides visually balanced.
|
|
545
|
-
*/
|
|
546
|
-
function balancedSplitS(
|
|
547
|
-
children: readonly MindmapNode[],
|
|
548
|
-
hideDescriptions: boolean,
|
|
549
|
-
sc: ScaledConstants
|
|
550
|
-
): { right: MindmapNode[]; left: MindmapNode[] } {
|
|
551
|
-
if (children.length <= 1) {
|
|
552
|
-
return { right: [...children], left: [] };
|
|
553
|
-
}
|
|
554
|
-
if (children.length === 2) {
|
|
555
|
-
return { right: [children[0]!], left: [children[1]!] };
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const weights = children.map((c, i) => ({
|
|
559
|
-
index: i,
|
|
560
|
-
node: c,
|
|
561
|
-
height: computeSubtreeHeightS(c, 1, hideDescriptions, sc),
|
|
562
|
-
}));
|
|
563
|
-
|
|
564
|
-
const right: MindmapNode[] = [];
|
|
565
|
-
const left: MindmapNode[] = [];
|
|
566
|
-
let rightWeight = 0;
|
|
567
|
-
let leftWeight = 0;
|
|
568
|
-
|
|
569
|
-
for (const w of weights) {
|
|
570
|
-
if (rightWeight <= leftWeight) {
|
|
571
|
-
right.push(w.node);
|
|
572
|
-
rightWeight += w.height;
|
|
573
|
-
} else {
|
|
574
|
-
left.push(w.node);
|
|
575
|
-
leftWeight += w.height;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
return { right, left };
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
/** Offset all coordinates in an SVG path by (dx, dy) */
|
|
583
|
-
function offsetPath(path: string, dx: number, dy: number): string {
|
|
584
|
-
if (dx === 0 && dy === 0) return path;
|
|
585
|
-
return path.replace(
|
|
586
|
-
/([ML])\s*([\d.e+-]+)\s+([\d.e+-]+)/g,
|
|
587
|
-
(_, cmd, x, y) => `${cmd} ${parseFloat(x) + dx} ${parseFloat(y) + dy}`
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// ============================================================
|
|
592
|
-
// Node sizing
|
|
593
|
-
// ============================================================
|
|
594
|
-
|
|
595
|
-
function nodeWidthS(depth: number, sc: ScaledConstants): number {
|
|
596
|
-
if (depth === 0) return sc.rootWidth;
|
|
597
|
-
if (depth === 1) return sc.depth1Width;
|
|
598
|
-
return sc.leafWidth;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function nodeHeightS(
|
|
602
|
-
node: MindmapNode,
|
|
603
|
-
hideDescriptions: boolean,
|
|
604
|
-
sc: ScaledConstants
|
|
605
|
-
): number {
|
|
606
|
-
const depth = getNodeDepth(node);
|
|
607
|
-
const w = nodeWidthS(depth, sc);
|
|
608
|
-
const text = computeNodeText(
|
|
609
|
-
node.label,
|
|
610
|
-
node.description ? [...node.description] : undefined,
|
|
611
|
-
depth,
|
|
612
|
-
w,
|
|
613
|
-
hideDescriptions
|
|
614
|
-
);
|
|
615
|
-
const labelLineCount = text.labelLines.length;
|
|
616
|
-
const labelH =
|
|
617
|
-
labelLineCount <= 1
|
|
618
|
-
? SINGLE_LABEL_HEIGHT
|
|
619
|
-
: LABEL_LINE_HEIGHT * labelLineCount;
|
|
620
|
-
let h = labelH + NODE_V_PAD;
|
|
621
|
-
if (text.descLines.length > 0) {
|
|
622
|
-
h += DESC_LINE_HEIGHT * text.descLines.length + 4;
|
|
623
|
-
}
|
|
624
|
-
return h;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
/** Walk parentId chain to compute depth. Cached via roots traversal isn't needed — trees are small. */
|
|
628
|
-
function getNodeDepth(node: MindmapNode): number {
|
|
629
|
-
// The node structure doesn't carry depth directly, but we can
|
|
630
|
-
// infer from the layout context. For nodeHeight we need depth
|
|
631
|
-
// for font sizing. Use a simple heuristic: walk up parentId.
|
|
632
|
-
// Since MindmapNode doesn't have a parent reference (only parentId),
|
|
633
|
-
// and we don't have the node map here, we use a depth cache.
|
|
634
|
-
return nodeDepthCache.get(node.id) ?? 0;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const nodeDepthCache = new Map<string, number>();
|
|
638
|
-
|
|
639
|
-
/** Populate depth cache by walking the tree. Call before layout. */
|
|
640
|
-
function populateDepthCache(roots: readonly MindmapNode[]): void {
|
|
641
|
-
nodeDepthCache.clear();
|
|
642
|
-
const walk = (nodes: readonly MindmapNode[], depth: number) => {
|
|
643
|
-
for (const n of nodes) {
|
|
644
|
-
nodeDepthCache.set(n.id, depth);
|
|
645
|
-
walk(n.children, depth + 1);
|
|
646
|
-
}
|
|
647
|
-
};
|
|
648
|
-
walk(roots, 0);
|
|
649
|
-
}
|