[v11.0.x] Canvas: Render selected connection last (#86931)

Canvas: Render selected connection last (#85492)

(cherry picked from commit d91dbbbef5)

Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
pull/86932/head
grafana-delivery-bot[bot] 1 year ago committed by GitHub
parent 188c4a39e9
commit ac7082d039
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 681
      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) // Figure out target and then target's relative coordinates drawing (if no target do parent)
const renderConnections = () => { const renderConnections = () => {
return scene.connections.state.map((v, idx) => { return (
const { source, target, info, vertices } = v; scene.connections.state
const sourceRect = source.div?.getBoundingClientRect(); // Render selected connection last, ensuring it is above other connections
const parent = source.div?.parentElement; .sort((_a, b) => (selectedConnection === b && scene.panel.context.instanceState.selectedConnection ? -1 : 0))
const transformScale = scene.scale; .map((v, idx) => {
const parentRect = getParentBoundingClientRect(scene); const { source, target, info, vertices } = v;
const sourceRect = source.div?.getBoundingClientRect();
if (!sourceRect || !parent || !parentRect) { const parent = source.div?.parentElement;
return; const transformScale = scene.scale;
} const parentRect = getParentBoundingClientRect(scene);
const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale); if (!sourceRect || !parent || !parentRect) {
const midpoint = calculateMidpoint(x1, y1, x2, y2); return;
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;
}
} }
if (index === 0) { const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale);
// For first vertex const midpoint = calculateMidpoint(x1, y1, x2, y2);
addVertices.push(calculateMidpoint(0, 0, x, y)); const xDist = x2 - x1;
const yDist = y2 - y1;
// 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 { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle } = getConnectionStyles(
const lSegmentNext = calculateDistance(X, Y, Xn, Yn); info,
if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegmentNext)) { scene,
// Limit curve control points to mid segment defaultArrowSize,
lHalfArc = 0.5 * lSegmentNext; defaultArrowDirection
} );
// 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 isSelected = selectedConnection === v && scene.panel.context.instanceState.selectedConnection;
const lSegmentNext = calculateDistance(X, Y, Xn, Yn);
if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegmentNext)) { const connectionCursorStyle = scene.isEditingEnabled ? 'grab' : '';
// Limit curve control points to mid segment const selectedStyles = { stroke: '#44aaff', strokeOpacity: 0.6, strokeWidth: strokeWidth + 5 };
lHalfArc = 0.5 * lSegmentNext;
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 if (index === 0) {
const lDelta = lSegment - lHalfArc; // For first vertex
xa = lDelta * Math.cos(angle1) + Xp; addVertices.push(calculateMidpoint(0, 0, x, y));
ya = lDelta * Math.sin(angle1) + Yp;
xb = lHalfArc * Math.cos(angle2) + X; // Only calculate arcs if there is a radius
yb = lHalfArc * Math.sin(angle2) + Y; if (radius) {
// Length of segment
// Check if arc control points are inside of segment, otherwise swap sign const lSegment = calculateDistance(X, Y, x1, y1);
if ((xa > X && xa > Xp) || (xa < X && xa < Xp)) { if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegment)) {
xa = (lDelta + 2 * lHalfArc) * Math.cos(angle1) + Xp; // Limit curve control points to mid segment
ya = (lDelta + 2 * lHalfArc) * Math.sin(angle1) + Yp; lHalfArc = 0.5 * lSegment;
xb = -lHalfArc * Math.cos(angle2) + X; }
yb = -lHalfArc * Math.sin(angle2) + Y; // 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
if (index === vertices.length - 1) { addVertices.push(calculateMidpoint(1, 1, x, y));
// For last vertex only }
addVertices.push(calculateMidpoint(1, 1, x, y)); // Add segment to path
} pathString += `L${xa} ${ya} `;
// Add segment to path
pathString += `L${xa} ${ya} `;
if (lHalfArc !== 0) { if (lHalfArc !== 0) {
// Add arc if applicable // Add arc if applicable
pathString += `Q ${X} ${Y} ${xb} ${yb} `; pathString += `Q ${X} ${Y} ${xb} ${yb} `;
}
});
// Add last segment
pathString += `L${x2} ${y2}`;
} }
});
// Add last segment
pathString += `L${x2} ${y2}`;
}
const markerStart = const markerStart =
arrowDirection === ConnectionDirection.Reverse || arrowDirection === ConnectionDirection.Both arrowDirection === ConnectionDirection.Reverse || arrowDirection === ConnectionDirection.Both
? `url(#${CONNECTION_HEAD_ID_START})` ? `url(#${CONNECTION_HEAD_ID_START})`
: undefined; : undefined;
const markerEnd = const markerEnd =
arrowDirection === ConnectionDirection.Forward || arrowDirection === ConnectionDirection.Both arrowDirection === ConnectionDirection.Forward || arrowDirection === ConnectionDirection.Both
? `url(#${CONNECTION_HEAD_ID_END})` ? `url(#${CONNECTION_HEAD_ID_END})`
: undefined; : undefined;
return ( return (
<svg className={styles.connection} key={idx}> <svg className={styles.connection} key={idx}>
<g onClick={() => selectConnection(v)}> <g onClick={() => selectConnection(v)}>
<defs> <defs>
<marker <marker
id={CONNECTION_HEAD_ID_START} id={CONNECTION_HEAD_ID_START}
markerWidth="10" markerWidth="10"
markerHeight="7" markerHeight="7"
refX="0" refX="0"
refY="3.5" refY="3.5"
orient="auto" orient="auto"
stroke={strokeColor} stroke={strokeColor}
> >
<polygon points="10 0, 0 3.5, 10 7" fill={strokeColor} /> <polygon points="10 0, 0 3.5, 10 7" fill={strokeColor} />
</marker> </marker>
<marker <marker
id={CONNECTION_HEAD_ID_END} id={CONNECTION_HEAD_ID_END}
markerWidth="10" markerWidth="10"
markerHeight="7" markerHeight="7"
refX="10" refX="10"
refY="3.5" refY="3.5"
orient="auto" orient="auto"
stroke={strokeColor} stroke={strokeColor}
> >
<polygon points="0 0, 10 3.5, 0 7" fill={strokeColor} /> <polygon points="0 0, 10 3.5, 0 7" fill={strokeColor} />
</marker> </marker>
</defs> </defs>
{vertices?.length ? ( {vertices?.length ? (
<g>
<path
id={`${CONNECTION_LINE_ID}_transparent`}
d={pathString}
cursor={connectionCursorStyle}
pointerEvents="auto"
stroke="transparent"
strokeWidth={15}
fill={'none'}
style={isSelected ? selectedStyles : {}}
/>
<path
d={pathString}
stroke={strokeColor}
strokeWidth={strokeWidth}
strokeDasharray={lineStyle}
fill={'none'}
markerEnd={markerEnd}
markerStart={markerStart}
/>
{isSelected && (
<g> <g>
{vertices.map((value, index) => { <path
const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y); id={`${CONNECTION_LINE_ID}_transparent`}
return ( d={pathString}
<circle cursor={connectionCursorStyle}
id={CONNECTION_VERTEX_ID} pointerEvents="auto"
data-index={index} stroke="transparent"
key={`${CONNECTION_VERTEX_ID}${index}_${idx}`} strokeWidth={15}
cx={x} fill={'none'}
cy={y} style={isSelected ? selectedStyles : {}}
r={5} />
stroke={strokeColor} <path
className={styles.vertex} d={pathString}
cursor={'crosshair'} stroke={strokeColor}
pointerEvents="auto" strokeWidth={strokeWidth}
/> strokeDasharray={lineStyle}
); fill={'none'}
})} markerEnd={markerEnd}
{vertices.length < maximumVertices && markerStart={markerStart}
addVertices.map((value, index) => { />
const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y); {isSelected && (
return ( <g>
<circle {vertices.map((value, index) => {
id={CONNECTION_VERTEX_ADD_ID} const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y);
data-index={index} return (
key={`${CONNECTION_VERTEX_ADD_ID}${index}_${idx}`} <circle
cx={x} id={CONNECTION_VERTEX_ID}
cy={y} data-index={index}
r={4} key={`${CONNECTION_VERTEX_ID}${index}_${idx}`}
stroke={strokeColor} cx={x}
className={styles.addVertex} cy={y}
cursor={'crosshair'} r={5}
pointerEvents="auto" stroke={strokeColor}
/> className={styles.vertex}
); cursor={'crosshair'}
})} pointerEvents="auto"
/>
);
})}
{vertices.length < maximumVertices &&
addVertices.map((value, index) => {
const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y);
return (
<circle
id={CONNECTION_VERTEX_ADD_ID}
data-index={index}
key={`${CONNECTION_VERTEX_ADD_ID}${index}_${idx}`}
cx={x}
cy={y}
r={4}
stroke={strokeColor}
className={styles.addVertex}
cursor={'crosshair'}
pointerEvents="auto"
/>
);
})}
</g>
)}
</g>
) : (
<g>
<line
id={`${CONNECTION_LINE_ID}_transparent`}
cursor={connectionCursorStyle}
pointerEvents="auto"
stroke="transparent"
strokeWidth={15}
style={isSelected ? selectedStyles : {}}
x1={x1}
y1={y1}
x2={x2}
y2={y2}
/>
<line
id={CONNECTION_LINE_ID}
stroke={strokeColor}
pointerEvents="auto"
strokeWidth={strokeWidth}
markerEnd={markerEnd}
markerStart={markerStart}
strokeDasharray={lineStyle}
x1={x1}
y1={y1}
x2={x2}
y2={y2}
cursor={connectionCursorStyle}
/>
{isSelected && (
<circle
id={CONNECTION_VERTEX_ADD_ID}
data-index={0}
cx={midpoint.x}
cy={midpoint.y}
r={4}
stroke={strokeColor}
className={styles.addVertex}
cursor={'crosshair'}
pointerEvents="auto"
/>
)}
</g> </g>
)} )}
</g> </g>
) : ( </svg>
<g> );
<line })
id={`${CONNECTION_LINE_ID}_transparent`} );
cursor={connectionCursorStyle}
pointerEvents="auto"
stroke="transparent"
strokeWidth={15}
style={isSelected ? selectedStyles : {}}
x1={x1}
y1={y1}
x2={x2}
y2={y2}
/>
<line
id={CONNECTION_LINE_ID}
stroke={strokeColor}
pointerEvents="auto"
strokeWidth={strokeWidth}
markerEnd={markerEnd}
markerStart={markerStart}
strokeDasharray={lineStyle}
x1={x1}
y1={y1}
x2={x2}
y2={y2}
cursor={connectionCursorStyle}
/>
{isSelected && (
<circle
id={CONNECTION_VERTEX_ADD_ID}
data-index={0}
cx={midpoint.x}
cy={midpoint.y}
r={4}
stroke={strokeColor}
className={styles.addVertex}
cursor={'crosshair'}
pointerEvents="auto"
/>
)}
</g>
)}
</g>
</svg>
);
});
}; };
return ( return (

Loading…
Cancel
Save