pict-section-flow 0.0.1 → 0.0.2
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/docs/README.md +19 -0
- package/{example_application → example_applications/simple_cards}/html/index.html +2 -2
- package/example_applications/simple_cards/package.json +43 -0
- package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +434 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Each.js +36 -0
- package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +54 -0
- package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +48 -0
- package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +35 -0
- package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +47 -0
- package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +53 -0
- package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +95 -0
- package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +37 -0
- package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +59 -0
- package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-Layout.js +5 -1
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +312 -0
- package/package.json +6 -6
- package/source/Pict-Section-Flow.js +19 -0
- package/source/PictFlowCard.js +207 -0
- package/source/PictFlowCardPropertiesPanel.js +105 -0
- package/source/panels/FlowCardPropertiesPanel-Form.js +174 -0
- package/source/panels/FlowCardPropertiesPanel-Markdown.js +148 -0
- package/source/panels/FlowCardPropertiesPanel-Template.js +88 -0
- package/source/panels/FlowCardPropertiesPanel-View.js +114 -0
- package/source/providers/PictProvider-Flow-EventHandler.js +19 -8
- package/source/providers/PictProvider-Flow-Geometry.js +64 -0
- package/source/providers/PictProvider-Flow-Layouts.js +284 -0
- package/source/providers/PictProvider-Flow-NodeTypes.js +70 -0
- package/source/providers/PictProvider-Flow-PanelChrome.js +72 -0
- package/source/providers/PictProvider-Flow-SVGHelpers.js +30 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +324 -66
- package/source/services/PictService-Flow-InteractionManager.js +399 -75
- package/source/services/PictService-Flow-Layout.js +159 -0
- package/source/services/PictService-Flow-PathGenerator.js +199 -0
- package/source/services/PictService-Flow-Tether.js +544 -0
- package/source/views/PictView-Flow-Node.js +95 -18
- package/source/views/PictView-Flow-PropertiesPanel.js +435 -0
- package/source/views/PictView-Flow-Toolbar.js +491 -5
- package/source/views/PictView-Flow.js +830 -8
- package/example_application/package.json +0 -41
- package/example_application/source/Pict-Application-FlowExample.js +0 -241
- package/example_application/source/views/PictView-FlowExample-MainWorkspace.js +0 -191
- /package/{example_application → example_applications/simple_cards}/css/flowexample.css +0 -0
- /package/{example_application → example_applications/simple_cards}/source/Pict-Application-FlowExample-Configuration.json +0 -0
- /package/{example_application → example_applications/simple_cards}/source/providers/PictRouter-FlowExample-Configuration.json +0 -0
- /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-About.js +0 -0
- /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-BottomBar.js +0 -0
- /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-Documentation.js +0 -0
- /package/{example_application → example_applications/simple_cards}/source/views/PictView-FlowExample-TopBar.js +0 -0
|
@@ -170,6 +170,165 @@ class PictServiceFlowLayout extends libFableServiceProviderBase
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Auto-layout a subset of nodes, positioning them to the right of
|
|
175
|
+
* any fixed (already-positioned) nodes.
|
|
176
|
+
*
|
|
177
|
+
* Uses the same topological sort approach as autoLayout, but only
|
|
178
|
+
* repositions the nodes in pNodesToLayout. The pFixedNodes are used
|
|
179
|
+
* to compute a bounding box that the new layout avoids.
|
|
180
|
+
*
|
|
181
|
+
* @param {Array} pNodesToLayout - Nodes that need new positions
|
|
182
|
+
* @param {Array} pFixedNodes - Nodes that already have positions (read-only)
|
|
183
|
+
* @param {Array} pConnections - All connections in the flow
|
|
184
|
+
*/
|
|
185
|
+
autoLayoutSubset(pNodesToLayout, pFixedNodes, pConnections)
|
|
186
|
+
{
|
|
187
|
+
if (!pNodesToLayout || pNodesToLayout.length === 0) return;
|
|
188
|
+
|
|
189
|
+
// Compute the starting X position to the right of all fixed nodes
|
|
190
|
+
let tmpStartX = this._StartX;
|
|
191
|
+
let tmpStartY = this._StartY;
|
|
192
|
+
|
|
193
|
+
if (pFixedNodes && pFixedNodes.length > 0)
|
|
194
|
+
{
|
|
195
|
+
let tmpMaxX = -Infinity;
|
|
196
|
+
|
|
197
|
+
for (let i = 0; i < pFixedNodes.length; i++)
|
|
198
|
+
{
|
|
199
|
+
let tmpRight = pFixedNodes[i].X + (pFixedNodes[i].Width || 180);
|
|
200
|
+
if (tmpRight > tmpMaxX)
|
|
201
|
+
{
|
|
202
|
+
tmpMaxX = tmpRight;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Place unmatched nodes to the right of all fixed nodes
|
|
207
|
+
tmpStartX = tmpMaxX + this._HorizontalSpacing;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Build a set of nodes we are laying out for quick lookup
|
|
211
|
+
let tmpNodeSet = {};
|
|
212
|
+
for (let i = 0; i < pNodesToLayout.length; i++)
|
|
213
|
+
{
|
|
214
|
+
tmpNodeSet[pNodesToLayout[i].Hash] = true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Build adjacency information only for nodes in the subset
|
|
218
|
+
let tmpNodeMap = {};
|
|
219
|
+
let tmpInDegree = {};
|
|
220
|
+
let tmpOutEdges = {};
|
|
221
|
+
|
|
222
|
+
for (let i = 0; i < pNodesToLayout.length; i++)
|
|
223
|
+
{
|
|
224
|
+
let tmpNode = pNodesToLayout[i];
|
|
225
|
+
tmpNodeMap[tmpNode.Hash] = tmpNode;
|
|
226
|
+
tmpInDegree[tmpNode.Hash] = 0;
|
|
227
|
+
tmpOutEdges[tmpNode.Hash] = [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Only count edges between nodes in the subset
|
|
231
|
+
for (let i = 0; i < pConnections.length; i++)
|
|
232
|
+
{
|
|
233
|
+
let tmpConn = pConnections[i];
|
|
234
|
+
let tmpSourceInSubset = tmpNodeSet[tmpConn.SourceNodeHash];
|
|
235
|
+
let tmpTargetInSubset = tmpNodeSet[tmpConn.TargetNodeHash];
|
|
236
|
+
|
|
237
|
+
if (tmpSourceInSubset && tmpTargetInSubset)
|
|
238
|
+
{
|
|
239
|
+
tmpInDegree[tmpConn.TargetNodeHash]++;
|
|
240
|
+
tmpOutEdges[tmpConn.SourceNodeHash].push(tmpConn.TargetNodeHash);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Topological sort (Kahn's algorithm)
|
|
245
|
+
let tmpLayers = [];
|
|
246
|
+
let tmpQueue = [];
|
|
247
|
+
let tmpAssigned = {};
|
|
248
|
+
|
|
249
|
+
for (let tmpHash in tmpInDegree)
|
|
250
|
+
{
|
|
251
|
+
if (tmpInDegree[tmpHash] === 0)
|
|
252
|
+
{
|
|
253
|
+
tmpQueue.push(tmpHash);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
while (tmpQueue.length > 0)
|
|
258
|
+
{
|
|
259
|
+
let tmpCurrentLayer = [];
|
|
260
|
+
let tmpNextQueue = [];
|
|
261
|
+
|
|
262
|
+
for (let i = 0; i < tmpQueue.length; i++)
|
|
263
|
+
{
|
|
264
|
+
let tmpNodeHash = tmpQueue[i];
|
|
265
|
+
if (tmpAssigned[tmpNodeHash]) continue;
|
|
266
|
+
|
|
267
|
+
tmpAssigned[tmpNodeHash] = true;
|
|
268
|
+
tmpCurrentLayer.push(tmpNodeHash);
|
|
269
|
+
|
|
270
|
+
let tmpEdges = tmpOutEdges[tmpNodeHash] || [];
|
|
271
|
+
for (let j = 0; j < tmpEdges.length; j++)
|
|
272
|
+
{
|
|
273
|
+
let tmpTargetHash = tmpEdges[j];
|
|
274
|
+
tmpInDegree[tmpTargetHash]--;
|
|
275
|
+
if (tmpInDegree[tmpTargetHash] <= 0 && !tmpAssigned[tmpTargetHash])
|
|
276
|
+
{
|
|
277
|
+
tmpNextQueue.push(tmpTargetHash);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (tmpCurrentLayer.length > 0)
|
|
283
|
+
{
|
|
284
|
+
tmpLayers.push(tmpCurrentLayer);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
tmpQueue = tmpNextQueue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Handle remaining unassigned nodes (cycles or disconnected)
|
|
291
|
+
let tmpRemainingNodes = [];
|
|
292
|
+
for (let i = 0; i < pNodesToLayout.length; i++)
|
|
293
|
+
{
|
|
294
|
+
if (!tmpAssigned[pNodesToLayout[i].Hash])
|
|
295
|
+
{
|
|
296
|
+
tmpRemainingNodes.push(pNodesToLayout[i].Hash);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (tmpRemainingNodes.length > 0)
|
|
300
|
+
{
|
|
301
|
+
tmpLayers.push(tmpRemainingNodes);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Assign positions based on layers, starting from tmpStartX
|
|
305
|
+
let tmpCurrentX = tmpStartX;
|
|
306
|
+
|
|
307
|
+
for (let tmpLayerIndex = 0; tmpLayerIndex < tmpLayers.length; tmpLayerIndex++)
|
|
308
|
+
{
|
|
309
|
+
let tmpLayer = tmpLayers[tmpLayerIndex];
|
|
310
|
+
let tmpMaxWidth = 0;
|
|
311
|
+
let tmpCurrentY = tmpStartY;
|
|
312
|
+
|
|
313
|
+
for (let i = 0; i < tmpLayer.length; i++)
|
|
314
|
+
{
|
|
315
|
+
let tmpNode = tmpNodeMap[tmpLayer[i]];
|
|
316
|
+
if (!tmpNode) continue;
|
|
317
|
+
|
|
318
|
+
tmpNode.X = tmpCurrentX;
|
|
319
|
+
tmpNode.Y = tmpCurrentY;
|
|
320
|
+
|
|
321
|
+
let tmpWidth = tmpNode.Width || 180;
|
|
322
|
+
let tmpHeight = tmpNode.Height || 80;
|
|
323
|
+
|
|
324
|
+
tmpMaxWidth = Math.max(tmpMaxWidth, tmpWidth);
|
|
325
|
+
tmpCurrentY += tmpHeight + this._VerticalSpacing;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
tmpCurrentX += tmpMaxWidth + this._HorizontalSpacing;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
173
332
|
/**
|
|
174
333
|
* Center all nodes around a given point
|
|
175
334
|
* @param {Array} pNodes
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PictService-Flow-PathGenerator
|
|
5
|
+
*
|
|
6
|
+
* Centralizes SVG path generation for the flow diagram.
|
|
7
|
+
* Provides shared building blocks used by both the ConnectionRenderer
|
|
8
|
+
* (port-to-port connections) and the TetherService (panel-to-node tethers).
|
|
9
|
+
*
|
|
10
|
+
* Responsibilities:
|
|
11
|
+
* - Departure/approach point calculation from anchors
|
|
12
|
+
* - Auto orthogonal corner computation for right-angle paths
|
|
13
|
+
* - Cubic bezier evaluation at arbitrary parameter t
|
|
14
|
+
* - SVG path string assembly (bezier, split-bezier, orthogonal)
|
|
15
|
+
*/
|
|
16
|
+
class PictServiceFlowPathGenerator extends libFableServiceProviderBase
|
|
17
|
+
{
|
|
18
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
19
|
+
{
|
|
20
|
+
super(pFable, pOptions, pServiceHash);
|
|
21
|
+
|
|
22
|
+
this.serviceType = 'PictServiceFlowPathGenerator';
|
|
23
|
+
|
|
24
|
+
this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---- Departure / Approach Calculation ----
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compute departure and approach points from start/end anchors.
|
|
31
|
+
* The departure point extends outward from the start in its side direction,
|
|
32
|
+
* and the approach point extends outward from the end in its side direction.
|
|
33
|
+
*
|
|
34
|
+
* @param {{x: number, y: number, side: string}} pFrom - Start anchor with side
|
|
35
|
+
* @param {{x: number, y: number, side: string}} pTo - End anchor with side
|
|
36
|
+
* @param {number} pDepartDist - Distance for departure/approach straight segments
|
|
37
|
+
* @returns {{departX: number, departY: number, approachX: number, approachY: number, fromDir: {dx: number, dy: number}, toDir: {dx: number, dy: number}}}
|
|
38
|
+
*/
|
|
39
|
+
computeDepartApproach(pFrom, pTo, pDepartDist)
|
|
40
|
+
{
|
|
41
|
+
let tmpGeometry = this._FlowView._GeometryProvider;
|
|
42
|
+
|
|
43
|
+
let tmpFromDir = tmpGeometry.sideDirection(pFrom.side || 'right');
|
|
44
|
+
let tmpToDir = tmpGeometry.sideDirection(pTo.side || 'left');
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
departX: pFrom.x + tmpFromDir.dx * pDepartDist,
|
|
48
|
+
departY: pFrom.y + tmpFromDir.dy * pDepartDist,
|
|
49
|
+
approachX: pTo.x + tmpToDir.dx * pDepartDist,
|
|
50
|
+
approachY: pTo.y + tmpToDir.dy * pDepartDist,
|
|
51
|
+
fromDir: tmpFromDir,
|
|
52
|
+
toDir: tmpToDir
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---- Orthogonal Corner Calculation ----
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Compute auto orthogonal corners for an L-shaped or Z-shaped path.
|
|
60
|
+
* Determines corner placement based on departure/approach directions.
|
|
61
|
+
*
|
|
62
|
+
* Used by both connection and tether renderers for right-angle paths.
|
|
63
|
+
*
|
|
64
|
+
* @param {number} pDepartX
|
|
65
|
+
* @param {number} pDepartY
|
|
66
|
+
* @param {number} pApproachX
|
|
67
|
+
* @param {number} pApproachY
|
|
68
|
+
* @param {{dx: number, dy: number}} pFromDir - Departure direction vector
|
|
69
|
+
* @param {{dx: number, dy: number}} pToDir - Approach direction vector
|
|
70
|
+
* @param {number} pMidOffset - Offset for the corridor midpoint
|
|
71
|
+
* @returns {{corner1: {x: number, y: number}, corner2: {x: number, y: number}, midpoint: {x: number, y: number}}}
|
|
72
|
+
*/
|
|
73
|
+
computeAutoOrthogonalCorners(pDepartX, pDepartY, pApproachX, pApproachY, pFromDir, pToDir, pMidOffset)
|
|
74
|
+
{
|
|
75
|
+
let tmpOffset = pMidOffset || 0;
|
|
76
|
+
let tmpFromHoriz = Math.abs(pFromDir.dx) > 0;
|
|
77
|
+
let tmpToHoriz = Math.abs(pToDir.dx) > 0;
|
|
78
|
+
|
|
79
|
+
let tmpCorner1, tmpCorner2, tmpMidpoint;
|
|
80
|
+
|
|
81
|
+
if (tmpFromHoriz && tmpToHoriz)
|
|
82
|
+
{
|
|
83
|
+
// Both horizontal departure/approach: corridor is vertical
|
|
84
|
+
let tmpMidX = (pDepartX + pApproachX) / 2 + tmpOffset;
|
|
85
|
+
tmpCorner1 = { x: tmpMidX, y: pDepartY };
|
|
86
|
+
tmpCorner2 = { x: tmpMidX, y: pApproachY };
|
|
87
|
+
tmpMidpoint = { x: tmpMidX, y: (pDepartY + pApproachY) / 2 };
|
|
88
|
+
}
|
|
89
|
+
else if (!tmpFromHoriz && !tmpToHoriz)
|
|
90
|
+
{
|
|
91
|
+
// Both vertical: corridor is horizontal
|
|
92
|
+
let tmpMidY = (pDepartY + pApproachY) / 2 + tmpOffset;
|
|
93
|
+
tmpCorner1 = { x: pDepartX, y: tmpMidY };
|
|
94
|
+
tmpCorner2 = { x: pApproachX, y: tmpMidY };
|
|
95
|
+
tmpMidpoint = { x: (pDepartX + pApproachX) / 2, y: tmpMidY };
|
|
96
|
+
}
|
|
97
|
+
else if (tmpFromHoriz && !tmpToHoriz)
|
|
98
|
+
{
|
|
99
|
+
// Horizontal→Vertical: single L-bend
|
|
100
|
+
tmpCorner1 = { x: pApproachX + tmpOffset, y: pDepartY };
|
|
101
|
+
tmpCorner2 = { x: pApproachX + tmpOffset, y: pApproachY };
|
|
102
|
+
tmpMidpoint = { x: pApproachX + tmpOffset, y: (pDepartY + pApproachY) / 2 };
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
{
|
|
106
|
+
// Vertical→Horizontal: single L-bend
|
|
107
|
+
tmpCorner1 = { x: pDepartX, y: pApproachY + tmpOffset };
|
|
108
|
+
tmpCorner2 = { x: pApproachX, y: pApproachY + tmpOffset };
|
|
109
|
+
tmpMidpoint = { x: (pDepartX + pApproachX) / 2, y: pApproachY + tmpOffset };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { corner1: tmpCorner1, corner2: tmpCorner2, midpoint: tmpMidpoint };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---- Bezier Evaluation ----
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Evaluate a cubic bezier curve at parameter t.
|
|
119
|
+
* B(t) = (1-t)³P0 + 3(1-t)²tP1 + 3(1-t)t²P2 + t³P3
|
|
120
|
+
*
|
|
121
|
+
* @param {{x: number, y: number}} pP0 - Start point
|
|
122
|
+
* @param {{x: number, y: number}} pP1 - First control point
|
|
123
|
+
* @param {{x: number, y: number}} pP2 - Second control point
|
|
124
|
+
* @param {{x: number, y: number}} pP3 - End point
|
|
125
|
+
* @param {number} pT - Parameter in range [0, 1]
|
|
126
|
+
* @returns {{x: number, y: number}}
|
|
127
|
+
*/
|
|
128
|
+
evaluateCubicBezier(pP0, pP1, pP2, pP3, pT)
|
|
129
|
+
{
|
|
130
|
+
let tmpOMT = 1 - pT;
|
|
131
|
+
let tmpOMT2 = tmpOMT * tmpOMT;
|
|
132
|
+
let tmpOMT3 = tmpOMT2 * tmpOMT;
|
|
133
|
+
let tmpT2 = pT * pT;
|
|
134
|
+
let tmpT3 = tmpT2 * pT;
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
x: tmpOMT3 * pP0.x + 3 * tmpOMT2 * pT * pP1.x + 3 * tmpOMT * tmpT2 * pP2.x + tmpT3 * pP3.x,
|
|
138
|
+
y: tmpOMT3 * pP0.y + 3 * tmpOMT2 * pT * pP1.y + 3 * tmpOMT * tmpT2 * pP2.y + tmpT3 * pP3.y
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- SVG Path String Assembly ----
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build an SVG bezier path string.
|
|
146
|
+
* Pattern: M start L depart C cp1, cp2, approach L end
|
|
147
|
+
*
|
|
148
|
+
* @param {{x: number, y: number}} pStart - Start point
|
|
149
|
+
* @param {{x: number, y: number}} pDepart - Departure point after straight segment
|
|
150
|
+
* @param {{x: number, y: number}} pCP1 - First control point
|
|
151
|
+
* @param {{x: number, y: number}} pCP2 - Second control point
|
|
152
|
+
* @param {{x: number, y: number}} pApproach - Approach point before final straight segment
|
|
153
|
+
* @param {{x: number, y: number}} pEnd - End point
|
|
154
|
+
* @returns {string} SVG path d attribute
|
|
155
|
+
*/
|
|
156
|
+
buildBezierPathString(pStart, pDepart, pCP1, pCP2, pApproach, pEnd)
|
|
157
|
+
{
|
|
158
|
+
return `M ${pStart.x} ${pStart.y} L ${pDepart.x} ${pDepart.y} C ${pCP1.x} ${pCP1.y}, ${pCP2.x} ${pCP2.y}, ${pApproach.x} ${pApproach.y} L ${pEnd.x} ${pEnd.y}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Build an SVG split bezier path string (two cubic segments through a handle point).
|
|
163
|
+
* Pattern: M start L depart C cp1a, cp1b, handle C cp2a, cp2b, approach L end
|
|
164
|
+
*
|
|
165
|
+
* @param {{x: number, y: number}} pStart
|
|
166
|
+
* @param {{x: number, y: number}} pDepart
|
|
167
|
+
* @param {{x: number, y: number}} pCP1a - First segment's first control point
|
|
168
|
+
* @param {{x: number, y: number}} pCP1b - First segment's second control point
|
|
169
|
+
* @param {{x: number, y: number}} pHandle - Handle point where the two segments meet
|
|
170
|
+
* @param {{x: number, y: number}} pCP2a - Second segment's first control point
|
|
171
|
+
* @param {{x: number, y: number}} pCP2b - Second segment's second control point
|
|
172
|
+
* @param {{x: number, y: number}} pApproach
|
|
173
|
+
* @param {{x: number, y: number}} pEnd
|
|
174
|
+
* @returns {string} SVG path d attribute
|
|
175
|
+
*/
|
|
176
|
+
buildSplitBezierPathString(pStart, pDepart, pCP1a, pCP1b, pHandle, pCP2a, pCP2b, pApproach, pEnd)
|
|
177
|
+
{
|
|
178
|
+
return `M ${pStart.x} ${pStart.y} L ${pDepart.x} ${pDepart.y} C ${pCP1a.x} ${pCP1a.y}, ${pCP1b.x} ${pCP1b.y}, ${pHandle.x} ${pHandle.y} C ${pCP2a.x} ${pCP2a.y}, ${pCP2b.x} ${pCP2b.y}, ${pApproach.x} ${pApproach.y} L ${pEnd.x} ${pEnd.y}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Build an SVG orthogonal (right-angle) path string.
|
|
183
|
+
* Pattern: M start L depart L corner1 L corner2 L approach L end
|
|
184
|
+
*
|
|
185
|
+
* @param {{x: number, y: number}} pStart
|
|
186
|
+
* @param {{x: number, y: number}} pDepart
|
|
187
|
+
* @param {{x: number, y: number}} pCorner1
|
|
188
|
+
* @param {{x: number, y: number}} pCorner2
|
|
189
|
+
* @param {{x: number, y: number}} pApproach
|
|
190
|
+
* @param {{x: number, y: number}} pEnd
|
|
191
|
+
* @returns {string} SVG path d attribute
|
|
192
|
+
*/
|
|
193
|
+
buildOrthogonalPathString(pStart, pDepart, pCorner1, pCorner2, pApproach, pEnd)
|
|
194
|
+
{
|
|
195
|
+
return `M ${pStart.x} ${pStart.y} L ${pDepart.x} ${pDepart.y} L ${pCorner1.x} ${pCorner1.y} L ${pCorner2.x} ${pCorner2.y} L ${pApproach.x} ${pApproach.y} L ${pEnd.x} ${pEnd.y}`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = PictServiceFlowPathGenerator;
|