Merge pull request #363 from wbkd/develop

Develop
This commit is contained in:
Moritz
2020-07-26 17:09:39 +02:00
committed by GitHub
9 changed files with 56 additions and 59 deletions
+23 -2
View File
@@ -22,7 +22,7 @@ React Flow is a library for building node-based graphs. You can easily implement
- [ReactFlowProvider](#reactflowprovider)
- [Styling](#styling)
- [Helper Functions](#helper-functions)
- [Access Internal State](#access-internal-state)
- [Access Internal State and Actions](#access-internal-state-and-actions)
- [Examples](#examples)
- [Development](#development)
- [Testing](#testing)
@@ -156,6 +156,12 @@ Fits view port so that all nodes are visible
`getElements = (): Elements`
### setTransform
Sets position and zoom of the pane
`setTransform = (transform: FlowTransform): void`
# Nodes
There are three different [node types](#node-types--custom-nodes) (`default`, `input`, `output`) you can use. The node types differ in the number and types of handles. An input node has only a source handle, a default node has a source and a target and an output node has only a target handle. You create nodes by adding them to the `elements` array of the `ReactFlow` component.
@@ -507,7 +513,7 @@ Returns all direct child nodes of the passed node
You can use these function as seen in [this example](/example/src/Overview/index.js#L40-L41) or use your own ones.
# Access Internal State
# Access Internal State and Actions
Under the hood React Flow uses [Easy Peasy](https://easy-peasy.now.sh/) for state handling.
If you need to access the internal state you can use the `useStoreState` hook inside a child component of the `ReactFlow` component:
@@ -530,6 +536,21 @@ const Flow = () => (
);
```
You will not need this in most cases but you can also use the internal actions that are defined in the [store](/src/store/index.ts):
```javascript
import React, { useEffect } from 'react';
import { useStoreActions } from 'react-flow-renderer'
const TransformUpdater = ({ x, y, zoom }) => {
const setTransform = useStoreActions(a => a.setInitTransform);
useEffect(() => {
setTransform({ x, y, k: zoom })
}, [x, y, zoom])
});
```
If you need more control you can wrap the `ReactFlow` component with the `ReactFlowProvider` component in order to be able to call `useStoreState` outside of the `ReactFlow` component.
# Examples
-3
View File
@@ -13,9 +13,6 @@ describe('Basic Flow Rendering', () => {
it('renders a grid', () => {
cy.get('.react-flow__background');
const gridStroke = Cypress.$('.react-flow__background path').attr('stroke');
expect(gridStroke).to.equal('#eee');
});
it('selects a node by click', () => {
+1 -1
View File
@@ -136,7 +136,7 @@ const OverviewFlow = () => {
}}
/>
<Controls />
<Background color="#888" gap={16} />
<Background color="#aaa" gap={16} />
</ReactFlow>
);
};
+19 -13
View File
@@ -1,4 +1,4 @@
import React, { memo, HTMLAttributes } from 'react';
import React, { memo, useMemo, HTMLAttributes } from 'react';
import cc from 'classcat';
import { useStoreState } from '../../store/hooks';
@@ -21,26 +21,32 @@ const defaultColors = {
const Background = memo(
({ variant = BackgroundVariant.Dots, gap = 24, size = 0.5, color, style, className }: BackgroundProps) => {
const width = useStoreState((s) => s.width);
const height = useStoreState((s) => s.height);
const [x, y, scale] = useStoreState((s) => s.transform);
const bgClasses = cc(['react-flow__background', className]);
const bgColor = color ? color : defaultColors[variant];
const scaledGap = gap * scale;
const xOffset = x % scaledGap;
const yOffset = y % scaledGap;
const isLines = variant === BackgroundVariant.Lines;
const path = isLines
? createGridLinesPath(width, height, xOffset, yOffset, scaledGap)
: createGridDotsPath(width, height, xOffset, yOffset, scaledGap, size);
const fill = isLines ? 'none' : bgColor;
const stroke = isLines ? bgColor : 'none';
const bgSvgTile = useMemo(() => {
const isLines = variant === BackgroundVariant.Lines;
const bgColor = color ? color : defaultColors[variant];
const path = isLines ? createGridLinesPath(scaledGap, size, bgColor) : createGridDotsPath(size, bgColor);
return encodeURIComponent(
`<svg width="${scaledGap}" height="${scaledGap}" xmlns='http://www.w3.org/2000/svg'>${path}</svg>`
);
}, [variant, scaledGap, size, color]);
return (
<svg width={width} height={height} style={style} className={bgClasses}>
<path fill={fill} stroke={stroke} strokeWidth={size} d={path} />
</svg>
<div
className={bgClasses}
style={{
...style,
backgroundImage: `url("data:image/svg+xml;utf8,${bgSvgTile}")`,
backgroundPosition: `${xOffset}px ${yOffset}px`,
}}
></div>
);
}
);
@@ -2,4 +2,6 @@
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
+4 -34
View File
@@ -1,37 +1,7 @@
export const createGridLinesPath = (
width: number,
height: number,
xOffset: number,
yOffset: number,
gap: number
): string => {
const lineCountX = Math.ceil(width / gap) + 1;
const lineCountY = Math.ceil(height / gap) + 1;
const xValues = Array.from({ length: lineCountX }, (_, i) => `M${i * gap + xOffset} 0 V${height}`);
const yValues = Array.from({ length: lineCountY }, (_, i) => `M0 ${i * gap + yOffset} H${width}`);
return [...xValues, ...yValues].join(' ');
export const createGridLinesPath = (scaledGap: number, strokeWidth: number, stroke: string): string => {
return `<path stroke="${stroke}" strokeWidth="${strokeWidth}" d="M0 0 V${scaledGap} M0 0 H${scaledGap}" />`;
};
export const createGridDotsPath = (
width: number,
height: number,
xOffset: number,
yOffset: number,
gap: number,
size: number
): string => {
const lineCountX = Math.ceil(width / gap) + 1;
const lineCountY = Math.ceil(height / gap) + 1;
const values = Array.from({ length: lineCountX }, (_, col) => {
const x = col * gap + xOffset;
return Array.from({ length: lineCountY }, (_, row) => {
const y = row * gap + yOffset;
return `M${x} ${y - size} l${size} ${size} l${-size} ${size} l${-size} ${-size}z`;
}).join(' ');
});
return values.join(' ');
export const createGridDotsPath = (size: number, fill: string): string => {
return `<circle cx="${size}" cy="${size}" r="${size}" fill="${fill}" />`;
};
+2
View File
@@ -183,6 +183,8 @@ const GraphView = memo(
zoomOut: () => zoom(-0.2),
project,
getElements,
setTransform: (transform: FlowTransform) =>
setInitTransform({ x: transform.x, y: transform.y, k: transform.zoom }),
});
}
+4 -6
View File
@@ -13,10 +13,8 @@ const useElementUpdater = (elements: Elements): void => {
const setEdges = useStoreActions((a) => a.setEdges);
useEffect(() => {
const nodes: Node[] = elements.filter(isNode);
const edges: Edge[] = elements.filter(isEdge).map((e) => parseElement(e) as Edge);
const nextNodes: Node[] = nodes.map((propNode) => {
const nextEdges: Edge[] = elements.filter(isEdge).map((e) => parseElement(e) as Edge);
const nextNodes: Node[] = elements.filter(isNode).map((propNode) => {
const existingNode = stateNodes.find((n) => n.id === propNode.id);
if (existingNode) {
@@ -83,14 +81,14 @@ const useElementUpdater = (elements: Elements): void => {
});
const nodesChanged: boolean = !isEqual(stateNodes, nextNodes);
const edgesChanged: boolean = !isEqual(stateEdges, edges);
const edgesChanged: boolean = !isEqual(stateEdges, nextEdges);
if (nodesChanged) {
setNodes(nextNodes);
}
if (edgesChanged) {
setEdges(edges);
setEdges(nextEdges);
}
}, [elements, stateNodes, stateEdges]);
};
+1
View File
@@ -175,6 +175,7 @@ type OnLoadParams = {
fitView: FitViewFunc;
project: ProjectFunc;
getElements: () => Elements;
setTransform: (transform: FlowTransform) => void;
};
export type OnLoadFunc = (params: OnLoadParams) => void;