Skip to main content

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:

  1. 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 the id of port and its type).
  2. createInputOutputVerticalNode(settings) - input on the top border and output on the bottom.
  3. createInputHorizontalNode(settings) - input on the left border.
  4. createInputVerticalNode(settings) - input on the top border.
  5. createOutputHorizontalNode(settings) - output on the right border.
  6. createOutputVerticalNode(settings) - output on the bottom border.
  7. 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).

Definition of Sum node component from calculator example
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 Ports 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:

  1. Right in the component definition - this way every node of this type will have those ports by default.
  2. 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.

Example
<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>