obsidianmd / jsoncanvas

An open file format for infinite canvas data.
https://jsoncanvas.org
MIT License
2.47k stars 80 forks source link

Node Based Editing #2

Open rodydavis opened 6 months ago

rodydavis commented 6 months ago

I am working on a node based editor that is also aiming to be generic and an open format.

I also store the relationships in a pretty similar way, but curious if there could be some support for edges and nodes with additional metadata:

{
  "nodes": [
    {
      "id": "node-1",
      "type": "number-value",
      "x": 36,
      "y": 48,
      "width": 176,
      "height": 68,
      "inputs": {
          "source": {
             "type": "number",
             "value": "1"
          }
      },
      "outputs": {
          "result": {
             "type": "number",
             "value": "#source"
          }
      }
    },
    {
      "id": "node-2",
      "type": "number-value",
      "x": 36,
      "y": 48,
      "width": 176,
      "height": 68,
      "inputs": {
          "source": {
             "type": "number",
             "value": "2"
          }
      },
      "outputs": {
          "result": {
             "type": "number",
             "value": "#source"
          }
      }
    },
    {
      "id": "node-3",
      "type": "number-add",
      "x": 36,
      "y": 48,
      "width": 176,
      "height": 68,
      "inputs": {
          "left": {
             "type": "number",
             "value": "0"
          },
          "right": {
             "type": "number",
             "value": "0"
          }
      },
      "outputs": {
          "result": {
             "type": "number",
             "value": "#left + #right"
          }
      }
    }
  ],
  "edges": [
    {
      "id": "edge-1",
      "fromNode": "node-1",
      "fromSide": "right",
      "fromEnd": "none",
      "toNode": "node-3",
      "toSide": "left",
      "toEnd": "arrow",
      "metadata": {
          "output": "#result",
          "input": "#left"
      },
    },
    {
      "id": "edge-2",
      "fromNode": "node-2",
      "fromSide": "right",
      "fromEnd": "none",
      "toNode": "node-3",
      "toSide": "left",
      "toEnd": "arrow",
      "metadata": {
          "output": "#result",
          "input": "#right"
      },
    }
  ]
}

This could allow an expressive format for editing between applications too.

The metadata object could also be top level but might conflict with spec properties:

{
  "id": "edge-1",
  "fromNode": "node-1",
  "fromSide": "right",
  "fromEnd": "none",
  "toNode": "node-3",
  "toSide": "left",
  "toEnd": "arrow",
  "output": "#result",
  "input": "#left"
}
rodydavis commented 6 months ago

https://github.com/obsidianmd/jsoncanvas/assets/31253215/c5da7eab-d6fc-4b33-a890-58823cd29dfb

nqthqn commented 6 months ago

@rodydavis — Could you render an edge's label property inside the nodes? Maybe with the use of a delimiter to accommodate labels in fromNode and toNode?

io.canvas

{
  "nodes": [
    {
      "id": "A"
    },
    {
      "id": "B"
    },
    {
      "id": "C"
    }
  ],
  "edges": [
    {
      "id": "e1",
      "fromNode": "A",
      "toNode": "C",
      "label": "A side label :: C side label, from A"
    },
    {
      "id": "e2",
      "fromNode": "B",
      "toNode": "C",
      "label": "B side label :: C side label, from B"
    }
  ]
}
rodydavis commented 6 months ago

Specifically the extra information is needed to connect not just to a side, but to a specific input/output.

If I used the existing schema I could probably do the following:

{
  "nodes": [
    {
      "id": "node-1",
      "type": "number-value",
      "x": 36,
      "y": 48,
      "width": 176,
      "height": 68,
      "inputs": {
          "source": {
             "type": "number",
             "value": "1"
          }
      },
      "outputs": {
          "result": {
             "type": "number",
             "value": "#source"
          }
      }
    },
    {
      "id": "node-2",
      "type": "number-value",
      "x": 36,
      "y": 48,
      "width": 176,
      "height": 68,
      "inputs": {
          "source": {
             "type": "number",
             "value": "2"
          }
      },
      "outputs": {
          "result": {
             "type": "number",
             "value": "#source"
          }
      }
    },
    {
      "id": "node-3",
      "type": "number-add",
      "x": 36,
      "y": 48,
      "width": 176,
      "height": 68,
      "inputs": {
          "left": {
             "type": "number",
             "value": "0"
          },
          "right": {
             "type": "number",
             "value": "0"
          }
      },
      "outputs": {
          "result": {
             "type": "number",
             "value": "#left + #right"
          }
      }
    }
  ],
  "edges": [
    {
      "id": "edge-1",
      "fromNode": "node-1#result",
      "fromSide": "right",
      "fromEnd": "none",
      "toNode": "node-3#left",
      "toSide": "left",
      "toEnd": "arrow"
    },
    {
      "id": "edge-2",
      "fromNode": "node-2#result",
      "fromSide": "right",
      "fromEnd": "none",
      "toNode": "node-3#right",
      "toSide": "left",
      "toEnd": "arrow"
    }
  ]
}

This would required renderers to ignore everything after the # for fromNode and toNode, but would totally work

rodydavis commented 6 months ago

This would be a custom node type, with default edge rendering that if the editor supported it could map the edges directly to the inputs/outputs.

rodydavis commented 6 months ago

Could also be useful for projects like unit too: https://github.com/samuelmtimbo/unit

rodydavis commented 6 months ago

Also good progress on flutter renderer:

Screenshot 2024-03-11 at 11 21 49 PM
rodydavis commented 5 months ago

Also found this interesting, might be asking for ports: https://rtsys.informatik.uni-kiel.de/confluence/display/KIELER/JSON+Graph+Format

jg-l commented 5 months ago

node-demo.mov

This is super neat btw.

Can easily make custom Node, (obviously wouldn't be fully compliant to JSON Canvas spec by @kepano) with my library https://github.com/jg-l/json_canvas

The Node class is sealed right now, but easy to amend it as just an abstract class for more extensibility

class NumberNode extends Node {
  final num inValue;
  final num outValue;
  NumberNode({
    required this.inValue,
    required this.outValue,
    required super.id,
    required super.type,
    required super.x,
    required super.y,
    required super.width,
    required super.height,
    super.color,
  });
}
rodydavis commented 5 months ago

Here is the schema I created with dart_mappable:

https://gist.github.com/rodydavis/17a0a19c8d91a08e2674a57b93ba5259

rodydavis commented 5 months ago

I used a discriminatorKey to be able to add custom nodes in the future and support a know good fallback too

jg-l commented 5 months ago

Your implementation is clean! I was hesitant to leverage a 3rd party package or build_runner in my implementation, at the cost of of more boilerplate though!

rodydavis commented 5 months ago

I think dart_mappable is a awesome compromise because it is easy to extend and adjust overtime!

rodydavis commented 5 months ago

The nice thing about a spec, is that there can be multiple implementations that all work together! 👍🏻

nileshtrivedi commented 3 months ago

I had built a SVG-based graph dataset editor: https://codeberg.org/nilesh/grapher . Here is a demo site.

image

The design constraints were ability to import and export clean data, not requiring a server or accounts etc.

Will it be useful to add support for JSONCanvas format in this tool?