refactor(elements): only render visible handling

This commit is contained in:
moklick
2020-11-15 18:05:44 +01:00
parent 35887cac89
commit 393defc638
5 changed files with 105 additions and 95 deletions
+34 -68
View File
@@ -4,17 +4,8 @@ import { useStoreState } from '../../store/hooks';
import ConnectionLine from '../../components/ConnectionLine/index'; import ConnectionLine from '../../components/ConnectionLine/index';
import { isEdge } from '../../utils/graph'; import { isEdge } from '../../utils/graph';
import MarkerDefinitions from './MarkerDefinitions'; import MarkerDefinitions from './MarkerDefinitions';
import { getHandlePosition, getHandle, isEdgeVisible } from './utils'; import { getEdgePositions, getHandle, isEdgeVisible, getSourceTargetNode } from './utils';
import { import { Position, Edge, Node, Elements, ConnectionLineType, ConnectionLineComponent, Transform } from '../../types';
Position,
Edge,
Node,
HandleElement,
Elements,
ConnectionLineType,
ConnectionLineComponent,
Transform,
} from '../../types';
interface EdgeRendererProps { interface EdgeRendererProps {
edgeTypes: any; edgeTypes: any;
@@ -24,46 +15,9 @@ interface EdgeRendererProps {
arrowHeadColor: string; arrowHeadColor: string;
markerEndId?: string; markerEndId?: string;
connectionLineComponent?: ConnectionLineComponent; connectionLineComponent?: ConnectionLineComponent;
onlyRenderVisibleElements: boolean;
} }
interface EdgePositions {
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
}
function getEdgePositions(
sourceNode: Node,
sourceHandle: HandleElement | unknown,
sourcePosition: Position,
targetNode: Node,
targetHandle: HandleElement | unknown,
targetPosition: Position
): EdgePositions {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
const sourceX = sourceNode.__rf.position.x + sourceHandlePos.x;
const sourceY = sourceNode.__rf.position.y + sourceHandlePos.y;
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
const targetX = targetNode.__rf.position.x + targetHandlePos.x;
const targetY = targetNode.__rf.position.y + targetHandlePos.y;
return {
sourceX,
sourceY,
targetX,
targetY,
};
}
type SourceTargetNode = {
sourceNode: Node | null;
targetNode: Node | null;
};
const initialSourceTarget: SourceTargetNode = { sourceNode: null, targetNode: null };
function renderEdge( function renderEdge(
edge: Edge, edge: Edge,
props: EdgeRendererProps, props: EdgeRendererProps,
@@ -72,18 +26,12 @@ function renderEdge(
elementsSelectable: boolean, elementsSelectable: boolean,
transform: Transform, transform: Transform,
width: number, width: number,
height: number height: number,
onlyRenderVisibleElements: boolean
) { ) {
const sourceHandleId = edge.sourceHandle || null; const sourceHandleId = edge.sourceHandle || null;
const targetHandleId = edge.targetHandle || null; const targetHandleId = edge.targetHandle || null;
const { sourceNode, targetNode } = nodes.reduce((res, node) => { const { sourceNode, targetNode } = getSourceTargetNode(edge, nodes);
if (node.id === edge.source) {
res.sourceNode = node;
} else if (node.id === edge.target) {
res.targetNode = node;
}
return res;
}, initialSourceTarget);
if (!sourceNode) { if (!sourceNode) {
console.warn(`couldn't create edge for source id: ${edge.source}`); console.warn(`couldn't create edge for source id: ${edge.source}`);
@@ -99,15 +47,17 @@ function renderEdge(
return null; return null;
} }
const renderEdge = isEdgeVisible({ const isVisible = onlyRenderVisibleElements
sourcePos: sourceNode.__rf.position, ? isEdgeVisible({
targetPos: targetNode.__rf.position, sourcePos: sourceNode.__rf.position,
width, targetPos: targetNode.__rf.position,
height, width,
transform, height,
}); transform,
})
: true;
if (!renderEdge) { if (!isVisible) {
return null; return null;
} }
@@ -192,7 +142,13 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
return null; return null;
} }
const { connectionLineType, arrowHeadColor, connectionLineStyle, connectionLineComponent } = props; const {
connectionLineType,
arrowHeadColor,
connectionLineStyle,
connectionLineComponent,
onlyRenderVisibleElements,
} = props;
const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`; const transformStyle = `translate(${transform[0]},${transform[1]}) scale(${transform[2]})`;
const renderConnectionLine = connectionNodeId && connectionHandleType; const renderConnectionLine = connectionNodeId && connectionHandleType;
@@ -201,7 +157,17 @@ const EdgeRenderer = (props: EdgeRendererProps) => {
<MarkerDefinitions color={arrowHeadColor} /> <MarkerDefinitions color={arrowHeadColor} />
<g transform={transformStyle}> <g transform={transformStyle}>
{edges.map((edge: Edge) => {edges.map((edge: Edge) =>
renderEdge(edge, props, nodes, selectedElements, elementsSelectable, transform, width, height) renderEdge(
edge,
props,
nodes,
selectedElements,
elementsSelectable,
transform,
width,
height,
onlyRenderVisibleElements
)
)} )}
{renderConnectionLine && ( {renderConnectionLine && (
<ConnectionLine <ConnectionLine
+61 -1
View File
@@ -4,7 +4,17 @@ import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../compon
import wrapEdge from '../../components/Edges/wrapEdge'; import wrapEdge from '../../components/Edges/wrapEdge';
import { rectToBox } from '../../utils/graph'; import { rectToBox } from '../../utils/graph';
import { EdgeTypesType, EdgeProps, Position, Node, XYPosition, ElementId, HandleElement, Transform } from '../../types'; import {
EdgeTypesType,
EdgeProps,
Position,
Node,
XYPosition,
ElementId,
HandleElement,
Transform,
Edge,
} from '../../types';
export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType { export function createEdgeTypes(edgeTypes: EdgeTypesType): EdgeTypesType {
const standardTypes: EdgeTypesType = { const standardTypes: EdgeTypesType = {
@@ -81,6 +91,37 @@ export function getHandle(bounds: HandleElement[], handleId: ElementId | null):
return handle; return handle;
} }
interface EdgePositions {
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
}
export const getEdgePositions = (
sourceNode: Node,
sourceHandle: HandleElement | unknown,
sourcePosition: Position,
targetNode: Node,
targetHandle: HandleElement | unknown,
targetPosition: Position
): EdgePositions => {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
const sourceX = sourceNode.__rf.position.x + sourceHandlePos.x;
const sourceY = sourceNode.__rf.position.y + sourceHandlePos.y;
const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle);
const targetX = targetNode.__rf.position.x + targetHandlePos.x;
const targetY = targetNode.__rf.position.y + targetHandlePos.y;
return {
sourceX,
sourceY,
targetX,
targetY,
};
};
interface IsEdgeVisibleParams { interface IsEdgeVisibleParams {
sourcePos: XYPosition; sourcePos: XYPosition;
targetPos: XYPosition; targetPos: XYPosition;
@@ -118,3 +159,22 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }
return overlappingArea > 0; return overlappingArea > 0;
} }
type SourceTargetNode = {
sourceNode: Node | null;
targetNode: Node | null;
};
export const getSourceTargetNode = (edge: Edge, nodes: Node[]): SourceTargetNode => {
return nodes.reduce(
(res, node) => {
if (node.id === edge.source) {
res.sourceNode = node;
} else if (node.id === edge.target) {
res.targetNode = node;
}
return res;
},
{ sourceNode: null, targetNode: null } as SourceTargetNode
);
};
+2 -7
View File
@@ -93,7 +93,6 @@ const GraphView = ({
const setMinZoom = useStoreActions((actions) => actions.setMinZoom); const setMinZoom = useStoreActions((actions) => actions.setMinZoom);
const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom); const setMaxZoom = useStoreActions((actions) => actions.setMaxZoom);
const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent); const setTranslateExtent = useStoreActions((actions) => actions.setTranslateExtent);
const setOnlyRenderVisibleElements = useStoreActions((actions) => actions.setOnlyRenderVisibleElements);
const currentStore = useStore(); const currentStore = useStore();
const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper(); const { zoomIn, zoomOut, zoomTo, transform, fitView, initialized } = useZoomPanHelper();
@@ -188,12 +187,6 @@ const GraphView = ({
} }
}, [translateExtent]); }, [translateExtent]);
useEffect(() => {
if (typeof onlyRenderVisibleElements !== 'undefined') {
setOnlyRenderVisibleElements(onlyRenderVisibleElements);
}
}, [translateExtent]);
return ( return (
<FlowRenderer <FlowRenderer
onPaneClick={onPaneClick} onPaneClick={onPaneClick}
@@ -233,6 +226,7 @@ const GraphView = ({
selectNodesOnDrag={selectNodesOnDrag} selectNodesOnDrag={selectNodesOnDrag}
snapToGrid={snapToGrid} snapToGrid={snapToGrid}
snapGrid={snapGrid} snapGrid={snapGrid}
onlyRenderVisibleElements={onlyRenderVisibleElements}
/> />
<EdgeRenderer <EdgeRenderer
edgeTypes={edgeTypes} edgeTypes={edgeTypes}
@@ -242,6 +236,7 @@ const GraphView = ({
arrowHeadColor={arrowHeadColor} arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId} markerEndId={markerEndId}
connectionLineComponent={connectionLineComponent} connectionLineComponent={connectionLineComponent}
onlyRenderVisibleElements={onlyRenderVisibleElements}
/> />
</FlowRenderer> </FlowRenderer>
); );
+6 -1
View File
@@ -1,5 +1,6 @@
import React, { memo, useMemo, ComponentType, MouseEvent } from 'react'; import React, { memo, useMemo, ComponentType, MouseEvent } from 'react';
import { getNodesInside } from '../../utils/graph';
import { useStoreState } from '../../store/hooks'; import { useStoreState } from '../../store/hooks';
import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types'; import { Node, NodeTypesType, WrapNodeProps, Edge } from '../../types';
@@ -15,6 +16,7 @@ interface NodeRendererProps {
onNodeDragStop?: (event: MouseEvent, node: Node) => void; onNodeDragStop?: (event: MouseEvent, node: Node) => void;
snapToGrid: boolean; snapToGrid: boolean;
snapGrid: [number, number]; snapGrid: [number, number];
onlyRenderVisibleElements: boolean;
} }
const NodeRenderer = (props: NodeRendererProps) => { const NodeRenderer = (props: NodeRendererProps) => {
@@ -23,7 +25,10 @@ const NodeRenderer = (props: NodeRendererProps) => {
const nodesDraggable = useStoreState((state) => state.nodesDraggable); const nodesDraggable = useStoreState((state) => state.nodesDraggable);
const nodesConnectable = useStoreState((state) => state.nodesConnectable); const nodesConnectable = useStoreState((state) => state.nodesConnectable);
const elementsSelectable = useStoreState((state) => state.elementsSelectable); const elementsSelectable = useStoreState((state) => state.elementsSelectable);
const visibleNodes = useStoreState((state) => state.visibleNodes); const viewportBox = useStoreState((state) => state.viewportBox);
const nodes = useStoreState((state) => state.nodes);
const visibleNodes = props.onlyRenderVisibleElements ? getNodesInside(nodes, viewportBox, transform, true) : nodes;
const transformStyle = useMemo( const transformStyle = useMemo(
() => ({ () => ({
+2 -18
View File
@@ -44,12 +44,11 @@ export interface StoreModel {
height: number; height: number;
transform: Transform; transform: Transform;
elements: Elements; elements: Elements;
visibleNodes: Computed<StoreModel, Node[]>;
nodes: Computed<StoreModel, Node[]>; nodes: Computed<StoreModel, Node[]>;
edges: Computed<StoreModel, Edge[]>; edges: Computed<StoreModel, Edge[]>;
selectedElements: Elements | null; selectedElements: Elements | null;
selectedNodesBbox: Rect; selectedNodesBbox: Rect;
onlyRenderVisibleElements: boolean; viewportBox: Computed<StoreModel, Rect>;
d3Zoom: ZoomBehavior<Element, unknown> | null; d3Zoom: ZoomBehavior<Element, unknown> | null;
d3Selection: D3Selection<Element, unknown, null, undefined> | null; d3Selection: D3Selection<Element, unknown, null, undefined> | null;
@@ -132,8 +131,6 @@ export interface StoreModel {
unsetUserSelection: Action<StoreModel>; unsetUserSelection: Action<StoreModel>;
setMultiSelectionActive: Action<StoreModel, boolean>; setMultiSelectionActive: Action<StoreModel, boolean>;
setOnlyRenderVisibleElements: Action<StoreModel, boolean>;
} }
export const storeModel: StoreModel = { export const storeModel: StoreModel = {
@@ -142,19 +139,10 @@ export const storeModel: StoreModel = {
transform: [0, 0, 1], transform: [0, 0, 1],
elements: [], elements: [],
nodes: computed((state) => state.elements.filter(isNode)), nodes: computed((state) => state.elements.filter(isNode)),
visibleNodes: computed((state) => { viewportBox: computed((state) => ({ x: 0, y: 0, width: state.width, height: state.height })),
if (!state.onlyRenderVisibleElements) {
return state.nodes;
}
const viewportBox = { x: 0, y: 0, width: state.width, height: state.height };
return getNodesInside(state.nodes, viewportBox, state.transform, true);
}),
edges: computed((state) => state.elements.filter(isEdge)), edges: computed((state) => state.elements.filter(isEdge)),
selectedElements: null, selectedElements: null,
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 }, selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
onlyRenderVisibleElements: true,
d3Zoom: null, d3Zoom: null,
d3Selection: null, d3Selection: null,
@@ -452,10 +440,6 @@ export const storeModel: StoreModel = {
setMultiSelectionActive: action((state, isActive) => { setMultiSelectionActive: action((state, isActive) => {
state.multiSelectionActive = isActive; state.multiSelectionActive = isActive;
}), }),
setOnlyRenderVisibleElements: action((state, onlyRenderVisible) => {
state.onlyRenderVisibleElements = onlyRenderVisible;
}),
}; };
const nodeEnv: string = (typeof __ENV__ !== 'undefined' && __ENV__) as string; const nodeEnv: string = (typeof __ENV__ !== 'undefined' && __ENV__) as string;