pict-section-flow 0.0.17 → 0.0.19
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/Theme_Integration.md +150 -0
- package/docs/_cover.md +1 -1
- package/docs/_sidebar.md +7 -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 +8 -7
- package/source/Pict-Section-Flow.js +26 -0
- package/source/providers/PictProvider-Flow-CSS.js +435 -61
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +9 -5
- package/source/providers/PictProvider-Flow-NodeTypes.js +10 -0
- package/source/providers/PictProvider-Flow-PanelChrome.js +7 -17
- 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-InteractionManager.js +39 -42
- package/source/services/PictService-Flow-Layout.js +305 -121
- package/source/services/PictService-Flow-PortRenderer.js +108 -26
- package/source/services/PictService-Flow-RenderManager.js +41 -11
- package/source/views/PictView-Flow-FloatingToolbar.js +69 -61
- package/source/views/PictView-Flow-Node.js +47 -6
- package/source/views/PictView-Flow-PropertiesPanel.js +46 -53
- package/source/views/PictView-Flow-Toolbar.js +1065 -485
- package/source/views/PictView-Flow.js +455 -7
- package/test/Layout_tests.js +1400 -0
- package/test/PortRenderer_tests.js +11 -2
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const libCoerce = require('./Layout-Coerce.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Layout-Grid
|
|
5
|
+
*
|
|
6
|
+
* Auto-arrange nodes in a roughly-square grid. Cell width and height
|
|
7
|
+
* default to the largest node dimensions plus margins. Column count
|
|
8
|
+
* defaults to ceil(sqrt(n)) when `Columns` is 'auto'.
|
|
9
|
+
*
|
|
10
|
+
* Continuous params (margins, cell sizes, origin) are typed
|
|
11
|
+
* `PreciseNumber` so they survive solver chains; the integer column
|
|
12
|
+
* count and the categorical OrderBy stay `Number` / enum.
|
|
13
|
+
*/
|
|
14
|
+
module.exports =
|
|
15
|
+
{
|
|
16
|
+
Name: 'Grid',
|
|
17
|
+
Label: 'Grid',
|
|
18
|
+
Description: 'Auto-arrange in a roughly-square grid.',
|
|
19
|
+
DefaultEdgeTheme: 'Orthogonal',
|
|
20
|
+
|
|
21
|
+
Apply: function (pNodes, pConnections, pParameters)
|
|
22
|
+
{
|
|
23
|
+
if (!pNodes || pNodes.length === 0) return;
|
|
24
|
+
|
|
25
|
+
let tmpParams = pParameters || {};
|
|
26
|
+
let tmpSpacing = libCoerce.toFloat(tmpParams.Spacing, 1.0);
|
|
27
|
+
let tmpColumnsParam = tmpParams.Columns;
|
|
28
|
+
let tmpCellWidthParam = tmpParams.CellWidth;
|
|
29
|
+
let tmpCellHeightParam = tmpParams.CellHeight;
|
|
30
|
+
let tmpHorizontalMargin = libCoerce.toFloat(tmpParams.HorizontalMargin, 40) * tmpSpacing;
|
|
31
|
+
let tmpVerticalMargin = libCoerce.toFloat(tmpParams.VerticalMargin, 40) * tmpSpacing;
|
|
32
|
+
let tmpStartX = libCoerce.toFloat(tmpParams.StartX, 100);
|
|
33
|
+
let tmpStartY = libCoerce.toFloat(tmpParams.StartY, 100);
|
|
34
|
+
let tmpOrderBy = tmpParams.OrderBy || 'index';
|
|
35
|
+
|
|
36
|
+
let tmpColumns;
|
|
37
|
+
if (tmpColumnsParam === 'auto' || tmpColumnsParam == null || tmpColumnsParam === '')
|
|
38
|
+
{
|
|
39
|
+
tmpColumns = Math.max(1, Math.ceil(Math.sqrt(pNodes.length)));
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
{
|
|
43
|
+
tmpColumns = Math.max(1, libCoerce.toInt(tmpColumnsParam, Math.max(1, Math.ceil(Math.sqrt(pNodes.length)))));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Compute cell dimensions from largest node if not specified
|
|
47
|
+
let tmpMaxWidth = 0;
|
|
48
|
+
let tmpMaxHeight = 0;
|
|
49
|
+
for (let i = 0; i < pNodes.length; i++)
|
|
50
|
+
{
|
|
51
|
+
tmpMaxWidth = Math.max(tmpMaxWidth, pNodes[i].Width || 180);
|
|
52
|
+
tmpMaxHeight = Math.max(tmpMaxHeight, pNodes[i].Height || 80);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let tmpCellWidth = (tmpCellWidthParam == null) ? tmpMaxWidth + tmpHorizontalMargin : libCoerce.toFloat(tmpCellWidthParam, tmpMaxWidth + tmpHorizontalMargin);
|
|
56
|
+
let tmpCellHeight = (tmpCellHeightParam == null) ? tmpMaxHeight + tmpVerticalMargin : libCoerce.toFloat(tmpCellHeightParam, tmpMaxHeight + tmpVerticalMargin);
|
|
57
|
+
|
|
58
|
+
// Ordered iteration
|
|
59
|
+
let tmpOrdered = pNodes.slice();
|
|
60
|
+
if (tmpOrderBy === 'hash')
|
|
61
|
+
{
|
|
62
|
+
tmpOrdered.sort((pA, pB) => String(pA.Hash).localeCompare(String(pB.Hash)));
|
|
63
|
+
}
|
|
64
|
+
else if (tmpOrderBy === 'title')
|
|
65
|
+
{
|
|
66
|
+
tmpOrdered.sort((pA, pB) => String(pA.Title || pA.Hash).localeCompare(String(pB.Title || pB.Hash)));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < tmpOrdered.length; i++)
|
|
70
|
+
{
|
|
71
|
+
let tmpRow = Math.floor(i / tmpColumns);
|
|
72
|
+
let tmpCol = i % tmpColumns;
|
|
73
|
+
tmpOrdered[i].X = tmpStartX + tmpCol * tmpCellWidth;
|
|
74
|
+
tmpOrdered[i].Y = tmpStartY + tmpRow * tmpCellHeight;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
DefaultParameters:
|
|
79
|
+
{
|
|
80
|
+
Spacing: 1.0,
|
|
81
|
+
Columns: 'auto',
|
|
82
|
+
HorizontalMargin: 40,
|
|
83
|
+
VerticalMargin: 40,
|
|
84
|
+
StartX: 100,
|
|
85
|
+
StartY: 100,
|
|
86
|
+
OrderBy: 'index'
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
ParameterSchema:
|
|
90
|
+
{
|
|
91
|
+
Spacing: { Type: 'PreciseNumber', Label: 'Spacing (multiplier)', Default: 1.0, Min: 0.1, Max: 5 },
|
|
92
|
+
Columns: { Type: 'string', Label: 'Columns', Default: 'auto', Description: '"auto" or an integer' },
|
|
93
|
+
CellWidth: { Type: 'PreciseNumber', Label: 'Cell width', Description: 'Defaults to largest node width + horizontal margin', Min: 1, Max: 5000 },
|
|
94
|
+
CellHeight: { Type: 'PreciseNumber', Label: 'Cell height', Description: 'Defaults to largest node height + vertical margin', Min: 1, Max: 5000 },
|
|
95
|
+
HorizontalMargin: { Type: 'PreciseNumber', Label: 'Horizontal margin', Default: 40, Min: 0, Max: 1000 },
|
|
96
|
+
VerticalMargin: { Type: 'PreciseNumber', Label: 'Vertical margin', Default: 40, Min: 0, Max: 1000 },
|
|
97
|
+
StartX: { Type: 'PreciseNumber', Label: 'Start X', Default: 100, Min: -10000, Max: 10000 },
|
|
98
|
+
StartY: { Type: 'PreciseNumber', Label: 'Start Y', Default: 100, Min: -10000, Max: 10000 },
|
|
99
|
+
OrderBy: { Type: 'enum', Label: 'Order by', Default: 'index', Options: ['index', 'hash', 'title'] }
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
ParameterManifest:
|
|
103
|
+
{
|
|
104
|
+
Scope: 'PictFlowLayout-Grid',
|
|
105
|
+
Sections:
|
|
106
|
+
[
|
|
107
|
+
{ Name: 'Grid Parameters', Hash: 'PFLGridSection', Groups: [{ Name: 'Defaults', Hash: 'PFLGridGroup' }] }
|
|
108
|
+
],
|
|
109
|
+
Descriptors:
|
|
110
|
+
{
|
|
111
|
+
'PictFlowLayoutEditor.Parameters.Spacing':
|
|
112
|
+
{ Name: 'Spacing (multiplier)', Hash: 'Spacing', DataType: 'PreciseNumber', Default: 1.0, PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 0, Width: 12, Min: 0.1, Max: 5 } },
|
|
113
|
+
'PictFlowLayoutEditor.Parameters.Columns':
|
|
114
|
+
{ Name: 'Columns ("auto" or integer)', Hash: 'Columns', DataType: 'String', Default: 'auto', PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 1, Width: 6 } },
|
|
115
|
+
'PictFlowLayoutEditor.Parameters.OrderBy':
|
|
116
|
+
{
|
|
117
|
+
Name: 'Order by', Hash: 'OrderBy', DataType: 'String', Default: 'index',
|
|
118
|
+
PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 1, Width: 6, InputType: 'Option', SelectOptions: [{ Value: 'index', Name: 'Index' }, { Value: 'hash', Name: 'Hash' }, { Value: 'title', Name: 'Title' }] }
|
|
119
|
+
},
|
|
120
|
+
'PictFlowLayoutEditor.Parameters.CellWidth':
|
|
121
|
+
{ Name: 'Cell width (auto = blank)', Hash: 'CellWidth', DataType: 'PreciseNumber', PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 2, Width: 6, Min: 1, Max: 5000 } },
|
|
122
|
+
'PictFlowLayoutEditor.Parameters.CellHeight':
|
|
123
|
+
{ Name: 'Cell height (auto = blank)', Hash: 'CellHeight', DataType: 'PreciseNumber', PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 2, Width: 6, Min: 1, Max: 5000 } },
|
|
124
|
+
'PictFlowLayoutEditor.Parameters.HorizontalMargin':
|
|
125
|
+
{ Name: 'Horizontal margin', Hash: 'HorizontalMargin', DataType: 'PreciseNumber', Default: 40, PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 3, Width: 6, Min: 0, Max: 1000 } },
|
|
126
|
+
'PictFlowLayoutEditor.Parameters.VerticalMargin':
|
|
127
|
+
{ Name: 'Vertical margin', Hash: 'VerticalMargin', DataType: 'PreciseNumber', Default: 40, PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 3, Width: 6, Min: 0, Max: 1000 } },
|
|
128
|
+
'PictFlowLayoutEditor.Parameters.StartX':
|
|
129
|
+
{ Name: 'Start X', Hash: 'StartX', DataType: 'PreciseNumber', Default: 100, PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 4, Width: 6, Min: -10000, Max: 10000 } },
|
|
130
|
+
'PictFlowLayoutEditor.Parameters.StartY':
|
|
131
|
+
{ Name: 'Start Y', Hash: 'StartY', DataType: 'PreciseNumber', Default: 100, PictForm: { Section: 'PFLGridSection', Group: 'PFLGridGroup', Row: 4, Width: 6, Min: -10000, Max: 10000 } }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
const libCoerce = require('./Layout-Coerce.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Layout-Layered
|
|
5
|
+
*
|
|
6
|
+
* Topological-sort (Kahn's algorithm) left-to-right layered layout.
|
|
7
|
+
*
|
|
8
|
+
* This is the original `autoLayout` behavior of pict-section-flow,
|
|
9
|
+
* extracted into a layout-algorithm descriptor. Calling
|
|
10
|
+
* `_LayoutService.autoLayout(nodes, connections)` with no algorithm
|
|
11
|
+
* argument dispatches here, preserving backwards compatibility.
|
|
12
|
+
*
|
|
13
|
+
* Numeric parameters are typed `PreciseNumber` (big.js-backed strings)
|
|
14
|
+
* so they survive ExpressionParser solver chains without float drift;
|
|
15
|
+
* the simulation coerces back to JS floats at entry via Layout-Coerce.
|
|
16
|
+
*/
|
|
17
|
+
module.exports =
|
|
18
|
+
{
|
|
19
|
+
Name: 'Layered',
|
|
20
|
+
Label: 'Layered (Topological)',
|
|
21
|
+
Description: 'Left-to-right layers based on connection topology.',
|
|
22
|
+
DefaultEdgeTheme: 'Orthogonal',
|
|
23
|
+
|
|
24
|
+
Apply: function (pNodes, pConnections, pParameters)
|
|
25
|
+
{
|
|
26
|
+
if (!pNodes || pNodes.length === 0) return;
|
|
27
|
+
|
|
28
|
+
let tmpParams = pParameters || {};
|
|
29
|
+
let tmpSpacing = libCoerce.toFloat(tmpParams.Spacing, 1.0);
|
|
30
|
+
let tmpHorizontalSpacing = libCoerce.toFloat(tmpParams.HorizontalSpacing, 250) * tmpSpacing;
|
|
31
|
+
let tmpVerticalSpacing = libCoerce.toFloat(tmpParams.VerticalSpacing, 120) * tmpSpacing;
|
|
32
|
+
let tmpStartX = libCoerce.toFloat(tmpParams.StartX, 100);
|
|
33
|
+
let tmpStartY = libCoerce.toFloat(tmpParams.StartY, 100);
|
|
34
|
+
|
|
35
|
+
let tmpConnections = Array.isArray(pConnections) ? pConnections : [];
|
|
36
|
+
|
|
37
|
+
// Build adjacency information
|
|
38
|
+
let tmpNodeMap = {};
|
|
39
|
+
let tmpInDegree = {};
|
|
40
|
+
let tmpOutEdges = {};
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < pNodes.length; i++)
|
|
43
|
+
{
|
|
44
|
+
let tmpNode = pNodes[i];
|
|
45
|
+
tmpNodeMap[tmpNode.Hash] = tmpNode;
|
|
46
|
+
tmpInDegree[tmpNode.Hash] = 0;
|
|
47
|
+
tmpOutEdges[tmpNode.Hash] = [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < tmpConnections.length; i++)
|
|
51
|
+
{
|
|
52
|
+
let tmpConn = tmpConnections[i];
|
|
53
|
+
if (tmpInDegree.hasOwnProperty(tmpConn.TargetNodeHash))
|
|
54
|
+
{
|
|
55
|
+
tmpInDegree[tmpConn.TargetNodeHash]++;
|
|
56
|
+
}
|
|
57
|
+
if (tmpOutEdges.hasOwnProperty(tmpConn.SourceNodeHash))
|
|
58
|
+
{
|
|
59
|
+
tmpOutEdges[tmpConn.SourceNodeHash].push(tmpConn.TargetNodeHash);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Topological sort (Kahn's algorithm)
|
|
64
|
+
let tmpLayers = [];
|
|
65
|
+
let tmpQueue = [];
|
|
66
|
+
let tmpAssigned = {};
|
|
67
|
+
|
|
68
|
+
for (let tmpHash in tmpInDegree)
|
|
69
|
+
{
|
|
70
|
+
if (tmpInDegree[tmpHash] === 0)
|
|
71
|
+
{
|
|
72
|
+
tmpQueue.push(tmpHash);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
while (tmpQueue.length > 0)
|
|
77
|
+
{
|
|
78
|
+
let tmpCurrentLayer = [];
|
|
79
|
+
let tmpNextQueue = [];
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < tmpQueue.length; i++)
|
|
82
|
+
{
|
|
83
|
+
let tmpNodeHash = tmpQueue[i];
|
|
84
|
+
if (tmpAssigned[tmpNodeHash]) continue;
|
|
85
|
+
|
|
86
|
+
tmpAssigned[tmpNodeHash] = true;
|
|
87
|
+
tmpCurrentLayer.push(tmpNodeHash);
|
|
88
|
+
|
|
89
|
+
let tmpEdges = tmpOutEdges[tmpNodeHash] || [];
|
|
90
|
+
for (let j = 0; j < tmpEdges.length; j++)
|
|
91
|
+
{
|
|
92
|
+
let tmpTargetHash = tmpEdges[j];
|
|
93
|
+
tmpInDegree[tmpTargetHash]--;
|
|
94
|
+
if (tmpInDegree[tmpTargetHash] <= 0 && !tmpAssigned[tmpTargetHash])
|
|
95
|
+
{
|
|
96
|
+
tmpNextQueue.push(tmpTargetHash);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (tmpCurrentLayer.length > 0)
|
|
102
|
+
{
|
|
103
|
+
tmpLayers.push(tmpCurrentLayer);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
tmpQueue = tmpNextQueue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Handle cycles or disconnected nodes
|
|
110
|
+
let tmpRemainingNodes = [];
|
|
111
|
+
for (let i = 0; i < pNodes.length; i++)
|
|
112
|
+
{
|
|
113
|
+
if (!tmpAssigned[pNodes[i].Hash])
|
|
114
|
+
{
|
|
115
|
+
tmpRemainingNodes.push(pNodes[i].Hash);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (tmpRemainingNodes.length > 0)
|
|
119
|
+
{
|
|
120
|
+
tmpLayers.push(tmpRemainingNodes);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Assign positions based on layers
|
|
124
|
+
let tmpCurrentX = tmpStartX;
|
|
125
|
+
|
|
126
|
+
for (let tmpLayerIndex = 0; tmpLayerIndex < tmpLayers.length; tmpLayerIndex++)
|
|
127
|
+
{
|
|
128
|
+
let tmpLayer = tmpLayers[tmpLayerIndex];
|
|
129
|
+
let tmpMaxWidth = 0;
|
|
130
|
+
let tmpCurrentY = tmpStartY;
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < tmpLayer.length; i++)
|
|
133
|
+
{
|
|
134
|
+
let tmpNode = tmpNodeMap[tmpLayer[i]];
|
|
135
|
+
if (!tmpNode) continue;
|
|
136
|
+
|
|
137
|
+
tmpNode.X = tmpCurrentX;
|
|
138
|
+
tmpNode.Y = tmpCurrentY;
|
|
139
|
+
|
|
140
|
+
let tmpWidth = tmpNode.Width || 180;
|
|
141
|
+
let tmpHeight = tmpNode.Height || 80;
|
|
142
|
+
|
|
143
|
+
tmpMaxWidth = Math.max(tmpMaxWidth, tmpWidth);
|
|
144
|
+
tmpCurrentY += tmpHeight + tmpVerticalSpacing;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
tmpCurrentX += tmpMaxWidth + tmpHorizontalSpacing;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
DefaultParameters:
|
|
152
|
+
{
|
|
153
|
+
Spacing: 1.0,
|
|
154
|
+
HorizontalSpacing: 250,
|
|
155
|
+
VerticalSpacing: 120,
|
|
156
|
+
StartX: 100,
|
|
157
|
+
StartY: 100
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
ParameterSchema:
|
|
161
|
+
{
|
|
162
|
+
Spacing: { Type: 'PreciseNumber', Label: 'Spacing (multiplier)', Default: 1.0, Min: 0.1, Max: 5 },
|
|
163
|
+
HorizontalSpacing: { Type: 'PreciseNumber', Label: 'Horizontal spacing', Default: 250, Min: 0, Max: 1000 },
|
|
164
|
+
VerticalSpacing: { Type: 'PreciseNumber', Label: 'Vertical spacing', Default: 120, Min: 0, Max: 1000 },
|
|
165
|
+
StartX: { Type: 'PreciseNumber', Label: 'Start X', Default: 100, Min: -10000, Max: 10000 },
|
|
166
|
+
StartY: { Type: 'PreciseNumber', Label: 'Start Y', Default: 100, Min: -10000, Max: 10000 }
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
// Manyfest descriptor catalog consumed by pict-section-form's metacontroller.
|
|
170
|
+
// Descriptor keys are full data paths relative to the marshal destination
|
|
171
|
+
// (default `AppData`); the toolbar binds `AppData.PictFlowLayoutEditor.Parameters`
|
|
172
|
+
// to the active flow's `_FlowData.LayoutParameters` before injecting.
|
|
173
|
+
ParameterManifest:
|
|
174
|
+
{
|
|
175
|
+
Scope: 'PictFlowLayout-Layered',
|
|
176
|
+
Sections:
|
|
177
|
+
[
|
|
178
|
+
{ Name: 'Layered Parameters', Hash: 'PictFlowLayoutSection', Groups: [{ Name: 'Defaults', Hash: 'PictFlowLayoutGroup' }] }
|
|
179
|
+
],
|
|
180
|
+
Descriptors:
|
|
181
|
+
{
|
|
182
|
+
'PictFlowLayoutEditor.Parameters.Spacing':
|
|
183
|
+
{
|
|
184
|
+
Name: 'Spacing (multiplier)', Hash: 'Spacing', DataType: 'PreciseNumber', Default: 1.0,
|
|
185
|
+
PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 0, Width: 12, Min: 0.1, Max: 5 }
|
|
186
|
+
},
|
|
187
|
+
'PictFlowLayoutEditor.Parameters.HorizontalSpacing':
|
|
188
|
+
{
|
|
189
|
+
Name: 'Horizontal spacing', Hash: 'HorizontalSpacing', DataType: 'PreciseNumber', Default: 250,
|
|
190
|
+
PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 1, Width: 6, Min: 0, Max: 1000 }
|
|
191
|
+
},
|
|
192
|
+
'PictFlowLayoutEditor.Parameters.VerticalSpacing':
|
|
193
|
+
{
|
|
194
|
+
Name: 'Vertical spacing', Hash: 'VerticalSpacing', DataType: 'PreciseNumber', Default: 120,
|
|
195
|
+
PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 1, Width: 6, Min: 0, Max: 1000 }
|
|
196
|
+
},
|
|
197
|
+
'PictFlowLayoutEditor.Parameters.StartX':
|
|
198
|
+
{
|
|
199
|
+
Name: 'Start X', Hash: 'StartX', DataType: 'PreciseNumber', Default: 100,
|
|
200
|
+
PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 2, Width: 6, Min: -10000, Max: 10000 }
|
|
201
|
+
},
|
|
202
|
+
'PictFlowLayoutEditor.Parameters.StartY':
|
|
203
|
+
{
|
|
204
|
+
Name: 'Start Y', Hash: 'StartY', DataType: 'PreciseNumber', Default: 100,
|
|
205
|
+
PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 2, Width: 6, Min: -10000, Max: 10000 }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const libCoerce = require('./Layout-Coerce.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Layout-Tabular
|
|
5
|
+
*
|
|
6
|
+
* Vertical single-column flow. Nodes are stacked top-to-bottom with
|
|
7
|
+
* VerticalSpacing between successive rows.
|
|
8
|
+
*/
|
|
9
|
+
module.exports =
|
|
10
|
+
{
|
|
11
|
+
Name: 'Tabular',
|
|
12
|
+
Label: 'Tabular (Top-to-Bottom)',
|
|
13
|
+
Description: 'Single column, top-to-bottom.',
|
|
14
|
+
DefaultEdgeTheme: 'Orthogonal',
|
|
15
|
+
|
|
16
|
+
Apply: function (pNodes, pConnections, pParameters)
|
|
17
|
+
{
|
|
18
|
+
if (!pNodes || pNodes.length === 0) return;
|
|
19
|
+
|
|
20
|
+
let tmpParams = pParameters || {};
|
|
21
|
+
let tmpSpacing = libCoerce.toFloat(tmpParams.Spacing, 1.0);
|
|
22
|
+
let tmpStartX = libCoerce.toFloat(tmpParams.StartX, 100);
|
|
23
|
+
let tmpStartY = libCoerce.toFloat(tmpParams.StartY, 100);
|
|
24
|
+
let tmpVerticalSpacing = libCoerce.toFloat(tmpParams.VerticalSpacing, 40) * tmpSpacing;
|
|
25
|
+
let tmpRowHeightParam = tmpParams.RowHeight;
|
|
26
|
+
let tmpOrderBy = tmpParams.OrderBy || 'index';
|
|
27
|
+
|
|
28
|
+
let tmpOrdered = pNodes.slice();
|
|
29
|
+
if (tmpOrderBy === 'hash')
|
|
30
|
+
{
|
|
31
|
+
tmpOrdered.sort((pA, pB) => String(pA.Hash).localeCompare(String(pB.Hash)));
|
|
32
|
+
}
|
|
33
|
+
else if (tmpOrderBy === 'title')
|
|
34
|
+
{
|
|
35
|
+
tmpOrdered.sort((pA, pB) => String(pA.Title || pA.Hash).localeCompare(String(pB.Title || pB.Hash)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let tmpY = tmpStartY;
|
|
39
|
+
for (let i = 0; i < tmpOrdered.length; i++)
|
|
40
|
+
{
|
|
41
|
+
let tmpNode = tmpOrdered[i];
|
|
42
|
+
tmpNode.X = tmpStartX;
|
|
43
|
+
tmpNode.Y = tmpY;
|
|
44
|
+
let tmpRowHeight = (tmpRowHeightParam == null) ? (tmpNode.Height || 80) : libCoerce.toFloat(tmpRowHeightParam, tmpNode.Height || 80);
|
|
45
|
+
tmpY += tmpRowHeight + tmpVerticalSpacing;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
DefaultParameters:
|
|
50
|
+
{
|
|
51
|
+
Spacing: 1.0,
|
|
52
|
+
StartX: 100,
|
|
53
|
+
StartY: 100,
|
|
54
|
+
VerticalSpacing: 40,
|
|
55
|
+
OrderBy: 'index'
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
ParameterSchema:
|
|
59
|
+
{
|
|
60
|
+
Spacing: { Type: 'PreciseNumber', Label: 'Spacing (multiplier)', Default: 1.0, Min: 0.1, Max: 5 },
|
|
61
|
+
StartX: { Type: 'PreciseNumber', Label: 'Start X', Default: 100, Min: -10000, Max: 10000 },
|
|
62
|
+
StartY: { Type: 'PreciseNumber', Label: 'Start Y', Default: 100, Min: -10000, Max: 10000 },
|
|
63
|
+
VerticalSpacing: { Type: 'PreciseNumber', Label: 'Vertical spacing', Default: 40, Min: 0, Max: 1000 },
|
|
64
|
+
RowHeight: { Type: 'PreciseNumber', Label: 'Row height', Description: 'Defaults to each node\'s own height', Min: 1, Max: 5000 },
|
|
65
|
+
OrderBy: { Type: 'enum', Label: 'Order by', Default: 'index', Options: ['index', 'hash', 'title'] }
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
ParameterManifest:
|
|
69
|
+
{
|
|
70
|
+
Scope: 'PictFlowLayout-Tabular',
|
|
71
|
+
Sections:
|
|
72
|
+
[
|
|
73
|
+
{ Name: 'Tabular Parameters', Hash: 'PFLTabularSection', Groups: [{ Name: 'Defaults', Hash: 'PFLTabularGroup' }] }
|
|
74
|
+
],
|
|
75
|
+
Descriptors:
|
|
76
|
+
{
|
|
77
|
+
'PictFlowLayoutEditor.Parameters.Spacing':
|
|
78
|
+
{ Name: 'Spacing (multiplier)', Hash: 'Spacing', DataType: 'PreciseNumber', Default: 1.0, PictForm: { Section: 'PFLTabularSection', Group: 'PFLTabularGroup', Row: 0, Width: 12, Min: 0.1, Max: 5 } },
|
|
79
|
+
'PictFlowLayoutEditor.Parameters.StartX':
|
|
80
|
+
{ Name: 'Start X', Hash: 'StartX', DataType: 'PreciseNumber', Default: 100, PictForm: { Section: 'PFLTabularSection', Group: 'PFLTabularGroup', Row: 1, Width: 6, Min: -10000, Max: 10000 } },
|
|
81
|
+
'PictFlowLayoutEditor.Parameters.StartY':
|
|
82
|
+
{ Name: 'Start Y', Hash: 'StartY', DataType: 'PreciseNumber', Default: 100, PictForm: { Section: 'PFLTabularSection', Group: 'PFLTabularGroup', Row: 1, Width: 6, Min: -10000, Max: 10000 } },
|
|
83
|
+
'PictFlowLayoutEditor.Parameters.VerticalSpacing':
|
|
84
|
+
{ Name: 'Vertical spacing', Hash: 'VerticalSpacing', DataType: 'PreciseNumber', Default: 40, PictForm: { Section: 'PFLTabularSection', Group: 'PFLTabularGroup', Row: 2, Width: 6, Min: 0, Max: 1000 } },
|
|
85
|
+
'PictFlowLayoutEditor.Parameters.RowHeight':
|
|
86
|
+
{ Name: 'Row height (auto = blank)', Hash: 'RowHeight', DataType: 'PreciseNumber', PictForm: { Section: 'PFLTabularSection', Group: 'PFLTabularGroup', Row: 2, Width: 6, Min: 1, Max: 5000 } },
|
|
87
|
+
'PictFlowLayoutEditor.Parameters.OrderBy':
|
|
88
|
+
{
|
|
89
|
+
Name: 'Order by', Hash: 'OrderBy', DataType: 'String', Default: 'index',
|
|
90
|
+
PictForm: { Section: 'PFLTabularSection', Group: 'PFLTabularGroup', Row: 3, Width: 12, InputType: 'Option', SelectOptions: [{ Value: 'index', Name: 'Index' }, { Value: 'hash', Name: 'Hash' }, { Value: 'title', Name: 'Title' }] }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|