Skip to content

fibo/flow-view

Repository files navigation

flow-view

is a visual editor for Dataflow programming

Demo
flow view Simpsons example

Installation

Using npm

With npm do

npm install flow-view

Using a CDN

Try this in your HTML page

<script type="module">
  import { FlowView } from "https://unpkg.com/flow-view"

  const flowView = new FlowView(document.body)
</script>

Usage

GUI

Try demo here

  • Drag on canvas to translate all items.
  • Click on item to select it.
  • Click while pressing SHIFT to enable multi selection.
  • Drag selected items to translate them.
  • Drag from a node output to a node input to create an edge.
  • Press BACKSPACE to delete selected items.
  • Double click on edge to delete it.
  • Double click on canvas to open the selector.
  • Type into the selector then press ENTER to create a new node.

Constructor

Create a FlowView instance and pass it a container. It will create a flow-view custom element and attach it to the container. Be aware that the flow-view custom element will fit the whole height of its container, so make sure to style properly to avoid a zero height container.

<!doctype html>
<html>
  <body>
    <script type="module">
      import { FlowView } from "https://unpkg.com/flow-view"

      const flowView = new FlowView(document.body)
    </script>
  </body>
</html>

If some flow-view custom element is already in the page, it can be passed to the FlowView constructor. argument.

<!doctype html>
<html>
  <body>
    <flow-view id="my-view"></flow-view>

    <script type="module">
      import { FlowView } from "https://unpkg.com/flow-view"

      const flowView = new FlowView(document.getElementById("my-view"))
    </script>
  </body>
</html>

Color schemes

Optionally set color scheme. If not provided it defaults to both light and dark according to system preferences.

Light scheme.

<flow-view light></flow-view>

Dark scheme.

<flow-view dark></flow-view>

See also color schemes example.

addNodeDefinitions({ nodes?, types? })

Add a list to define which nodes are available. It is not required but it makes sense to be provided in the majority of use cases.

flowView.addNodeDefinitions({
  nodes: [
    { name: "Marge", type: "parent" },
    { name: "Homer", type: "parent" },
    { name: "Bart", type: "child" },
    { name: "Lisa", type: "child" },
    { name: "Mr. Burns" }
  ],
  types: {
    parent: {
      inputs: [],
      outputs: [{ name: "out" }]
    },
    child: {
      inputs: [{ name: "in1" }, { name: "in2" }],
      outputs: []
    }
  }
})

node(id)

Get flow-view node by id.

const node = flowView.node("abc")

edge(id)

Get flow-view edge by id.

const edge = flowView.edge("abc")

graph

Access current flow-view graph.

console.log(flowView.graph)

loadGraph({ nodes = [], edges = [] })

Load a flow-view graph.

flowView.loadGraph({
  nodes: [
    {
      id: "dad",
      text: "Homer",
      x: 60,
      y: 70,
      outs: [{ id: "children" }]
    },
    {
      id: "mom",
      text: "Marge",
      x: 160,
      y: 70,
      outs: [{ id: "children" }]
    },
    {
      id: "son",
      text: "Bart",
      x: 60,
      y: 240,
      ins: [{ id: "father" }, { id: "mother" }]
    },
    {
      id: "daughter",
      text: "Lisa",
      x: 220,
      y: 220,
      ins: [{ id: "father" }, { id: "mother" }]
    }
  ],
  edges: [
    { from: ["dad", "children"], to: ["son", "father"] },
    { from: ["dad", "children"], to: ["daughter", "father"] },
    { from: ["mom", "children"], to: ["son", "mother"] },
    { from: ["mom", "children"], to: ["daughter", "mother"] }
  ]
})

clearGraph()

Empty current graph.

flowView.clearGraph()

destroy()

Delete flow-view custom element.

flowView.destroy()

An use case for destroy() is the following. Suppose you are using Next.js, you need to load flow-view with an async import into a useEffect which needs to return a callback to be called when component is unmounted.

This is a sample code.

import type { FlowView } from "flow-view";
import { FC, useEffect, useRef } from "react";

const MyComponent: FC = () => {
  const flowViewContainerRef = useRef<HTMLDivElement | null>(null);
  const flowViewRef = useRef<FlowView | null>(null);

  useEffect(() => {
    let unmounted = false;

    const importFlowView = async () => {
      if (unmounted) return;
      if (flowViewContainerRef.current === null) return;
      if (flowViewRef.current !== null) return;

      const { FlowView } = await import("flow-view");

      const flowView = new FlowView({
        container: flowViewContainerRef.current,
      });
      flowViewRef.current = flowView;
    };

    importFlowView();

    return () => {
      unmounted = true;
      if (flowViewRef.current !== null) flowViewRef.current.destroy();
    };
  }, [flowViewRef, flowViewContainerRef]);

  return <div ref={flowViewContainerRef}></div>;
};

newNode() and newEdge()

Create nodes and edges programmatically. See programmatic example here.

// Create two nodes.

const node1 = flowView.newNode({
  text: "Hello",
  ins: [{}, {}],
  outs: [{ id: "output1" }],
  x: 100,
  y: 100,
  width: 80
})
const node2 = flowView.newNode({
  text: "World",
  ins: [{ id: "input1" }],
  width: 100,
  x: 250,
  y: 400
})

// Connect nodes with an edge.
flowView.newEdge({
  from: [node1.id, "output1"],
  to: [node2.id, "input1"]
})

deleteNode() and deleteEdge()

Delete nodes and edges programmatically. Notice that when a node is deleted, all its connected edges are deleted too.

const nodeId = "abc"
const edgeId = "123"

flowView.deleteNode(nodeId)
flowView.deleteEdge(edgeId)

addNodeClass(nodeType, NodeClass)

Can add custom node class. See custom node example here.

onChange(callback)

Set callback to be invoked on every view change. See demo code here.

Callback signature is ({ action, data }, info) => void, where

  • action can be CREATE_NODE, DELETE_NODE, etc.
  • data change based on action
  • info can contain { isLoadGraph: true } or other optional information.

nodeTextToType(func)

Set a function that will be invoked on node creation to resolve node type from node text.

License

MIT