[v11.0.x] Canvas: Fix division by zero (#86932)

Canvas: Fix division by zero (#85443)

* Canvas: Fix division by zero

* Fix last add vertex control

* Apply fix to existing vertices

* Apply deltas to temporary paths

* Persist original source and target coorindates

* Add follow up TODO

* Update connection svg to handle originals

* Get rid of minimum constraints for deltas

* Apply persist coordinate system to vertex handlers

* Consolidate and rename selected value

* Update connection arcs to use new coordinates

* fix editor crashing with scenes

---------

Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
(cherry picked from commit ed8eacbc7e)

Co-authored-by: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com>
pull/86934/head
grafana-delivery-bot[bot] 1 year ago committed by GitHub
parent ac7082d039
commit b95072dad2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts
  2. 2
      public/app/features/canvas/element.ts
  3. 74
      public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx
  4. 89
      public/app/plugins/panel/canvas/components/connections/Connections.tsx
  5. 2
      public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx
  6. 2
      public/app/plugins/panel/canvas/panelcfg.cue
  7. 2
      public/app/plugins/panel/canvas/panelcfg.gen.ts
  8. 2
      public/app/plugins/panel/canvas/types.ts
  9. 16
      public/app/plugins/panel/canvas/utils.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<ConnectionCoordinates>;
}

@ -60,6 +60,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
}

@ -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 } = getConnectionStyles(
info,
@ -169,8 +177,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;
@ -188,17 +196,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);
}
@ -209,8 +217,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);
}
@ -228,12 +236,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;
@ -244,8 +254,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
@ -256,15 +266,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;
}
@ -277,8 +287,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);
@ -292,8 +302,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
@ -321,7 +331,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} `;
@ -396,14 +408,13 @@ export const ConnectionSVG = ({
{isSelected && (
<g>
{vertices.map((value, index) => {
const { x, y } = calculateAbsoluteCoords(x1, y1, x2, y2, value.x, value.y);
return (
<circle
id={CONNECTION_VERTEX_ID}
data-index={index}
key={`${CONNECTION_VERTEX_ID}${index}_${idx}`}
cx={x}
cy={y}
cx={value.x * xDist + xStart}
cy={value.y * yDist + yStart}
r={5}
stroke={strokeColor}
className={styles.vertex}
@ -414,14 +425,13 @@ export const ConnectionSVG = ({
})}
{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}
cx={value.x * xDist + xStart}
cy={value.y * yDist + yStart}
r={4}
stroke={strokeColor}
className={styles.addVertex}

@ -333,32 +333,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 - 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;
}
}
@ -419,23 +428,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;
}
@ -478,29 +488,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;
}
}
@ -548,14 +570,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);
@ -572,6 +594,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();

@ -34,7 +34,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' });
}

@ -74,6 +74,8 @@ composableKinds: PanelCfg: {
color?: ui.ColorDimensionConfig
size?: ui.ScaleDimensionConfig
vertices?: [...ConnectionCoordinates]
sourceOriginal?: ConnectionCoordinates
targetOriginal?: ConnectionCoordinates
} @cuetsy(kind="interface")
CanvasElementOptions: {
name: string

@ -81,8 +81,10 @@ export interface CanvasConnection {
path: ConnectionPath;
size?: ui.ScaleDimensionConfig;
source: ConnectionCoordinates;
sourceOriginal?: ConnectionCoordinates;
target: ConnectionCoordinates;
targetName?: string;
targetOriginal?: ConnectionCoordinates;
vertices?: Array<ConnectionCoordinates>;
}

@ -42,6 +42,8 @@ export interface ConnectionState {
target: ElementState;
info: CanvasConnection;
vertices?: ConnectionCoordinates[];
sourceOriginal?: ConnectionCoordinates;
targetOriginal?: ConnectionCoordinates;
}
export enum LineStyle {

@ -298,6 +298,8 @@ export function getConnections(sceneByName: Map<string, ElementState>) {
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

Loading…
Cancel
Save