Modelling a DAG, Visual Programming Interface

I thought it would be interesting to build a visual programming interface such as Grasshopper or Max. The key components of such an interface:

  • The user creates a program by creating nodes and connecting them together, creating a directed graph.
  • There are various types of node that each have their own behavior, and a set number of inputs, and outputs.
  • Inputs and outputs are strongly typed; you can’t connect a String output to an Int input for example.
  • Any number of instances of a node can be created.
  • The created graph must be acyclic; you might call it a Directed Acyclic Graph.

How would you model such an interface in Elm? I want to use the type system to create this as much as possible. Nodes, inputs, and outputs seem to lend themselves well to union types, but it’s unclear how I can model the state of each node in a way that makes it easy to query all of the nodes connected to it, without having double references where adjacent nodes have to “agree” that they are connected to each other.

For the visual part at least I would check out https://github.com/erkal/kite (demo: https://erkal.github.io/kite/), it’s beautiful and all Elm.

From the data structure side there is a DOT files (https://package.elm-lang.org/packages/brandly/elm-dot-lang/latest/DotLang), and https://github.com/elm-community/graph that may be good for inspiration on structuring them.

3 Likes

These are great resources, thank you! I’m not that worried about the view implementation yet, but I’m sure looking at Kite’s source will help later.

After thinking about this for a while, I decided to write my own Graph with elm-community/graph as a reference. Something like this:

type alias Graph = Dict NodeId Node

type alias Port = { id : NodeId, index : Int }

type Edge
  = Float Port Port
  | Texture Port Port
  | ...

type alias Node n =
  { id : NodeId
  , nodeType : NodeType
  , incoming : Array (Maybe Edge)
  , outgoing : Array (Maybe Edge)
  }

where NodeType is an enum of all the different possible “types” of nodes.
While I think this would work fine, I was hoping to use the type system to enforce that you can’t connect nodes via incompatible edges or create multiple instances of the same node “type” that are different. For instance, with this system I could potentially make two instances of a node with nodeType = BlurTexture, where one has inputs [Float, Texture] and the other has [Texture, Texture]. This shouldn’t be possible because only one set of input types is valid.

I also thought about defining nodes like this:

type Node
  = NodeImageIn NodeId () EdgeTexture
  | NodeImageOut NodeId EdgeTexture ()
  | NodeBlur NodeId (EdgeTexture, EdgeFloat) EdgeTexture

But I’m not even sure how I would begin to implement an addNode function for something like this.

Since I’m assuming that you never have the situation of one node having more than one parent, you are actually only looking at a subset of DAGs, namely trees. You might also get some inspiration from looking at the way the-sett/tea-tree was written… Although that was possibly designed for much larger structures than you had in mind.

1 Like

Actually nodes can have any number of parents and any number of children, so I can’t model this structure as a tree. For example you might have a NodeAdd that has two Float inputs and one Float output, but also a NodeRGBSplit that has one Texture input and three Texture outputs. However, the tea-tree that you linked looks interesting—maybe I can approach the API in a similar way.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.