Node
Modifying built-in component
The build-in component for nodes simplifies customization and provides the most common variants of ports number and their location.
Nevertheless, the nodes content vary considerably among different projects, thats why this library provides only one built-in component for node content: NodeLabel
. This component display only a node label in the center of the node component. You can provide your own component for node content as a property innerNode
of the settings you pass into functions to create built-in node component.
Settings
interface INodeDefaultSettingsWithoutPorts {
innerNode?: React.FunctionComponent<{ node: NodeState }>; // component to display node content
removeDefaultClasses?: true; // set to true to remove default classes
classes?: { // see styling section below
base?: string[],
hovered?: string[],
selected?: string[],
"selected-hovered"?: string[],
};
style?: {
base?: React.CSSProperties,
hovered?: React.CSSProperties,
selected?: React.CSSProperties,
"selected-hovered"?: React.CSSProperties,
};
}
Creating
The functions to create node component with predefined ports are:
createInputOutputHorizontalNode(settings)
- create a component with input on the left border of the node and output on the right ('input' and 'output' means nothing but theid
of port and itstype
).createInputOutputVerticalNode(settings)
- input on the top border and output on the bottom.createInputHorizontalNode(settings)
- input on the left border.createInputVerticalNode(settings)
- input on the top border.createOutputHorizontalNode(settings)
- output on the right border.createOutputVerticalNode(settings)
- output on the bottom border.createStarNode(settings)
- there 4 ports on each border with ids:left
,top
,right
,bottom
.
If you want to define your own set of ports there is a function createNode(settings)
, which besides the settings enables you to provide ports properties (see documentation for Port).
createNode({
ports: [ // array of ports properties
{
id: 'number_1',
type: 'input',
position: 'left-center',
offsetFromOrigin: [0, -8],
},
{
id: 'number_2',
type: 'input',
position: 'left-center',
offsetFromOrigin: [0, 8],
},
{
id: 'output',
type: 'output',
position: 'right-center',
},
],
innerNode: Sum,
classes: { base: [styles.nodePadding] },
})
info
All those listed methods will not only add Port
s components to render ports you pass to it, but also register them in the component state, see Ports.
Styling
You can provide classes and styles for node in settings for each state, like hovered, selected or both. The base
classes and styles are applied regardless of node state.
createStarNode({
classes: {
base: ['node_base'],
hovered: ['node_hovered'], // if node will be hovered the classes 'node_base' and 'node_hovered' will be added to component along with the default ones
selected: ['node_selected'],
"selected-hovered": ['node_hovered','node_selected', 'node_hovered_and_selected'],
};
style: {
base: {
border: '1px solid black'
},
selected: { // will be merged with 'base' when node will be selected
border: '1px solid red', // this will override border in 'base'
boxShadow: '3px 3px 3px grey'; // this will be added
},
};
})
Your own component
Creating your own component for node is very simple and require only wrapping it in observer
as were written in the Introduction.
Let's write a node component with background depending on rather node is hovered or not. It will also display it's position and label. To see other data available in entity
object, which you will also get as a prop, go to the Node state.
To render ports just use component Port
and pass to it id of the port to render.
const NodeComponent = observer(({ entity }) => {
return (
<div
style={{
padding: 15,
backgroundColor: entity.hovered ? '#8c8cff' : '#b6b6ff', // thanks to wrapping in 'observer' component will subscribe to all properties you read from any observable object
width: 150,
fontSize: 10,
}}
>
<div>Label: {entity.label]}</div>
<div>X: {entity.position[0]}</div>
<div>Y: {entity.position[1]}</div>
<Port id='input' />
</div>
);
});
If you have many of ports or they can be added/removed dynamically, you can use code like this to render them all:
{Array.from(entity.ports).map(([id]) => (
<Port id={id} key={id} />
))}
Ports
When you add <Port id='some-id' />
component to anywhere in node itself or its childs, it just means that you want to render in this place the port found in node's state by the id "some-id". But if this port has not been added to state then Port
component will not render anything. There are two places where you can define which ports node will have:
- Right in the component definition - this way every node of this type will have those ports by default.
- In a node state - this way only this specific node will have specified port.
You can also combine those methods and define some default ports for type, and then override some of port's values or add new ones for specific node.
<Diagram
settings={{
nodes: {
components: {
custom_node: {
component: MyCustomNode, // your React functional component
settings: {
ports: [ // ports are added to all nodes of type 'custom_node'
{
id: 'port_1',
position: 'bottom-center',
data: 'any data you want to store here'
}
]
}
}
}
}
}}
initState={{
nodes: [{
id: 'node_1',
position: [100, 100],
type: 'custom_node', // the component key you added in settings
ports: [
{
id: 'port_1',
data: "override data defined in component",
position: null, // use null to override value to undefined even if this property is defined in component
},
{
id: 'port_2', // add a new port
}
]
}],
}}
/>
User interaction
By default the library will listen for drag, click or other events on all HTML elements inside a node. But sometimes we want to allow user to drag only node header, or to disable dragging for slider element etc. To achieve this you can use constants for HTML classes, that are importable from library as ENABLE_NODE_USER_INTERACTION_CLASS
and DISABLE_NODE_USER_INTERACTION_CLASS
.
For example:
<div className={'node_class'}> {/* user will be able to drag this element */}
<div className={DISABLE_NODE_USER_INTERACTION_CLASS}> {/* but not this */}
<div> {/* and not this, because its parent has DISABLE_NODE_USER_INTERACTION_CLASS */}
<div className={ENABLE_NODE_USER_INTERACTION_CLASS}> {/* but this user will be able to drag again */}
<div></div> {/* and this also yes */}
</div>
</div>
</div>
</div>