pict-section-flow 0.0.16 → 0.0.18
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/README.md +18 -18
- package/docs/Architecture.md +1 -1
- package/docs/Data_Model.md +2 -2
- package/docs/Getting_Started.md +5 -5
- package/docs/Implementation_Reference.md +6 -6
- package/docs/Layout_Persistence.md +3 -3
- package/docs/README.md +12 -12
- package/docs/_cover.md +1 -1
- package/docs/_sidebar.md +6 -6
- package/docs/_version.json +7 -0
- package/docs/api/PictFlowCard.md +6 -6
- package/docs/api/PictFlowCardPropertiesPanel.md +2 -2
- package/docs/api/addConnection.md +4 -4
- package/docs/api/addNode.md +6 -6
- package/docs/api/autoLayout.md +2 -2
- package/docs/api/getFlowData.md +5 -5
- package/docs/api/marshalToView.md +3 -3
- package/docs/api/openPanel.md +2 -2
- package/docs/api/registerHandler.md +3 -3
- package/docs/api/registerNodeType.md +3 -3
- package/docs/api/removeConnection.md +5 -5
- package/docs/api/removeNode.md +6 -6
- package/docs/api/saveLayout.md +2 -2
- package/docs/api/screenToSVGCoords.md +2 -2
- package/docs/api/selectNode.md +3 -3
- package/docs/api/setTheme.md +2 -2
- package/docs/api/setZoom.md +3 -3
- package/docs/api/toggleFullscreen.md +2 -2
- package/docs/card-help/EACH.md +3 -3
- package/docs/card-help/FREAD.md +5 -5
- package/docs/card-help/FWRITE.md +5 -5
- package/docs/card-help/GET.md +2 -2
- package/docs/card-help/ITE.md +3 -3
- package/docs/card-help/LOG.md +4 -4
- package/docs/card-help/NOTE.md +1 -1
- package/docs/card-help/PREV.md +2 -2
- package/docs/card-help/SET.md +5 -5
- package/docs/card-help/SPKL.md +2 -2
- package/docs/card-help/STAT.md +3 -3
- package/docs/card-help/SW.md +4 -4
- package/docs/css/docuserve.css +277 -23
- package/docs/index.html +2 -2
- package/docs/retold-catalog.json +1 -1
- package/docs/retold-keyword-index.json +1 -1
- package/example_applications/simple_cards/css/flowexample.css +2 -2
- package/example_applications/simple_cards/source/card-help-content.js +12 -12
- package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +1 -1
- package/example_applications/simple_cards/source/sample-flows.js +410 -0
- package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +5 -5
- package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +5 -5
- package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +4 -4
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +141 -8
- package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +2 -2
- package/package.json +3 -2
- package/source/Pict-Section-Flow.js +26 -0
- package/source/providers/PictProvider-Flow-CSS.js +244 -14
- package/source/providers/PictProvider-Flow-Theme.js +7 -7
- package/source/providers/edges/Edge-Bezier.js +41 -0
- package/source/providers/edges/Edge-Orthogonal.js +37 -0
- package/source/providers/edges/Edge-OrthogonalSnap.js +72 -0
- package/source/providers/edges/Edge-Perimeter-Linear.js +31 -0
- package/source/providers/edges/Edge-Perimeter-Orthogonal.js +39 -0
- package/source/providers/edges/Edge-Perimeter.js +48 -0
- package/source/providers/edges/Edge-PerimeterMath.js +92 -0
- package/source/providers/edges/Edge-Straight.js +24 -0
- package/source/providers/layouts/Layout-Circular.js +203 -0
- package/source/providers/layouts/Layout-Coerce.js +40 -0
- package/source/providers/layouts/Layout-Columnar.js +134 -0
- package/source/providers/layouts/Layout-Custom.js +27 -0
- package/source/providers/layouts/Layout-ForcedFromCenter.js +256 -0
- package/source/providers/layouts/Layout-Grid.js +134 -0
- package/source/providers/layouts/Layout-Layered.js +209 -0
- package/source/providers/layouts/Layout-Tabular.js +94 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +532 -28
- package/source/services/PictService-Flow-DataManager.js +12 -1
- package/source/services/PictService-Flow-Layout.js +305 -121
- package/source/services/PictService-Flow-PortRenderer.js +122 -26
- package/source/services/PictService-Flow-RenderManager.js +41 -11
- package/source/views/PictView-Flow-FloatingToolbar.js +3 -3
- package/source/views/PictView-Flow-Node.js +28 -0
- package/source/views/PictView-Flow-Toolbar.js +715 -10
- package/source/views/PictView-Flow.js +272 -5
- package/test/Layout_tests.js +1400 -0
- package/test/PortRenderer_tests.js +11 -2
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
2
2
|
|
|
3
|
+
const libLayoutCustom = require('../providers/layouts/Layout-Custom.js');
|
|
4
|
+
const libLayoutLayered = require('../providers/layouts/Layout-Layered.js');
|
|
5
|
+
const libLayoutForcedFromCenter = require('../providers/layouts/Layout-ForcedFromCenter.js');
|
|
6
|
+
const libLayoutGrid = require('../providers/layouts/Layout-Grid.js');
|
|
7
|
+
const libLayoutCircular = require('../providers/layouts/Layout-Circular.js');
|
|
8
|
+
const libLayoutTabular = require('../providers/layouts/Layout-Tabular.js');
|
|
9
|
+
const libLayoutColumnar = require('../providers/layouts/Layout-Columnar.js');
|
|
10
|
+
|
|
11
|
+
const libEdgeBezier = require('../providers/edges/Edge-Bezier.js');
|
|
12
|
+
const libEdgeOrthogonal = require('../providers/edges/Edge-Orthogonal.js');
|
|
13
|
+
const libEdgeStraight = require('../providers/edges/Edge-Straight.js');
|
|
14
|
+
const libEdgeOrthogonalSnap = require('../providers/edges/Edge-OrthogonalSnap.js');
|
|
15
|
+
const libEdgePerimeter = require('../providers/edges/Edge-Perimeter.js');
|
|
16
|
+
const libEdgePerimeterLinear = require('../providers/edges/Edge-Perimeter-Linear.js');
|
|
17
|
+
const libEdgePerimeterOrthogonal = require('../providers/edges/Edge-Perimeter-Orthogonal.js');
|
|
18
|
+
|
|
19
|
+
const _BUILTIN_ALGORITHMS =
|
|
20
|
+
[
|
|
21
|
+
libLayoutCustom,
|
|
22
|
+
libLayoutLayered,
|
|
23
|
+
libLayoutForcedFromCenter,
|
|
24
|
+
libLayoutGrid,
|
|
25
|
+
libLayoutCircular,
|
|
26
|
+
libLayoutTabular,
|
|
27
|
+
libLayoutColumnar
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const _BUILTIN_EDGE_THEMES =
|
|
31
|
+
[
|
|
32
|
+
libEdgeBezier,
|
|
33
|
+
libEdgeOrthogonal,
|
|
34
|
+
libEdgeStraight,
|
|
35
|
+
libEdgeOrthogonalSnap,
|
|
36
|
+
libEdgePerimeter,
|
|
37
|
+
libEdgePerimeterLinear,
|
|
38
|
+
libEdgePerimeterOrthogonal
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const _LEGACY_ALGORITHM = 'Layered';
|
|
42
|
+
const _DEFAULT_EDGE_THEME = 'Bezier';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* PictServiceFlowLayout
|
|
46
|
+
*
|
|
47
|
+
* Layout-algorithm registry and dispatcher. Holds plain descriptor
|
|
48
|
+
* objects (`{ Name, Label, Apply, DefaultParameters, ParameterSchema }`)
|
|
49
|
+
* for every registered layout algorithm, plus the small helpers
|
|
50
|
+
* `snapToGrid` and `centerNodes` and the special-case `autoLayoutSubset`.
|
|
51
|
+
*
|
|
52
|
+
* Algorithms are pure functions over `(nodes, connections, parameters)`
|
|
53
|
+
* that mutate node X/Y in place. Third parties extend the registry via
|
|
54
|
+
* `registerAlgorithm(pDescriptor)`.
|
|
55
|
+
*
|
|
56
|
+
* Note: `_LayoutProvider` (PictProvider-Flow-Layouts.js) is a different
|
|
57
|
+
* concept — it manages **saved layout snapshots** (named position
|
|
58
|
+
* captures the user can restore later). Don't confuse "layouts" (saved
|
|
59
|
+
* snapshots) with "layout algorithms" (procedural arrangement).
|
|
60
|
+
*/
|
|
3
61
|
class PictServiceFlowLayout extends libFableServiceProviderBase
|
|
4
62
|
{
|
|
5
63
|
constructor(pFable, pOptions, pServiceHash)
|
|
@@ -10,173 +68,301 @@ class PictServiceFlowLayout extends libFableServiceProviderBase
|
|
|
10
68
|
|
|
11
69
|
this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
|
|
12
70
|
|
|
13
|
-
//
|
|
71
|
+
// Legacy spacing fields kept for `autoLayoutSubset` and any
|
|
72
|
+
// external consumer that read them. The Layered algorithm itself
|
|
73
|
+
// pulls spacing from its DefaultParameters.
|
|
14
74
|
this._HorizontalSpacing = 250;
|
|
15
75
|
this._VerticalSpacing = 120;
|
|
16
76
|
this._StartX = 100;
|
|
17
77
|
this._StartY = 100;
|
|
78
|
+
|
|
79
|
+
// Algorithm registry: Name → descriptor
|
|
80
|
+
this._Algorithms = {};
|
|
81
|
+
|
|
82
|
+
// Edge-theme registry: Name → descriptor
|
|
83
|
+
this._EdgeThemes = {};
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < _BUILTIN_ALGORITHMS.length; i++)
|
|
86
|
+
{
|
|
87
|
+
this.registerAlgorithm(_BUILTIN_ALGORITHMS[i]);
|
|
88
|
+
}
|
|
89
|
+
for (let i = 0; i < _BUILTIN_EDGE_THEMES.length; i++)
|
|
90
|
+
{
|
|
91
|
+
this.registerEdgeTheme(_BUILTIN_EDGE_THEMES[i]);
|
|
92
|
+
}
|
|
18
93
|
}
|
|
19
94
|
|
|
95
|
+
// ── Algorithm Registry ────────────────────────────────────────────────
|
|
96
|
+
|
|
20
97
|
/**
|
|
21
|
-
*
|
|
22
|
-
* @param {
|
|
23
|
-
* @
|
|
24
|
-
* @returns {number}
|
|
98
|
+
* Register a layout algorithm.
|
|
99
|
+
* @param {Object} pDescriptor - { Name, Label, Apply, DefaultParameters, ParameterSchema }
|
|
100
|
+
* @returns {boolean} true if registered, false if invalid
|
|
25
101
|
*/
|
|
26
|
-
|
|
102
|
+
registerAlgorithm(pDescriptor)
|
|
27
103
|
{
|
|
28
|
-
if (!
|
|
29
|
-
|
|
104
|
+
if (!pDescriptor || typeof pDescriptor !== 'object')
|
|
105
|
+
{
|
|
106
|
+
this.log.warn('PictServiceFlowLayout registerAlgorithm: descriptor must be an object');
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (typeof pDescriptor.Name !== 'string' || pDescriptor.Name === '')
|
|
110
|
+
{
|
|
111
|
+
this.log.warn('PictServiceFlowLayout registerAlgorithm: descriptor.Name is required');
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (typeof pDescriptor.Apply !== 'function')
|
|
115
|
+
{
|
|
116
|
+
this.log.warn(`PictServiceFlowLayout registerAlgorithm: descriptor.Apply for '${pDescriptor.Name}' must be a function`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this._Algorithms[pDescriptor.Name] = pDescriptor;
|
|
121
|
+
return true;
|
|
30
122
|
}
|
|
31
123
|
|
|
32
124
|
/**
|
|
33
|
-
*
|
|
34
|
-
* @param {
|
|
35
|
-
* @
|
|
125
|
+
* Look up a registered algorithm by name.
|
|
126
|
+
* @param {string} pName
|
|
127
|
+
* @returns {Object|null}
|
|
36
128
|
*/
|
|
37
|
-
|
|
129
|
+
getAlgorithm(pName)
|
|
38
130
|
{
|
|
39
|
-
|
|
131
|
+
return this._Algorithms[pName] || null;
|
|
132
|
+
}
|
|
40
133
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
134
|
+
/**
|
|
135
|
+
* List all registered algorithm names in registration order.
|
|
136
|
+
* @returns {string[]}
|
|
137
|
+
*/
|
|
138
|
+
getAlgorithmNames()
|
|
139
|
+
{
|
|
140
|
+
return Object.keys(this._Algorithms);
|
|
141
|
+
}
|
|
45
142
|
|
|
46
|
-
|
|
143
|
+
/**
|
|
144
|
+
* List all registered algorithm descriptors.
|
|
145
|
+
* @returns {Object[]}
|
|
146
|
+
*/
|
|
147
|
+
listAlgorithms()
|
|
148
|
+
{
|
|
149
|
+
let tmpKeys = Object.keys(this._Algorithms);
|
|
150
|
+
let tmpResult = [];
|
|
151
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
47
152
|
{
|
|
48
|
-
|
|
49
|
-
tmpNodeMap[tmpNode.Hash] = tmpNode;
|
|
50
|
-
tmpInDegree[tmpNode.Hash] = 0;
|
|
51
|
-
tmpOutEdges[tmpNode.Hash] = [];
|
|
153
|
+
tmpResult.push(this._Algorithms[tmpKeys[i]]);
|
|
52
154
|
}
|
|
155
|
+
return tmpResult;
|
|
156
|
+
}
|
|
53
157
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
158
|
+
/**
|
|
159
|
+
* Build the merged parameter set for an algorithm: default values
|
|
160
|
+
* with caller-supplied overrides on top.
|
|
161
|
+
* @param {string} pName
|
|
162
|
+
* @param {Object} [pOverrides]
|
|
163
|
+
* @returns {Object}
|
|
164
|
+
*/
|
|
165
|
+
getMergedParameters(pName, pOverrides)
|
|
166
|
+
{
|
|
167
|
+
let tmpAlgo = this.getAlgorithm(pName);
|
|
168
|
+
if (!tmpAlgo) return Object.assign({}, pOverrides || {});
|
|
169
|
+
return Object.assign({}, tmpAlgo.DefaultParameters || {}, pOverrides || {});
|
|
170
|
+
}
|
|
66
171
|
|
|
67
|
-
|
|
68
|
-
let tmpLayers = [];
|
|
69
|
-
let tmpQueue = [];
|
|
70
|
-
let tmpAssigned = {};
|
|
172
|
+
// ── Edge-Theme Registry ──────────────────────────────────────────────
|
|
71
173
|
|
|
72
|
-
|
|
73
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Register an edge theme.
|
|
176
|
+
* @param {Object} pDescriptor - { Name, Label, Description, GeneratePath, AdjustLayout?, DefaultParameters?, ParameterSchema? }
|
|
177
|
+
* @returns {boolean} true if registered, false if invalid
|
|
178
|
+
*/
|
|
179
|
+
registerEdgeTheme(pDescriptor)
|
|
180
|
+
{
|
|
181
|
+
if (!pDescriptor || typeof pDescriptor !== 'object')
|
|
74
182
|
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
tmpQueue.push(tmpHash);
|
|
78
|
-
}
|
|
183
|
+
this.log.warn('PictServiceFlowLayout registerEdgeTheme: descriptor must be an object');
|
|
184
|
+
return false;
|
|
79
185
|
}
|
|
80
|
-
|
|
81
|
-
while (tmpQueue.length > 0)
|
|
186
|
+
if (typeof pDescriptor.Name !== 'string' || pDescriptor.Name === '')
|
|
82
187
|
{
|
|
83
|
-
|
|
188
|
+
this.log.warn('PictServiceFlowLayout registerEdgeTheme: descriptor.Name is required');
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (typeof pDescriptor.GeneratePath !== 'function')
|
|
192
|
+
{
|
|
193
|
+
this.log.warn(`PictServiceFlowLayout registerEdgeTheme: descriptor.GeneratePath for '${pDescriptor.Name}' must be a function`);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
this._EdgeThemes[pDescriptor.Name] = pDescriptor;
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
84
199
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Look up a registered edge theme by name.
|
|
202
|
+
* @param {string} pName
|
|
203
|
+
* @returns {Object|null}
|
|
204
|
+
*/
|
|
205
|
+
getEdgeTheme(pName)
|
|
206
|
+
{
|
|
207
|
+
return this._EdgeThemes[pName] || null;
|
|
208
|
+
}
|
|
90
209
|
|
|
91
|
-
|
|
92
|
-
|
|
210
|
+
/**
|
|
211
|
+
* List all registered edge-theme names in registration order.
|
|
212
|
+
* @returns {string[]}
|
|
213
|
+
*/
|
|
214
|
+
getEdgeThemeNames()
|
|
215
|
+
{
|
|
216
|
+
return Object.keys(this._EdgeThemes);
|
|
217
|
+
}
|
|
93
218
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
219
|
+
/**
|
|
220
|
+
* List all registered edge-theme descriptors.
|
|
221
|
+
* @returns {Object[]}
|
|
222
|
+
*/
|
|
223
|
+
listEdgeThemes()
|
|
224
|
+
{
|
|
225
|
+
let tmpKeys = Object.keys(this._EdgeThemes);
|
|
226
|
+
let tmpResult = [];
|
|
227
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
228
|
+
{
|
|
229
|
+
tmpResult.push(this._EdgeThemes[tmpKeys[i]]);
|
|
230
|
+
}
|
|
231
|
+
return tmpResult;
|
|
232
|
+
}
|
|
106
233
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Resolve which edge theme should render a given connection. Order:
|
|
236
|
+
* 1. `Connection.Data.EdgeTheme` (per-connection override)
|
|
237
|
+
* 2. Per-connection `Connection.Data.LineMode === 'orthogonal'`
|
|
238
|
+
* → 'Orthogonal' (back-compat for the existing line-mode flag)
|
|
239
|
+
* 3. `_FlowData.EdgeTheme` (flow-level)
|
|
240
|
+
* 4. Active layout's `DefaultEdgeTheme`
|
|
241
|
+
* 5. The hard fallback ('Bezier')
|
|
242
|
+
*
|
|
243
|
+
* Returns null only if even the fallback isn't registered, in which
|
|
244
|
+
* case the renderer should use its built-in path generators.
|
|
245
|
+
*
|
|
246
|
+
* @param {Object} pConnection
|
|
247
|
+
* @returns {Object|null}
|
|
248
|
+
*/
|
|
249
|
+
resolveActiveEdgeTheme(pConnection)
|
|
250
|
+
{
|
|
251
|
+
let tmpData = (pConnection && pConnection.Data) || {};
|
|
252
|
+
let tmpFlowView = this._FlowView;
|
|
253
|
+
let tmpFlowData = tmpFlowView ? tmpFlowView._FlowData : null;
|
|
111
254
|
|
|
112
|
-
|
|
255
|
+
if (tmpData.EdgeTheme)
|
|
256
|
+
{
|
|
257
|
+
let tmpTheme = this.getEdgeTheme(tmpData.EdgeTheme);
|
|
258
|
+
if (tmpTheme) return tmpTheme;
|
|
113
259
|
}
|
|
114
|
-
|
|
115
|
-
// Handle any remaining unassigned nodes (cycles or disconnected)
|
|
116
|
-
let tmpRemainingNodes = [];
|
|
117
|
-
for (let i = 0; i < pNodes.length; i++)
|
|
260
|
+
if (tmpData.LineMode === 'orthogonal' && !tmpData.EdgeTheme)
|
|
118
261
|
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
tmpRemainingNodes.push(pNodes[i].Hash);
|
|
122
|
-
}
|
|
262
|
+
let tmpTheme = this.getEdgeTheme('Orthogonal');
|
|
263
|
+
if (tmpTheme) return tmpTheme;
|
|
123
264
|
}
|
|
124
|
-
if (
|
|
265
|
+
if (tmpFlowData && tmpFlowData.EdgeTheme)
|
|
125
266
|
{
|
|
126
|
-
|
|
267
|
+
let tmpTheme = this.getEdgeTheme(tmpFlowData.EdgeTheme);
|
|
268
|
+
if (tmpTheme) return tmpTheme;
|
|
127
269
|
}
|
|
128
|
-
|
|
129
|
-
// Assign positions based on layers
|
|
130
|
-
let tmpCurrentX = this._StartX;
|
|
131
|
-
|
|
132
|
-
for (let tmpLayerIndex = 0; tmpLayerIndex < tmpLayers.length; tmpLayerIndex++)
|
|
270
|
+
if (tmpFlowData && tmpFlowData.LayoutAlgorithm)
|
|
133
271
|
{
|
|
134
|
-
let
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Calculate the total height for this layer to center vertically
|
|
138
|
-
let tmpTotalHeight = 0;
|
|
139
|
-
for (let i = 0; i < tmpLayer.length; i++)
|
|
272
|
+
let tmpAlgo = this.getAlgorithm(tmpFlowData.LayoutAlgorithm);
|
|
273
|
+
if (tmpAlgo && tmpAlgo.DefaultEdgeTheme)
|
|
140
274
|
{
|
|
141
|
-
let
|
|
142
|
-
if (
|
|
143
|
-
{
|
|
144
|
-
tmpTotalHeight += tmpNode.Height || 80;
|
|
145
|
-
if (i < tmpLayer.length - 1)
|
|
146
|
-
{
|
|
147
|
-
tmpTotalHeight += this._VerticalSpacing;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
275
|
+
let tmpTheme = this.getEdgeTheme(tmpAlgo.DefaultEdgeTheme);
|
|
276
|
+
if (tmpTheme) return tmpTheme;
|
|
150
277
|
}
|
|
278
|
+
}
|
|
279
|
+
return this.getEdgeTheme(_DEFAULT_EDGE_THEME);
|
|
280
|
+
}
|
|
151
281
|
|
|
152
|
-
|
|
282
|
+
/**
|
|
283
|
+
* Build the merged parameter set for an edge theme.
|
|
284
|
+
* @param {string} pName
|
|
285
|
+
* @param {Object} [pOverrides]
|
|
286
|
+
* @returns {Object}
|
|
287
|
+
*/
|
|
288
|
+
getMergedEdgeThemeParameters(pName, pOverrides)
|
|
289
|
+
{
|
|
290
|
+
let tmpTheme = this.getEdgeTheme(pName);
|
|
291
|
+
if (!tmpTheme) return Object.assign({}, pOverrides || {});
|
|
292
|
+
return Object.assign({}, tmpTheme.DefaultParameters || {}, pOverrides || {});
|
|
293
|
+
}
|
|
153
294
|
|
|
154
|
-
|
|
155
|
-
{
|
|
156
|
-
let tmpNode = tmpNodeMap[tmpLayer[i]];
|
|
157
|
-
if (!tmpNode) continue;
|
|
295
|
+
// ── Dispatch ──────────────────────────────────────────────────────────
|
|
158
296
|
|
|
159
|
-
|
|
160
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Apply a registered layout algorithm. Mutates node X/Y in place.
|
|
299
|
+
* @param {Array} pNodes
|
|
300
|
+
* @param {Array} pConnections
|
|
301
|
+
* @param {string} pAlgorithmName
|
|
302
|
+
* @param {Object} [pParameters]
|
|
303
|
+
*/
|
|
304
|
+
applyLayout(pNodes, pConnections, pAlgorithmName, pParameters)
|
|
305
|
+
{
|
|
306
|
+
let tmpAlgo = this.getAlgorithm(pAlgorithmName);
|
|
307
|
+
if (!tmpAlgo)
|
|
308
|
+
{
|
|
309
|
+
this.log.warn(`PictServiceFlowLayout applyLayout: unknown algorithm '${pAlgorithmName}', falling back to '${_LEGACY_ALGORITHM}'`);
|
|
310
|
+
tmpAlgo = this.getAlgorithm(_LEGACY_ALGORITHM);
|
|
311
|
+
if (!tmpAlgo) return;
|
|
312
|
+
}
|
|
313
|
+
let tmpMerged = Object.assign({}, tmpAlgo.DefaultParameters || {}, pParameters || {});
|
|
314
|
+
tmpAlgo.Apply(pNodes, pConnections, tmpMerged);
|
|
315
|
+
}
|
|
161
316
|
|
|
162
|
-
|
|
163
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Auto-layout entry point.
|
|
319
|
+
*
|
|
320
|
+
* - Legacy 2-arg form `autoLayout(nodes, connections)` dispatches to
|
|
321
|
+
* the Layered algorithm and is byte-for-byte identical to the
|
|
322
|
+
* pre-subsystem behavior.
|
|
323
|
+
* - 3- or 4-arg form `autoLayout(nodes, connections, name, params)`
|
|
324
|
+
* dispatches to the named algorithm.
|
|
325
|
+
*
|
|
326
|
+
* @param {Array} pNodes
|
|
327
|
+
* @param {Array} pConnections
|
|
328
|
+
* @param {string} [pAlgorithmName] - defaults to the Layered algorithm
|
|
329
|
+
* @param {Object} [pParameters]
|
|
330
|
+
*/
|
|
331
|
+
autoLayout(pNodes, pConnections, pAlgorithmName, pParameters)
|
|
332
|
+
{
|
|
333
|
+
if (!pNodes || pNodes.length === 0) return;
|
|
334
|
+
let tmpName = (typeof pAlgorithmName === 'string' && pAlgorithmName !== '') ? pAlgorithmName : _LEGACY_ALGORITHM;
|
|
335
|
+
this.applyLayout(pNodes, pConnections, tmpName, pParameters);
|
|
336
|
+
}
|
|
164
337
|
|
|
165
|
-
|
|
166
|
-
tmpCurrentY += tmpHeight + this._VerticalSpacing;
|
|
167
|
-
}
|
|
338
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
168
339
|
|
|
169
|
-
|
|
170
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Snap a coordinate to the nearest grid point.
|
|
342
|
+
* @param {number} pValue
|
|
343
|
+
* @param {number} pGridSize
|
|
344
|
+
* @returns {number}
|
|
345
|
+
*/
|
|
346
|
+
snapToGrid(pValue, pGridSize)
|
|
347
|
+
{
|
|
348
|
+
if (!pGridSize || pGridSize <= 0) return pValue;
|
|
349
|
+
return Math.round(pValue / pGridSize) * pGridSize;
|
|
171
350
|
}
|
|
172
351
|
|
|
173
352
|
/**
|
|
174
353
|
* Auto-layout a subset of nodes, positioning them to the right of
|
|
175
354
|
* any fixed (already-positioned) nodes.
|
|
176
355
|
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
356
|
+
* Used by the saved-layout restore flow (`PictProvider-Flow-Layouts`)
|
|
357
|
+
* to position nodes that exist in the current flow but were absent
|
|
358
|
+
* from the saved snapshot.
|
|
359
|
+
*
|
|
360
|
+
* **Always runs the Layered algorithm**, regardless of the flow's
|
|
361
|
+
* configured `LayoutAlgorithm`. Running ForcedFromCenter or another
|
|
362
|
+
* algorithm on a partial node set would jitter the matched nodes
|
|
363
|
+
* (which are intentionally fixed). The contract here is "place the
|
|
364
|
+
* orphans in a sensible default arrangement," not "obey the user's
|
|
365
|
+
* full-graph algorithm."
|
|
180
366
|
*
|
|
181
367
|
* @param {Array} pNodesToLayout - Nodes that need new positions
|
|
182
368
|
* @param {Array} pFixedNodes - Nodes that already have positions (read-only)
|
|
@@ -203,7 +389,6 @@ class PictServiceFlowLayout extends libFableServiceProviderBase
|
|
|
203
389
|
}
|
|
204
390
|
}
|
|
205
391
|
|
|
206
|
-
// Place unmatched nodes to the right of all fixed nodes
|
|
207
392
|
tmpStartX = tmpMaxX + this._HorizontalSpacing;
|
|
208
393
|
}
|
|
209
394
|
|
|
@@ -227,7 +412,6 @@ class PictServiceFlowLayout extends libFableServiceProviderBase
|
|
|
227
412
|
tmpOutEdges[tmpNode.Hash] = [];
|
|
228
413
|
}
|
|
229
414
|
|
|
230
|
-
// Only count edges between nodes in the subset
|
|
231
415
|
for (let i = 0; i < pConnections.length; i++)
|
|
232
416
|
{
|
|
233
417
|
let tmpConn = pConnections[i];
|
|
@@ -330,7 +514,7 @@ class PictServiceFlowLayout extends libFableServiceProviderBase
|
|
|
330
514
|
}
|
|
331
515
|
|
|
332
516
|
/**
|
|
333
|
-
* Center all nodes around a given point
|
|
517
|
+
* Center all nodes around a given point.
|
|
334
518
|
* @param {Array} pNodes
|
|
335
519
|
* @param {number} pCenterX
|
|
336
520
|
* @param {number} pCenterY
|