refactor(elements): only render visible handling
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user