@@ -8,6 +8,28 @@ import {
88
99import { isSequenceFlow } from '../../../simulator/util/ModelUtil' ;
1010
11+ // how far (px) a point on a flow must be from every sibling flow for the
12+ // flow to count as "diverged" - roughly the toggle button size, so buttons
13+ // end up on visually distinct parts of their flows
14+ const SEPARATION = 30 ;
15+
16+ // step used when scanning a flow for its divergence point
17+ const SAMPLE_STEP = 8 ;
18+
19+ // once a flow has diverged, push the anchor a little further so the button
20+ // sits clearly past the corner rather than right on it
21+ const DIVERGENCE_MARGIN = 20 ;
22+
23+ // fallback (flows never diverge, e.g. fully overlapping): distance along the
24+ // flow for the first button ...
25+ const FLOW_ANCHOR_DISTANCE = 40 ;
26+
27+ // ... and how much each subsequent button is pushed further along its flow
28+ const FLOW_ANCHOR_STEP = 32 ;
29+
30+ // keep the anchor off the very end of short flows
31+ const FLOW_ANCHOR_END_MARGIN = 20 ;
32+
1133export default function InclusiveGatewayHandler ( inclusiveGatewaySettings ) {
1234 this . _inclusiveGatewaySettings = inclusiveGatewaySettings ;
1335}
@@ -32,19 +54,173 @@ InclusiveGatewayHandler.prototype.createContextPads = function(element) {
3254 </button>
3355 ` ;
3456
35- return nonDefaultFlows . map ( sequenceFlow => {
57+ return nonDefaultFlows . map ( ( sequenceFlow , index ) => {
3658 const action = ( ) => {
3759 this . _inclusiveGatewaySettings . toggleSequenceFlow ( element , sequenceFlow ) ;
3860 } ;
3961
62+ const siblings = outgoingFlows . filter ( flow => flow !== sequenceFlow ) ;
63+
4064 return {
4165 action,
4266 element : sequenceFlow ,
43- html
67+ html,
68+ position : getFlowAnchor ( sequenceFlow , index , siblings )
4469 } ;
4570 } ) ;
4671} ;
4772
4873InclusiveGatewayHandler . $inject = [
4974 'inclusiveGatewaySettings'
50- ] ;
75+ ] ;
76+
77+
78+ // helpers ///////////////
79+
80+ /**
81+ * Absolute point (diagram coordinates) at which the toggle button for a
82+ * sequence flow should be centered.
83+ *
84+ * Gateways commonly route several outgoing flows over a shared trunk (e.g. a
85+ * vertical segment) before they branch off to their targets. Anchoring the
86+ * button where the flow first separates from all its siblings puts it on a
87+ * part of the flow that is unambiguously its own, so buttons no longer pile
88+ * up near the gateway.
89+ *
90+ * @param {Object } sequenceFlow
91+ * @param {Number } index
92+ * @param {Array<Object> } siblings other outgoing flows of the same gateway
93+ *
94+ * @return {{ x: Number, y: Number }|null }
95+ */
96+ function getFlowAnchor ( sequenceFlow , index , siblings ) {
97+ const waypoints = sequenceFlow . waypoints ;
98+
99+ if ( ! waypoints || waypoints . length < 2 ) {
100+ return null ;
101+ }
102+
103+ const length = getFlowLength ( waypoints ) ;
104+
105+ // never place the anchor past the flow; keep it off the target end
106+ const maxDistance = Math . max ( length - FLOW_ANCHOR_END_MARGIN , length / 2 ) ;
107+
108+ const siblingWaypoints = siblings
109+ . map ( flow => flow . waypoints )
110+ . filter ( wps => wps && wps . length >= 2 ) ;
111+
112+ const divergence = getDivergenceDistance ( waypoints , siblingWaypoints , length ) ;
113+
114+ const distanceAlong = divergence === null
115+
116+ // flows never separate (fully overlapping / no siblings): fall back to a
117+ // fixed, per-flow-staggered distance
118+ ? FLOW_ANCHOR_DISTANCE + index * FLOW_ANCHOR_STEP
119+
120+ // place the button just past the point where the flow becomes distinct
121+ : divergence + DIVERGENCE_MARGIN ;
122+
123+ return getPointAlong ( waypoints , Math . min ( distanceAlong , maxDistance ) ) ;
124+ }
125+
126+ /**
127+ * Distance along <waypoints> at which the flow is at least SEPARATION px away
128+ * from every sibling flow, or null if that never happens.
129+ */
130+ function getDivergenceDistance ( waypoints , siblingWaypoints , length ) {
131+ if ( ! siblingWaypoints . length ) {
132+ return null ;
133+ }
134+
135+ for ( let d = 0 ; d <= length ; d += SAMPLE_STEP ) {
136+ const point = getPointAlong ( waypoints , d ) ;
137+
138+ const clearOfAll = siblingWaypoints . every (
139+ wps => getDistanceToPolyline ( point , wps ) >= SEPARATION
140+ ) ;
141+
142+ if ( clearOfAll ) {
143+ return d ;
144+ }
145+ }
146+
147+ return null ;
148+ }
149+
150+ function distance ( a , b ) {
151+ return Math . sqrt ( Math . pow ( b . x - a . x , 2 ) + Math . pow ( b . y - a . y , 2 ) ) ;
152+ }
153+
154+ /**
155+ * Shortest distance from point <p> to a polyline.
156+ */
157+ function getDistanceToPolyline ( p , waypoints ) {
158+ let min = Infinity ;
159+
160+ for ( let i = 0 ; i < waypoints . length - 1 ; i ++ ) {
161+ min = Math . min ( min , getDistanceToSegment ( p , waypoints [ i ] , waypoints [ i + 1 ] ) ) ;
162+ }
163+
164+ return min ;
165+ }
166+
167+ /**
168+ * Shortest distance from point <p> to the segment <a>-<b>.
169+ */
170+ function getDistanceToSegment ( p , a , b ) {
171+ const dx = b . x - a . x ,
172+ dy = b . y - a . y ;
173+
174+ const lengthSquared = dx * dx + dy * dy ;
175+
176+ if ( lengthSquared === 0 ) {
177+ return distance ( p , a ) ;
178+ }
179+
180+ // projection of p onto the segment, clamped to [0, 1]
181+ let t = ( ( p . x - a . x ) * dx + ( p . y - a . y ) * dy ) / lengthSquared ;
182+
183+ t = Math . max ( 0 , Math . min ( 1 , t ) ) ;
184+
185+ return distance ( p , { x : a . x + t * dx , y : a . y + t * dy } ) ;
186+ }
187+
188+ function getFlowLength ( waypoints ) {
189+ let length = 0 ;
190+
191+ for ( let i = 0 ; i < waypoints . length - 1 ; i ++ ) {
192+ length += distance ( waypoints [ i ] , waypoints [ i + 1 ] ) ;
193+ }
194+
195+ return length ;
196+ }
197+
198+ /**
199+ * Point <d> px along the polyline, measured from its start
200+ * (the gateway/source side, since waypoints run source -> target).
201+ */
202+ function getPointAlong ( waypoints , d ) {
203+ let remaining = d ;
204+
205+ for ( let i = 0 ; i < waypoints . length - 1 ; i ++ ) {
206+ const start = waypoints [ i ] ,
207+ end = waypoints [ i + 1 ] ;
208+
209+ const segment = distance ( start , end ) ;
210+
211+ if ( segment >= remaining ) {
212+ const t = segment === 0 ? 0 : remaining / segment ;
213+
214+ return {
215+ x : start . x + ( end . x - start . x ) * t ,
216+ y : start . y + ( end . y - start . y ) * t
217+ } ;
218+ }
219+
220+ remaining -= segment ;
221+ }
222+
223+ const last = waypoints [ waypoints . length - 1 ] ;
224+
225+ return { x : last . x , y : last . y } ;
226+ }
0 commit comments