Merge pull request #361 from wbkd/develop

Develop
This commit is contained in:
Moritz
2020-07-24 17:50:51 +02:00
committed by GitHub
7 changed files with 132 additions and 14 deletions
+4 -1
View File
@@ -73,6 +73,7 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
- `minZoom`: default: `0.5` - `minZoom`: default: `0.5`
- `maxZoom`: default: `2` - `maxZoom`: default: `2`
- `defaultZoom`: default: `1` - `defaultZoom`: default: `1`
- `defaultPosition`: default: `[0, 0]`
- `snapToGrid`: default: `false` - `snapToGrid`: default: `false`
- `snapGrid`: [x, y] array - default: `[16, 16]` - `snapGrid`: [x, y] array - default: `[16, 16]`
- `onlyRenderVisibleNodes`: default: `true` - `onlyRenderVisibleNodes`: default: `true`
@@ -88,7 +89,9 @@ const BasicFlow = () => <ReactFlow elements={elements} />;
- `onNodeContextMenu(evt: MouseEvent, node: Node)`: node context menu - `onNodeContextMenu(evt: MouseEvent, node: Node)`: node context menu
- `onConnect({ source, target })`: called when user connects two nodes - `onConnect({ source, target })`: called when user connects two nodes
- `onLoad(reactFlowInstance)`: called after flow is initialized - `onLoad(reactFlowInstance)`: called after flow is initialized
- `onMove()`: called when user pans or zooms - `onMove()`: called when user is panning or zooming
- `onMoveStart()`: called when user starts panning or zooming
- `onMoveEnd()`: called when user ends panning or zooming
- `onSelectionChange(elements: Elements)`: called when user selects one or multiple elements - `onSelectionChange(elements: Elements)`: called when user selects one or multiple elements
#### Interaction #### Interaction
+3
View File
@@ -11,6 +11,8 @@ const onLoad = (reactFlowInstance) => {
reactFlowInstance.fitView(); reactFlowInstance.fitView();
}; };
const onMoveEnd = (transform) => console.log('zoom/move end', transform);
const initialElements = [ const initialElements = [
{ {
id: '1', id: '1',
@@ -116,6 +118,7 @@ const OverviewFlow = () => {
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop} onNodeDragStop={onNodeDragStop}
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
onMoveEnd={onMoveEnd}
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
onLoad={onLoad} onLoad={onLoad}
connectionLineStyle={{ stroke: '#ddd' }} connectionLineStyle={{ stroke: '#ddd' }}
+30 -7
View File
@@ -21,6 +21,7 @@ import {
Edge, Edge,
Connection, Connection,
ConnectionLineType, ConnectionLineType,
FlowTransform,
} from '../../types'; } from '../../types';
export interface GraphViewProps { export interface GraphViewProps {
@@ -35,7 +36,9 @@ export interface GraphViewProps {
onNodeDragStop?: (node: Node) => void; onNodeDragStop?: (node: Node) => void;
onConnect?: (connection: Connection | Edge) => void; onConnect?: (connection: Connection | Edge) => void;
onLoad?: OnLoadFunc; onLoad?: OnLoadFunc;
onMove?: () => void; onMove?: (flowTransform?: FlowTransform) => void;
onMoveStart?: (flowTransform?: FlowTransform) => void;
onMoveEnd?: (flowTransform?: FlowTransform) => void;
selectionKeyCode: number; selectionKeyCode: number;
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType; edgeTypes: EdgeTypesType;
@@ -52,6 +55,7 @@ export interface GraphViewProps {
minZoom: number; minZoom: number;
maxZoom: number; maxZoom: number;
defaultZoom: number; defaultZoom: number;
defaultPosition: [number, number];
arrowHeadColor: string; arrowHeadColor: string;
markerEndId?: string; markerEndId?: string;
zoomOnScroll: boolean; zoomOnScroll: boolean;
@@ -63,6 +67,8 @@ const GraphView = memo(
nodeTypes, nodeTypes,
edgeTypes, edgeTypes,
onMove, onMove,
onMoveStart,
onMoveEnd,
onLoad, onLoad,
onElementClick, onElementClick,
onNodeMouseEnter, onNodeMouseEnter,
@@ -88,6 +94,7 @@ const GraphView = memo(
minZoom, minZoom,
maxZoom, maxZoom,
defaultZoom, defaultZoom,
defaultPosition,
arrowHeadColor, arrowHeadColor,
markerEndId, markerEndId,
zoomOnScroll, zoomOnScroll,
@@ -106,7 +113,7 @@ const GraphView = memo(
const setNodesDraggable = useStoreActions((actions) => actions.setNodesDraggable); const setNodesDraggable = useStoreActions((actions) => actions.setNodesDraggable);
const setNodesConnectable = useStoreActions((actions) => actions.setNodesConnectable); const setNodesConnectable = useStoreActions((actions) => actions.setNodesConnectable);
const setElementsSelectable = useStoreActions((actions) => actions.setElementsSelectable); const setElementsSelectable = useStoreActions((actions) => actions.setElementsSelectable);
const updateTransform = useStoreActions((actions) => actions.updateTransform); const setInitTransform = useStoreActions((actions) => actions.setInitTransform);
const setMinMaxZoom = useStoreActions((actions) => actions.setMinMaxZoom); const setMinMaxZoom = useStoreActions((actions) => actions.setMinMaxZoom);
const fitView = useStoreActions((actions) => actions.fitView); const fitView = useStoreActions((actions) => actions.fitView);
const zoom = useStoreActions((actions) => actions.zoom); const zoom = useStoreActions((actions) => actions.zoom);
@@ -139,10 +146,6 @@ const GraphView = memo(
setOnConnect(onConnect); setOnConnect(onConnect);
} }
if (defaultZoom !== 1) {
updateTransform({ x: 0, y: 0, k: defaultZoom });
}
if (rendererNode.current) { if (rendererNode.current) {
resizeObserver = new ResizeObserver((entries) => { resizeObserver = new ResizeObserver((entries) => {
for (let _ of entries) { for (let _ of entries) {
@@ -162,7 +165,15 @@ const GraphView = memo(
}; };
}, []); }, []);
useD3Zoom({ zoomPane, onMove, selectionKeyPressed, zoomOnScroll, zoomOnDoubleClick }); useD3Zoom({
zoomPane,
onMove,
onMoveStart,
onMoveEnd,
selectionKeyPressed,
zoomOnScroll,
zoomOnDoubleClick,
});
useEffect(() => { useEffect(() => {
if (d3Initialised && onLoad) { if (d3Initialised && onLoad) {
@@ -174,6 +185,18 @@ const GraphView = memo(
getElements, getElements,
}); });
} }
if (d3Initialised) {
const initialTransform = {
x: defaultPosition[0],
y: defaultPosition[1],
k: defaultZoom,
};
if (initialTransform.x !== 0 || initialTransform.y !== 0 || initialTransform.k !== 1) {
setInitTransform(initialTransform);
}
}
}, [d3Initialised, onLoad]); }, [d3Initialised, onLoad]);
useEffect(() => { useEffect(() => {
+12 -1
View File
@@ -26,6 +26,7 @@ import {
Edge, Edge,
Connection, Connection,
ConnectionLineType, ConnectionLineType,
FlowTransform,
} from '../../types'; } from '../../types';
import '../../style.css'; import '../../style.css';
@@ -42,7 +43,9 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
onNodeDragStop?: (node: Node) => void; onNodeDragStop?: (node: Node) => void;
onConnect?: (connection: Edge | Connection) => void; onConnect?: (connection: Edge | Connection) => void;
onLoad?: OnLoadFunc; onLoad?: OnLoadFunc;
onMove?: () => void; onMove?: (flowTransform?: FlowTransform) => void;
onMoveStart?: (flowTransform?: FlowTransform) => void;
onMoveEnd?: (flowTransform?: FlowTransform) => void;
onSelectionChange?: (elements: Elements | null) => void; onSelectionChange?: (elements: Elements | null) => void;
nodeTypes: NodeTypesType; nodeTypes: NodeTypesType;
edgeTypes: EdgeTypesType; edgeTypes: EdgeTypesType;
@@ -60,6 +63,7 @@ export interface ReactFlowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
minZoom: number; minZoom: number;
maxZoom: number; maxZoom: number;
defaultZoom: number; defaultZoom: number;
defaultPosition: [number, number];
arrowHeadColor: string; arrowHeadColor: string;
markerEndId?: string; markerEndId?: string;
zoomOnScroll: boolean; zoomOnScroll: boolean;
@@ -76,6 +80,8 @@ const ReactFlow = ({
edgeTypes, edgeTypes,
onLoad, onLoad,
onMove, onMove,
onMoveStart,
onMoveEnd,
onElementsRemove, onElementsRemove,
onConnect, onConnect,
onNodeMouseEnter, onNodeMouseEnter,
@@ -99,6 +105,7 @@ const ReactFlow = ({
minZoom, minZoom,
maxZoom, maxZoom,
defaultZoom, defaultZoom,
defaultPosition,
arrowHeadColor, arrowHeadColor,
markerEndId, markerEndId,
zoomOnScroll, zoomOnScroll,
@@ -114,6 +121,8 @@ const ReactFlow = ({
<GraphView <GraphView
onLoad={onLoad} onLoad={onLoad}
onMove={onMove} onMove={onMove}
onMoveStart={onMoveStart}
onMoveEnd={onMoveEnd}
onElementClick={onElementClick} onElementClick={onElementClick}
onNodeMouseEnter={onNodeMouseEnter} onNodeMouseEnter={onNodeMouseEnter}
onNodeMouseMove={onNodeMouseMove} onNodeMouseMove={onNodeMouseMove}
@@ -140,6 +149,7 @@ const ReactFlow = ({
minZoom={minZoom} minZoom={minZoom}
maxZoom={maxZoom} maxZoom={maxZoom}
defaultZoom={defaultZoom} defaultZoom={defaultZoom}
defaultPosition={defaultPosition}
arrowHeadColor={arrowHeadColor} arrowHeadColor={arrowHeadColor}
markerEndId={markerEndId} markerEndId={markerEndId}
zoomOnScroll={zoomOnScroll} zoomOnScroll={zoomOnScroll}
@@ -179,6 +189,7 @@ ReactFlow.defaultProps = {
minZoom: 0.5, minZoom: 0.5,
maxZoom: 2, maxZoom: 2,
defaultZoom: 1, defaultZoom: 1,
defaultPosition: [0, 0],
arrowHeadColor: '#bbb', arrowHeadColor: '#bbb',
zoomOnScroll: true, zoomOnScroll: true,
zoomOnDoubleClick: true, zoomOnDoubleClick: true,
+63 -5
View File
@@ -1,17 +1,40 @@
import { useEffect, MutableRefObject } from 'react'; import { useEffect, useRef, MutableRefObject } from 'react';
import { event } from 'd3-selection'; import { event } from 'd3-selection';
import { useStoreState, useStoreActions } from '../store/hooks'; import { useStoreState, useStoreActions } from '../store/hooks';
import { FlowTransform } from '../types';
interface UseD3ZoomParams { interface UseD3ZoomParams {
zoomPane: MutableRefObject<Element | null>; zoomPane: MutableRefObject<Element | null>;
selectionKeyPressed: boolean; selectionKeyPressed: boolean;
zoomOnScroll: boolean; zoomOnScroll: boolean;
zoomOnDoubleClick: boolean; zoomOnDoubleClick: boolean;
onMove?: () => void; onMove?: (flowTransform?: FlowTransform) => void;
onMoveStart?: (flowTransform?: FlowTransform) => void;
onMoveEnd?: (flowTransform?: FlowTransform) => void;
} }
export default ({ zoomPane, onMove, zoomOnScroll, zoomOnDoubleClick, selectionKeyPressed }: UseD3ZoomParams): void => { const viewChanged = (prevTransform: FlowTransform, eventTransform: any): boolean =>
prevTransform.x !== eventTransform.x ||
prevTransform.y !== eventTransform.y ||
prevTransform.zoom !== eventTransform.k;
const eventToFlowTransform = (eventTransform: any): FlowTransform => ({
x: eventTransform.x,
y: eventTransform.y,
zoom: eventTransform.k,
});
export default ({
zoomPane,
onMove,
onMoveStart,
onMoveEnd,
zoomOnScroll,
zoomOnDoubleClick,
selectionKeyPressed,
}: UseD3ZoomParams): void => {
const prevTransform = useRef<FlowTransform>({ x: 0, y: 0, zoom: 0 });
const d3Zoom = useStoreState((s) => s.d3Zoom); const d3Zoom = useStoreState((s) => s.d3Zoom);
const initD3 = useStoreActions((actions) => actions.initD3); const initD3 = useStoreActions((actions) => actions.initD3);
@@ -32,12 +55,47 @@ export default ({ zoomPane, onMove, zoomOnScroll, zoomOnDoubleClick, selectionKe
updateTransform(event.transform); updateTransform(event.transform);
if (onMove) { if (onMove) {
onMove(); const flowTransform = eventToFlowTransform(event.transform);
onMove(flowTransform);
} }
}); });
} }
} }
}, [selectionKeyPressed, d3Zoom]); }, [selectionKeyPressed, d3Zoom, updateTransform, onMove]);
useEffect(() => {
if (d3Zoom) {
if (onMoveStart) {
d3Zoom.on('start', () => {
if (viewChanged(prevTransform.current, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform);
prevTransform.current = flowTransform;
onMoveStart(flowTransform);
}
});
} else {
d3Zoom.on('start', null);
}
}
}, [d3Zoom, onMoveStart]);
useEffect(() => {
if (d3Zoom) {
if (onMoveEnd) {
d3Zoom.on('end', () => {
if (viewChanged(prevTransform.current, event.transform)) {
const flowTransform = eventToFlowTransform(event.transform);
prevTransform.current = flowTransform;
onMoveEnd(flowTransform);
}
});
} else {
d3Zoom.on('end', null);
}
}
}, [d3Zoom, onMoveEnd]);
useEffect(() => { useEffect(() => {
if (d3Zoom) { if (d3Zoom) {
+14
View File
@@ -105,6 +105,8 @@ export interface StoreModel {
updateTransform: Action<StoreModel, TransformXYK>; updateTransform: Action<StoreModel, TransformXYK>;
setInitTransform: Action<StoreModel, TransformXYK>;
updateSize: Action<StoreModel, Dimensions>; updateSize: Action<StoreModel, Dimensions>;
initD3: Action<StoreModel, Element>; initD3: Action<StoreModel, Element>;
@@ -342,6 +344,18 @@ export const storeModel: StoreModel = {
state.transform[2] = transform.k; state.transform[2] = transform.k;
}), }),
setInitTransform: action((state, transform) => {
state.transform[0] = transform.x;
state.transform[1] = transform.y;
state.transform[2] = transform.k;
if (state.d3Selection) {
const updatedTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.k);
// we need to sync the d3 zoom transform with the updated transform
state.d3Selection.property('__zoom', updatedTransform);
}
}),
updateSize: action((state, size) => { updateSize: action((state, size) => {
state.width = size.width; state.width = size.width;
state.height = size.height; state.height = size.height;
+6
View File
@@ -241,3 +241,9 @@ export type NodePosUpdate = {
id: ElementId; id: ElementId;
pos: XYPosition; pos: XYPosition;
}; };
export type FlowTransform = {
x: number;
y: number;
zoom: number;
};