@@ -31,15 +31,15 @@ const BasicGraph = () => (
|
||||
|
||||
# Props
|
||||
|
||||
- `elements`: array with nodes and edges
|
||||
- `elements`: array of [nodes](#nodes) and [edges](#edges) *(required)*
|
||||
- `onElementClick`: element click handler
|
||||
- `onElementsRemove`: element remove handler
|
||||
- `onNodeDragStop`: node drag stop handler
|
||||
- `onConnect`: connect handler
|
||||
- `onLoad`: editor load handler
|
||||
- `onMove`: move handler
|
||||
- `nodeTypes`: object with node types
|
||||
- `edgeTypes`: object with node types
|
||||
- `nodeTypes`: object with [node types](#node-types--custom-nodes)
|
||||
- `edgeTypes`: object with [edge types](#edge-types--custom-edges)
|
||||
- `style`: css style passed to the wrapper
|
||||
- `connectionLineType`: connection line type = `straight` or `bezier`
|
||||
- `connectionLineStyle`: connection style as svg attributes
|
||||
@@ -53,7 +53,127 @@ const BasicGraph = () => (
|
||||
- `snapGrid`: [x, y] array - default: `[16, 16]`
|
||||
- `onlyRenderVisibleNodes`: default: `true`
|
||||
|
||||
## Nodes
|
||||
|
||||
There are three different [node types](#node-types--custom-nodes) (`default`, `input`, `output`) you can use. You can also create [custom nodes](#node-types--custom-nodes).
|
||||
|
||||
Node example: `{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }`
|
||||
|
||||
**Node Props**
|
||||
|
||||
- `id`: string *(required)*
|
||||
- `position`: { x: number, y: number } *(required)*
|
||||
- `data`: {} *(required if you are using a standard type, otherwise depends on your implementation)*
|
||||
- `type`: 'input' | 'output' | 'default' or a custom one you implemented
|
||||
- `style`: css properties
|
||||
|
||||
## Edges
|
||||
|
||||
There are three [edge types](#edge-types--custom-edges) (`straight`, `default`, `step`) you can use. The default type is `default`. You can also create [custom edges](#edge-types--custom-edges).
|
||||
|
||||
Edge example: `{ id: 'e1-2', type: 'straight', source: '1', target: '2', animated: true }`
|
||||
|
||||
**Edge Props**
|
||||
|
||||
- `id`: string *(required)*
|
||||
- `source`: string *(required)*
|
||||
- `target`: string *(required)*
|
||||
- `type`: 'input' | 'output' | 'default' or a custom one you implemented
|
||||
- `animated`: boolean
|
||||
- `style`: css properties
|
||||
|
||||
## Node Types / Custom Nodes
|
||||
|
||||
The standard node types are `input`, `default` and `output`. The default node types object looks like this:
|
||||
|
||||
```javascript
|
||||
{
|
||||
input: InputNode,
|
||||
default: DefaultNode,
|
||||
output: OutputNode
|
||||
}
|
||||
```
|
||||
|
||||
The keys represent the type names and the values are the node components that gets rendered.
|
||||
If you want to introduce a new node type you can pass a node types object like this:
|
||||
|
||||
```javascript
|
||||
nodeTypes={{
|
||||
special: MyCustomNode
|
||||
}}
|
||||
```
|
||||
|
||||
You can now use type `special` for a node.
|
||||
The `default`, `input` and `output` types will be still available except you overwrite one of them.
|
||||
You can find an example of how to implement a custom node in [custom nodes example](example/src/CustomNodes).
|
||||
|
||||
## Edge Types / Custom Edges
|
||||
|
||||
The standard edge types are `straight`, `default` and `step`. The default edge types object looks like this:
|
||||
|
||||
```javascript
|
||||
{
|
||||
default: BezierEdge,
|
||||
straight: StraightEdge,
|
||||
step: StepEdge
|
||||
}
|
||||
```
|
||||
|
||||
The keys represents the type name and the value is the edge component.
|
||||
If you want to introduce a new edge type you can pass a edge types object like this:
|
||||
|
||||
```javascript
|
||||
edgeTypes={{
|
||||
special: MyCustomEdge
|
||||
}}
|
||||
```
|
||||
|
||||
You can now use type `special` for an edge.
|
||||
The `straight`, `default` and `step` types will be still available except you overwrite one of them.
|
||||
|
||||
# Plugins
|
||||
|
||||
- Mini map
|
||||
- Controls: zoom & fit view
|
||||
## MiniMap
|
||||
|
||||
You can use the MiniMap plugin by passing it as a children of you graph:
|
||||
|
||||
```javascript
|
||||
import Graph, { MiniMap } from 'react-flow';
|
||||
|
||||
const GraphWithMiniMap = () => (
|
||||
<Graph
|
||||
elements={elements}
|
||||
>
|
||||
<MiniMap />
|
||||
</Graph>
|
||||
);
|
||||
```
|
||||
|
||||
**Props**
|
||||
|
||||
- `nodeColor`: string | function - if you pass a color as a string all nodes will get that color. If you pass a function you can return a color depending on the node.
|
||||
- `nodeBorderRadius`: number
|
||||
- `maskColor`: string
|
||||
- `style`: css properties
|
||||
- `className`: class name
|
||||
|
||||
## Controls
|
||||
|
||||
The control panel contains a zoom-in, zoom-out and a fit-view button. You can use it by passing it as children to your graph:
|
||||
|
||||
```javascript
|
||||
import Graph, { Controls } from 'react-flow';
|
||||
|
||||
const GraphWithControls = () => (
|
||||
<Graph
|
||||
elements={elements}
|
||||
>
|
||||
<Controls />
|
||||
</Graph>
|
||||
);
|
||||
```
|
||||
|
||||
**Props**
|
||||
|
||||
- `style`: css properties
|
||||
- `className`: class name
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('Basic Graph Rendering', () => {
|
||||
expect(gridStroke).to.equal('#eee');
|
||||
});
|
||||
|
||||
it('selects a node', () => {
|
||||
it('selects a node by click', () => {
|
||||
cy.get('.react-flow__node:first').click().should('have.class', 'selected');
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('Basic Graph Rendering', () => {
|
||||
cy.get('.react-flow__node:first').should('not.have.class', 'selected');
|
||||
});
|
||||
|
||||
it('selects an edge', () => {
|
||||
it('selects an edge by click', () => {
|
||||
cy.get('.react-flow__edge:first').click().should('have.class', 'selected');
|
||||
});
|
||||
|
||||
@@ -33,6 +33,22 @@ describe('Basic Graph Rendering', () => {
|
||||
cy.get('.react-flow__edge:first').should('not.have.class', 'selected');
|
||||
});
|
||||
|
||||
it('selects one node with a selection', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, force: true })
|
||||
.trigger('mousemove', 800, 75, { which: 1 })
|
||||
.trigger('mouseup', 'bottomRight', { force: true })
|
||||
.get('.react-flow__node')
|
||||
.first()
|
||||
.should('have.class', 'selected')
|
||||
.get('.react-flow__node')
|
||||
.last()
|
||||
.should('have.not.class', 'selected')
|
||||
.get('.react-flow__nodesselection-rect');
|
||||
});
|
||||
|
||||
it('selects all nodes', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
|
||||
@@ -7,11 +7,58 @@ describe('Empty Flow Rendering', () => {
|
||||
cy.get('.react-flow__edge').should('not.exist');
|
||||
});
|
||||
|
||||
it('renders empty selection', () => {
|
||||
cy.get('body')
|
||||
.type('{shift}', { release: false })
|
||||
.get('.react-flow__selectionpane')
|
||||
.trigger('mousedown', 'topLeft', { which: 1, force: true })
|
||||
.trigger('mousemove', 'bottomRight', { which: 1 })
|
||||
.trigger('mouseup', 'bottomRight', { force: true })
|
||||
});
|
||||
|
||||
it('renders a control panel', () => {
|
||||
cy.get('.react-flow__controls');
|
||||
});
|
||||
|
||||
it('renders a mini map', () => {
|
||||
it('uses zoom in control', () => {
|
||||
cy.get('.react-flow__controls-zoomin').click();
|
||||
});
|
||||
|
||||
it('uses zoom out control', () => {
|
||||
cy.get('.react-flow__controls-zoomout').click();
|
||||
});
|
||||
|
||||
it('uses fit view control', () => {
|
||||
cy.get('.react-flow__controls-fitview').click();
|
||||
});
|
||||
|
||||
it('renders an empty mini map', () => {
|
||||
cy.get('.react-flow__minimap');
|
||||
cy.get('.react-flow__minimap-node').should('not.exist');
|
||||
});
|
||||
|
||||
it('adds two nodes', () => {
|
||||
cy.contains('add node').click();
|
||||
cy.contains('add node').click();
|
||||
});
|
||||
|
||||
it('connects nodes', () => {
|
||||
cy.get('.react-flow__node')
|
||||
.first()
|
||||
.find('.react-flow__handle.source')
|
||||
.trigger('mousedown', { which: 1 });
|
||||
|
||||
cy.get('.react-flow__node')
|
||||
.last()
|
||||
.find('.react-flow__handle.target')
|
||||
.trigger('mousemove')
|
||||
.trigger('mouseup', { force: true });
|
||||
|
||||
cy.get('.react-flow__edge').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('renders mini map with two nodes', () => {
|
||||
cy.get('.react-flow__minimap');
|
||||
cy.get('.react-flow__minimap-node').should('have.length', 2);
|
||||
});
|
||||
});
|
||||
|
||||
Vendored
+1312
-1309
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1312
-1309
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -127,6 +127,11 @@ function renderEdge(edge: Edge, props: EdgeRendererProps, nodes: Node[], selecte
|
||||
if (!targetNode) {
|
||||
throw new Error(`couldn't create edge for target id: ${targetId}`);
|
||||
}
|
||||
|
||||
if (!sourceNode.__rg.width || !sourceNode.__rg.height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const edgeType = edge.type || 'default';
|
||||
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
|
||||
const sourceHandle = getHandle(sourceNode.__rg.handleBounds.source, sourceHandleId);
|
||||
|
||||
+2
-2
@@ -178,10 +178,10 @@ export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => {
|
||||
});
|
||||
};
|
||||
|
||||
export const fitView = ({ padding }: FitViewParams = { padding: 0 }): void => {
|
||||
export const fitView = ({ padding }: FitViewParams = { padding: 0.1 }): void => {
|
||||
const { nodes, width, height, d3Selection, d3Zoom } = store.getState();
|
||||
|
||||
if (!d3Selection || !d3Zoom) {
|
||||
if (!d3Selection || !d3Zoom || !nodes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user