Custom Nodes
A node contains the node content itself and its ports. Check NodeModel source code, if you want to see what class methods can be extended.

Extending the NodeModel

If you want to create a custom node that looks entirely different, then you need to create a component that renders using its default or customized data mode. In the example below, it uses a customized data model DiamondNodeModel to render DiamondNodeWidget, and both of them are being created in the model factory DiamondNodeFactory.
Because our Diamond node always has four ports, we add four default port models into the DiamondNodeModel. Depending on the type of node you are creating, this is basically where you store your vertex data in the graph theory sense.
1
// DiamondNodeModel.ts
2
import { NodeModel, NodeModelGenerics, PortModelAlignment } from '@projectstorm/react-diagrams';
3
import { DiamondPortModel } from './DiamondPortModel';
4
​
5
export interface DiamondNodeModelGenerics {
6
PORT: DiamondPortModel;
7
}
8
​
9
// this can be further extended for more complicated node types
10
export class DiamondNodeModel extends NodeModel<NodeModelGenerics & DiamondNodeModelGenerics> {
11
constructor() {
12
super({
13
type: 'diamond'
14
});
15
this.addPort(new DiamondPortModel(PortModelAlignment.TOP));
16
this.addPort(new DiamondPortModel(PortModelAlignment.LEFT));
17
this.addPort(new DiamondPortModel(PortModelAlignment.BOTTOM));
18
this.addPort(new DiamondPortModel(PortModelAlignment.RIGHT));
19
}
20
}
Copied!
This is where we create our customized component. This component can be any customized react component as long as they respect the node and engine props. Ports also need to be rendered inside the node component.
1
// DiamondNodeWidget.tsx
2
import * as React from 'react';
3
import { DiamondNodeModel } from './DiamondNodeModel';
4
import { DiagramEngine, PortModelAlignment, PortWidget } from '@projectstorm/react-diagrams';
5
import styled from '@emotion/styled';
6
​
7
export interface DiamondNodeWidgetProps {
8
// node and engine props are required
9
node: DiamondNodeModel;
10
engine: DiagramEngine;
11
size?: number;
12
}
13
​
14
namespace S {
15
export const Port = styled.div`
16
width: 16px;
17
height: 16px;
18
z-index: 10;
19
background: rgba(0, 0, 0, 0.5);
20
border-radius: 8px;
21
cursor: pointer;
22
&:hover {
23
background: rgba(0, 0, 0, 1);
24
}
25
`;
26
}
27
​
28
// this can be any customized react component as long as they respect
29
// the node and engine props
30
export class DiamondNodeWidget extends React.Component<DiamondNodeWidgetProps> {
31
render() {
32
return (
33
<div
34
className={'diamond-node'}
35
style={{
36
position: 'relative',
37
width: this.props.size,
38
height: this.props.size
39
}}>
40
<svg
41
width={this.props.size}
42
height={this.props.size}
43
dangerouslySetInnerHTML={{
44
__html:
45
`
46
<g id="Layer_1">
47
</g>
48
<g id="Layer_2">
49
<polygon fill="mediumpurple" stroke="${
50
this.props.node.isSelected() ? 'white' : '#000000'
51
}" stroke-width="3" stroke-miterlimit="10" points="10,` +
52
this.props.size / 2 +
53
` ` +
54
this.props.size / 2 +
55
`,10 ` +
56
(this.props.size - 10) +
57
`,` +
58
this.props.size / 2 +
59
` ` +
60
this.props.size / 2 +
61
`,` +
62
(this.props.size - 10) +
63
` "/>
64
</g>
65
`
66
}}
67
/>
68
<PortWidget
69
style={{
70
top: this.props.size / 2 - 8,
71
left: -8,
72
position: 'absolute'
73
}}
74
port={this.props.node.getPort(PortModelAlignment.LEFT)}
75
engine={this.props.engine}>
76
<S.Port />
77
</PortWidget>
78
<PortWidget
79
style={{
80
left: this.props.size / 2 - 8,
81
top: -8,
82
position: 'absolute'
83
}}
84
port={this.props.node.getPort(PortModelAlignment.TOP)}
85
engine={this.props.engine}>
86
<S.Port />
87
</PortWidget>
88
<PortWidget
89
style={{
90
left: this.props.size - 8,
91
top: this.props.size / 2 - 8,
92
position: 'absolute'
93
}}
94
port={this.props.node.getPort(PortModelAlignment.RIGHT)}
95
engine={this.props.engine}>
96
<S.Port />
97
</PortWidget>
98
<PortWidget
99
style={{
100
left: this.props.size / 2 - 8,
101
top: this.props.size - 8,
102
position: 'absolute'
103
}}
104
port={this.props.node.getPort(PortModelAlignment.BOTTOM)}
105
engine={this.props.engine}>
106
<S.Port />
107
</PortWidget>
108
</div>
109
);
110
}
111
}
Copied!
Now we need to create a new node factory to tell the system how our new node model fits into the core system. We specifically are going to extend the DefaultLinkFactory because we want to render a DiamondNodeWidget with data model being DiamondNodeModel. To accomplish that, we simply extend generateReactWidget(event) to return a DiamondNodeWidget and extend generateModel to return a DiamondNodeModel instance.
1
// DiamondNodeFactory.tsx
2
import { DiamondNodeWidget } from './DiamondNodeWidget';
3
import { DiamondNodeModel } from './DiamondNodeModel';
4
import * as React from 'react';
5
import { AbstractReactFactory } from '@projectstorm/react-canvas-core';
6
import { DiagramEngine } from '@projectstorm/react-diagrams-core';
7
​
8
export class DiamondNodeFactory extends AbstractReactFactory<DiamondNodeModel, DiagramEngine> {
9
constructor() {
10
super('diamond');
11
}
12
​
13
generateReactWidget(event): JSX.Element {
14
// event.model is basically what's returned from generateModel()
15
return <DiamondNodeWidget engine={this.engine} size={50} node={event.model} />;
16
}
17
​
18
generateModel(event) {
19
return new DiamondNodeModel();
20
}
21
}
Copied!
The actual code for the DiamondNode can be found here (it is in the demo-custom-node1 folder in the demo gallery).
This is the easiest and most simple way to get started with custom nodes.
Last modified 4mo ago
Copy link