# Introduction ::introduction :: **Rete.js** (pronounced /หˆriห.ti/, meaning "network" in Italian) is a framework for creating visual interfaces and workflows. It provides out-of-the-box solutions for visualization using various libraries and frameworks, as well as solutions for processing graphs based on dataflow and control flow approaches. ::alert **You are reading the documentation for Rete.js v2** - Rete.js v1 documentation is still available at [rete.js.org](https://rete.js.org){rel="nofollow"} - Upgrading from Rete.js v1? Check out the [Migration](https://retejs.org/docs/migration) guide :: In the interactive example above, you can see the notation of the basic elements of the classic editor view and the graph processing using the Dataflow-based engine. This demonstrates the essential capabilities of the framework: - **Visualization**: you can choose React.js, Vue.js, Angular, Svelte or Lit to visualize nodes, sockets, controls, and connections. These visual components can be tailored to your specific needs by creating custom components for each framework, and they can all coexist in a single editor. - **Processing**: the framework offers various types of engines that enable processing diagrams based on their nature, including dataflow and control flow. These types can be combined within the same graph. ::youtube-video{name="intro"} :: ## Ecosystem The framework is made up of various packages, including the core `rete` package and different plugins. This approach provides flexibility in choosing only the required packages for your use case, avoiding unnecessary dependencies. It also offers the flexibility to choose different versions (e.g., testing out an RC version of a specific plugin) and substitute these packages with your custom builds (forks or plugins created from scratch). Each of packages has its own repository, providing development flexibility but also adding overhead. For this reason, **Rete CLI** was created to build TypeScript modules with built-in ESLint and Jest support, reducing boilerplate code and providing basic tools for plugin development and debugging, ultimately saving developers time. Rete.js provides various handy resources and tools to get you started quickly: - **Examples**: check out the [Examples](https://retejs.org/examples) page or [Codesandbox](https://codesandbox.io/search?refinementList%5Btags%5D%5B0%5D=rete.js){rel="nofollow"} for code samples - **Rete Kit**: easily generate a basic project for your framework of choice using this tool. For more details, visit the [Rete Kit](https://retejs.org/docs/development/rete-kit) page. - **Guides**: get hands-on experience by following the [step-by-step guides](https://retejs.org/docs/guides/basic). This will enable you to explore the existing features and learn how to extend and customize the framework to your needs # Getting started ::references :::ref-example{link="/examples" title="Examples"} ::: :::ref-guide{link="/docs/development/rete-kit" title="Rete Kit"} ::: :::ref-external --- link: https://codesandbox.io/s/rete-js-v2-yrzxe5 title: Codesandbox --- ::: :::ref-external{link="https://codepen.io/Ni55aN/pen/rNZKejd" title="Codepen"} ::: :: There are two easy ways to begin working with the framework: forking [the examples](https://retejs.org/examples) on Codesandbox or creating a local application using [Rete Kit](https://retejs.org/docs/development/rete-kit). The first option allows for experimentation with functionality, which not always covered in the guides. Alternatively, the second option enables the creation of a local application with specific node editor features for a chosen version of **React.js**, **Vue.js**, **Angular**, **Svelte** or **Lit**. Afterward, following the guides will help familiarize yourself with the framework's features and capabilities. ::youtube-video{name="getting-started"} :: ## Prerequisites Before diving into Rete.js, it's important to have an understanding of JavaScript or TypeScript fundamentals. The framework is primarily designed with TypeScript in mind, with examples and guides showcasing code in this language. However, for newcomers to TypeScript or those looking to quickly prototype, it's still possible to use Rete.js directly in JavaScript code. If TypeScript is your preferred choice, make sure you have TypeScript version 4.7 or higher installed. ## Playgrounds - [Codesandbox](https://codesandbox.io/s/rete-js-v2-yrzxe5){rel="nofollow"} - [Codepen](https://codepen.io/Ni55aN/pen/rNZKejd){rel="nofollow"} ## Creating an application using devkit Use [Rete Kit](https://retejs.org/docs/development/rete-kit) to quickly set up a Rete.js application. It lets you select a stack (**React.js**, **Vue.js**, **Angular**, **Svelte** or **Lit**) and the set of features. ## Adding Rete.js to your application Framework packages are available on NPM and support common formats like ES (.esm.js), CommonJS (.common.js), and UMD (.min.js). The command below provides an example of how to install the framework packages for the latest version. ```bash npm i rete rete-area-plugin rete-connection-plugin rete-render-utils rete-react-plugin react react-dom ``` For specific information on the required packages, refer [one of the guides](https://retejs.org/docs/guides/basic) ## Usage from CDN Framework packages are also available on numerous CDNs that serve npm packages. To add them to an HTML page, use the following example: ```html ``` Use these packages by accessing their namespace, which can be found in the `name` field of `rete.config.ts` file for each package. Make sure to add the required peer dependencies ```js Rete ReteAreaPlugin ReteConnectionPlugin ReteReactPlugin ``` Furthermore, you can integrate them on platforms like [Codepen](https://codepen.io){rel="nofollow"} using [esm.sh](https://esm.sh){rel="nofollow"}. # LLMs.txt Enhance your visual programming experience with AI tools that understand Rete.js node editor patterns, dataflow concepts, and plugin architecture. ## What is LLMs.txt? Building node editors and visual programming interfaces involves unique patterns and concepts that differ from traditional application development. Our AI integration through [LLMs.txt](https://llmstxt.org/){rel="nofollow"} helps coding assistants understand: - **Node-based architecture** and connection patterns - **Dataflow vs Control flow** processing paradigms - **Plugin composition** for extensible editors - **Multi-framework rendering** (React, Vue, Angular, Svelte) - **Graph processing** and manipulation techniques This enables AI tools to provide contextually accurate suggestions for your visual programming projects. ## Resources Our documentation is optimized for AI consumption through specialized endpoints that understand visual programming workflows: - **[llms.txt](https://retejs.org/llms.txt){rel="nofollow"}** - Essential node editor concepts, core APIs, and quick reference patterns - **[llms-full.txt](https://retejs.org/llms-full.txt){rel="nofollow"}** - Complete visual programming knowledge base including advanced patterns, plugin architecture, and processing engines These endpoints are continuously updated to reflect the latest in node editor development practices and framework capabilities. ## How to Use You can integrate Rete.js documentation with any AI coding assistant by providing the LLMs.txt URLs as context. This gives the AI comprehensive knowledge about node editor development patterns and best practices. ### Basic integration steps 1. Copy the LLMs.txt URL: `https://retejs.org/llms.txt` (or `llms-full.txt` for complete docs) 2. Reference it in your AI tool's context or conversation 3. Ask questions about Rete.js development, node editor patterns, or get code suggestions ### VS Code GitHub Copilot Add the LLMs.txt URL to your workspace for enhanced context-aware suggestions: 1. Create a `.copilot-instructions.md` file in your project root 2. Add the reference: ```markdown # Copilot Instructions For Rete.js development, reference the documentation at: https://retejs.org/llms-full.txt Focus on node editor patterns, dataflow programming, and plugin architecture. ``` 3. Copilot will now understand Rete.js concepts when providing code completions and suggestions ### Google Gemini Chat Reference the documentation directly in your conversation: ```text I'm building a node editor with Rete.js. Please review the documentation at https://retejs.org/llms-full.txt and help me create a custom node that processes image data through multiple transformation steps. ``` ### Model Context Protocol (MCP) MCP-compatible tools like Context7 automatically discover and fetch the Rete.js documentation as context to AI models: 1. The complete Rete.js documentation is automatically loaded as context for AI conversations 2. AI models have persistent access to node editor patterns, plugin architecture, and best practices 3. No manual setup required - the context is always available This approach ensures every AI interaction has comprehensive Rete.js knowledge without any configuration. # Plugin system ::references :::ref-github --- link: https://github.com/retejs/rete/blob/main/src/scope.ts title: Source code --- ::: :: Plugins offer the ability to add new functionality mostly through a single entry point. They communicate with each other using signals that propagate from parent plugins to child plugins. Since plugins can have multiple child plugins, these signals are passed through in the order they are connected (it can be important when including plugins such as `rete-readonly-plugin`) ::diagram{caption="Architecture" name="plugin-system/architecture"} :: The following code example demonstrates the creation of two scopes: the parent and the child. Both scopes log signals. ```ts import { Scope } from 'rete'; const parentScope = new Scope('parent'); // number is produced type const childScope = new Scope('child'); // [number] is expected types of parent chain parentScope.addPipe((context) => { // add pipe to parent scope console.log('parent', context); // number return context; }); childScope.addPipe((context) => { // add pipe to child scope console.log('child', context); // string | number return context; }); parentScope.use(childScope); // forward all signals to child scope const returnedNumber = await parentScope.emit(1); // can emit number const returnedString = await childScope.emit('a'); // can emit string ``` Keep in mind that the order of `use` and `addPipe` affects the order in which the parent and child handlers are called. Logs: ```log parent 1 child 1 child a ``` Signals can be modified or prevented in some cases. ::diagram{caption="Prevent and modify" name="plugin-system/addPipe"} :: ```ts parentScope.addPipe((context) => { return context * 2; }); childScope.addPipe((context) => { if (context === 'b') return // prevent propagation of 'b' return context; }); const doubledNumber = await parentScope.emit(1); // 2 const expectedString = await childScope.emit('a'); // 'a' const expectedUndefined = await childScope.emit('b'); // undefined ``` Static typing is used to ensure that the expected signals of used plugins are compatible with those produced by its parent plugin. ```ts import { Scope } from 'rete'; const parentScope = new Scope('parent'); const childScope = new Scope('child'); parentScope.use(childScope); // Type 'boolean' is not assignable to type 'string | number'.ts(2345) ``` Child plugins can access the instance of the parent plugin both for direct access to its interfaces and for producing signals on behalf of the parent plugin ```ts import { Scope } from 'rete'; class Root extends Scope { isRoot = true } class Root2 extends Scope { isRoot2 = true } const parentScope = new Root('parent'); const childScope = new Scope('child'); parentScope.use(childScope); const parent = childScope.parentScope(); // Root instance, but Scope from TS perspective const root = childScope.parentScope(Root); // Root instance const wrongInstance = childScope.parentScope(Root2); // throws exception ``` # Presets A preset is a set of pre-built functionality that typically forms the foundation of an editor but can be replaced with another preset from the same category or a custom one. ::diagram{caption="Architecture" name="presets/architecture"} :: ## Data structures For example, there is a classic editor preset that provides classes such as Node, Connection, Input, Output, and Socket. ```ts import { ClassicPreset } from 'rete'; const { Node, Connection, Socket, Input, Output, Control } = ClassicPreset ``` ## Rendering In addition, each render plugin has preset for displaying classic nodes based on the data structures mentioned above. ```ts import { ReactPlugin, Presets as ReactPresets } from 'rete-react-plugin' const reactPlugin = new ReactPlugin({ createRoot }) reactPlugin.addPreset(ReactPresets.classic.setup()) reactPlugin.addPreset(ReactPresets.contextMenu.setup()) ``` ## Interaction `rete-connection-plugin` comes with presets, one of which replicates the connection interaction functionality from framework v1. Additionally, an alternative preset with a simpler way to interact with connections has been included. ```ts import { ConnectionPlugin, Presets as ConnectionPresets } from 'rete-connection-plugin' const connection = new ConnectionPlugin() connection.addPreset(ConnectionPresets.classic.setup()) ``` ## Conclusion In essence, the presets can be used in any scenario that involves the need to implement a particular functionality through the use of one or more alternative approaches. # Editor ::diagram{caption="NodeEditor" name="editor/node-editor"} :: `NodeEditor` is a class that implements an interface for interacting with a graph. Similar to other modules, it extends `Scope`: can produce signals and provides the ability to connect plugins. ```ts import { NodeEditor, BaseSchemes } from 'rete' type Schemes = BaseSchemes // has Node { id: string } and Connection { id: string, source: string, target: string } const editor = new NodeEditor() ``` The `Schemes` type will be used for further type inference purposes. There is a [classic preset](https://retejs.org/docs/concepts/presets#data-structures) that provides the interfaces for building nodes. The editor is applicable on both the client and server sides. On the client side, it can provide data for visualization purposes. On the server side, it can provide data for graph processing, for example, through `rete-engine` or other interactions using `rete-structures`. ## Node and Connection management ::diagram{caption="ClassicPreset" name="editor/classic-preset"} :: We can add nodes as a regular object with a mandatory `id` field, or as nodes from `ClassicPreset` ```ts import { ClassicPreset } from 'rete' const node = new ClassicPreset.Node('Label') node.addOutput('output', new ClassicPreset.Output(socket, 'Title')) await editor.addNode(node) ``` Removing can be achieved with `id` ```ts await editor.removeNode(node.id) ``` To create a connection, you can use a basic object with mandatory fields (`id`, `source`, `target`) or a classic preset that requires the source and target nodes to be passed for TypeScript type checking. ```ts import { ClassicPreset } from 'rete' const connection = new ClassicPreset.Connection(sourceNode, 'portKey', targetNode, 'portKey') await editor.addConnection(connection) ``` Removing the connection by `id` ```ts await editor.removeConnection(connection.id) ``` ## Create a 2D area ::diagram{caption="AreaPlugin" name="editor/area"} :: In order to visualize on HTML, `rete-area-plugin` is necessary. This plugin is responsible for basic features, such as zooming and dragging, and serves as an entry point for other plugins for visualizing and interacting with users ```ts import { AreaPlugin } from 'rete-area-plugin' const area = new AreaPlugin(container) // container is HTMLElement where the area will be inserted ``` The `AreaExtra` type is necessary for incorporating other signal types, such as rendering various types of elements aside from `node` and `connection` This plugin includes extensions. Some of them implement the functionality of v1, but with one significant difference - they are optional. For instance, the node selection extension not only supports node selection, but it is expandable (check out [Comments](https://retejs.org/examples/comments) example), but it can also be substituted with an alternative implementation. ```ts import { AreaExtensions } from 'rete-area-plugin' AreaExtensions.selectableNodes(area, AreaExtensions.selector(), { accumulating: AreaExtensions.accumulateOnCtrl() }) ``` ## Interaction with connections ::diagram{caption="ConnectionPlugin" name="editor/connection"} :: The `rete-connection-plugin` plugin is responsible for user interaction with connections (creation, deletion) ```ts import { BidirectFlow, ConnectionPlugin, Presets as ConnectionPresets } from 'rete-connection-plugin' const connection = new ConnectionPlugin() connection.addPreset(ConnectionPresets.classic.setup()) ``` Unlike Rete.js v1, this plugin doesn't render connections. ## Rendering ::diagram{caption="Rendering" name="editor/rendering"} :: The rendering of the UI is exclusively handled by rendering plugins (with a few exceptions), which provide presets for various kinds of functionality. Let's take a look at the example using `rete-react-plugin` ```ts import { ReactArea2D, ReactPlugin, Presets as ReactPresets } from 'rete-react-plugin' import { MinimapExtra } from 'rete-minimap-plugin' import { ContextMenuExtra } from 'rete-context-menu-plugin' type AreaExtra = | ReactArea2D | ContextMenuExtra | MinimapExtra const reactPlugin = new ReactPlugin() reactPlugin.addPreset(ReactPresets.classic.setup()) reactPlugin.addPreset(ReactPresets.contextMenu.setup()) reactPlugin.addPreset(ReactPresets.minimap.setup()) area.use(reactPlugin) ``` Every element in the editor, like node, control, socket, or connection, is technically an independent tree of elements, which offers flexibility in combining different rendering frameworks. More information is available in the [Integration](https://retejs.org/docs/concepts/integration) article. # Engine ::diagram{caption="Architecture" name="engine/architecture"} :: The `rete-engine` is a package that implements two approaches for processing graph: [Dataflow](https://retejs.org/#dataflow) and [Control flow](https://retejs.org/#control-flow) ### Dataflow The dataflow approach is focused solely on data, where the target node requests data from incoming nodes. Graph processing proceeds from left to right, passing the output of nodes as input arguments to outgoing nodes. ::diagram{caption="Dataflow" name="engine/dataflow"} :: This approach is commonly used in products with node editors such as Blender. The code below represents the basic constructs required for the `DataflowEngine` to work: - **Nodes must implement a `data` method**: this method accepts data from incoming nodes - **Connect the engine to the editor**: the engine will register every added node for further processing - **Fetching node data**: `fetch` initiates a graph traversal starting from the target node and visiting all its predecessors ```ts import { ClassicPreset } from 'rete-engine' import { DataflowEngine } from 'rete-engine' const { Node, Socket } = ClassicPreset class NodeAdd extends Node<{ left: Socket, right: Socket }, { value: Socket }, { }> { constructor() { // init controls and ports } // mandatory method data(inputs: { left?: number[], right?: number[] }): { value: number } { const left = inputs.left[0] || 0 const right = inputs.right[0] || 0 return { value: left + right } } } const engine = new DataflowEngine() editor.use(engine) const nodeOutput = await engine.fetch(resultNode.id) ``` ### Control flow Control flow is a node traversal approach that allows you to determine how to pass control to the next nodes. ::diagram{caption="Control flow" name="engine/control-flow"} :: The processing starts at the start node, which specifies how control is passed through its outgoing connections. For instance, it can be a delay or a branching. The closest example is UE4 Blueprints. ```ts import { ControlFlowEngine } from 'rete-engine' const { Node, Socket } = ClassicPreset class Log extends Node<{ enter: Socket }, { out: Socket }, {}> { constructor() { // init ports } // mandatory method execute(input: 'enter', forward: (output: 'out') => void) { console.log('log something') forward('out') } } const engine = new ControlFlowEngine() editor.use(engine) engine.execute(startNode.id) ``` ## Hybrid In addition, these approaches can be combined. For example, ports named 'exec' can be used to control flow, while other ports manage data. ```ts const controlflow = new ControlFlowEngine(node => { return { inputs: () => ['exec'], outputs: () => ['exec'] } }) const dataflow = new DataflowEngine(({ inputs, outputs }) => { return { inputs: () => Object.keys(inputs).filter(name => name !== 'exec'), outputs: () => Object.keys(outputs).filter(name => name !== 'exec') } }) ``` Alternatively, you can use the `Dataflow` and `ControlFlow` classes directly, affording more precise control over the graph processing. ```ts import { ControlFlow, Dataflow } from 'rete-engine' const control = new ControlFlow(editor) const dataflow = new Dataflow(editor) control.add(startNode, { inputs: () => [], outputs: () => ['exec'], async execute(input, forward) { const inputs = await dataflow.fetchInputs(startNode.id) forward('exec') } }) dataflow.add(startNode, { inputs: () => ['data'], outputs: () => ['data'], data(fetchInputs) { const inputs = await fetchInputs() const data = { data: inputs.data[0] // forward input data (assuming there is only one input connection to port "data") } return data } }) ``` ## Conclusion This engine version incorporates revised approaches to graph processing and addresses the shortcomings of the previous version, which was initially geared towards strict dataflow without recursion support. When it comes to graph processing, there's no one-size-fits-all solution. In simple cases, you can use `DataflowEngine` and `ControlFlowEngine`, while in more complex cases, you can use `ControlFlow` and `Dataflow` or write your own solution by studying [the source code](https://github.com/retejs/engine){rel="nofollow"} of the `rete-engine` package # Integration ::diagram{caption="Architecture" name="integration/architecture"} :: This framework is not bound to any UI rendering framework and can be integrated with the most popular frameworks/libraries such as **Angular**, **Svelte**, **Lit**, **Vue.js**, **React.js**. The following rendering packages are available: - [`rete-react-plugin`](https://www.npmjs.com/package/rete-react-plugin){rel="nofollow"} - [`rete-vue-plugin`](https://www.npmjs.com/package/rete-vue-plugin){rel="nofollow"} - [`rete-angular-plugin`](https://www.npmjs.com/package/rete-angular-plugin){rel="nofollow"} - [`rete-svelte-plugin`](https://www.npmjs.com/package/rete-svelte-plugin){rel="nofollow"} - [`@retejs/lit-plugin`](https://www.npmjs.com/package/@retejs/lit-plugin){rel="nofollow"} The primary objective is to empower you to choose the visualization tools that align with your specific needs. Additionally, if you ever need to use the render plugin for a different framework within your application (such as during a project migration), you can easily do so. Please note that `rete-angular-plugin` is only compatible with Angular applications. ## Classic preset ::diagram{caption="Presets" name="integration/presets"} :: By default, you can use the classic preset that has built-in components for: - nodes - connections - some controls (numeric or text input fields) ```ts import { AngularPlugin, AngularArea2D, Presets as AngularPresets } from 'rete-angular-plugin' const angular = new AngularPlugin>({ injector }) angular.addPreset(AngularPresets.classic.setup()) area.use(angular) ``` The framework allows you to swap out the pre-defined components with any other components as per your needs. The node component, in particular, can be customized extensively. Refer to the [Customization](https://retejs.org/docs/guides/renderers/react#customization) guide for more details ## Combining render plugins ::diagram{caption="Combine" name="integration/combine"} :: This framework version has improved approaches for combining various rendering frameworkswhile ensuring TypeScript support. For instance, you can render one node using **Vue.js** and another using **React.js**. ```ts import { ReactArea2D, ReactPlugin, Presets as ReactPresets } from 'rete-react-plugin' import { VueArea2D, VuePlugin, Presets as VuePresets } from 'rete-vue-plugin' type AreaExtra = | ReactArea2D | VueArea2D const reactPlugin = new ReactPlugin() const vuePlugin = new VuePlugin() reactPlugin.addPreset(ReactPresets.classic.setup({ customize: { node(data) { if (data.payload instanceof AddNode) return null // prevent rendering of AddNode by React.js return ReactPresets.classic.Node } } })) vuePlugin.addPreset(VuePresets.classic.setup({ customize: { node() { return VuePresets.classic.Node // render all nodes that weren't rendered by previously used render plugin } } })) // order matters area.use(reactPlugin) area.use(vuePlugin) ``` The `AddNode` node in this example is rendered using **Vue.js**, while all other nodes are rendered using **React.js**. Using multiple frameworks at once may have performance drawbacks, but it can also offer a significant boost to prototyping speed when when time-to-market is critical. # Basic editor ::alert This guide features the `rete-react-plugin`. You can use it in any application, regardless of the framework you're using (**React.js**, **Vue.js**, **Angular**, **Svelte** etc.). Follow this guide to use the corresponding render plugin in your **Angular**, **Svelte** or **Vue.js** application, with reference to the respective guides for [Angular](https://retejs.org/docs/guides/renderers/angular), [Svelte](https://retejs.org/docs/guides/renderers/svelte), [Vue.js](https://retejs.org/docs/guides/renderers/vue) etc. :: ::references :::ref-example{link="/examples/basic" title="Basic"} ::: :::ref-guide{link="/docs/guides/data-structures" title="Data structures"} ::: :::ref-github{link="https://github.com/retejs/rete" title="Core"} ::: :::ref-github{link="https://github.com/retejs/area-plugin" title="Area plugin"} ::: :::ref-github --- link: https://github.com/retejs/connection-plugin title: Connection plugin --- ::: :: In order to complete this guide, you can create an application on **[Codesandbox](https://codesandbox.io){rel="nofollow"}** by choosing any client application template or set up the application locally. ## Install dependencies ::kit :: ```bash npm i rete rete-area-plugin rete-connection-plugin rete-react-plugin rete-render-utils styled-components react@18 react-dom@18 ``` ## Defining types and initializing the editor instance ```ts import { NodeEditor, GetSchemes, ClassicPreset } from "rete"; type Schemes = GetSchemes< ClassicPreset.Node, ClassicPreset.Connection >; const editor = new NodeEditor(); ``` where `Schemes` is a type that will assist you with type inference during plugin configuration. For more intricate examples, it may be necessary to extend the `ClassicPreset.Node` and `ClassicPreset.Connection` classes. ## Add an arbitrary node Creating a node that contains one control and the output port. Keep in mind that `addNode` is an asynchronous method. ```ts const socket = new ClassicPreset.Socket("socket"); const nodeA = new ClassicPreset.Node("A"); nodeA.addControl("a", new ClassicPreset.InputControl("text", {})); nodeA.addOutput("a", new ClassicPreset.Output(socket)); await editor.addNode(nodeA); ``` ## Create an area to render with React.js Place this code before calling `addNode`: ```ts import { createRoot } from "react-dom/client"; import { AreaPlugin } from "rete-area-plugin"; import { ReactPlugin, Presets, ReactArea2D } from "rete-react-plugin"; type AreaExtra = ReactArea2D; const area = new AreaPlugin(container); const render = new ReactPlugin({ createRoot }); render.addPreset(Presets.classic.setup()); editor.use(area); area.use(render); ``` where - `container` is the HTMLElement where the editor will be placed - `AreaExtra` type enables the inclusion of custom viewable elements, as only `node` and `connection` are available by default ## Adding another node ```ts const nodeB = new ClassicPreset.Node("B"); nodeB.addControl("b", new ClassicPreset.InputControl("text", {})); nodeB.addInput("b", new ClassicPreset.Input(socket)); await editor.addNode(nodeB); ``` Let's establish connection between these nodes ```ts await editor.addConnection(new ClassicPreset.Connection(nodeA, "a", nodeB, "b")); ``` ## Node positioning The screen will display two overlapping nodes, but we can adjust the position of the second node. ```ts await area.translate(nodeB.id, { x: 270, y: 0 }); ``` ## Interactive connections This feature enables users to interact with the nodes. ```ts import { ConnectionPlugin, Presets as ConnectionPresets } from "rete-connection-plugin" const connection = new ConnectionPlugin(); connection.addPreset(ConnectionPresets.classic.setup()) area.use(connection); ``` ## Fit viewport Use the `zoomAt` extension to automatically adjust the viewport to fit all nodes. ```ts import { AreaExtensions } from "rete-area-plugin"; AreaExtensions.zoomAt(area, editor.getNodes()); ``` By default, node dimensions are calculated using `clientWidth`/`clientHeight`. If this method is called right after appending nodes, it might not work correctly until the element is visible on the screen. Instead, you can specify `width`/`height` properties in the node class, as [demonstrated in this step](https://retejs.org/docs/guides/arrange#create-node-base). ## Selectable nodes Additionally, extensions offer various capabilities, like enabling the user to select nodes. ```ts AreaExtensions.selectableNodes(area, AreaExtensions.selector(), { accumulating: AreaExtensions.accumulateOnCtrl() }); ``` For further details, check out the [Selectable](https://retejs.org/docs/guides/selectable) guide. ## Nodes order If your application allows node selection, users may want to view selected nodes without any visual obstructions. To facilitate this, an additional extension is provided that automatically brings the selected node to the front. ```ts AreaExtensions.simpleNodesOrder(area); ``` Check out the complete result on the [Basic](https://retejs.org/examples/basic/react) example page. # React.js ::alert This guide is an extension of the [Basic](https://retejs.org/docs/guides/basic) guide and provides in-depth instructions for using the `rete-react-plugin` :: ::references :::ref-example{link="/examples/basic/react" title="Basic"} ::: :::ref-example{link="/examples/controls/react" title="Controls"} ::: :::ref-example{link="/examples/customization/react" title="Customization"} ::: :::ref-github{link="https://github.com/retejs/react-plugin" title="Plugin"} ::: :::ref-external{link="https://react.dev/" title="React.js"} ::: :::ref-guide{link="/docs/guides/context-menu" title="Context menu"} ::: :::ref-guide{link="/docs/guides/minimap" title="Minimap"} ::: :::ref-guide{link="/docs/guides/reroute" title="Reroute"} ::: :: This plugin uses a classic preset that includes visual components for nodes, connections, sockets, and input controls. It leverages `styled-components` to design these components. This plugin can be used in any application, regardless of your stack (**React.js**, **Vue.js**, **Angular**, etc.). ## Install dependencies ::kit :: ```bash npm i rete-react-plugin rete-render-utils styled-components ``` If you're using this plugin in an application that doesn't utilize **React.js**, make sure to install the required **React.js** dependencies as well. ```bash npm i react@18 react-dom@18 ``` ## Plugin connection ```ts import { createRoot } from "react-dom/client"; import { AreaPlugin } from "rete-area-plugin"; import { ReactPlugin, Presets, ReactArea2D } from "rete-react-plugin"; type AreaExtra = ReactArea2D; // .... const render = new ReactPlugin({ createRoot }); render.addPreset(Presets.classic.setup()); area.use(render); ``` Check out the [Basic](https://retejs.org/examples/basic/react) page for an example of how to use this rendering plugin. ## Using React.js 16 In case you're using React.js version 16 (or 17), just remove the `createRoot` method ```ts const render = new ReactPlugin(); ``` ## "useRete" hook When working with React app, `useRete` hook eliminates the need for boilerplate code that binds an editor to HTML element. This becomes particularly crucial for dynamic app updates where the old instance of the editor must be removed and replaced with a new one. ```tsx import { useRete } from 'rete-react-plugin'; function App() { const [ref, editor] = useRete(createEditor) return
} ``` where `createEditor` should return object with `destroy` method (usually it has `area.destroy()` call) ## Controls This plugin provides built-in controls that are displayed based on the following objects: - `ClassicPreset.InputControl` as `` or `` Simply add the control to the node ```ts node.addControl('my-control', new ClassicPreset.InputControl("number", { initial: 0, readonly: false, change(value) { } })) ``` If you want to add different types of controls, you can return the necessary functional component in the `control` handler of `customize` property. ```tsx render.addPreset(Presets.classic.setup({ customize: { control(context) { if (context.payload.isButton) { return (props: { data: { isButton: true, label: string, onClick: () => void }}) => ( ) } if (context.payload instanceof ClassicPreset.InputControl) { // don't forget to explicitly specify the built-in Presets.classic.Control return Presets.classic.Control; } } } })); node.addControl('my-button', { isButton: true, label: 'Click', onClick() {} }) ``` This is a simplified version suitable for introductory purposes. For projects, it is recommended to follow the approach demonstrated in [the example](https://retejs.org/examples/controls/react) Make sure to call `stopPropagation` in `onPointerDown` to prevent the area from intercepting events such as `click`. If you are encountering this issue in React 16 or your interactive elements are added to a custom node instead of a control, try the following solution: ```tsx import { Drag } from "rete-react-plugin"; ``` Or use a hook to avoid extra nesting ```tsx const ref = React.useRef(null) Drag.useNoDrag(ref) ``` ## Customization In a similar manner to the approach outlined above, you can replace node, connection, or socket components. ### Node styles The easiest approach is to extend the current component and use `styled-components` to add extra styles. ```tsx import { Presets } from "rete-react-plugin"; import { css } from "styled-components"; const myStyles = css<{ selected?: boolean }>` background: white; ${(props) => props.selected && css` border-color: red; `} `; function StyledNode(props: { data: Schemes['Node'] }) { return myStyles} {...props} />; } render.addPreset(Presets.classic.setup({ customize: { node() { return StyledNode } } })) ``` Implementing this will result in all your nodes using `myStyles`. ### Specific nodes You can add an extra condition to apply these styles only to specific nodes. ```ts render.addPreset(Presets.classic.setup({ customize: { node(context) { if (context.payload.label === "White") { return StyledNode; } return Presets.classic.Node; } } })) await editor.addNode(new ClassicPreset.Node('White')) ``` ### Full node customization If you want to completely change the node structure, you can implement your own component similar to [Node.tsx](https://github.com/retejs/react-plugin/blob/next/src/presets/classic/components/Node.tsx){rel="nofollow"} from the classic preset. ```ts import { CustomNode } from './CustomNode' render.addPreset(Presets.classic.setup({ customize: { node() { return CustomNode } } })) ``` The implementation of `CustomNode` is available in the **CustomNode.tsx** file of the [Customization for React.js](https://retejs.org/examples/customization/react) example. ### Full customization of connections Use **Connection.tsx** as a starting point from the [presets/classic/components](https://github.com/retejs/react-plugin/blob/next/src/presets/classic/components){rel="nofollow"} directory of the plugin's source code. ```ts import { CustomConnection } from './CustomConnection' render.addPreset(Presets.classic.setup({ customize: { connection() { return CustomConnection } } })) ``` ### Full socket customization Use **Socket.tsx** as a starting point from the [presets/classic/components](https://github.com/retejs/react-plugin/blob/next/src/presets/classic/components){rel="nofollow"} directory of the plugin's source code. ```ts import { CustomSocket } from './CustomSocket' render.addPreset(Presets.classic.setup({ customize: { socket() { return CustomSocket } } })) ``` ## Customize context menu As the context menu components utilize `styled-components`, you can customize their styles by: ```ts import styled from "styled-components"; const { Menu, Common, Search, Item, Subitems } = Presets.contextMenu const CustomMenu = styled(Menu)` width: 320px; ` const CustomItem = styled(Item)` background: grey; ` render.addPreset(Presets.contextMenu.setup({ customize: { main: () => CustomMenu, item: () => CustomItem, common: () => Common, search: () => Search, subitems: () => Subitems } })) ``` ## Other presets - [context menu](https://retejs.org/docs/guides/context-menu) - [minimap](https://retejs.org/docs/guides/minimap) - [reroute](https://retejs.org/docs/guides/reroute) Check out the complete result on the [Customization for React.js](https://retejs.org/examples/customization/react) example page. # Vue.js ::alert This guide is an extension of the [Basic](https://retejs.org/docs/guides/basic) guide and provides instructions for using the `rete-vue-plugin` instead of `rete-react-plugin` :: ::references :::ref-example{link="/examples/basic/vue" title="Basic"} ::: :::ref-example{link="/examples/customization/vue" title="Customization"} ::: :::ref-example{link="/examples/controls/vue" title="Controls"} ::: :::ref-github{link="https://github.com/retejs/vue-plugin" title="Plugin"} ::: :::ref-external{link="https://vuejs.org/" title="Vue.js"} ::: :::ref-guide{link="/docs/guides/context-menu" title="Context menu"} ::: :::ref-guide{link="/docs/guides/minimap" title="Minimap"} ::: :::ref-guide{link="/docs/guides/reroute" title="Reroute"} ::: :: This plugin offers a classic preset that comes with visual components for nodes, connections, sockets, and input controls. Supports both versions of Vue.js: 2 and 3 You can use this plugin in any application, irrespective of the application stack (React.js, Vue.js, Angular). However, using SFC requires a bundler with a corresponding template compiler installed, which is a separate topic for discussion. ## Install dependencies ::kit :: ```bash npm i rete-vue-plugin rete-render-utils ``` ## Plugin connection ```ts import { AreaPlugin } from "rete-area-plugin"; import { VuePlugin, Presets, VueArea2D } from "rete-vue-plugin"; type AreaExtra = VueArea2D; // .... const render = new VuePlugin(); render.addPreset(Presets.classic.setup()); area.use(render); ``` Check out the [Vue] page(/examples/basic/vue) page for an example usage of this render plugin. ## Using Vue.js 2 To use the plugin with Vue 2, add `/vue2` to the import statement. ```ts import { VuePlugin, Presets, VueArea2D } from "rete-vue-plugin/vue2"; ``` ## Controls This plugin provides built-in controls that are displayed based on the following objects: - `ClassicPreset.InputControl` as `` or `` Simply add the control to the node ```ts node.addControl('my-control', new ClassicPreset.InputControl("number", { initial: 0, readonly: false, change(value) { } })) ``` If you want to add different types of controls, you can return the necessary component in the `control` handler of `customize` property. ```tsx import MyButton from './MyButton.vue' render.addPreset(Presets.classic.setup({ customize: { control(context) { if (context.payload.isButton) { return MyButton } if (context.payload instanceof ClassicPreset.InputControl) { // don't forget to explicitly specify the built-in Presets.classic.Control return Presets.classic.Control; } } } })); node.addControl('my-button', { isButton: true, label: 'Click', onClick() {} }) ``` **MyButton.vue** ```vue ``` This is a simplified version suitable for introductory purposes. For projects, it is recommended to follow the approach demonstrated in [the example](https://retejs.org/examples/controls/vue) Make sure to specify `@pointerdown.stop` to prevent the area from intercepting events such as `click`. ## Customization In a similar manner to the approach outlined above, you can replace node, connection, or socket components. ### Customization of all nodes If you want to completely change the node structure, you can implement your own component similar to [Node.vue](https://github.com/retejs/vue-plugin/blob/next/src/presets/classic/components/Node.vue){rel="nofollow"} from the classic preset ```ts import CustomNode from './CustomNode.vue' render.addPreset(Presets.classic.setup({ customize: { node() { return CustomNode } } })) ``` The implementation of `CustomNode` is available in the **CustomNode.vue** file of the [Customization for Vue.js](https://retejs.org/examples/customization/vue) example. ### Specific nodes You can add an extra condition to apply this component only to specific nodes. ```ts render.addPreset(Presets.classic.setup({ customize: { node(context) { if (context.payload.label === "Custom") { return CustomNode; } return Presets.classic.Node; } } })) await editor.addNode(new ClassicPreset.Node('White')) ``` ### Connection customization Use **Connection.vue** as a starting point from the [presets/classic/components](https://github.com/retejs/vue-plugin/blob/next/src/presets/classic/components){rel="nofollow"} directory of the plugin's source code. ```ts import CustomConnection from './CustomConnection.vue' render.addPreset(Presets.classic.setup({ customize: { connection() { return CustomConnection } } })) ``` ### Socket customization Use **Socket.vue** as a starting point from the [presets/classic/components](https://github.com/retejs/vue-plugin/blob/next/src/presets/classic/components){rel="nofollow"} directory of the plugin's source code. ```ts import CustomSocket from './CustomSocket.vue' render.addPreset(Presets.classic.setup({ customize: { socket() { return CustomSocket } } })) ``` ## Customize context menu In order to customize the context menu for this rendering plugin, one can override styles using selectors (and it's important to consider the specificity of selectors in CSS) ```scss [rete-context-menu] { width: 320px !important; .block:first-child input { background: grey; } .block.item { background: grey; } } ``` ## Use Vue.js plugins Since `rete-vue-plugin` creates independent Vue.js instance for nodes, sockets, controls, etc., it doesn't inherit plugins from your project's main Vue instance. To bridge this gap, the plugin offers a solution: injecting a custom Vue application instance. This capability ensures that any Vue plugins or global components you wish to employ within your Rete-specific Vue components are accessible, enabling seamless sharing between your Vue.js application and Rete.js editor. ### Vue.js 3 The following example demonstrates how to configure custom Vue.js 3 instance: ```ts import { Presets, VuePlugin } from "rete-vue-plugin"; import { createApp } from "vue"; const render = new VuePlugin({ setup(context) { const app = createApp(context); app.use(yourPlugin); return app; }, }); ``` where `yourPlugin` is an instance of any plugin (like [Vuetify](https://vuetifyjs.com/en/getting-started){rel="nofollow"} or [Vue I18N](https://vue-i18n.intlify.dev/){rel="nofollow"}) ### Vue.js 2 Since the initialization for Vue.js 2 is slightly different, let's take a look at the following example: ```ts import { Presets, VuePlugin } from "rete-vue-plugin"; import Vue from "vue"; const render = new VuePlugin({ setup(context) { const app = new Vue({ ...context, yourPlugin }); return app; }, }); ``` where `yourPlugin` is an instance of any plugin (like [Vuetify](https://vuetifyjs.com/en/getting-started){rel="nofollow"} or [Vue I18N](https://kazupon.github.io/vue-i18n/){rel="nofollow"}) ## Other presets - [context menu](https://retejs.org/docs/guides/context-menu) - [minimap](https://retejs.org/docs/guides/minimap) - [reroute](https://retejs.org/docs/guides/reroute) Check out the complete result on the [Customization for Vue.js](https://retejs.org/examples/customization/vue) example page. # Angular ::alert This guide is an extension of the [Basic](https://retejs.org/docs/guides/basic) guide and provides instructions for using the `rete-angular-plugin` instead of `rete-react-plugin` :: ::references :::ref-example{link="/examples/basic/angular" title="Basic"} ::: :::ref-example{link="/examples/customization/angular" title="Customization"} ::: :::ref-example{link="/examples/controls/angular" title="Controls"} ::: :::ref-github{link="https://github.com/retejs/angular-plugin" title="Plugin"} ::: :::ref-external{link="https://angular.io/" title="Angular"} ::: :::ref-guide{link="/docs/guides/context-menu" title="Context menu"} ::: :::ref-guide{link="/docs/guides/minimap" title="Minimap"} ::: :::ref-guide{link="/docs/guides/reroute" title="Reroute"} ::: :: This plugin offers a classic preset that comes with visual components for nodes, connections, sockets, and input controls. Compatible with Angular 12, 13, 14, 15, 16, 17, 18 and 19 This plugin is **exclusively** designed for Angular applications as it requires an `Injector` instance, unlike other render plugins. Additionally, the plugin supports Standalone mode in integration starting from Angular 19. ## Install dependencies ::kit :: ```bash npm i rete-angular-plugin rete-render-utils @angular/elements@19 ``` **Please note**: this plugin relies on `@angular/elements`, which is based on Web Components. However, Web Components have a limitation - they [cannot be unregistered](https://github.com/WICG/webcomponents/issues/754){rel="nofollow"}. This limitation may result in the reuse of the initial Angular component instead of creating a new one when a node with the same identifier is added, potentially leading to the use of outdated data within a custom node, such as data from an injected service. ## Plugin connection This is an example of integration in **Angular 19**, but you can specify a different version (12, 13, 14, 15, 16, 17, 18 or 19) in the import that matches the version of your application. These versions have been compiled with Ivy. ```ts import { AreaPlugin } from "rete-area-plugin"; import { AngularPlugin, Presets, AngularArea2D } from "rete-angular-plugin/19"; type AreaExtra = AngularArea2D; // .... const render = new AngularPlugin({ injector }); render.addPreset(Presets.classic.setup()); area.use(render); ``` where `injector` is an instance of `Injector` that can be obtained through dependency injection (DI). ## Use Legacy View Engine Additionally, the plugin provides support for the legacy engine which can be imported in the following way ```ts import { AngularPlugin, Presets, AngularArea2D } from "rete-angular-plugin"; ``` ## Controls This plugin provides built-in controls that are displayed based on the following objects: - `ClassicPreset.InputControl` as `` or `` Simply add the control to the node ```ts node.addControl('my-control', new ClassicPreset.InputControl("number", { initial: 0, readonly: false, change(value) { } })) ``` If you want to add different types of controls, you can return the necessary component in the `control` handler of `customize` property. ```tsx import { ControlComponent } from "rete-angular-plugin/19"; import { MyButtonComponent } from './my-button.component' render.addPreset(Presets.classic.setup({ customize: { control(context) { if (context.payload.isButton) { return MyButtonComponent } if (context.payload instanceof ClassicPreset.InputControl) { // don't forget to explicitly specify the built-in ControlComponent return ControlComponent } } } })); node.addControl('my-button', { isButton: true, label: 'Click', onClick() {} }) ``` ```ts import { Component, Input } from "@angular/core"; @Component({ selector: "app-button", template: `` }) export class ButtonComponent { @Input() data!: { label: string, onClick: () => void }; } ``` This is a simplified version suitable for introductory purposes. For projects, it is recommended to follow the approach demonstrated in [the example](https://retejs.org/examples/controls/angular) Make sure to specify `(pointerdown)="$event.stopPropagation()"` to prevent the area from intercepting events such as `click`. ## Customization In a similar manner to the approach outlined above, you can replace node, connection, or socket components. ### Customization of all nodes If you want to completely change the node structure, you can implement your own component similar to [node](https://github.com/retejs/angular-plugin/blob/next/src/presets/classic/components/node){rel="nofollow"} from the classic preset ```ts import { CustomNodeComponent } from './custom-node.component' render.addPreset(Presets.classic.setup({ customize: { node() { return CustomNodeComponent } } })) ``` The implementation of `CustomNodeComponent` is available in the **custom-node.component.ts** file of the [Customization for Angular](https://retejs.org/examples/customization/angular) example. ### Specific nodes You can add an extra condition to apply this component only to specific nodes. ```ts import { NodeComponent } from "rete-angular-plugin/19"; render.addPreset(Presets.classic.setup({ customize: { node(context) { if (context.payload.label === "Custom") { return CustomNodeComponent; } return NodeComponent; // use built-in component } } })) await editor.addNode(new ClassicPreset.Node('White')) ``` ### Connection customization Use **connection** as a starting point from the [presets/classic/components](https://github.com/retejs/angular-plugin/blob/next/src/presets/classic/components){rel="nofollow"} directory of the plugin's source code. ```ts import { CustomConnectionComponent } from './custom-connection.component' render.addPreset(Presets.classic.setup({ customize: { connection() { return CustomConnectionComponent } } })) ``` ### Socket customization Use **socket** as a starting point from the [presets/classic/components](https://github.com/retejs/angular-plugin/blob/next/src/presets/classic/components){rel="nofollow"} directory of the plugin's source code. ```ts import { CustomSocketComponent } from './custom-socket.component' render.addPreset(Presets.classic.setup({ customize: { socket() { return CustomSocketComponent } } })) ``` ## Customize context menu In order to customize the context menu for this rendering plugin, one can override styles using selectors (and it's important to consider the specificity of selectors in CSS) ```scss [rete-context-menu] { width: 320px; context-menu-search input.search { background: grey; } context-menu-item.block { background: grey; } } ``` ## Other presets - [context menu](https://retejs.org/docs/guides/context-menu) - [minimap](https://retejs.org/docs/guides/minimap) - [reroute](https://retejs.org/docs/guides/reroute) Check out the complete result on the [Customization for Angular](https://retejs.org/examples/customization/angular) example page. # Svelte ::alert This guide is an extension of the [Basic](https://retejs.org/docs/guides/basic) guide and provides instructions for using the `rete-svelte-plugin` instead of `rete-react-plugin` :: ::references :::ref-example{link="/examples/basic/svelte" title="Basic"} ::: :::ref-example{link="/examples/customization/svelte" title="Customization"} ::: :::ref-example{link="/examples/controls/svelte" title="Controls"} ::: :::ref-github{link="https://github.com/retejs/svelte-plugin" title="Plugin"} ::: :::ref-external{link="https://svelte.dev/" title="Svelte"} ::: :::ref-guide{link="/docs/guides/context-menu" title="Context menu"} ::: :::ref-guide{link="/docs/guides/minimap" title="Minimap"} ::: :::ref-guide{link="/docs/guides/reroute" title="Reroute"} ::: :: This plugin offers a classic preset that comes with visual components for nodes, connections, sockets, and input controls. Supports latest versions of Svelte: 5, 4 and 3 ## Install dependencies ::kit :: ```bash npm i rete-svelte-plugin rete-render-utils sass ``` ## Plugin connection ```ts import { AreaPlugin } from "rete-area-plugin"; import { SveltePlugin, Presets, SvelteArea2D } from "rete-svelte-plugin/5"; // or "rete-svelte-plugin" for older versions type AreaExtra = SvelteArea2D; // .... const render = new SveltePlugin(); render.addPreset(Presets.classic.setup()); area.use(render); ``` Please note that this plugin is intended for client-side use only. Therefore, modules that use it within your `*.svelte` modules need to be dynamically imported. ```ts onMount(async () => { const { createEditor } = await import("./editor"); }) ``` Check out the [Svelte](https://retejs.org/examples/basic/svelte) page for an example usage of this render plugin. ## Controls This plugin provides built-in controls that are displayed based on the following objects: - `ClassicPreset.InputControl` as `` or `` Simply add the control to the node ```ts node.addControl('my-control', new ClassicPreset.InputControl("number", { initial: 0, readonly: false, change(value) { } })) ``` If you want to add different types of controls, you can return the necessary component in the `control` handler of `customize` property. ```tsx import MyButton from './MyButton.svelte' render.addPreset(Presets.classic.setup({ customize: { control(context) { if (context.payload.isButton) { return MyButton } if (context.payload instanceof ClassicPreset.InputControl) { // don't forget to explicitly specify the built-in Presets.classic.Control return Presets.classic.Control; } } } })); node.addControl('my-button', { isButton: true, label: 'Click', onClick() {} }) ``` **MyButton.svelte** ```svelte ``` This is a simplified version suitable for introductory purposes. For projects, it is recommended to follow the approach demonstrated in [the example](https://retejs.org/examples/controls/svelte) Make sure to specify `on:pointerdown|stopPropagation` to prevent the area from intercepting events such as `click`. ## Customization In a similar manner to the approach outlined above, you can replace node, connection, or socket components. ### Customization of all nodes If you want to completely change the node structure, you can implement your own component similar to [Node.svelte](https://github.com/retejs/svelte-plugin/blob/main/src/presets/classic/components/Node.svelte){rel="nofollow"} from the classic preset ```ts import CustomNode from './CustomNode.svelte' render.addPreset(Presets.classic.setup({ customize: { node() { return CustomNode } } })) ``` The implementation of `CustomNode` is available in the **CustomNode.svelte** file of the [Customization for Svelte](https://retejs.org/examples/customization/svelte) example. ### Specific nodes You can add an extra condition to apply this component only to specific nodes. ```ts render.addPreset(Presets.classic.setup({ customize: { node(context) { if (context.payload.label === "Custom") { return CustomNode; } return Presets.classic.Node; } } })) await editor.addNode(new ClassicPreset.Node('White')) ``` ### Connection customization Use **Connection.svelte** as a starting point from the [presets/classic/components](https://github.com/retejs/svelte-plugin/blob/main/src/presets/classic/components){rel="nofollow"} directory of the plugin's source code. ```ts import CustomConnection from './CustomConnection.svelte' render.addPreset(Presets.classic.setup({ customize: { connection() { return CustomConnection } } })) ``` ### Socket customization Use **Socket.svelte** as a starting point from the [presets/classic/components](https://github.com/retejs/svelte-plugin/blob/main/src/presets/classic/components){rel="nofollow"} directory of the plugin's source code. ```ts import CustomSocket from './CustomSocket.svelte' render.addPreset(Presets.classic.setup({ customize: { socket() { return CustomSocket } } })) ``` ## Customize context menu In order to customize the context menu for this rendering plugin, one can override styles using selectors (and it's important to consider the specificity of selectors in CSS) ```scss .rete-context-menu { width: 320px !important; .block:first-child input { background: grey; } .block.item { background: grey; } } ``` ## Other presets - [context menu](https://retejs.org/docs/guides/context-menu) - [minimap](https://retejs.org/docs/guides/minimap) - [reroute](https://retejs.org/docs/guides/reroute) Check out the complete result on the [Customization for Svelte](https://retejs.org/examples/customization/svelte) example page. # Lit ::alert This guide is an extension of the [Basic](https://retejs.org/docs/guides/basic) guide and provides instructions for using the `@retejs/lit-plugin` instead of `rete-react-plugin` :: ::references :::ref-example{link="/examples/basic/lit" title="Basic"} ::: :::ref-example{link="/examples/customization/lit" title="Customization"} ::: :::ref-example{link="/examples/controls/lit" title="Controls"} ::: :::ref-github{link="https://github.com/retejs/lit-plugin" title="Plugin"} ::: :::ref-external{link="https://lit.dev/" title="Lit"} ::: :::ref-guide{link="/docs/guides/context-menu" title="Context menu"} ::: :::ref-guide{link="/docs/guides/minimap" title="Minimap"} ::: :::ref-guide{link="/docs/guides/reroute" title="Reroute"} ::: :: This plugin offers a classic preset that comes with visual components for nodes, connections, sockets, and input controls. Supports Lit version 3 ## Install dependencies ::kit :: ```bash npm i @retejs/lit-plugin rete-render-utils lit ``` ## Plugin connection ```ts import { AreaPlugin } from "rete-area-plugin"; import { LitPlugin, Presets, LitArea2D } from "@retejs/lit-plugin"; type AreaExtra = LitArea2D; // .... const render = new LitPlugin(); render.addPreset(Presets.classic.setup()); area.use(render); ``` Check out the [Lit](https://retejs.org/examples/basic/lit) page for an example usage of this render plugin. ## Controls This plugin provides built-in controls that are displayed based on the following objects: - `ClassicPreset.InputControl` as `` or `` Simply add the control to the node ```ts node.addControl('my-control', new ClassicPreset.InputControl("number", { initial: 0, readonly: false, change(value) { } })) ``` If you want to add different types of controls, you can return the necessary component in the `control` handler of `customize` property. ```ts import { MyButtonElement } from './MyButton' customElements.define("my-button", MyButtonElement); render.addPreset(Presets.classic.setup({ customize: { control(context) { if (context.payload.isButton) { const { payload } = context; return () => html``; } if (context.payload instanceof ClassicPreset.InputControl) { // don't forget to explicitly specify the built-in return () => html``; } } } })); node.addControl('my-button', { isButton: true, label: 'Click', onClick() {} }) ``` **MyButton.ts** ```ts export class CustomButton extends LitElement { static get properties() { return { data: { type: Object }, }; } declare data: object; render() { return html` `; } } ``` This is a simplified version suitable for introductory purposes. For projects, it is recommended to follow the approach demonstrated in [the example](https://retejs.org/examples/controls/lit) Make sure to specify `@pointerdown` and `@doubleclick` to prevent the area from intercepting events such as `@click`. ## Customization In a similar manner to the approach outlined above, you can replace node, connection, or socket components. ### Customization of all nodes If you want to completely change the node structure, you can implement your own component similar to [node.ts](https://github.com/retejs/lit-plugin/blob/main/src/presets/classic/components/node.ts){rel="nofollow"} from the classic preset ```ts import { CustomNodeElement } from './CustomNode' customElements.define("custom-node", CustomNodeElement) render.addPreset(Presets.classic.setup({ customize: { node(context) { return ({ emit }) => html``; } } })) ``` The implementation of `CustomNodeElement` is available in the **CustomNode.ts** file of the [Customization for Lit](https://retejs.org/examples/customization/lit) example. ### Specific nodes You can add an extra condition to apply this component only to specific nodes. ```ts render.addPreset(Presets.classic.setup({ customize: { node(context) { if (context.payload.label === "Custom") { return ({ emit }) => html``; } return ({ emit }) => html``; } } })) await editor.addNode(new ClassicPreset.Node('White')) ``` ### Connection customization Use **connection.ts** as a starting point from the [presets/classic/components](https://github.com/retejs/lit-plugin/blob/main/src/presets/classic/components/connection.ts){rel="nofollow"} directory of the plugin's source code. ```ts import { CustomConnectionElement } from './CustomConnection' customElements.define("custom-connection", CustomConnectionElement); render.addPreset(Presets.classic.setup({ customize: { connection() { return ({ path }) => html``; } } })) ``` ### Socket customization Use **socket.ts** as a starting point from the [presets/classic/components](https://github.com/retejs/lit-plugin/blob/main/src/presets/classic/components/socket.ts){rel="nofollow"} directory of the plugin's source code. ```ts import { CustomSocketElement } from './CustomSocket' customElements.define("custom-socket", CustomSocketElement); render.addPreset(Presets.classic.setup({ customize: { socket(context) { return () => html``; } } })) ``` ## Other presets - [context menu](https://retejs.org/docs/guides/context-menu) - [minimap](https://retejs.org/docs/guides/minimap) - [reroute](https://retejs.org/docs/guides/reroute) Check out the complete result on the [Customization for Lit](https://retejs.org/examples/customization/lit) example page. # Dataflow ::alert Not familiar with Dataflow concept? Check out the [Dataflow](https://retejs.org/docs/concepts/engine#dataflow) article to get up to speed :: ::references :::ref-example{link="/examples/processing/dataflow" title="Dataflow"} ::: :::ref-example{link="/examples/3d-configurator" title="3D Configurator"} ::: :::ref-example{link="/examples/allmatter" title="Allmatter"} ::: :::ref-github{link="https://github.com/retejs/engine" title="Plugin"} ::: :: ## Install dependencies ::kit :: ```bash npm i rete rete-engine ``` ## Prepare nodes Let's take a look at a simplified example of a graph with two node types: `NumberNode` and `AddNode`. These nodes are built exclusively for processing (on the server-side, e.g.) and don't have any integrations with the user interface. You can find a link to the complete example with the UI at the end of the article. ```ts const socket = new ClassicPreset.Socket("socket"); class NumberNode extends ClassicPreset.Node { constructor(public value: number) { super("Number"); this.addOutput("value", new ClassicPreset.Output(socket, "Number")); } data(): { value: number } { return { value: this.value }; } } class AddNode extends ClassicPreset.Node { constructor() { super("Add"); this.addInput("left", new ClassicPreset.Input(socket, "Left")); this.addInput("right", new ClassicPreset.Input(socket, "Right")); this.addOutput("value", new ClassicPreset.Output(socket, "Number")); } data(inputs: { left?: number[]; right?: number[] }): { value: number } { const { left, right } = inputs; const value = (left && left[0] || 0) + (right && right[0] || 0) return { value }; } } class Connection< A extends Node, B extends Node > extends ClassicPreset.Connection {} type Node = NumberNode | AddNode; type ConnProps = Connection | Connection; type Schemes = GetSchemes; ``` ## Connect ```ts import { DataflowEngine } from "rete-engine"; import { NodeEditor } from "rete"; const editor = new NodeEditor(); const engine = new DataflowEngine(); editor.use(engine); ``` ## Add nodes and connections ```ts const a = new NumberNode(1); const b = new NumberNode(1); const sum = new AddNode(); const con1 = new Connection(a, "value", c, "left"); const con2 = new Connection(b, "value", c, "right"); await editor.addNode(a); await editor.addNode(b); await editor.addNode(sum); await editor.addConnection(con1); await editor.addConnection(con2); ``` ## Start the processing Retrieve output data from the `sum` node. ```ts const result = await engine.fetch(sum.id) ``` The value of `result` will be `{ value: 2 }`, which is the sum of the initial input values of the `sum` node. If you want to modify `a.value` or `b.value`, you need to clear the cache before processing the graph again. The output values of nodes are cached to avoid repetitive node execution. ```ts engine.reset() // reset all nodes // or specific nodes engine.reset(a.id) engine.reset(b.id) ``` Additionally, the `data` methods can be async. In such cases, the `sum` node will wait for the `data` methods of its input nodes to complete execution. After all of them have returned a value, the engine will execute the `data` method of the `sum` node Check out the complete result on the [Dataflow](https://retejs.org/examples/processing/dataflow) example page. # Control flow ::alert Not familiar with Control flow concept? Check out the [Control flow](https://retejs.org/docs/concepts/engine#control-flow) article to get up to speed :: ::references :::ref-example{link="/examples/processing/control-flow" title="Control flow"} ::: :::ref-github{link="https://github.com/retejs/engine" title="Plugin"} ::: :: ## Install dependencies ::kit :: ```bash npm i rete rete-engine ``` ## Prepare nodes Let's take a simple example of a graph with two types of nodes: `Log` and `Delay`. These nodes can perform specific operations and pass control to outgoing nodes in a certain way At the end of the article, you can find a link to the full example that includes visual components. Defining a node class that logs a message and passes control to outgoing nodes via the `exec` port: ```ts const socket = new ClassicPreset.Socket("socket"); class Log extends ClassicPreset.Node { constructor(public message: string) { super("Log"); this.addInput("exec", new ClassicPreset.Input(socket, "Exec", true)); this.addOutput("exec", new ClassicPreset.Output(socket, "Exec")); } execute(input: "exec", forward: (output: "exec") => void) { console.log(this.message); forward("exec"); } } ``` Defining a class that handles delays, where the only purpose is to pass control to outgoing nodes through the `exec` port after a specified timeout: ```ts class Delay extends ClassicPreset.Node { constructor(private seconds: number) { super("Delay"); this.addInput("exec", new ClassicPreset.Input(socket, "Exec", true)); this.addOutput("exec", new ClassicPreset.Output(socket, "Exec")); } execute(input: "exec" | undefined, forward: (output: "exec") => void) { setTimeout(() => forward("exec"), seconds * 1000) } } class Connection extends ClassicPreset.Connection {} type NodeProps = Start | Log | Delay; type ConnProps = | Connection | Connection | Connection | Connection | Connection; type Schemes = GetSchemes; ``` ## Connect ```ts import { ControlFlowEngine } from "rete-engine"; import { NodeEditor } from "rete"; const editor = new NodeEditor(); const engine = new ControlFlowEngine(); editor.use(engine); ``` ## Add nodes and connections Let's add a sequence of nodes in the form of Log -> Delay -> Log ```ts const log1 = new Log("log before delay"); const delay = new Delay(2); const log2 = new Log("log after delay"); const con2 = new Connection(log1, "exec", delay, "exec"); const con3 = new Connection(delay, "exec", log2, "exec"); await editor.addNode(log1); await editor.addNode(delay); await editor.addNode(log2); await editor.addConnection(con2); await editor.addConnection(con3); ``` ## Execution The node `log1` serves as the starting point for the graph execution. ```ts engine.execute(log1.id); ``` This operation triggers the `execute` method of the `Log` class, with `undefined` as the `input` parameter because the node was directly called, without being passed from an incoming node. Then, calling `forward("exec")` passes control to all the outgoing nodes. In our case, the `Delay` node does the same thing but after a delay using `setTimeout`. Logs: ```log "log before delay" // delay for 2 seconds "log after delay" ``` Check out the complete result on the [Control flow](https://retejs.org/examples/processing/control-flow) example page. # Hybrid Engine ::alert For a brief overview of the concept of combining Dataflow and Control flow, we recommend reading the [Hybrid](https://retejs.org/docs/concepts/engine#hybrid) article :: ::references :::ref-example{link="/examples/processing/hybrid-engine" title="Hybrid engine"} ::: :::ref-example{link="/examples/chatbot" title="Chatbot"} ::: :::ref-github{link="https://github.com/retejs/engine" title="Plugin"} ::: :::ref-guide{link="/docs/guides/processing/dataflow" title="Dataflow"} ::: :::ref-guide{link="/docs/guides/processing/control-flow" title="Control flow"} ::: :: ## Install dependencies ::kit :: ```bash npm i rete rete-engine ``` ## Prepare nodes In order to work properly, all node classes must implement `execute` method for Control flow and `data` method for Dataflow. The `Start` class is designed to pass control and has a default `data` method that returns an empty object. ```ts const socket = new ClassicPreset.Socket("socket"); class Start extends ClassicPreset.Node { constructor() { super("Start"); this.addOutput("exec", new ClassicPreset.Output(socket, "Exec")); } execute(_: never, forward: (output: "exec") => void) { forward("exec"); } data() { return {}; } } ``` Along with receiving control, the `Log` class can also request data from the incoming nodes through the `message` port using the `fetchInputs` method of the `DataflowEngine` instance. ```ts class Log extends ClassicPreset.Node { constructor() { super("Log"); this.addInput("exec", new ClassicPreset.Input(socket, "Exec", true)); this.addInput("message", new ClassicPreset.Input(socket, "Text")); this.addOutput("exec", new ClassicPreset.Output(socket, "Exec")); } async execute(input: "exec", forward: (output: "exec") => void) { const inputs = (await dataflow.fetchInputs(this.id)) as { message: string[]; }; console.log((inputs.message && inputs.message[0]) || ""); forward("exec"); } data() { return {}; } } ``` The `TextNode` class is responsible only for providing data and cannot receive or pass control. ```ts class TextNode extends ClassicPreset.Node { constructor(private text: string) { super("Text"); this.addOutput("value", new ClassicPreset.Output(socket, "Number")); } execute() {} data(): { value: string } { return { value: this.text }; } } class Connection extends ClassicPreset.Connection {} type NodeProps = Start | Log | TextNode; type ConnProps = Connection | Connection; type Schemes = GetSchemes; ``` ## Connect ```ts import { NodeEditor } from "rete"; import { DataflowEngine, ControlFlowEngine } from "rete-engine"; const editor = new NodeEditor(); const dataflow = new DataflowEngine(({ inputs, outputs }) => { return { inputs: () => Object.keys(inputs).filter((name) => name !== "exec"), outputs: () => Object.keys(outputs).filter((name) => name !== "exec") }; }); const controlflow = new ControlFlowEngine(() => { return { inputs: () => ["exec"], outputs: () => ["exec"] }; }); editor.use(dataflow); editor.use(controlflow); ``` ## Add nodes and connections ```ts const start = new Start(); const text1 = new TextNode("log"); const log1 = new Log(); const con1 = new Connection(start, "exec", log1, "exec"); const con2 = new Connection(text1, "value", log1, "message"); await editor.addNode(start); await editor.addNode(text1); await editor.addNode(log1); await editor.addConnection(con1); await editor.addConnection(con2); ``` ## Execution The node `start` serves as the starting point for the graph execution. ```ts engine.execute(start.id); ``` As a result, the `start` node passes control to the `log1` node, which fetches data from the `text1` node using the `fetchInputs` method. Check out the complete result on the [Hybrid engine](https://retejs.org/examples/processing/hybrid-engine) example page. Additionally, you can explore another more sophisticated [Chatbot](https://retejs.org/examples/chatbot) example employing this approach. # Codegen ::safety-tape{text="Under development"} :: # 3D ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/3d" title="3D"} ::: :::ref-example{link="/examples/3d/multiple-3d" title="Multiple 3D editors"} ::: :::ref-external{link="https://threejs.org/" title="Three.js"} ::: :::ref-github{link="https://github.com/retejs/area-3d-plugin" title="Plugin"} ::: :: This guide outlines how to incorporate the node editor into a 3D scene. It can be achieved by `rete-area-3d-plugin`, which substitutes the standard `rete-area-plugin`. ::diagram{caption="Architecture" name="guides/3d/index"} :: Powered by Three.js, this plugin creates a scene that contains a camera and two renderers - `CSS3DRenderer` and `WebGLRenderer`. Consequently, 3D objects and native HTML elements are rendered together in a single viewport, enabling overlapping. The primary advantage of utilizing native elements (as opposed to rendering to texture) is the preservation of full interactivity. ## Install dependencies ::kit :: Prior to using this plugin, `three` peer dependency must be installed separately. ```bash npm i rete-area-3d-plugin three @types/three ``` ## Plugin connection If you have followed the basic guide, you will need to replace the initialization of Area2DPlugin with Area3DPlugin and change some type definitions. ```ts import { Area3D, Area3DPlugin } from 'rete-area-3d-plugin'; type AreaExtra = | Area3D const area = new Area3DPlugin(container); editor.use(area); ``` Consequently, the container will incorporate both the `` element and a container for HTML elements that undergo 3D transformation. In case your editor has nodes, you can see them in the viewport, but they will not be interactive. To provide visual feedback for node interaction, it should be part of an animation loop and be linked to `Object3D`, which is directly responsible for dragging. ```ts Area3DExtensions.animate(area) ``` This extension adopts the typical **three.js** approach to animation loops, utilizing the `render()` function along with the `requestAnimationFrame()`. Additionally, we need to link our nodes and connections with the geometry in the 3D scene to allow for interactive functionality and prevent unwanted overlapping with other objects. ```ts Area3DExtensions.forms.connection(render); Area3DExtensions.forms.node(area); ``` During rendering, these extensions produce geometry that accurately replicates the form of nodes and connections. Now you can interact with the nodes and rotate or move the camera around them. Check out the complete result on the [3D](https://retejs.org/examples/3d) example page. ## Additional extensions Some extensions available in the `rete-area-plugin` can be utilized solely within the editor, disregarding the viewport and similar factors. ```ts import { AreaExtensions } from 'rete-area-plugin' AreaExtensions.showInputControl(area) AreaExtensions.simpleNodesOrder(area) AreaExtensions.snapGrid(area, { size: 30 }) AreaExtensions.selectableNodes(area, AreaExtensions.selector(), { accumulating: AreaExtensions.accumulateOnCtrl() }); ``` In this plugin, the following extensions are available: ```ts import { Area3DExtensions } from 'rete-area-3d-plugin' Area3DExtensions.forms.comment(area) // creates geometry for comments plugin Area3DExtensions.forms.reroute(area) // creates geometry for reroute plugin Area3DExtensions.lookAt(area, editor.getNodes()) // alternative to zoomAt from rete-area-plugin ``` ## Scene management ::diagram{caption="Scene" name="guides/3d/scene"} :: As previously stated, this plugin creates a scene complete with camera and renderers. To access them, you can use the following code: ```ts const { scene } = area.area scene.root // three.js' Scene scene.camera // PerspectiveCamera scene.renderer.css3d // CSS3DRenderer scene.renderer.webgl // WebGLRenderer scene.orbit // OrbitControls ``` For instance, you have the option to deactivate orbital control (`scene.orbit.enabled = false`) and add an alternative one, or adjust the settings of renderers or camera. You can add 3D objects by accessing the `scene.root`, which is a `Scene` instance in Three.js. ## Custom HTML elements If you want to include HTML elements within the scene, specifically within the editor's canvas, use the following code: ```ts area.area.content.add(element) ``` An HTML element will be added to the editor canvas at position zero. To ensure it doesn't overlap with other scene elements, it should have geometry that mirrors its form. ```ts area.area.content.updateGeometry(element, bufferGeometry) ``` Check out more examples in [the source code](https://github.com/retejs/area-3d-plugin/tree/main/src/extensions/forms){rel="nofollow"} With regard to other HTML elements, it will maintain its own order, and you can modify it as follows: ```ts area.area.content.reorder(element, nextElement) ``` Removing element: ```ts area.area.content.remove(element) ``` ## Customization In case you have customized your nodes or connections, you might discover that they don't match the placeholder in the 3D scene. To adapt them to new forms, you can either implement your own geometry or reuse existing with various arguments. Example of modifying the border radius of a node: ```ts Area3DExtensions.forms.node(area, { customize(node) { return Area3DExtensions.forms.createClassicNodeGeometry(node, { borderRadius: 0 }) } }) ``` Example of a connection geometry featuring increased width: ```ts Area3DExtensions.forms.connection(reactRender, { customize(path) { return Area3DExtensions.forms.createClassicConnectionGeometry(path, 10) } }) ``` ## Multiple editors Additionally, the plugin offers the capability to include several editors in a single scene. You can achieve this by creating a main `Area3DPlugin` instance and sharing it with other editors. ```ts const mainEditor = new NodeEditor(); const mainArea = new Area3DPlugin(container) mainEditor.use(mainArea) const secondaryEditor = new NodeEditor(); const secondaryArea = mainArea.share() secondaryEditor.use(secondaryArea) ``` Within a certain scene, you'll encounter two editors, placed in the same plane and overlapping each other. To separate them, you can change the position and orientation of the second editor. ```ts const canvas = secondaryArea.area.getCanvas() canvas.rotateY(Math.PI / 2) canvas.get(sharedArea)?.translateX(500) ``` Check out the complete result on the [Multiple 3D editors](https://retejs.org/examples/3d/multiple-3d) example page. # Data structures Similar to a graph, this framework contains data as nodes and edges. One correction: in the editor's terminology, the graph edges are known as connections. The `NodeEditor` instance stores this data in a normalized format, specifically as two distinct lists containing objects of the following type: - `{ id: }` for a nodes - `{ id: , source: , target: }` for a connections ::diagram{caption="NodeEditor" name="editor/node-editor"} :: Initializing the editor involves using a basic scheme without any supplementary fields to start with: ```ts import { NodeEditor, BaseSchemes, getUID } from 'rete' const editor = new NodeEditor() ``` ## Add nodes and connections When dealing with graph data, you have the option to create arbitrary identifiers for nodes and connections, or utilize the existing ones. ```ts const a = { id: getUID() } const b = { id: getUID() } const connection = { id: getUID(), source: a.id, target: b.id } await editor.addNode(a) await editor.addNode(b) await editor.addConnection(connection) ``` ## Retrieve nodes and connections We can now retrieve a list of newly added nodes and connections ```ts editor.getNodes() // returns [a, b] editor.getConnections() // returns [connection] ``` You have all the necessary methods to process the graph, such as retrieving a list of input connections or all incoming nodes, as we will discuss in the following sections. ## Incoming/outgoing connections ::diagram --- caption: Incoming/outgoing connections name: guides/data-structures/connections --- :: The following code shows how to retrieve a list of incoming and outgoing connections for `node`: ```ts const connections = editor.getConnections() const incomingConnections = connections.filter(connection => connection.target === node.id) const outgoingConnections = connections.filter(connection => connection.source === node.id) ``` ## Incoming/outgoing nodes ::diagram{caption="Incoming/outgoing nodes" name="guides/data-structures/nodes"} :: We can use variables from the previous section to retrieve incoming or outgoing nodes: ```ts const incomers = incomingConnections.map(connection => editor.getNode(connection.source)) const outgoers = outgoingConnections.map(connection => editor.getNode(connection.target)) ``` In general, this is sufficient for simple cases, but if there are more than one connections between nodes, we will have to remove duplicates: ```ts Array.from(new Set(incomers)) Array.from(new Set(outgoers)) ``` ## Advanced methods The previously mentioned approaches are fairly flexible, but they require the implementation of more advanced methods on your own. Fortunately, the [`rete-structures` package](https://github.com/retejs/structures){rel="nofollow"} offers such methods divided into 4 categories: - **Mapping**: iterating through a list of nodes while transforming or filtering it - **Sets**: techniques for working with graphs as sets, including union, difference and intersection. - **Subgraph**: graphs that have parent-child relationships between nodes. - **Traverse**: traversing nodes by their connections, such as retrieving only incoming nodes or all predecessors. ::diagram{caption="rete-structures" name="guides/data-structures/structures"} :: ### Usage Install the dependency ```bash npm i rete-structures ``` Use the following import statement and declaration ```ts import { structures } from 'rete-structures' const graph = structures(editor) graph.nodes() graph.connections() ``` There are other `graph` methods that serve distinct purposes, demonstrated below with code and an interactive preview. The resulting nodes are prominent and the preview allows users to select nodes and observe changes. The execution of any of the methods usually yields nodes that were not discarded, as well as their connections if both of their nodes are present. ### Traverse #### Roots Nodes without incoming connections are known as root nodes ```ts structures(graph).roots() ``` ::structures{#roots} :: #### Leaves Leaf nodes are those that have no outgoing connections ```ts structures(graph).leaves() ``` ::structures{#leaves} :: #### Incomers Incoming nodes directly connected to the selected node ```ts structures(graph).incomers(selectedNodeId) ``` ::structures{#incomers pick="e"} :: #### Outgoers Outgoing nodes directly connected to the selected node ```ts structures(graph).outgoers(selectedNodeId) ``` ::structures{#outgoers pick="a"} :: #### Predecessors Every incoming node, as well as its own incoming nodes and so on ```ts structures(graph).predecessors(selectedNodeId) ``` ::structures{#predecessors pick="f"} :: #### Successors Every outgoing node, as well as its own outgoing nodes and so on ```ts structures(graph).successors(selectedNodeId) ``` ::structures{#successors pick="c"} :: ### Mapping #### Filter Filtering can be applied to both nodes and connections ```ts structures(graph).filter(Boolean, ({ source, target }) => source === selectedNodeId || target === selectedNodeId) ``` ::structures{#filter pick="d"} :: ### Sets The following examples shows the cases where selected node serves as the second graph. #### Union The union of a graph and a node from this graph gives the same graph ```ts structures(editor).union({ nodes: [selectedNode], connections: [] }) ``` ::structures{#union pick="d"} :: #### Difference By subtracting the node from the graph, you can obtain a new graph without that node ```ts structures(editor).difference({ nodes: [selectedNode], connections: [] }) ``` ::structures{#union pick="d"} :: #### Intersection By intersecting the graph with the node from this graph, you'll get a new graph that includes only the selected node ```ts structures(editor).intersection({ nodes: [selectedNode], connections: [] }) ``` ::structures{#intersection pick="d"} :: ### Subgraph This category pertains to graphs that contain nodes with a parent-child relationship, specifically, nodes that have a `parent` field defined and are nested within other nodes. #### Children The list of direct descendant, namely children ```ts structures(editor).children((n) => n.id === selectedNodeId); ``` ::structures{#children pick="nestedParent"} :: #### Parent The list of parents ```ts structures(editor).parents((n) => n.id === selectedNodeId); ``` ::structures{#parent pick="a"} :: #### Descendants The list of all descendants, including children, grandchildren and successive generations ```ts structures(editor).descendants((n) => n.id === selectedNodeId); ``` ::structures{#descendants pick="rootParent"} :: #### Ancestors The list of all ancestors, from parents to great-grandparents and beyond ```ts structures(editor).ancestors((n) => n.id === selectedNodeId); ``` ::structures{#ancestors pick="a"} :: #### Orphans Nodes without a specified `parent` property ```ts structures(editor).orphans(); ``` ::structures{#orphans} :: #### Siblings Nodes that have a common parent with the selected node, even if it has no parent ```ts structures(editor).siblings((n) => n.id === selectedNodeId) ``` ::structures{#siblings pick="b"} :: # Arrange nodes ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/arrange" title="Arrange"} ::: :::ref-guide{link="/docs/guides/scopes" title="Scopes"} ::: :::ref-github --- link: https://github.com/retejs/auto-arrange-plugin title: Arrange plugin --- ::: :::ref-external{link="https://github.com/kieler/elkjs" title="Elk.js"} ::: :: ::diagram{caption="Architecture" name="guides/arrange/index"} :: ## Install dependencies ::kit :: Prior to using this plugin, `elkjs` peer dependency must be installed separately. ```bash npm i rete-auto-arrange-plugin elkjs ``` Additionally, it may be necessary to install `web-worker` if your bundler doesn't recognize this dependency by default ```bash npm i web-worker ``` ## Create the node base The node's width and height need to be specified as `elkjs` requires these values, especially if the node sizes vary. ```ts class Node extends ClassicPreset.Node { width = 180; height = 120; } class Connection extends ClassicPreset.Connection {} type Schemes = GetSchemes>; ``` ## Plugin connection ```ts import { AutoArrangePlugin, Presets as ArrangePresets } from "rete-auto-arrange-plugin"; const arrange = new AutoArrangePlugin(); arrange.addPreset(ArrangePresets.classic.setup()); area.use(arrange); ``` ## Arrange added nodes ```ts await arrange.layout(); ``` ## Arrange nested nodes Any node that has the `parent` property will be considered as a nested node within its corresponding parent node, as indicated by the id specified in this field. There are no additional settings required. The [Scopes](https://retejs.org/docs/guides/scopes) guide includes an example of how to use it in practice. ## Animated arrange If you prefer a seamless transition of nodes position, you can apply an animated `TransitionApplier` with configurable animation duration and timing function ```ts import { ArrangeAppliers } from "rete-auto-arrange-plugin"; const applier = new ArrangeAppliers.TransitionApplier({ duration: 500, timingFunction: (t) => t, async onTick() { // called on every frame update } }); await arrange.layout({ applier }) ``` ## Arrange options Additionally, you can make use of [global options for elk.js](https://eclipse.dev/elk/reference/options.html){rel="nofollow"}. For instance, this empowers you to alter the spacing between nodes ```ts await arrange.layout({ options: { 'elk.spacing.nodeNode': 100, 'elk.layered.spacing.nodeNodeBetweenLayers': 100 } }); ``` Check out the complete result on the [Arrange](https://retejs.org/examples/arrange) example page. # Selectable ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/basic" title="Basic"} ::: :::ref-example --- link: /examples/lasso-marquee-selection title: Lasso/marquee selection --- ::: :::ref-guide{link="/docs/guides/selectable/connections" title="Connections"} ::: :::ref-guide{link="/docs/guides/comments#selectable" title="Comments"} ::: :::ref-guide{link="/docs/guides/reroute#selectable" title="Reroute"} ::: :::ref-github{link="https://github.com/retejs/area-plugin" title="Area Plugin"} ::: :: ## Selectable nodes As explained in the [Basic](https://retejs.org/docs/guides/basic#selectable-nodes) guide, you can enable node selection by using the `selectableNodes` extension ```ts const selector = AreaExtensions.selector() const accumulating = AreaExtensions.accumulateOnCtrl() AreaExtensions.selectableNodes(area, selector, { accumulating }); ``` The code indicates that users can select multiple nodes by holding down the Ctrl key, then these nodes can be moved together ## Selection or deselecting In addition to user actions, a node can be selected or deselected through the built-in methods of `selectableNodes` ```ts const selectableNodes = AreaExtensions.selectableNodes(area, selector, { accumulating }); selectableNodes.select(nodeId) // select a single node, the previous selection will be cleared selectableNodes.select(nodeId, true) // select a node without clearing previous selections selectableNodes.unselect(nodeId) // remove the node from the selected li ``` ## Selectable custom elements All elements added to the area can be added to the selector. They can act like nodes: can be selected and moved alongside other elements that are currently selected Let's take a look at an example of adding an element to the selector ```ts const id = 'element-id' const label = 'element-type' selector.add({ id, label, translate(dx, dy) { // change position of current element by dx,dy }, unselect() { // triggered when removed from selector // here you can trigger styles updating }, }, accumulating.active()) ``` Once this step is completed, the `translate` function will be called every time a selected node or other element is moved. Before making other selected elements move with the element being dragged, you need to mark the element that the user is directly interacting with. ```ts selector.pick({ id, label }) ``` When an event such as `pointermove` is triggered on your element, it's important to verify that it is a grabbed element, and then apply the offset to all the others. ```ts if (selector.isPicked({ id, label })) selector.translate(dx, dy) ``` where `dx`, `dy` is an offset of your element within the area's coordinates. Keep in mind that if `transform.k` isn't equal to 1, the values will deviate from the screen coordinates. This `pick` + `isPicked` approach prevents looping when calling `selector.translate` not only on the `pointermove` event, but also on any position changes of the element through other means. Removing an element from the selector can be easily achieved by: ```ts selector.remove({ id, label }) ``` ## Extend selector Apart from the `AreaExtensions.selector()` function, you have the option to directly use the `AreaExtensions.Selector` class. ```ts const selector = new AreaExtensions.Selector() ``` The benefit of using a class-based selector is that it can be expanded and customized to include the functionalities you need, like tracking the selected or unselected elements ```ts import type { SelectorEntity } from 'rete-area-plugin/_types/extensions/selectable' class MySelector extends AreaExtensions.Selector { add(entity: E, accumulate: boolean): void { super.add(entity, accumulate) console.log('added', entity) } remove(entity: E): void { super.remove(entity) console.log('removed', entity) } } ``` ## Other use cases - [Connections](https://retejs.org/docs/guides/selectable/connections) - [Comments](https://retejs.org/docs/guides/comments#selectable) - [Reroute](https://retejs.org/docs/guides/reroute#selectable) # Selectable connections ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example --- link: /examples/selectable-connections title: Selectable connections --- ::: :::ref-guide{link="/docs/guides/selectable" title="Selectable"} ::: :: ## Getting types ready Introducing an optional `selected` field to the connection type will allow us to specify programmatically added connections as selected ```ts class Connection extends ClassicPreset.Connection< ClassicPreset.Node, ClassicPreset.Node > { selected?: boolean; } type Schemes = GetSchemes; ``` ## Custom connection Our first step is to implement a custom connection that is clickable and capable of changing color when selected. We will start with the [Connection.tsx](https://github.com/retejs/react-plugin/blob/next/src/presets/classic/components/Connection.tsx){rel="nofollow"} component from the classic preset and enhance its functionality by implementing new features. We can make the connection more user-friendly by defining a duplicating transparent path. ```ts import styled from "styled-components"; const HoverPath = styled.path` fill: none; stroke-width: 15px; pointer-events: auto; stroke: transparent; `; ``` We will enhance the existing `Path` component by adding a `selected` prop and modifying the stroke color. ```ts const Path = styled.path<{ selected?: boolean >` stroke: ${(props) => (props.selected ? "rgb(255, 217, 44)" : "steelblue")}; `; ``` The component should return the following template: ```tsx e.stopPropagation()} onClick={props.click} > ``` The existing connection can be marked as follows: ```ts connection.selected = true; area.update("connection", connection.id); ``` ## Synchronization of selected connections Let's get the selector ready ```ts const selector = AreaExtensions.selector(); const accumulating = AreaExtensions.accumulateOnCtrl(); AreaExtensions.selectableNodes(area, selector, { accumulating }); ``` Adding a connection to the selector can be done in the following way (for instance, by clicking on the connection): ```ts selector.add({ id: connection.id, label: 'connection', translate() {}, unselect() { connection.selected = false; area.update("connection", connection.id); } }, accumulating.active()) connection.selected = true; area.update("connection", connection.id); ``` Now we'll consolidate all of this into a single component using a straightforward closure-based approach (although other methods are preferable in production code), and pass it as a custom connection component ```tsx function SelectableConnectionBind(props: { data: Schemes["Connection"] }) { return ( { selector.add({ /// ... }) // here should be placed here the code for adding to the selector, as shown above }} /> ); } render.addPreset(Presets.classic.setup({ customize: { connection() { return SelectableConnectionBind; } } })); ``` Check out the complete result on the [Selectable connections](https://retejs.org/examples/selectable-connections) example page. # Connections ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-guide --- link: /docs/guides/validation#connections-validation title: Validation --- ::: :::ref-example{link="/examples/chatbot" title="Chatbot"} ::: :: This guide provides a more detailed exploration of the functionalities offered by the rete-connection-plugin plugin, enabling user interaction with connections. When a user clicks on a socket, the connection that trails behind the cursor is referred to as a pseudo-connection, which is is an object with an additional property `isPseudo: true`. ## Presets You may have already seen the following preset usage, which allows users to add connections by clicking/pressing on an input/output socket and clicking/releasing on an output/input socket: ```ts connection.addPreset(ConnectionPresets.classic.setup()) ``` which is equivalent to the code below: ```ts import { ClassicFlow } from 'rete-connection-plugin' connection.addPreset(() => new ClassicFlow()) ``` If the input socket is already connected, clicking or pressing on it will remove the connection and substitute it with a pseudo-connection. ### Custom preset If you prefer an alternative method for adding connections, you can make use of `BidirectFlow`. In this mode, adding a node is done by clicking on the input/output socket and dragging the pseudo-connection onto a socket of the opposite type. ```ts import { ClassicFlow } from 'rete-connection-plugin' connection.addPreset(() => new BidirectFlow()) ``` Additionally, utilizing the initial socket data, you have the option to select a specific flow or disable interaction with a particular socket altogether ```ts connection.addPreset(({ nodeId, side, key }) => { if (isReadonly(nodeId, side, key)) return undefined if (usesBidirect(nodeId, side, key)) return new BidirectFlow() return new ClassicFlow() }) ``` If the existing workflows don't meet your needs, you have the option to implement your own solution by referencing [the source code](https://github.com/retejs/connection-plugin/blob/next/src/flow/builtin/bidirect.ts){rel="nofollow"} of the existing ones. ## Enhanced behavior Enhancing the behavior of existing presets can involve tracking the events `connectionpick` and `connectiondrop` ```ts connection.addPipe(context => { if (context.type === 'connectionpick') { // when the user clicks on the socket const { socket } = context.data } if (context.type === 'connectiondrop') { // when the user clicks on the socket or any area const { socket, initial, created } = context.data } return context }) ``` The `connectionpick` event can be prevented ```ts connection.addPipe(context => { if (context.type === 'connectionpick') { if (readonly) return } return context }) ``` ## User-created connection By default, when a user creates connections using the UI, the plugin adds the connection as an object, rather than an instance of a class, such as `ClassicPreset.Connection`. If you want to customize the process of adding these connection, specify the `makeConnection` option in `ClassicFlow` or `BidirectFlow`. ```ts import { getSourceTarget } from 'rete-connection-plugin' connection.addPreset(() => new ClassicFlow({ makeConnection(from, to, context) { const [source, target] = getSourceTarget(from, to) || [null, null]; const { editor } = context; if (source && target) { editor.addConnection( new MyConnection( editor.getNode(source.nodeId), source.key, editor.getNode(target.nodeId), target.key ) ); return true; // ensure that the connection has been successfully added } } })) ``` Additionally, the usage of `getSourceTarget` is essential in this case, as the `from` and `to` options carry data about the initial and final sockets, which might not necessarily match the output and input sockets. ## Configure connection start/end positions Every connection is associated with a starting and ending point, directly connected to a socket (excluding pseudo-connections where end point depends on the cursor position). By default, the right side of the output socket serves as the starting point, while the left side of the input socket serves as the starting point. Modifying the socket size will cause a shift in the starting/ending point of the connection. To configure the start and end positions of the connection, you can provide `getDOMSocketPosition` with these offset coordinates relative to the socket center. This method is used by default when the `socketPositionWatcher` option is not specified. ```ts import { getDOMSocketPosition } from 'rete-render-utils' render.addPreset(Presets.classic.setup({ socketPositionWatcher: getDOMSocketPosition({ offset({ x, y }, nodeId, side, key) { return { x: x + 10 * (side === 'input' ? -1 : 1), y: y } }, }) })) ``` In this scenario, socket position calculation relies on DOM elements. There are cases when such an approach may prove inefficient due to performance or other implementation nuances (for instance, in the [LOD example](https://retejs.org/examples/lod) where displaying connections is required even when the actual sockets are not present in the DOM). In order to make use of your calculation approach for connection start/end positions, you can extend the abstract class `BaseSocketPosition` and implement the `calculatePosition` method. ```ts import { BaseSocketPosition } from 'rete-render-utils' type Position = { x: number, y: number } type Side = 'input' | 'output' export class ComputedSocketPosition extends BaseSocketPosition { async calculatePosition(nodeId: string, side: Side, key: string): Promise { if (!this.area) return null return { x: side === 'input' ? 0 : getNodeWith(nodeId) y: 0 } } } render.addPreset(Presets.classic.setup({ socketPositionWatcher: new ComputedSocketPosition() })) ``` where `calculatePosition` is expected to return the position relative to the node's position, or `null` if it cannot be calculated # Context menu ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/context-menu" title="Context menu"} ::: :::ref-github --- link: https://github.com/retejs/context-menu-plugin title: Plugin --- ::: :: ## Install dependencies ::kit :: ```bash npm i rete-context-menu-plugin ``` ## Prepare nodes To improve convenience and code reusability, it is recommended to create separate classes for nodes: ```ts class NodeA extends ClassicPreset.Node { constructor(socket: ClassicPreset.Socket) { super("NodeA"); this.addControl("port", new ClassicPreset.InputControl("text", {})); this.addOutput("port", new ClassicPreset.Output(socket)); } } /// class NodeB extends ... type Node = NodeA | NodeB; type Schemes = GetSchemes>; ``` ## Plugin connection For a simple solution, use the classic preset and specify a list of labeled items along with a function that returns the required node ```ts import { ContextMenuExtra, ContextMenuPlugin, Presets as ContextMenuPresets } from "rete-context-menu-plugin"; type AreaExtra = ReactArea2D | ContextMenuExtra; const contextMenu = new ContextMenuPlugin({ items: ContextMenuPresets.classic.setup([ ["NodeA", () => new NodeA(socket)], ["NodeB", () => new NodeB(socket)] ]) }); area.use(contextMenu); ``` But this is not sufficient as the render plugin is responsible for visualization ## Rendering the context menu Currently, the visualization of the context menu is possible using rendering plugins for **React.js**, **Vue.js**, **Angular**, **Svelte** and **Lit**. ```ts import { Presets } from "rete-react-plugin"; // or rete-vue-plugin, rete-angular-plugin, rete-svelte-plugin, @retejs/lit-plugin render.addPreset(Presets.contextMenu.setup()); ``` For a comprehensive guide on how to connect a specific renderer plugin to your stack version, please follow the guide for [React.js](https://retejs.org/docs/guides/renderers/react), [Vue.js](https://retejs.org/docs/guides/renderers/vue), [Angular](https://retejs.org/docs/guides/renderers/angular), [Svelte](https://retejs.org/docs/guides/renderers/svelte) or [Lit](https://retejs.org/docs/guides/renderers/lit) Clicking on the free space opens up a menu that displays the available nodes for creation, or simply click on an existing node to delete it. ## Subitems In order to specify node item as subitem, you can use the same definition using an array instead of a factory function: ```ts const contextMenu = new ContextMenuPlugin({ items: ContextMenuPresets.classic.setup([ ["Math", [ ["Number", () => new NumberNode()], ]] ]) }) ``` ## Built-in options for nodes and connections All nodes and connections have a `Delete` option in their context menu. This option allows you to delete a node, removing its connections first, or delete individual connections. Another option that you won't see by default on nodes is `Clone`. It appears for nodes that have a `clone` method. For example: ```ts class NodeA extends ClassicPreset.Node { clone() { return new NodeA() } } ``` ## Custom preset While the classic preset lets you briefly specify items for the main menu and node-specific menu, it might not offer enough flexibility. In such cases, you can define your own menu items: ```ts const contextMenu = new ContextMenuPlugin({ items(context, plugin) { if (context === 'root') { return { searchBar: false, list: [ { label: 'Custom', key: '1', handler: () => console.log('Custom') }, { label: 'Collection', key: '1', handler: () => null, subitems: [ { label: 'Subitem', key: '1', handler: () => console.log('Subitem') } ] } ] } } return { searchBar: false, list: [] } } }) ``` where `context` is `'root'`, node instance or connection instance ## Open the menu programmatically #{trigger-context-menu} To manually open the context menu, you need to create a `PointerEvent` with the required coordinates and call the `area.emit()` method: ```ts const event = new PointerEvent('contextmenu', { clientX: x, clientY: y, }) await area.emit({ type: 'contextmenu', data: { event, context } }) ``` where - `x`, `y` are numerical values (for example, the cursor coordinates, which you should extract separately), - `context` can be `'root'` or an instance of a node, connection, or other elements in your editor. Check out the complete result on the [Context menu](https://retejs.org/examples/context-menu) example page. # Readonly ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/readonly" title="Readonly"} ::: :::ref-github{link="https://github.com/retejs/readonly-plugin" title="Plugin"} ::: :: ::diagram{caption="Prevent events" name="guides/readonly/index"} :: ## Install dependencies ::kit :: ```bash npm i rete-readonly-plugin ``` ## Plugin connection ```ts import { ReadonlyPlugin } from "rete-readonly-plugin"; const readonly = new ReadonlyPlugin(); editor.use(readonly.root); editor.use(area); area.use(readonly.area); area.use(render); ``` Make sure to follow the order to connect `readonly.root` and `readonly.area` before any other plugins. ## Enable The plugin allows modifications to the editor by default, giving you the ability to establish the editor's initial appearance using methods such as `addNode`. Use the following code to activate it: ```ts readonly.enable(); ``` ## Disable Disabling can be done in a similar way: ```ts readonly.disable(); ``` ## Controls When referring to the in-built controls provided by the framework, like `ClassicPreset.InputControl`, it's important to specify the readonly feature individually in the below manner: ```ts new ClassicPreset.InputControl('text', { readonly: true }) ``` Since other controls can be custom, it is up to the developer to implement their "read-only" capability. ## Disable connection manipulation Finally, if you have completed the [Basic](https://retejs.org/docs/guides/basic) guide, make sure to remove the `ConnectionPlugin` import. ```ts area.use(connection); // should be removed ``` Check out the complete result on the [Readonly](https://retejs.org/examples/readonly) example page. # Modules ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) and [Dataflow](https://retejs.org/docs/guides/processing/dataflow) guides. It is recommended to review them for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/modules" title="Modules"} ::: :::ref-guide{link="/docs/guides/processing/dataflow" title="Dataflow"} ::: :::ref-guide{link="/docs/guides/processing/control-flow" title="Control flow"} ::: :::ref-example{link="/examples/allmatter" title="Allmatter"} ::: :: This guide outlines the key aspects of building **Module** nodes that process nested graphs, considering the available **Input** and **Output** nodes. This guide exclusively relies on [Dataflow](https://retejs.org/docs/guides/processing/dataflow) approach to streamline code comprehension. Once you have gone through this guide and the example provided, you should be able to implement Control flow using the instructions in the [Control flow](https://retejs.org/docs/guides/processing/control-flow) guide. ## Prepare nodes The core idea behind modules is to create distinct graphs featuring **Input** and **Output** nodes. The next step is to create a dedicated **Module** node that reflects the ports based on the nodes of the relevant type specified in the graph. To begin, let's create a node that will serve as our input point: ```ts export class InputNode extends ClassicPreset.Node { public value = null; constructor(public key: string) { super("Input"); this.addOutput("value", new ClassicPreset.Output(socket, "Number")); } data() { return { value: this.value }; } } ``` The user-defined `key` is crucial to associate it with the input port of **Module** node. Also, we need to specify `value` property to inject the input data. In order for a module to have any use, an **Output** node is necessary ```ts export class OutputNode extends ClassicPreset.Node { constructor(public key: string) { super("Output"); this.addInput("value", new ClassicPreset.Input(socket, "Number")); } data() { return {}; } } ``` In this instance, `data` method returns an empty object as the input data can be obtained through the `fetchInputs` method without the node's execution being necessary. The **Module** node, which serves as a portal into a nested graph and displays input and output ports, is the most complex node. Let's look at a simplified example: ```ts export class ModuleNode { module: null | Module = null; constructor(path: string) { super("Module"); this.setModule(path); } public async setModule(path: string) { this.module = findModule(path); await removeNodeConnections(this.id); if (this.module) { const { inputs, outputs } = this.module.getPorts(); syncPorts(this, inputs, outputs); } else { syncPorts(this, [], []); } } async data(inputs: Record) { const data = await this.module?.exec(inputs); return data || {}; } } ``` where - `findModule` function returns an object representing a module, allowing access to its ports for display and the execution of a nested graph - `syncPorts` updates input and output ports by removing outdated ones and adding new ones - `removeNodeConnections` function deletes all connections, allowing us to remove ports if we need to switch modules Keep in mind that making any dynamic changes to nodes, as seen in this example with `syncPorts`, requires calling `area.update('node', node.id)`. To prevent conflicting calls from multiple **Module** nodes using the same nested graph, make sure to initialize a new editor and engine within the `module.exec` method. ## Nested graph execution Here's a simplified example of how a nested graph processor can be implemented: ```ts function findModule(path: string) { return { getPorts() { const editor = new NodeEditor(); await importGraphByPath(path, editor); const nodes = editor.getNodes(); const inputs = nodes .filter((n): n is InputNode => n instanceof InputNode) .map((n) => n.key); const outputs = nodes .filter((n): n is OutputNode => n instanceof OutputNode) .map((n) => n.key); return { inputs, outputs }; }, exec: async (inputData: Record) => { const engine = new DataflowEngine(); const editor = new NodeEditor(); editor.use(engine); await importGraphByPath(path, editor); const nodes = editor.getNodes(); injectInputs(nodes, inputData); return retrieveOutputs(nodes, engine); } }; ``` where - `getPorts` retrieves the keys for both **Input** and **Output** nodes and returns them - `importGraphByPath` is supposed to load the essential nodes and connections for your module into the editor Each call creates a new instance of the editor to avoid conflicts when processing the graph. The following method involves injecting the input data of the **Module** node into the **Input** nodes of the nested graph. ```ts function injectInputs(nodes: Schemes["Node"][], inputData: Record) { const inputNodes = nodes.filter(node => node instanceof InputNode); inputNodes.forEach((node) => { // keep in mind that there may be no input connections, and we assume there's a maximum of one connection possible node.value = inputData[node.key] && inputData[node.key][0]; }); } ``` Once the input data has been injected, the next step is to retrieve the output from the nodes: ```ts async function retrieveOutputs(nodes: Schemes["Node"][], engine: DataflowEngine) { const outputNodes = nodes.filter(node => node instanceof OutputNode); // can be processed concurrently const outputs = await Promise.all(outputNodes.map(async node => { const data = await engine.fetchInputs(node.id); // we consider only the data from the first connection as there can be at most one input connection return [node.key, data.value[0]] as const; })); return Object.fromEntries(outputs); } ``` Check out the complete result on the [Modules](https://retejs.org/examples/modules) example page. Additionally, this approach is implemented in [Allmatter](https://retejs.org/examples/allmatter) application. # Scopes ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/scopes" title="Scopes"} ::: :::ref-guide{link="/docs/guides/arrange" title="Arrange nodes"} ::: :::ref-github{link="https://github.com/retejs/scopes-plugin" title="Plugin"} ::: :: The `rete-scopes-plugin` plugin provides features for building a node editor with nested nodes, also known as a subgraph. This plugin is categorized as [advanced](https://retejs.org/docs/licensing). This approach involves using nodes as parent elements for other nodes. This enables the movement of a parent node to trigger the movement of all of its child nodes, and the movement of child nodes to adjust the size of their parent nodes. The nesting can extend to more than one level. ## Install dependencies ::kit :: ```bash npm i rete-scopes-plugin ``` ## Prepare nodes The plugin requires node sizes to be explicitly specified: ```ts class Node extends ClassicPreset.Node { width = 190; height = 120; parent?: string } class Connection extends ClassicPreset.Connection {} type Schemes = GetSchemes>; ``` The purpose of this is to enable dynamic resizing of the node's sizes if it has nested nodes. Additionally, it's suggested to specify an optional `parent` field to programmatically designate parent nodes. ## Plugin connection ```ts import { ScopesPlugin, Presets as ScopesPresets } from 'rete-scopes-plugin' const scopes = new ScopesPlugin() scopes.addPreset(ScopesPresets.classic.setup()) area.use(scopes) ``` This code relies on a classic preset that offers functionality for user-scope interactions. To select a node and assign it as a parent, the user may long-press the node and drag it over another node. ## Adding nodes Let's add a couple of nodes, with one serving as a parent: ```ts const node1 = new Node('A'); const parent1 = new Node('Parent'); node1.parent = parent1.id; // specify node1 as nested into parent1 await editor.addNode(parent1); // make sure to add the parent node before adding its child await editor.addNode(node1); ``` ## Nodes order Bu default, the plugin is set up to use its own node ordering. If you followed the [Basic](https://retejs.org/docs/guides/basic) guide, you need to remove `AreaExtensions.simpleNodesOrder` ```ts AreaExtensions.simpleNodesOrder(area); ``` The ordering of nested nodes is a bit different as parent nodes shouldn't overlap its child nodes. When bringing node to front or adding child nodes to it, the order of child nodes is adapted (by `reordered` event) ## Arrange nodes The same [Arrange nodes](https://retejs.org/docs/guides/arrange) guide can be used for automated positioning of nodes in relation to one another, as the plugin used within also supports nested nodes. ## Dynamically changing relationships After the nodes have been added to the editor, to change the bindings between nodes, in addition to changing `node.parent` you need to explicitly call the update method ```ts // previously parent1.id was assigned node1.parent = undefined; await scopes.update(parent1.id) ``` ## Change the parent node padding By default, all nodes have the same padding. You can find these values in [the API documentation](https://retejs.org/docs/api/rete-scopes-plugin#props). The padding can be customized for each node individually using the `padding` option. ```ts const scopes = new ScopesPlugin({ padding: nodeId => ({ top: 40, left: 20, right: 20, bottom: 20 }) }); ``` ## Excluding nodes The `exclude` option allows you to exclude specific nodes from being processed by the plugin. This can be useful when you want certain nodes not to affect the size of the parent node. ```ts const scopes = new ScopesPlugin({ exclude: nodeId => excludedNodes.includes(nodeId) }); ``` ## Changing parent node size The generated sizes of parent nodes can be customized for specific nodes using the `size` option. This functionality is useful, for example, when child nodes have been removed, and you need to set a new size to display the layout correctly. ```ts const scopes = new ScopesPlugin({ size: (nodeId, size) => ({ width: size.width, height: 100 }) }); ``` Check out the complete result on the [Scopes](https://retejs.org/examples/scopes) example page. # Import/export ::references :::ref-example{link="/examples/modules" title="Modules"} ::: :::ref-guide{link="/docs/guides/data-structures" title="Data structures"} ::: :::ref-example{link="/examples/3d-configurator" title="3D Configurator"} ::: :: Out of the box, the editor can handle any JS objects as nodes and connections, as long as they include a mandatory `id` field for both. Additionally, connections must have `source` and `target` fields. These objects can be either plain data objects or objects that include methods. Check out the [Data structures](https://retejs.org/docs/guides/data-structures) guide for details. The current version of the editor doesn't support importing/exporting by default due to some limitations: - The process of serializing nodes/connections to JSON can be challenging - The import order of nodes may vary depending on how your graph is structured ## Valid JSON objects Suppose we have a straightforward example where the node is a valid JSON object ```ts import { NodeEditor, BaseSchemes, getUID } from 'rete' const editor = new NodeEditor() const node = { id: getUID(), label: 'Label' } await editor.addNode(node) ``` In this case, we can easily export such nodes into JSON to save them in a database, for instance. ## Non-valid JSON objects Objects that aren't valid JSON, such as instances of classes, objects with functions, or objects with cyclic references pose a challenge. Discarding them would mean losing the advantages that come with using JS. For instance, node classes can be used to create nodes, allowing for more reliable and convenient interaction through methods, and providing the flexibility to use different paradigms. ```ts import { ClassicPreset } from 'rete' const node = new ClassicPreset.Node('Label') node.addOutput('port', new ClassicPreset.Output(socket, 'Label')) await editor.addNode(node) ``` While serialization and deserialization might be one way to convert such objects into valid JSON, this approach may not work in complex scenarios. ## Exporting and importing nodes If you want to export a graph, you can use the following code as a reference. Please note that this is not a fully functional code, but rather an approximation to help you implement your own import/export functionality according to your specific requirements. ```ts const data = { nodes: [] } const nodes = editor.getNodes() for (const node of nodes) { data.nodes.push({ id: node.id, label: node.label, inputs: /// .... controls: /// .... outputs: /// .... }) } ``` In order to revert the transformation, we must initialize node instances, inputs, outputs, and controls based on the objects provided. ```ts for (const { id, label, inputs, outputs, controls } of data.nodes) { const node = new ClassicPreset.Node(label); node.id = id; /// ... inputs /// ... controls /// ... outputs await editor.addNode(node) } ``` The complete example can be found [at the link](https://codesandbox.io/s/rete-js-v2-import-export-999y8z?file=/src/index.ts:3276-3465){rel="nofollow"}. Note that this example has been simplified for ease of understanding. Additionally, importing or exporting inputs and outputs may not always be necessary if they are static and we know the node type. In such cases, we can simply save the node name and relevant data, which can be used to create instances of nodes with pre-defined ports. Check out the [3D Configurator](https://retejs.org/examples/3d-configurator) and the [Modules](https://retejs.org/examples/modules) examples with implementation of this approach. ## Import nodes order Another challenge during graph import could be the order in which nodes need to be imported. In a simple cases, the order will be the same as the order in which they were added to the editor. ```ts const graph = /// loaded JSON-valid object from DB for (const node of graph.nodes) { await editor.addNode(node) } ``` When dealing with more complex graphs, the order of adding nodes can vary. For instance, in a graph with nested nodes, it might be necessary to add parent nodes prior to child nodes. Moreover, it's quite possible that a user may create a child node before its parent node while working in the editor. Let's take a look at an example of importing a graph where certain nodes have `parent` field indicating their association with another node. As a result, these nodes must be imported after their parent node has been created. ```ts async function importForParent(nodes, parent = undefined) { const nodes = nodes.filter(node => node.parent === parent) for (const node of nodes) { await editor.addNode(node) await importForParent(nodes, node.id) } } const graph = /// loaded JSON-valid object from DB await importForParent(graph.nodes) ``` Since this approach is more complex and there are multiple ways to do it, the import method will vary depending on your specific use case. # Validation ::references :::ref-example{link="/examples/chatbot" title="Chatbot"} ::: :: The framework provides a flexible plugin and message system, which enables you to extend the functionality of various components and manage their behavior. This can be achieved by using pipes for specific message types. ## Nodes validation Validation will be implemented for adding nodes, where nodes of a certain type will be prevented until the starting node is added ```ts editor.addPipe(context => { if (context.type === 'nodecreate') { if (context.data instanceof EndNode) { const hasBeginNode = editor.getNodes().some(n => n instanceof BeginNode) if (!hasBeginNode) { alert('cannot add EndNode until BeginNode is added') return } } } return context }) ``` where `EndNode` and `BeginNode` are classes that extend `ClassicPreset.Node`. This particular case utilized a method of preventing message propagation by specifying `return` with no value. This technique is appropriate for types like `nodecreate`, `noderemove`, `connectioncreate` and `connectionremove`. ## Connections validation The same principle can be used to prevent messages of `connectioncreate` type. ```ts editor.addPipe((context) => { if (context.type === "connectioncreate") { if (!canCreateConnection(editor, context.data)) { alert("Sockets are not compatible"); return; } } return context; }); ``` where `canCreateConnection` is any function that takes a connection instance `context.data` and returns a boolean. Let's take the [Chatbot](https://retejs.org/examples/chatbot) example, particularly the modules `chatbot/utils.ts` and `chatbot/sockets.ts`, and apply the same approach to our `canCreateConnection` function. First, we'll implement advanced sockets: ```ts import { ClassicPreset } from "rete"; export class ActionSocket extends ClassicPreset.Socket { constructor() { super("Action"); } isCompatibleWith(socket: ClassicPreset.Socket) { return socket instanceof ActionSocket; } } export class TextSocket extends ClassicPreset.Socket { constructor() { super("Text"); } isCompatibleWith(socket: ClassicPreset.Socket) { return socket instanceof TextSocket; } } ``` by using `isCompatibleWith` method, socket types are able to specify which other socket types they are compatible. After that, we will implement our function which takes a connection as an argument, returns the corresponding sockets from the nodes, and checks them for compatibility. ```ts export function canCreateConnection(editor: NodeEditor, connection: Schemes["Connection"]) { const { source, target } = getConnectionSockets(editor, connection); return source && target && source.isCompatibleWith(target) } ``` where `getConnectionSockets` function was taken from the aforementioned example. # Minimap ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/minimap" title="Minimap"} ::: :::ref-github{link="https://github.com/retejs/minimap-plugin" title="Plugin"} ::: :: ## Install dependencies ::kit :: ```bash npm i rete-minimap-plugin ``` ## Prepare nodes Predefined node sizes are necessary for the minimap plugin as they are displayed on the minimap with their respective dimensions. ```ts class Node extends ClassicPreset.Node { width = 190; height = 120; } class Connection extends ClassicPreset.Connection {} type Schemes = GetSchemes>; ``` ## Plugin connection ```ts import { MinimapExtra, MinimapPlugin } from "rete-minimap-plugin"; type AreaExtra = // ... | MinimapExtra const minimap = new MinimapPlugin()(); area.use(minimap); ``` But this is not sufficient as the render plugin is responsible for visualization ## Rendering the minimap Currently, the visualization of the minimap is possible using rendering plugins for **React.js**, **Vue.js**, **Angular**, **Svelte** and **Lit**. ```ts import { Presets } from "rete-react-plugin"; // or rete-vue-plugin, rete-angular-plugin, rete-svelte-plugin, @retejs/lit-plugin render.addPreset(Presets.minimap.setup({ size: 200 })); ``` For a comprehensive guide on how to connect a specific renderer plugin to your stack version, please follow the guide for [React.js](https://retejs.org/docs/guides/renderers/react), [Vue.js](https://retejs.org/docs/guides/renderers/vue), [Angular](https://retejs.org/docs/guides/renderers/angular), [Svelte](https://retejs.org/docs/guides/renderers/svelte) or [Lit](https://retejs.org/docs/guides/renderers/lit) The minimap is automatically inserted into your editor and has absolute positioning. If your editor is smaller than the viewport, your editor's container should have `position: relative`. Check out the complete result on the [Minimap](https://retejs.org/examples/minimap) example page. # Dock menu ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/dock" title="Dock menu"} ::: :::ref-github{link="https://github.com/retejs/dock-plugin" title="Plugin"} ::: :: ## Install dependencies ::kit :: ```bash npm i rete-dock-plugin ``` ## Plugin connection Currently, the only preset available is the classic preset, which enables adding nodes to the editor by dragging their previews into the editor area. ```ts import { DockPlugin, DockPresets } from "rete-dock-plugin"; const dock = new DockPlugin(); dock.addPreset(DockPresets.classic.setup({ area, size: 100, scale: 0.6 })); area.use(dock); ``` ## Adding nodes In order to display node previews, you need to specify a function that returns a node instance. This function is called when a node is added to the Dock menu or dragged onto the editor area. ```ts dock.add(() => new NodeA()); dock.add(() => new NodeB()); ``` ## Add at a specific position Inserting a node at the 3rd position (2nd index) ```ts dock.add(() => new NodeA(), 2); ``` ## Remove nodes Use the same function passed to `add` to remove the added node ```ts const createNodeA = () => new NodeA() dock.add(createNodeA); dock.remove(createNodeA); ``` Check out the complete result on the [Dock menu](https://retejs.org/examples/dock) example page. # Connection path ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/connection-path" title="Connection path"} ::: :::ref-github --- link: https://github.com/retejs/connection-path-plugin title: Plugin --- ::: :: ## Install dependencies ::kit :: ```bash npm i rete-connection-path-plugin d3-shape ``` ## Plugin connection The `curveStep` method is used for all connections in this example. Keep in mind that the plugin must be linked to the render plugin, which must emit the `connectionpath` event. ```ts import { ConnectionPathPlugin } from "rete-connection-path-plugin"; import { curveStep } from "d3-shape"; const pathPlugin = new ConnectionPathPlugin>({ curve: () => curveStep }); render.use(pathPlugin); ``` ## Connection-specific path The `curve` option takes a callback function with the connection instance as the first parameter, allowing you to customize the path type. ```ts import { curveStep, curveMonotoneX, curveLinear, CurveFactory } from "d3-shape"; class Connection extends ClassicPreset.Connection< ClassicPreset.Node, ClassicPreset.Node > { curve?: CurveFactory; } const pathPlugin = new ConnectionPathPlugin>({ curve: (connection) => connection.curve || curveStep }); const monotoneConnection = new Connection(a, "port", b, "port"); const linearConnection = new Connection(a, "port", b, "port"); monotoneConnection.curve = curveMonotoneX; linearConnection.curve = curveLinear; ``` Check out the complete result on the [Connection path](https://retejs.org/examples/connection-path) example page. # Reroute ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/reroute" title="Reroute"} ::: :::ref-guide{link="/docs/guides/selectable" title="Selectable"} ::: :::ref-github --- link: https://github.com/retejs/connection-reroute-plugin title: Plugin --- ::: :: ## Install dependencies ::kit :: ```bash npm i rete-connection-reroute-plugin ``` ## Plugin connection ```ts import { ReroutePlugin, RerouteExtra } from "rete-connection-reroute-plugin"; type AreaExtra = | Area2D // this type is mandatory | ReactArea2D | RerouteExtra; const reroutePlugin = new ReroutePlugin(); render.use(reroutePlugin) ``` The plugin is connected, but you need also connect a visualization preset to render the pins ## Rendering Currently, the visualization of the reroute pins is possible using rendering plugins for **React.js**, **Vue.js**, **Angular**, **Svelte** and **Lit**. ```ts import { Presets } from "rete-react-plugin"; // or rete-vue-plugin, rete-angular-plugin, rete-svelte-plugin, @retejs/lit-plugin render.addPreset(Presets.reroute.setup({ contextMenu(id) { reroutePlugin.remove(id); }, translate(id, dx, dy) { reroutePlugin.translate(id, dx, dy); } })); ``` For a comprehensive guide on how to connect a specific renderer plugin to your stack version, please follow the guide for [React.js](https://retejs.org/docs/guides/renderers/react), [Vue.js](https://retejs.org/docs/guides/renderers/vue), [Angular](https://retejs.org/docs/guides/renderers/angular), [Svelte](https://retejs.org/docs/guides/renderers/svelte) or [Lit](https://retejs.org/docs/guides/renderers/lit) ## Selectable Pins As explained in the [Selectable](https://retejs.org/docs/guides/selectable) guide, you can adjust the selection of all types of elements. The following code must be used to incorporate pins to the selection system: ```ts import { RerouteExtensions } from "rete-connection-reroute-plugin"; const selector = AreaExtensions.selector(); const accumulating = AreaExtensions.accumulateOnCtrl(); AreaExtensions.selectableNodes(area, selector, { accumulating }); RerouteExtensions.selectablePins(reroutePlugin, selector, accumulating); render.addPreset(Presets.reroute.setup({ pointerdown(id) { reroutePlugin.unselect(id); reroutePlugin.select(id); }, // keep contextMenu and translate from the code above })); ``` where - `RerouteExtensions.selectablePins` is a compact extension that adds or removes pins to/from the registry of selected elements, and enables their movement. - `pointerdown` event is triggered upon clicking a pin and designates it as the selected pin Check out the complete result on the [Reroute](https://retejs.org/examples/reroute) example page. # Undo/Redo ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/history" title="History"} ::: :::ref-github{link="https://github.com/retejs/history-plugin" title="Plugin"} ::: :: ## Install dependencies ::kit :: ```bash npm i rete-history-plugin ``` ## Plugin connection ```ts import { HistoryPlugin, HistoryActions } from "rete-history-plugin"; const history = new HistoryPlugin>(); area.use(history); ``` ## Classic preset Similar to the majority of plugins, it relies on a preset to unlock its complete functionality. ```ts import { Presets } from "rete-history-plugin"; history.addPreset(Presets.classic.setup()) ``` It is responsible for tracking the history of adding, removing, or translating nodes, as well as tracking the adding and removing of connections. ## Usage The plugin doesn't track user actions by default, such as keyboard shortcuts. To enable user interaction, you can implement usage of `undo`/`redo` methods on your own ```ts await history.undo() await history.redo() ``` Alternatively, you can use the default extension that tracks the `Ctrl+Z`/`Ctrl+Y` shortcuts ```ts import { HistoryExtensions } from "rete-history-plugin"; HistoryExtensions.keyboard(history); ``` ## History management If you need additional functionality beyond node and connection tracking, you have the option to create custom actions and add them to the history ```ts class MyAction implements HistoryAction { undo(): void | Promise { // undo action } redo(): void | Promise { // redo action } } const history = new HistoryPlugin | MyAction>(); const myAction = new MyAction() history.add(myAction) ``` To retrieve a history list, use the `getHistorySnapshot` or `getRecent` methods (please note that modifying the history directly is not possible) ```ts const list = history.getHistorySnapshot() const recentFor1s = history.getRecent(1000) ``` By using the latest method, we retrieve a list of actions within the past second. Since these actions are time-dependent, the plugin can intelligently group them together, reducing the need for users to repeatedly press 'Ctrl + Z' when multiple actions occur within a short time frame. This is particularly useful when a user deletes a node, triggering the automatic removal of its associated connections. Additionally, you can configure the timing as follows, where `200` is the default time threshold in milliseconds. If the time interval between adjacent actions is shorter than the specified time, the plugin will consider them to be part of the same group. ```ts const history = new HistoryPlugin({ timing: 200 }); ``` In order to make the undo or redo process independent of time constraints between actions, you can adopt the following method: ```ts // action 1 // action 2 history.separate() // or myAction.separated = true // action 3 ``` In this way, when the `undo` command is triggered for the first time, it will only undo the third action, even the time interval between it and the preceding action is less than 200 milliseconds. Check out the complete result on the [History](https://retejs.org/examples/history) example page. # Comments ::alert This guide is based on the [Basic](https://retejs.org/docs/guides/basic) guide. It is recommended to review it for a comprehensive understanding of this guide. :: ::references :::ref-example{link="/examples/comments" title="Comments"} ::: :::ref-github{link="https://github.com/retejs/comment-plugin" title="Plugin"} ::: :: ## Install dependencies ::kit :: ```bash npm i rete-comment-plugin ``` ## Prepare nodes ```ts class Node extends ClassicPreset.Node { width = 180; height = 140; } class Connection extends ClassicPreset.Connection {} type Schemes = GetSchemes>; ``` ## Plugin connection ```ts import { CommentPlugin } from "rete-comment-plugin"; const comment = new CommentPlugin(); area.use(comment); ``` ## Add comments programmatically Adding an inline comment ```ts comment.addInline("Inline comment text", [360, -20], node.dd); ``` where `[360, -20]` is the position of the comment, which can be placed freely in relation to the `node` it is attached to. Adding a frame comment ```ts comment.addFrame("Frame comment text", [node.id]); ``` where `[node.id]` refers to the nodes that are covered by this comment ## Selectable comments ```ts import { CommentExtensions } from "rete-comment-plugin"; const selector = AreaExtensions.selector(); const accumulating = AreaExtensions.accumulateOnCtrl(); CommentExtensions.selectable(comment, selector, accumulating); ``` ## Edit comment text The comment text can be edited by RMB. By default, a prompt with an input field will be displayed, but you can customize this. ```ts const comment = new CommentPlugin({ edit: async (comment) => { return prompt('Edit comment', comment.text) } }); ``` where the `edit` property must accept an asynchronous function that returns the text for the comment. Check out the complete result on the [Comments](https://retejs.org/examples/comments) example page. # Performance ::references :::ref-example{link="/examples/performance" title="Performance"} ::: :::ref-example{link="/examples/lod" title="LOD"} ::: :::ref-example{link="/examples/lod-gpu" title="LOD GPU"} ::: :: In client applications, you may frequently encounter two issues: resource-intensive operations and low FPS. The former is caused by synchronous operations such as executing resource-intensive JS code or some browser APIs that block the main thread. The latter is often attributed to the direct rendering of elements by the browser - the more complex and numerous they are, the more time it takes for the browser to create the layout and render them. In the context of this framework, the following approaches can be adopted to minimize the impact of the aforementioned issues: ## Connect plugins only when needed In the case of transforming a graph where intermediate results aren't visualized, it may be unnecessary to connect additional plugins. Instead, copy the transformed result to a new editor that already has all required plugins connected. ## Simplifying nodes at a specific zoom level This technique is particularly useful when visualizing a large number of nodes. In such cases, the bottleneck is typically the browser's rendering of elements when all nodes are visible in the viewport Usually, if many nodes are displayed in the viewport, the zoom level is quite low and each node occupies a relatively small area. Consequently, these nodes can be replaced with content-free rectangles of the same size, reducing the cost of rendering while maintaining a good UX The LOD (Level of Detail) technique, commonly used in 3D visualization, can also be applied here. Check out the [LOD](https://retejs.org/examples/lod) example. # Quality assurance ::references :::ref-github{link="https://github.com/retejs/rete-qa" title="Rete QA"} ::: :::ref-external{link="https://playwright.dev/" title="Playwright"} ::: :: There are two kinds of tests in this project - unit and E2E Unit tests are used to test individual modules within a package. While this approach is fast, it doesn't guarantee that the entire product functions correctly from a user's perspective. On the other hand, E2E tests enable comprehensive testing of the product from the user's point of view. We rely on **Playwright** for E2E testing, which is integrated into `rete-qa` package and comes equipped with a set of UI tests for basic framework features. Guidelines for writing tests: - **keep it simple**: create tests that are easy to write and read - **don't focus too much on code coverage metrics**: use them as reference but avoid drawing conclusions solely based on test coverage % - **test one thing at a time**: this will help you locate issues more easily - **cover edge cases**: ensures reliability of tests ## Rete QA tool The main purpose of this tool is to conduct regression UI testing on different combinations of supported features, which are presented as individual packages and various integrations with UI frameworks. ::diagram{caption="Architecture" name="qa/index"} :: [Playwright](https://playwright.dev){rel="nofollow"} serves as the underlying technology for this testing tool and enables it to conduct tests in three different browser types: **Chromium**, **Firefox**, **WebKit**. Additionally, we test our framework on various versions of commonly used UI frameworks, such as Angular, Svelte, Vue.js, and React.js. This gives us **51** test runs across different environments as we have a matrix of browsers and framework versions ::rete-qa-sheet{:browsers="browsers" :stacks="stacks" title="Browser \ Stack"} :: ### Installation ```bash npm i -g rete-qa ``` Don't forget to install the additional system dependencies as per the [Playwright's instructions](https://playwright.dev/docs/ci#introduction){rel="nofollow"}. ### Initialization Run the command and wait until the projects are generated for various frameworks and their dependencies are installed ```bash rete-qa init --deps-alias dependencies.json ``` where `dependencies.json` (optional) is a JSON file, which should contain a mapping of dependencies Dependencies are typically installed from npmjs using `latest` tag, but you can specify a different version by providing `name@version` or path to the tarball. For example, you can substitute a plugin with your own version of the plugin that has not yet been published ```json { "my-rete-plugin": "../my-plugin/my-rete-plugin-1.0.0.tgz" } ``` ### Run tests Run the tests for provided stacks (**React.js**, **Vue.js**, **Angular**, **Svelte**) and different browsers (**Chromium**, **Firefox**, **WebKit**) ```bash rete-qa test ``` # Development This documentation article is for developers who are looking to develop new plugins, improve existing ones, or debug them. ## Rete CLI Rete CLI is a development tool that includes TypeScript, ESLint, and Jest for plugin building without the need for environment setup. It uses Rollup for building and has pre-configured Babel presets for TypeScript support. Check out the [Rete CLI](https://retejs.org/docs/development/rete-cli) article for details. ## Rete Kit This tool aims to enhance efficiency during plugin or project development using the framework. It provides several features, such as instant plugin structure creation, project creation and batch build for dependencies. Check out the [Rete Kit](https://retejs.org/docs/development/rete-kit) article for details. ## Style guide ESLint is used to ensure code style consistency across all packages. The configuration is provided by [Rete CLI](https://retejs.org/#rete-cli) and comprises rules that are deemed more suitable by the maintainers. If necessary, you can customize the configuration by adding required rules Although linters are helpful, there are some cases they may miss. To supplement this, we have included a list of recommendations: - **Use comment with purpose**: prioritize refactoring your code to be more clear and concise before relying on comments to explain it - **Simplicity**: strive for a balance between code reuse and the amount of boilerplate code. While boilerplate code may seem redundant, it is often more appropriate than overly complicated solutions that can obscure the code's purpose - **Handling errors and exceptions**: always handle errors and exceptions properly, providing users with useful information about the issue. Ensure that any exceptions thrown do not hinder the application's performance and are gracefully handled whenever possible - **Performance**: the code should be optimized to ensure there are no significant freezes with large data sets, but it's equally important to maintain code readability and ease of maintenance rather than focusing solely on micro-optimizations # Rete CLI ::references :::ref-github{link="https://github.com/retejs/rete-cli" title="Rete CLI"} ::: :::ref-external{link="https://rollupjs.org/" title="Rollup"} ::: :::ref-external{link="https://eslint.org/" title="ESLint"} ::: :::ref-external{link="https://jestjs.io/" title="Jest"} ::: :::ref-external{link="https://typedoc.org/" title="TypeDoc"} ::: :: ::diagram{caption="Architecture" name="cli/index"} :: Rete CLI is a building tool with built-in support for TypeScript and ESLint along with predefined rules. Additionally, it includes Jest. These features make it simple to start building new plugins without having to setup your own environment for building, linting, and testing. The build feature is based on Rollup and comes with pre-configured Babel presets for TypeScript support. ## Installation ```bash npm i -g rete-cli ``` ## Building the project The first step is to create a configuration file called `rete.config.ts` ```ts import { ReteOptions } from 'rete-cli' export default{ input: 'src/index.ts', // path to entry script name: 'Namespace' // namespace for UMD bundles } ``` Run the command ```bash rete build --config rete.config.ts ``` The generated `dist` directory is publish-ready and includes all required bundles, type definitions, README.md, and package.json files with their corresponding paths. The `--watch` option can be used to trigger automatic project building upon save, while the `--outputs` option allows you to specify multiple output paths separated by commas for the build destination. ## Create an advanced configuration Let's take a look at several configuration options that are supported: - connect Rollup plugins - specify third-party dependencies that shouldn't be bundled - specify output path - connect Babel plugins and presets ```ts import { ReteOptions } from 'rete-cli' export default [ // config with multiple entries { input: 'src/foo/index.ts', name: 'Namespace' babel: { presets: [ require('@babel/preset-env'), // used by default, but should be declared when you specifies 'presets' require('@babel/preset-typescript'), // used by default require('@babel/preset-react'), ] } }, { input: 'src/bar/index.ts', name: 'Namespace' globals: { 'rete': 'Rete', }, plugins: [ // specify Rollup plugins commonjs(), ], babel: { plugins: [ // include Babel plugins ] } } ] ``` ## Linting By default, running `rete build` command includes a linting step prior to generating the bundles. However, you can also perform linting independently by running a separate command. ```bash rete lint ``` # Rete Kit ::references :::ref-github{link="https://github.com/retejs/rete-kit" title="Rete Kit"} ::: :::ref-external{link="https://angular.io/" title="Angular"} ::: :::ref-external{link="https://vuejs.org/" title="Vue.js"} ::: :::ref-external{link="https://react.dev/" title="React.js"} ::: :::ref-external{link="https://vitejs.dev/" title="Vite.js"} ::: :::ref-external{link="https://svelte.dev/" title="Svelte"} ::: :::ref-external{link="https://lit.dev/" title="Lit"} ::: :::ref-external{link="https://nextjs.org/" title="Next.js"} ::: :::ref-external{link="https://nuxt.com/" title="Nuxt"} ::: :: ::diagram{caption="Architecture" name="kit/index"} :: The purpose of this tool is to improve efficiency when developing plugins or projects using this framework. It offers the following features: - **Plugin creating**: use this feature to create a basic plugin structure instantly, without the need for setting up a build system, linter, or test runner - **Application creation**: choose the framework to build your application, specify the version and desired features and get a ready-to-use application to jumpstart your development process - **Batch build**: select copies of repositories containing the source code of the plugins being developed and this tool will start building them in a watch mode, as well as synchronizing their dependencies ## Install ```bash npm i -g rete-kit ``` ## Create an application Inquirer mode ```bash rete-kit app ``` Or, you can specify the options ```bash rete-kit app --name --stack --stack-version --features --deps-alias ``` where - `` option lets you choose `angular`, `vue`, `vue-vite`, `react`, `react-vite`, `svelte`, `lit-vite`, `vite`, `next` or `nuxt` - `` is a comma-separated list of the Rete.js editor features - `` is a JSON file that maps dependencies. By default, it installs the latest version from npmjs, but you can specify a different version using the format `name@version` or a path to the tarball Additionally, re-executing the command with the same `name`, `stack`, and `stack-version` options enables you to apply supplementary features without having to recreate the application. After completion, you will have a directory with an application that can usually be started using the command `npm run start` (depending on the stack). When opening the application, you can to specifying a query parameter `template` in the URL with the following values: - `default`: default, a classic graph with dataflow example - `perf`: a graph with a large number of nodes, which can be adjusted using `cols` and `rows` parameters - `customization`: custom nodes and connections specific for each render plugin - `3d`: three.js-based scene with an integrated editor using [`rete-area-3d-plugin`](https://retejs.org/docs/guides/3d). For instance, an Angular customization template can be available at {rel="nofollow"}. ## Plugin creation You can easily create a plugin within your codebase by following the example of other plugins and extending `Scope`, without the need to build it as a separate package. In case you want to develop a plugin as a separate package, use this command: ```bash rete-kit plugin --name ``` where `` is a string that will be transformed into different formats and used in the templates, such as `rete.config.ts` and the `package.json` name. The generated plugin includes all the essential configs, allowing you to start working with the source code immediately. ## Building dependencies for development Developing modules that are separated into different packages is a challenging process. In contrast to a single codebase where the build system can detect changes in the directory and apply hot reload, developers need to manually set up the build of each dependency they work on and insert the changes into the project. Basically, `npm link` and a bash scripts can be used to build required modules in watch mode. However, `npm link` has certain limitations that might not be immediately noticeable. These limitations can stem from the shared dependencies of the project and the dependencies we're working on. The `rete-kit build` command was created to address such issues. It has two modes: - **building all project dependencies**: specify your project's path. The tool scans the current directory recursively (up to two levels deep) for repositories that contain the `rete` dependency. In the watch mode, it directly builds them into the `node_modules` directory where they are used ```bash rete-kit build --for ./my-project ``` - **performing a build of specific directories**: specify the directories containing the source code for dependencies that should be included during the build process using `--folders` option. Similar to the first mode, the resulting build will be inserted into the `node_modules` directory of the target ```bash rete-kit build --folders my-plugin-1,my-plugin-1,my-project ``` Please note that to use the hot reload feature to its fullest, you will need to disable the cache for the relevant dependencies. Otherwise, any changes made will not be applied on the fly. To accomplish this in Webpack, you can specify `snapshot.managedPaths`. If the project still doesn't update, it may be necessary to manually clear the cache of compiled modules. # Troubleshooting ## Check the documentation First, take a look at the framework documentation. Frequently, common problems are addressed in the documentation, and you may find a solution to your problem by utilizing the search function on the website or documentation ## Explore community resources When the documentation falls short, explore community resources. Search for answers on the [Discord server](https://discord.com/invite/cxSFkPZdsV){rel="nofollow"} and [GitHub issues](https://github.com/search?q=org%3Aretejs\&type=issues){rel="nofollow"} ## Check your dependencies Ensure that you're using the latest package versions. Updates frequently include bug fixes and enhancements that can potentially resolve the problem at hand. Additionally, ensure that dependencies use their peer dependencies correctly. Avoid situations where your application is using multiple copies of the same dependency (for example, via `npm link`), which may lead to incorrect behavior of operators such as `instanceof`. ## Debugging Leverage your browser's devtools to debug the issue. The console is an effective tool for diagnosing errors, warnings, and other issues. Additionally, you can take advantage of breakpoints, conditional breakpoints, or logpoints to facilitate the debugging process. In case of extreme difficulty, you can clone the repository, make modifications for debugging purposes, and build it in development mode for your project using the [Rete CLI](https://retejs.org/docs/development/rete-cli) ## Reproduce the issue Build a minimal example that reproduces the problem you're facing. This can assist in identifying the issue's origin. External factors may frequently be the cause of the problem, since JS allows you to delve into implementation details and patch them ## Ask for help If you have followed all the preceding steps and are still unable to find a solution, don't hesitate to seek help from the community. Make sure to include a live example that reproduces the problem or your attempts to achieve the desired outcome. # Licensing The MIT license applied to most framework packages permits you to use them without any limitations in personal and commercial projects alike. Certain plugins are classified as advanced and currently have only a CC-BY-NC-SA-4.0 license. These include: - [rete-structures](https://github.com/retejs/structures){rel="nofollow"} - [rete-scopes-plugin](https://github.com/retejs/scopes-plugin){rel="nofollow"} They plugins are freely available for use in personal, non-commercial projects. Commercial use is currently not permitted due to the absence of a dual licensing model. However, as this functionality isn't primary, you are free to utilize other available features of the framework and develop additional custom solution. If you have any questions or suggestions, please contact us at # Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org){rel="nofollow"}, version 1.4, available at {rel="nofollow"} For answers to common questions about this code of conduct, see {rel="nofollow"} # Contribution Thank you for your interest in contributing to this project! Before you get started, please make sure to review our [Code of conduct](https://retejs.org/docs/code-of-conduct) and [Licensing](https://retejs.org/docs/licensing). By contributing to our project, you agree to comply with the code of conduct and license your contributions in line with our licensing terms. ## Code contribution The contribution process follows the standard Github method of forking the repository and submitting a pull request. When submitting a pull request, make sure to reference the relevant issue if applicable. For improvements, provide a detailed explanation of the changes and their rationale. We also recommend discussing the issue in the GitHub issues section prior to submitting the pull request to ascertain whether the problem is common and if the proposed enhancement can be implemented as a standalone package or requires modification of the current codebase. - Follow the [Style guide](https://retejs.org/docs/development/#style-guide) - Keep pull requests small and focused on a single issue or topic - Write simple and meaningful unit tests for new code that you're contributing - Test changes using Rete QA and, if possible, add E2E tests for new features When making changes to the code, keep in mind any potential breaking changes and try to use plugins to extend functionality whenever possible instead of modifying the existing code. This approach aligns with the project architecture's goal of minimizing the need for core code changes. Check out the [Development](https://retejs.org/docs/development) page for a more in-depth guide on developing plugins. ### Step-by-step contribution to the package Whether you're a newcomer or have experience contributing to the open-source in JS ecosystem, this guide will help you avoid mistakes while saving changes to the version control system and validating them. #### Prerequisites - Node.js (recommended v18) - GIT - Editor (VS Code, etc.) - GitHub profile #### Steps ::diagram{caption="Flow" name="contribution/package"} :: - Fork the repository - Clone the fork locally: `git clone ` - Create a branch for your fix or feature (`fix/`, `feature/`, etc.). - Install dependencies: `npm ci` - Check if the build works in your environment: `npm run build` - Make changes - Run tests, linter, and build - [Validate changes](https://retejs.org/#package-validate-changes) - Commit changes according to [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/){rel="nofollow"} - Push changes to the corresponding branch of this repository - Create a pull request #### Validate changes ::diagram{caption="Validation flow" name="contribution/validate-changes"} :: - Create package tarball: `cd dist && npm pack` - Check changes in your application - Install the received \*.tgz in your application - Start the application - Check changes in template applications - Create an application using [Rete Kit](https://retejs.org/docs/development/rete-kit) - Install the received \*.tgz in this application - Start the application - E2E testing - Specify path to the received \*.tgz in [deps alias file](https://retejs.org/docs/quality-assurance#init) - Initialize the test environment using [Rete QA](https://retejs.org/docs/quality-assurance#rete-qa) with `--deps-alias` - Run tests: `rete-qa test` Additionally, if you aim to boost this process during continuous changes using Hot Reload, you have the option to use the [rete-kit build](https://retejs.org/docs/development/rete-kit#build-deps-rete-kit) command. This will ensure that changes in the source code of the cloned package repository are distributed across all specified dependencies and applications. ## Testing and Bug reporting Testing the project in various environments (browsers, build tools) and reporting bugs is greatly appreciated. [Rete Kit](https://retejs.org/docs/development/rete-kit) tool can assist in testing the project with various frameworks by allowing you to easily create a basic editor for the required version. If you come across a bug, please report it by creating an issue in the appropriate GitHub repository. If you're not sure where the error comes from, create an issue in the main repository. Your ticket should include detailed steps to reproduce the issue. Please ensure that there is no previous ticket created with the same problem. If one exists, you can add a comment with a detailed description of the issue from your perspective or add reaction (this is also important). ## Documentation We welcome any contributions from the community to improve the documentation of our project. If you come across any typos or inaccuracies, please feel free to report them in the [retejs.org](https://github.com/retejs/retejs.org){rel="nofollow"} repository or submit a pull request with your suggested changes. For those looking to contribute to the documentation, we recommend reviewing the [Contribution Guide](https://github.com/retejs/retejs.org/blob/main/CONTRIBUTION.md){rel="nofollow"} to get started ## Contribute to the community Experienced with Rete.js? Here are a few ways to contribute to the community: - engage with users by responding to their questions on GitHub Issues and the Discord server (links available in the header) - participate in initiatives by reacting (voting) to them or prioritize existing issues under discussion - Pull Request reviews are appreciated, if you have experience with TypeScript or fluent in the language that the documentation has been updated for Additionally, you can share information about this framework on your social media. It may help someone who is struggling with a problem or task that this framework can solve. ## Financial support Donations to the project can be made through a [Patreon subscription](https://patreon.com/ni55an){rel="nofollow"} to support its continued development. *Thank you for your valuable contribution to the project! If you have any questions or recommendations, please feel free to contact us at * # API Explore the packages listed below to access their API documentation ## Core ::api-overview{filter="rete"} :: ## Processing ::api-overview{filter="rete-engine,rete-structures"} :: ## Area ::api-overview{filter="rete-area-plugin,rete-area-3d-plugin"} :: ## Interaction ::api-overview --- filter: rete-connection-plugin,rete-context-menu-plugin,rete-readonly-plugin,rete-scopes-plugin,rete-dock-plugin,rete-history-plugin --- :: ## Integration ::api-overview --- filter: rete-react-plugin,rete-vue-plugin,rete-angular-plugin,rete-svelte-plugin,@retejs/lit-plugin --- :: ## Visualization ::api-overview --- filter: rete-auto-arrange-plugin,rete-minimap-plugin,rete-render-utils,rete-comment-plugin,rete-connection-path-plugin,rete-connection-reroute-plugin --- :: # FAQ ::questions :::qa{#own-plugin question="How to develop my own plugin?"} Check out the [instructions](https://retejs.org/docs/development#create-plugin) for Rete Kit ::: :::qa --- id: server-side question: Can the graph be processed on the server-side, such as Node.js? --- Yes, follow the [Dataflow](https://retejs.org/docs/guides/processing/dataflow) or [Control flow](https://retejs.org/docs/guides/processing/control-flow) guides ::: :::qa --- id: processing-on-different-langs question: Can the graph be processed using Python or other server-side programming languages? --- The engine only supports JS runtime. You can either bind JS into your environment or implement your own engine, similar to `rete-engine` (it has a straightforward implementation) ::: :::qa --- id: use-for-commercial question: Can the framework be used for commercial purposes? --- In short, yes. Refer to the [Licensing](https://retejs.org/docs/licensing) for details ::: :::qa --- id: no-typescript question: Can I use the framework if I don't know TypeScript? --- It can be used in JS code, but not recommended because of poor DX ::: :::qa{#save-to-json question="Is there a way to save a graph as a JSON file?"} Yes, follow the [Import/export](https://retejs.org/docs/guides/import-export) guide ::: :::qa{#scroll-prevented question="How to disable scroll prevention?"} Replace zoom handler with `null` or use a custom one ```ts const area = new AreaPlugin(container) area.area.setZoomHandler(null) ``` ::: :::qa{#dynamic-zoom question="How to enable/disable zoom dynamically?"} Replace the zoom handler with `null` on some event and restore it on opposite (e.g. Ctrl press/release) ```ts import { Zoom } from 'rete-area-plugin' // call on init area.area.setZoomHandler(null) // call on Ctrl press area.area.setZoomHandler(new Zoom(0.1)) // call on Ctrl release area.area.setZoomHandler(null) ``` ::: :::qa --- id: pan-middle-mouse-button question: How to pan the area using the middle mouse button? --- Replace the drag handler for a specific area by calling `setDragHandler` ```ts import { Drag } from 'rete-area-plugin'; area.area.setDragHandler(new Drag({ down: e => { if (e.pointerType === 'mouse' && e.button !== 1) return false e.preventDefault() return true }, move: () => true })) ``` ::: :::qa{question="How can I retrieve the position of a node?"} The position is stored within a `NodeView` instance. ```ts const view = area.nodeViews.get(nodeId) if (view) { view.position // { x, y } } ``` Keep in mind that the `NodeView` instance may not exist, for example if it hasn't yet been added. In such cases, it's preferable to handle this gracefully (throw an exception only when necessary) ::: :::qa{question="How can I translate a node/change the position of a node?"} ```ts area.translate(nodeId, { x: 0, y: 0 }) ``` ::: :::qa --- id: arrange-nodes question: How can I automatically arrange the position of nodes? --- Follow the [Arrange nodes](https://retejs.org/docs/guides/arrange) guide ::: :::qa{#force-update question="How to force an update on nodes or controls?"} Call the following methods for the corresponding nodes or controls after making changes to the state ```ts const area = new AreaPlugin(container) area.update('node', node.id) area.update("control", control.id); ``` ::: :::qa --- id: various-render-plugins question: How to render various nodes or controls using Angular, React, and Vue in one editor? --- Check out the [Combining render plugins](https://retejs.org/docs/concepts/integration#combine) in the [Integration](https://retejs.org/docs/concepts/integration) article ::: :::qa --- id: add-custom-elements-to-area question: How can I add my own elements to the editor area? --- Use the methods of `content` property of the area plugin ```ts const area = new AreaPlugin(container) area.content.add(element) area.content.remove(element) ``` It's also possible to make this element draggable ```ts const dragHandler = new Drag() dragHandler.initialize(element, { /* getters */ }, { /* events */ }) ``` ::: :::qa{#customize-node question="How to customize nodes?"} Check out the relevant customization guide for [React.js](https://retejs.org/docs/guides/renderers/react#customization), [Vue.js](https://retejs.org/docs/guides/renderers/vue#customization), [Angular](https://retejs.org/docs/guides/renderers/angular#customization), [Svelte](https://retejs.org/docs/guides/renderers/svelte#customization) or [Lit](https://retejs.org/docs/guides/renderers/lit#customization). ::: :::qa --- id: collapse-node question: How to collapse the node (to minimize a node's size by hiding its controls)? --- Define how elements should be hidden when the node is collapsed by creating a custom node component. Check out the relevant customization guide for [React.js](https://retejs.org/docs/guides/renderers/react#customization), [Vue.js](https://retejs.org/docs/guides/renderers/vue#customization), [Angular](https://retejs.org/docs/guides/renderers/angular#customization), [Svelte](https://retejs.org/docs/guides/renderers/svelte#customization) or [Lit](https://retejs.org/docs/guides/renderers/lit#customization). ::: :::qa --- id: new-render-plugins question: What are the steps to implement render plugin for other framework? --- - [Setup a plugin](https://retejs.org/docs/development/#create-plugin) - Use the source code of the following packages as a reference: [React.js](https://github.com/retejs/react-plugin){rel="nofollow"}, [Vue.js](https://github.com/retejs/vue-plugin){rel="nofollow"}, [Angular](https://github.com/retejs/angular-plugin){rel="nofollow"}, [Svelte](https://github.com/retejs/svelte-plugin){rel="nofollow"} or [Lit](https://github.com/retejs/lit-plugin){rel="nofollow"} ::: :::qa --- id: prevent-node-movement-on-control question: Is there way to prevent nodes from being moved while interacting with controls? --- You need to stop propagation of the `pointerdown` event. Check the relevant render plugin guide for controls: [React.js](https://retejs.org/guides/renderers/react#controls), [Vue.js](https://retejs.org/guides/renderers/vue#controls), [Angular](https://retejs.org/guides/renderers/angular#controls), [Svelte](https://retejs.org/guides/renderers/svelte#controls), [Lit](https://retejs.org/guides/renderers/lit#controls) ::: :::qa --- id: click-event-doesnt-work-on-control question: Why doesn't the control capture click/pointer events? --- By default, the area captures these events, so you need to stop the propagation of `pointerdown` event to prevent this Check the relevant render plugin guide for controls: [React.js](https://retejs.org/guides/renderers/react#controls), [Vue.js](https://retejs.org/guides/renderers/vue#controls), [Angular](https://retejs.org/guides/renderers/angular#controls), [Svelte](https://retejs.org/guides/renderers/svelte#controls), [Lit](https://retejs.org/guides/renderers/lit#controls) ::: :::qa --- id: user-select question: Is there a way to make the text within a node selectable? --- By default, a node is configured with the CSS property `user-select: none` to prevent conflict between text selection and node dragging. Therefore, to enable text selection within a custom node component, you need to specify `user-select: all` property for the desired element within the node. Additionally, ensure you call `e.stopPropagation()` on the \`pointerdown\`\` event to prevent text selection interruptions while dragging. ::: :::qa{#nodepicked question="How to detect click on the node?"} Whenever a user clicks on the node, the `nodepicked` event is fired: ```ts area.addPipe(context => { if (context.type === 'nodepicked') { const node = editor.getNode(context.data.id) } return context }) ``` In case you need to track not only click, but also node selections, you have the option [to extend the selector](https://retejs.org/docs/guides/selectable#extend-selector) to observe selected elements (not just nodes) within the editor ::: :::qa --- id: add-inputs-outputs-controls-dynamically question: How to add inputs, outputs or controls dynamically? --- You can add them as usual using the `addInput`/`addOutput`/`AddControl` methods, and then force a node update ```ts const area = new AreaPlugin(container) area.update('node', node.id) ``` ::: :::qa{#add-control-to-output question="How to add a control to the output?"} The process of adding such elements requires the creation of a custom node. Check out the relevant customization guide for [React.js](https://retejs.org/docs/guides/renderers/react#customization), [Vue.js](https://retejs.org/docs/guides/renderers/vue#customization), [Angular](https://retejs.org/docs/guides/renderers/angular#customization), [Svelte](https://retejs.org/docs/guides/renderers/svelte#customization) or [Lit](https://retejs.org/docs/guides/renderers/lit#customization). ::: :::qa --- id: undirected-graph question: How to create an undirected graph with nodes that don't have input/output sockets? --- You can use the classic preset with custom nodes and a unified socket for both input and output port. Check out the [Undirected](https://retejs.org/examples/undirected) example ::: :::qa --- id: vertically-oriented-editor question: How to make an editor oriented vertically? --- Check out the [Vertical flow](https://retejs.org/examples/vertical-flow) example ::: :::qa --- id: order-inputs-controls-outputs question: How to change the order of inputs/controls/outputs? --- The classic rendering preset offers the flexibility to specify an optional `index` field for inputs, outputs or controls. This feature enables you to change the order of these elements within their lists. ```ts const input = new ClassicPreset.Input(socket) const output = new ClassicPreset.Output(socket) const control = new ClassicPreset.InputControl('text') input.index = 0; output.index = 0; control.index = 0; ``` ::: :::qa --- id: align-inputs-outputs question: How to change the alignment of inputs/outputs? --- The process of changing the node layout requires the creation of a custom node. Check out the relevant customization guide for [React.js](https://retejs.org/docs/guides/renderers/react#customization), [Vue.js](https://retejs.org/docs/guides/renderers/vue#customization), [Angular](https://retejs.org/docs/guides/renderers/angular#customization), [Svelte](https://retejs.org/docs/guides/renderers/svelte#customization) or [Lit](https://retejs.org/docs/guides/renderers/lit#customization). ::: :::qa --- id: render-vanilla-js question: Is it possible to render UI using vanilla JS exclusively, without resorting to frameworks? --- In short, it is possible, but there is no plugin available for this approach since it doesn't offer significant advantages in comparison to the development costs ::: :::qa --- id: update-rete-deps question: What is the best way to update all `rete` related dependencies? --- If you don't want to update all of your dependencies at once with `npm update`, you can selectively update those that begin with `rete` using a regular expression ```bash npx npm-check-updates /^rete/ --target @latest -u ``` ::: :::qa{#loop-connections question="How to display a loop connection?"} All available rendering plugins can display a loop connection if it has specified `isLoop` property ```ts class Connection extends ClassicPreset.Connection { isLoop = false } const connection = new Connection(source, output, target, input) connection.isLoop = true ``` ::: :::qa --- id: minimum-ts-version question: What is the minimal TypeScript version required? --- The minimum required TypeScript version is 4.7. Otherwise, you might encounter the error `Type instantiation is excessively deep and possibly infinite. ts(2589)` when using the `use` method. If you can't use a later version for some reason, the only solution is to use `@ts-ignore`. For example, when creating an Angular 12 application with [Rete Kit](https://retejs.org/docs/development/rete-kit), a version 4.7 higher than the officially supported one is installed. ::: :::qa --- id: zoom-area question: How can I change the editor's zoom level programmatically? --- Using the `area.zoom` method enables you to specify the desired zoom level and the offset points for aligning the zoom ```ts await area.area.zoom(0.8, 0, 0); ``` In the given example, the zoom will be decreased with respect to the top left boundary. If you want to modify the zoom relative to the center of the viewport, refer to the following code ```ts const delta = 0.2; const { k } = area.area.transform; const box = area.container.getBoundingClientRect(); const x = box.width / 2 / k; const y = box.height / 2 / k; area.area.zoom(k * (1 - delta), x * delta, y * delta); ``` ::: :::qa{#translate-area question="How can I modify the position of the area?"} By utilizing the `area.translate` method, you can change the coordinates as follows ```ts await area.area.translate(100, 20) ``` Change in position relative to current coordinates ```ts const { x, y } = area.area.transform await area.area.translate(x + 100, y + 20) ``` Change the editor's position considering the zoom factor ```ts const { k } = area.area.transform await area.area.translate(100 * k, 20 * k) ``` ::: :::qa --- id: different-stack question: What if my app uses different stack and isn't based on React.js, Vue.js, or Angular? --- In case you're using a framework other than React.js, Vue.js, Angular, Svelte or Lit (for which Rete.js provides a rendering plugin), you have the option to utilize the [React.js plugin](https://retejs.org/docs/guides/renderers/react) to render nodes and other editor elements. For a quick start, you can create a React.js application using [Rete Kit](https://retejs.org/docs/development/rete-kit), copy the code of the editor from `src/rete/default.tsx`, install the relevant dependencies in your project and call `createEditor`, providing the HTMLElement container created by your application. ::: :::qa --- id: viewport-center question: How can I retrieve the coordinates of the viewport center? --- First of all, you need to obtain the viewport center in screen coordinates using `getBoundingClientRect`. Afterward, you should transform them into editor coordinates by applying the zoom factor `k` and offset it relative to area's position. ```ts const area = new AreaPlugin(container) const { x, y, k } = area.area.transform const box = area.container.getBoundingClientRect() const halfWidth = box.width / 2 / k const halfHeight = box.height / 2 / k return { x: halfWidth - x / k, y: halfHeight - y / k } ``` ::: :::qa --- id: sockets-warning question: What does the warning 'Found more than one element for socket with same key and side' mean? --- The warning "Found more than one element for socket with same key and side" means that there are duplicate sockets in the editor that were not properly removed after an update, or due to an asynchronous approach when unmounting components by different UI frameworks, the lifecycle of old and new ones overlaps. In the first case, when the number of these warnings constantly accumulates, you most likely have a memory leak problem because some custom node was not correctly unmounted. In the second case, the warning can be avoided by adding a delay between removing the scheme/node and mounting a new one with the same identifiers. In the worst case, this warning should not indicate memory leak issues, so you can ignore it. ::: :::qa --- id: select-deselect-event question: How to track the select or deselect event of a node? --- The framework does not have a reserved event for this. Instead, you can \[/docs/guides/selectable#extend-selector]\(extend the selector) by triggering events in the relevant methods. ::: :::qa --- id: dblclick-prevent-zoom question: How to prevent/disable zoom on dblclick? --- ```ts area.addPipe(context => { if (context.type === 'zoom' && context.data.source === 'dblclick') return return context }) ``` ::: :: # Migration ::references :::ref-external{link="https://rete.js.org" title="Rete.js v1"} ::: :: The current version of the framework contains numerous breaking changes compared to its predecessor. Let's, start by exploring the differences between v1 and v2, both from a developer's and user's point of view: | Context | v1 | v2 | Reference | | ------------------------- | ---------------------------------------------- | -------------------------------------- | --------- | | TypeScript | Partial support | TypeScript-first | | | Quick start | Codepen examples | DevKit, Codesandbox examples | | | Architecture | Event-based | Middleware-like signals | | | Tools | `rete-cli` | `rete-cli`, `rete-kit`, `rete-qa` | | | Testing | unit testing | unit + E2E testing | | | **UI** | | | | | Nodes order | fixed order | bring forward picked nodes | | | Selection | built-in for nodes only | advanced selection + custom elements | | | Controls | no built-in controls provided | built-in classic input control | | | Arrange nodes | limited | powered by `elkjs` | | | **Code** | | | | | Node creation | Component-based approach | up to you | | | Editor/Engine identifiers | mandatory, required for import/export | up to you | | | Node identifier | incremental decimal id | unique id | | | Import/export | Built-in, limited | up to you | | | Validation | Socket-based validation | up to you | | | Dataflow processing | limited (no recursion) | `DataflowEngine` with dynamic fetching | | | Control flow processing | simulated by Task plugin with limitations | `ControlFlowEngine` | | | Modules | `rete-module-plugin` | up to you | | | Connection plugin | responsible for both rendering and interaction | responsible for interaction only | | ## Connecting plugins ::side-by-side #left Connect the plugin by importing it by default import. The second parameter is used for passing the plugin's options/parameters: ```ts // v1 import HistoryPlugin from 'rete-history-plugin'; editor.use(HistoryPlugin, { keyboard: true }); ``` #right All plugins are implemented as classes and can be extended, providing flexible customization without modifying the core. ```ts // v2 import { HistoryPlugin, HistoryExtensions, Presets } from 'rete-history-plugin' const history = new HistoryPlugin() history.addPreset(Presets.classic.setup()) HistoryExtensions.keyboard(history) area.use(history) ``` :: ## Creating nodes ::side-by-side #left In the v1, nodes are generated via components that were registered within the editor, which enabled the creation of numerous instances of nodes belonging to the same Component type. ```ts // v1 class NumComponent extends Rete.Component { constructor(){ super("Number"); } builder(node) { node.addControl(new NumControl('num')) node.addOutput(new Rete.Output('num', "Number", numSocket)) return node } } const numComponent = new NumComponent() editor.register(numComponent); const node = await numComponent.createNode({ num: 2 }); ``` #right The current version doesn't include Component as an abstraction, but you can implement similar approach if needed. ```ts // v2 const node = new ClassicPreset.Node('Number') node.addControl('num', new NumControl('num')) node.addOutput('num', new ClassicPreset.Output(numSocket, "Number")); await editor.addNode(node) ``` :: ## Saving data in a node ::side-by-side #left The data can be saved using method `putData`. It is expected that the data should be in a valid JSON format, as it may be used for import/export. ```ts // v1 node.putData('myData', 'data') control.putData('myData', 'data') // where control is part of node ``` #right There are no rigid import/export guidelines to follow in the current version, which means you have complete flexibility in how you store your data in nodes. ```ts // v2 class MyNode extends ClassicPreset.Node { myData = 'data' } ``` :: ## Import/export ::side-by-side #left Because of the limitations mentioned earlier, the editor can be effortlessly exported and imported. ```ts // v1 const data = editor.toJSON(); await editor.fromJSON(data); ``` #right The current version incorporates a revised approach that requires implementation, as demonstrated in [Import/export](https://retejs.org/docs/guides/import-export). :: ## Selectable nodes ::side-by-side #left Selecting elements is a feature integrated within the editor ```ts // v1 editor.selected.list editor.selected.add(node, accumulate) ``` The downside to this implementation is its incapability to support anything other than node selection. #right The selection of nodes (and other elements) looks like: ```ts // v2 const selector = AreaExtensions.selector() const accumulating = AreaExtensions.accumulateOnCtrl() const nodeSelector = AreaExtensions.selectableNodes(area, selector, { accumulating }); editor.getNodes().filter(node => node.selected) nodeSelector.select(add.id) ``` :: ## Events listening ::side-by-side #left The typical way to listen to events that can be prevented ```ts // v1 editor.on('nodecreate', node => { return node.canCreate }); ``` \* - unchanged :br \*\* - moved to different package :br \*\*\* - removed ### `rete` package events - nodecreate \* - nodecreated \* - noderemove \* - noderemoved \* - connectioncreate \* - connectioncreated \* - connectionremove \* - connectionremoved \* - translatenode \*\*\* - nodetranslate \*\* - nodetranslated \*\* - nodedraged \*\*\* - nodedragged \*\* - selectnode \*\*\* - multiselectnode \*\*\* - nodeselect \*\*\* - nodeselected \*\*\* - rendernode \*\* (renamed to 'render') - rendersocket \*\* (renamed to 'render') - rendercontrol \*\* (renamed to 'render') - renderconnection \*\* (renamed to 'render') - updateconnection \*\*\* - keydown \*\*\* - keyup \*\*\* - translate \*\* - translated \*\* - zoom \*\* - zoomed \*\* - click \*\* (renamed to 'nodepicked') - mousemove \*\*\* (renamed to 'pointermove') - contextmenu \*\* - import \*\*\* - export \*\*\* - process \*\*\* - clear \*\* ### `rete-connection-plugin` package events - connectionpath \*\* - connectiondrop \* - connectionpick \* - resetconnection \*\*\* #right The current version uses a specific kind of signal implementation that involves object-based signals. Additionally, pipes are used to either manipulate these objects or prevent signal propagation. ```ts // v2 editor.addPipe(context => { if (context.type === 'nodecreate') return return context }) ``` ### `rete` package events - nodecreate - nodecreated - noderemove - noderemoved - connectioncreate - connectioncreated - connectionremove - connectionremoved - clear - clearcancelled - cleared ### `rete-area-plugin` package events - nodepicked - nodedragged - nodetranslate - nodetranslated - contextmenu - pointerdown - pointermove - pointerup - noderesize - noderesized - render - unmount - reordered - translate - translated - zoom - zoomed - resized ### `rete-connection-plugin` package events - connectionpick - connectiondrop ### `rete-angular-plugin` package events - connectionpath ### `rete-vue-plugin` package events - connectionpath ### `rete-react-plugin` package events - connectionpath :: ## Validate connections ::side-by-side #left There is a built-in connection validation based on socket compatibility ```ts // v1 const anyTypeSocket = new Rete.Socket('Any type'); numSocket.combineWith(anyTypeSocket); ``` This approach is simple but has some limitations. #right Connection validation can be implemented independently, that provides more flexibility. ```ts // v2 editor.addPipe(context => { if (context.type === 'connectioncreate') { if (canCreateConnection(context.data)) return false } return context }) ``` :: ## Engine (dataflow) ::side-by-side #left The component with defined `worker` method should be registered ```ts // v1 const engine = new Rete.Engine('demo@0.1.0'); engine.register(myComponent); ``` Define `worker` method of the component ```ts // v1 worker(node, inputs, outputs){ outputs['num'] = node.data.num; } ``` Trigger the processing ```ts // v1 await engine.process(data); ``` #right Create the `DataflowEngine` instance to connect to the editor. Unlike the first version, there is no need to pass `data` with nodes and connections. ```ts // v2 import { DataflowEngine } from 'rete-engine' const engine = new DataflowEngine() editor.use(engine) ``` Node method example ```ts // v2 data(inputs) { const { left, right } = inputs return { sum: left[0] + right[0] } } ``` Start the processing ```ts // v2 engine.fetch(node.id) ``` :: ## Task plugin (control flow) ::side-by-side #left This approach is implemented using the `rete-task-plugin` and based on the `Rete.Engine`. Therefore, it has the aforementioned limitations ```ts // v1 import TaskPlugin from 'rete-task-plugin'; editor.use(TaskPlugin); ``` Component's constructor has specified outputs that are intended for control flow or dataflow ```ts // v1 this.task = { outputs: { exec: 'option', data: 'output' }, init(task) { task.run('any data'); task.reset(); } } ``` Define the `worker` method, which returns data and specifies closed output ports for control flow ```ts // v1 worker(node, inputs, data) { this.closed = ['exec']; return { data } } ``` #right The `rete-engine` package is used, which has a separate implementation of the engine for control flow ```ts // v2 import { ControlFlowEngine } from 'rete-engine' const engine = new ControlFlowEngine() editor.use(engine) ``` By default, all ports are configured to pass control, but you can designate certain ones for this ```ts // v2 const engine = new ControlFlowEngine(() => { return { inputs: () => ["exec"], outputs: () => ["exec"] }; }); ``` The following serves as the node method: ```ts // v2 execute(input: 'exec', forward: (output: 'exec') => void) { forward('exec') } ``` Unlike the previous version, this approach is completely decoupled from the dataflow. Nevertheless, it can be used in conjunction with `DataflowEngine`. ```ts // v2 async execute(input: 'exec', forward: (output: 'exec') => void) { const inputs = await dataflow.fetchInputs(this.id) forward('exec') } ``` :: ## Render plugins As a demonstration, we have opted to use `rete-react-render-plugin` ::side-by-side #left ```ts // v1 import ReactRenderPlugin from 'rete-react-render-plugin'; editor.use(ReactRenderPlugin) ``` #right ```ts // v2 import { ReactPlugin } from 'rete-react-plugin' const reactPlugin = new ReactPlugin() area.use(reactPlugin) ``` :: ### Custom nodes and controls ::side-by-side #left The following code is used to specify the components needed for specific nodes and controls ```ts // v1 class AddComponent extends Rete.Component { constructor() { super("Add"); this.data.component = MyNode; } } class MyControl extends Rete.Control { constructor(emitter, key, name) { super(key); this.render = 'react'; this.component = MyReactControl; this.props = { emitter, name }; } } ``` Alternatively, component can be specified for all nodes ```ts // v1 editor.use(ReactRenderPlugin, { component: MyNode }); ``` #right In this version, the components to be visualized are defined in the classic preset that is connected ```ts // v2 reactPlugin.addPreset(ReactPresets.classic.setup({ customize: { node(data) { return MyNode }, control() { return MyReactControl } }})) ``` This approach offers greater flexibility, enabling you to define additional conditions within the handlers :: ## Translate nodes ::side-by-side #left Retrieve the view of the node and execute its `translate` method ```ts // v1 editor.view.nodes.get(node).translate(x, y) ``` #right The plugin instance contains `translate` method that only needs the node identifier. ```ts // v2 await area.translate(node.id, { x, y }) ``` :: ## Arrange nodes ::side-by-side #left The plugin offers approach for positioning nodes, but its functionality is significantly restricted. ```ts // v1 import AutoArrangePlugin from 'rete-auto-arrange-plugin'; editor.use(AutoArrangePlugin, {}); editor.trigger('arrange'); ``` #right The plugin leverages the advanced functionality of the `elkjs` package. ```ts // v2 import { AutoArrangePlugin, Presets as ArrangePresets } from "rete-auto-arrange-plugin"; const arrange = new AutoArrangePlugin(); arrange.addPreset(ArrangePresets.classic.setup()); area.use(arrange); await arrange.layout() ``` :: ## Fit viewport ::side-by-side #left The `zoomAt` method requires an editor instance that is responsible for visualization ```ts // v1 import AreaPlugin from "rete-area-plugin"; AreaPlugin.zoomAt(editor); ``` #right For visualization purposes in this version, an instance of `AreaPlugin` is required. ```ts // v2 import { AreaExtensions } from "rete-area-plugin"; AreaExtensions.zoomAt(area, editor.getNodes()); ``` :: # Overview ::examples-overview{top} :: ::examples-overview{:top='false'} :: # React.js ::references :::ref-guide{link="/docs/guides/basic" title="Basic"} ::: :::ref-guide{link="/docs/guides/renderers/react" title="Guide"} ::: :::ref-example{link="/examples/controls/react" title="Controls"} ::: :::ref-example{link="/examples/customization/react" title="Customization"} ::: :::ref-github{link="https://github.com/retejs/rete" title="Core"} ::: :::ref-github --- link: https://github.com/retejs/react-render-plugin title: Plugin --- ::: :::ref-external{link="https://react.dev/" title="React.js"} ::: :: A basic editor example featuring two connected nodes, each equipped with an input field. The rendering of nodes and connections is accomplished using `rete-react-plugin` for seamless integration with **React.js**. Users have the option to select these nodes. Node positions are manually defined according to predefined coordinates. ::kit :: ::example{#rete-js-v2-yrzxe5 module="/src/editor.ts"} :: # Vue.js ::references :::ref-guide{link="/docs/guides/renderers/vue" title="Guide"} ::: :::ref-example{link="/examples/controls/vue" title="Controls"} ::: :::ref-example{link="/examples/customization/vue" title="Customization"} ::: :::ref-github{link="https://github.com/retejs/vue-render-plugin" title="Plugin"} ::: :::ref-external{link="https://vuejs.org/" title="Vue.js"} ::: :: A basic editor example featuring two connected nodes, each equipped with an input field. The rendering of nodes and connections is accomplished using `rete-vue-plugin` for seamless integration with **Vue.js**. Users have the option to choose these nodes, causing them to move to the front. Node positions are manually defined according to predefined coordinates. ::kit :: ::example{#rete-js-v2-vue-js-578gq8 module="/src/editor.ts"} :: # Angular ::references :::ref-guide{link="/docs/guides/renderers/angular" title="Guide"} ::: :::ref-example{link="/examples/controls/angular" title="Controls"} ::: :::ref-example{link="/examples/customization/angular" title="Customization"} ::: :::ref-github --- link: https://github.com/retejs/angular-render-plugin title: Plugin --- ::: :::ref-external{link="https://angular.io/" title="Angular"} ::: :: A basic editor example featuring two connected nodes, each equipped with an input field. The rendering of nodes and connections is accomplished using `rete-angular-plugin` for seamless integration with **Angular**. Users have the option to choose these nodes, causing them to move to the front. Node positions are manually defined according to predefined coordinates. ::kit :: ::example{#rete-js-v2-angular-9htmrp module="/src/app/editor.ts"} :: # Svelte ::references :::ref-guide{link="/docs/guides/renderers/svelte" title="Guide"} ::: :::ref-example{link="/examples/controls/svelte" title="Controls"} ::: :::ref-example{link="/examples/customization/svelte" title="Customization"} ::: :::ref-github --- link: https://github.com/retejs/svelte-render-plugin title: Plugin --- ::: :::ref-external{link="https://svelte.dev/" title="Svelte"} ::: :: A basic editor example featuring two connected nodes, each equipped with an input field. The rendering of nodes and connections is accomplished using `rete-svelte-plugin` for seamless integration with **Svelte**. Users have the option to choose these nodes, causing them to move to the front. Node positions are manually defined according to predefined coordinates. ::kit :: ::example{#rete-js-svelte-s645rn module="/src/lib/editor.ts"} :: # Lit ::references :::ref-guide{link="/docs/guides/renderers/lit" title="Guide"} ::: :::ref-example{link="/examples/controls/lit" title="Controls"} ::: :::ref-example{link="/examples/customization/lit" title="Customization"} ::: :::ref-github{link="https://github.com/retejs/lit-plugin" title="Plugin"} ::: :::ref-external{link="https://lit.dev/" title="Lit"} ::: :: A basic editor example featuring two connected nodes, each equipped with an input field. The rendering of nodes and connections is accomplished using `@retejs/lit-plugin` for seamless integration with **Lit**. Users have the option to choose these nodes, causing them to move to the front. Node positions are manually defined according to predefined coordinates. ::kit :: ::example{#rete-js-v2-lit-js-vvx95j module="/src/editor.ts"} :: # Basic The following list presents examples that demonstrate the basic editor setup for available framework integrations. If you can't find an example for a specific UI library or framework, you can cast your vote for its integration in [this discussion](https://github.com/retejs/rete/discussions/635){rel="nofollow"} ::examples-overview{filter="/examples/basic"} :: # Controls for React.js ::references :::ref-guide{link="/docs/guides/renderers/react/#controls" title="Guide"} ::: :::ref-example{link="/examples/basic/react" title="Basic"} ::: :::ref-example{link="/examples/customization/react" title="Customization"} ::: :::ref-github --- link: https://github.com/retejs/react-render-plugin title: Plugin --- ::: :::ref-external{link="https://react.dev/" title="React.js"} ::: :: This example showcases the use of a built-in input control and custom controls for **React.js**, which you can implement according to your specific needs. In this case, the custom controls include a button that randomly sets the input field's value and a radial progress indicator that synchronizes with the input field's value ::kit :: ::example{#rete-js-v2-custom-controls-2vp0wx module="/src/editor.tsx"} :: # Controls for Vue.js ::references :::ref-guide{link="/docs/guides/renderers/vue/#controls" title="Guide"} ::: :::ref-example{link="/examples/basic/vue" title="Basic"} ::: :::ref-example{link="/examples/customization/vue" title="Customization"} ::: :::ref-github{link="https://github.com/retejs/vue-render-plugin" title="Plugin"} ::: :::ref-external{link="https://vuejs.org/" title="Vue.js"} ::: :: This example showcases the use of a built-in input control and custom controls for **Vue.js**, which you can implement according to your specific needs. In this case, the custom controls include a button that randomly sets the input field's value and a radial progress indicator that synchronizes with the input field's value ::kit :: ::example{#rete-js-v2-vue-js-custom-controls-qrdo2c module="/src/editor.ts"} :: # Controls for Angular ::references :::ref-guide{link="/docs/guides/renderers/angular/#controls" title="Guide"} ::: :::ref-example{link="/examples/basic/angular" title="Basic"} ::: :::ref-example{link="/examples/customization/angular" title="Customization"} ::: :::ref-github --- link: https://github.com/retejs/angular-render-plugin title: Plugin --- ::: :::ref-external{link="https://angular.io/" title="Angular"} ::: :: This example showcases the use of a built-in input control and custom controls for **Angular**, which you can implement according to your specific needs. In this case, the custom controls include a button that randomly sets the input field's value and a radial progress indicator that synchronizes with the input field's value ::kit :: ::example --- id: rete-js-v2-angular-custom-controls-cknttr module: /src/app/editor.ts --- :: # Controls for Svelte ::references :::ref-guide{link="/docs/guides/renderers/svelte/#controls" title="Guide"} ::: :::ref-example{link="/examples/basic/svelte" title="Basic"} ::: :::ref-example{link="/examples/customization/svelte" title="Customization"} ::: :::ref-github --- link: https://github.com/retejs/svelte-render-plugin title: Plugin --- ::: :::ref-external{link="https://svelte.dev/" title="Svelte"} ::: :: This example showcases the use of a built-in input control and custom controls for **Svelte**, which you can implement according to your specific needs. In this case, the custom controls include a button that randomly sets the input field's value and a radial progress indicator that synchronizes with the input field's value ::kit :: ::example{#rete-js-svelte-controls-9dm3dn module="/src/lib/editor.ts"} :: # Controls for Lit ::references :::ref-guide{link="/docs/guides/renderers/lit/#controls" title="Guide"} ::: :::ref-example{link="/examples/basic/lit" title="Basic"} ::: :::ref-example{link="/examples/customization/lit" title="Customization"} ::: :::ref-github{link="https://github.com/retejs/lit-plugin" title="Plugin"} ::: :::ref-external{link="https://lit.dev/" title="Lit"} ::: :: This example showcases the use of a built-in input control and custom controls for **Lit**, which you can implement according to your specific needs. In this case, the custom controls include a button that randomly sets the input field's value and a radial progress indicator that synchronizes with the input field's value ::kit :: ::example{#rete-js-v2-lit-js-custom-controls-3w4ypd module="/src/editor.ts"} :: # Controls The following list presents examples that demonstrate the various custom controls for available framework integrations. If you can't find an example for a specific UI library or framework, you can cast your vote for its integration in [this discussion](https://github.com/retejs/rete/discussions/635){rel="nofollow"} ::examples-overview{filter="/examples/controls"} :: # Customization for React.js ::references :::ref-guide{link="/docs/guides/renderers/react/#customization" title="Guide"} ::: :::ref-example{link="/examples/basic/react" title="Basic"} ::: :::ref-example{link="/examples/controls/react" title="Controls"} ::: :::ref-github --- link: https://github.com/retejs/react-render-plugin title: Plugin --- ::: :::ref-external{link="https://react.dev/" title="React.js"} ::: :: The following example demonstrates the implementation of custom nodes, sockets, and connections for **React.js**, allowing you to adapt them to your use cases. These elements can be dynamically configured based on the parameters of various objects (nodes, sockets, connections). Additionally, a grid-like gradient background has been added using pure JS and CSS. Custom components visualized with **React.js** can be used independently of your tech stack, even [without JSX](https://react.dev/reference/react/createElement){rel="nofollow"}. Therefore, if your UI framework lacks integration, you can render parts of the editor using **React.js**, leveraging this example. ::kit :: ::example{#rete-js-v2-custom-node-jpwdh3 module="/src/editor.ts"} :: # Customization for Vue.js ::references :::ref-guide{link="/docs/guides/renderers/vue/#customization" title="Guide"} ::: :::ref-example{link="/examples/basic/vue" title="Basic"} ::: :::ref-example{link="/examples/controls/vue" title="Controls"} ::: :::ref-github{link="https://github.com/retejs/vue-render-plugin" title="Plugin"} ::: :::ref-external{link="https://vuejs.org/" title="Vue.js"} ::: :: The following example demonstrates the implementation of custom nodes, sockets, and connections for **Vue.js**, allowing you to adapt them to your use cases. These elements can be dynamically configured based on the parameters of various objects (nodes, sockets, connections). Additionally, a grid-like gradient background has been added using pure JS and CSS. ::kit :: ::example{#rete-js-v2-vue-js-customization-ubl35h module="/src/editor.ts"} :: # Customization for Angular ::references :::ref-guide{link="/docs/guides/renderers/angular/#customization" title="Guide"} ::: :::ref-example{link="/examples/basic/angular" title="Basic"} ::: :::ref-example{link="/examples/controls/angular" title="Controls"} ::: :::ref-github --- link: https://github.com/retejs/angular-render-plugin title: Plugin --- ::: :::ref-external{link="https://angular.io/" title="Angular"} ::: :: The following example demonstrates the implementation of custom nodes, sockets, and connections for **Angular**, allowing you to adapt them to your use cases. These elements can be dynamically configured based on the parameters of various objects (nodes, sockets, connections). Additionally, a grid-like gradient background has been added using pure JS and CSS ::kit :: ::example{#rete-js-v2-angular-customization-w9umwf module="/src/app/editor.ts"} :: # Customization for Svelte ::references :::ref-guide{link="/docs/guides/renderers/svelte/#customization" title="Guide"} ::: :::ref-example{link="/examples/basic/svelte" title="Basic"} ::: :::ref-example{link="/examples/controls/svelte" title="Controls"} ::: :::ref-github --- link: https://github.com/retejs/svelte-render-plugin title: Plugin --- ::: :::ref-external{link="https://svelte.dev/" title="Svelte"} ::: :: The following example demonstrates the implementation of custom nodes, sockets, and connections for **Svelte**, allowing you to adapt them to your use cases. These elements can be dynamically configured based on the parameters of various objects (nodes, sockets, connections). Additionally, a grid-like gradient background has been added using pure JS and CSS ::kit :: ::example{#rete-js-svelte-forked-29v37z module="/src/editor.ts"} :: # Customization for Lit ::references :::ref-guide{link="/docs/guides/renderers/lit/#customization" title="Guide"} ::: :::ref-example{link="/examples/basic/lit" title="Basic"} ::: :::ref-example{link="/examples/controls/lit" title="Controls"} ::: :::ref-github{link="https://github.com/retejs/lit-plugin" title="Plugin"} ::: :::ref-external{link="https://lit.dev/" title="Lit"} ::: :: The following example demonstrates the implementation of custom nodes, sockets, and connections for **Lit**, allowing you to adapt them to your use cases. These elements can be dynamically configured based on the parameters of various objects (nodes, sockets, connections). Additionally, a grid-like gradient background has been added using pure JS and CSS ::kit :: ::example{#rete-js-v2-lit-js-customization-gw7gzk module="/src/editor.ts"} :: # Customization The following list presents examples that demonstrate the custom nodes for available framework integrations. If you can't find an example for a specific UI library or framework, you can cast your vote for its integration in [this discussion](https://github.com/retejs/rete/discussions/635){rel="nofollow"} ::examples-overview{filter="/examples/customization"} :: # Performance ::references :::ref-guide{link="/docs/guides/basic" title="Guide"} ::: :::ref-example{link="/examples/lod" title="LOD"} ::: :::ref-example{link="/examples/lod-gpu" title="LOD GPU"} ::: :: Within this example, you can set the number of nodes to be displayed and assess the performance during their rendering. Given that visualizing a large number of elements on a page always comes with performance issues, you can explore examples such as [LOD](https://retejs.org/examples/lod) and [LOD GPU](https://retejs.org/examples/lod-gpu) using specialized approaches to mitigate these limitations ::kit :: ::example{#rete-js-v2-performance-cnbfc0 module="/src/editor.ts"} :: # Vertical flow The given example illustrates a vertically oriented editor. It has been achieved by making subtle changes to nodes and connections, specifically: - using a custom node component with modified structure and some styles. - adjusting connection junction points with sockets. - changing the connection path to a vertical orientation. - modifying port placement points in the arrangement. ::pro-example{src="https://retejs-vertical-flow.netlify.app"} :: The actual example is built using React.js, but around 75% of the listed changes are stack-agnostic. This means that to utilize them in Angular, Vue.js, Svelte, or Lit, you'll need to create a corresponding custom component. If you encounter any issues with it, don't hesitate to reach out via GitHub Issues in the relevant repository. # Area extensions ::references :::ref-guide{link="/docs/guides/basic" title="Basic"} ::: :::ref-guide{link="/docs/guides/selectable" title="Selectable"} ::: :::ref-example{link="/examples/viewport-bound" title="Viewport-bound"} ::: :::ref-github{link="https://github.com/retejs/area-plugin" title="Area Plugin"} ::: :: This example showcases the use of built-in extensions provided by `rete-area-plugin`, such as: - `selectableNodes`: enabling users to select nodes - `restrictor`: restricting zoom and pan areas within the area - `snapGrid`: snapping node positions to a grid ::kit :: ::example{#rete-js-v2-area-extensions-rhskwv module="/src/editor.ts"} :: # Dataflow ::references :::ref-guide{link="/docs/guides/processing/dataflow" title="Guide"} ::: :::ref-example{link="/examples/processing/hybrid-engine" title="Hybrid"} ::: :::ref-example{link="/examples/3d-configurator" title="3D Configurator"} ::: :::ref-example{link="/examples/allmatter" title="Allmatter"} ::: :::ref-github{link="https://github.com/retejs/engine" title="Plugin"} ::: :: This example showcases a data processing pipeline using `rete-engine`, where data flows from left to right through nodes. Each node features a `data` method, which receives arrays of incoming data from their respective input sockets and delivers an object containing data corresponding to the output sockets. To initiate their execution, you can make use of the `engine.fetch` method by specifying the identifier of the target node. Consequently, the engine will execute all predecessors recursively, extracting their output data and delivering it to the specified node. ::kit :: ::example{#rete-js-v2-dataflow-engine-tyhr1e module="/src/editor.ts"} :: # Control flow ::references :::ref-guide{link="/docs/guides/processing/control-flow" title="Guide"} ::: :::ref-example{link="/examples/processing/hybrid-engine" title="Hybrid"} ::: :::ref-github{link="https://github.com/retejs/engine" title="Plugin"} ::: :: This example showcases an executing of schema via control flow using `rete-engine`, where each node dynamically decides which of its outgoing nodes will receive control. Each node features an `execute` method that takes an input port key as a control source, and a function for conveying control to outgoing nodes through a defined output port. To initiate the execution of the flow, you can use `engine.execute` method, specifying the identifier of the starting node. Consequently, the outgoing nodes will be executed sequentially, starting from the designated node. ::kit :: ::example{#rete-js-v2-control-flow-engine-yqi8z7 module="/src/editor.ts"} :: # Hybrid Engine ::references :::ref-guide{link="/docs/guides/processing/hybrid" title="Guide"} ::: :::ref-example{link="/examples/processing/dataflow" title="Dataflow"} ::: :::ref-example{link="/examples/processing/control-flow" title="Control flow"} ::: :::ref-example{link="/examples/chatbot" title="Chatbot"} ::: :::ref-github{link="https://github.com/retejs/engine" title="Plugin"} ::: :: This example shows how `rete-engine` allows for the simultaneous integration of both dataflow and control flow. Consequently, certain nodes serve as data sources, others manage the flow, and a third set incorporates both of these approaches. ::kit :: ::example{#rete-js-v2-hybrid-engine-erkdtu module="/src/editor.ts"} :: # Undirected ::references :::ref-example{link="/examples/customization/react" title="Customization"} ::: :::ref-example --- link: /examples/selectable-connections title: Selectable connections --- ::: :::ref-example{link="/examples/connection-path" title="Connection path"} ::: :: This example demonstrates several essential features: - Round nodes - Selectable connections - Unified port for connections - Removal of connections with a designated button located in the middle of the path - Connections represented by straight lines and loops with markers at the end ::pro-example{src="https://retejs-undirected.netlify.app"} :: The classic preset is used for rendering, which involves splitting into input and output ports, but in this case, a trick is employed to pack them into a single unified socket, represented by a transparent circle slightly larger in radius than its node. Custom components are used to represent the nodes and connections. The `rete-connection-path-plugin` plugin is used for setting up connection path and marker. Instead of `getDOMSocketPosition`, a custom socket position calculator is used to ensure that the beginning and end of the connection coincide with the node's border. Approximately 50% of the example's source code relies on the stack and is available for React.js and Angular. If you're using a different stack, you can migrate the necessary components by referencing the source code or request their implementation through GitHub Issues in the appropriate repository. # Arrange nodes ::references :::ref-guide{link="/docs/guides/arrange" title="Guide"} ::: :::ref-example{link="/examples/sankey" title="Sankey diagram"} ::: :::ref-external{link="https://github.com/kieler/elkjs" title="Elk.js"} ::: :: Automated node arrangement, or commonly referred to as layouting, enables the positioning of nodes in relation to each other, considering their connections. This is achieved through a specialized plugin that utilizes [elk.js](https://github.com/kieler/elkjs){rel="nofollow"} to calculate node positions. Additionally, it offers extensive customization capabilities, allowing users to configure node placement through various options and an applier to assign the calculated positions to nodes (e.g incorporating animations). ::kit :: ::example{#rete-js-v2-arrange-layout-nodes-ri75lp module="/src/editor.ts"} :: # Insert node In this scenario, the user can insert the node into connection between other nodes. The implementation replaces the connection with two new connections when the selected node is dropped onto the connection. After adding the connection, the graph is arranged with animation. ::kit :: ::example{#rete-js-v2-insert-node-givohx module="/src/editor.ts"} :: Use the `insert-node` directory and call `insertableNodes` with proper arguments to integrate this feature into your application. # Magnetic connection The given example showcases a technique to enhance UX by enlarging the connection dropping area. This means that users do not have to aim directly at the socket to make a connection, particularly when the socket is small or the zoom level is low. ::kit :: ::example{#rete-js-v2-magnetic-connection-078ild module="/src/editor.ts"} :: In order to implement this strategy in your project, just copy the `magnetic-connection` directory and the corresponding `useMagneticConnection` function call in the `editor.ts` module. Depending on the rendering plugin you are using, you can either use the existing component from the directory or create your own. # Smooth zoom ::references :::ref-guide{link="/docs/api/rete-area-plugin#zoom" title="API"} ::: :: The editor comes with instant zoom enabled by default, with a step equivalent to one scroll wheel movement. If you require customization of this behavior, you can extend the `Zoom` class ::kit :: ::example{#rete-js-v2-smooth-zoom-6sqv8j module="/src/editor.ts"} :: Within this example, the core logic is contained in `src/zoom.ts`. To integrate it into the editor, just include the following line: ```ts area.area.setZoomHandler(new SmoothZoom(0.5, 200, "cubicBezier(.45,.91,.49,.98)", area)); ``` # Context menu ::references :::ref-guide{link="/docs/guides/context-menu" title="Guide"} ::: :::ref-github --- link: https://github.com/retejs/context-menu-plugin title: Plugin --- ::: :: This example demonstrates the use of a context menu presented as a separate plugin. It allows for the creation of a common context menu with a list of nodes to be created (including nested sub-items), as well as individual context menus for nodes with options for deletion and copying. The provided functionality relies on a classic preset, but for enhanced customization, it can be substituted to facilitate more dynamic menu item generation. ::kit :: ::example{#rete-js-v2-context-menu-jcelzo module="/src/editor.ts"} :: # Panning boundary ::references :::ref-github{link="https://github.com/retejs/area-plugin" title="Plugin"} ::: :::ref-example{link="/examples/viewport-bound" title="Viewport-bound nodes"} ::: :: This example demonstrates the implementation of panning the area when the cursor with a dragged node is approaches the editor's boundary. Depending on the distance to any of the area's boundaries, starting from a certain threshold, the panning speed will be inversely proportional to the distance to the boundary. ::kit :: ::example{#rete-js-v2-panning-boundary-4zvf6t module="/src/editor.ts"} :: Although this example was originally designed for React.js, the showcased feature is completely framework-agnostic. You can seamlessly integrate it into your application on a different stack by copying the feature module and connecting it into your editor. # Readonly ::references :::ref-guide{link="/docs/guides/readonly" title="Guide"} ::: :::ref-github{link="https://github.com/retejs/readonly-plugin" title="Plugin"} ::: :: The provided example showcases a simple plugin known as `rete-readonly-plugin`. When activated, this plugin prevents the addition of nodes and connections to the editor's core, as well as the translation of nodes within the area. On the other hand, the plugin intentionally lacks various customization options to keep it simple, allowing you to explore its source code and create your own solutions tailored to your specific use case (e.g. restricting the addition of specific node types only). ::kit :: ::example{#rete-js-v2-readonly-4pi274 module="/src/editor.ts"} :: # Modules ::references :::ref-guide{link="/docs/guides/modules" title="Guide"} ::: :::ref-example{link="/examples/allmatter" title="Allmatter"} ::: :: This example showcases a schema reusability technique, where processing is carried out using `DataflowEngine`. This is accomplished by creating a dedicated `Module` node that loads a nested schema containing `Input` and `Output` nodes, subsequently generating corresponding sockets. As a result, the module node initializes the engine, feeds it with input data, executes it, and retrieves the output data. ::kit :: ::example{#rete-js-v2-modules-vhr0h5 module="/src/editor.ts"} :: # Scopes ::references :::ref-guide{link="/docs/guides/scopes" title="Guide"} ::: :::ref-github{link="https://github.com/retejs/scopes-plugin" title="Plugin"} ::: :: The structures shown in this example may also be referred to as subgraphs or nested nodes. This functionality is achieved using the advanced `rete-scopes-plugin` plugin. Changing a node's parent is easy: simply long-press the node and move it over the new parent node. ::kit :: ::example{#rete-js-v2-scopes-grgie8 module="/src/editor.ts"} :: # Selectable connections ::references :::ref-guide{link="/docs/guides/selectable/connections" title="Guide"} ::: :::ref-github{link="https://github.com/retejs/area-plugin" title="Area plugin"} ::: :: The editor doesn't offer a built-in connection selection feature. However, if you're using `BidirectFlow` and can't delete connections from UI, or you need to select connections for other purposes, you can create a custom connection and sync it with `AreaExtensions.selector` ::kit :: ::example{#rete-js-v2-selectable-connections-cfetvh module="/src/editor.tsx"} :: # Labeled connections ::references :::ref-guide --- link: /docs/guides/renderers/react#full-connection-customization title: Guide --- ::: :: You can add labels to connections by implementing a custom connection component. This approach provides you with full flexibility to customize and optimize this feature. All you need is: - create a custom connection component - calculate the position of the point relative to the path (start, middle, end) - display a text block at this position - create user-added connections with predefined labels ::pro-example{src="https://retejs-labeled-connections.netlify.app"} :: Although the example is built on React.js, you can easily adapt it for a different stack. To do so, simply copy certain modules from this example and substitute the custom connection component with one that matches your stack. If you encounter any issues with it, don't hesitate to reach out via GitHub Issues in the relevant repository. # Lasso/marquee selection ::references :::ref-guide{link="/docs/guides/selectable" title="Guide"} ::: :::ref-github{link="https://github.com/retejs/area-plugin" title="Area plugin"} ::: :: Node selection can be done not only through clicking but also by utilizing a flexible selector system, enabling you to expand it and select nodes by drawing a lasso or rectangle, for instance. The nodes that intersect with the drawn shape will be selected. In this particular example, you have the option to switch between a selection button, a highlighting shape and an intersecting area of the node. ::pro-example{src="https://retejs-lasso-marquee.netlify.app"} :: Although this example was originally designed for React.js, the showcased feature is completely framework-agnostic. You can seamlessly integrate it into your application on a different stack by copying the feature module and connecting it into your editor. # Minimap ::references :::ref-guide{link="/docs/guides/minimap" title="Guide"} ::: :::ref-github{link="https://github.com/retejs/minimap-plugin" title="Plugin"} ::: :: By default, a minimap is placed in the bottom-right corner and features rectangles that represent the positions of nodes and the viewport. All nodes added in the editor are dynamically displayed on the minimap, ensuring that they are all within the minimap's boundaries. Additionally, you can change the viewport's position by dragging its mini- viewport. ::kit :: ::example{#rete-js-v2-minimap-ob9uqc module="/src/editor.ts"} :: # Dock menu ::references :::ref-guide{link="/docs/guides/dock-menu" title="Guide"} ::: :::ref-github{link="https://github.com/retejs/dock-plugin" title="Plugin"} ::: :: Dock menu is a section with a list of nodes that users can add to the editor by clicking or dragging. In the default preset, this menu is placed at the bottom of the editor, but you can create your own preset with custom styles. Furthermore, you can dynamically add or remove nodes from this list, configuring them with the desired properties and specifying their order ::kit :: ::example{#rete-js-v2-forked-t2enm0 module="/src/editor.ts"} :: # Sankey diagram ::references :::ref-guide{link="/examples/customization/react" title="Customization"} ::: :::ref-example{link="/examples/arrange" title="Arrange"} ::: :: The Sankey diagram relies on the node editor's classic preset and includes custom components for nodes, connections, and sockets. Nodes are given a predefined capacity (equal to height), and ports are generated for each connection with a height (weight) equal to the connection's weight. ::kit :: ::example{#rete-js-v2-sankey-diagram-v75qmr module="/src/editor.ts"} :: Implementing this feature in your project is easy - just copy the `sankey` directory and some code from `editor.ts`, including the `Presets.classic.setup` options and `importGraph`. Additionally, you can restrict node movement with `createSankeyRestrictor` to keep source nodes on the left of target nodes. Finally, add an option to `arrange.layout` to increase the distance between nodes. This example was specifically developed for **React.js**, but you can adapt it for use in other rendering plugins by adapting the custom components to your stack. # Connection path ::references :::ref-guide{link="/docs/guides/connection-path" title="Guide"} ::: :::ref-github --- link: https://github.com/retejs/connection-path-plugin title: Plugin --- ::: :: This example showcases the usage of a plugin for configuring connection paths. It facilitates the customization of both the paths (linear, step, monotone) and the points they traverse. Additionally, there's an option to include an arrow-shaped marker. ::kit :: ::example{#rete-js-v2-connection-path-5dneyc module="/src/editor.ts"} :: # Reroute ::references :::ref-guide{link="/docs/guides/reroute" title="Guide"} ::: :::ref-github --- link: https://github.com/retejs/connection-reroute-plugin title: Plugin --- ::: :: This particular example shows the usage of a plugin designed for user-controlled connection rerouting. Users can insert rerouting points by clicking on a connection or remove them by right-clicking. These points can be dragged or selected by users (similarly to nodes) to move multiple points at once. ::kit :: ::example{#rete-js-v2-reroute-8doi0c module="/src/editor.ts"} :: # Viewport-bound nodes ::references :::ref-example{link="/examples/area-extensions" title="Area extensions"} ::: :::ref-github{link="https://github.com/retejs/area-plugin" title="Area plugin"} ::: :::ref-example{link="/examples/panning-boundary" title="Panning boundary"} ::: :: This example illustrates the process of handling area coordinates, performing conversions between different coordinate systems, and harnessing a versatile event system to restrict node positions. ::pro-example{src="https://retejs-viewport-bound.netlify.app"} :: Although this example was originally designed for React.js, the showcased feature is completely framework-agnostic. You can seamlessly integrate it into your application on a different stack by copying the feature module and connecting it into your editor. # Comments ::references :::ref-guide{link="/docs/guides/comments" title="Guide"} ::: :::ref-github{link="https://github.com/retejs/comment-plugin" title="Plugin"} ::: :: This example demonstrates the use of the comments plugin. There are two types of comments: frames (for grouping nodes) and inline (for individual nodes). Users can add nodes to frame comments by drag and drop, while inline comments can be attached in a reverse manner by dragging and dropping them onto a node. ::kit :: ::example{#rete-js-v2-comments-ql406e module="/src/editor.ts"} :: # History ::references :::ref-guide{link="/docs/guides/undo-redo" title="Guide"} ::: :::ref-github{link="https://github.com/retejs/history-plugin" title="Plugin"} ::: :: This example showcases the use of the history plugin. It tracks changes in the editor, such as adding, deleting, or moving nodes, as well as adding or removing connections. These changes can be undone or redone programmatically or by the user (for instance, using keyboard shortcuts like Ctrl+Z/Ctrl+Y). All undo/redo operations are automatically grouped by time (default is 200 ms). So, if a sequence of operations occurs within a short time frame, the user can undo or redo them with a single keypress. ::kit :: ::example{#rete-js-v2-history-gwl0cy module="/src/editor.ts"} :: # LOD ::references :::ref-guide{link="/docs/best-practices/performance" title="Performance"} ::: :::ref-example{link="/examples/lod-gpu" title="LOD GPU"} ::: :: This example demonstrates the use of LOD (level of detail) to simplify nodes by replacing them with simplified versions at certain level of zoom. Nodes outside the viewport are also excluded from rendering. These methods enhance the performance of editors with a large number of nodes ::pro-example{src="https://retejs-lod.netlify.app"} :: Although this example was originally designed for React.js, the showcased feature is completely framework-agnostic. You can seamlessly integrate it into your application on a different stack by copying the feature module and connecting it into your editor. # LOD GPU ::references :::ref-guide{link="/docs/best-practices/performance" title="Performance"} ::: :::ref-example{link="/examples/lod" title="LOD"} ::: :::ref-external{link="https://pixijs.com" title="Pixi.js"} ::: :: This example represents an improved version of [the LOD example](https://retejs.org/examples/lod), allowing the visualization of thousands of nodes within a single editor. The improvement lies in rendering simplified versions of nodes and connections on the canvas, utilizing the GPU through [Pixi.js](https://pixijs.com){rel="nofollow"}, whenever rendering the original node becomes unreasonable due to its small on-screen size. Once the user zooms in, the original node is rendered at an appropriate level of detail, enabling user interaction. ::pro-example{src="https://retejs-lod-gpu.netlify.app"} :: Although this example was originally designed for React.js, the showcased feature is completely framework-agnostic. You can seamlessly integrate it into your application on a different stack by copying the feature module and connecting it into your editor. # 3D Configurator ::references :::ref-example{link="/examples/processing/dataflow" title="Dataflow"} ::: :: This example showcases the use of an editor that lets you combine nodes to create unique color blends and apply them to a car's body and wheels. The editor uses `DataflowEngine` for node processing. Initial nodes are loaded from a JSON file through a custom importer implemented for this editor. ::kit :: ::example{#rete-js-2-3d-configurator-43o7ex module="/src/node-editor/index.js"} :: # Chatbot ::references :::ref-guide{link="/docs/guides/validation" title="Validation"} ::: :::ref-example{link="/examples/customization/react" title="Customization"} ::: :::ref-example{link="/examples/processing/hybrid-engine" title="Hybrid"} ::: :: This is a simple demonstration of a chatbot whose behavior is programmed using a visual editor. In this implementation, all elements are customized, including animated connections. The chat window is presented as a separate node that can be moved only using special handles at the bottom and top. A dataflow and control flow-based engine is used to process the graph. There is also validation of connections by checking socket compatibility. ::kit :: ::example{#rete-js-v2-chatbot-programming-nok9iq module="/src/editor.ts"} :: `DataflowEngine` and `ControlFlowEngine` are being used here, just like in the [Hybrid engine](https://retejs.org/examples/processing/hybrid-engine) example. Validation of connections carried out by extending `ClassicPreset.Socket` and including a corresponding method that takes in another socket as an argument and checks for compatibility. If the sockets are incompatible, a message is displayed and the connection is not added. # 3D ::references :::ref-guide{link="/docs/guides/3d" title="Guide"} ::: :::ref-example{link="/examples/3d/multiple-3d" title="Multiple editors"} ::: :::ref-external{link="https://threejs.org/" title="Three.js"} ::: :::ref-github{link="https://github.com/retejs/area-3d-plugin" title="Plugin"} ::: :: With the [`rete-area-3d-plugin`](https://github.com/retejs/area-3d-plugin){rel="nofollow"} plugin, the native HTML editor can be seamlessly integrated into 3D scene without compromising interactive capabilities with nodes and other editor components. Based on **Three.js**, this scene uses `Sky` and `Water` modules (can be found in [the official example](https://threejs.org/examples/webgl_shaders_ocean.html){rel="nofollow"}) to showcase the visual effect of integrated nodes and connections on a 3D environment. ::kit :: ::example{#rete-js-v2-3d-vrnzly module="/src/editor.ts"} :: # Multiple 3D editors ::references :::ref-guide{link="/docs/guides/3d/#multiple-editors" title="Guide"} ::: :::ref-example{link="/examples/3d" title="3D"} ::: :::ref-external{link="https://threejs.org/" title="Three.js"} ::: :::ref-github{link="https://github.com/retejs/area-3d-plugin" title="Plugin"} ::: :: This editor showcases one of the 3D features mentioned in [the guide](https://retejs.org/docs/guides/3d#multiple-editors), specifically the capability of the `rete-area-3d-plugin` to integrate multiple editors within a single scene. There are two types of editors available here: the materials editor (on the left) and the geometry editor (on the right). They are quite simple and designed for customizing the frame for the painting. Each editor has its own contextual menu. You can navigate the scene in two ways: through pointer capture mode or orbital control mode, which also allows you to edit schemes. ::pro-example{src="https://retejs-multiple-3d.netlify.app"} :: Although this example was originally designed for React.js, the showcased feature is completely framework-agnostic. You can seamlessly integrate it into your application on a different stack by copying the feature module and connecting it into your editor. # 3D material authoring tool ::references :::ref-guide{link="/docs/guides/modules" title="Modules"} ::: :::ref-guide{link="/docs/guides/processing/dataflow" title="Dataflow"} ::: :::ref-example{link="/examples/controls/react" title="Controls"} ::: :: This project serves as a Rete.js demonstration ๐Ÿ” Additionally, you can refer to its source code when migrating your projects from v1 ::frame-example{src="https://ni55an.github.io/allmatter"} :: # Code generation ::references :::ref-external{link="https://studio.retejs.org/" title="Rete Studio"} ::: :: This example showcases the embedding of Rete Studio's Playground, enabling you to input JavaScript code and check its graph representation, which can also be transformed into JavaScript code. ::frame-example --- src: https://studio.retejs.org/playground/headless?language=javascript --- ::