Canvas: Add vertex control to connections (#83653)

* Canvas: Add vertex control to connections

* Add function for vertex conversion

* Add vertex interface

* Add future vertex handling

* Only show vertices when connection selected

* Add vertices to save model

* Apply select constraint to first midpoint

* Add some infrastructure for vertex edit

* Render vertex edit and capture events

* Save vertex edit on button release

* Handle adding new vertices

* Limit number of vertices to 10

* Handle zoom for vertex edit and creation

* Rename future to add

* Remove more references to future

* Remove unsued console log

* Clean up styles

* Add some clarity for path generation

* Add clarity to connections event handling

---------

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
Drew Slobodnjak
2024-03-15 09:35:07 -07:00
committed by GitHub
co-authored by nmarrs
parent 6d74553653
commit f273681956
10 changed files with 457 additions and 31 deletions
@@ -83,8 +83,13 @@ export interface CanvasConnection {
source: ConnectionCoordinates;
target: ConnectionCoordinates;
targetName?: string;
vertices?: Array<ConnectionCoordinates>;
}
export const defaultCanvasConnection: Partial<CanvasConnection> = {
vertices: [],
};
export interface CanvasElementOptions {
background?: BackgroundConfig;
border?: LineConfig;
+1
View File
@@ -49,6 +49,7 @@ export interface CanvasConnection {
path: ConnectionPath;
color?: ColorDimensionConfig;
size?: ScaleDimensionConfig;
vertices?: ConnectionCoordinates[];
// See https://github.com/anseki/leader-line#options for more examples of more properties
}
+19 -1
View File
@@ -28,7 +28,11 @@ import {
} from 'app/features/dimensions/utils';
import { CanvasContextMenu } from 'app/plugins/panel/canvas/components/CanvasContextMenu';
import { CanvasTooltip } from 'app/plugins/panel/canvas/components/CanvasTooltip';
import { CONNECTION_ANCHOR_DIV_ID } from 'app/plugins/panel/canvas/components/connections/ConnectionAnchors';
import {
CONNECTION_ANCHOR_DIV_ID,
CONNECTION_VERTEX_ADD_ID,
CONNECTION_VERTEX_ID,
} from 'app/plugins/panel/canvas/components/connections/ConnectionAnchors';
import { Connections } from 'app/plugins/panel/canvas/components/connections/Connections';
import { AnchorPoint, CanvasTooltipPayload, LayerActionID } from 'app/plugins/panel/canvas/types';
import { getParent, getTransformInstance } from 'app/plugins/panel/canvas/utils';
@@ -615,6 +619,20 @@ export class Scene {
return;
}
// If selected target is a vertex, eject to handle vertex event
if (selectedTarget.id === CONNECTION_VERTEX_ID) {
this.connections.handleVertexDragStart(selectedTarget);
event.stop();
return;
}
// If selected target is an add vertex point, eject to handle add vertex event
if (selectedTarget.id === CONNECTION_VERTEX_ADD_ID) {
this.connections.handleVertexAddDragStart(selectedTarget);
event.stop();
return;
}
const isTargetMoveableElement =
this.moveable!.isMoveableElement(selectedTarget) ||
targets.some((target) => target === selectedTarget || target.contains(selectedTarget));
@@ -15,6 +15,8 @@ type Props = {
export const CONNECTION_ANCHOR_DIV_ID = 'connectionControl';
export const CONNECTION_ANCHOR_ALT = 'connection anchor';
export const CONNECTION_ANCHOR_HIGHLIGHT_OFFSET = 8;
export const CONNECTION_VERTEX_ID = 'vertex';
export const CONNECTION_VERTEX_ADD_ID = 'vertexAdd';
const ANCHOR_PADDING = 3;
@@ -6,19 +6,38 @@ import { useStyles2 } from '@grafana/ui';
import { config } from 'app/core/config';
import { Scene } from 'app/features/canvas/runtime/scene';
import { ConnectionCoordinates } from '../../panelcfg.gen';
import { ConnectionState } from '../../types';
import { calculateCoordinates, getConnectionStyles, getParentBoundingClientRect } from '../../utils';
import {
calculateAbsoluteCoords,
calculateCoordinates,
calculateMidpoint,
getConnectionStyles,
getParentBoundingClientRect,
} from '../../utils';
import { CONNECTION_VERTEX_ADD_ID, CONNECTION_VERTEX_ID } from './ConnectionAnchors';
type Props = {
setSVGRef: (anchorElement: SVGSVGElement) => void;
setLineRef: (anchorElement: SVGLineElement) => void;
setSVGVertexRef: (anchorElement: SVGSVGElement) => void;
setVertexPathRef: (anchorElement: SVGPathElement) => void;
setVertexRef: (anchorElement: SVGCircleElement) => void;
scene: Scene;
};
let idCounter = 0;
const htmlElementTypes = ['input', 'textarea'];
export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
export const ConnectionSVG = ({
setSVGRef,
setLineRef,
setSVGVertexRef,
setVertexPathRef,
setVertexRef,
scene,
}: Props) => {
const styles = useStyles2(getStyles);
const headId = Date.now() + '_' + idCounter++;
@@ -26,6 +45,7 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
const EDITOR_HEAD_ID = useMemo(() => `editorHead-${headId}`, [headId]);
const defaultArrowColor = config.theme2.colors.text.primary;
const defaultArrowSize = 2;
const maximumVertices = 10;
const [selectedConnection, setSelectedConnection] = useState<ConnectionState | undefined>(undefined);
@@ -101,7 +121,7 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
// 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 } = v;
const { source, target, info, vertices } = v;
const sourceRect = source.div?.getBoundingClientRect();
const parent = source.div?.parentElement;
const transformScale = scene.scale;
@@ -112,6 +132,7 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
}
const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale);
const midpoint = calculateMidpoint(x1, y1, x2, y2);
const { strokeColor, strokeWidth } = getConnectionStyles(info, scene, defaultArrowSize);
@@ -122,6 +143,30 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
const CONNECTION_HEAD_ID = `connectionHead-${headId + Math.random()}`;
// 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;
pathString += `L${x * (x2 - x1) + x1} ${y * (y2 - y1) + y1} `;
if (index === 0) {
// For first vertex
addVertices.push(calculateMidpoint(0, 0, x, y));
} else {
// For all other vertices
const previousVertex = vertices[index - 1];
addVertices.push(calculateMidpoint(previousVertex.x, previousVertex.y, x, y));
}
if (index === vertices.length - 1) {
// For last vertex
addVertices.push(calculateMidpoint(1, 1, x, y));
}
});
pathString += `L${x2} ${y2}`;
}
return (
<svg className={styles.connection} key={idx}>
<g onClick={() => selectConnection(v)}>
@@ -138,30 +183,106 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
<polygon points="0 0, 10 3.5, 0 7" fill={strokeColor} />
</marker>
</defs>
<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={`url(#${CONNECTION_HEAD_ID})`}
x1={x1}
y1={y1}
x2={x2}
y2={y2}
cursor={connectionCursorStyle}
/>
{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}
fill={'none'}
markerEnd={`url(#${CONNECTION_HEAD_ID})`}
/>
{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}
r={5}
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={`url(#${CONNECTION_HEAD_ID})`}
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>
);
@@ -186,6 +307,16 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
</defs>
<line ref={setLineRef} stroke={defaultArrowColor} strokeWidth={2} markerEnd={`url(#${EDITOR_HEAD_ID})`} />
</svg>
<svg ref={setSVGVertexRef} className={styles.editorSVG}>
<path
ref={setVertexPathRef}
stroke={defaultArrowColor}
strokeWidth={2}
strokeDasharray={'5, 5'}
fill={'none'}
/>
<circle ref={setVertexRef} stroke={defaultArrowColor} r={4} className={styles.vertex} />
</svg>
{renderConnections()}
</>
);
@@ -207,4 +338,13 @@ const getStyles = (theme: GrafanaTheme2) => ({
zIndex: 1000,
pointerEvents: 'none',
}),
vertex: css({
fill: '#44aaff',
strokeWidth: 2,
}),
addVertex: css({
fill: '#44aaff',
opacity: 0.5,
strokeWidth: 1,
}),
});
@@ -2,12 +2,18 @@ import React from 'react';
import { BehaviorSubject } from 'rxjs';
import { config } from '@grafana/runtime';
import { CanvasConnection, ConnectionPath } from 'app/features/canvas';
import { CanvasConnection, ConnectionCoordinates, ConnectionPath } from 'app/features/canvas';
import { ElementState } from 'app/features/canvas/runtime/element';
import { Scene } from 'app/features/canvas/runtime/scene';
import { ConnectionState } from '../../types';
import { getConnections, getParentBoundingClientRect, isConnectionSource, isConnectionTarget } from '../../utils';
import {
calculateCoordinates,
getConnections,
getParentBoundingClientRect,
isConnectionSource,
isConnectionTarget,
} from '../../utils';
import { CONNECTION_ANCHOR_ALT, ConnectionAnchors, CONNECTION_ANCHOR_HIGHLIGHT_OFFSET } from './ConnectionAnchors';
import { ConnectionSVG } from './ConnectionSVG';
@@ -17,9 +23,13 @@ export class Connections {
connectionAnchorDiv?: HTMLDivElement;
connectionSVG?: SVGElement;
connectionLine?: SVGLineElement;
connectionSVGVertex?: SVGElement;
connectionVertexPath?: SVGPathElement;
connectionVertex?: SVGCircleElement;
connectionSource?: ElementState;
connectionTarget?: ElementState;
isDrawingConnection?: boolean;
selectedVertexIndex?: number;
didConnectionLeaveHighlight?: boolean;
state: ConnectionState[] = [];
readonly selection = new BehaviorSubject<ConnectionState | undefined>(undefined);
@@ -62,6 +72,18 @@ export class Connections {
this.connectionLine = connectionLine;
};
setConnectionSVGVertexRef = (connectionSVG: SVGSVGElement) => {
this.connectionSVGVertex = connectionSVG;
};
setConnectionVertexRef = (connectionVertex: SVGCircleElement) => {
this.connectionVertex = connectionVertex;
};
setConnectionVertexPathRef = (connectionVertexPath: SVGPathElement) => {
this.connectionVertexPath = connectionVertexPath;
};
// Recursively find the first parent that is a canvas element
findElementTarget = (element: Element): ElementState | undefined => {
let elementTarget = undefined;
@@ -251,6 +273,192 @@ export class Connections {
}
};
// Handles mousemove and mouseup events when dragging an existing vertex
vertexListener = (event: MouseEvent) => {
event.preventDefault();
if (!(this.connectionVertex && this.scene.div && this.scene.div.parentElement)) {
return;
}
const transformScale = this.scene.scale;
const parentBoundingRect = getParentBoundingClientRect(this.scene);
if (!parentBoundingRect) {
return;
}
const x = (event.pageX - parentBoundingRect.x) / transformScale ?? 0;
const y = (event.pageY - parentBoundingRect.y) / transformScale ?? 0;
this.connectionVertex?.setAttribute('cx', `${x}`);
this.connectionVertex?.setAttribute('cy', `${y}`);
const sourceRect = this.selection.value!.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,
transformScale
);
let vx1 = x1;
let vy1 = y1;
let vx2 = x2;
let vy2 = y2;
if (this.selection.value && this.selection.value.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);
}
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;
}
}
// Display temporary vertex during drag
this.connectionVertexPath?.setAttribute('d', `M${vx1} ${vy1} L${x} ${y} L${vx2} ${vy2}`);
this.connectionSVGVertex!.style.display = 'block';
// Handle mouseup
if (!event.buttons) {
// Remove existing event listener
this.scene.selecto?.rootContainer?.removeEventListener('mousemove', this.vertexListener);
this.scene.selecto?.rootContainer?.removeEventListener('mouseup', this.vertexListener);
this.scene.selecto!.rootContainer!.style.cursor = 'auto';
this.connectionSVGVertex!.style.display = 'none';
// call onChange here and update appropriate index of connection vertices array
const connectionIndex = this.selection.value?.index;
const vertexIndex = this.selectedVertexIndex;
if (connectionIndex !== undefined && vertexIndex !== undefined) {
const currentSource = this.scene.connections.state[connectionIndex].source;
if (currentSource.options.connections) {
const currentConnections = [...currentSource.options.connections];
if (currentConnections[connectionIndex].vertices) {
const currentVertices = [...currentConnections[connectionIndex].vertices!];
const currentVertex = { ...currentVertices[vertexIndex] };
currentVertex.x = (x - x1) / (x2 - x1);
currentVertex.y = (y - y1) / (y2 - y1);
currentVertices[vertexIndex] = currentVertex;
currentConnections[connectionIndex] = {
...currentConnections[connectionIndex],
vertices: currentVertices,
};
// Update save model
currentSource.onChange({ ...currentSource.options, connections: currentConnections });
this.updateState();
this.scene.save();
}
}
}
}
};
// Handles mousemove and mouseup events when dragging a new vertex
vertexAddListener = (event: MouseEvent) => {
event.preventDefault();
if (!(this.connectionVertex && this.scene.div && this.scene.div.parentElement)) {
return;
}
const transformScale = this.scene.scale;
const parentBoundingRect = getParentBoundingClientRect(this.scene);
if (!parentBoundingRect) {
return;
}
const x = (event.pageX - parentBoundingRect.x) / transformScale ?? 0;
const y = (event.pageY - parentBoundingRect.y) / transformScale ?? 0;
this.connectionVertex?.setAttribute('cx', `${x}`);
this.connectionVertex?.setAttribute('cy', `${y}`);
const sourceRect = this.selection.value!.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,
transformScale
);
let vx1 = x1;
let vy1 = y1;
let vx2 = x2;
let vy2 = y2;
if (this.selection.value && this.selection.value.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);
}
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;
}
}
// Display temporary vertex during drag
this.connectionVertexPath?.setAttribute('d', `M${vx1} ${vy1} L${x} ${y} L${vx2} ${vy2}`);
this.connectionSVGVertex!.style.display = 'block';
// Handle mouseup
if (!event.buttons) {
// Remove existing event listener
this.scene.selecto?.rootContainer?.removeEventListener('mousemove', this.vertexAddListener);
this.scene.selecto?.rootContainer?.removeEventListener('mouseup', this.vertexAddListener);
this.scene.selecto!.rootContainer!.style.cursor = 'auto';
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 vertexIndex = this.selectedVertexIndex;
if (connectionIndex !== undefined && vertexIndex !== undefined) {
const currentSource = this.scene.connections.state[connectionIndex].source;
if (currentSource.options.connections) {
const currentConnections = [...currentSource.options.connections];
const newVertex = { x: (x - x1) / (x2 - x1), y: (y - y1) / (y2 - y1) };
if (currentConnections[connectionIndex].vertices) {
const currentVertices = [...currentConnections[connectionIndex].vertices!];
currentVertices.splice(vertexIndex, 0, newVertex);
currentConnections[connectionIndex] = {
...currentConnections[connectionIndex],
vertices: currentVertices,
};
} else {
// For first vertex creation
const currentVertices: ConnectionCoordinates[] = [newVertex];
currentConnections[connectionIndex] = {
...currentConnections[connectionIndex],
vertices: currentVertices,
};
}
// Update save model
currentSource.onChange({ ...currentSource.options, connections: currentConnections });
this.updateState();
this.scene.save();
}
}
}
};
handleConnectionDragStart = (selectedTarget: HTMLElement, clientX: number, clientY: number) => {
this.scene.selecto!.rootContainer!.style.cursor = 'crosshair';
if (this.connectionSVG && this.connectionLine && this.scene.div && this.scene.div.parentElement) {
@@ -283,6 +491,26 @@ export class Connections {
this.scene.selecto?.rootContainer?.addEventListener('mousemove', this.connectionListener);
};
// Add event listener at root container during existing vertex drag
handleVertexDragStart = (selectedTarget: HTMLElement) => {
// Get vertex index from selected target data
this.selectedVertexIndex = Number(selectedTarget.getAttribute('data-index'));
this.scene.selecto!.rootContainer!.style.cursor = 'crosshair';
this.scene.selecto?.rootContainer?.addEventListener('mousemove', this.vertexListener);
this.scene.selecto?.rootContainer?.addEventListener('mouseup', this.vertexListener);
};
// Add event listener at root container during creation of new vertex
handleVertexAddDragStart = (selectedTarget: HTMLElement) => {
// Get vertex index from selected target data
this.selectedVertexIndex = Number(selectedTarget.getAttribute('data-index'));
this.scene.selecto!.rootContainer!.style.cursor = 'crosshair';
this.scene.selecto?.rootContainer?.addEventListener('mousemove', this.vertexAddListener);
this.scene.selecto?.rootContainer?.addEventListener('mouseup', this.vertexAddListener);
};
onChange = (current: ConnectionState, update: CanvasConnection) => {
const connections = current.source.options.connections?.splice(0) ?? [];
connections[current.index] = update;
@@ -299,7 +527,14 @@ export class Connections {
return (
<>
<ConnectionAnchors setRef={this.setConnectionAnchorRef} handleMouseLeave={this.handleMouseLeave} />
<ConnectionSVG setSVGRef={this.setConnectionSVGRef} setLineRef={this.setConnectionLineRef} scene={this.scene} />
<ConnectionSVG
setSVGRef={this.setConnectionSVGRef}
setLineRef={this.setConnectionLineRef}
setSVGVertexRef={this.setConnectionSVGVertexRef}
setVertexPathRef={this.setConnectionVertexPathRef}
setVertexRef={this.setConnectionVertexRef}
scene={this.scene}
/>
</>
);
}
@@ -70,6 +70,7 @@ composableKinds: PanelCfg: {
path: ConnectionPath
color?: ui.ColorDimensionConfig
size?: ui.ScaleDimensionConfig
vertices?: [...ConnectionCoordinates]
} @cuetsy(kind="interface")
CanvasElementOptions: {
name: string
@@ -81,8 +81,13 @@ export interface CanvasConnection {
source: ConnectionCoordinates;
target: ConnectionCoordinates;
targetName?: string;
vertices?: Array<ConnectionCoordinates>;
}
export const defaultCanvasConnection: Partial<CanvasConnection> = {
vertices: [],
};
export interface CanvasElementOptions {
background?: BackgroundConfig;
border?: LineConfig;
+3
View File
@@ -1,6 +1,8 @@
import { CanvasConnection } from '../../../features/canvas';
import { ElementState } from '../../../features/canvas/runtime/element';
import { ConnectionCoordinates } from './panelcfg.gen';
export enum LayerActionID {
Delete = 'delete',
Duplicate = 'duplicate',
@@ -39,4 +41,5 @@ export interface ConnectionState {
source: ElementState;
target: ElementState;
info: CanvasConnection;
vertices?: ConnectionCoordinates[];
}
+16
View File
@@ -290,6 +290,7 @@ export function getConnections(sceneByName: Map<string, ElementState>) {
source: v,
target,
info: c,
vertices: c.vertices ?? undefined,
});
}
});
@@ -347,6 +348,21 @@ export const calculateCoordinates = (
return { x1, y1, x2, y2 };
};
export const calculateMidpoint = (x1: number, y1: number, x2: number, y2: number) => {
return { x: (x1 + x2) / 2, y: (y1 + y2) / 2 };
};
export const calculateAbsoluteCoords = (
x1: number,
y1: number,
x2: number,
y2: number,
valueX: number,
valueY: number
) => {
return { x: valueX * (x2 - x1) + x1, y: valueY * (y2 - y1) + y1 };
};
// @TODO revisit, currently returning last row index for field
export const getRowIndex = (fieldName: string | undefined, scene: Scene) => {
if (fieldName) {