THIS REPOSITORY IS ARCHIVED if you are looking for a react library to build node based uis, checkout https://reactflow.dev/
Ramen is a simple but extensible react library for building node editors for visual programming.
You can find examples and the complete documentation here.
Ramen can be installed via npm.
$ npm i -S @au-re/ramen
import Ramen from "@au-re/ramen";
Start by creating a schema. The schema defines the types of nodes, fields and connections that can exist in your graph.
const schema = {
nodeTypes: {
number: {
fields: [
{
id: "number",
dataType: "number",
output: true,
}
]
},
add: {
fields: [
{
id: "number1",
dataType: "number",
input: true,
},
{
id: "number2",
dataType: "number",
input: true,
},
{
id: "result",
dataType: "number",
output: true,
}
]
}
},
dataTypes: {
number: {
name: "Number",
color: "#333",
validTargets: [
"number",
],
},
},
};
The graph state is persisted in the following format:
const graph = {
xPos: 100,
yPos: 200,
nodes: [
{
id: "0",
x: 100,
y: 50,
type: "number",
},
{
id: "1",
x: 100,
y: 200,
type: "number",
},
{
id: "2",
x: 450,
y: 50,
type: "add",
},
],
connections: [
{
originNode: "0",
originPin: "number",
targetNode: "2",
targetPin: "number1",
},
{
originNode: "1",
originPin: "number",
targetNode: "2",
targetPin: "number2",
},
],
}
To initialize a node editor you can pass both the schema and the graph. Note that connections in graph
that are not allowed by the schema will be ignored.
<Ramen
schema={schema}
initialGraph={graph}
/>
The NodeEditor can be either controlled or uncontrolled. By adding a graph property to the editor, it becomes controlled: Passing a new graph value to the editor will cause the editor to rerender and the graph will no longer update its own state internally. Instead you should use a callback to check changes in the graph.
<Ramen
graph={graph}
/>
You can listen to changes to the graph structure, e.g. a new connection is created, removed.
<Ramen
onNodeCreated
onNodeDeleted
onConnectionCreated
onConnectionDeleted
onGraphChange
onControlChange
/>
In order to update the state of a node it is often necessary to provide an input of some sorts for the case that no connection to a field exists. For that reason you can pass custom inputs to ramen:
function NumberControl(props) {
const { defaultValue } = props;
return <input type="number" />
}
const schema = {
nodeTypes: {
type1: {
fields: [
{
id: "input",
dataType: "number",
controlType: "numberControl"
}
]
}
}
};
<NodeEditor
schema={schema}
controls={{
numberControl: NumberControl,
}}
onControlChange={(nodeId, fieldId, data) => {}}
/>
You can initialize the state of controls with the initialGraph
value, or the graph
value if you are using the editor controlled.
const graph = {
nodes: [
{
id: "0",
type: "add",
defaultValues: {
number1: 10
}
},
],
};
You can customize every aspect of the editor by either passing a custom theme or custom components.
The simplest way to customize the look of the editor is by swapping the theme. Ramen ships with two
themes, dark
and light
.
Ramen uses the styled-components
library for styling. You can use styled-components ThemeProvider
to pass a new theme:
import { ThemeProvider } from "styled-components";
// example theme
const theme = {};
<ThemeProvider theme={theme}>
<NodeEditor />
<ThemeProvider
You can find more information on how to use the ThemeProvider
and how to switch themes here.
You can also provide custom components:
function Background() {
return (<div></div>);
}
<NodeEditor
Background={Background}
/>
Custom components can be styled to your liking, and you can modify their internal behavior.
// custom node with an input field instead of a title
function Node(props) {
const { name } = props;
return (
<div>
<input value={name} />
{children}
</div>)
}
<NodeEditor
Node={Node}
/>
The following components can be passed as properties:
<NodeEditor
Background
Node
Noodle
Field
ContextMenu
BoxSelection
/>
Children of the node editor will be displayed overlaying the graph editor. You can access the internal state of the graph editor through the editorContext
.
import { editorContext } from "@au-re/ramen";
function MiniMap(props) {
const { graph, setGraph } = React.useContext(editorContext);
// render a minimap
}
<NodeEditor>
<MiniMap />
</NodeEditor>