pict-section-flow 1.3.0 → 2.0.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.
Files changed (162) hide show
  1. package/package.json +7 -2
  2. package/source/Pict-Section-Flow.js +20 -14
  3. package/source/providers/PictProvider-Flow-Background.js +303 -0
  4. package/source/providers/PictProvider-Flow-CSS.js +99 -7
  5. package/source/providers/PictProvider-Flow-ConnectorShapes.js +8 -0
  6. package/source/providers/PictProvider-Flow-Geometry.js +11 -421
  7. package/source/providers/PictProvider-Flow-Icons.js +20 -0
  8. package/source/providers/PictProvider-Flow-Layouts.js +107 -0
  9. package/source/services/PictService-Flow-ConnectionRenderer.js +77 -5
  10. package/source/services/PictService-Flow-CursorManager.js +113 -0
  11. package/source/services/PictService-Flow-InteractionManager.js +443 -61
  12. package/source/services/PictService-Flow-Layout.js +21 -16
  13. package/source/services/PictService-Flow-PathGenerator.js +30 -417
  14. package/source/services/PictService-Flow-RenderManager.js +9 -1
  15. package/source/services/PictService-Flow-ViewportManager.js +102 -0
  16. package/source/views/PictView-Flow-FloatingToolbar.js +57 -0
  17. package/source/views/PictView-Flow-Node.js +36 -0
  18. package/source/views/PictView-Flow-PropertiesPanel.js +27 -5
  19. package/source/views/PictView-Flow-Toolbar.js +148 -13
  20. package/source/views/PictView-Flow.js +628 -3
  21. package/.claude/launch.json +0 -11
  22. package/docs/.nojekyll +0 -0
  23. package/docs/Architecture.md +0 -163
  24. package/docs/Custom-Styling.md +0 -275
  25. package/docs/Data_Model.md +0 -149
  26. package/docs/Event_System.md +0 -156
  27. package/docs/Getting_Started.md +0 -237
  28. package/docs/Implementation_Reference.md +0 -528
  29. package/docs/Layout_Persistence.md +0 -117
  30. package/docs/README.md +0 -103
  31. package/docs/Theme_Integration.md +0 -150
  32. package/docs/_brand.json +0 -18
  33. package/docs/_cover.md +0 -17
  34. package/docs/_playground.json +0 -24
  35. package/docs/_sidebar.md +0 -57
  36. package/docs/_topbar.md +0 -8
  37. package/docs/_version.json +0 -7
  38. package/docs/api/PictFlowCard.md +0 -216
  39. package/docs/api/PictFlowCardPropertiesPanel.md +0 -235
  40. package/docs/api/addConnection.md +0 -101
  41. package/docs/api/addNode.md +0 -137
  42. package/docs/api/autoLayout.md +0 -77
  43. package/docs/api/getFlowData.md +0 -112
  44. package/docs/api/marshalToView.md +0 -95
  45. package/docs/api/openPanel.md +0 -128
  46. package/docs/api/registerHandler.md +0 -174
  47. package/docs/api/registerNodeType.md +0 -142
  48. package/docs/api/removeConnection.md +0 -57
  49. package/docs/api/removeNode.md +0 -80
  50. package/docs/api/saveLayout.md +0 -152
  51. package/docs/api/screenToSVGCoords.md +0 -68
  52. package/docs/api/selectNode.md +0 -116
  53. package/docs/api/setTheme.md +0 -168
  54. package/docs/api/setZoom.md +0 -97
  55. package/docs/api/toggleFullscreen.md +0 -68
  56. package/docs/card-help/EACH.md +0 -19
  57. package/docs/card-help/FREAD.md +0 -24
  58. package/docs/card-help/FWRITE.md +0 -24
  59. package/docs/card-help/GET.md +0 -22
  60. package/docs/card-help/ITE.md +0 -23
  61. package/docs/card-help/LOG.md +0 -23
  62. package/docs/card-help/NOTE.md +0 -17
  63. package/docs/card-help/PREV.md +0 -18
  64. package/docs/card-help/SET.md +0 -27
  65. package/docs/card-help/SPKL.md +0 -22
  66. package/docs/card-help/STAT.md +0 -23
  67. package/docs/card-help/SW.md +0 -25
  68. package/docs/diagrams/architecture-at-a-glance.excalidraw +0 -4270
  69. package/docs/diagrams/architecture-at-a-glance.mmd +0 -30
  70. package/docs/diagrams/architecture-at-a-glance.svg +0 -2
  71. package/docs/diagrams/data-flow.excalidraw +0 -1451
  72. package/docs/diagrams/data-flow.mmd +0 -17
  73. package/docs/diagrams/data-flow.svg +0 -2
  74. package/docs/diagrams/high-level-design.excalidraw +0 -5767
  75. package/docs/diagrams/high-level-design.mmd +0 -86
  76. package/docs/diagrams/high-level-design.svg +0 -2
  77. package/docs/diagrams/relationships.excalidraw +0 -3852
  78. package/docs/diagrams/relationships.mmd +0 -9
  79. package/docs/diagrams/relationships.svg +0 -2
  80. package/docs/diagrams/service-initialization-sequence.excalidraw +0 -1466
  81. package/docs/diagrams/service-initialization-sequence.mmd +0 -19
  82. package/docs/diagrams/service-initialization-sequence.svg +0 -2
  83. package/docs/diagrams/svg-layer-structure.excalidraw +0 -1060
  84. package/docs/diagrams/svg-layer-structure.mmd +0 -18
  85. package/docs/diagrams/svg-layer-structure.svg +0 -2
  86. package/docs/examples/README.md +0 -9
  87. package/docs/examples/simple_cards/README.md +0 -677
  88. package/docs/examples/simple_cards/css/flowexample.css +0 -65
  89. package/docs/examples/simple_cards/index.html +0 -32
  90. package/docs/examples/simple_cards/js/pict.min.js +0 -12
  91. package/docs/examples/simple_cards/pict-section-flow-example-simple-cards.compatible.min.js +0 -1
  92. package/docs/index.html +0 -38
  93. package/docs/playground/app.json +0 -6
  94. package/docs/playground/appdata.json +0 -85
  95. package/docs/playground/application.js +0 -23
  96. package/docs/playground/pict.json +0 -17
  97. package/docs/playground/runtime/pict-application.min.js +0 -2
  98. package/docs/playground/runtime/pict-section-flow.min.js +0 -2
  99. package/docs/playground/runtime/pict-section-modal.min.js +0 -2
  100. package/docs/playground/runtime/pict.min.js +0 -12
  101. package/docs/retold-catalog.json +0 -244
  102. package/docs/retold-keyword-index.json +0 -26028
  103. package/example_applications/simple_cards/css/flowexample.css +0 -65
  104. package/example_applications/simple_cards/html/index.html +0 -32
  105. package/example_applications/simple_cards/package.json +0 -52
  106. package/example_applications/simple_cards/source/Pict-Application-FlowExample-Configuration.json +0 -15
  107. package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +0 -539
  108. package/example_applications/simple_cards/source/card-help-content.js +0 -16
  109. package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +0 -38
  110. package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +0 -44
  111. package/example_applications/simple_cards/source/cards/FlowCard-Each.js +0 -38
  112. package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +0 -56
  113. package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +0 -50
  114. package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +0 -37
  115. package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +0 -49
  116. package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +0 -55
  117. package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +0 -97
  118. package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +0 -100
  119. package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +0 -46
  120. package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +0 -39
  121. package/example_applications/simple_cards/source/providers/PictRouter-FlowExample-Configuration.json +0 -22
  122. package/example_applications/simple_cards/source/sample-flows.js +0 -410
  123. package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +0 -184
  124. package/example_applications/simple_cards/source/views/PictView-FlowExample-BottomBar.js +0 -77
  125. package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +0 -325
  126. package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +0 -59
  127. package/example_applications/simple_cards/source/views/PictView-FlowExample-Layout.js +0 -90
  128. package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +0 -453
  129. package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +0 -95
  130. package/scripts/generate-card-help.js +0 -214
  131. package/source/providers/edges/Edge-Bezier.js +0 -41
  132. package/source/providers/edges/Edge-Orthogonal.js +0 -37
  133. package/source/providers/edges/Edge-OrthogonalSnap.js +0 -72
  134. package/source/providers/edges/Edge-Perimeter-Linear.js +0 -31
  135. package/source/providers/edges/Edge-Perimeter-Orthogonal.js +0 -39
  136. package/source/providers/edges/Edge-Perimeter.js +0 -48
  137. package/source/providers/edges/Edge-PerimeterMath.js +0 -92
  138. package/source/providers/edges/Edge-Straight.js +0 -24
  139. package/source/providers/layouts/Layout-Circular.js +0 -203
  140. package/source/providers/layouts/Layout-Coerce.js +0 -40
  141. package/source/providers/layouts/Layout-Columnar.js +0 -134
  142. package/source/providers/layouts/Layout-Custom.js +0 -27
  143. package/source/providers/layouts/Layout-ForcedFromCenter.js +0 -256
  144. package/source/providers/layouts/Layout-Grid.js +0 -134
  145. package/source/providers/layouts/Layout-Layered.js +0 -155
  146. package/source/providers/layouts/Layout-Rank.js +0 -141
  147. package/source/providers/layouts/Layout-Staggered.js +0 -131
  148. package/source/providers/layouts/Layout-Tabular.js +0 -94
  149. package/test/ConnectionHandleManager_tests.js +0 -717
  150. package/test/ConnectionRenderer_tests.js +0 -591
  151. package/test/DataManager_tests.js +0 -859
  152. package/test/Geometry_tests.js +0 -767
  153. package/test/InteractionManager_tests.js +0 -279
  154. package/test/Layout_tests.js +0 -1604
  155. package/test/NodeView_tests.js +0 -66
  156. package/test/PanelManager_tests.js +0 -172
  157. package/test/PathGenerator_tests.js +0 -978
  158. package/test/PortRenderer_tests.js +0 -376
  159. package/test/RenderManager_tests.js +0 -756
  160. package/test/Renderer_tests.js +0 -133
  161. package/test/SelectionManager_tests.js +0 -185
  162. package/test/StylePresets_tests.js +0 -153
