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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reactflow-edge-routing",
3
- "version": "0.1.3",
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",
@@ -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 mid = Math.floor(points.length / 2);
879
- const midP = points[mid];
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];