fitView()}>
+
fitView({ padding: 0.1 })}
+ >
)}
diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx
index df6d1158..9b9d7640 100644
--- a/src/components/Edges/wrapEdge.tsx
+++ b/src/components/Edges/wrapEdge.tsx
@@ -1,7 +1,7 @@
import React, { memo, ComponentType, CSSProperties } from 'react';
import cx from 'classnames';
-import store from '../../store';
+import { useStoreActions } from '../../store/hooks';
import { ElementId, Edge, EdgeCompProps } from '../../types';
interface EdgeWrapperProps {
@@ -38,6 +38,7 @@ export default (EdgeComponent: ComponentType
) => {
className,
...rest
}: EdgeWrapperProps) => {
+ const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
const edgeClasses = cx('react-flow__edge', `react-flow__edge-${type}`, className, { selected, animated });
const edgeGroupStyle: CSSProperties = {
pointerEvents: isInteractive ? 'all' : 'none',
@@ -47,7 +48,7 @@ export default (EdgeComponent: ComponentType) => {
return;
}
- store.dispatch.setSelectedElements({ id, source, target });
+ setSelectedElements({ id, source, target });
if (onClick) {
onClick({ id, source, target, type });
diff --git a/src/hooks/useD3Zoom.ts b/src/hooks/useD3Zoom.ts
index 95f3a0d9..20f66780 100644
--- a/src/hooks/useD3Zoom.ts
+++ b/src/hooks/useD3Zoom.ts
@@ -1,5 +1,5 @@
import { useEffect, MutableRefObject } from 'react';
-import { zoom, zoomIdentity } from 'd3-zoom';
+import { zoom } from 'd3-zoom';
import { select, event } from 'd3-selection';
import { useStoreState, useStoreActions } from '../store/hooks';
@@ -11,8 +11,6 @@ interface UseD3ZoomParams {
}
export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): void => {
- const transform = useStoreState((s) => s.transform);
- const d3Selection = useStoreState((s) => s.d3Selection);
const d3Zoom = useStoreState((s) => s.d3Zoom);
const initD3 = useStoreActions((actions) => actions.initD3);
@@ -35,7 +33,7 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
d3Zoom.on('zoom', null);
} else {
d3Zoom.on('zoom', () => {
- if (event.sourceEvent && event.sourceEvent.target !== zoomPane.current) {
+ if (!event.sourceEvent || (event.sourceEvent && event.sourceEvent.target !== zoomPane.current)) {
return;
}
@@ -45,12 +43,6 @@ export default ({ zoomPane, onMove, selectionKeyPressed }: UseD3ZoomParams): voi
onMove();
}
});
-
- if (d3Selection && d3Zoom) {
- // we need to restore the graph transform otherwise d3 zoom transform and graph transform are not synced
- const graphTransform = zoomIdentity.translate(transform[0], transform[1]).scale(transform[2]);
- d3Selection.call(d3Zoom.transform, graphTransform);
- }
}
return () => {
diff --git a/src/store/index.ts b/src/store/index.ts
index 5d843a83..2f9953c8 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -1,6 +1,7 @@
-import { createStore, Action, action } from 'easy-peasy';
+import { createStore, Action, action, Thunk, thunk } from 'easy-peasy';
import isEqual from 'fast-deep-equal';
import { Selection as D3Selection, ZoomBehavior } from 'd3';
+import { zoomIdentity } from 'd3-zoom';
import { getDimensions } from '../utils';
import { getHandleBounds } from '../components/Nodes/utils';
@@ -20,6 +21,7 @@ import {
HandleType,
SetConnectionId,
NodePosUpdate,
+ FitViewParams,
} from '../types';
type TransformXYK = {
@@ -122,6 +124,11 @@ export interface StoreModel {
setUserSelection: Action;
updateUserSelection: Action;
unsetUserSelection: Action;
+
+ fitView: Action;
+ zoom: Action;
+ zoomIn: Thunk;
+ zoomOut: Thunk;
}
export const storeModel: StoreModel = {
@@ -370,6 +377,50 @@ export const storeModel: StoreModel = {
setInteractive: action((state, isInteractive) => {
state.isInteractive = isInteractive;
}),
+
+ fitView: action((state, { padding = 0.1 }) => {
+ const { nodes, width, height, d3Selection, d3Zoom } = state;
+
+ if (!d3Selection || !d3Zoom || !nodes.length) {
+ return;
+ }
+
+ const bounds = getRectOfNodes(nodes);
+ const maxBoundsSize = Math.max(bounds.width, bounds.height);
+ const k = Math.min(width, height) / (maxBoundsSize + maxBoundsSize * padding);
+ const boundsCenterX = bounds.x + bounds.width / 2;
+ const boundsCenterY = bounds.y + bounds.height / 2;
+ const transform = [width / 2 - boundsCenterX * k, height / 2 - boundsCenterY * k];
+ const fittedTransform = zoomIdentity.translate(transform[0], transform[1]).scale(k);
+
+ d3Selection.call(d3Zoom.transform, fittedTransform);
+
+ state.transform = [fittedTransform.x, fittedTransform.y, fittedTransform.k];
+ }),
+
+ zoom: action((state, amount) => {
+ const { d3Zoom, d3Selection, transform } = state;
+ const nextZoom = transform[2] + amount;
+
+ if (d3Zoom && d3Selection) {
+ d3Zoom.scaleTo(d3Selection, nextZoom);
+
+ const graphTransform = zoomIdentity.translate(transform[0], transform[1]).scale(nextZoom);
+ d3Selection.call(d3Zoom.transform, graphTransform);
+
+ console.log(graphTransform);
+
+ state.transform = [graphTransform.x, graphTransform.y, graphTransform.k];
+ }
+ }),
+
+ zoomIn: thunk((actions) => {
+ actions.zoom(0.2);
+ }),
+
+ zoomOut: thunk((actions) => {
+ actions.zoom(-0.2);
+ }),
};
const store = createStore(storeModel);
diff --git a/src/types/index.ts b/src/types/index.ts
index 859d2727..74697a9b 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -134,7 +134,7 @@ export interface WrapNodeProps {
}
export type FitViewParams = {
- padding: number;
+ padding?: number;
};
export type FitViewFunc = (fitViewOptions: FitViewParams) => void;
export type ProjectFunc = (position: XYPosition) => XYPosition;
diff --git a/src/utils/graph.ts b/src/utils/graph.ts
index 65c06be9..c20d3162 100644
--- a/src/utils/graph.ts
+++ b/src/utils/graph.ts
@@ -182,7 +182,7 @@ export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
});
};
-export const fitView = ({ padding }: FitViewParams = { padding: 0.1 }): void => {
+export const fitView = ({ padding = 0.1 }: FitViewParams = { padding: 0.1 }): void => {
const { nodes, width, height, d3Selection, d3Zoom } = store.getState();
if (!d3Selection || !d3Zoom || !nodes.length) {