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
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const libPictProvider = require('pict-provider');
|
|
2
2
|
|
|
3
|
+
// Built-in node types reference theme-aware roles via `ColorRole`. The
|
|
4
|
+
// hex `TitleBarColor` / `BodyStyle` values remain as fallbacks for
|
|
5
|
+
// rendering paths that bypass CSS (legacy consumers, exports). When a
|
|
6
|
+
// role is set, the renderer skips applying those hex attributes — the
|
|
7
|
+
// role's CSS class drives the visuals so theme swaps propagate.
|
|
3
8
|
const _DefaultNodeTypes =
|
|
4
9
|
{
|
|
5
10
|
'default':
|
|
@@ -13,6 +18,7 @@ const _DefaultNodeTypes =
|
|
|
13
18
|
{ Hash: null, Direction: 'input', Side: 'left', Label: 'In' },
|
|
14
19
|
{ Hash: null, Direction: 'output', Side: 'right', Label: 'Out' }
|
|
15
20
|
],
|
|
21
|
+
ColorRole: 'neutral',
|
|
16
22
|
TitleBarColor: '#2c3e50',
|
|
17
23
|
BodyStyle: {}
|
|
18
24
|
},
|
|
@@ -26,6 +32,7 @@ const _DefaultNodeTypes =
|
|
|
26
32
|
[
|
|
27
33
|
{ Hash: null, Direction: 'output', Side: 'right', Label: 'Out' }
|
|
28
34
|
],
|
|
35
|
+
ColorRole: 'success',
|
|
29
36
|
TitleBarColor: '#27ae60',
|
|
30
37
|
BodyStyle:
|
|
31
38
|
{
|
|
@@ -43,6 +50,7 @@ const _DefaultNodeTypes =
|
|
|
43
50
|
[
|
|
44
51
|
{ Hash: null, Direction: 'input', Side: 'left', Label: 'In' }
|
|
45
52
|
],
|
|
53
|
+
ColorRole: 'accent',
|
|
46
54
|
TitleBarColor: '#1abc9c',
|
|
47
55
|
BodyStyle:
|
|
48
56
|
{
|
|
@@ -60,6 +68,7 @@ const _DefaultNodeTypes =
|
|
|
60
68
|
[
|
|
61
69
|
{ Hash: null, Direction: 'input', Side: 'left', Label: 'In' }
|
|
62
70
|
],
|
|
71
|
+
ColorRole: 'error',
|
|
63
72
|
TitleBarColor: '#e74c3c',
|
|
64
73
|
BodyStyle:
|
|
65
74
|
{
|
|
@@ -79,6 +88,7 @@ const _DefaultNodeTypes =
|
|
|
79
88
|
{ Hash: null, Direction: 'output', Side: 'right', Label: 'Yes' },
|
|
80
89
|
{ Hash: null, Direction: 'output', Side: 'bottom', Label: 'No' }
|
|
81
90
|
],
|
|
91
|
+
ColorRole: 'warning',
|
|
82
92
|
TitleBarColor: '#f39c12',
|
|
83
93
|
BodyStyle:
|
|
84
94
|
{
|
|
@@ -50,7 +50,11 @@ class PictProviderFlowPanelChrome extends libFableServiceProviderBase
|
|
|
50
50
|
let tmpPict = this._FlowView.pict || this._FlowView.fable;
|
|
51
51
|
let tmpTitle = pPanelData.Title || 'Properties';
|
|
52
52
|
let tmpChromeHTML = tmpPict.parseTemplateByHash('Flow-PanelChrome-Template',
|
|
53
|
-
{
|
|
53
|
+
{
|
|
54
|
+
Hash: pPanelData.Hash,
|
|
55
|
+
Title: tmpTitle,
|
|
56
|
+
FlowViewIdentifier: this._FlowView.options.ViewIdentifier
|
|
57
|
+
});
|
|
54
58
|
|
|
55
59
|
tmpFO.innerHTML = tmpChromeHTML;
|
|
56
60
|
|
|
@@ -65,22 +69,8 @@ class PictProviderFlowPanelChrome extends libFableServiceProviderBase
|
|
|
65
69
|
tmpCloseIcon.textContent = '\u2715';
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
let tmpContent = tmpFO.querySelector('.pict-flow-panel-content');
|
|
71
|
-
if (tmpContent)
|
|
72
|
-
{
|
|
73
|
-
tmpContent.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
|
|
74
|
-
tmpContent.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Isolate events on the tab bar
|
|
78
|
-
let tmpTabbar = tmpFO.querySelector('.pict-flow-panel-tabbar');
|
|
79
|
-
if (tmpTabbar)
|
|
80
|
-
{
|
|
81
|
-
tmpTabbar.addEventListener('pointerdown', (pEvent) => { pEvent.stopPropagation(); });
|
|
82
|
-
tmpTabbar.addEventListener('wheel', (pEvent) => { pEvent.stopPropagation(); });
|
|
83
|
-
}
|
|
72
|
+
// Pointer/wheel isolation lives on the inline onpointerdown/onwheel
|
|
73
|
+
// attributes in Flow-PanelChrome-Template — see PictView-Flow.js.
|
|
84
74
|
|
|
85
75
|
pPanelsLayer.appendChild(tmpFO);
|
|
86
76
|
|
|
@@ -219,7 +219,7 @@ class PictProviderFlowTheme extends libFableServiceProviderBase
|
|
|
219
219
|
.pict-flow-toolbar-btn {
|
|
220
220
|
background-color: rgba(255,255,255,0.05);
|
|
221
221
|
border-color: rgba(255,255,255,0.2);
|
|
222
|
-
color: #ffffff;
|
|
222
|
+
color: var(--theme-color-background-panel, #ffffff);
|
|
223
223
|
}
|
|
224
224
|
.pict-flow-toolbar-btn:hover {
|
|
225
225
|
background-color: rgba(255,255,255,0.1);
|
|
@@ -458,16 +458,16 @@ class PictProviderFlowTheme extends libFableServiceProviderBase
|
|
|
458
458
|
.pict-flow-toolbar {
|
|
459
459
|
background-color: #c0c0c0;
|
|
460
460
|
border-bottom: 2px solid #808080;
|
|
461
|
-
border-top: 1px solid #ffffff;
|
|
461
|
+
border-top: 1px solid var(--theme-color-background-panel, #ffffff);
|
|
462
462
|
}
|
|
463
463
|
.pict-flow-toolbar-btn {
|
|
464
464
|
background-color: #c0c0c0;
|
|
465
465
|
border: 2px outset #c0c0c0;
|
|
466
466
|
border-radius: 0;
|
|
467
|
-
color: #000000;
|
|
467
|
+
color: var(--theme-color-text-primary, #000000);
|
|
468
468
|
}
|
|
469
469
|
.pict-flow-toolbar-btn:hover {
|
|
470
|
-
background-color: #d0d0d0;
|
|
470
|
+
background-color: var(--theme-color-border-default, #d0d0d0);
|
|
471
471
|
}
|
|
472
472
|
.pict-flow-toolbar-btn:active {
|
|
473
473
|
border-style: inset;
|
|
@@ -544,10 +544,10 @@ class PictProviderFlowTheme extends libFableServiceProviderBase
|
|
|
544
544
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
|
545
545
|
}
|
|
546
546
|
/* Node-type bracket colors — each type gets its own bracket color */
|
|
547
|
-
.pict-flow-node-start .pict-flow-node-bracket { stroke: #27ae60; }
|
|
547
|
+
.pict-flow-node-start .pict-flow-node-bracket { stroke: var(--theme-color-status-success, #27ae60); }
|
|
548
548
|
.pict-flow-node-end .pict-flow-node-bracket { stroke: #1abc9c; }
|
|
549
|
-
.pict-flow-node-halt .pict-flow-node-bracket { stroke: #e74c3c; }
|
|
550
|
-
.pict-flow-node-decision .pict-flow-node-bracket { stroke: #f39c12; }
|
|
549
|
+
.pict-flow-node-halt .pict-flow-node-bracket { stroke: var(--theme-color-status-error, #e74c3c); }
|
|
550
|
+
.pict-flow-node-decision .pict-flow-node-bracket { stroke: var(--theme-color-status-warning, #f39c12); }
|
|
551
551
|
.pict-flow-node-default .pict-flow-node-bracket { stroke: #3498db; }
|
|
552
552
|
.pict-flow-node-action .pict-flow-node-bracket { stroke: #2c3e50; }
|
|
553
553
|
/* Override variant rules: no fills/strokes on body rects in whiteboard */
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-Bezier
|
|
3
|
+
*
|
|
4
|
+
* Default edge theme: smooth cubic bezier between source and target ports,
|
|
5
|
+
* with departure/approach geometry derived from each port's `Side`. This
|
|
6
|
+
* is a thin wrapper around the renderer's pre-existing bezier path
|
|
7
|
+
* generator — selecting it is equivalent to the historical default.
|
|
8
|
+
*
|
|
9
|
+
* Honors per-connection multi-handle waypoints (BezierHandles) when
|
|
10
|
+
* present so user-edited curves still survive.
|
|
11
|
+
*/
|
|
12
|
+
module.exports =
|
|
13
|
+
{
|
|
14
|
+
Name: 'Bezier',
|
|
15
|
+
Label: 'Bezier (smooth)',
|
|
16
|
+
Description: 'Smooth cubic curves with side-aware departures.',
|
|
17
|
+
|
|
18
|
+
GeneratePath: function (pContext)
|
|
19
|
+
{
|
|
20
|
+
let tmpHelpers = pContext.Helpers;
|
|
21
|
+
let tmpSrc = pContext.Source;
|
|
22
|
+
let tmpTgt = pContext.Target;
|
|
23
|
+
let tmpData = (pContext.Connection && pContext.Connection.Data) || {};
|
|
24
|
+
|
|
25
|
+
// Preserve user-customized multi-handle bezier waypoints.
|
|
26
|
+
if (tmpData.HandleCustomized)
|
|
27
|
+
{
|
|
28
|
+
let tmpHandles = tmpHelpers.getBezierHandles(tmpData);
|
|
29
|
+
if (tmpHandles.length > 0)
|
|
30
|
+
{
|
|
31
|
+
return tmpHelpers.generateMultiBezier(tmpSrc, tmpTgt, tmpHandles);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return tmpHelpers.generateBezier(tmpSrc, tmpTgt);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
DefaultParameters: {},
|
|
39
|
+
|
|
40
|
+
ParameterSchema: {}
|
|
41
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-Orthogonal
|
|
3
|
+
*
|
|
4
|
+
* Right-angle (Manhattan) routing between source and target. Reads
|
|
5
|
+
* cleanly for layered DAGs and grid-shaped layouts. Per-connection
|
|
6
|
+
* `OrthoMidOffset` and customized corner overrides are preserved.
|
|
7
|
+
*/
|
|
8
|
+
module.exports =
|
|
9
|
+
{
|
|
10
|
+
Name: 'Orthogonal',
|
|
11
|
+
Label: 'Orthogonal (right-angle)',
|
|
12
|
+
Description: 'Right-angle routing — reads well for DAGs and grids.',
|
|
13
|
+
|
|
14
|
+
GeneratePath: function (pContext)
|
|
15
|
+
{
|
|
16
|
+
let tmpHelpers = pContext.Helpers;
|
|
17
|
+
let tmpSrc = pContext.Source;
|
|
18
|
+
let tmpTgt = pContext.Target;
|
|
19
|
+
let tmpData = (pContext.Connection && pContext.Connection.Data) || {};
|
|
20
|
+
|
|
21
|
+
let tmpCorners = null;
|
|
22
|
+
if (tmpData.HandleCustomized && tmpData.OrthoCorner1X != null)
|
|
23
|
+
{
|
|
24
|
+
tmpCorners =
|
|
25
|
+
{
|
|
26
|
+
corner1: { x: tmpData.OrthoCorner1X, y: tmpData.OrthoCorner1Y },
|
|
27
|
+
corner2: { x: tmpData.OrthoCorner2X, y: tmpData.OrthoCorner2Y }
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return tmpHelpers.generateOrthogonal(tmpSrc, tmpTgt, tmpCorners, tmpData.OrthoMidOffset || 0);
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
DefaultParameters: {},
|
|
35
|
+
|
|
36
|
+
ParameterSchema: {}
|
|
37
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const libCoerce = require('../layouts/Layout-Coerce.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edge-OrthogonalSnap
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates the "edge theme can affect node placement" axis. Same
|
|
7
|
+
* right-angle routing as Edge-Orthogonal, but with an `AdjustLayout`
|
|
8
|
+
* pass that snaps every node's X/Y to a grid so corner segments line
|
|
9
|
+
* up cleanly across the diagram.
|
|
10
|
+
*
|
|
11
|
+
* Trade-off: nodes shift slightly from the layout's natural positions
|
|
12
|
+
* to gain visual alignment. The user opts into this by picking the
|
|
13
|
+
* theme — the layout algorithm itself is unaware.
|
|
14
|
+
*/
|
|
15
|
+
module.exports =
|
|
16
|
+
{
|
|
17
|
+
Name: 'OrthogonalSnap',
|
|
18
|
+
Label: 'Orthogonal (snap to grid)',
|
|
19
|
+
Description: 'Right-angle routing; snaps node positions to a grid for cleaner corner alignment. Demonstrates an edge theme that adjusts node placement.',
|
|
20
|
+
|
|
21
|
+
GeneratePath: function (pContext)
|
|
22
|
+
{
|
|
23
|
+
let tmpHelpers = pContext.Helpers;
|
|
24
|
+
let tmpSrc = pContext.Source;
|
|
25
|
+
let tmpTgt = pContext.Target;
|
|
26
|
+
let tmpData = (pContext.Connection && pContext.Connection.Data) || {};
|
|
27
|
+
|
|
28
|
+
let tmpCorners = null;
|
|
29
|
+
if (tmpData.HandleCustomized && tmpData.OrthoCorner1X != null)
|
|
30
|
+
{
|
|
31
|
+
tmpCorners =
|
|
32
|
+
{
|
|
33
|
+
corner1: { x: tmpData.OrthoCorner1X, y: tmpData.OrthoCorner1Y },
|
|
34
|
+
corner2: { x: tmpData.OrthoCorner2X, y: tmpData.OrthoCorner2Y }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return tmpHelpers.generateOrthogonal(tmpSrc, tmpTgt, tmpCorners, tmpData.OrthoMidOffset || 0);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Snap each node's (X, Y) to the configured grid after the layout
|
|
43
|
+
* has run. Mutates pNodes in place.
|
|
44
|
+
*
|
|
45
|
+
* @param {Array} pNodes
|
|
46
|
+
* @param {Array} pConnections
|
|
47
|
+
* @param {Object} pParameters
|
|
48
|
+
*/
|
|
49
|
+
AdjustLayout: function (pNodes, pConnections, pParameters)
|
|
50
|
+
{
|
|
51
|
+
let tmpGridSize = libCoerce.toFloat((pParameters || {}).GridSize, 20);
|
|
52
|
+
if (tmpGridSize <= 0) return;
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < pNodes.length; i++)
|
|
55
|
+
{
|
|
56
|
+
let tmpNode = pNodes[i];
|
|
57
|
+
if (typeof tmpNode.X !== 'number' || typeof tmpNode.Y !== 'number') continue;
|
|
58
|
+
tmpNode.X = Math.round(tmpNode.X / tmpGridSize) * tmpGridSize;
|
|
59
|
+
tmpNode.Y = Math.round(tmpNode.Y / tmpGridSize) * tmpGridSize;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
DefaultParameters:
|
|
64
|
+
{
|
|
65
|
+
GridSize: 20
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
ParameterSchema:
|
|
69
|
+
{
|
|
70
|
+
GridSize: { Type: 'PreciseNumber', Label: 'Grid size', Default: 20, Min: 1, Max: 200 }
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const libPerimeterMath = require('./Edge-PerimeterMath.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edge-Perimeter-Linear
|
|
5
|
+
*
|
|
6
|
+
* Perimeter attachment + literal straight line between the two
|
|
7
|
+
* resolved exit points. Reads cleanly for force-directed and circular
|
|
8
|
+
* layouts where the topology already determines edge angles, and the
|
|
9
|
+
* curves of bezier add visual noise.
|
|
10
|
+
*/
|
|
11
|
+
module.exports =
|
|
12
|
+
{
|
|
13
|
+
Name: 'Perimeter-Linear',
|
|
14
|
+
Label: 'Perimeter (linear)',
|
|
15
|
+
Description: 'Each connection exits the node at the perimeter, then runs as a straight line to the other end.',
|
|
16
|
+
|
|
17
|
+
GeneratePath: function (pContext)
|
|
18
|
+
{
|
|
19
|
+
let tmpS = pContext.Source;
|
|
20
|
+
let tmpT = pContext.Target;
|
|
21
|
+
return `M ${tmpS.x} ${tmpS.y} L ${tmpT.x} ${tmpT.y}`;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
ResolveAttachment: function (pContext)
|
|
25
|
+
{
|
|
26
|
+
return libPerimeterMath.resolvePerimeterAttachment(pContext);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
DefaultParameters: {},
|
|
30
|
+
ParameterSchema: {}
|
|
31
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const libPerimeterMath = require('./Edge-PerimeterMath.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edge-Perimeter-Orthogonal
|
|
5
|
+
*
|
|
6
|
+
* Perimeter attachment + right-angle (Manhattan) routing between
|
|
7
|
+
* the two resolved exit points. Best when the layout already has
|
|
8
|
+
* grid-like structure (Grid, Mesh) and you want connections to
|
|
9
|
+
* leave from whichever side faces the partner.
|
|
10
|
+
*/
|
|
11
|
+
module.exports =
|
|
12
|
+
{
|
|
13
|
+
Name: 'Perimeter-Orthogonal',
|
|
14
|
+
Label: 'Perimeter (orthogonal)',
|
|
15
|
+
Description: 'Each connection exits the node at the perimeter, then routes via right-angle segments to the other end.',
|
|
16
|
+
|
|
17
|
+
GeneratePath: function (pContext)
|
|
18
|
+
{
|
|
19
|
+
let tmpData = (pContext.Connection && pContext.Connection.Data) || {};
|
|
20
|
+
let tmpCorners = null;
|
|
21
|
+
if (tmpData.HandleCustomized && tmpData.OrthoCorner1X != null)
|
|
22
|
+
{
|
|
23
|
+
tmpCorners =
|
|
24
|
+
{
|
|
25
|
+
corner1: { x: tmpData.OrthoCorner1X, y: tmpData.OrthoCorner1Y },
|
|
26
|
+
corner2: { x: tmpData.OrthoCorner2X, y: tmpData.OrthoCorner2Y }
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return pContext.Helpers.generateOrthogonal(pContext.Source, pContext.Target, tmpCorners, tmpData.OrthoMidOffset || 0);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
ResolveAttachment: function (pContext)
|
|
33
|
+
{
|
|
34
|
+
return libPerimeterMath.resolvePerimeterAttachment(pContext);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
DefaultParameters: {},
|
|
38
|
+
ParameterSchema: {}
|
|
39
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const libPerimeterMath = require('./Edge-PerimeterMath.js');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Edge-Perimeter (Bezier)
|
|
5
|
+
*
|
|
6
|
+
* "Smart exit" routing with smooth bezier curves. Each connection
|
|
7
|
+
* exits the node at the perimeter point closest to its target —
|
|
8
|
+
* solves star/hub topologies where many connections share one port.
|
|
9
|
+
*
|
|
10
|
+
* Renders the path as a side-aware bezier (uses the side returned by
|
|
11
|
+
* `ResolveAttachment` so the curve departs in the right direction).
|
|
12
|
+
*
|
|
13
|
+
* See `Edge-Perimeter-Linear` and `Edge-Perimeter-Orthogonal` for the
|
|
14
|
+
* straight-line and right-angle variants.
|
|
15
|
+
*/
|
|
16
|
+
module.exports =
|
|
17
|
+
{
|
|
18
|
+
Name: 'Perimeter',
|
|
19
|
+
Label: 'Perimeter (bezier)',
|
|
20
|
+
Description: 'Each connection exits the node at the perimeter point closest to its target, with smooth bezier curves. Fixes star/hub topologies where many lines share one port.',
|
|
21
|
+
|
|
22
|
+
GeneratePath: function (pContext)
|
|
23
|
+
{
|
|
24
|
+
// Honor user-edited bezier handles when the connection is selected
|
|
25
|
+
// and dragged. Without this, the drag handles render but moving
|
|
26
|
+
// them does nothing because Perimeter would always re-emit a
|
|
27
|
+
// fresh side-aware bezier.
|
|
28
|
+
let tmpHelpers = pContext.Helpers;
|
|
29
|
+
let tmpData = (pContext.Connection && pContext.Connection.Data) || {};
|
|
30
|
+
if (tmpData.HandleCustomized)
|
|
31
|
+
{
|
|
32
|
+
let tmpHandles = tmpHelpers.getBezierHandles(tmpData);
|
|
33
|
+
if (tmpHandles.length > 0)
|
|
34
|
+
{
|
|
35
|
+
return tmpHelpers.generateMultiBezier(pContext.Source, pContext.Target, tmpHandles);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return tmpHelpers.generateBezier(pContext.Source, pContext.Target);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
ResolveAttachment: function (pContext)
|
|
42
|
+
{
|
|
43
|
+
return libPerimeterMath.resolvePerimeterAttachment(pContext);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
DefaultParameters: {},
|
|
47
|
+
ParameterSchema: {}
|
|
48
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-PerimeterMath
|
|
3
|
+
*
|
|
4
|
+
* Shared geometry helper for the Perimeter family of edge themes
|
|
5
|
+
* (Perimeter, Perimeter-Linear, Perimeter-Orthogonal). Solves the
|
|
6
|
+
* "where on this node's bounding box should the line attach so it
|
|
7
|
+
* points at the other end" question.
|
|
8
|
+
*
|
|
9
|
+
* The themes themselves only differ in how they render the path
|
|
10
|
+
* between the two attachment points; the attachment math is identical.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a perimeter attachment point for one end of a connection.
|
|
15
|
+
* Traces a line from the node's center toward the other end and returns
|
|
16
|
+
* the first crossing of the node's bounding-box perimeter, plus the
|
|
17
|
+
* `side` of that crossing (so the bezier helper departs in the right
|
|
18
|
+
* direction).
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} pContext - the same shape the renderer passes into
|
|
21
|
+
* ResolveAttachment (Node, OtherNode, OtherDefaultPosition,
|
|
22
|
+
* DefaultPosition, …).
|
|
23
|
+
* @returns {{x: number, y: number, side: string}|Object|null}
|
|
24
|
+
* Returns null when node geometry is missing; returns
|
|
25
|
+
* DefaultPosition (verbatim) for degenerate same-center cases.
|
|
26
|
+
*/
|
|
27
|
+
function _resolvePerimeterAttachment(pContext)
|
|
28
|
+
{
|
|
29
|
+
let tmpNode = pContext.Node;
|
|
30
|
+
if (!tmpNode || typeof tmpNode.X !== 'number' || typeof tmpNode.Y !== 'number') return null;
|
|
31
|
+
|
|
32
|
+
let tmpW = tmpNode.Width || 180;
|
|
33
|
+
let tmpH = tmpNode.Height || 80;
|
|
34
|
+
let tmpCx = tmpNode.X + tmpW / 2;
|
|
35
|
+
let tmpCy = tmpNode.Y + tmpH / 2;
|
|
36
|
+
|
|
37
|
+
let tmpAimX, tmpAimY;
|
|
38
|
+
if (pContext.OtherNode && typeof pContext.OtherNode.X === 'number')
|
|
39
|
+
{
|
|
40
|
+
let tmpOW = pContext.OtherNode.Width || 180;
|
|
41
|
+
let tmpOH = pContext.OtherNode.Height || 80;
|
|
42
|
+
tmpAimX = pContext.OtherNode.X + tmpOW / 2;
|
|
43
|
+
tmpAimY = pContext.OtherNode.Y + tmpOH / 2;
|
|
44
|
+
}
|
|
45
|
+
else if (pContext.OtherDefaultPosition)
|
|
46
|
+
{
|
|
47
|
+
tmpAimX = pContext.OtherDefaultPosition.x;
|
|
48
|
+
tmpAimY = pContext.OtherDefaultPosition.y;
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
{
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let tmpDX = tmpAimX - tmpCx;
|
|
56
|
+
let tmpDY = tmpAimY - tmpCy;
|
|
57
|
+
|
|
58
|
+
if (tmpDX === 0 && tmpDY === 0) return pContext.DefaultPosition || null;
|
|
59
|
+
|
|
60
|
+
let tmpHalfW = tmpW / 2;
|
|
61
|
+
let tmpHalfH = tmpH / 2;
|
|
62
|
+
|
|
63
|
+
let tmpTX = (tmpDX > 0) ? tmpHalfW / tmpDX
|
|
64
|
+
: (tmpDX < 0) ? -tmpHalfW / tmpDX
|
|
65
|
+
: Infinity;
|
|
66
|
+
let tmpTY = (tmpDY > 0) ? tmpHalfH / tmpDY
|
|
67
|
+
: (tmpDY < 0) ? -tmpHalfH / tmpDY
|
|
68
|
+
: Infinity;
|
|
69
|
+
|
|
70
|
+
let tmpT, tmpSide;
|
|
71
|
+
if (tmpTX < tmpTY)
|
|
72
|
+
{
|
|
73
|
+
tmpT = tmpTX;
|
|
74
|
+
tmpSide = (tmpDX > 0) ? 'right' : 'left';
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
{
|
|
78
|
+
tmpT = tmpTY;
|
|
79
|
+
tmpSide = (tmpDY > 0) ? 'bottom' : 'top';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
x: tmpCx + tmpT * tmpDX,
|
|
84
|
+
y: tmpCy + tmpT * tmpDY,
|
|
85
|
+
side: tmpSide
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports =
|
|
90
|
+
{
|
|
91
|
+
resolvePerimeterAttachment: _resolvePerimeterAttachment
|
|
92
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge-Straight
|
|
3
|
+
*
|
|
4
|
+
* Literal straight line from source port to target port. Useful for
|
|
5
|
+
* dense clusters where curves add visual noise, and for force-directed
|
|
6
|
+
* layouts where the topology already determines edge angles.
|
|
7
|
+
*/
|
|
8
|
+
module.exports =
|
|
9
|
+
{
|
|
10
|
+
Name: 'Straight',
|
|
11
|
+
Label: 'Straight line',
|
|
12
|
+
Description: 'Plain straight line between ports.',
|
|
13
|
+
|
|
14
|
+
GeneratePath: function (pContext)
|
|
15
|
+
{
|
|
16
|
+
let tmpSrc = pContext.Source;
|
|
17
|
+
let tmpTgt = pContext.Target;
|
|
18
|
+
return `M ${tmpSrc.x} ${tmpSrc.y} L ${tmpTgt.x} ${tmpTgt.y}`;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
DefaultParameters: {},
|
|
22
|
+
|
|
23
|
+
ParameterSchema: {}
|
|
24
|
+
};
|