From 384c05240f969ad6af407161cb401305f26cef0f Mon Sep 17 00:00:00 2001 From: Braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Mon, 16 Aug 2021 21:17:38 +0200 Subject: [PATCH] feat(example): Add edge types example (stress tests) * Remove a lot of computed functions or merge them into existing ones * --- examples/EdgeTypes/index.tsx | 43 ++++++++++ examples/EdgeTypes/utils.ts | 106 ++++++++++++++++++++++++ examples/router.ts | 4 + src/components/ConnectionLine/index.tsx | 26 +++--- src/components/Edges/Edge.tsx | 39 ++++----- src/components/Edges/EdgeAnchor.tsx | 9 +- src/components/Edges/SmoothStepEdge.tsx | 43 +++++----- src/components/Edges/utils.ts | 15 ++++ src/components/Handle/index.tsx | 24 +++--- src/components/Nodes/Node.tsx | 42 ++++------ src/container/EdgeRenderer/index.tsx | 12 +-- src/container/GraphView/index.tsx | 5 +- src/container/RevueFlow/index.tsx | 9 +- 13 files changed, 268 insertions(+), 109 deletions(-) create mode 100644 examples/EdgeTypes/index.tsx create mode 100644 examples/EdgeTypes/utils.ts diff --git a/examples/EdgeTypes/index.tsx b/examples/EdgeTypes/index.tsx new file mode 100644 index 00000000..54db6b84 --- /dev/null +++ b/examples/EdgeTypes/index.tsx @@ -0,0 +1,43 @@ +/** + * Example for checking the different edge types and source and target positions + */ +import RevueFlow, { + removeElements, + addEdge, + MiniMap, + Controls, + Background, + OnLoadParams, + Connection, + Edge, + Elements +} from '../../src'; +import { getElements } from './utils'; +import { defineComponent, ref } from 'vue'; + +const onLoad = (reactFlowInstance: OnLoadParams) => { + reactFlowInstance.fitView(); + console.log(reactFlowInstance.getElements()); +}; + +const initialElements = getElements(); + +const EdgeTypesFlow = defineComponent({ + setup() { + const elements = ref(initialElements); + const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value)); + const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value)); + + return () => ( + + ); + } +}); + +export default EdgeTypesFlow; diff --git a/examples/EdgeTypes/utils.ts b/examples/EdgeTypes/utils.ts new file mode 100644 index 00000000..27f89955 --- /dev/null +++ b/examples/EdgeTypes/utils.ts @@ -0,0 +1,106 @@ +import { ElementId, Elements, Position } from '../../src'; + +const nodeWidth = 80; +const nodeGapWidth = nodeWidth * 2; +const nodeStyle = { width: nodeWidth, fontSize: 11, color: 'white' }; + +const sourceTargetPositions = [ + { source: Position.Bottom, target: Position.Top }, + { source: Position.Right, target: Position.Left } +]; +const nodeColors = [ + ['#1e9e99', '#4cb3ac', '#6ec9c0', '#8ddfd4'], + ['#0f4c75', '#1b5d8b', '#276fa1', '#3282b8'] +]; +const edgeTypes = ['default', 'step', 'smoothstep', 'straight']; +const offsets = [ + { + x: 0, + y: -nodeGapWidth + }, + { + x: nodeGapWidth, + y: -nodeGapWidth + }, + { + x: nodeGapWidth, + y: 0 + }, + { + x: nodeGapWidth, + y: nodeGapWidth + }, + { + x: 0, + y: nodeGapWidth + }, + { + x: -nodeGapWidth, + y: nodeGapWidth + }, + { + x: -nodeGapWidth, + y: 0 + }, + { + x: -nodeGapWidth, + y: -nodeGapWidth + } +]; + +let id = 0; +const getNodeId = (): ElementId => (id++).toString(); + +export function getElements(): Elements { + const initialElements = []; + + for (let sourceTargetIndex = 0; sourceTargetIndex < sourceTargetPositions.length; sourceTargetIndex++) { + const currSourceTargetPos = sourceTargetPositions[sourceTargetIndex]; + + for (let edgeTypeIndex = 0; edgeTypeIndex < edgeTypes.length; edgeTypeIndex++) { + const currEdgeType = edgeTypes[edgeTypeIndex]; + + for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) { + const currOffset = offsets[offsetIndex]; + + const style = { ...nodeStyle, background: nodeColors[sourceTargetIndex][edgeTypeIndex] }; + const sourcePosition = { + x: offsetIndex * nodeWidth * 4, + y: edgeTypeIndex * 300 + sourceTargetIndex * edgeTypes.length * 300 + }; + const sourceId = getNodeId(); + const sourceData = { label: `Source ${sourceId}` }; + const sourceNode = { + id: sourceId, + style, + data: sourceData, + position: sourcePosition, + sourcePosition: currSourceTargetPos.source, + targetPosition: currSourceTargetPos.target + }; + + const targetId = getNodeId(); + const targetData = { label: `Target ${targetId}` }; + const targetPosition = { + x: sourcePosition.x + currOffset.x, + y: sourcePosition.y + currOffset.y + }; + const targetNode = { + id: targetId, + style, + data: targetData, + position: targetPosition, + sourcePosition: currSourceTargetPos.source, + targetPosition: currSourceTargetPos.target + }; + + initialElements.push(sourceNode); + initialElements.push(targetNode); + + initialElements.push({ id: `${sourceId}-${targetId}`, source: sourceId, target: targetId, type: currEdgeType }); + } + } + } + + return initialElements as Elements; +} diff --git a/examples/router.ts b/examples/router.ts index 28215198..a19235e9 100644 --- a/examples/router.ts +++ b/examples/router.ts @@ -28,6 +28,10 @@ export const routes: RouterOptions['routes'] = [ { path: '/button-edge', component: () => import('./EdgeWithButton/EdgeWithButton.vue') + }, + { + path: '/edge-types', + component: () => import('./EdgeTypes') } ]; diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 5802f0a8..58a151bd 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -31,7 +31,6 @@ export default defineComponent({ setup(props) { const store = inject('store')!; const sourceNode = ref(store.nodes.find((n) => n.id === store.connectionNodeId) || null); - const nodesConnectable = computed(() => store.nodesConnectable); const sourceHandle = computed(() => store.connectionHandleId && store.connectionHandleType @@ -40,22 +39,25 @@ export default defineComponent({ ) : store.connectionHandleType && sourceNode.value?.__rf.handleBounds[store.connectionHandleType][0] ); - const sourceHandleX = computed(() => - sourceHandle.value ? sourceHandle.value.x + sourceHandle.value.width / 2 : (sourceNode.value?.__rf.width as number) / 2 + + const sourceX = computed(() => + sourceNode.value?.__rf.position.x + sourceHandle.value + ? sourceHandle.value.x + sourceHandle.value.width / 2 + : (sourceNode.value?.__rf.width as number) / 2 ); - const sourceHandleY = computed(() => - sourceHandle.value ? sourceHandle.value.y + sourceHandle.value.height / 2 : sourceNode.value?.__rf.height + const sourceY = computed(() => + sourceNode.value?.__rf.position.y + sourceHandle.value + ? sourceHandle.value.y + sourceHandle.value.height / 2 + : sourceNode.value?.__rf.height ); - const sourceX = computed(() => sourceNode.value?.__rf.position.x + sourceHandleX.value); - const sourceY = computed(() => sourceNode.value?.__rf.position.y + sourceHandleY.value); const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2]); const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2]); - - const isRightOrLeft = computed( - () => sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right + const targetPosition = computed(() => + sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right + ? Position.Left + : Position.Top ); - const targetPosition = computed(() => (isRightOrLeft.value ? Position.Left : Position.Top)); let dAttr = computed(() => `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`); @@ -114,7 +116,7 @@ export default defineComponent({ } return () => - nodesConnectable.value && sourceNode.value ? ( + store.nodesConnectable && sourceNode.value ? ( diff --git a/src/components/Edges/Edge.tsx b/src/components/Edges/Edge.tsx index ddaf291c..9ce46067 100644 --- a/src/components/Edges/Edge.tsx +++ b/src/components/Edges/Edge.tsx @@ -69,17 +69,15 @@ export default defineComponent({ console.warn(`couldn't create edge for target id: ${props.edge.target}; edge id: ${props.edge.id}`); } - const targetNodeBounds = computed(() => nodes.value.targetNode?.__rf.handleBounds); // when connection type is loose we can define all handles as sources - const targetNodeHandles = computed(() => + const targetNodeHandles = () => props.connectionMode === ConnectionMode.Strict - ? targetNodeBounds.value?.target - : targetNodeBounds.value?.target || targetNodeBounds.value?.source - ); + ? nodes.value.targetNode?.__rf.handleBounds.target + : nodes.value.targetNode?.__rf.handleBounds.target || nodes.value.targetNode?.__rf.handleBounds.source; const sourceHandle = computed( () => nodes.value.sourceNode && getHandle(nodes.value.sourceNode.__rf.handleBounds.source, props.edge.sourceHandle || null) ); - const targetHandle = computed(() => getHandle(targetNodeHandles.value, props.edge.targetHandle || null)); + const targetHandle = computed(() => getHandle(targetNodeHandles(), props.edge.targetHandle || null)); const sourcePosition = computed(() => (sourceHandle.value ? sourceHandle.value.position : Position.Bottom)); const targetPosition = computed(() => (targetHandle.value ? targetHandle.value.position : Position.Top)); @@ -88,16 +86,16 @@ export default defineComponent({ sourceY: number; targetX: number; targetY: number; - }>(() => { - return getEdgePositions( + }>(() => + getEdgePositions( nodes.value.sourceNode as Node, sourceHandle.value, sourcePosition.value, nodes.value.targetNode as Node, targetHandle.value, targetPosition.value - ); - }); + ) + ); const isVisible = computed(() => { return props.onlyRenderVisibleElements @@ -113,13 +111,6 @@ export default defineComponent({ }); const isSelected = computed(() => store.selectedElements?.some((elm) => isEdge(elm) && elm.id === props.edge.id) || false); - const updating = ref(false); - const inactive = computed(() => !store.elementsSelectable); - const edgeClasses = computed(() => [ - 'revue-flow__edge', - `revue-flow__edge-${props.type}`, - { selected: isSelected.value, animated: props.edge.animated, inactive: inactive.value, updating: updating.value } - ]); const edgeElement = computed(() => { const el: Edge = { @@ -192,13 +183,23 @@ export default defineComponent({ handleEdgeUpdater(event, false); }; + const updating = ref(false); const onEdgeUpdaterMouseEnter = () => (updating.value = true); const onEdgeUpdaterMouseOut = () => (updating.value = false); return () => isVisible.value && edgePositions.value ? ( { if (position === Position.Left) return x - shift; @@ -41,13 +41,12 @@ export default defineComponent({ } }, setup(props) { - const radius = computed(() => props.radius || 10); return () => ( diff --git a/src/components/Edges/SmoothStepEdge.tsx b/src/components/Edges/SmoothStepEdge.tsx index 6178e9e0..f12a91bb 100644 --- a/src/components/Edges/SmoothStepEdge.tsx +++ b/src/components/Edges/SmoothStepEdge.tsx @@ -1,8 +1,9 @@ -import { computed, defineComponent } from 'vue'; +import { defineComponent } from 'vue'; import EdgeText from './EdgeText'; -import { getMarkerEnd, getCenter, EdgeSmoothProps } from './utils'; +import { getMarkerEnd, getCenter, EdgeSmoothProps, GetCenterParams } from './utils'; import { ArrowHeadType, EdgeType, Position } from '../../types'; import { reactify } from '@vueuse/core'; +import { getBezierPath } from './BezierEdge'; // These are some helper methods for drawing the round corners // The name indicates the direction of the path. "bottomLeftCorner" goes @@ -98,26 +99,24 @@ export default defineComponent({ ...EdgeSmoothProps }, setup(props) { - const centered = computed(() => { + const centered = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetCenterParams) => { return getCenter({ - sourceX: props.sourceX, - sourceY: props.sourceY, - targetX: props.targetX, - targetY: props.targetY, - sourcePosition: props.sourcePosition, - targetPosition: props.targetPosition + sourceX: sourceX, + sourceY: sourceY, + targetX: targetX, + targetY: targetY, + sourcePosition: sourcePosition, + targetPosition: targetPosition }); }); - - const path = computed(() => { - return getSmoothStepPath({ - sourceX: props.sourceX, - sourceY: props.sourceY, - targetX: props.targetX, - targetY: props.targetY, - sourcePosition: props.sourcePosition, - targetPosition: props.targetPosition, - borderRadius: props.borderRadius + const path = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetSmoothStepPathParams) => { + return getBezierPath({ + sourceX: sourceX, + sourceY: sourceY, + targetX: targetX, + targetY: targetY, + targetPosition: targetPosition, + sourcePosition: sourcePosition }); }); @@ -128,13 +127,13 @@ export default defineComponent({ {props.label ? ( , + required: true, + default: 0 + }, + target: { + type: String as PropType, + required: true, + default: 0 + }, + selected: { + type: Boolean as PropType, + required: true, + default: false + }, targetX: { type: Number as PropType, required: true, diff --git a/src/components/Handle/index.tsx b/src/components/Handle/index.tsx index 5ac59094..7016880b 100644 --- a/src/components/Handle/index.tsx +++ b/src/components/Handle/index.tsx @@ -1,6 +1,6 @@ import { ElementId, Position, RevueFlowStore } from '../../types'; import { onMouseDown, ValidConnectionFunc } from './handler'; -import { computed, defineComponent, inject, PropType } from 'vue'; +import { defineComponent, inject, PropType } from 'vue'; import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'; const alwaysValid = () => true; @@ -41,23 +41,21 @@ export default defineComponent({ const onMouseDownHandler = (event: MouseEvent) => onMouseDown(event, store, hooks, props.id as string, nodeId, props.type === 'target', props.isValidConnection); - const handleClasses = computed(() => [ - 'revue-flow__handle', - `revue-flow__handle-${props.position}`, - 'nodrag', - { - source: props.type !== 'target', - target: props.type === 'target', - connectable: props.isConnectable - } - ]); - return () => (
{slots.default ? slots.default() : ''} diff --git a/src/components/Nodes/Node.tsx b/src/components/Nodes/Node.tsx index d26e188d..c912bb44 100644 --- a/src/components/Nodes/Node.tsx +++ b/src/components/Nodes/Node.tsx @@ -1,4 +1,4 @@ -import { computed, CSSProperties, defineComponent, h, inject, PropType, provide } from 'vue'; +import { computed, defineComponent, h, inject, PropType, provide } from 'vue'; import { templateRef, tryOnMounted, useResizeObserver } from '@vueuse/core'; import { DraggableCore, DraggableEventListener } from '@braks/revue-draggable'; import { Node, NodeDimensionUpdate, NodeType, RevueFlowStore } from '../../types'; @@ -46,7 +46,6 @@ export default defineComponent({ const nodeElement = templateRef('nodeElement', null); const selected = computed(() => store.selectedElements?.some(({ id }) => id === props.node.id) || false); - const transform = computed(() => store.transform); const isDraggable = computed( () => props.node.draggable || (store.nodesDraggable && typeof props.node.draggable === 'undefined') ); @@ -66,16 +65,7 @@ export default defineComponent({ data: props.node.data } as Node) ); - const grid = computed(() => (props.snapToGrid ? props.snapGrid : [1, 1])! as [number, number]); - const initialized = computed(() => props.node.__rf.width !== null && props.node.__rf.height !== null); - const nodeStyle = computed(() => ({ - zIndex: selected.value ? 10 : 3, - transform: `translate(${props.node.__rf.position.x}px,${props.node.__rf.position.y}px)`, - pointerEvents: isSelectable.value || isDraggable.value ? 'all' : 'none', - opacity: initialized.value ? 1 : 0, - ...props.node.style - })); const onMouseEnterHandler = () => { if (props.node.__rf.isDragging) { return; @@ -188,21 +178,12 @@ export default defineComponent({ ]); }); - const nodeClasses = computed(() => [ - 'revue-flow__node', - `revue-flow__node-${props.node.type}`, - { - selected: selected.value, - selectable: isSelectable.value - } - ]); - return () => (
('store'); - const transform = computed(() => store?.transform); + const store = inject('store')!; + const transformStyle = computed(() => { - return `translate(${transform.value?.[0]},${transform.value?.[1]}) scale(${transform.value?.[2]})`; + return `translate(${store.transform[0]},${store.transform[1]}) scale(${store.transform[2]})`; }); - const renderConnectionLine = computed(() => store?.connectionNodeId && store?.connectionHandleType); + const renderConnectionLine = computed(() => store.connectionNodeId && store.connectionHandleType); return () => ( - + - {store?.edges.map((edge) => ( + {store.edges.map((edge) => ( { + tryOnMounted(() => { init(); }); diff --git a/src/container/RevueFlow/index.tsx b/src/container/RevueFlow/index.tsx index f0a95361..bca365f0 100644 --- a/src/container/RevueFlow/index.tsx +++ b/src/container/RevueFlow/index.tsx @@ -1,5 +1,5 @@ import { defineComponent, onBeforeUnmount, onUpdated, PropType, provide, watch } from 'vue'; -import { reactify, useVModel } from '@vueuse/core'; +import { useVModel } from '@vueuse/core'; import '../../style.css'; import '../../theme-default.css'; import GraphView from '../GraphView'; @@ -240,14 +240,11 @@ export default defineComponent({ store.setElements([]); }); - const nodeTypesParsed = reactify(() => createNodeTypes({ ...defaultNodeTypes, ...props.nodeTypes })); - const edgeTypesParsed = reactify(() => createEdgeTypes({ ...defaultEdgeTypes, ...props.edgeTypes })); - return () => (