From c1065e6e68be1f1588b2df18b091a26381461fc9 Mon Sep 17 00:00:00 2001 From: Drew Slobodnjak <60050885+drew08t@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:38:17 -0700 Subject: [PATCH] Canvas: Add direction options for connections (#84620) * Canvas: Add direction options for connections * Fix radio control to handle default * Fix broken options from merge * Remove unused comment * Change category and capitalize options for arrow * Consolidate marker start and end for connections * Fix linter issue --- public/app/features/canvas/element.ts | 8 ++++ .../components/connections/ConnectionSVG.tsx | 41 ++++++++++++++++--- .../panel/canvas/editor/connectionEditor.tsx | 1 + .../plugins/panel/canvas/editor/options.ts | 23 ++++++++++- public/app/plugins/panel/canvas/utils.ts | 11 ++++- 5 files changed, 76 insertions(+), 8 deletions(-) diff --git a/public/app/features/canvas/element.ts b/public/app/features/canvas/element.ts index 82dc4ad831f..7ffd3ba85a0 100644 --- a/public/app/features/canvas/element.ts +++ b/public/app/features/canvas/element.ts @@ -42,6 +42,13 @@ export enum ConnectionPath { Straight = 'straight', } +export enum ConnectionDirection { + Forward = 'forward', + Reverse = 'reverse', + Both = 'both', + None = 'none', +} + export interface CanvasConnection { source: ConnectionCoordinates; target: ConnectionCoordinates; @@ -51,6 +58,7 @@ export interface CanvasConnection { size?: ScaleDimensionConfig; lineStyle?: string; vertices?: ConnectionCoordinates[]; + direction?: ConnectionDirection; // 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 c8f897b49bc..c2c2a4adf0e 100644 --- a/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx +++ b/public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; import { config } from 'app/core/config'; +import { ConnectionDirection } from 'app/features/canvas'; import { Scene } from 'app/features/canvas/runtime/scene'; import { ConnectionCoordinates } from '../../panelcfg.gen'; @@ -45,6 +46,7 @@ export const ConnectionSVG = ({ const EDITOR_HEAD_ID = useMemo(() => `editorHead-${headId}`, [headId]); const defaultArrowColor = config.theme2.colors.text.primary; const defaultArrowSize = 2; + const defaultArrowDirection = ConnectionDirection.Forward; const maximumVertices = 10; const [selectedConnection, setSelectedConnection] = useState(undefined); @@ -134,14 +136,20 @@ export const ConnectionSVG = ({ const { x1, y1, x2, y2 } = calculateCoordinates(sourceRect, parentRect, info, target, transformScale); const midpoint = calculateMidpoint(x1, y1, x2, y2); - const { strokeColor, strokeWidth, lineStyle } = getConnectionStyles(info, scene, defaultArrowSize); + const { strokeColor, strokeWidth, 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 = `connectionHead-${headId + Math.random()}`; + const CONNECTION_HEAD_ID_START = `connectionHeadStart-${headId + Math.random()}`; + const CONNECTION_HEAD_ID_END = `connectionHeadEnd-${headId + Math.random()}`; // Create vertex path and populate array of add vertex controls const addVertices: ConnectionCoordinates[] = []; @@ -167,12 +175,33 @@ export const ConnectionSVG = ({ pathString += `L${x2} ${y2}`; } + const markerStart = + arrowDirection === ConnectionDirection.Reverse || arrowDirection === ConnectionDirection.Both + ? `url(#${CONNECTION_HEAD_ID_START})` + : undefined; + + const markerEnd = + arrowDirection === ConnectionDirection.Forward || arrowDirection === ConnectionDirection.Both + ? `url(#${CONNECTION_HEAD_ID_END})` + : undefined; + return ( selectConnection(v)}> + + + {isSelected && ( @@ -262,8 +292,9 @@ export const ConnectionSVG = ({ stroke={strokeColor} pointerEvents="auto" strokeWidth={strokeWidth} + markerEnd={markerEnd} + markerStart={markerStart} strokeDasharray={lineStyle} - markerEnd={`url(#${CONNECTION_HEAD_ID})`} x1={x1} y1={y1} x2={x2} diff --git a/public/app/plugins/panel/canvas/editor/connectionEditor.tsx b/public/app/plugins/panel/canvas/editor/connectionEditor.tsx index 87027fb82f6..586393b1f32 100644 --- a/public/app/plugins/panel/canvas/editor/connectionEditor.tsx +++ b/public/app/plugins/panel/canvas/editor/connectionEditor.tsx @@ -36,6 +36,7 @@ export function getConnectionEditor(opts: CanvasConnectionEditorOptions): Nested const ctx = { ...context, options: opts.connection.info }; optionBuilder.addColor(builder, ctx); optionBuilder.addSize(builder, ctx); + optionBuilder.addDirection(builder, ctx); optionBuilder.addLineStyle(builder, ctx); }, }; diff --git a/public/app/plugins/panel/canvas/editor/options.ts b/public/app/plugins/panel/canvas/editor/options.ts index e09b5e049ae..0d3fa542999 100644 --- a/public/app/plugins/panel/canvas/editor/options.ts +++ b/public/app/plugins/panel/canvas/editor/options.ts @@ -1,5 +1,7 @@ +import { capitalize } from 'lodash'; + import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin'; -import { CanvasConnection, CanvasElementOptions } from 'app/features/canvas'; +import { CanvasConnection, CanvasElementOptions, ConnectionDirection } from 'app/features/canvas'; import { ColorDimensionEditor, ResourceDimensionEditor, ScaleDimensionEditor } from 'app/features/dimensions/editors'; import { BackgroundSizeEditor } from 'app/features/dimensions/editors/BackgroundSizeEditor'; @@ -12,6 +14,7 @@ interface OptionSuppliers { addBorder: PanelOptionsSupplier; addColor: PanelOptionsSupplier; addSize: PanelOptionsSupplier; + addDirection: PanelOptionsSupplier; addLineStyle: PanelOptionsSupplier; } @@ -126,6 +129,24 @@ export const optionBuilder: OptionSuppliers = { }); }, + addDirection: (builder, context) => { + const category = ['Arrow Direction']; + builder.addRadio({ + category, + path: 'direction', + name: 'Direction', + settings: { + options: [ + { value: undefined, label: capitalize(ConnectionDirection.Forward) }, + { value: ConnectionDirection.Reverse, label: capitalize(ConnectionDirection.Reverse) }, + { value: ConnectionDirection.Both, label: capitalize(ConnectionDirection.Both) }, + { value: ConnectionDirection.None, label: capitalize(ConnectionDirection.None) }, + ], + }, + defaultValue: ConnectionDirection.Forward, + }); + }, + addLineStyle: (builder, context) => { const category = ['Line style']; builder.addCustomEditor({ diff --git a/public/app/plugins/panel/canvas/utils.ts b/public/app/plugins/panel/canvas/utils.ts index ee78835dd1c..cc445b3013f 100644 --- a/public/app/plugins/panel/canvas/utils.ts +++ b/public/app/plugins/panel/canvas/utils.ts @@ -10,6 +10,7 @@ import { canvasElementRegistry, CanvasElementOptions, CanvasConnection, + ConnectionDirection, } from 'app/features/canvas'; import { notFoundItem } from 'app/features/canvas/elements/notFound'; import { ElementState } from 'app/features/canvas/runtime/element'; @@ -378,13 +379,19 @@ export const getRowIndex = (fieldName: string | undefined, scene: Scene) => { return 0; }; -export const getConnectionStyles = (info: CanvasConnection, scene: Scene, defaultArrowSize: number) => { +export const getConnectionStyles = ( + info: CanvasConnection, + scene: Scene, + defaultArrowSize: number, + defaultArrowDirection: ConnectionDirection +) => { const defaultArrowColor = config.theme2.colors.text.primary; const lastRowIndex = getRowIndex(info.size?.field, scene); const strokeColor = info.color ? scene.context.getColor(info.color).value() : defaultArrowColor; const strokeWidth = info.size ? scene.context.getScale(info.size).get(lastRowIndex) : defaultArrowSize; + const arrowDirection = info.direction ? info.direction : defaultArrowDirection; const lineStyle = info.lineStyle === LineStyle.Dashed ? StrokeDasharray.Dashed : StrokeDasharray.Solid; - return { strokeColor, strokeWidth, lineStyle }; + return { strokeColor, strokeWidth, arrowDirection, lineStyle }; }; export const getParentBoundingClientRect = (scene: Scene) => {