jsoncanvas
jsoncanvas copied to clipboard
Node Based Editing
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"
}
https://github.com/obsidianmd/jsoncanvas/assets/31253215/c5da7eab-d6fc-4b33-a890-58823cd29dfb
@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"
}
]
}
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
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.
Could also be useful for projects like unit too: https://github.com/samuelmtimbo/unit
Also good progress on flutter renderer:
Also found this interesting, might be asking for ports: https://rtsys.informatik.uni-kiel.de/confluence/display/KIELER/JSON+Graph+Format
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,
});
}
Here is the schema I created with dart_mappable:
https://gist.github.com/rodydavis/17a0a19c8d91a08e2674a57b93ba5259
I used a discriminatorKey
to be able to add custom nodes in the future and support a know good fallback too
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!
I think dart_mappable is a awesome compromise because it is easy to extend and adjust overtime!
The nice thing about a spec, is that there can be multiple implementations that all work together! 👍🏻
I had built a SVG-based graph dataset editor: https://codeberg.org/nilesh/grapher . Here is a demo site.
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?