diff --git a/README.md b/README.md
index d3708de8..a5fb0dd6 100644
--- a/README.md
+++ b/README.md
@@ -198,6 +198,79 @@ const Example = (
### Readonly Mode
[stories/ReadonlyMode.tsx](./stories/ReadonlyMode.tsx)
+### Tooltips
+[stories/Tooltips.tsx](./stories/Tooltips.tsx)
+
+You can add tooltips by adding `tooltipsGlobal` into the chartState (`IChart`) or `tooltip` to the node objects.
+`tooltipsGlobal` will apply for all nodes and `tooltip` for individual nodes.
+
+#### Example
+
+```tsx
+
+export const tooltipChart: IChart = {
+ tooltipsGlobal: {
+ showTooltip: true,
+ toogleOffWhenClicked: 'global',
+ text: 'This is the global tooltip and will be toggled off, when clicked',
+ },
+ offset: {
+ x: 0,
+ y: 0,
+ },
+ scale: 1,
+ nodes: {
+ node1: {
+ tooltip: {
+ showTooltip: true,
+ text: 'this is the tooltip for node1',
+ },
+...
+ node2: {
+ tooltip: {
+ showTooltip: true,
+ toogleOffWhenClicked: 'node',
+ text: 'this is the tooltip for node2 and will be toggled off when clicked',
+ },
+ id: 'node2',
+...
+ node3: {
+ tooltip: {
+ showTooltip: false,
+ text: 'this is the tooltip for node3 but its off',
+ },
+ id: 'node3',
+ type: 'input-output',
+ position: {
+ x: 100,
+ y: 600,
+ },
+...
+```
+You can also customize the tooltipComponent by adding it to Component props of flowChart:
+
+```tsx
+const ExampleToolTipComponent = (props: ITooltipComponentDefaultProps) => {
+ return (
+
+
{props.tooltip}
+
+ )
+}
+
+export class Tooltips extends React.Component {
+...
+ return (
+
+
+ )
+}
+
+```
+
### Other Demos
[stories/ExternalReactState.tsx](./stories)
diff --git a/package-lock.json b/package-lock.json
index daf3120a..088385a7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15688,8 +15688,7 @@
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"react-json-view": {
"version": "1.19.1",
@@ -15820,6 +15819,32 @@
"prop-types": "^15.6.0"
}
},
+ "react-tooltip": {
+ "version": "4.2.10",
+ "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.10.tgz",
+ "integrity": "sha512-D7ZLx6/QwpUl0SZRek3IZy/HWpsEEp0v3562tcT8IwZgu8IgV7hY5ZzniTkHyRcuL+IQnljpjj7A7zCgl2+T3w==",
+ "requires": {
+ "prop-types": "^15.7.2",
+ "uuid": "^7.0.3"
+ },
+ "dependencies": {
+ "prop-types": {
+ "version": "15.7.2",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
+ "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.8.1"
+ }
+ },
+ "uuid": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
+ "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="
+ }
+ }
+ },
"react-zoom-pan-pinch": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-1.6.1.tgz",
diff --git a/package.json b/package.json
index 61cd1374..cb3bc302 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"pathfinding": "^0.4.18",
"react-draggable": "^4.4.3",
"react-resize-observer": "^1.1.1",
+ "react-tooltip": "^4.2.10",
"react-zoom-pan-pinch": "^1.6.1",
"uuid": "^3.3.2"
},
diff --git a/src/components/FlowChart/FlowChart.tsx b/src/components/FlowChart/FlowChart.tsx
index 16490922..2de0fb02 100644
--- a/src/components/FlowChart/FlowChart.tsx
+++ b/src/components/FlowChart/FlowChart.tsx
@@ -1,10 +1,10 @@
import * as React from 'react'
import {
- CanvasInnerDefault, CanvasOuterDefault, CanvasWrapper, ICanvasInnerDefaultProps, ICanvasOuterDefaultProps, IChart, IConfig, ILink,
- ILinkDefaultProps, INodeDefaultProps, INodeInnerDefaultProps, IOnCanvasClick, IOnCanvasDrop, IOnDeleteKey, IOnDragCanvas,
- IOnDragCanvasStop, IOnDragNode, IOnDragNodeStop, IOnLinkCancel, IOnLinkClick, IOnLinkComplete, IOnLinkMouseEnter,
- IOnLinkMouseLeave, IOnLinkMove, IOnLinkStart, IOnNodeClick, IOnNodeDoubleClick, IOnNodeMouseEnter, IOnNodeMouseLeave, IOnNodeSizeChange,
- IOnPortPositionChange, IOnZoomCanvas, IPortDefaultProps, IPortsDefaultProps, ISelectedOrHovered, LinkDefault, LinkWrapper, NodeDefault, NodeInnerDefault, NodeWrapper, PortDefault, PortsDefault,
+ CanvasInnerDefault, CanvasOuterDefault, CanvasWrapper, ICanvasInnerDefaultProps, ICanvasOuterDefaultProps, IChart, IConfig, IDeleteTooltip,
+ ILink, ILinkDefaultProps, INodeDefaultProps, INodeInnerDefaultProps, IOnCanvasClick, IOnCanvasDrop, IOnDeleteKey, IOnDragCanvas, IOnDragCanvasStop, IOnDragNode, IOnDragNodeStop,
+ IOnLinkCancel, IOnLinkClick, IOnLinkComplete, IOnLinkMouseEnter, IOnLinkMouseLeave, IOnLinkMove, IOnLinkStart, IOnNodeClick, IOnNodeDoubleClick,
+ IOnNodeMouseEnter, IOnNodeMouseLeave, IOnNodeSizeChange, IOnPortPositionChange, IOnZoomCanvas, IPortDefaultProps, IPortsDefaultProps, ISelectedOrHovered, IToggletooltip,
+ ITooltipComponentDefaultProps, LinkDefault, LinkWrapper, NodeDefault, NodeInnerDefault, NodeWrapper, PortDefault, PortsDefault, TooltipComponentDefault,
} from '../../'
import { getMatrix } from './utils/grid'
@@ -29,7 +29,9 @@ export interface IFlowChartCallbacks {
onNodeMouseEnter: IOnNodeMouseEnter
onNodeMouseLeave: IOnNodeMouseLeave
onNodeSizeChange: IOnNodeSizeChange
- onZoomCanvas: IOnZoomCanvas
+ onZoomCanvas: IOnZoomCanvas,
+ deleteTooltip: IDeleteTooltip,
+ toggleTooltip: IToggletooltip,
}
export interface IFlowChartComponents {
@@ -40,6 +42,7 @@ export interface IFlowChartComponents {
Port?: React.FunctionComponent
Node?: React.FunctionComponent
Link?: React.FunctionComponent
+ TooltipComponent?: React.FunctionComponent
}
export interface IFlowChartProps {
@@ -90,6 +93,8 @@ export const FlowChart = (props: IFlowChartProps) => {
onNodeMouseLeave,
onNodeSizeChange,
onZoomCanvas,
+ deleteTooltip,
+ toggleTooltip,
},
Components: {
CanvasOuter = CanvasOuterDefault,
@@ -99,6 +104,7 @@ export const FlowChart = (props: IFlowChartProps) => {
Port = PortDefault,
Node = NodeDefault,
Link = LinkDefault,
+ TooltipComponent = TooltipComponentDefault,
} = {},
config = {},
} = props
@@ -106,7 +112,7 @@ export const FlowChart = (props: IFlowChartProps) => {
const canvasCallbacks = { onDragCanvas, onDragCanvasStop, onCanvasClick, onDeleteKey, onCanvasDrop, onZoomCanvas }
const linkCallbacks = { onLinkMouseEnter, onLinkMouseLeave, onLinkClick }
- const nodeCallbacks = { onDragNode, onNodeClick, onDragNodeStop, onNodeMouseEnter, onNodeMouseLeave, onNodeSizeChange,onNodeDoubleClick }
+ const nodeCallbacks = { onDragNode, onNodeClick, onDragNodeStop, onNodeMouseEnter, onNodeMouseLeave, onNodeSizeChange,onNodeDoubleClick, deleteTooltip, toggleTooltip }
const portCallbacks = { onPortPositionChange, onLinkStart, onLinkMove, onLinkComplete, onLinkCancel }
const nodesInView = Object.keys(nodes).filter((nodeId) => {
@@ -171,12 +177,18 @@ export const FlowChart = (props: IFlowChartProps) => {
const selectedLink = getSelectedLinkForNode(selected, nodeId, links)
const hoveredLink = getSelectedLinkForNode(hovered, nodeId, links)
+ const nodeWithGlobalTooltip = { ...nodes[nodeId] }
+ if (chart.tooltipsGlobal && chart.tooltipsGlobal.showTooltip) {
+ nodeWithGlobalTooltip.tooltip = chart.tooltipsGlobal
+ }
+
return (
{
endPos,
...props,
}
-
return config.showArrowHead
?
:
diff --git a/src/components/Node/Node.wrapper.tsx b/src/components/Node/Node.wrapper.tsx
index 2f3ccb25..552f6828 100644
--- a/src/components/Node/Node.wrapper.tsx
+++ b/src/components/Node/Node.wrapper.tsx
@@ -1,8 +1,10 @@
import * as React from 'react'
import Draggable, { DraggableData } from 'react-draggable'
import ResizeObserver from 'react-resize-observer'
+import ReactTooltip from 'react-tooltip'
import {
IConfig,
+ IDeleteTooltip,
ILink,
INode,
INodeInnerDefaultProps,
@@ -23,16 +25,21 @@ import {
IPosition,
ISelectedOrHovered,
ISize,
+ IToggletooltip,
+ ITooltipComponentDefaultProps,
PortWrapper,
+ TooltipComponentDefault,
} from '../../'
import { noop } from '../../utils'
import CanvasContext from '../Canvas/CanvasContext'
+import { TooltipComponentWrapper } from '../TooltipComponent/TooltipComponent.Wrapper'
import { INodeDefaultProps, NodeDefault } from './Node.default'
export interface INodeWrapperProps {
config: IConfig
node: INode
Component: React.FunctionComponent
+ TooltipComponent?: React.FunctionComponent
offset: IPosition
selected: ISelectedOrHovered | undefined
hovered: ISelectedOrHovered | undefined
@@ -54,6 +61,8 @@ export interface INodeWrapperProps {
onNodeSizeChange: IOnNodeSizeChange
onNodeMouseEnter: IOnNodeMouseEnter
onNodeMouseLeave: IOnNodeMouseLeave
+ deleteTooltip: IDeleteTooltip
+ toggleTooltip: IToggletooltip
}
export const NodeWrapper = ({
@@ -65,6 +74,7 @@ export const NodeWrapper = ({
onNodeDoubleClick,
isSelected,
Component = NodeDefault,
+ TooltipComponent = TooltipComponentDefault,
onNodeSizeChange,
onNodeMouseEnter,
onNodeMouseLeave,
@@ -81,6 +91,8 @@ export const NodeWrapper = ({
onLinkMove,
onLinkComplete,
onLinkCancel,
+ toggleTooltip,
+ deleteTooltip,
}: INodeWrapperProps) => {
const { zoomScale } = React.useContext(CanvasContext)
const [size, setSize] = React.useState({ width: 0, height: 0 })
@@ -119,6 +131,12 @@ export const NodeWrapper = ({
onNodeClick({ config, nodeId: node.id })
}
}
+ if (node.tooltip && node.tooltip.showTooltip) {
+ switch (node.tooltip.toogleOffWhenClicked) {
+ case 'global': toggleTooltip({ nodeId: 'global' }); break
+ case 'node' : toggleTooltip({ nodeId: node.id }); break
+ }
+ }
},
[config, node.id],
)
@@ -157,8 +175,16 @@ export const NodeWrapper = ({
}
}, [node, compRef.current, size.width, size.height])
+ let tooltip = ''
+ if (node.tooltip && node.tooltip.showTooltip) {
+ tooltip = node.tooltip.text
+ }
const children = (
-
+
{
const newSize = { width: rect.width, height: rect.height }
@@ -189,6 +215,9 @@ export const NodeWrapper = ({
/>
))}
+
+ {tooltip && }
+
)
diff --git a/src/components/TooltipComponent/TooltipComponent.Wrapper.tsx b/src/components/TooltipComponent/TooltipComponent.Wrapper.tsx
new file mode 100644
index 00000000..6dce7763
--- /dev/null
+++ b/src/components/TooltipComponent/TooltipComponent.Wrapper.tsx
@@ -0,0 +1,20 @@
+import * as React from 'react'
+import { ITooltipComponentDefaultProps, TooltipComponentDefault } from './TooltipComponent.default'
+
+export const TooltipComponentWrapper =
+ ({
+ Component = TooltipComponentDefault,
+ tooltip,
+ }: ITooltipComponentWrapperProps) => {
+ return (
+ <>
+
+ >
+ )
+ }
+export interface ITooltipComponentWrapperProps {
+ Component: React.FunctionComponent
+ className?: string
+ tooltip: string
+ style?: object
+}
diff --git a/src/components/TooltipComponent/TooltipComponent.default.tsx b/src/components/TooltipComponent/TooltipComponent.default.tsx
new file mode 100644
index 00000000..17c187c5
--- /dev/null
+++ b/src/components/TooltipComponent/TooltipComponent.default.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react'
+
+export const TooltipComponentDefault = ({ tooltip }: ITooltipComponentDefaultProps) => {
+ return (
+ <>{tooltip}>
+ )
+}
+export interface ITooltipComponentDefaultProps {
+ className?: string
+ tooltip: string
+ style?: object
+}
diff --git a/src/components/TooltipComponent/index.ts b/src/components/TooltipComponent/index.ts
new file mode 100644
index 00000000..57b328aa
--- /dev/null
+++ b/src/components/TooltipComponent/index.ts
@@ -0,0 +1 @@
+export * from './TooltipComponent.default'
diff --git a/src/components/index.ts b/src/components/index.ts
index d8886094..43175508 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -6,3 +6,4 @@ export * from './Ports'
export * from './PortsGroup'
export * from './Link'
export * from './FlowChart'
+export * from './TooltipComponent'
diff --git a/src/container/actions.ts b/src/container/actions.ts
index abb496d0..f86589d3 100644
--- a/src/container/actions.ts
+++ b/src/container/actions.ts
@@ -1,6 +1,6 @@
import {
IChart,
- IConfig,
+ IConfig, IDeleteTooltip,
identity,
IOnCanvasClick,
IOnCanvasDrop,
@@ -23,7 +23,7 @@ import {
IOnNodeSizeChange,
IOnPortPositionChange,
IOnZoomCanvas,
- IStateCallback,
+ IStateCallback, IToggletooltip,
} from '../'
import { rotate } from './utils/rotate'
@@ -293,3 +293,40 @@ export const onZoomCanvas: IOnZoomCanvas = ({ config, data }) => (chart: IChart)
chart.scale = data.scale
return chart
}
+
+export const deleteTooltip: IDeleteTooltip = ({ nodeId }) => (chart: IChart): IChart => {
+ switch (nodeId) {
+ case 'global':
+ delete chart.tooltipsGlobal
+ break
+ case undefined:
+ break
+ default:
+ delete chart.nodes[nodeId].tooltip
+ break
+ }
+
+ return chart
+}
+
+export const toggleTooltip: IToggletooltip = ({ nodeId }) => (chart: IChart): IChart => {
+ if (nodeId === 'global') {
+ if (chart.tooltipsGlobal) {
+ chart.tooltipsGlobal.showTooltip ?
+ chart.tooltipsGlobal.showTooltip = false :
+ chart.tooltipsGlobal.showTooltip = true
+ }
+ }
+ if (nodeId && nodeId !== 'global') {
+ if (chart.nodes[nodeId] && chart.nodes[nodeId].tooltip) {
+ // typescript doesn't understand, that there is a check for undefined above.
+ // @ts-ignore
+ chart.nodes[nodeId].tooltip.showTooltip ?
+ // @ts-ignore
+ chart.nodes[nodeId].tooltip.showTooltip = false :
+ // @ts-ignore
+ chart.nodes[nodeId].tooltip.showTooltip = true
+ }
+ }
+ return chart
+}
diff --git a/src/types/chart.ts b/src/types/chart.ts
index 61406a32..cca9e566 100644
--- a/src/types/chart.ts
+++ b/src/types/chart.ts
@@ -17,6 +17,7 @@ export type IChart<
/** System Temp */
selected: ISelectedOrHovered
hovered: ISelectedOrHovered,
+ tooltipsGlobal?: ITooltipConfig,
} & (ChartProps extends undefined ? {
properties?: any,
} : {
@@ -29,6 +30,7 @@ export interface ISelectedOrHovered {
}
export type INode = {
+ tooltip?: ITooltipConfig,
id: string
type: string
position: IPosition
@@ -74,3 +76,9 @@ export type ILink = {
} : {
properties: LinkProps,
})
+
+interface ITooltipConfig {
+ showTooltip: boolean,
+ toogleOffWhenClicked?: 'global' | 'node'
+ text: string,
+}
diff --git a/src/types/functions.ts b/src/types/functions.ts
index 1db5461d..743f7351 100644
--- a/src/types/functions.ts
+++ b/src/types/functions.ts
@@ -116,3 +116,7 @@ export interface IOnCanvasDropInput {
export type IOnCanvasDrop = (input: IOnCanvasDropInput) => void
export type IOnZoomCanvas = (input: { config?: IConfig; data: any }) => void
+
+export type IDeleteTooltip = (input: { nodeId?: string }) => void
+
+export type IToggletooltip = (input: { nodeId?: string }) => void
diff --git a/stories/Tooltips.tsx b/stories/Tooltips.tsx
new file mode 100644
index 00000000..cf150fdc
--- /dev/null
+++ b/stories/Tooltips.tsx
@@ -0,0 +1,59 @@
+import { cloneDeep, mapValues } from 'lodash'
+import * as React from 'react'
+import { FlowChart } from '../src'
+import { ITooltipComponentDefaultProps } from '../src/components/TooltipComponent/TooltipComponent.default'
+import * as actions from '../src/container/actions'
+import { Page } from './components'
+import { tooltipChart } from './misc/tooltipChartState'
+
+/**
+ * State is external to the Element
+ *
+ * You could easily move this state to Redux or similar by creating your own callback actions.
+ */
+
+const ExampleToolTipComponent = (props: ITooltipComponentDefaultProps) => {
+ return (
+
+
{props.tooltip}
+
+ )
+}
+
+export class Tooltips extends React.Component {
+ public state = cloneDeep(tooltipChart)
+ public render () {
+ const chart = this.state
+ const stateActions = mapValues(actions, (func: any) =>
+ (...args: any) => this.setState(func(...args))) as typeof actions
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/stories/index.tsx b/stories/index.tsx
index 90061abf..650c4f20 100644
--- a/stories/index.tsx
+++ b/stories/index.tsx
@@ -12,13 +12,14 @@ import { DragAndDropSidebar } from './DragAndDropSidebar'
import { ExternalReactState } from './ExternalReactState'
import { InternalReactState } from './InternalReactState'
import { LinkColors } from './LinkColors'
-import { NodeReadonly } from './NodeReadonly'
import { LinkWithArrowHead } from './LinkWithArrowHead'
+import { NodeReadonly } from './NodeReadonly'
import { ReadonlyMode } from './ReadonlyMode'
import { SelectableMode } from './SelectableMode'
import { SelectedSidebar } from './SelectedSidebar'
import { SmartRouting } from './SmartRouting'
import { StressTestDemo } from './StressTest'
+import { Tooltips } from './Tooltips'
import { Zoom } from './Zoom'
storiesOf('State', module)
@@ -49,3 +50,4 @@ storiesOf('Other Config', module)
.add('Zoom', () => )
.add('Type-safe properties', CustomGraphTypes)
.add('Link arrow heads',() => )
+ .add('Tooltips',() => )
diff --git a/stories/misc/tooltipChartState.ts b/stories/misc/tooltipChartState.ts
new file mode 100644
index 00000000..1b1aa4e8
--- /dev/null
+++ b/stories/misc/tooltipChartState.ts
@@ -0,0 +1,150 @@
+import { IChart } from '../../src'
+
+export const tooltipChart: IChart = {
+ tooltipsGlobal: {
+ showTooltip: true,
+ toogleOffWhenClicked: 'global',
+ text: 'This is the global tooltip and will be toggled off, when clicked',
+ },
+ offset: {
+ x: 0,
+ y: 0,
+ },
+ scale: 1,
+ nodes: {
+ node1: {
+ tooltip: {
+ showTooltip: true,
+ text: 'this is the tooltip for node1',
+ },
+ id: 'node1',
+ type: 'output-only',
+ position: {
+ x: 300,
+ y: 100,
+ },
+ ports: {
+ port1: {
+ id: 'port1',
+ type: 'output',
+ properties: {
+ value: 'yes',
+ },
+ },
+ port2: {
+ id: 'port2',
+ type: 'output',
+ properties: {
+ value: 'no',
+ },
+ },
+ },
+ },
+ node2: {
+ tooltip: {
+ showTooltip: true,
+ toogleOffWhenClicked: 'node',
+ text: 'this is the tooltip for node2 and will be toggled off when clicked',
+ },
+ id: 'node2',
+ type: 'input-output',
+ position: {
+ x: 300,
+ y: 300,
+ },
+ ports: {
+ port1: {
+ id: 'port1',
+ type: 'input',
+ },
+ port2: {
+ id: 'port2',
+ type: 'output',
+ },
+ },
+ },
+ node3: {
+ tooltip: {
+ showTooltip: false,
+ text: 'this is the tooltip for node3 but its off',
+ },
+ id: 'node3',
+ type: 'input-output',
+ position: {
+ x: 100,
+ y: 600,
+ },
+ ports: {
+ port1: {
+ id: 'port1',
+ type: 'input',
+ },
+ port2: {
+ id: 'port2',
+ type: 'output',
+ },
+ },
+ },
+ node4: {
+ id: 'node4',
+ type: 'input-output',
+ position: {
+ x: 500,
+ y: 600,
+ },
+ ports: {
+ port1: {
+ id: 'port1',
+ type: 'input',
+ },
+ port2: {
+ id: 'port2',
+ type: 'output',
+ },
+ },
+ },
+ },
+ links: {
+ link1: {
+ id: 'link1',
+ from: {
+ nodeId: 'node1',
+ portId: 'port2',
+ },
+ to: {
+ nodeId: 'node2',
+ portId: 'port1',
+ },
+ properties: {
+ label: 'example link label',
+ },
+ },
+ link2: {
+ id: 'link2',
+ from: {
+ nodeId: 'node2',
+ portId: 'port2',
+ },
+ to: {
+ nodeId: 'node3',
+ portId: 'port1',
+ },
+ properties: {
+ label: 'another example link label',
+ },
+ },
+ link3: {
+ id: 'link3',
+ from: {
+ nodeId: 'node2',
+ portId: 'port2',
+ },
+ to: {
+ nodeId: 'node4',
+ portId: 'port1',
+ },
+ },
+ },
+ selected: {},
+ hovered: {},
+}