feat(edges): allow different types

This commit is contained in:
moklick
2019-07-24 23:25:14 +02:00
parent 6312f6bd1e
commit 9d44f26c68
10 changed files with 109 additions and 40 deletions

View File

@@ -85,7 +85,7 @@ class App extends PureComponent {
return (
<Graph
elements={this.state.elements}
onNodeClick={node => console.log(node)}
onElementClick={node => console.log('clicked', node)}
onNodeRemove={nodeIds => console.log('remove', nodeIds)}
style={{ width: '100%', height: '100%' }}
onLoad={graphInstance => this.onLoad(graphInstance)}

View File

@@ -11,9 +11,7 @@ export default (props) => {
return (
<path
className="react-graph__edge"
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
onClick={e => console.log('on edge click')}
/>
);
};

View File

@@ -0,0 +1,33 @@
import React, { useContext } from 'react';
import ReactDraggable from 'react-draggable';
import cx from 'classnames';
import { GraphContext } from '../../GraphContext';
import { updateNodePos, setSelectedNodesIds } from '../../state/actions';
const isInputTarget = (e) => ['INPUT', 'SELECT', 'TEXTAREA'].includes(e.target.nodeName);
export default EdgeComponent => (props) => {
const { state, dispatch } = useContext(GraphContext);
const { data, onClick } = props;
const { id } = data;
const [ x, y, k ] = state.transform;
const selected = state.selectedNodeIds.includes(id);
const edgeClasses = cx('react-graph__edge', { selected });
return (
<g
className={edgeClasses}
onClick={(e) => {
if (isInputTarget(e)) {
return false;
}
// dispatch(setSelectedNodesIds(id));
onClick({ data });
}}
>
<EdgeComponent {...props} />
</g>
);
};

View File

@@ -1,30 +1,34 @@
import React, { PureComponent } from 'react';
import Edge from './Edge';
import { Consumer } from '../GraphContext';
function renderEdge(e, nodes) {
const sourceNode = nodes.find(n => n.data.id === e.data.source);
const targetNode = nodes.find(n => n.data.id === e.data.target);
if (!sourceNode) {
throw new Error(`couldn't create edge for source id: ${e.data.source}`);
}
if (!targetNode) {
throw new Error(`couldn't create edge for source id: ${e.data.target}`);
}
return (
<Edge
key={`${e.data.source}-${e.data.target}`}
sourceNode={sourceNode}
targetNode={targetNode}
/>
);
}
class EdgeRenderer extends PureComponent {
renderEdge(e, nodes, onElementClick) {
const edgeType = e.data.type || 'default';
const sourceNode = nodes.find(n => n.data.id === e.data.source);
const targetNode = nodes.find(n => n.data.id === e.data.target);
if (!sourceNode) {
throw new Error(`couldn't create edge for source id: ${e.data.source}`);
}
if (!targetNode) {
throw new Error(`couldn't create edge for source id: ${e.data.target}`);
}
const EdgeComponent = this.props.edgeTypes[edgeType] || this.props.edgeTypes.default;
return (
<EdgeComponent
key={`${e.data.source}-${e.data.target}`}
sourceNode={sourceNode}
targetNode={targetNode}
onClick={onElementClick}
{...e}
/>
);
}
render() {
const { width, height } = this.props;
@@ -34,7 +38,7 @@ class EdgeRenderer extends PureComponent {
return (
<Consumer>
{({ state }) => (
{({ state, onElementClick }) => (
<svg
width={width}
height={height}
@@ -43,7 +47,7 @@ class EdgeRenderer extends PureComponent {
<g
transform={`translate(${state.transform[0]},${state.transform[1]}) scale(${state.transform[2]})`}
>
{state.edges.map(e => renderEdge(e, state.nodes))}
{state.edges.map(e => this.renderEdge(e, state.nodes, onElementClick))}
</g>
</svg>
)}

22
src/EdgeRenderer/utils.js Normal file
View File

@@ -0,0 +1,22 @@
import DefaultEdge from './EdgeTypes/DefaultEdge';
import wrapEdge from './EdgeTypes/wrapEdge';
export function createEdgeTypes(edgeTypes) {
const standardTypes = {
default: wrapEdge(edgeTypes.default || DefaultEdge),
};
const specialTypes = Object
.keys(DefaultEdge)
.filter(k => !['default'].includes(k))
.reduce((res, key) => {
res[key] = wrapEdge(nodeTypes[key] || DefaultEdge);
return res;
}, {});
return {
...standardTypes,
...specialTypes
};
}

View File

@@ -19,7 +19,7 @@ function usePrevious(value) {
export const Provider = (props) => {
const {
onNodeClick,
onElementClick,
children
} = props;
@@ -52,7 +52,7 @@ export const Provider = (props) => {
});
const graphContext = {
onNodeClick,
onElementClick,
state,
dispatch
};

View File

@@ -74,7 +74,11 @@ const GraphView = (props) => {
return (
<div className="react-graph__renderer">
<NodeRenderer nodeTypes={props.nodeTypes} />
<EdgeRenderer width={state.width} height={state.height} />
<EdgeRenderer
width={state.width}
height={state.height}
edgeTypes={props.edgeTypes}
/>
{shiftPressed && <UserSelection />}
{state.nodesSelectionActive && <NodesSelection />}
<div

View File

@@ -12,7 +12,7 @@ export default NodeComponent => (props) => {
const { state, dispatch } = useContext(GraphContext);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const { data, onNodeClick, __rg } = props;
const { data, onClick, __rg } = props;
const { position } = __rg;
const { id } = data;
const [ x, y, k ] = state.transform;
@@ -70,7 +70,7 @@ export default NodeComponent => (props) => {
}
dispatch(setSelectedNodesIds(id));
onNodeClick({ data, position });
onClick({ data, position });
}}
>
<NodeComponent {...props} />

View File

@@ -4,7 +4,7 @@ import { Consumer } from '../GraphContext';
class NodeRenderer extends PureComponent {
renderNode(d, onNodeClick) {
renderNode(d, onElementClick) {
const nodeType = d.data.type || 'default';
if (!this.props.nodeTypes[nodeType]) {
console.warn(`No node type found for type "${nodeType}". Using fallback type "default".`);
@@ -15,7 +15,7 @@ class NodeRenderer extends PureComponent {
return (
<NodeComponent
key={d.data.id}
onNodeClick={onNodeClick}
onClick={onElementClick}
{...d}
/>
);
@@ -24,14 +24,14 @@ class NodeRenderer extends PureComponent {
render() {
return (
<Consumer>
{({ onNodeClick, state }) => (
{({ onElementClick, state }) => (
<div
className="react-graph__nodes"
style={{
transform: `translate(${state.transform[0]}px,${state.transform[1]}px) scale(${state.transform[2]})`
}}
>
{state.nodes.map(d => this.renderNode(d, onNodeClick))}
{state.nodes.map(d => this.renderNode(d, onElementClick))}
</div>
)}
</Consumer>

View File

@@ -4,11 +4,14 @@ import { parseElements, separateElements } from './graph-utils';
import GraphView from './GraphView';
import GlobalKeyHandler from './GlobalKeyHandler';
import { Provider } from './GraphContext';
import { createNodeTypes } from './NodeRenderer/utils';
import DefaultNode from './NodeRenderer/NodeTypes/DefaultNode';
import InputNode from './NodeRenderer/NodeTypes/InputNode';
import OutputNode from './NodeRenderer/NodeTypes/OutputNode';
import { createNodeTypes } from './NodeRenderer/utils';
import DefaultEdge from './EdgeRenderer/EdgeTypes/DefaultEdge';
import { createEdgeTypes } from './EdgeRenderer/utils';
import './style.css';
@@ -17,11 +20,12 @@ class ReactGraph extends PureComponent {
super(props);
this.nodeTypes = createNodeTypes(props.nodeTypes);
this.edgeTypes = createEdgeTypes(props.edgeTypes);
}
render() {
const {
style, onNodeClick, children, onLoad, onMove, onChange, elements, onNodeRemove
style, onElementClick, children, onLoad, onMove, onChange, elements, onNodeRemove
} = this.props;
const { nodes, edges } = elements
@@ -30,12 +34,13 @@ class ReactGraph extends PureComponent {
return (
<div style={style} className="react-graph">
<Provider nodes={nodes} edges={edges} onNodeClick={onNodeClick}>
<Provider nodes={nodes} edges={edges} onElementClick={onElementClick}>
<GraphView
onLoad={onLoad}
onMove={onMove}
onChange={onChange}
nodeTypes={this.nodeTypes}
edgeTypes={this.edgeTypes}
/>
<GlobalKeyHandler
onNodeRemove={onNodeRemove}
@@ -48,7 +53,7 @@ class ReactGraph extends PureComponent {
}
ReactGraph.defaultProps = {
onNodeClick: () => {},
onElementClick: () => {},
onNodeRemove: () => {},
onLoad: () => {},
onMove: () => {},
@@ -57,6 +62,9 @@ ReactGraph.defaultProps = {
input: InputNode,
default: DefaultNode,
output: OutputNode
},
edgeTypes: {
default: DefaultEdge
}
};