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.
- package/package.json +7 -2
- package/source/Pict-Section-Flow.js +20 -14
- package/source/providers/PictProvider-Flow-Background.js +303 -0
- package/source/providers/PictProvider-Flow-CSS.js +99 -7
- package/source/providers/PictProvider-Flow-ConnectorShapes.js +8 -0
- package/source/providers/PictProvider-Flow-Geometry.js +11 -421
- package/source/providers/PictProvider-Flow-Icons.js +20 -0
- package/source/providers/PictProvider-Flow-Layouts.js +107 -0
- package/source/services/PictService-Flow-ConnectionRenderer.js +77 -5
- package/source/services/PictService-Flow-CursorManager.js +113 -0
- package/source/services/PictService-Flow-InteractionManager.js +443 -61
- package/source/services/PictService-Flow-Layout.js +21 -16
- package/source/services/PictService-Flow-PathGenerator.js +30 -417
- package/source/services/PictService-Flow-RenderManager.js +9 -1
- package/source/services/PictService-Flow-ViewportManager.js +102 -0
- package/source/views/PictView-Flow-FloatingToolbar.js +57 -0
- package/source/views/PictView-Flow-Node.js +36 -0
- package/source/views/PictView-Flow-PropertiesPanel.js +27 -5
- package/source/views/PictView-Flow-Toolbar.js +148 -13
- package/source/views/PictView-Flow.js +628 -3
- package/.claude/launch.json +0 -11
- package/docs/.nojekyll +0 -0
- package/docs/Architecture.md +0 -163
- package/docs/Custom-Styling.md +0 -275
- package/docs/Data_Model.md +0 -149
- package/docs/Event_System.md +0 -156
- package/docs/Getting_Started.md +0 -237
- package/docs/Implementation_Reference.md +0 -528
- package/docs/Layout_Persistence.md +0 -117
- package/docs/README.md +0 -103
- package/docs/Theme_Integration.md +0 -150
- package/docs/_brand.json +0 -18
- package/docs/_cover.md +0 -17
- package/docs/_playground.json +0 -24
- package/docs/_sidebar.md +0 -57
- package/docs/_topbar.md +0 -8
- package/docs/_version.json +0 -7
- package/docs/api/PictFlowCard.md +0 -216
- package/docs/api/PictFlowCardPropertiesPanel.md +0 -235
- package/docs/api/addConnection.md +0 -101
- package/docs/api/addNode.md +0 -137
- package/docs/api/autoLayout.md +0 -77
- package/docs/api/getFlowData.md +0 -112
- package/docs/api/marshalToView.md +0 -95
- package/docs/api/openPanel.md +0 -128
- package/docs/api/registerHandler.md +0 -174
- package/docs/api/registerNodeType.md +0 -142
- package/docs/api/removeConnection.md +0 -57
- package/docs/api/removeNode.md +0 -80
- package/docs/api/saveLayout.md +0 -152
- package/docs/api/screenToSVGCoords.md +0 -68
- package/docs/api/selectNode.md +0 -116
- package/docs/api/setTheme.md +0 -168
- package/docs/api/setZoom.md +0 -97
- package/docs/api/toggleFullscreen.md +0 -68
- package/docs/card-help/EACH.md +0 -19
- package/docs/card-help/FREAD.md +0 -24
- package/docs/card-help/FWRITE.md +0 -24
- package/docs/card-help/GET.md +0 -22
- package/docs/card-help/ITE.md +0 -23
- package/docs/card-help/LOG.md +0 -23
- package/docs/card-help/NOTE.md +0 -17
- package/docs/card-help/PREV.md +0 -18
- package/docs/card-help/SET.md +0 -27
- package/docs/card-help/SPKL.md +0 -22
- package/docs/card-help/STAT.md +0 -23
- package/docs/card-help/SW.md +0 -25
- package/docs/diagrams/architecture-at-a-glance.excalidraw +0 -4270
- package/docs/diagrams/architecture-at-a-glance.mmd +0 -30
- package/docs/diagrams/architecture-at-a-glance.svg +0 -2
- package/docs/diagrams/data-flow.excalidraw +0 -1451
- package/docs/diagrams/data-flow.mmd +0 -17
- package/docs/diagrams/data-flow.svg +0 -2
- package/docs/diagrams/high-level-design.excalidraw +0 -5767
- package/docs/diagrams/high-level-design.mmd +0 -86
- package/docs/diagrams/high-level-design.svg +0 -2
- package/docs/diagrams/relationships.excalidraw +0 -3852
- package/docs/diagrams/relationships.mmd +0 -9
- package/docs/diagrams/relationships.svg +0 -2
- package/docs/diagrams/service-initialization-sequence.excalidraw +0 -1466
- package/docs/diagrams/service-initialization-sequence.mmd +0 -19
- package/docs/diagrams/service-initialization-sequence.svg +0 -2
- package/docs/diagrams/svg-layer-structure.excalidraw +0 -1060
- package/docs/diagrams/svg-layer-structure.mmd +0 -18
- package/docs/diagrams/svg-layer-structure.svg +0 -2
- package/docs/examples/README.md +0 -9
- package/docs/examples/simple_cards/README.md +0 -677
- package/docs/examples/simple_cards/css/flowexample.css +0 -65
- package/docs/examples/simple_cards/index.html +0 -32
- package/docs/examples/simple_cards/js/pict.min.js +0 -12
- package/docs/examples/simple_cards/pict-section-flow-example-simple-cards.compatible.min.js +0 -1
- package/docs/index.html +0 -38
- package/docs/playground/app.json +0 -6
- package/docs/playground/appdata.json +0 -85
- package/docs/playground/application.js +0 -23
- package/docs/playground/pict.json +0 -17
- package/docs/playground/runtime/pict-application.min.js +0 -2
- package/docs/playground/runtime/pict-section-flow.min.js +0 -2
- package/docs/playground/runtime/pict-section-modal.min.js +0 -2
- package/docs/playground/runtime/pict.min.js +0 -12
- package/docs/retold-catalog.json +0 -244
- package/docs/retold-keyword-index.json +0 -26028
- package/example_applications/simple_cards/css/flowexample.css +0 -65
- package/example_applications/simple_cards/html/index.html +0 -32
- package/example_applications/simple_cards/package.json +0 -52
- package/example_applications/simple_cards/source/Pict-Application-FlowExample-Configuration.json +0 -15
- package/example_applications/simple_cards/source/Pict-Application-FlowExample.js +0 -539
- package/example_applications/simple_cards/source/card-help-content.js +0 -16
- package/example_applications/simple_cards/source/cards/FlowCard-Comment.js +0 -38
- package/example_applications/simple_cards/source/cards/FlowCard-DataPreview.js +0 -44
- package/example_applications/simple_cards/source/cards/FlowCard-Each.js +0 -38
- package/example_applications/simple_cards/source/cards/FlowCard-FileRead.js +0 -56
- package/example_applications/simple_cards/source/cards/FlowCard-FileWrite.js +0 -50
- package/example_applications/simple_cards/source/cards/FlowCard-GetValue.js +0 -37
- package/example_applications/simple_cards/source/cards/FlowCard-IfThenElse.js +0 -49
- package/example_applications/simple_cards/source/cards/FlowCard-LogValues.js +0 -55
- package/example_applications/simple_cards/source/cards/FlowCard-SetValue.js +0 -97
- package/example_applications/simple_cards/source/cards/FlowCard-Sparkline.js +0 -100
- package/example_applications/simple_cards/source/cards/FlowCard-StatusMonitor.js +0 -46
- package/example_applications/simple_cards/source/cards/FlowCard-Switch.js +0 -39
- package/example_applications/simple_cards/source/providers/PictRouter-FlowExample-Configuration.json +0 -22
- package/example_applications/simple_cards/source/sample-flows.js +0 -410
- package/example_applications/simple_cards/source/views/PictView-FlowExample-About.js +0 -184
- package/example_applications/simple_cards/source/views/PictView-FlowExample-BottomBar.js +0 -77
- package/example_applications/simple_cards/source/views/PictView-FlowExample-Documentation.js +0 -325
- package/example_applications/simple_cards/source/views/PictView-FlowExample-FileWriteInfo.js +0 -59
- package/example_applications/simple_cards/source/views/PictView-FlowExample-Layout.js +0 -90
- package/example_applications/simple_cards/source/views/PictView-FlowExample-MainWorkspace.js +0 -453
- package/example_applications/simple_cards/source/views/PictView-FlowExample-TopBar.js +0 -95
- package/scripts/generate-card-help.js +0 -214
- package/source/providers/edges/Edge-Bezier.js +0 -41
- package/source/providers/edges/Edge-Orthogonal.js +0 -37
- package/source/providers/edges/Edge-OrthogonalSnap.js +0 -72
- package/source/providers/edges/Edge-Perimeter-Linear.js +0 -31
- package/source/providers/edges/Edge-Perimeter-Orthogonal.js +0 -39
- package/source/providers/edges/Edge-Perimeter.js +0 -48
- package/source/providers/edges/Edge-PerimeterMath.js +0 -92
- package/source/providers/edges/Edge-Straight.js +0 -24
- package/source/providers/layouts/Layout-Circular.js +0 -203
- package/source/providers/layouts/Layout-Coerce.js +0 -40
- package/source/providers/layouts/Layout-Columnar.js +0 -134
- package/source/providers/layouts/Layout-Custom.js +0 -27
- package/source/providers/layouts/Layout-ForcedFromCenter.js +0 -256
- package/source/providers/layouts/Layout-Grid.js +0 -134
- package/source/providers/layouts/Layout-Layered.js +0 -155
- package/source/providers/layouts/Layout-Rank.js +0 -141
- package/source/providers/layouts/Layout-Staggered.js +0 -131
- package/source/providers/layouts/Layout-Tabular.js +0 -94
- package/test/ConnectionHandleManager_tests.js +0 -717
- package/test/ConnectionRenderer_tests.js +0 -591
- package/test/DataManager_tests.js +0 -859
- package/test/Geometry_tests.js +0 -767
- package/test/InteractionManager_tests.js +0 -279
- package/test/Layout_tests.js +0 -1604
- package/test/NodeView_tests.js +0 -66
- package/test/PanelManager_tests.js +0 -172
- package/test/PathGenerator_tests.js +0 -978
- package/test/PortRenderer_tests.js +0 -376
- package/test/RenderManager_tests.js +0 -756
- package/test/Renderer_tests.js +0 -133
- package/test/SelectionManager_tests.js +0 -185
- package/test/StylePresets_tests.js +0 -153
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
3
|
+
// Layout algorithm and edge-theme descriptors now live in the standalone
|
|
4
|
+
// pict-provider-graphlayout module (flow 2.0 Phase 1b). The variable names below
|
|
5
|
+
// are unchanged so the registration logic that follows is untouched.
|
|
6
|
+
const libGraphLayout = require('pict-provider-graphlayout');
|
|
7
|
+
|
|
8
|
+
const libLayoutCustom = libGraphLayout.Layouts.Custom;
|
|
9
|
+
const libLayoutLayered = libGraphLayout.Layouts.Layered;
|
|
10
|
+
const libLayoutStaggered = libGraphLayout.Layouts.Staggered;
|
|
11
|
+
const libLayoutForcedFromCenter = libGraphLayout.Layouts.ForcedFromCenter;
|
|
12
|
+
const libLayoutGrid = libGraphLayout.Layouts.Grid;
|
|
13
|
+
const libLayoutCircular = libGraphLayout.Layouts.Circular;
|
|
14
|
+
const libLayoutTabular = libGraphLayout.Layouts.Tabular;
|
|
15
|
+
const libLayoutColumnar = libGraphLayout.Layouts.Columnar;
|
|
16
|
+
|
|
17
|
+
const libEdgeBezier = libGraphLayout.Edges.Bezier;
|
|
18
|
+
const libEdgeOrthogonal = libGraphLayout.Edges.Orthogonal;
|
|
19
|
+
const libEdgeStraight = libGraphLayout.Edges.Straight;
|
|
20
|
+
const libEdgeOrthogonalSnap = libGraphLayout.Edges.OrthogonalSnap;
|
|
21
|
+
const libEdgePerimeter = libGraphLayout.Edges.Perimeter;
|
|
22
|
+
const libEdgePerimeterLinear = libGraphLayout.Edges.PerimeterLinear;
|
|
23
|
+
const libEdgePerimeterOrthogonal = libGraphLayout.Edges.PerimeterOrthogonal;
|
|
19
24
|
|
|
20
25
|
const _BUILTIN_ALGORITHMS =
|
|
21
26
|
[
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
2
2
|
|
|
3
|
+
const libPathGeneratorCore = require('pict-provider-graphlayout').PathGenerator;
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* PictService-Flow-PathGenerator
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* Backwards-compatible shim. The SVG path-generation math now lives in the
|
|
9
|
+
* standalone pict-provider-graphlayout module as a geometry-injected core (flow
|
|
10
|
+
* 2.0 Phase 1b). This service keeps the historical serviceType and the
|
|
11
|
+
* `_FlowView` property, and delegates every method to the core. The core gets a
|
|
12
|
+
* resolver so it reads the live FlowView geometry provider at call time, exactly
|
|
13
|
+
* as the original implementation did.
|
|
9
14
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
+
* Used by the ConnectionRenderer (port-to-port connections) and the
|
|
16
|
+
* TetherService (panel-to-node tethers).
|
|
15
17
|
*/
|
|
16
18
|
class PictServiceFlowPathGenerator extends libFableServiceProviderBase
|
|
17
19
|
{
|
|
@@ -22,459 +24,70 @@ class PictServiceFlowPathGenerator extends libFableServiceProviderBase
|
|
|
22
24
|
this.serviceType = 'PictServiceFlowPathGenerator';
|
|
23
25
|
|
|
24
26
|
this._FlowView = (pOptions && pOptions.FlowView) ? pOptions.FlowView : null;
|
|
25
|
-
}
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
let tmpSelf = this;
|
|
29
|
+
this._Core = new libPathGeneratorCore(
|
|
30
|
+
{
|
|
31
|
+
geometryResolver: function ()
|
|
32
|
+
{
|
|
33
|
+
return tmpSelf._FlowView ? tmpSelf._FlowView._GeometryProvider : null;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
28
37
|
|
|
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
38
|
computeDepartApproach(pFrom, pTo, pDepartDist)
|
|
40
39
|
{
|
|
41
|
-
|
|
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
|
-
};
|
|
40
|
+
return this._Core.computeDepartApproach(pFrom, pTo, pDepartDist);
|
|
54
41
|
}
|
|
55
42
|
|
|
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
43
|
computeAutoOrthogonalCorners(pDepartX, pDepartY, pApproachX, pApproachY, pFromDir, pToDir, pMidOffset)
|
|
74
44
|
{
|
|
75
|
-
|
|
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 };
|
|
45
|
+
return this._Core.computeAutoOrthogonalCorners(pDepartX, pDepartY, pApproachX, pApproachY, pFromDir, pToDir, pMidOffset);
|
|
113
46
|
}
|
|
114
47
|
|
|
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
48
|
evaluateCubicBezier(pP0, pP1, pP2, pP3, pT)
|
|
129
49
|
{
|
|
130
|
-
|
|
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
|
-
};
|
|
50
|
+
return this._Core.evaluateCubicBezier(pP0, pP1, pP2, pP3, pT);
|
|
140
51
|
}
|
|
141
52
|
|
|
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
53
|
buildBezierPathString(pStart, pDepart, pCP1, pCP2, pApproach, pEnd)
|
|
157
54
|
{
|
|
158
|
-
return
|
|
55
|
+
return this._Core.buildBezierPathString(pStart, pDepart, pCP1, pCP2, pApproach, pEnd);
|
|
159
56
|
}
|
|
160
57
|
|
|
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
58
|
buildSplitBezierPathString(pStart, pDepart, pCP1a, pCP1b, pHandle, pCP2a, pCP2b, pApproach, pEnd)
|
|
177
59
|
{
|
|
178
|
-
return
|
|
60
|
+
return this._Core.buildSplitBezierPathString(pStart, pDepart, pCP1a, pCP1b, pHandle, pCP2a, pCP2b, pApproach, pEnd);
|
|
179
61
|
}
|
|
180
62
|
|
|
181
|
-
/**
|
|
182
|
-
* Build an SVG multi-segment bezier path string.
|
|
183
|
-
* Generates N+1 cubic bezier segments through N handle points.
|
|
184
|
-
*
|
|
185
|
-
* Pattern: M start L depart C cp,cp,handle[0] C cp,cp,handle[1] ... C cp,cp,approach L end
|
|
186
|
-
*
|
|
187
|
-
* Control points are computed using Catmull-Rom-to-Bezier conversion
|
|
188
|
-
* for C1 (smooth tangent) continuity at every handle.
|
|
189
|
-
*
|
|
190
|
-
* @param {{x: number, y: number}} pStart - Port anchor start
|
|
191
|
-
* @param {{x: number, y: number}} pDepart - Departure point after straight segment
|
|
192
|
-
* @param {Array<{x: number, y: number}>} pHandles - Ordered handle waypoints
|
|
193
|
-
* @param {{x: number, y: number}} pApproach - Approach point before final straight segment
|
|
194
|
-
* @param {{x: number, y: number}} pEnd - Port anchor end
|
|
195
|
-
* @param {{dx: number, dy: number}} pStartDir - Departure direction unit vector
|
|
196
|
-
* @param {{dx: number, dy: number}} pEndDir - Approach direction unit vector
|
|
197
|
-
* @returns {string} SVG path d attribute
|
|
198
|
-
*/
|
|
199
63
|
buildMultiBezierPathString(pStart, pDepart, pHandles, pApproach, pEnd, pStartDir, pEndDir)
|
|
200
64
|
{
|
|
201
|
-
|
|
202
|
-
let tmpWaypoints = [pDepart];
|
|
203
|
-
for (let i = 0; i < pHandles.length; i++)
|
|
204
|
-
{
|
|
205
|
-
tmpWaypoints.push(pHandles[i]);
|
|
206
|
-
}
|
|
207
|
-
tmpWaypoints.push(pApproach);
|
|
208
|
-
|
|
209
|
-
let tmpPath = `M ${pStart.x} ${pStart.y} L ${pDepart.x} ${pDepart.y}`;
|
|
210
|
-
|
|
211
|
-
for (let i = 0; i < tmpWaypoints.length - 1; i++)
|
|
212
|
-
{
|
|
213
|
-
let tmpFrom = tmpWaypoints[i];
|
|
214
|
-
let tmpTo = tmpWaypoints[i + 1];
|
|
215
|
-
|
|
216
|
-
let tmpSegDX = tmpTo.x - tmpFrom.x;
|
|
217
|
-
let tmpSegDY = tmpTo.y - tmpFrom.y;
|
|
218
|
-
let tmpSegLen = Math.sqrt(tmpSegDX * tmpSegDX + tmpSegDY * tmpSegDY);
|
|
219
|
-
if (tmpSegLen < 1)
|
|
220
|
-
{
|
|
221
|
-
tmpSegLen = 1;
|
|
222
|
-
}
|
|
223
|
-
let tmpScale = tmpSegLen * 0.35;
|
|
224
|
-
|
|
225
|
-
// Tangent at tmpFrom
|
|
226
|
-
let tmpTanFromX, tmpTanFromY;
|
|
227
|
-
if (i === 0)
|
|
228
|
-
{
|
|
229
|
-
// First segment: use the port departure direction
|
|
230
|
-
tmpTanFromX = pStartDir.dx;
|
|
231
|
-
tmpTanFromY = pStartDir.dy;
|
|
232
|
-
}
|
|
233
|
-
else
|
|
234
|
-
{
|
|
235
|
-
// Interior handle: tangent points from previous toward next waypoint
|
|
236
|
-
let tmpPrev = tmpWaypoints[i - 1];
|
|
237
|
-
let tmpNext = tmpWaypoints[i + 1];
|
|
238
|
-
tmpTanFromX = tmpNext.x - tmpPrev.x;
|
|
239
|
-
tmpTanFromY = tmpNext.y - tmpPrev.y;
|
|
240
|
-
let tmpTanLen = Math.sqrt(tmpTanFromX * tmpTanFromX + tmpTanFromY * tmpTanFromY);
|
|
241
|
-
if (tmpTanLen < 1) tmpTanLen = 1;
|
|
242
|
-
tmpTanFromX /= tmpTanLen;
|
|
243
|
-
tmpTanFromY /= tmpTanLen;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Tangent at tmpTo
|
|
247
|
-
let tmpTanToX, tmpTanToY;
|
|
248
|
-
if (i === tmpWaypoints.length - 2)
|
|
249
|
-
{
|
|
250
|
-
// Last segment: use the port approach direction (reversed for incoming)
|
|
251
|
-
tmpTanToX = -pEndDir.dx;
|
|
252
|
-
tmpTanToY = -pEndDir.dy;
|
|
253
|
-
}
|
|
254
|
-
else
|
|
255
|
-
{
|
|
256
|
-
// Interior handle: tangent points from previous toward next waypoint
|
|
257
|
-
let tmpPrev = tmpWaypoints[i];
|
|
258
|
-
let tmpNext = tmpWaypoints[i + 2];
|
|
259
|
-
tmpTanToX = tmpNext.x - tmpPrev.x;
|
|
260
|
-
tmpTanToY = tmpNext.y - tmpPrev.y;
|
|
261
|
-
let tmpTanLen = Math.sqrt(tmpTanToX * tmpTanToX + tmpTanToY * tmpTanToY);
|
|
262
|
-
if (tmpTanLen < 1) tmpTanLen = 1;
|
|
263
|
-
tmpTanToX /= tmpTanLen;
|
|
264
|
-
tmpTanToY /= tmpTanLen;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
let tmpCP1X = tmpFrom.x + tmpTanFromX * tmpScale;
|
|
268
|
-
let tmpCP1Y = tmpFrom.y + tmpTanFromY * tmpScale;
|
|
269
|
-
let tmpCP2X = tmpTo.x - tmpTanToX * tmpScale;
|
|
270
|
-
let tmpCP2Y = tmpTo.y - tmpTanToY * tmpScale;
|
|
271
|
-
|
|
272
|
-
tmpPath += ` C ${tmpCP1X} ${tmpCP1Y}, ${tmpCP2X} ${tmpCP2Y}, ${tmpTo.x} ${tmpTo.y}`;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
tmpPath += ` L ${pEnd.x} ${pEnd.y}`;
|
|
276
|
-
|
|
277
|
-
return tmpPath;
|
|
65
|
+
return this._Core.buildMultiBezierPathString(pStart, pDepart, pHandles, pApproach, pEnd, pStartDir, pEndDir);
|
|
278
66
|
}
|
|
279
67
|
|
|
280
|
-
/**
|
|
281
|
-
* Build an SVG orthogonal (right-angle) path string.
|
|
282
|
-
* Pattern: M start L depart L corner1 L corner2 L approach L end
|
|
283
|
-
*
|
|
284
|
-
* @param {{x: number, y: number}} pStart
|
|
285
|
-
* @param {{x: number, y: number}} pDepart
|
|
286
|
-
* @param {{x: number, y: number}} pCorner1
|
|
287
|
-
* @param {{x: number, y: number}} pCorner2
|
|
288
|
-
* @param {{x: number, y: number}} pApproach
|
|
289
|
-
* @param {{x: number, y: number}} pEnd
|
|
290
|
-
* @returns {string} SVG path d attribute
|
|
291
|
-
*/
|
|
292
68
|
buildOrthogonalPathString(pStart, pDepart, pCorner1, pCorner2, pApproach, pEnd)
|
|
293
69
|
{
|
|
294
|
-
return
|
|
70
|
+
return this._Core.buildOrthogonalPathString(pStart, pDepart, pCorner1, pCorner2, pApproach, pEnd);
|
|
295
71
|
}
|
|
296
72
|
|
|
297
|
-
// ---- Directional Geometry ----
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Compute full directional geometry between two port anchors, including
|
|
301
|
-
* departure/approach points and bezier control points.
|
|
302
|
-
*
|
|
303
|
-
* Uses sophisticated facing detection: when ports face each other the
|
|
304
|
-
* curve offset scales with inline distance; when ports are on the same
|
|
305
|
-
* axis but not facing, a wider offset prevents the path from collapsing;
|
|
306
|
-
* perpendicular exits use a moderate offset.
|
|
307
|
-
*
|
|
308
|
-
* @param {{x: number, y: number, side: string}} pStart
|
|
309
|
-
* @param {{x: number, y: number, side: string}} pEnd
|
|
310
|
-
* @returns {{departX: number, departY: number, approachX: number, approachY: number, cp1X: number, cp1Y: number, cp2X: number, cp2Y: number, startDir: {dx: number, dy: number}, endDir: {dx: number, dy: number}}}
|
|
311
|
-
*/
|
|
312
73
|
computeDirectionalGeometry(pStart, pEnd)
|
|
313
74
|
{
|
|
314
|
-
|
|
315
|
-
let tmpEndDir = this._FlowView._GeometryProvider.sideDirection(pEnd.side || 'left');
|
|
316
|
-
|
|
317
|
-
let tmpStraightLen = 20;
|
|
318
|
-
|
|
319
|
-
let tmpDepartX = pStart.x + tmpStartDir.dx * tmpStraightLen;
|
|
320
|
-
let tmpDepartY = pStart.y + tmpStartDir.dy * tmpStraightLen;
|
|
321
|
-
|
|
322
|
-
let tmpApproachX = pEnd.x + tmpEndDir.dx * tmpStraightLen;
|
|
323
|
-
let tmpApproachY = pEnd.y + tmpEndDir.dy * tmpStraightLen;
|
|
324
|
-
|
|
325
|
-
let tmpDX = Math.abs(tmpApproachX - tmpDepartX);
|
|
326
|
-
let tmpDY = Math.abs(tmpApproachY - tmpDepartY);
|
|
327
|
-
let tmpDist = Math.sqrt(tmpDX * tmpDX + tmpDY * tmpDY);
|
|
328
|
-
|
|
329
|
-
let tmpBaseOffset = Math.max(Math.min(tmpDist * 0.4, 180), 30);
|
|
330
|
-
|
|
331
|
-
let tmpSameAxis = (tmpStartDir.dx !== 0 && tmpEndDir.dx !== 0) ||
|
|
332
|
-
(tmpStartDir.dy !== 0 && tmpEndDir.dy !== 0);
|
|
333
|
-
|
|
334
|
-
let tmpFacingEachOther = false;
|
|
335
|
-
if (tmpSameAxis)
|
|
336
|
-
{
|
|
337
|
-
if (tmpStartDir.dx === 1 && tmpEndDir.dx === -1 && pEnd.x >= pStart.x)
|
|
338
|
-
{
|
|
339
|
-
tmpFacingEachOther = true;
|
|
340
|
-
}
|
|
341
|
-
else if (tmpStartDir.dx === -1 && tmpEndDir.dx === 1 && pEnd.x <= pStart.x)
|
|
342
|
-
{
|
|
343
|
-
tmpFacingEachOther = true;
|
|
344
|
-
}
|
|
345
|
-
else if (tmpStartDir.dy === 1 && tmpEndDir.dy === -1 && pEnd.y >= pStart.y)
|
|
346
|
-
{
|
|
347
|
-
tmpFacingEachOther = true;
|
|
348
|
-
}
|
|
349
|
-
else if (tmpStartDir.dy === -1 && tmpEndDir.dy === 1 && pEnd.y <= pStart.y)
|
|
350
|
-
{
|
|
351
|
-
tmpFacingEachOther = true;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
let tmpCurveOffset;
|
|
356
|
-
|
|
357
|
-
if (tmpFacingEachOther)
|
|
358
|
-
{
|
|
359
|
-
let tmpInlineDist = (tmpStartDir.dx !== 0) ? tmpDX : tmpDY;
|
|
360
|
-
tmpCurveOffset = Math.max(tmpInlineDist * 0.35, 30);
|
|
361
|
-
}
|
|
362
|
-
else if (tmpSameAxis)
|
|
363
|
-
{
|
|
364
|
-
tmpCurveOffset = Math.max(tmpBaseOffset, 60);
|
|
365
|
-
}
|
|
366
|
-
else
|
|
367
|
-
{
|
|
368
|
-
tmpCurveOffset = Math.max(tmpBaseOffset * 0.8, 40);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
let tmpCP1X = tmpDepartX + tmpStartDir.dx * tmpCurveOffset;
|
|
372
|
-
let tmpCP1Y = tmpDepartY + tmpStartDir.dy * tmpCurveOffset;
|
|
373
|
-
let tmpCP2X = tmpApproachX + tmpEndDir.dx * tmpCurveOffset;
|
|
374
|
-
let tmpCP2Y = tmpApproachY + tmpEndDir.dy * tmpCurveOffset;
|
|
375
|
-
|
|
376
|
-
return {
|
|
377
|
-
departX: tmpDepartX, departY: tmpDepartY,
|
|
378
|
-
approachX: tmpApproachX, approachY: tmpApproachY,
|
|
379
|
-
cp1X: tmpCP1X, cp1Y: tmpCP1Y,
|
|
380
|
-
cp2X: tmpCP2X, cp2Y: tmpCP2Y,
|
|
381
|
-
startDir: tmpStartDir, endDir: tmpEndDir
|
|
382
|
-
};
|
|
75
|
+
return this._Core.computeDirectionalGeometry(pStart, pEnd);
|
|
383
76
|
}
|
|
384
77
|
|
|
385
|
-
// ---- Distance Utilities ----
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Distance from point (pPX, pPY) to line segment (pAX, pAY)-(pBX, pBY).
|
|
389
|
-
* Pure math utility, no state.
|
|
390
|
-
*
|
|
391
|
-
* @param {number} pPX
|
|
392
|
-
* @param {number} pPY
|
|
393
|
-
* @param {number} pAX
|
|
394
|
-
* @param {number} pAY
|
|
395
|
-
* @param {number} pBX
|
|
396
|
-
* @param {number} pBY
|
|
397
|
-
* @returns {number}
|
|
398
|
-
*/
|
|
399
78
|
distanceToSegment(pPX, pPY, pAX, pAY, pBX, pBY)
|
|
400
79
|
{
|
|
401
|
-
|
|
402
|
-
let tmpDY = pBY - pAY;
|
|
403
|
-
let tmpLenSq = tmpDX * tmpDX + tmpDY * tmpDY;
|
|
404
|
-
|
|
405
|
-
if (tmpLenSq < 0.001)
|
|
406
|
-
{
|
|
407
|
-
// Degenerate segment
|
|
408
|
-
let tmpDPX = pPX - pAX;
|
|
409
|
-
let tmpDPY = pPY - pAY;
|
|
410
|
-
return Math.sqrt(tmpDPX * tmpDPX + tmpDPY * tmpDPY);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Project point onto segment, clamped to [0, 1]
|
|
414
|
-
let tmpT = ((pPX - pAX) * tmpDX + (pPY - pAY) * tmpDY) / tmpLenSq;
|
|
415
|
-
if (tmpT < 0) tmpT = 0;
|
|
416
|
-
if (tmpT > 1) tmpT = 1;
|
|
417
|
-
|
|
418
|
-
let tmpClosestX = pAX + tmpT * tmpDX;
|
|
419
|
-
let tmpClosestY = pAY + tmpT * tmpDY;
|
|
420
|
-
let tmpDistX = pPX - tmpClosestX;
|
|
421
|
-
let tmpDistY = pPY - tmpClosestY;
|
|
422
|
-
return Math.sqrt(tmpDistX * tmpDistX + tmpDistY * tmpDistY);
|
|
80
|
+
return this._Core.distanceToSegment(pPX, pPY, pAX, pAY, pBX, pBY);
|
|
423
81
|
}
|
|
424
82
|
|
|
425
|
-
// ---- Auto Midpoint Calculation ----
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Get the auto-calculated midpoint of the default bezier curve between
|
|
429
|
-
* two port anchors, using the full directional geometry (facing detection,
|
|
430
|
-
* adaptive curve offsets). Evaluates the cubic bezier at t=0.5.
|
|
431
|
-
*
|
|
432
|
-
* Used by ConnectionRenderer for connection midpoints.
|
|
433
|
-
*
|
|
434
|
-
* @param {{x: number, y: number, side: string}} pStart
|
|
435
|
-
* @param {{x: number, y: number, side: string}} pEnd
|
|
436
|
-
* @returns {{x: number, y: number}}
|
|
437
|
-
*/
|
|
438
83
|
getAutoMidpoint(pStart, pEnd)
|
|
439
84
|
{
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
return this.evaluateCubicBezier(
|
|
443
|
-
{ x: tmpGeo.departX, y: tmpGeo.departY },
|
|
444
|
-
{ x: tmpGeo.cp1X, y: tmpGeo.cp1Y },
|
|
445
|
-
{ x: tmpGeo.cp2X, y: tmpGeo.cp2Y },
|
|
446
|
-
{ x: tmpGeo.approachX, y: tmpGeo.approachY },
|
|
447
|
-
0.5
|
|
448
|
-
);
|
|
85
|
+
return this._Core.getAutoMidpoint(pStart, pEnd);
|
|
449
86
|
}
|
|
450
87
|
|
|
451
|
-
/**
|
|
452
|
-
* Get the auto-calculated midpoint using simple span-based control points.
|
|
453
|
-
* Uses computeDepartApproach for basic geometry, then span * 0.4 for
|
|
454
|
-
* control point distance. Evaluates the cubic bezier at t=0.5.
|
|
455
|
-
*
|
|
456
|
-
* Used by TetherService for tether midpoints.
|
|
457
|
-
*
|
|
458
|
-
* @param {{x: number, y: number, side: string}} pFrom
|
|
459
|
-
* @param {{x: number, y: number, side: string}} pTo
|
|
460
|
-
* @param {number} pDepartDist - Departure/approach distance
|
|
461
|
-
* @returns {{x: number, y: number}}
|
|
462
|
-
*/
|
|
463
88
|
getAutoMidpointSimple(pFrom, pTo, pDepartDist)
|
|
464
89
|
{
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
let tmpSpanX = Math.abs(tmpDA.approachX - tmpDA.departX);
|
|
468
|
-
let tmpSpanY = Math.abs(tmpDA.approachY - tmpDA.departY);
|
|
469
|
-
let tmpSpan = Math.max(tmpSpanX, tmpSpanY, 40);
|
|
470
|
-
let tmpCPDist = tmpSpan * 0.4;
|
|
471
|
-
|
|
472
|
-
let tmpP0 = { x: tmpDA.departX, y: tmpDA.departY };
|
|
473
|
-
let tmpP1 = { x: tmpDA.departX + tmpDA.fromDir.dx * tmpCPDist, y: tmpDA.departY + tmpDA.fromDir.dy * tmpCPDist };
|
|
474
|
-
let tmpP2 = { x: tmpDA.approachX + tmpDA.toDir.dx * tmpCPDist, y: tmpDA.approachY + tmpDA.toDir.dy * tmpCPDist };
|
|
475
|
-
let tmpP3 = { x: tmpDA.approachX, y: tmpDA.approachY };
|
|
476
|
-
|
|
477
|
-
return this.evaluateCubicBezier(tmpP0, tmpP1, tmpP2, tmpP3, 0.5);
|
|
90
|
+
return this._Core.getAutoMidpointSimple(pFrom, pTo, pDepartDist);
|
|
478
91
|
}
|
|
479
92
|
}
|
|
480
93
|
|
|
@@ -96,7 +96,15 @@ class PictServiceFlowRenderManager extends libFableServiceProviderBase
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
// Dispatch through the renderable-renderer registry. The default
|
|
100
|
+
// 'card' renderer is the node view, so existing diagrams (and
|
|
101
|
+
// ultravisor) render identically; a node type can name a different
|
|
102
|
+
// renderer via its RenderableType field. Fall back to the node view
|
|
103
|
+
// directly when no registry is present (e.g. a minimal test harness).
|
|
104
|
+
let tmpRenderer = (typeof this._FlowView.resolveRenderableRenderer === 'function')
|
|
105
|
+
? this._FlowView.resolveRenderableRenderer(tmpNode, tmpNodeTypeConfig)
|
|
106
|
+
: this._FlowView._NodeView;
|
|
107
|
+
tmpRenderer.renderNode(tmpNode, this._FlowView._NodesLayer, tmpIsSelected, tmpNodeTypeConfig);
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
// Render properties panels and tethers
|
|
@@ -32,6 +32,20 @@ class PictServiceFlowViewportManager extends libFableServiceProviderBase
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Pan the viewport by a delta in screen pixels (added to the current pan).
|
|
37
|
+
* Used by wheel-pan and any consumer that wants to nudge the canvas.
|
|
38
|
+
* @param {number} pDX
|
|
39
|
+
* @param {number} pDY
|
|
40
|
+
*/
|
|
41
|
+
panBy(pDX, pDY)
|
|
42
|
+
{
|
|
43
|
+
let tmpVS = this._FlowView._FlowData.ViewState;
|
|
44
|
+
tmpVS.PanX += pDX;
|
|
45
|
+
tmpVS.PanY += pDY;
|
|
46
|
+
this.updateViewportTransform();
|
|
47
|
+
}
|
|
48
|
+
|
|
35
49
|
/**
|
|
36
50
|
* Set zoom level
|
|
37
51
|
* @param {number} pZoom - The zoom level
|
|
@@ -95,6 +109,94 @@ class PictServiceFlowViewportManager extends libFableServiceProviderBase
|
|
|
95
109
|
this.updateViewportTransform();
|
|
96
110
|
}
|
|
97
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Fit the viewport to a content frame box (origin + size in content space),
|
|
114
|
+
* centering it with a little padding. Mirrors zoomToFit but for a fixed box
|
|
115
|
+
* rather than the node bounds.
|
|
116
|
+
* @param {Object} pFrame - { X, Y, Width, Height }
|
|
117
|
+
* @returns {boolean}
|
|
118
|
+
*/
|
|
119
|
+
fitToFrame(pFrame)
|
|
120
|
+
{
|
|
121
|
+
if (!pFrame || !pFrame.Width || !pFrame.Height) return false;
|
|
122
|
+
if (!this._FlowView._SVGElement) return false;
|
|
123
|
+
|
|
124
|
+
let tmpPadding = 20;
|
|
125
|
+
let tmpFrameWidth = pFrame.Width + tmpPadding * 2;
|
|
126
|
+
let tmpFrameHeight = pFrame.Height + tmpPadding * 2;
|
|
127
|
+
|
|
128
|
+
let tmpSVGRect = this._FlowView._SVGElement.getBoundingClientRect();
|
|
129
|
+
let tmpScaleX = tmpSVGRect.width / tmpFrameWidth;
|
|
130
|
+
let tmpScaleY = tmpSVGRect.height / tmpFrameHeight;
|
|
131
|
+
let tmpZoom = Math.min(tmpScaleX, tmpScaleY);
|
|
132
|
+
tmpZoom = Math.max(this._FlowView.options.MinZoom, Math.min(this._FlowView.options.MaxZoom, tmpZoom));
|
|
133
|
+
|
|
134
|
+
let tmpCenterX = (pFrame.X || 0) + pFrame.Width / 2;
|
|
135
|
+
let tmpCenterY = (pFrame.Y || 0) + pFrame.Height / 2;
|
|
136
|
+
|
|
137
|
+
this._FlowView._FlowData.ViewState.Zoom = tmpZoom;
|
|
138
|
+
this._FlowView._FlowData.ViewState.PanX = (tmpSVGRect.width / 2) - (tmpCenterX * tmpZoom);
|
|
139
|
+
this._FlowView._FlowData.ViewState.PanY = (tmpSVGRect.height / 2) - (tmpCenterY * tmpZoom);
|
|
140
|
+
|
|
141
|
+
this.updateViewportTransform();
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Fit the frame's WIDTH to the container width (vs fitToFrame, which contains the whole
|
|
147
|
+
* frame and centers it), anchoring the frame's top-left at the container's top-left plus
|
|
148
|
+
* an optional top margin. Content outside the frame bleeds past the edges on purpose;
|
|
149
|
+
* vertical overflow is the host's call (a jumbotron clips to the frame height, a
|
|
150
|
+
* fullscreen / background view lets the user scroll down).
|
|
151
|
+
* @param {Object} pFrame - { X, Y, Width }
|
|
152
|
+
* @param {Object} [pOptions] - { TopMargin }
|
|
153
|
+
* @returns {boolean}
|
|
154
|
+
*/
|
|
155
|
+
fitToFrameWidth(pFrame, pOptions)
|
|
156
|
+
{
|
|
157
|
+
if (!pFrame || !pFrame.Width) return false;
|
|
158
|
+
if (!this._FlowView._SVGElement) return false;
|
|
159
|
+
|
|
160
|
+
let tmpRect = this._FlowView._SVGElement.getBoundingClientRect();
|
|
161
|
+
let tmpResult = PictServiceFlowViewportManager.computeFitToWidth(pFrame, tmpRect.width,
|
|
162
|
+
{
|
|
163
|
+
TopMargin: (pOptions && pOptions.TopMargin) || 0,
|
|
164
|
+
MinZoom: this._FlowView.options.MinZoom,
|
|
165
|
+
MaxZoom: this._FlowView.options.MaxZoom
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
this._FlowView._FlowData.ViewState.Zoom = tmpResult.Zoom;
|
|
169
|
+
this._FlowView._FlowData.ViewState.PanX = tmpResult.PanX;
|
|
170
|
+
this._FlowView._FlowData.ViewState.PanY = tmpResult.PanY;
|
|
171
|
+
|
|
172
|
+
this.updateViewportTransform();
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Pure fit-to-width math: zoom so the frame's width equals the container width (clamped to
|
|
178
|
+
* the view's zoom bounds), and pan so the frame's top-left sits at (0, TopMargin). No DOM,
|
|
179
|
+
* so it is unit tested directly.
|
|
180
|
+
* @param {Object} pFrame - { X, Y, Width }
|
|
181
|
+
* @param {number} pContainerWidth
|
|
182
|
+
* @param {Object} [pOptions] - { TopMargin, MinZoom, MaxZoom }
|
|
183
|
+
* @returns {{Zoom:number, PanX:number, PanY:number}}
|
|
184
|
+
*/
|
|
185
|
+
static computeFitToWidth(pFrame, pContainerWidth, pOptions)
|
|
186
|
+
{
|
|
187
|
+
let tmpOptions = pOptions || {};
|
|
188
|
+
let tmpZoom = (pFrame && pFrame.Width > 0 && pContainerWidth > 0) ? (pContainerWidth / pFrame.Width) : 1;
|
|
189
|
+
let tmpMin = (typeof tmpOptions.MinZoom === 'number') ? tmpOptions.MinZoom : 0.05;
|
|
190
|
+
let tmpMax = (typeof tmpOptions.MaxZoom === 'number') ? tmpOptions.MaxZoom : 8;
|
|
191
|
+
tmpZoom = Math.max(tmpMin, Math.min(tmpMax, tmpZoom));
|
|
192
|
+
let tmpTopMargin = tmpOptions.TopMargin || 0;
|
|
193
|
+
return {
|
|
194
|
+
Zoom: tmpZoom,
|
|
195
|
+
PanX: -(((pFrame && pFrame.X) || 0) * tmpZoom),
|
|
196
|
+
PanY: tmpTopMargin - (((pFrame && pFrame.Y) || 0) * tmpZoom)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
98
200
|
/**
|
|
99
201
|
* Convert screen coordinates to SVG viewport coordinates
|
|
100
202
|
* @param {number} pScreenX
|