mirror of https://github.com/grafana/grafana.git
				
				
				
			Canvas: Add corner radius option (#84873)
* Canvas: Add corner radius option * Update connection radius logic * Simplify angle calcs * Simplify math to be a bit more clear and efficient * Add checks for hyperbola behavior * Prevent arc calcs if no radius * Add comments for SOME clarity * Add some more clarity to comments * Fix linter issue * Check for segment overlap for first vertex * Update public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx * Add comment for calc clarity
This commit is contained in:
		
							parent
							
								
									863a3d1c2c
								
							
						
					
					
						commit
						0b4830ccfd
					
				|  | @ -58,6 +58,7 @@ export interface BackgroundConfig { | ||||||
| 
 | 
 | ||||||
| export interface LineConfig { | export interface LineConfig { | ||||||
|   color?: ui.ColorDimensionConfig; |   color?: ui.ColorDimensionConfig; | ||||||
|  |   radius?: number; | ||||||
|   width?: number; |   width?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -58,6 +58,7 @@ export interface CanvasConnection { | ||||||
|   size?: ScaleDimensionConfig; |   size?: ScaleDimensionConfig; | ||||||
|   lineStyle?: string; |   lineStyle?: string; | ||||||
|   vertices?: ConnectionCoordinates[]; |   vertices?: ConnectionCoordinates[]; | ||||||
|  |   radius?: ScaleDimensionConfig; | ||||||
|   direction?: ConnectionDirection; |   direction?: ConnectionDirection; | ||||||
|   // See https://github.com/anseki/leader-line#options for more examples of more properties
 |   // See https://github.com/anseki/leader-line#options for more examples of more properties
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -358,6 +358,10 @@ export class ElementState implements LayerElement { | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (border && border.radius !== undefined) { | ||||||
|  |       css.borderRadius = `${border.radius}px`; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     this.dataStyle = css; |     this.dataStyle = css; | ||||||
|     this.applyLayoutStylesToDiv(); |     this.applyLayoutStylesToDiv(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -11,7 +11,9 @@ import { ConnectionCoordinates } from '../../panelcfg.gen'; | ||||||
| import { ConnectionState } from '../../types'; | import { ConnectionState } from '../../types'; | ||||||
| import { | import { | ||||||
|   calculateAbsoluteCoords, |   calculateAbsoluteCoords, | ||||||
|  |   calculateAngle, | ||||||
|   calculateCoordinates, |   calculateCoordinates, | ||||||
|  |   calculateDistance, | ||||||
|   calculateMidpoint, |   calculateMidpoint, | ||||||
|   getConnectionStyles, |   getConnectionStyles, | ||||||
|   getParentBoundingClientRect, |   getParentBoundingClientRect, | ||||||
|  | @ -135,8 +137,10 @@ export const ConnectionSVG = ({ | ||||||
| 
 | 
 | ||||||
|       const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale); |       const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale); | ||||||
|       const midpoint = calculateMidpoint(x1, y1, x2, y2); |       const midpoint = calculateMidpoint(x1, y1, x2, y2); | ||||||
|  |       const xDist = x2 - x1; | ||||||
|  |       const yDist = y2 - y1; | ||||||
| 
 | 
 | ||||||
|       const { strokeColor, strokeWidth, arrowDirection, lineStyle } = getConnectionStyles( |       const { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle } = getConnectionStyles( | ||||||
|         info, |         info, | ||||||
|         scene, |         scene, | ||||||
|         defaultArrowSize, |         defaultArrowSize, | ||||||
|  | @ -151,6 +155,7 @@ export const ConnectionSVG = ({ | ||||||
|       const CONNECTION_HEAD_ID_START = `connectionHeadStart-${headId + Math.random()}`; |       const CONNECTION_HEAD_ID_START = `connectionHeadStart-${headId + Math.random()}`; | ||||||
|       const CONNECTION_HEAD_ID_END = `connectionHeadEnd-${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
 |       // Create vertex path and populate array of add vertex controls
 | ||||||
|       const addVertices: ConnectionCoordinates[] = []; |       const addVertices: ConnectionCoordinates[] = []; | ||||||
|       let pathString = `M${x1} ${y1} `; |       let pathString = `M${x1} ${y1} `; | ||||||
|  | @ -158,20 +163,171 @@ export const ConnectionSVG = ({ | ||||||
|         vertices.map((vertex, index) => { |         vertices.map((vertex, index) => { | ||||||
|           const x = vertex.x; |           const x = vertex.x; | ||||||
|           const y = vertex.y; |           const y = vertex.y; | ||||||
|           pathString += `L${x * (x2 - x1) + x1} ${y * (y2 - y1) + y1} `; | 
 | ||||||
|  |           // 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) { |           if (index === 0) { | ||||||
|             // For first vertex
 |             // For first vertex
 | ||||||
|             addVertices.push(calculateMidpoint(0, 0, x, y)); |             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 { |           } else { | ||||||
|             // For all other vertices
 |             // For all other vertices
 | ||||||
|             const previousVertex = vertices[index - 1]; |             const previousVertex = vertices[index - 1]; | ||||||
|             addVertices.push(calculateMidpoint(previousVertex.x, previousVertex.y, x, y)); |             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) { |           if (index === vertices.length - 1) { | ||||||
|             // For last vertex
 |             // For last vertex only
 | ||||||
|             addVertices.push(calculateMidpoint(1, 1, x, y)); |             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} `; | ||||||
|  |           } | ||||||
|         }); |         }); | ||||||
|  |         // Add last segment
 | ||||||
|         pathString += `L${x2} ${y2}`; |         pathString += `L${x2} ${y2}`; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ import { ConnectionSVG } from './ConnectionSVG'; | ||||||
| export const CONNECTION_VERTEX_ID = 'vertex'; | export const CONNECTION_VERTEX_ID = 'vertex'; | ||||||
| export const CONNECTION_VERTEX_ADD_ID = 'vertexAdd'; | export const CONNECTION_VERTEX_ADD_ID = 'vertexAdd'; | ||||||
| const CONNECTION_VERTEX_ORTHO_TOLERANCE = 0.05; // Cartesian ratio against vertical or horizontal tolerance
 | const CONNECTION_VERTEX_ORTHO_TOLERANCE = 0.05; // Cartesian ratio against vertical or horizontal tolerance
 | ||||||
| const CONNECTION_VERTEX_SNAP_TOLERANCE = 5; // Multi-segment snapping angle in degrees to trigger vertex removal
 | const CONNECTION_VERTEX_SNAP_TOLERANCE = (5 / 180) * Math.PI; // Multi-segment snapping angle in radians to trigger vertex removal
 | ||||||
| 
 | 
 | ||||||
| export class Connections { | export class Connections { | ||||||
|   scene: Scene; |   scene: Scene; | ||||||
|  |  | ||||||
|  | @ -36,6 +36,7 @@ export function getConnectionEditor(opts: CanvasConnectionEditorOptions): Nested | ||||||
|       const ctx = { ...context, options: opts.connection.info }; |       const ctx = { ...context, options: opts.connection.info }; | ||||||
|       optionBuilder.addColor(builder, ctx); |       optionBuilder.addColor(builder, ctx); | ||||||
|       optionBuilder.addSize(builder, ctx); |       optionBuilder.addSize(builder, ctx); | ||||||
|  |       optionBuilder.addRadius(builder, ctx); | ||||||
|       optionBuilder.addDirection(builder, ctx); |       optionBuilder.addDirection(builder, ctx); | ||||||
|       optionBuilder.addLineStyle(builder, ctx); |       optionBuilder.addLineStyle(builder, ctx); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -14,6 +14,7 @@ interface OptionSuppliers { | ||||||
|   addBorder: PanelOptionsSupplier<CanvasElementOptions>; |   addBorder: PanelOptionsSupplier<CanvasElementOptions>; | ||||||
|   addColor: PanelOptionsSupplier<CanvasConnection>; |   addColor: PanelOptionsSupplier<CanvasConnection>; | ||||||
|   addSize: PanelOptionsSupplier<CanvasConnection>; |   addSize: PanelOptionsSupplier<CanvasConnection>; | ||||||
|  |   addRadius: PanelOptionsSupplier<CanvasConnection>; | ||||||
|   addDirection: PanelOptionsSupplier<CanvasConnection>; |   addDirection: PanelOptionsSupplier<CanvasConnection>; | ||||||
|   addLineStyle: PanelOptionsSupplier<CanvasConnection>; |   addLineStyle: PanelOptionsSupplier<CanvasConnection>; | ||||||
| } | } | ||||||
|  | @ -90,6 +91,17 @@ export const optionBuilder: OptionSuppliers = { | ||||||
|         }, |         }, | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     builder.addSliderInput({ | ||||||
|  |       category, | ||||||
|  |       path: 'border.radius', | ||||||
|  |       name: 'Radius', | ||||||
|  |       defaultValue: 0, | ||||||
|  |       settings: { | ||||||
|  |         min: 0, | ||||||
|  |         max: 60, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   addColor: (builder, context) => { |   addColor: (builder, context) => { | ||||||
|  | @ -129,6 +141,27 @@ export const optionBuilder: OptionSuppliers = { | ||||||
|     }); |     }); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|  |   addRadius: (builder, context) => { | ||||||
|  |     const category = ['Radius']; | ||||||
|  |     builder.addCustomEditor({ | ||||||
|  |       category, | ||||||
|  |       id: 'radius', | ||||||
|  |       path: 'radius', | ||||||
|  |       name: 'Radius', | ||||||
|  |       editor: ScaleDimensionEditor, | ||||||
|  |       settings: { | ||||||
|  |         min: 0, | ||||||
|  |         max: 200, | ||||||
|  |       }, | ||||||
|  |       defaultValue: { | ||||||
|  |         // Configured values
 | ||||||
|  |         fixed: 0, | ||||||
|  |         min: 0, | ||||||
|  |         max: 100, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|   addDirection: (builder, context) => { |   addDirection: (builder, context) => { | ||||||
|     const category = ['Arrow Direction']; |     const category = ['Arrow Direction']; | ||||||
|     builder.addRadio({ |     builder.addRadio({ | ||||||
|  |  | ||||||
|  | @ -54,6 +54,7 @@ composableKinds: PanelCfg: { | ||||||
| 				LineConfig: { | 				LineConfig: { | ||||||
| 					color?:  ui.ColorDimensionConfig | 					color?:  ui.ColorDimensionConfig | ||||||
| 					width?:  float64 | 					width?:  float64 | ||||||
|  | 					radius?: float64 | ||||||
| 				} @cuetsy(kind="interface") | 				} @cuetsy(kind="interface") | ||||||
| 
 | 
 | ||||||
| 				HttpRequestMethod: "GET" | "POST" | "PUT" @cuetsy(kind="enum", memberNames="GET|POST|PUT") | 				HttpRequestMethod: "GET" | "POST" | "PUT" @cuetsy(kind="enum", memberNames="GET|POST|PUT") | ||||||
|  |  | ||||||
|  | @ -56,6 +56,7 @@ export interface BackgroundConfig { | ||||||
| 
 | 
 | ||||||
| export interface LineConfig { | export interface LineConfig { | ||||||
|   color?: ui.ColorDimensionConfig; |   color?: ui.ColorDimensionConfig; | ||||||
|  |   radius?: number; | ||||||
|   width?: number; |   width?: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -370,8 +370,14 @@ export const calculateAbsoluteCoords = ( | ||||||
|   return { x: valueX * (x2 - x1) + x1, y: valueY * (y2 - y1) + y1 }; |   return { x: valueX * (x2 - x1) + x1, y: valueY * (y2 - y1) + y1 }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | // Calculate angle between two points and return angle in radians
 | ||||||
| export const calculateAngle = (x1: number, y1: number, x2: number, y2: number) => { | export const calculateAngle = (x1: number, y1: number, x2: number, y2: number) => { | ||||||
|   return (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI; |   return Math.atan2(y2 - y1, x2 - x1); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const calculateDistance = (x1: number, y1: number, x2: number, y2: number) => { | ||||||
|  |   //TODO add sqrt approx option
 | ||||||
|  |   return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // @TODO revisit, currently returning last row index for field
 | // @TODO revisit, currently returning last row index for field
 | ||||||
|  | @ -395,9 +401,10 @@ export const getConnectionStyles = ( | ||||||
|   const lastRowIndex = getRowIndex(info.size?.field, scene); |   const lastRowIndex = getRowIndex(info.size?.field, scene); | ||||||
|   const strokeColor = info.color ? scene.context.getColor(info.color).value() : defaultArrowColor; |   const strokeColor = info.color ? scene.context.getColor(info.color).value() : defaultArrowColor; | ||||||
|   const strokeWidth = info.size ? scene.context.getScale(info.size).get(lastRowIndex) : defaultArrowSize; |   const strokeWidth = info.size ? scene.context.getScale(info.size).get(lastRowIndex) : defaultArrowSize; | ||||||
|  |   const strokeRadius = info.radius ? scene.context.getScale(info.radius).get(lastRowIndex) : 0; | ||||||
|   const arrowDirection = info.direction ? info.direction : defaultArrowDirection; |   const arrowDirection = info.direction ? info.direction : defaultArrowDirection; | ||||||
|   const lineStyle = info.lineStyle === LineStyle.Dashed ? StrokeDasharray.Dashed : StrokeDasharray.Solid; |   const lineStyle = info.lineStyle === LineStyle.Dashed ? StrokeDasharray.Dashed : StrokeDasharray.Solid; | ||||||
|   return { strokeColor, strokeWidth, arrowDirection, lineStyle }; |   return { strokeColor, strokeWidth, strokeRadius, arrowDirection, lineStyle }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const getParentBoundingClientRect = (scene: Scene) => { | export const getParentBoundingClientRect = (scene: Scene) => { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue