diff --git a/examples/nextjs/pages/CustomNode/index.tsx b/examples/nextjs/pages/CustomNode/index.tsx
index 40f2add1..0daa35b0 100644
--- a/examples/nextjs/pages/CustomNode/index.tsx
+++ b/examples/nextjs/pages/CustomNode/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, MouseEvent, ChangeEvent } from 'react';
+import React, { useState, useEffect, MouseEvent, ChangeEvent, useCallback } from 'react';
import {
ReactFlow,
MiniMap,
@@ -118,8 +118,11 @@ const CustomNodeFlow = () => {
]);
}, []);
- const onConnect = (connection: Connection) =>
- setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds));
+ const onConnect = useCallback(
+ (connection: Connection) =>
+ setEdges((eds) => addEdge({ ...connection, animated: true, style: { stroke: '#fff' } }, eds)),
+ [setEdges]
+ );
return (
{
let id = 0;
const getId = () => `dndnode_${id++}`;
+const nodeOrigin: NodeOrigin = [0.5, 0.5];
+
const DnDFlow = () => {
const [reactFlowInstance, setReactFlowInstance] = useState();
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
@@ -74,6 +77,7 @@ const DnDFlow = () => {
onInit={onInit}
onDrop={onDrop}
onDragOver={onDragOver}
+ nodeOrigin={nodeOrigin}
>
diff --git a/packages/core/src/components/Nodes/wrapNode.tsx b/packages/core/src/components/Nodes/wrapNode.tsx
index bdca639b..854175b5 100644
--- a/packages/core/src/components/Nodes/wrapNode.tsx
+++ b/packages/core/src/components/Nodes/wrapNode.tsx
@@ -24,6 +24,8 @@ export default (NodeComponent: ComponentType) => {
data,
xPos,
yPos,
+ xPosOrigin,
+ yPosOrigin,
selected,
onClick,
onMouseEnter,
@@ -161,7 +163,7 @@ export default (NodeComponent: ComponentType) => {
ref={nodeRef}
style={{
zIndex,
- transform: `translate(${xPos}px,${yPos}px)`,
+ transform: `translate(${xPosOrigin}px,${yPosOrigin}px)`,
pointerEvents: hasPointerEvents ? 'all' : 'none',
visibility: initialized ? 'visible' : 'hidden',
...style,
diff --git a/packages/core/src/container/FlowRenderer/index.tsx b/packages/core/src/container/FlowRenderer/index.tsx
index 99725dfe..76accfbb 100644
--- a/packages/core/src/container/FlowRenderer/index.tsx
+++ b/packages/core/src/container/FlowRenderer/index.tsx
@@ -24,6 +24,7 @@ export type FlowRendererProps = Omit<
| 'selectNodesOnDrag'
| 'defaultMarkerColor'
| 'rfId'
+ | 'nodeOrigin'
> & {
children: ReactNode;
};
diff --git a/packages/core/src/container/GraphView/index.tsx b/packages/core/src/container/GraphView/index.tsx
index f48d2b74..70b2d326 100644
--- a/packages/core/src/container/GraphView/index.tsx
+++ b/packages/core/src/container/GraphView/index.tsx
@@ -13,6 +13,7 @@ import {
ReactFlowProps,
Viewport,
CoordinateExtent,
+ NodeOrigin,
} from '../../types';
export interface GraphViewProps
@@ -35,6 +36,7 @@ export interface GraphViewProps
defaultViewport: Viewport;
rfId: string;
disableKeyboardA11y: boolean;
+ nodeOrigin: NodeOrigin;
}
const GraphView = ({
@@ -96,6 +98,7 @@ const GraphView = ({
noPanClassName,
elevateEdgesOnSelect,
disableKeyboardA11y,
+ nodeOrigin,
rfId,
}: GraphViewProps) => {
useOnInitHandler(onInit);
@@ -171,6 +174,7 @@ const GraphView = ({
noPanClassName={noPanClassName}
noDragClassName={noDragClassName}
disableKeyboardA11y={disableKeyboardA11y}
+ nodeOrigin={nodeOrigin}
rfId={rfId}
/>
diff --git a/packages/core/src/container/NodeRenderer/index.tsx b/packages/core/src/container/NodeRenderer/index.tsx
index 85ef9181..0b3f3ea3 100644
--- a/packages/core/src/container/NodeRenderer/index.tsx
+++ b/packages/core/src/container/NodeRenderer/index.tsx
@@ -7,6 +7,7 @@ import { internalsSymbol } from '../../utils';
import { containerStyle } from '../../styles';
import { GraphViewProps } from '../GraphView';
import { Position, ReactFlowState, WrapNodeProps } from '../../types';
+import { getPositionWithOrigin } from './utils';
type NodeRendererProps = Pick<
GraphViewProps,
@@ -23,6 +24,7 @@ type NodeRendererProps = Pick<
| 'noDragClassName'
| 'rfId'
| 'disableKeyboardA11y'
+ | 'nodeOrigin'
>;
const selector = (s: ReactFlowState) => ({
@@ -81,6 +83,15 @@ const NodeRenderer = (props: NodeRendererProps) => {
const isDraggable = !!(node.draggable || (nodesDraggable && typeof node.draggable === 'undefined'));
const isSelectable = !!(node.selectable || (elementsSelectable && typeof node.selectable === 'undefined'));
const isConnectable = !!(node.connectable || (nodesConnectable && typeof node.connectable === 'undefined'));
+ const posX = node.position?.x ?? 0;
+ const posY = node.position?.y ?? 0;
+ const posOrigin = getPositionWithOrigin({
+ x: posX,
+ y: posY,
+ width: node.width ?? 0,
+ height: node.height ?? 0,
+ origin: props.nodeOrigin,
+ });
return (
{
sourcePosition={node.sourcePosition || Position.Bottom}
targetPosition={node.targetPosition || Position.Top}
hidden={node.hidden}
- xPos={node.positionAbsolute?.x ?? 0}
- yPos={node.positionAbsolute?.y ?? 0}
+ xPos={posX}
+ yPos={posY}
+ xPosOrigin={posOrigin.x}
+ yPosOrigin={posOrigin.y}
selectNodesOnDrag={props.selectNodesOnDrag}
onClick={props.onNodeClick}
onMouseEnter={props.onNodeMouseEnter}
diff --git a/packages/core/src/container/NodeRenderer/utils.ts b/packages/core/src/container/NodeRenderer/utils.ts
index 0683279a..6d1d68a6 100644
--- a/packages/core/src/container/NodeRenderer/utils.ts
+++ b/packages/core/src/container/NodeRenderer/utils.ts
@@ -5,7 +5,7 @@ import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode';
import GroupNode from '../../components/Nodes/GroupNode';
import wrapNode from '../../components/Nodes/wrapNode';
-import { NodeTypes, NodeProps, NodeTypesWrapped } from '../../types';
+import { NodeTypes, NodeProps, NodeTypesWrapped, NodeOrigin, XYPosition } from '../../types';
export type CreateNodeTypes = (nodeTypes: NodeTypes) => NodeTypesWrapped;
@@ -31,3 +31,31 @@ export function createNodeTypes(nodeTypes: NodeTypes): NodeTypesWrapped {
...specialTypes,
};
}
+
+export const getPositionWithOrigin = ({
+ x,
+ y,
+ width,
+ height,
+ origin,
+}: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ origin: NodeOrigin;
+}): XYPosition => {
+ if (!width || !height) {
+ return { x, y };
+ }
+
+ if (origin[0] < 0 || origin[1] < 0 || origin[0] > 1 || origin[1] > 1) {
+ console.warn('[ReactFlow]: nodeOrigin must be between 0 and 1');
+ return { x, y };
+ }
+
+ return {
+ x: x - width * origin[0],
+ y: y - height * origin[1],
+ };
+};
diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx
index 224debc3..0285ba73 100644
--- a/packages/core/src/container/ReactFlow/index.tsx
+++ b/packages/core/src/container/ReactFlow/index.tsx
@@ -6,6 +6,7 @@ import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge, SimpleBezierEdge }
import DefaultNode from '../../components/Nodes/DefaultNode';
import InputNode from '../../components/Nodes/InputNode';
import OutputNode from '../../components/Nodes/OutputNode';
+import GroupNode from '../../components/Nodes/GroupNode';
import SelectionListener from '../../components/SelectionListener';
import StoreUpdater from '../../components/StoreUpdater';
@@ -14,6 +15,7 @@ import {
ConnectionMode,
EdgeTypes,
EdgeTypesWrapped,
+ NodeOrigin,
NodeTypes,
NodeTypesWrapped,
PanOnScrollMode,
@@ -33,6 +35,7 @@ const defaultNodeTypes: NodeTypes = {
input: InputNode,
default: DefaultNode,
output: OutputNode,
+ group: GroupNode,
};
const defaultEdgeTypes: EdgeTypes = {
@@ -43,6 +46,7 @@ const defaultEdgeTypes: EdgeTypes = {
simplebezier: SimpleBezierEdge,
};
+const initNodeOrigin: NodeOrigin = [0, 0];
const initSnapGrid: [number, number] = [15, 15];
const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 };
@@ -104,6 +108,7 @@ const ReactFlow = forwardRef(
selectNodesOnDrag = true,
nodesDraggable,
nodesConnectable,
+ nodeOrigin = initNodeOrigin,
elementsSelectable,
defaultViewport = initDefaultViewport,
minZoom = 0.5,
@@ -219,6 +224,7 @@ const ReactFlow = forwardRef(
elevateEdgesOnSelect={elevateEdgesOnSelect}
rfId={rfId}
disableKeyboardA11y={disableKeyboardA11y}
+ nodeOrigin={nodeOrigin}
/>
{
onlyRenderVisibleElements?: boolean;
nodesDraggable?: boolean;
nodesConnectable?: boolean;
+ nodeOrigin?: NodeOrigin;
+ initNodeOrigin?: NodeOrigin;
elementsSelectable?: boolean;
selectNodesOnDrag?: boolean;
panOnDrag?: boolean;
diff --git a/packages/core/src/types/nodes.ts b/packages/core/src/types/nodes.ts
index c9dbb65c..5ba91cf4 100644
--- a/packages/core/src/types/nodes.ts
+++ b/packages/core/src/types/nodes.ts
@@ -68,6 +68,8 @@ export interface WrapNodeProps {
isConnectable: boolean;
xPos: number;
yPos: number;
+ xPosOrigin: number;
+ yPosOrigin: number;
initialized: boolean;
isSelectable: boolean;
isDraggable: boolean;
@@ -124,3 +126,5 @@ export type NodeDragItem = {
parentNode?: string;
dragging?: boolean;
};
+
+export type NodeOrigin = [number, number];