reactflow-edge-routing 0.1.3 → 0.1.4
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 +1 -1
- package/src/routing-core.ts +59 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reactflow-edge-routing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Orthogonal edge routing for React Flow using obstacle-router. Edges route around nodes while pins stay fixed at their anchor points.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/routing-core.ts
CHANGED
|
@@ -199,6 +199,59 @@ function getRouterFlags(connectorType: ConnectorType | undefined): number {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
// ---- Label Positioning Helpers ----
|
|
203
|
+
|
|
204
|
+
/** Walk path points and return the point at fraction t (0–1) of total arc length. */
|
|
205
|
+
function pointAtFraction(points: { x: number; y: number }[], t: number): { x: number; y: number } {
|
|
206
|
+
if (points.length === 1) return points[0];
|
|
207
|
+
let total = 0;
|
|
208
|
+
for (let i = 1; i < points.length; i++) {
|
|
209
|
+
const dx = points[i].x - points[i - 1].x;
|
|
210
|
+
const dy = points[i].y - points[i - 1].y;
|
|
211
|
+
total += Math.sqrt(dx * dx + dy * dy);
|
|
212
|
+
}
|
|
213
|
+
const target = total * Math.max(0, Math.min(1, t));
|
|
214
|
+
let walked = 0;
|
|
215
|
+
for (let i = 1; i < points.length; i++) {
|
|
216
|
+
const dx = points[i].x - points[i - 1].x;
|
|
217
|
+
const dy = points[i].y - points[i - 1].y;
|
|
218
|
+
const segLen = Math.sqrt(dx * dx + dy * dy);
|
|
219
|
+
if (walked + segLen >= target) {
|
|
220
|
+
const frac = segLen > 0 ? (target - walked) / segLen : 0;
|
|
221
|
+
return { x: points[i - 1].x + dx * frac, y: points[i - 1].y + dy * frac };
|
|
222
|
+
}
|
|
223
|
+
walked += segLen;
|
|
224
|
+
}
|
|
225
|
+
return points[points.length - 1];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* For each edge that has routed points, compute the fraction t (0–1) along
|
|
230
|
+
* the path where its label should sit. Edges sharing the same source handle
|
|
231
|
+
* are staggered so their labels don't overlap.
|
|
232
|
+
*/
|
|
233
|
+
function buildLabelFractions(
|
|
234
|
+
edges: FlowEdge[],
|
|
235
|
+
edgePoints: Map<string, { x: number; y: number }[]>
|
|
236
|
+
): Map<string, number> {
|
|
237
|
+
const groups = new Map<string, string[]>();
|
|
238
|
+
for (const edge of edges) {
|
|
239
|
+
if (!edgePoints.has(edge.id)) continue;
|
|
240
|
+
const key = `${edge.source}|${edge.sourceHandle ?? ""}`;
|
|
241
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
242
|
+
groups.get(key)!.push(edge.id);
|
|
243
|
+
}
|
|
244
|
+
const fractions = new Map<string, number>();
|
|
245
|
+
for (const group of groups.values()) {
|
|
246
|
+
const n = group.length;
|
|
247
|
+
const step = Math.min(0.12, 0.4 / Math.max(1, n - 1));
|
|
248
|
+
for (let i = 0; i < n; i++) {
|
|
249
|
+
fractions.set(group[i], 0.5 + (i - (n - 1) / 2) * step);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return fractions;
|
|
253
|
+
}
|
|
254
|
+
|
|
202
255
|
// ---- Geometry ----
|
|
203
256
|
|
|
204
257
|
export class Geometry {
|
|
@@ -868,6 +921,7 @@ export class RoutingEngine {
|
|
|
868
921
|
}
|
|
869
922
|
|
|
870
923
|
const connType = opts.connectorType ?? "orthogonal";
|
|
924
|
+
const labelFractions = buildLabelFractions(edges, edgePoints);
|
|
871
925
|
for (const [edgeId, points] of edgePoints) {
|
|
872
926
|
const edgeRounding = opts.edgeRounding ?? 0;
|
|
873
927
|
const path = connType === "bezier"
|
|
@@ -875,8 +929,8 @@ export class RoutingEngine {
|
|
|
875
929
|
: edgeRounding > 0
|
|
876
930
|
? PathBuilder.polylineToPath(points.length, (i) => points[i], { cornerRadius: edgeRounding })
|
|
877
931
|
: PathBuilder.pointsToSvgPath(points);
|
|
878
|
-
const
|
|
879
|
-
const midP = points
|
|
932
|
+
const t = labelFractions.get(edgeId) ?? 0.5;
|
|
933
|
+
const midP = pointAtFraction(points, t);
|
|
880
934
|
const labelP = gridSize > 0 ? Geometry.snapToGrid(midP.x, midP.y, gridSize) : midP;
|
|
881
935
|
const first = points[0];
|
|
882
936
|
const last = points[points.length - 1];
|
|
@@ -1037,6 +1091,7 @@ export class PersistentRouter {
|
|
|
1037
1091
|
}
|
|
1038
1092
|
|
|
1039
1093
|
const connType = opts.connectorType ?? "orthogonal";
|
|
1094
|
+
const labelFractions = buildLabelFractions(this.prevEdges, edgePoints);
|
|
1040
1095
|
for (const [edgeId, points] of edgePoints) {
|
|
1041
1096
|
const edgeRounding = opts.edgeRounding ?? 0;
|
|
1042
1097
|
const path = connType === "bezier"
|
|
@@ -1044,10 +1099,8 @@ export class PersistentRouter {
|
|
|
1044
1099
|
: edgeRounding > 0
|
|
1045
1100
|
? PathBuilder.polylineToPath(points.length, (i) => points[i], { cornerRadius: edgeRounding })
|
|
1046
1101
|
: PathBuilder.pointsToSvgPath(points);
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const mid = Math.floor(points.length / 2);
|
|
1050
|
-
const midP = points[mid];
|
|
1102
|
+
const t = labelFractions.get(edgeId) ?? 0.5;
|
|
1103
|
+
const midP = pointAtFraction(points, t);
|
|
1051
1104
|
const labelP = gridSize > 0 ? Geometry.snapToGrid(midP.x, midP.y, gridSize) : midP;
|
|
1052
1105
|
const first = points[0];
|
|
1053
1106
|
const last = points[points.length - 1];
|