From ac7082d039b0b4151a0fc323376c255fb9547d2b Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 07:51:11 -0700 Subject: [PATCH] [v11.0.x] Canvas: Render selected connection last (#86931) Canvas: Render selected connection last (#85492) (cherry picked from commit d91dbbbef54e789f4318da69fcb711e674d1928b) Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> --- .../components/connections/ConnectionSVG.tsx | 681 +++++++++--------- 1 file changed, 343 insertions(+), 338 deletions(-) diff --git a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx index 45c315a06c8..e5fde9ef6aa 100644 --- a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx +++ b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx @@ -124,358 +124,363 @@ export const ConnectionSVG = ({ // Figure out target and then target's relative coordinates drawing (if no target do parent) const renderConnections = () => { - return scene.connections.state.map((v, idx) => { - const { source, target, info, vertices } = v; - const sourceRect = source.div?.getBoundingClientRect(); - const parent = source.div?.parentElement; - const transformScale = scene.scale; - const parentRect = getParentBoundingClientRect(scene); - - if (!sourceRect || !parent || !parentRect) { - return; - } - - const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale); - const midpoint = calculateMidpoint(x1, y1, x2, y2); - const xDist = x2 - x1; - const yDist = y2 - y1; - - const { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle } = getConnectionStyles( - info, - scene, - defaultArrowSize, - defaultArrowDirection - ); - - const isSelected = selectedConnection === v && scene.panel.context.instanceState.selectedConnection; - - const connectionCursorStyle = scene.isEditingEnabled ? 'grab' : ''; - const selectedStyles = { stroke: '#44aaff', strokeOpacity: 0.6, strokeWidth: strokeWidth + 5 }; - - const CONNECTION_HEAD_ID_START = `connectionHeadStart-${headId + Math.random()}`; - const CONNECTION_HEAD_ID_END = `connectionHeadEnd-${headId + Math.random()}`; - - const radius = strokeRadius; - // Create vertex path and populate array of add vertex controls - const addVertices: ConnectionCoordinates[] = []; - let pathString = `M${x1} ${y1} `; - if (vertices?.length) { - vertices.map((vertex, index) => { - const x = vertex.x; - const y = vertex.y; - - // Convert vertex relative coordinates to scene coordinates - const X = x * xDist + x1; - const Y = y * yDist + y1; - - // Initialize coordinates for first arc control point - let xa = X; - let ya = Y; - - // Initialize coordinates for second arc control point - let xb = X; - let yb = Y; - - // Initialize half arc distance and segment angles - let lHalfArc = 0; - let angle1 = 0; - let angle2 = 0; - - // Only calculate arcs if there is a radius - if (radius) { - if (index < vertices.length - 1) { - const Xn = vertices[index + 1].x * xDist + x1; - const Yn = vertices[index + 1].y * yDist + y1; - if (index === 0) { - // First vertex - angle1 = calculateAngle(x1, y1, X, Y); - angle2 = calculateAngle(X, Y, Xn, Yn); - } else { - // All vertices - const previousVertex = vertices[index - 1]; - const Xp = previousVertex.x * xDist + x1; - const Yp = previousVertex.y * yDist + y1; - angle1 = calculateAngle(Xp, Yp, X, Y); - angle2 = calculateAngle(X, Y, Xn, Yn); - } - } else { - // Last vertex - let previousVertex = { x: 0, y: 0 }; - if (index > 0) { - // Not also the first vertex - previousVertex = vertices[index - 1]; - } - const Xp = previousVertex.x * xDist + x1; - const Yp = previousVertex.y * yDist + y1; - angle1 = calculateAngle(Xp, Yp, X, Y); - angle2 = calculateAngle(X, Y, x2, y2); - } - - // Calculate angle between two segments where arc will be placed - const theta = angle2 - angle1; //radians - // Attempt to determine if arc is counter clockwise (ccw) - const ccw = theta < 0; - // Half arc is used for arc control points - lHalfArc = radius * Math.tan(theta / 2); - if (ccw) { - lHalfArc *= -1; - } + return ( + scene.connections.state + // Render selected connection last, ensuring it is above other connections + .sort((_a, b) => (selectedConnection === b && scene.panel.context.instanceState.selectedConnection ? -1 : 0)) + .map((v, idx) => { + const { source, target, info, vertices } = v; + const sourceRect = source.div?.getBoundingClientRect(); + const parent = source.div?.parentElement; + const transformScale = scene.scale; + const parentRect = getParentBoundingClientRect(scene); + + if (!sourceRect || !parent || !parentRect) { + return; } - if (index === 0) { - // For first vertex - addVertices.push(calculateMidpoint(0, 0, x, y)); - - // Only calculate arcs if there is a radius - if (radius) { - // Length of segment - const lSegment = calculateDistance(X, Y, x1, y1); - if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegment)) { - // Limit curve control points to mid segment - lHalfArc = 0.5 * lSegment; - } - // Default next point to last point - let Xn = x2; - let Yn = y2; - if (index < vertices.length - 1) { - // Not also the last point - const nextVertex = vertices[index + 1]; - Xn = nextVertex.x * xDist + x1; - Yn = nextVertex.y * yDist + y1; - } + const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale); + const midpoint = calculateMidpoint(x1, y1, x2, y2); + const xDist = x2 - x1; + const yDist = y2 - y1; - // Length of next segment - const lSegmentNext = calculateDistance(X, Y, Xn, Yn); - if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegmentNext)) { - // Limit curve control points to mid segment - lHalfArc = 0.5 * lSegmentNext; - } - // Calculate arc control points - const lDelta = lSegment - lHalfArc; - xa = lDelta * Math.cos(angle1) + x1; - ya = lDelta * Math.sin(angle1) + y1; - xb = lHalfArc * Math.cos(angle2) + X; - yb = lHalfArc * Math.sin(angle2) + Y; - - // Check if arc control points are inside of segment, otherwise swap sign - if ((xa > X && xa > x1) || (xa < X && xa < x1)) { - xa = (lDelta + 2 * lHalfArc) * Math.cos(angle1) + x1; - ya = (lDelta + 2 * lHalfArc) * Math.sin(angle1) + y1; - xb = -lHalfArc * Math.cos(angle2) + X; - yb = -lHalfArc * Math.sin(angle2) + Y; - } - } - } else { - // For all other vertices - const previousVertex = vertices[index - 1]; - addVertices.push(calculateMidpoint(previousVertex.x, previousVertex.y, x, y)); - - // Only calculate arcs if there is a radius - if (radius) { - // Convert previous vertex relative coorindates to scene coordinates - const Xp = previousVertex.x * xDist + x1; - const Yp = previousVertex.y * yDist + y1; - - // Length of segment - const lSegment = calculateDistance(X, Y, Xp, Yp); - if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegment)) { - // Limit curve control points to mid segment - lHalfArc = 0.5 * lSegment; - } - // Default next point to last point - let Xn = x2; - let Yn = y2; - if (index < vertices.length - 1) { - // Not also the last point - const nextVertex = vertices[index + 1]; - Xn = nextVertex.x * xDist + x1; - Yn = nextVertex.y * yDist + y1; - } + const { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle } = getConnectionStyles( + info, + scene, + defaultArrowSize, + defaultArrowDirection + ); - // Length of next segment - const lSegmentNext = calculateDistance(X, Y, Xn, Yn); - if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegmentNext)) { - // Limit curve control points to mid segment - lHalfArc = 0.5 * lSegmentNext; + const isSelected = selectedConnection === v && scene.panel.context.instanceState.selectedConnection; + + const connectionCursorStyle = scene.isEditingEnabled ? 'grab' : ''; + const selectedStyles = { stroke: '#44aaff', strokeOpacity: 0.6, strokeWidth: strokeWidth + 5 }; + + const CONNECTION_HEAD_ID_START = `connectionHeadStart-${headId + Math.random()}`; + const CONNECTION_HEAD_ID_END = `connectionHeadEnd-${headId + Math.random()}`; + + const radius = strokeRadius; + // Create vertex path and populate array of add vertex controls + const addVertices: ConnectionCoordinates[] = []; + let pathString = `M${x1} ${y1} `; + if (vertices?.length) { + vertices.map((vertex, index) => { + const x = vertex.x; + const y = vertex.y; + + // Convert vertex relative coordinates to scene coordinates + const X = x * xDist + x1; + const Y = y * yDist + y1; + + // Initialize coordinates for first arc control point + let xa = X; + let ya = Y; + + // Initialize coordinates for second arc control point + let xb = X; + let yb = Y; + + // Initialize half arc distance and segment angles + let lHalfArc = 0; + let angle1 = 0; + let angle2 = 0; + + // Only calculate arcs if there is a radius + if (radius) { + if (index < vertices.length - 1) { + const Xn = vertices[index + 1].x * xDist + x1; + const Yn = vertices[index + 1].y * yDist + y1; + if (index === 0) { + // First vertex + angle1 = calculateAngle(x1, y1, X, Y); + angle2 = calculateAngle(X, Y, Xn, Yn); + } else { + // All vertices + const previousVertex = vertices[index - 1]; + const Xp = previousVertex.x * xDist + x1; + const Yp = previousVertex.y * yDist + y1; + angle1 = calculateAngle(Xp, Yp, X, Y); + angle2 = calculateAngle(X, Y, Xn, Yn); + } + } else { + // Last vertex + let previousVertex = { x: 0, y: 0 }; + if (index > 0) { + // Not also the first vertex + previousVertex = vertices[index - 1]; + } + const Xp = previousVertex.x * xDist + x1; + const Yp = previousVertex.y * yDist + y1; + angle1 = calculateAngle(Xp, Yp, X, Y); + angle2 = calculateAngle(X, Y, x2, y2); + } + + // Calculate angle between two segments where arc will be placed + const theta = angle2 - angle1; //radians + // Attempt to determine if arc is counter clockwise (ccw) + const ccw = theta < 0; + // Half arc is used for arc control points + lHalfArc = radius * Math.tan(theta / 2); + if (ccw) { + lHalfArc *= -1; + } } - // Calculate arc control points - const lDelta = lSegment - lHalfArc; - xa = lDelta * Math.cos(angle1) + Xp; - ya = lDelta * Math.sin(angle1) + Yp; - xb = lHalfArc * Math.cos(angle2) + X; - yb = lHalfArc * Math.sin(angle2) + Y; - - // Check if arc control points are inside of segment, otherwise swap sign - if ((xa > X && xa > Xp) || (xa < X && xa < Xp)) { - xa = (lDelta + 2 * lHalfArc) * Math.cos(angle1) + Xp; - ya = (lDelta + 2 * lHalfArc) * Math.sin(angle1) + Yp; - xb = -lHalfArc * Math.cos(angle2) + X; - yb = -lHalfArc * Math.sin(angle2) + Y; + if (index === 0) { + // For first vertex + addVertices.push(calculateMidpoint(0, 0, x, y)); + + // Only calculate arcs if there is a radius + if (radius) { + // Length of segment + const lSegment = calculateDistance(X, Y, x1, y1); + if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegment)) { + // Limit curve control points to mid segment + lHalfArc = 0.5 * lSegment; + } + // Default next point to last point + let Xn = x2; + let Yn = y2; + if (index < vertices.length - 1) { + // Not also the last point + const nextVertex = vertices[index + 1]; + Xn = nextVertex.x * xDist + x1; + Yn = nextVertex.y * yDist + y1; + } + + // Length of next segment + const lSegmentNext = calculateDistance(X, Y, Xn, Yn); + if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegmentNext)) { + // Limit curve control points to mid segment + lHalfArc = 0.5 * lSegmentNext; + } + // Calculate arc control points + const lDelta = lSegment - lHalfArc; + xa = lDelta * Math.cos(angle1) + x1; + ya = lDelta * Math.sin(angle1) + y1; + xb = lHalfArc * Math.cos(angle2) + X; + yb = lHalfArc * Math.sin(angle2) + Y; + + // Check if arc control points are inside of segment, otherwise swap sign + if ((xa > X && xa > x1) || (xa < X && xa < x1)) { + xa = (lDelta + 2 * lHalfArc) * Math.cos(angle1) + x1; + ya = (lDelta + 2 * lHalfArc) * Math.sin(angle1) + y1; + xb = -lHalfArc * Math.cos(angle2) + X; + yb = -lHalfArc * Math.sin(angle2) + Y; + } + } + } else { + // For all other vertices + const previousVertex = vertices[index - 1]; + addVertices.push(calculateMidpoint(previousVertex.x, previousVertex.y, x, y)); + + // Only calculate arcs if there is a radius + if (radius) { + // Convert previous vertex relative coorindates to scene coordinates + const Xp = previousVertex.x * xDist + x1; + const Yp = previousVertex.y * yDist + y1; + + // Length of segment + const lSegment = calculateDistance(X, Y, Xp, Yp); + if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegment)) { + // Limit curve control points to mid segment + lHalfArc = 0.5 * lSegment; + } + // Default next point to last point + let Xn = x2; + let Yn = y2; + if (index < vertices.length - 1) { + // Not also the last point + const nextVertex = vertices[index + 1]; + Xn = nextVertex.x * xDist + x1; + Yn = nextVertex.y * yDist + y1; + } + + // Length of next segment + const lSegmentNext = calculateDistance(X, Y, Xn, Yn); + if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegmentNext)) { + // Limit curve control points to mid segment + lHalfArc = 0.5 * lSegmentNext; + } + + // Calculate arc control points + const lDelta = lSegment - lHalfArc; + xa = lDelta * Math.cos(angle1) + Xp; + ya = lDelta * Math.sin(angle1) + Yp; + xb = lHalfArc * Math.cos(angle2) + X; + yb = lHalfArc * Math.sin(angle2) + Y; + + // Check if arc control points are inside of segment, otherwise swap sign + if ((xa > X && xa > Xp) || (xa < X && xa < Xp)) { + xa = (lDelta + 2 * lHalfArc) * Math.cos(angle1) + Xp; + ya = (lDelta + 2 * lHalfArc) * Math.sin(angle1) + Yp; + xb = -lHalfArc * Math.cos(angle2) + X; + yb = -lHalfArc * Math.sin(angle2) + Y; + } + } } - } - } - if (index === vertices.length - 1) { - // For last vertex only - addVertices.push(calculateMidpoint(1, 1, x, y)); - } - // Add segment to path - pathString += `L${xa} ${ya} `; + if (index === vertices.length - 1) { + // For last vertex only + addVertices.push(calculateMidpoint(1, 1, x, y)); + } + // Add segment to path + pathString += `L${xa} ${ya} `; - if (lHalfArc !== 0) { - // Add arc if applicable - pathString += `Q ${X} ${Y} ${xb} ${yb} `; + if (lHalfArc !== 0) { + // Add arc if applicable + pathString += `Q ${X} ${Y} ${xb} ${yb} `; + } + }); + // Add last segment + pathString += `L${x2} ${y2}`; } - }); - // Add last segment - pathString += `L${x2} ${y2}`; - } - const markerStart = - arrowDirection === ConnectionDirection.Reverse || arrowDirection === ConnectionDirection.Both - ? `url(#${CONNECTION_HEAD_ID_START})` - : undefined; - - const markerEnd = - arrowDirection === ConnectionDirection.Forward || arrowDirection === ConnectionDirection.Both - ? `url(#${CONNECTION_HEAD_ID_END})` - : undefined; - - return ( - - selectConnection(v)}> - - - - - - - - - {vertices?.length ? ( - - - - {isSelected && ( + const markerStart = + arrowDirection === ConnectionDirection.Reverse || arrowDirection === ConnectionDirection.Both + ? `url(#${CONNECTION_HEAD_ID_START})` + : undefined; + + const markerEnd = + arrowDirection === ConnectionDirection.Forward || arrowDirection === ConnectionDirection.Both + ? `url(#${CONNECTION_HEAD_ID_END})` + : undefined; + + return ( + + selectConnection(v)}> + + + + + + + + + {vertices?.length ? ( - {vertices.map((value, index) => { - const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y); - return ( - - ); - })} - {vertices.length < maximumVertices && - addVertices.map((value, index) => { - const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y); - return ( - - ); - })} + + + {isSelected && ( + + {vertices.map((value, index) => { + const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y); + return ( + + ); + })} + {vertices.length < maximumVertices && + addVertices.map((value, index) => { + const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y); + return ( + + ); + })} + + )} + + ) : ( + + + + {isSelected && ( + + )} )} - ) : ( - - - - {isSelected && ( - - )} - - )} - - - ); - }); + + ); + }) + ); }; return (