Creating Nodes
A graph is comprised of nodes as well as connections between these nodes. A node inside the graph can be seen as the instance of a class. You have to define the "class" before being able to create the node.
Each node is similar to a function in programming: It receives input data, performs some calculation and returns output data. For input and output so-called Node Interfaces are used. The calculation is done inside the calculate
function. You can find more about it here.
There are two approaches for defining node types:
defineNode()
This is the recommended way to create node types as it is easier to understand what's happening and provides more type safety when using TypeScript. The defineNode
function is similar to the defineComponent
function of Vue (hence the name). It expects a single argument, which is an object defining the node type.
You can find all the properties of the object here, but these are the most important ones:
type
: This is the only required property. The node type is used for saving and loading graphs, for example. It can essentially be seen as the name of your class. Therefore, it has to be unique.title
: (optional) The default title of the node. If this property isn't specified, thetype
is used as the default title. (Note that users can rename nodes, so don't rely on the title for identifying nodes).inputs
: (optional) An object specifying the input interfaces of the node. The keys are the "handle" for the node interface, while the values are functions that return aNodeInterface
instance (or an instance of a subclass ofNodeInterface
).outputs
: (optional) An object specifying the output interfaces of the node. It has the same structure as theinputs
object.calculate
: (optional) A function that is called whenever the node is executed. You can find more details on the Graph Execution page.
Additionally, you can provide lifecycle callbacks.
Let's put it all together into an example. In this case, we want to have a node that receives two numbers, performs an arithmetic operation on them and outputs the result. This means we need three inputs (the two numbers and the arithmetic operation) and one output (the result of the calculation):
import { defineNode, NodeInterface, NumberInterface, SelectInterface } from "baklavajs";
export default defineNode({
type: "MathNode",
inputs: {
number1: () => new NumberInterface("Number", 1),
number2: () => new NumberInterface("Number", 10),
operation: () => new SelectInterface("Operation", "Add", ["Add", "Subtract"]).setPort(false),
},
outputs: {
output: () => new NodeInterface("Output", 0),
},
calculate({ number1, number2, operation }) {
let output: number;
if (operation === "Add") {
output = number1 + number2;
} else if (operation === "Subtract") {
output = number1 - number2;
} else {
throw new Error("Unknown operation: " + operation);
}
return { output };
},
});
There are also other properties that allow you to customize your nodes :
name | data type | default |
---|---|---|
position.x | number | 0 |
position.y | number | 0 |
width | number | settings.defaultWidth |
disablePointerEvents | boolean | false |
twoColumn | boolean | false |
reverseY | boolean? | undefined |
import { AbstractNode, CalculateFunction, NodeInterface } from "@baklavajs/core";
import { TextareaInputInterface } from "../src/nodeinterfaces/textareainput/TextareaInputInterface";
import { ButtonInterface } from "../src";
import { InputAnswerInterface } from "./interface/InputAnswerInterface";
export default class AnswerNode extends AbstractNode {
public type = "answer";
public inputs: Record<string, NodeInterface<any>> = {
input: new NodeInterface("Content", ""),
description: new TextareaInputInterface("", "").setPort(false),
};
public outputs: Record<string, NodeInterface<any>> = {};
private counter = 0;
public constructor() {
super();
this.initializeIo();
this.width = 400;
this.title = "Answer";
this.reverseY = true;
}
public addChoice(value?: string) {
const name = "answer" + ++this.counter;
this.addOutput(
name,
new InputAnswerInterface(name, value, () => {
this.removeOutput(name);
}),
);
}
public onPlaced() {
this.addInput("addAnswer", new ButtonInterface("Add " + this.title, () => this.addChoice()));
}
calculate?: CalculateFunction<any, any> | undefined;
}
Class-based approach
For nodes that have dynamic inputs and outputs, it is also possible to use a class-based approach similar to BaklavaJS v1.
WARNING
It is very important to call this.initializeIo()
in your constructor! Otherwise, the node might not work properly.
Although it doesn't really make sense to use it in this case, here is the MathNode
from the example above written using the class-based approach: