Skip to content

Commit b0973b2

Browse files
committed
refactor: streamline edge shortening and Manhattan routing logic by integrating getPointOnQuadraticBezier for improved path calculations.
1 parent 6fc3f42 commit b0973b2

1 file changed

Lines changed: 123 additions & 19 deletions

File tree

src/NodeCanvas.jsx

Lines changed: 123 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ import { getPortPosition, calculateStaggeredPosition } from './utils/canvas/port
8787
import { computeCleanPolylineFromPorts, generateManhattanRoutingPath, generateCleanRoutingPath } from './utils/canvas/edgeRouting.js';
8888
import * as GeometryUtils from './utils/canvas/geometryUtils.js';
8989
import EdgeRenderer from './components/EdgeRenderer.jsx';
90-
import { calculateParallelEdgePath, distanceToQuadraticBezier, calculateCurveControlPoint, getTrimmedBezierPath } from './utils/canvas/parallelEdgeUtils.js';
90+
import { calculateParallelEdgePath, distanceToQuadraticBezier, calculateCurveControlPoint, getTrimmedBezierPath, getPointOnQuadraticBezier } from './utils/canvas/parallelEdgeUtils.js';
9191
import Panel from './Panel'; // This is now used for both sides
9292
import TypeList from './TypeList'; // Re-add TypeList component
9393
import SaveStatusDisplay from './SaveStatusDisplay'; // Import the save status display
@@ -10517,13 +10517,13 @@ function NodeCanvas() {
1051710517
const isCurvedEdge = curveInfo && curveInfo.totalInPair > 1;
1051810518

1051910519
// Only shorten connections at ends with arrows or hover state
10520-
// For curved edges, never shorten for hover - only for arrows
10521-
// This ensures the curve shape stays consistent when hovered
10520+
// For curved edges, NEVER change endpoints - we use trimmed paths instead
10521+
// This ensures the curve shape stays consistent
1052210522
let shouldShortenSource = isCurvedEdge
10523-
? arrowsToward.has(sourceNode.id)
10523+
? false // Never change curve endpoints
1052410524
: (isHovered || arrowsToward.has(sourceNode.id));
1052510525
let shouldShortenDest = isCurvedEdge
10526-
? arrowsToward.has(destNode.id)
10526+
? false // Never change curve endpoints
1052710527
: (isHovered || arrowsToward.has(destNode.id));
1052810528
if (enableAutoRouting && routingStyle === 'manhattan') {
1052910529
// In Manhattan mode, never shorten for hover—only for actual arrows
@@ -10699,10 +10699,12 @@ function NodeCanvas() {
1069910699
const parallelPath = calculateParallelEdgePath(startX, startY, endX, endY, curveInfo);
1070010700
const useCurve = parallelPath.type === 'curve';
1070110701

10702-
// For hover effect on curved edges, trim the curve to create "shorten" visual
10702+
// For hover effect or arrows on curved edges, trim the curve to create "shorten" visual
1070310703
// This keeps the curve shape consistent but renders a shorter portion
1070410704
let trimmedPath = null;
10705-
if (useCurve && isHovered && parallelPath.ctrlX !== null) {
10705+
const shouldTrimCurve = useCurve && parallelPath.ctrlX !== null &&
10706+
(isHovered || arrowsToward.has(sourceNode.id) || arrowsToward.has(destNode.id));
10707+
if (shouldTrimCurve) {
1070610708
trimmedPath = getTrimmedBezierPath(
1070710709
parallelPath.startX, parallelPath.startY,
1070810710
parallelPath.ctrlX, parallelPath.ctrlY,
@@ -11187,7 +11189,57 @@ function NodeCanvas() {
1118711189
// Calculate arrow positions (use fallback if intersections fail)
1118811190
let sourceArrowX, sourceArrowY, destArrowX, destArrowY, sourceArrowAngle, destArrowAngle;
1118911191

11190-
if (enableAutoRouting && routingStyle === 'clean') {
11192+
// For curved edges, calculate arrow/dot positions along the curve
11193+
if (useCurve && parallelPath.ctrlX !== null) {
11194+
const tSource = 0.08; // Position near source (8% along curve)
11195+
const tDest = 0.92; // Position near dest (92% along curve)
11196+
11197+
// Get positions along the curve
11198+
const sourcePoint = getPointOnQuadraticBezier(
11199+
tSource,
11200+
parallelPath.startX, parallelPath.startY,
11201+
parallelPath.ctrlX, parallelPath.ctrlY,
11202+
parallelPath.endX, parallelPath.endY
11203+
);
11204+
const destPoint = getPointOnQuadraticBezier(
11205+
tDest,
11206+
parallelPath.startX, parallelPath.startY,
11207+
parallelPath.ctrlX, parallelPath.ctrlY,
11208+
parallelPath.endX, parallelPath.endY
11209+
);
11210+
11211+
sourceArrowX = sourcePoint.x;
11212+
sourceArrowY = sourcePoint.y;
11213+
destArrowX = destPoint.x;
11214+
destArrowY = destPoint.y;
11215+
11216+
// Calculate tangent angles at these points
11217+
// Derivative of quadratic Bézier: B'(t) = 2(1-t)(P1-P0) + 2t(P2-P1)
11218+
const calcTangentAngle = (t, x0, y0, cx, cy, x1, y1) => {
11219+
const invT = 1 - t;
11220+
const tangentX = 2 * invT * (cx - x0) + 2 * t * (x1 - cx);
11221+
const tangentY = 2 * invT * (cy - y0) + 2 * t * (y1 - cy);
11222+
return Math.atan2(tangentY, tangentX) * (180 / Math.PI);
11223+
};
11224+
11225+
// Source arrow points backward (toward source node)
11226+
const sourceTangent = calcTangentAngle(
11227+
tSource,
11228+
parallelPath.startX, parallelPath.startY,
11229+
parallelPath.ctrlX, parallelPath.ctrlY,
11230+
parallelPath.endX, parallelPath.endY
11231+
);
11232+
sourceArrowAngle = sourceTangent + 180; // Point back toward source
11233+
11234+
// Dest arrow points forward (toward dest node)
11235+
const destTangent = calcTangentAngle(
11236+
tDest,
11237+
parallelPath.startX, parallelPath.startY,
11238+
parallelPath.ctrlX, parallelPath.ctrlY,
11239+
parallelPath.endX, parallelPath.endY
11240+
);
11241+
destArrowAngle = destTangent; // Point toward dest
11242+
} else if (enableAutoRouting && routingStyle === 'clean') {
1119111243
// Clean mode: use actual port assignments for proper arrow positioning
1119211244
const offset = showConnectionNames ? 6 : (shouldShortenSource || shouldShortenDest ? 3 : 5);
1119311245
const portAssignment = cleanLaneOffsets.get(edge.id);
@@ -11515,8 +11567,8 @@ function NodeCanvas() {
1151511567
</g>
1151611568
)}
1151711569

11518-
{/* Hover Dots - only visible when hovering and using straight routing */}
11519-
{isHovered && (!enableAutoRouting || routingStyle === 'straight') && (
11570+
{/* Hover Dots - visible when hovering straight edges or curved parallel edges */}
11571+
{isHovered && (!enableAutoRouting || routingStyle === 'straight' || useCurve) && (
1152011572
<>
1152111573
{/* Source Dot - only show if arrow not pointing toward source */}
1152211574
{!arrowsToward.has(sourceNode.id) && (
@@ -11677,13 +11729,13 @@ function NodeCanvas() {
1167711729
const isCurvedEdge = curveInfo && curveInfo.totalInPair > 1;
1167811730

1167911731
// Only shorten connections at ends with arrows or hover state
11680-
// For curved edges, never shorten for hover - only for arrows
11681-
// This ensures the curve shape stays consistent when hovered
11732+
// For curved edges, NEVER change endpoints - we use trimmed paths instead
11733+
// This ensures the curve shape stays consistent
1168211734
let shouldShortenSource = isCurvedEdge
11683-
? arrowsToward.has(sourceNode.id)
11735+
? false // Never change curve endpoints
1168411736
: (isHovered || arrowsToward.has(sourceNode.id));
1168511737
let shouldShortenDest = isCurvedEdge
11686-
? arrowsToward.has(destNode.id)
11738+
? false // Never change curve endpoints
1168711739
: (isHovered || arrowsToward.has(destNode.id));
1168811740
if (enableAutoRouting && routingStyle === 'manhattan') {
1168911741
// In Manhattan mode, never shorten for hover—only for actual arrows
@@ -11859,10 +11911,12 @@ function NodeCanvas() {
1185911911
const parallelPath = calculateParallelEdgePath(startX, startY, endX, endY, curveInfo);
1186011912
const useCurve = parallelPath.type === 'curve';
1186111913

11862-
// For hover effect on curved edges, trim the curve to create "shorten" visual
11914+
// For hover effect or arrows on curved edges, trim the curve to create "shorten" visual
1186311915
// This keeps the curve shape consistent but renders a shorter portion
1186411916
let trimmedPath = null;
11865-
if (useCurve && isHovered && parallelPath.ctrlX !== null) {
11917+
const shouldTrimCurve = useCurve && parallelPath.ctrlX !== null &&
11918+
(isHovered || arrowsToward.has(sourceNode.id) || arrowsToward.has(destNode.id));
11919+
if (shouldTrimCurve) {
1186611920
trimmedPath = getTrimmedBezierPath(
1186711921
parallelPath.startX, parallelPath.startY,
1186811922
parallelPath.ctrlX, parallelPath.ctrlY,
@@ -12212,7 +12266,57 @@ function NodeCanvas() {
1221212266
// Calculate arrow positions (use fallback if intersections fail)
1221312267
let sourceArrowX, sourceArrowY, destArrowX, destArrowY, sourceArrowAngle, destArrowAngle;
1221412268

12215-
if (enableAutoRouting && routingStyle === 'clean') {
12269+
// For curved edges, calculate arrow/dot positions along the curve
12270+
if (useCurve && parallelPath.ctrlX !== null) {
12271+
const tSource = 0.08; // Position near source (8% along curve)
12272+
const tDest = 0.92; // Position near dest (92% along curve)
12273+
12274+
// Get positions along the curve
12275+
const sourcePoint = getPointOnQuadraticBezier(
12276+
tSource,
12277+
parallelPath.startX, parallelPath.startY,
12278+
parallelPath.ctrlX, parallelPath.ctrlY,
12279+
parallelPath.endX, parallelPath.endY
12280+
);
12281+
const destPoint = getPointOnQuadraticBezier(
12282+
tDest,
12283+
parallelPath.startX, parallelPath.startY,
12284+
parallelPath.ctrlX, parallelPath.ctrlY,
12285+
parallelPath.endX, parallelPath.endY
12286+
);
12287+
12288+
sourceArrowX = sourcePoint.x;
12289+
sourceArrowY = sourcePoint.y;
12290+
destArrowX = destPoint.x;
12291+
destArrowY = destPoint.y;
12292+
12293+
// Calculate tangent angles at these points
12294+
// Derivative of quadratic Bézier: B'(t) = 2(1-t)(P1-P0) + 2t(P2-P1)
12295+
const calcTangentAngle = (t, x0, y0, cx, cy, x1, y1) => {
12296+
const invT = 1 - t;
12297+
const tangentX = 2 * invT * (cx - x0) + 2 * t * (x1 - cx);
12298+
const tangentY = 2 * invT * (cy - y0) + 2 * t * (y1 - cy);
12299+
return Math.atan2(tangentY, tangentX) * (180 / Math.PI);
12300+
};
12301+
12302+
// Source arrow points backward (toward source node)
12303+
const sourceTangent = calcTangentAngle(
12304+
tSource,
12305+
parallelPath.startX, parallelPath.startY,
12306+
parallelPath.ctrlX, parallelPath.ctrlY,
12307+
parallelPath.endX, parallelPath.endY
12308+
);
12309+
sourceArrowAngle = sourceTangent + 180; // Point back toward source
12310+
12311+
// Dest arrow points forward (toward dest node)
12312+
const destTangent = calcTangentAngle(
12313+
tDest,
12314+
parallelPath.startX, parallelPath.startY,
12315+
parallelPath.ctrlX, parallelPath.ctrlY,
12316+
parallelPath.endX, parallelPath.endY
12317+
);
12318+
destArrowAngle = destTangent; // Point toward dest
12319+
} else if (enableAutoRouting && routingStyle === 'clean') {
1221612320
// Clean mode: use actual port assignments for proper arrow positioning
1221712321
const offset = showConnectionNames ? 6 : (shouldShortenSource || shouldShortenDest ? 3 : 5);
1221812322
const portAssignment = cleanLaneOffsets.get(edge.id);
@@ -12540,8 +12644,8 @@ function NodeCanvas() {
1254012644
</g>
1254112645
)}
1254212646

12543-
{/* Hover Dots - only visible when hovering and using straight routing */}
12544-
{isHovered && (!enableAutoRouting || routingStyle === 'straight') && (
12647+
{/* Hover Dots - visible when hovering straight edges or curved parallel edges */}
12648+
{isHovered && (!enableAutoRouting || routingStyle === 'straight' || useCurve) && (
1254512649
<>
1254612650
{/* Source Dot - only show if arrow not pointing toward source */}
1254712651
{!arrowsToward.has(sourceNode.id) && (

0 commit comments

Comments
 (0)