@@ -1,134 +0,0 @@
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
- };
@@ -1,155 +0,0 @@
1
- const libCoerce = require('./Layout-Coerce.js');
2
- const libRank = require('./Layout-Rank.js');
3
-
4
- /**
5
- * Layout-Layered
6
- *
7
- * Cycle-tolerant left-to-right layered layout.
8
- *
9
- * This is the original `autoLayout` behavior of pict-section-flow,
10
- * extracted into a layout-algorithm descriptor. Calling
11
- * `_LayoutService.autoLayout(nodes, connections)` with no algorithm
12
- * argument dispatches here, preserving backwards compatibility.
13
- *
14
- * Numeric parameters are typed `PreciseNumber` (big.js-backed strings)
15
- * so they survive ExpressionParser solver chains without float drift;
16
- * the simulation coerces back to JS floats at entry via Layout-Coerce.
17
- */
18
- module.exports =
19
- {
20
- Name: 'Layered',
21
- Label: 'Layered (Topological)',
22
- Description: 'Left-to-right layers based on connection topology.',
23
- DefaultEdgeTheme: 'Orthogonal',
24
-
25
- Apply: function (pNodes, pConnections, pParameters)
26
- {
27
- if (!pNodes || pNodes.length === 0) return;
28
-
29
- let tmpParams = pParameters || {};
30
- let tmpSpacing = libCoerce.toFloat(tmpParams.Spacing, 1.0);
31
- let tmpHorizontalSpacing = libCoerce.toFloat(tmpParams.HorizontalSpacing, 250) * tmpSpacing;
32
- let tmpVerticalSpacing = libCoerce.toFloat(tmpParams.VerticalSpacing, 120) * tmpSpacing;
33
- let tmpStartX = libCoerce.toFloat(tmpParams.StartX, 100);
34
- let tmpStartY = libCoerce.toFloat(tmpParams.StartY, 100);
35
-
36
- let tmpConnections = Array.isArray(pConnections) ? pConnections : [];
37
-
38
- let tmpNodeMap = {};
39
- for (let i = 0; i < pNodes.length; i++)
40
- {
41
- tmpNodeMap[pNodes[i].Hash] = pNodes[i];
42
- }
43
-
44
- // Rank the nodes into layers, left to right, with cycle breaking. The
45
- // shared ranker keeps back-edged graphs (workflows, state machines) from
46
- // collapsing into one tall column the way plain Kahn's topological sort
47
- // would. See Layout-Rank.js.
48
- let tmpLayers = libRank.toRanks(pNodes, tmpConnections);
49
-
50
- // Measure each layer's stacked height so layers can be centered on one
51
- // shared horizontal axis. Without centering every layer top-aligns at
52
- // StartY, so a one-node layer beside a five-node layer reads as a
53
- // diagonal drift down the page instead of a balanced band.
54
- let tmpLayerHeights = [];
55
- let tmpMaxLayerHeight = 0;
56
- for (let l = 0; l < tmpLayers.length; l++)
57
- {
58
- let tmpHeight = 0;
59
- for (let i = 0; i < tmpLayers[l].length; i++)
60
- {
61
- let tmpNode = tmpNodeMap[tmpLayers[l][i]];
62
- tmpHeight += (tmpNode && tmpNode.Height) ? tmpNode.Height : 80;
63
- if (i > 0) tmpHeight += tmpVerticalSpacing;
64
- }
65
- tmpLayerHeights.push(tmpHeight);
66
- if (tmpHeight > tmpMaxLayerHeight) tmpMaxLayerHeight = tmpHeight;
67
- }
68
-
69
- // Assign positions: one column per layer, each layer vertically centered.
70
- let tmpCurrentX = tmpStartX;
71
-
72
- for (let tmpLayerIndex = 0; tmpLayerIndex < tmpLayers.length; tmpLayerIndex++)
73
- {
74
- let tmpLayer = tmpLayers[tmpLayerIndex];
75
- let tmpMaxWidth = 0;
76
- let tmpCurrentY = tmpStartY + ((tmpMaxLayerHeight - tmpLayerHeights[tmpLayerIndex]) / 2);
77
-
78
- for (let i = 0; i < tmpLayer.length; i++)
79
- {
80
- let tmpNode = tmpNodeMap[tmpLayer[i]];
81
- if (!tmpNode) continue;
82
-
83
- tmpNode.X = tmpCurrentX;
84
- tmpNode.Y = tmpCurrentY;
85
-
86
- let tmpWidth = tmpNode.Width || 180;
87
- let tmpHeight = tmpNode.Height || 80;
88
-
89
- tmpMaxWidth = Math.max(tmpMaxWidth, tmpWidth);
90
- tmpCurrentY += tmpHeight + tmpVerticalSpacing;
91
- }
92
-
93
- tmpCurrentX += tmpMaxWidth + tmpHorizontalSpacing;
94
- }
95
- },
96
-
97
- DefaultParameters:
98
- {
99
- Spacing: 1.0,
100
- HorizontalSpacing: 250,
101
- VerticalSpacing: 120,
102
- StartX: 100,
103
- StartY: 100
104
- },
105
-
106
- ParameterSchema:
107
- {
108
- Spacing: { Type: 'PreciseNumber', Label: 'Spacing (multiplier)', Default: 1.0, Min: 0.1, Max: 5 },
109
- HorizontalSpacing: { Type: 'PreciseNumber', Label: 'Horizontal spacing', Default: 250, Min: 0, Max: 1000 },
110
- VerticalSpacing: { Type: 'PreciseNumber', Label: 'Vertical spacing', Default: 120, Min: 0, Max: 1000 },
111
- StartX: { Type: 'PreciseNumber', Label: 'Start X', Default: 100, Min: -10000, Max: 10000 },
112
- StartY: { Type: 'PreciseNumber', Label: 'Start Y', Default: 100, Min: -10000, Max: 10000 }
113
- },
114
-
115
- // Manyfest descriptor catalog consumed by pict-section-form's metacontroller.
116
- // Descriptor keys are full data paths relative to the marshal destination
117
- // (default `AppData`); the toolbar binds `AppData.PictFlowLayoutEditor.Parameters`
118
- // to the active flow's `_FlowData.LayoutParameters` before injecting.
119
- ParameterManifest:
120
- {
121
- Scope: 'PictFlowLayout-Layered',
122
- Sections:
123
- [
124
- { Name: 'Layered Parameters', Hash: 'PictFlowLayoutSection', Groups: [{ Name: 'Defaults', Hash: 'PictFlowLayoutGroup' }] }
125
- ],
126
- Descriptors:
127
- {
128
- 'PictFlowLayoutEditor.Parameters.Spacing':
129
- {
130
- Name: 'Spacing (multiplier)', Hash: 'Spacing', DataType: 'PreciseNumber', Default: 1.0,
131
- PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 0, Width: 12, Min: 0.1, Max: 5 }
132
- },
133
- 'PictFlowLayoutEditor.Parameters.HorizontalSpacing':
134
- {
135
- Name: 'Horizontal spacing', Hash: 'HorizontalSpacing', DataType: 'PreciseNumber', Default: 250,
136
- PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 1, Width: 6, Min: 0, Max: 1000 }
137
- },
138
- 'PictFlowLayoutEditor.Parameters.VerticalSpacing':
139
- {
140
- Name: 'Vertical spacing', Hash: 'VerticalSpacing', DataType: 'PreciseNumber', Default: 120,
141
- PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 1, Width: 6, Min: 0, Max: 1000 }
142
- },
143
- 'PictFlowLayoutEditor.Parameters.StartX':
144
- {
145
- Name: 'Start X', Hash: 'StartX', DataType: 'PreciseNumber', Default: 100,
146
- PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 2, Width: 6, Min: -10000, Max: 10000 }
147
- },
148
- 'PictFlowLayoutEditor.Parameters.StartY':
149
- {
150
- Name: 'Start Y', Hash: 'StartY', DataType: 'PreciseNumber', Default: 100,
151
- PictForm: { Section: 'PictFlowLayoutSection', Group: 'PictFlowLayoutGroup', Row: 2, Width: 6, Min: -10000, Max: 10000 }
152
- }
153
- }
154
- }
155
- };
@@ -1,141 +0,0 @@
1
- /**
2
- * Layout-Rank
3
- *
4
- * Shared cycle-tolerant topological ranking for the directed layouts
5
- * (Layered, Staggered).
6
- *
7
- * Plain Kahn's topological sort drops every node that participates in a cycle
8
- * into a single trailing rank. Any graph with back-edges (workflows, state
9
- * machines: rejections, retries, "send back") therefore collapses into one
10
- * tall stripe of unranked nodes, which is the historical "auto-layout bunches
11
- * everything together and is useless" behavior.
12
- *
13
- * `toRanks` instead breaks each cycle at its most-resolved node: when a round
14
- * finds nothing dependency-free but nodes remain, it forces the unassigned
15
- * node with the fewest unmet predecessors into the next rank and continues.
16
- * Cyclic graphs then rank left to right the same way a DAG does.
17
- *
18
- * Returns an array of ranks; each rank is an array of node Hashes in stable
19
- * source order. Self-loops are ignored for ranking (they cannot define an
20
- * order). Callers map Hashes back to node objects themselves.
21
- */
22
- function toRanks(pNodes, pConnections)
23
- {
24
- let tmpRanks = [];
25
- if (!pNodes || pNodes.length === 0)
26
- {
27
- return tmpRanks;
28
- }
29
-
30
- let tmpConnections = Array.isArray(pConnections) ? pConnections : [];
31
-
32
- let tmpInDegree = {};
33
- let tmpOutEdges = {};
34
- for (let i = 0; i < pNodes.length; i++)
35
- {
36
- tmpInDegree[pNodes[i].Hash] = 0;
37
- tmpOutEdges[pNodes[i].Hash] = [];
38
- }
39
-
40
- for (let i = 0; i < tmpConnections.length; i++)
41
- {
42
- let tmpConn = tmpConnections[i];
43
- // A self-loop cannot define a rank order; skip it.
44
- if (tmpConn.SourceNodeHash === tmpConn.TargetNodeHash)
45
- {
46
- continue;
47
- }
48
- if (tmpInDegree.hasOwnProperty(tmpConn.TargetNodeHash))
49
- {
50
- tmpInDegree[tmpConn.TargetNodeHash]++;
51
- }
52
- if (tmpOutEdges.hasOwnProperty(tmpConn.SourceNodeHash))
53
- {
54
- tmpOutEdges[tmpConn.SourceNodeHash].push(tmpConn.TargetNodeHash);
55
- }
56
- }
57
-
58
- let tmpAssigned = {};
59
- let tmpAssignedCount = 0;
60
-
61
- while (tmpAssignedCount < pNodes.length)
62
- {
63
- let tmpRank = [];
64
-
65
- for (let i = 0; i < pNodes.length; i++)
66
- {
67
- let tmpHash = pNodes[i].Hash;
68
- if (!tmpAssigned[tmpHash] && tmpInDegree[tmpHash] <= 0)
69
- {
70
- tmpRank.push(tmpHash);
71
- }
72
- }
73
-
74
- if (tmpRank.length === 0)
75
- {
76
- // Cycle break: nothing is dependency-free, so force the unassigned
77
- // node with the fewest remaining predecessors (ties keep source
78
- // order, which keeps the result stable across runs).
79
- let tmpMinDegree = Infinity;
80
- let tmpPick = null;
81
- for (let i = 0; i < pNodes.length; i++)
82
- {
83
- let tmpHash = pNodes[i].Hash;
84
- if (!tmpAssigned[tmpHash] && tmpInDegree[tmpHash] < tmpMinDegree)
85
- {
86
- tmpMinDegree = tmpInDegree[tmpHash];
87
- tmpPick = tmpHash;
88
- }
89
- }
90
- if (tmpPick === null)
91
- {
92
- break; // safety; every node is already assigned
93
- }
94
- tmpRank.push(tmpPick);
95
- }
96
-
97
- // Commit the whole rank, then relax its out-edges so the next round
98
- // sees the successors it just freed.
99
- for (let i = 0; i < tmpRank.length; i++)
100
- {
101
- tmpAssigned[tmpRank[i]] = true;
102
- tmpAssignedCount++;
103
- }
104
- for (let i = 0; i < tmpRank.length; i++)
105
- {
106
- let tmpEdges = tmpOutEdges[tmpRank[i]] || [];
107
- for (let j = 0; j < tmpEdges.length; j++)
108
- {
109
- tmpInDegree[tmpEdges[j]]--;
110
- }
111
- }
112
-
113
- tmpRanks.push(tmpRank);
114
- }
115
-
116
- return tmpRanks;
117
- }
118
-
119
- /**
120
- * Flatten ranks to a single ordered list of node Hashes (rank by rank, source
121
- * order within a rank). Convenience for layouts that walk one sequence.
122
- */
123
- function toOrder(pNodes, pConnections)
124
- {
125
- let tmpRanks = toRanks(pNodes, pConnections);
126
- let tmpOrder = [];
127
- for (let i = 0; i < tmpRanks.length; i++)
128
- {
129
- for (let j = 0; j < tmpRanks[i].length; j++)
130
- {
131
- tmpOrder.push(tmpRanks[i][j]);
132
- }
133
- }
134
- return tmpOrder;
135
- }
136
-
137
- module.exports =
138
- {
139
- toRanks: toRanks,
140
- toOrder: toOrder
141
- };
@@ -1,131 +0,0 @@
1
- const libCoerce = require('./Layout-Coerce.js');
2
- const libRank = require('./Layout-Rank.js');
3
-
4
- /**
5
- * Layout-Staggered
6
- *
7
- * Serpentine "stairstep" layout for directed graphs. It ranks the nodes left
8
- * to right by connection topology (the same cycle-tolerant ranking the Layered
9
- * layout uses), then walks that ordered sequence along a band that steps down a
10
- * few rows and back up, advancing horizontally at every node.
11
- *
12
- * The point is vertical-space efficiency: a long directed chain (a workflow, a
13
- * delivery pipeline, a state machine) laid out purely left to right runs off
14
- * the right edge of the canvas. Folding it into a stairstep band keeps the
15
- * left-to-right reading order while using the height of the viewport, so the
16
- * whole graph frames at a usable zoom.
17
- *
18
- * `Rows` sets how deep the band steps before folding back: 2 is a simple
19
- * zigzag (odd nodes high, even nodes low); 3+ is a deeper stairstep. The row
20
- * index follows a triangle wave so the band descends and then climbs rather
21
- * than snapping back to the top between runs.
22
- *
23
- * Numeric parameters are typed `PreciseNumber` so they survive ExpressionParser
24
- * solver chains; Layout-Coerce converts them back to JS floats at entry.
25
- */
26
- module.exports =
27
- {
28
- Name: 'Staggered',
29
- Label: 'Staggered (Stairstep)',
30
- Description: 'Topological order folded along a serpentine stairstep band.',
31
- DefaultEdgeTheme: 'Perimeter',
32
-
33
- Apply: function (pNodes, pConnections, pParameters)
34
- {
35
- if (!pNodes || pNodes.length === 0) return;
36
-
37
- let tmpParams = pParameters || {};
38
- let tmpSpacing = libCoerce.toFloat(tmpParams.Spacing, 1.0);
39
- let tmpRows = Math.max(1, libCoerce.toInt(tmpParams.Rows, 2));
40
- let tmpColumnSpacing = libCoerce.toFloat(tmpParams.ColumnSpacing, 80) * tmpSpacing;
41
- let tmpRowOffset = libCoerce.toFloat(tmpParams.RowOffset, 150) * tmpSpacing;
42
- let tmpStartX = libCoerce.toFloat(tmpParams.StartX, 80);
43
- let tmpStartY = libCoerce.toFloat(tmpParams.StartY, 80);
44
-
45
- let tmpConnections = Array.isArray(pConnections) ? pConnections : [];
46
-
47
- let tmpNodeMap = {};
48
- for (let i = 0; i < pNodes.length; i++)
49
- {
50
- tmpNodeMap[pNodes[i].Hash] = pNodes[i];
51
- }
52
-
53
- let tmpOrder = libRank.toOrder(pNodes, tmpConnections);
54
-
55
- // A uniform column pitch (the widest node plus the spacing) keeps the
56
- // stairstep diagonal even regardless of individual node widths.
57
- let tmpMaxWidth = 0;
58
- for (let i = 0; i < pNodes.length; i++)
59
- {
60
- tmpMaxWidth = Math.max(tmpMaxWidth, pNodes[i].Width || 180);
61
- }
62
- let tmpColumnPitch = tmpMaxWidth + tmpColumnSpacing;
63
-
64
- // Triangle wave: descend (Rows - 1) steps, then climb (Rows - 1) steps.
65
- let tmpPeriod = (tmpRows > 1) ? (2 * (tmpRows - 1)) : 1;
66
-
67
- for (let i = 0; i < tmpOrder.length; i++)
68
- {
69
- let tmpNode = tmpNodeMap[tmpOrder[i]];
70
- if (!tmpNode) continue;
71
-
72
- let tmpRow;
73
- if (tmpRows <= 1)
74
- {
75
- tmpRow = 0;
76
- }
77
- else
78
- {
79
- let tmpPhase = i % tmpPeriod;
80
- tmpRow = (tmpPhase < tmpRows) ? tmpPhase : (tmpPeriod - tmpPhase);
81
- }
82
-
83
- tmpNode.X = tmpStartX + (i * tmpColumnPitch);
84
- tmpNode.Y = tmpStartY + (tmpRow * tmpRowOffset);
85
- }
86
- },
87
-
88
- DefaultParameters:
89
- {
90
- Spacing: 1.0,
91
- Rows: 2,
92
- ColumnSpacing: 80,
93
- RowOffset: 150,
94
- StartX: 80,
95
- StartY: 80
96
- },
97
-
98
- ParameterSchema:
99
- {
100
- Spacing: { Type: 'PreciseNumber', Label: 'Spacing (multiplier)', Default: 1.0, Min: 0.1, Max: 5 },
101
- Rows: { Type: 'Number', Label: 'Rows', Default: 2, Min: 1, Max: 12 },
102
- ColumnSpacing: { Type: 'PreciseNumber', Label: 'Column spacing', Default: 80, Min: 0, Max: 1000 },
103
- RowOffset: { Type: 'PreciseNumber', Label: 'Row offset', Default: 150, Min: 0, Max: 1000 },
104
- StartX: { Type: 'PreciseNumber', Label: 'Start X', Default: 80, Min: -10000, Max: 10000 },
105
- StartY: { Type: 'PreciseNumber', Label: 'Start Y', Default: 80, Min: -10000, Max: 10000 }
106
- },
107
-
108
- ParameterManifest:
109
- {
110
- Scope: 'PictFlowLayout-Staggered',
111
- Sections:
112
- [
113
- { Name: 'Staggered Parameters', Hash: 'PFLStaggeredSection', Groups: [{ Name: 'Defaults', Hash: 'PFLStaggeredGroup' }] }
114
- ],
115
- Descriptors:
116
- {
117
- 'PictFlowLayoutEditor.Parameters.Spacing':
118
- { Name: 'Spacing (multiplier)', Hash: 'Spacing', DataType: 'PreciseNumber', Default: 1.0, PictForm: { Section: 'PFLStaggeredSection', Group: 'PFLStaggeredGroup', Row: 0, Width: 6, Min: 0.1, Max: 5 } },
119
- 'PictFlowLayoutEditor.Parameters.Rows':
120
- { Name: 'Rows', Hash: 'Rows', DataType: 'Number', Default: 2, PictForm: { Section: 'PFLStaggeredSection', Group: 'PFLStaggeredGroup', Row: 0, Width: 6, Min: 1, Max: 12 } },
121
- 'PictFlowLayoutEditor.Parameters.ColumnSpacing':
122
- { Name: 'Column spacing', Hash: 'ColumnSpacing', DataType: 'PreciseNumber', Default: 80, PictForm: { Section: 'PFLStaggeredSection', Group: 'PFLStaggeredGroup', Row: 1, Width: 6, Min: 0, Max: 1000 } },
123
- 'PictFlowLayoutEditor.Parameters.RowOffset':
124
- { Name: 'Row offset', Hash: 'RowOffset', DataType: 'PreciseNumber', Default: 150, PictForm: { Section: 'PFLStaggeredSection', Group: 'PFLStaggeredGroup', Row: 1, Width: 6, Min: 0, Max: 1000 } },
125
- 'PictFlowLayoutEditor.Parameters.StartX':
126
- { Name: 'Start X', Hash: 'StartX', DataType: 'PreciseNumber', Default: 80, PictForm: { Section: 'PFLStaggeredSection', Group: 'PFLStaggeredGroup', Row: 2, Width: 6, Min: -10000, Max: 10000 } },
127
- 'PictFlowLayoutEditor.Parameters.StartY':
128
- { Name: 'Start Y', Hash: 'StartY', DataType: 'PreciseNumber', Default: 80, PictForm: { Section: 'PFLStaggeredSection', Group: 'PFLStaggeredGroup', Row: 2, Width: 6, Min: -10000, Max: 10000 } }
129
- }
130
- }
131
- };
@@ -1,94 +0,0 @@
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
- };