@ndbx/runtime
Version:
The `@ndbx/runtime` package provides a runtime environment to embed NodeBox visualizations directly into React applications. NodeBox is a powerful tool for creating interactive and generative visualizations, and this runtime allows you to integrate those
228 lines (200 loc) • 7.51 kB
text/typescript
import JSON5 from "json5";
import { Context, FunctionItem, Project, PortType, ParameterType } from "../src";
import { createProject, createNetwork, createNode } from "../src/mutation";
import { parseNodeStatements } from "../src/loaders";
import { Color } from "@ndbx/g";
export function createFunctionItem({
name,
category,
description,
source,
}: {
name: string;
category: string;
description: string;
source: string;
}): FunctionItem {
const { parameters, sections, inputPorts, outputPorts } = parseNodeStatements(source.split("\n"));
return {
type: "FUNCTION",
id: name,
name,
category,
description,
inputPorts,
outputPorts,
parameters,
sections,
source,
width: 1000,
height: 1000,
background: Color.black(),
};
}
export function createValueFn(): FunctionItem {
const source = `
export default function(node) {
const valueIn = node.numberIn({ name: "value" });
const attributeIn = node.stringIn({ name: "attribute", value: "value" });
const tableOut = node.tableOut({ name: "out" });
node.onRender = () => {
const table = [{ [attributeIn.value]: valueIn.value }];
tableOut.set(table);
};
}
`;
return createFunctionItem({ name: "value", category: "Math", description: "Generates a simple value", source });
}
export function createAddFn(): FunctionItem {
const source = `
import { tableFromArrays, makeVector } from 'https://esm.sh/apache-arrow';
export default function(node) {
const tableIn = node.tableIn({ name: "table" });
const attribute1In = node.stringIn({ name: "attribute", defaultValue: "value" });
const attribute2In = node.stringIn({ name: "attribute", defaultValue: "value" });
const targetAttributeIn = node.stringIn({ name: "targetAttribute", defaultValue: "value" });
const tableOut = node.tableOut({ name: "out" });
node.onRender = () => {
const [table, attribute1, attribute2, targetAttribute] = [tableIn.value, attribute1In.value, attribute2In.value, targetAttributeIn.value];
const vector1 = table.getChild(attribute1);
const vector2 = table.getChild(attribute2);
const vectorOut = makeVector(new Float64Array(table.numRows));
for (let i = 0; i < table.numRows; i++) {
const v1 = i < vector1.length ? vector1.get(i) : null;
const v2 = i < vector2.length ? vector2.get(i) : null;
if (typeof v1 !== "number" || typeof v2 !== "number") {
vectorOut.set(i, null);
} else {
vectorOut.set(i, v1 + v2);
}
}
const tableMap = table.schema.fields.reduce((acc, field) => {
acc[field.name] = table.getChild(field.name);
return acc;
}, {});
tableMap[targetAttribute] = vectorOut;
tableOut.set(makeTable(tableMap));
};
}
`;
return {
type: "FUNCTION",
id: "add",
name: "add",
category: "Math",
description: "Adds two columns",
inputPorts: [{ type: PortType.Table, name: "table" }],
outputPorts: [{ type: PortType.Table, name: "out" }],
parameters: [
{ type: ParameterType.String, name: "attribute1", label: "Attribute 1", defaultValue: "value" },
{ type: ParameterType.String, name: "attribute2", label: "Attribute 2", defaultValue: "value" },
{ type: ParameterType.String, name: "targetAttribute", label: "Target Attribute", defaultValue: "value" },
],
source,
};
}
export function createNegateFn(): FunctionItem {
const source = `
export default function(node) {
const tableIn = node.tableIn({ name: "table" });
const attributeIn = node.stringIn({ name: "attribute", value: "value" });
const targetAttributeIn = node.stringIn({ name: "targetAttribute", value: "value" });
const tableOut = node.tableOut({ name: "out" });
node.onRender = () => {
const [table, attribute, targetAttribute] = [tableIn.value, attributeIn.value, targetAttributeIn.value];
const newTable = [];
for (let i = 0; i < table.length; i++) {
const row = table[i];
const v = row[attribute];
if (typeof v === "number") {
newTable.push({...row, [targetAttribute]: -v});
} else {
newTable.push({...row, [targetAttribute]: null});
}
}
tableOut.set(newTable);
};
}
`;
return createFunctionItem({ name: "negate", category: "Math", description: "Negates a column", source });
}
export function createMakeNumbersFn(): FunctionItem {
const source = `
import { tableFromArrays, makeVector } from 'https://esm.sh/apache-arrow';
export default function(node) {
const templateIn = node.stringIn({ name: "template", defaultValue: "11;22;33" });
const targetAttributeIn = node.stringIn({ name: "targetAttribute", defaultValue: "value" });
const tableOut = node.tableOut({ name: "out" });
node.onRender = () => {
const [template, targetAttribute] = [templateIn.value, targetAttributeIn.value];
const array = template.split(";").map(x => parseFloat(x));
const table = tableFromArrays({ [targetAttribute]: array });
tableOut.set(table);
};
}
`;
return {
type: "FUNCTION",
id: "make-numbers",
name: "make-numbers",
category: "Math",
description: "Parse numbers from a string",
inputPorts: [],
outputPorts: [{ type: PortType.Table, name: "out" }],
parameters: [
{ type: ParameterType.String, name: "template", label: "Template", defaultValue: "11;22;33" },
{ type: ParameterType.String, name: "targetAttribute", label: "Target Attribute", defaultValue: "value" },
],
source,
};
}
export function createRectFn(): FunctionItem {
const source = `
import { Rect, Group } from "@ndbx/g";
export default function(node) {
const xIn = node.numberIn({ name: "x", value: 0 });
const yIn = node.numberIn({ name: "y", value: 0 });
const widthIn = node.numberIn({ name: "width", value: 100, min: 0 });
const heightIn = node.numberIn({ name: "height", value: 100, min: 0 });
const fillIn = node.colorIn({ name: "fill", value: "black" });
const shapeOut = node.shapeOut({ name: "Out" });
node.onRender = () => {
const rect = new Rect(xIn.value, yIn.value, widthIn.value, heightIn.value);
rect.fill = fillIn.value;
shapeOut.set(rect);
};
}
`;
return createFunctionItem({ name: "rect", category: "Graphics", description: "Draws a rectangle", source });
}
export function createMathProject(): Project {
const project = createProject("math");
project.items.push(createAddFn());
project.items.push(createNegateFn());
project.items.push(createValueFn());
project.items.push(createMakeNumbersFn());
return project;
}
export function createGraphicsProject(): Project {
const project = createProject("g");
project.items.push(createRectFn());
return project;
}
export function createTestContext() {
const project = createProject("test");
const cx = new Context(
project,
new Map(),
new Map([
["test/math", createMathProject()],
["test/g", createGraphicsProject()],
]),
);
const network = createNetwork(cx, project, "test");
// plan = createNetwork(plan, "test");
// plan = createNode(plan, "test", "math.add");
// for (const project of plan.dependencies) {
// project.functions.forEach((fn) => loadFunction(plan, project, fn));
// }
return { cx, project, network };
}