diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index 0b5f9574ee4..8fb27b3851c 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -83,8 +83,10 @@ export interface CanvasConnection { path: ConnectionPath; size?: ui.ScaleDimensionConfig; source: ConnectionCoordinates; + sourceOriginal?: ConnectionCoordinates; target: ConnectionCoordinates; targetName?: string; + targetOriginal?: ConnectionCoordinates; vertices?: Array; } diff --git a/public/app/features/canvas/element.ts b/public/app/features/canvas/element.ts index a3653d5ab92..371cc46ec43 100644 --- a/public/app/features/canvas/element.ts +++ b/public/app/features/canvas/element.ts @@ -61,6 +61,8 @@ export interface CanvasConnection { vertices?: ConnectionCoordinates[]; radius?: ScaleDimensionConfig; direction?: ConnectionDirection; + sourceOriginal?: ConnectionCoordinates; + targetOriginal?: ConnectionCoordinates; // See https://github.com/anseki/leader-line#options for more examples of more properties } diff --git a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx index 0c82fc3c578..5c15496e1c2 100644 --- a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx +++ b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx @@ -10,7 +10,6 @@ import { Scene } from 'app/features/canvas/runtime/scene'; import { ConnectionCoordinates } from '../../panelcfg.gen'; import { ConnectionState } from '../../types'; import { - calculateAbsoluteCoords, calculateAngle, calculateCoordinates, calculateDistance, @@ -140,9 +139,18 @@ export const ConnectionSVG = ({ } const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale); + + let { xStart, yStart, xEnd, yEnd } = { xStart: x1, yStart: y1, xEnd: x2, yEnd: y2 }; + if (v.sourceOriginal && v.targetOriginal) { + xStart = v.sourceOriginal.x; + yStart = v.sourceOriginal.y; + xEnd = v.targetOriginal.x; + yEnd = v.targetOriginal.y; + } + const midpoint = calculateMidpoint(x1, y1, x2, y2); - const xDist = x2 - x1; - const yDist = y2 - y1; + const xDist = xEnd - xStart; + const yDist = yEnd - yStart; const { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle, shouldAnimate } = getConnectionStyles(info, scene, defaultArrowSize, defaultArrowDirection); @@ -165,8 +173,8 @@ export const ConnectionSVG = ({ const y = vertex.y; // Convert vertex relative coordinates to scene coordinates - const X = x * xDist + x1; - const Y = y * yDist + y1; + const X = x * xDist + xStart; + const Y = y * yDist + yStart; // Initialize coordinates for first arc control point let xa = X; @@ -184,17 +192,17 @@ export const ConnectionSVG = ({ // 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; + const Xn = vertices[index + 1].x * xDist + xStart; + const Yn = vertices[index + 1].y * yDist + yStart; if (index === 0) { // First vertex - angle1 = calculateAngle(x1, y1, X, Y); + angle1 = calculateAngle(xStart, yStart, 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; + const Xp = previousVertex.x * xDist + xStart; + const Yp = previousVertex.y * yDist + yStart; angle1 = calculateAngle(Xp, Yp, X, Y); angle2 = calculateAngle(X, Y, Xn, Yn); } @@ -205,8 +213,8 @@ export const ConnectionSVG = ({ // Not also the first vertex previousVertex = vertices[index - 1]; } - const Xp = previousVertex.x * xDist + x1; - const Yp = previousVertex.y * yDist + y1; + const Xp = previousVertex.x * xDist + xStart; + const Yp = previousVertex.y * yDist + yStart; angle1 = calculateAngle(Xp, Yp, X, Y); angle2 = calculateAngle(X, Y, x2, y2); } @@ -224,12 +232,14 @@ export const ConnectionSVG = ({ if (index === 0) { // For first vertex - addVertices.push(calculateMidpoint(0, 0, x, y)); + addVertices.push( + calculateMidpoint((x1 - xStart) / (xEnd - xStart), (y1 - yStart) / (yEnd - yStart), x, y) + ); // Only calculate arcs if there is a radius if (radius) { // Length of segment - const lSegment = calculateDistance(X, Y, x1, y1); + const lSegment = calculateDistance(X, Y, xStart, yStart); if (Math.abs(lHalfArc) > 0.5 * Math.abs(lSegment)) { // Limit curve control points to mid segment lHalfArc = 0.5 * lSegment; @@ -240,8 +250,8 @@ export const ConnectionSVG = ({ 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; + Xn = nextVertex.x * xDist + xStart; + Yn = nextVertex.y * yDist + yStart; } // Length of next segment @@ -252,15 +262,15 @@ export const ConnectionSVG = ({ } // Calculate arc control points const lDelta = lSegment - lHalfArc; - xa = lDelta * Math.cos(angle1) + x1; - ya = lDelta * Math.sin(angle1) + y1; + xa = lDelta * Math.cos(angle1) + xStart; + ya = lDelta * Math.sin(angle1) + yStart; 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; + if ((xa > X && xa > xStart) || (xa < X && xa < xStart)) { + xa = (lDelta + 2 * lHalfArc) * Math.cos(angle1) + xStart; + ya = (lDelta + 2 * lHalfArc) * Math.sin(angle1) + yStart; xb = -lHalfArc * Math.cos(angle2) + X; yb = -lHalfArc * Math.sin(angle2) + Y; } @@ -273,8 +283,8 @@ export const ConnectionSVG = ({ // 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; + const Xp = previousVertex.x * xDist + xStart; + const Yp = previousVertex.y * yDist + yStart; // Length of segment const lSegment = calculateDistance(X, Y, Xp, Yp); @@ -288,8 +298,8 @@ export const ConnectionSVG = ({ 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; + Xn = nextVertex.x * xDist + xStart; + Yn = nextVertex.y * yDist + yStart; } // Length of next segment @@ -317,7 +327,9 @@ export const ConnectionSVG = ({ } if (index === vertices.length - 1) { // For last vertex only - addVertices.push(calculateMidpoint(1, 1, x, y)); + addVertices.push( + calculateMidpoint((x2 - xStart) / (xEnd - xStart), (y2 - yStart) / (yEnd - yStart), x, y) + ); } // Add segment to path pathString += `L${xa} ${ya} `; @@ -414,14 +426,13 @@ export const ConnectionSVG = ({ {isSelected && ( {vertices.map((value, index) => { - const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y); return ( { - const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y); return ( 0) { - vx1 += this.selection.value.vertices[this.selectedVertexIndex - 1].x * (x2 - x1); - vy1 += this.selection.value.vertices[this.selectedVertexIndex - 1].y * (y2 - y1); + vx1 = selectedValue.vertices[this.selectedVertexIndex - 1].x * xDist + xStart; + vy1 = selectedValue.vertices[this.selectedVertexIndex - 1].y * yDist + yStart; } - if ( - this.selectedVertexIndex !== undefined && - this.selectedVertexIndex < this.selection.value.vertices.length - 1 - ) { - vx2 = this.selection.value.vertices[this.selectedVertexIndex + 1].x * (x2 - x1) + x1; - vy2 = this.selection.value.vertices[this.selectedVertexIndex + 1].y * (y2 - y1) + y1; + if (this.selectedVertexIndex !== undefined && this.selectedVertexIndex < selectedValue.vertices.length - 1) { + vx2 = selectedValue.vertices[this.selectedVertexIndex + 1].x * xDist + xStart; + vy2 = selectedValue.vertices[this.selectedVertexIndex + 1].y * yDist + yStart; } } @@ -388,23 +397,24 @@ export class Connections { this.connectionSVGVertex!.style.display = 'none'; // call onChange here and update appropriate index of connection vertices array - const connectionIndex = this.selection.value?.index; + const connectionIndex = selectedValue?.index; const vertexIndex = this.selectedVertexIndex; if (connectionIndex !== undefined && vertexIndex !== undefined) { - const currentSource = this.selection.value!.source; + const currentSource = selectedValue!.source; if (currentSource.options.connections) { const currentConnections = [...currentSource.options.connections]; if (currentConnections[connectionIndex].vertices) { const currentVertices = [...currentConnections[connectionIndex].vertices!]; + // TODO for vertex removal, clear out originals? if (deleteVertex) { currentVertices.splice(vertexIndex, 1); } else { const currentVertex = { ...currentVertices[vertexIndex] }; - currentVertex.x = (xSnap - x1) / (x2 - x1); - currentVertex.y = (ySnap - y1) / (y2 - y1); + currentVertex.x = (xSnap - xStart) / xDist; + currentVertex.y = (ySnap - yStart) / yDist; currentVertices[vertexIndex] = currentVertex; } @@ -447,29 +457,41 @@ export class Connections { this.connectionVertex?.setAttribute('cx', `${x}`); this.connectionVertex?.setAttribute('cy', `${y}`); - const sourceRect = this.selection.value!.source.div!.getBoundingClientRect(); + const selectedValue = this.selection.value; + const sourceRect = selectedValue!.source.div!.getBoundingClientRect(); // calculate relative coordinates based on source and target coorindates of connection const { x1, y1, x2, y2 } = calculateCoordinates( sourceRect, parentBoundingRect, - this.selection.value?.info!, - this.selection.value!.target, + selectedValue?.info!, + selectedValue!.target, transformScale ); + let { xStart, yStart, xEnd, yEnd } = { xStart: x1, yStart: y1, xEnd: x2, yEnd: y2 }; + if (selectedValue?.sourceOriginal && selectedValue.targetOriginal) { + xStart = selectedValue.sourceOriginal.x; + yStart = selectedValue.sourceOriginal.y; + xEnd = selectedValue.targetOriginal.x; + yEnd = selectedValue.targetOriginal.y; + } + + const xDist = xEnd - xStart; + const yDist = yEnd - yStart; + let vx1 = x1; let vy1 = y1; let vx2 = x2; let vy2 = y2; - if (this.selection.value && this.selection.value.vertices) { + if (selectedValue && selectedValue.vertices) { if (this.selectedVertexIndex !== undefined && this.selectedVertexIndex > 0) { - vx1 += this.selection.value.vertices[this.selectedVertexIndex - 1].x * (x2 - x1); - vy1 += this.selection.value.vertices[this.selectedVertexIndex - 1].y * (y2 - y1); + vx1 = selectedValue.vertices[this.selectedVertexIndex - 1].x * xDist + xStart; + vy1 = selectedValue.vertices[this.selectedVertexIndex - 1].y * yDist + yStart; } - if (this.selectedVertexIndex !== undefined && this.selectedVertexIndex < this.selection.value.vertices.length) { - vx2 = this.selection.value.vertices[this.selectedVertexIndex].x * (x2 - x1) + x1; - vy2 = this.selection.value.vertices[this.selectedVertexIndex].y * (y2 - y1) + y1; + if (this.selectedVertexIndex !== undefined && this.selectedVertexIndex < selectedValue.vertices.length) { + vx2 = selectedValue.vertices[this.selectedVertexIndex].x * xDist + xStart; + vy2 = selectedValue.vertices[this.selectedVertexIndex].y * yDist + yStart; } } @@ -517,14 +539,14 @@ export class Connections { this.connectionSVGVertex!.style.display = 'none'; // call onChange here and insert new vertex at appropriate index of connection vertices array - const connectionIndex = this.selection.value?.index; + const connectionIndex = selectedValue?.index; const vertexIndex = this.selectedVertexIndex; if (connectionIndex !== undefined && vertexIndex !== undefined) { - const currentSource = this.selection.value!.source; + const currentSource = selectedValue!.source; if (currentSource.options.connections) { const currentConnections = [...currentSource.options.connections]; - const newVertex = { x: (x - x1) / (x2 - x1), y: (y - y1) / (y2 - y1) }; + const newVertex = { x: (x - xStart) / xDist, y: (y - yStart) / yDist }; if (currentConnections[connectionIndex].vertices) { const currentVertices = [...currentConnections[connectionIndex].vertices!]; currentVertices.splice(vertexIndex, 0, newVertex); @@ -541,6 +563,17 @@ export class Connections { }; } + // Check for original state + if ( + !currentConnections[connectionIndex].sourceOriginal || + !currentConnections[connectionIndex].targetOriginal + ) { + currentConnections[connectionIndex] = { + ...currentConnections[connectionIndex], + sourceOriginal: { x: x1, y: y1 }, + targetOriginal: { x: x2, y: y2 }, + }; + } // Update save model currentSource.onChange({ ...currentSource.options, connections: currentConnections }); this.updateState(); diff --git a/public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx b/public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx index cf628f07038..50acbf62220 100644 --- a/public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx +++ b/public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx @@ -35,7 +35,7 @@ export function InlineEditBody() { const pane = useMemo(() => { const p = activePanel?.panel; const state: InstanceState = instanceState; - if (!state || !p) { + if (!(state && state.scene) || !p) { return new OptionsPaneCategoryDescriptor({ id: 'root', title: 'root' }); } diff --git a/public/app/plugins/panel/canvas/panelcfg.cue b/public/app/plugins/panel/canvas/panelcfg.cue index f16ee227568..6da7cebacc4 100644 --- a/public/app/plugins/panel/canvas/panelcfg.cue +++ b/public/app/plugins/panel/canvas/panelcfg.cue @@ -74,6 +74,8 @@ composableKinds: PanelCfg: { color?: ui.ColorDimensionConfig size?: ui.ScaleDimensionConfig vertices?: [...ConnectionCoordinates] + sourceOriginal?: ConnectionCoordinates + targetOriginal?: ConnectionCoordinates } @cuetsy(kind="interface") CanvasElementOptions: { name: string diff --git a/public/app/plugins/panel/canvas/panelcfg.gen.ts b/public/app/plugins/panel/canvas/panelcfg.gen.ts index 81008dd5d06..9706c886f6d 100644 --- a/public/app/plugins/panel/canvas/panelcfg.gen.ts +++ b/public/app/plugins/panel/canvas/panelcfg.gen.ts @@ -81,8 +81,10 @@ export interface CanvasConnection { path: ConnectionPath; size?: ui.ScaleDimensionConfig; source: ConnectionCoordinates; + sourceOriginal?: ConnectionCoordinates; target: ConnectionCoordinates; targetName?: string; + targetOriginal?: ConnectionCoordinates; vertices?: Array; } diff --git a/public/app/plugins/panel/canvas/types.ts b/public/app/plugins/panel/canvas/types.ts index 775d059fee4..5acb3e5b189 100644 --- a/public/app/plugins/panel/canvas/types.ts +++ b/public/app/plugins/panel/canvas/types.ts @@ -42,6 +42,8 @@ export interface ConnectionState { target: ElementState; info: CanvasConnection; vertices?: ConnectionCoordinates[]; + sourceOriginal?: ConnectionCoordinates; + targetOriginal?: ConnectionCoordinates; } export enum LineStyle { diff --git a/public/app/plugins/panel/canvas/utils.ts b/public/app/plugins/panel/canvas/utils.ts index 8563acf8b98..da40db1f4a7 100644 --- a/public/app/plugins/panel/canvas/utils.ts +++ b/public/app/plugins/panel/canvas/utils.ts @@ -298,6 +298,8 @@ export function getConnections(sceneByName: Map) { target, info: c, vertices: c.vertices ?? undefined, + sourceOriginal: c.sourceOriginal ?? undefined, + targetOriginal: c.targetOriginal ?? undefined, }); } }); @@ -352,6 +354,14 @@ export const calculateCoordinates = ( } x2 /= transformScale; y2 /= transformScale; + + // TODO look into a better way to avoid division by zero + if (x2 - x1 === 0) { + x2 += 1; + } + if (y2 - y1 === 0) { + y2 += 1; + } return { x1, y1, x2, y2 }; }; @@ -365,9 +375,11 @@ export const calculateAbsoluteCoords = ( x2: number, y2: number, valueX: number, - valueY: number + valueY: number, + deltaX: number, + deltaY: number ) => { - return { x: valueX * (x2 - x1) + x1, y: valueY * (y2 - y1) + y1 }; + return { x: valueX * deltaX + x1, y: valueY * deltaY + y1 }; }; // Calculate angle between two points and return angle in radians