Layout links not understandable easily

I am trying out layouts provided by gojs, and stuck at how to improve the representation.

Here is my gojs model if you want the data

{ “class”: “go.GraphLinksModel”,
“nodeKeyProperty”: “id”,
“nodeDataArray”: [
{“id”:“node1”, “type”:“Native”, “imgPath”:“icons/native.png”, “predicate”:"", “loc”:“0 333.11674804687505”},

{“id”:“node2”, “type”:“Native”, “imgPath”:“icons/native.png”, “predicate”:“Copy.Id <> null”, “loc”:“200 333.11674804687505”},
{“id”:“node3”, “type”:“Native”, “imgPath”:“icons/native.png”, “predicate”:“Copy.Id <> null”, “loc”:“400 333.11674804687505”},
{“id”:“node4”, “type”:“Native”, “imgPath”:“icons/native.png”, “predicate”:"", “loc”:“600 333.116748046875”},
{“id”:“node5”, “type”:“Native”, “imgPath”:“icons/native.png”, “predicate”:"", “loc”:“800 0”},
{“id”:“node6”, “type”:“Native”, “imgPath”:“icons/native.png”, “predicate”:"", “loc”:“800 222.07783203125”},
{“id”:“node7”, “type”:“Native”, “imgPath”:“icons/native.png”, “predicate”:"", “loc”:“800 444.1556640625”},
{“id”:“node8”, “type”:“Native”, “imgPath”:“icons/native.png”, “predicate”:"", “loc”:“800 666.23349609375”},
{“id”:“JoinNode1”, “type”:“Join”, “imgPath”:“icons/join.png”, “predicate”:"", “loc”:“1000 0”},
{“id”:“JoinNode2”, “type”:“Join”, “imgPath”:“icons/join.png”, “predicate”:"", “loc”:“1000 222.07783203125”},
{“id”:“JoinNode3”, “type”:“Join”, “imgPath”:“icons/join.png”, “predicate”:"", “loc”:“1000 444.1556640625”},
{“id”:“JoinNode4”, “type”:“Join”, “imgPath”:“icons/join.png”, “predicate”:"", “loc”:“1000 666.23349609375”},
{“id”:“JoinOutput1”, “type”:“Table”, “imgPath”:“icons/table.png”, “predicate”:"", “loc”:“1220.15625 0”},
{“id”:“JoinOutput2”, “type”:“Table”, “imgPath”:“icons/table.png”, “predicate”:"", “loc”:“1220.15625 222.07783203125”},
{“id”:“JoinOutput3”, “type”:“Table”, “imgPath”:“icons/table.png”, “predicate”:"", “loc”:“1220.15625 444.1556640625”},
{“id”:“JoinOutput4”, “type”:“Table”, “imgPath”:“icons/table.png”, “predicate”:"", “loc”:“1220.15625 666.23349609375”}
],
“linkDataArray”: [
{“from”:“node1”, “to”:“node2”, “label”:“relation1”, “type”:“Native”, “imgPath”:“icons/nativeRelation.png”, “points”:[50,358.11674804687505,60,358.11674804687505,125,358.11674804687505,125,358.11674804687505,190,358.11674804687505,200,358.11674804687505]},
{“from”:“node2”, “to”:“node3”, “label”:“relation2”, “type”:“Native”, “imgPath”:“icons/nativeRelation.png”, “points”:[250,358.11674804687505,260,358.11674804687505,325,358.11674804687505,325,358.11674804687505,390,358.11674804687505,400,358.11674804687505]},
{“from”:“node3”, “to”:“node4”, “label”:“relation3”, “type”:“Native”, “imgPath”:“icons/nativeRelation.png”, “points”:[450,358.11674804687505,460,358.11674804687505,525,358.11674804687505,525,358.116748046875,590,358.116748046875,600,358.116748046875]},
{“from”:“node4”, “to”:“node5”, “label”:“relation4”, “type”:“Native”, “imgPath”:“icons/nativeRelation.png”, “points”:[650,358.116748046875,660,358.116748046875,725,358.116748046875,725,25,790,25,800,25]},
{“from”:“node4”, “to”:“node6”, “label”:“relation5”, “type”:“Semantic”, “imgPath”:“icons/semanticRelation.png”, “points”:[650,358.116748046875,660,358.116748046875,725,358.116748046875,725,247.07783203125,790,247.07783203125,800,247.07783203125]},
{“from”:“node4”, “to”:“node7”, “label”:“relation6”, “type”:“Semantic”, “imgPath”:“icons/semanticRelation.png”, “points”:[650,358.116748046875,660,358.116748046875,725,358.116748046875,725,469.1556640625,790,469.1556640625,800,469.1556640625]},
{“from”:“node4”, “to”:“node8”, “label”:“relation7”, “type”:“Native”, “imgPath”:“icons/nativeRelation.png”, “points”:[650,358.116748046875,660,358.116748046875,725,358.116748046875,725,691.23349609375,790,691.23349609375,800,691.23349609375]},
{“from”:“node4”, “to”:“JoinNode1”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[650,358.116748046875,660,358.116748046875,830.0390625,358.116748046875,830.0390625,25,1000.078125,25,1010.078125,25]},
{“from”:“node5”, “to”:“JoinNode1”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[850,25,860,25,930.0390625,25,930.0390625,25,1000.078125,25,1010.078125,25]},
{“from”:“node4”, “to”:“JoinNode2”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[650,358.116748046875,660,358.116748046875,830.0390625,358.116748046875,830.0390625,247.07783203125,1000.078125,247.07783203125,1010.078125,247.07783203125]},
{“from”:“node6”, “to”:“JoinNode2”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[850,247.07783203125,860,247.07783203125,930.0390625,247.07783203125,930.0390625,247.07783203125,1000.078125,247.07783203125,1010.078125,247.07783203125]},
{“from”:“node4”, “to”:“JoinNode3”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[650,358.116748046875,660,358.116748046875,830.0390625,358.116748046875,830.0390625,469.1556640625,1000.078125,469.1556640625,1010.078125,469.1556640625]},
{“from”:“node7”, “to”:“JoinNode3”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[850,469.1556640625,860,469.1556640625,930.0390625,469.1556640625,930.0390625,469.1556640625,1000.078125,469.1556640625,1010.078125,469.1556640625]},
{“from”:“node4”, “to”:“JoinNode4”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[650,358.116748046875,660,358.116748046875,830.0390625,358.116748046875,830.0390625,691.23349609375,1000.078125,691.23349609375,1010.078125,691.23349609375]},
{“from”:“node8”, “to”:“JoinNode4”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[850,691.23349609375,860,691.23349609375,930.0390625,691.23349609375,930.0390625,691.23349609375,1000.078125,691.23349609375,1010.078125,691.23349609375]},
{“from”:“JoinNode1”, “to”:“JoinOutput1”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[1060.078125,25,1070.078125,25,1147.14306640625,25,1147.14306640625,25,1224.2080078125,25,1234.2080078125,25]},
{“from”:“JoinNode2”, “to”:“JoinOutput2”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[1060.078125,247.07783203125,1070.078125,247.07783203125,1147.14306640625,247.07783203125,1147.14306640625,247.07783203125,1224.2080078125,247.07783203125,1234.2080078125,247.07783203125]},
{“from”:“JoinNode3”, “to”:“JoinOutput3”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[1060.078125,469.1556640625,1070.078125,469.1556640625,1147.14306640625,469.1556640625,1147.14306640625,469.1556640625,1224.2080078125,469.1556640625,1234.2080078125,469.1556640625]},
{“from”:“JoinNode4”, “to”:“JoinOutput4”, “label”:"", “type”:“Normal”, “imgPath”:"", “points”:[1060.078125,691.23349609375,1070.078125,691.23349609375,1147.14306640625,691.23349609375,1147.14306640625,691.23349609375,1224.2080078125,691.23349609375,1234.2080078125,691.23349609375]}
]}

Link template
$$(
                go.Link,
                {
                    cursor : 'pointer',
                    routing : go.Link.Orthogonal,
                    corner : 10
                },// basic link styles
                new go.Binding( 'points' ).makeTwoWay(),
                $$( go.Shape, { stroke : '#b5b5b5', 'strokeDashArray' : [ 3, 2 ] } ), // this shape is link
                $$( go.Shape, { fill : '#a5a5a5', stroke : '#a5a5a5', toArrow : 'Triangle' } ), // the "to" end arrowhead
            
                $$( go.Panel, 'Vertical',  // this Panel is link label
                        $$( go.Picture, { width : 30, height : 40 },
                            new go.Binding( 'source', 'imgPath' )
                        ),
            
                    $$( go.TextBlock, { margin : 3 },
                        new go.Binding( 'text', 'label' )
                    )
                )
            );

Here is where I have a problem.

I am applying treeLayout using

layout = $$( go.TreeLayout, {
isInitial : true,
angle : 0,
nodeSpacing : 200,
layerSpacing : 200
} );
goDiagram.startTransaction( ‘_toggleLayout’ );
goDiagram.layout = layout;
goDiagram.commitTransaction( ‘_toggleLayout’ );

Which renders my layout as following image

Now please find relation5 in image, it is not clearly visible that it is related to node6. Same goes with other relations. The other issue is JoinNode[ 1-4 ] and node4 are linked to each other, but this information is hard to gather by looking at the graph.

How can I improve the representation ? I tried the fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide but once I apply the layout this property is ignored.

The other layout that I tried was LayeredDigraphLayout. I applied the layout using

layout = layout = $$( go.LayeredDigraphLayout, {
angle : 0,
layerSpacing: 100,
columnSpacing: 50
} );
goDiagram.startTransaction( ‘_toggleLayout’ );
goDiagram.layout = layout;
goDiagram.commitTransaction( ‘_toggleLayout’ );

Which renders my layout as following image

In this layout the JoinNode[ 1-4 ] and node4 are linked to each other is some what visible. But there is too much cluttering between node4 and node[5-8] colummn.

So my question is, is there a setting to make my layout a bit clutter free with all information more clear than what is visible now ?

Yes, the graph is not tree-structured, so LayeredDigraphLayout might work better.

Try positioning the label to be closer to the “to” node by setting segmentIndex and segmentFraction on your “Vertical” Panel:

    { segmentIndex: -2, segmentFraction: 0.5 }

You may need to fiddle with the fraction to get better results. Thanks for providing your data and your link template.

Hi walter,

Using segmentIndex and segmentFraction does make the diagram a bit clear. But still it needs to be more readable.

  • If there are any other custom layouts provided by goJs that can help my case, please suggest. I am wide open for it.

  • One suggestion that I got in the current layouts was to remove the merging of links, when multiple links are coming in or going out. So can I use fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide even when I apply the layout ?

please have a look at the following image that I tried

In this image I have applied the ForceDirectedLayout and rearranged the node manually to place the nodes like the LayeredDigraphLayout with links in splitted format. I feel this looks a bit more readable.

How can I achieve this through LayeredDigraphLayout ?

Set https://gojs.net/latest/api/symbols/LayeredDigraphLayout.html#setsPortSpots to false.

Hi walter,

Sorry but I am able to implement the property as intended. Even if I set the value to false, it is still providing the same output.

I referred to the example at connectionPoints.html

My node template
gojsDiagram.nodeTemplate =
            $$( go.Node, 'Vertical',
                { zOrder : 5, opacity : 0.5 /*, background:"#c9c9c9"*/ },
                { selectable : true, selectionAdornmentTemplate : nodeSelectionAdornment }, // Node is selectable but the rectangular shape is not visible
                { contextMenu : myContextMenu }, // to make node start and accept link drag drop
                new go.Binding( 'location', 'loc', go.Point.parse ).makeTwoWay( go.Point.stringify ),// stores the location of node, and renders at the same postion if the model is loaded
            
                $$( go.Panel, 'Auto',
                    {
                        portId : '', // this whole panel acts as the only port for the node
                        fromSpot : go.Spot.RightSide,
                        toSpot : go.Spot.LeftSide,
                        fromLinkable : true,
                        toLinkable : true,
                        cursor : 'pointer',
                        background : 'white'
                    }, // link can be drawn using this panel
            
                    $$( go.Shape, { fill : 'transparent', stroke : '#c9c9c9', strokeWidth : 0 } ), // for node border
            
                    $$( go.Panel, 'Vertical',
                        $$( go.Picture,
                            {
                                source : 'icons/table.png',
                                width : 30,
                                height : 30,
                                margin : 5,
                                background : 'transparent',
                                cursor : 'grab',
                                alignment : go.Spot.Center,
                            },
                            { fromLinkable : false, toLinkable : false }, // if this is removed node looses drag functionality
                            new go.Binding( 'source', 'imgPath' )
                        )
                    ),// end Vertical Panel
                ),  // end Auto Panel
            
                // node name
                $$( go.TextBlock, { margin : 4 },
                    new go.Binding( 'text', 'id' )
                )
            );

Layout init changed to -

layout = $$( go.LayeredDigraphLayout, {
                angle : 0,
                layerSpacing: 150,
                setsPortSpots: false,
                columnSpacing: 50
            } );

Both your node template and layout look good to me. There must be something else that is affecting the layout? Are you sure that you are setting Diagram.layout before you load the model? Otherwise perhaps an oriented layout is setting the Link.fromSpot and Link.toSpot before the desired layout.

Your comment helped me narrow down, let me explain my scenario.

When the diagram loads tree layout is applied to the data, Now to change the layout I execute the following code -

    let layout = $$( go.LayeredDigraphLayout, {
            angle : angle,
            layerSpacing: 150,
            setsPortSpots: false,
            columnSpacing: 50
    } );
    myDiagram.startTransaction( '_toggleLayout' );
    myDiagram.layout = layout;
    myDiagram.commitTransaction( '_toggleLayout' );

If do the above case the links does not change. They are merged.

But if I set the LayeredDigraphLayout while setting the diagram properties then the code works as expected.

As we have the functionality to change layout can you please help me with this case.

Also set TreeLayout.setsPortSpot and setsChildPortSpot to false.

Thanks walter this did solve the issue.

Just a small follow up
If I change the angle then I need to update the fromSpot and toSpot property

The way in my mind was to traverse through the node and update the specific property of the panel ( somehow), but this seems a bit messy.

Is there a clean way than this ?

myDiagram.nodes.each(function(n) { n.port.fromSpot = ...; n.port.toSpot = ...; });

Hi walter,

The solution you suggested will not work for me. As if you look at the node template shared above the Auto panel which is within the vertical panel acts as the port.

So what I meant above was how to traverse to the auto panel cleanly.

.
.

Another question that I had was regarding the{ segmentIndex: -2, segmentFraction: 0.5 } that you suggested.

My updated link template
gojsDiagram.linkTemplate =
            $$(
                go.Link,
                {
                    cursor : 'pointer',
                    routing : go.Link.Orthogonal,
                    corner : 10
                },// basic link styles
                { selectable : true, selectionAdornmentTemplate :
                   $$( go.Adornment, 'Auto',
                                $$( go.Shape, 'RoundedRectangle', { fill : '#c9c9c9', strokeWidth : 0, opacity : 0.2 } ),
                                $$( go.Placeholder, { margin : 2 } ) )
                    },
                    new go.Binding( 'points' ).makeTwoWay(),
                
                $$( go.Shape, { stroke : '#b5b5b5', 'strokeDashArray' : [ 3, 2 ] },
                    new go.Binding( 'stroke', 'isHighlighted', function( h ) { return h ? 'red' : '#b5b5b5'; } ).ofObject(),
                    new go.Binding( 'strokeDashArray', 'isHighlighted', function( h ) { return h ? [ 3, 0 ] : [ 3, 2 ]; } ).ofObject()
                ), // this shape is link
                
                $$( go.Shape, { fill : '#a5a5a5', stroke : '#a5a5a5', toArrow : 'Triangle' },
                    new go.Binding( 'fill', 'isHighlighted', function( h ) { return h ? 'red' : '#a5a5a5'; } ).ofObject(),
                    new go.Binding( 'stroke', 'isHighlighted', function( h ) { return h ? 'red' : '#a5a5a5'; } ).ofObject()
                ), // the "to" end arrowhead
                
                $$( go.Panel, 'Vertical',  // this Panel is link label
                    { segmentIndex: -2, segmentFraction: 0.5 },
                    $$( go.Picture, { width : 30, height : 40 }, new go.Binding( 'source', 'imgPath' ) ),
                    $$( go.TextBlock, { margin : 3 }, new go.Binding( 'text', 'label' ) )
                )
            );

This renders the graph as intended, but my adornment placement is different. Check the below image

image

Also how I set the dimension of Adornment to the size of label as they are currently the size is different.
Check the below image

image

I have updated my code, above.

Surround the “Vertical” Panel with an “Auto” Panel that fits that gray rounded rectangular Shape around that “Vertical” Panel.

I am sorry walter I did not understand the solution that you stated.

I have added a new Auto panel which now contains the Vertical panel which has the image and text in it.
If this is what you meant then only this change is not working for me.

Please revert if any more change is required ?
And do mention the position of the adornment issue as well.

Set Link.selectionObjectName to whatever GraphObject.name you give to that “Vertical” Panel.

I changed the link template to

    gojsDiagram.linkTemplate =
            $$(
                go.Link,
                {
                    selectionObjectName: "imagePanel",
                    cursor : 'pointer',
                    routing : go.Link.Orthogonal,
                    corner : 10
                },// basic link styles
                { selectable : true, selectionAdornmentTemplate :
 $$( go.Adornment, 'Auto',
                   $$( go.Shape, 'RoundedRectangle', { fill : '#c9c9c9', strokeWidth : 0, opacity : 0.2 } ),
                   $$( go.Placeholder, { margin : 2 } )
},
                    new go.Binding( 'points' ).makeTwoWay(),
                
                $$( go.Shape, { stroke : '#b5b5b5', 'strokeDashArray' : [ 3, 2 ] },
                    new go.Binding( 'stroke', 'isHighlighted', function( h ) { return h ? 'red' : '#b5b5b5'; } ).ofObject(),
                    new go.Binding( 'strokeDashArray', 'isHighlighted', function( h ) { return h ? [ 3, 0 ] : [ 3, 2 ]; } ).ofObject()
                ), // this shape is link
                
                $$( go.Shape, { fill : '#a5a5a5', stroke : '#a5a5a5', toArrow : 'Triangle' },
                    new go.Binding( 'fill', 'isHighlighted', function( h ) { return h ? 'red' : '#a5a5a5'; } ).ofObject(),
                    new go.Binding( 'stroke', 'isHighlighted', function( h ) { return h ? 'red' : '#a5a5a5'; } ).ofObject()
                ), // the "to" end arrowhead

                
                $$( go.Panel, 'Vertical',  // this Panel is link label
                    { name : "imagePanel" },
                    { segmentIndex: -2, segmentFraction: 0.5 },
                    $$( go.Picture,
                        new go.Binding( 'desiredSize', 'desiredSize' ),
                        new go.Binding( 'source', 'imgPath' )
                    ),

                    $$( go.TextBlock, { margin : 3 },
                        new go.Binding( 'text', 'label' )
                    )
                )
            );

This change still provides the result as the below image

image

This seems to work for me:

    myDiagram.linkTemplate =
      $(go.Link,
        {
          selectionObjectName: "LABEL",
          selectionAdornmentTemplate:
            $(go.Adornment, "Auto",
              { layerName: "Grid" },
              $(go.Shape, "RoundedRectangle",
                { fill: "whitesmoke", strokeDashArray: [5, 5] }),
              $(go.Placeholder)
            )
        },
        $(go.Shape),
        $(go.Shape, { toArrow: "OpenTriangle" }),
        $(go.Panel, "Vertical",
          { name: "LABEL" },
          $(go.TextBlock, "top"),
          $(go.TextBlock, "bottom")
        )
      );

image

Or am I not understanding your intent?

Hi,

Sorry, but you completely missed a point.

Even the code you shared above has the same issue displayed in the image that I shared in just the previous reply.

The template that you shared with some modification ( modifications are marked )
gojsDiagram.linkTemplate =
            $$(
                go.Link,
                {
                    routing : go.Link.Orthogonal, // **This is newly added**
                    corner : 10, // **This is newly added**
                    
                    selectionObjectName : 'LABEL',
                    selectionAdornmentTemplate :
                        $$( go.Adornment, 'Auto',
                           { layerName : 'Grid' },
                           $$( go.Shape, 'RoundedRectangle',
                              { fill : 'whitesmoke', strokeDashArray : [ 5, 5 ] }
                           ),
                           $$( go.Placeholder )
                        )
                },
                $$( go.Shape ),
                $$( go.Shape, { toArrow : 'OpenTriangle' } ),
                $$(go.Panel, "Vertical",
                   { segmentIndex: -2, segmentFraction: 0.5 }, // **This is newly added**
                  { name: "LABEL" }, 
                   $$(go.TextBlock, "top"),
                   $$(go.TextBlock, "bottom")
                )
            );

The { segmentIndex: -2, segmentFraction: 0.5 } was suggested by you as a solution in this same thread.

Yes, you can set the segmentIndex and segmentFraction on the label in the link template, but that doesn’t need to change the selectionAdornmentTemplate. Doesn’t what you just posted do what you want?

I just tried adding those segment… property assignments and the Link.routing, and it seems to work well:

    myDiagram.linkTemplate =
      $(go.Link,
        {
          routing: go.Link.Orthogonal,
          corner: 10,
          selectionObjectName: "LABEL",
          selectionAdornmentTemplate:
            $(go.Adornment, "Auto",
              { layerName: "Grid" },
              $(go.Shape, "RoundedRectangle",
                { fill: "whitesmoke", strokeDashArray: [5, 5] }),
              $(go.Placeholder)
            )
        },
        $(go.Shape),
        $(go.Shape, { toArrow: "OpenTriangle" }),
        $(go.Panel, "Vertical",
          { name: "LABEL", segmentIndex: -2, segmentFraction: 0.5 },
          $(go.TextBlock, "top"),
          $(go.TextBlock, "bottom")
        )
      );

image

What am I missing from what you say you want? Or do you have additional requirements?

Another way of accomplishing the same result:

    myDiagram.linkTemplate =
      $(go.Link,
        {
          routing: go.Link.Orthogonal,
          corner: 10,
          selectionAdorned: false
        },
        $(go.Shape),
        $(go.Shape, { toArrow: "OpenTriangle" }),
        $(go.Panel, "Auto",
          { segmentIndex: -2, segmentFraction: 0.5 },
          $(go.Shape, "RoundedRectangle",
            { fill: "whitesmoke", strokeDashArray: [5, 5] },
            new go.Binding("visible", "isSelected").ofObject()),
          $(go.Panel, "Vertical",
            $(go.TextBlock, "top"),
            $(go.TextBlock, "bottom")
          )
        )
      );

This does not use a selection Adornment at all, but modifies the link label’s visibility based on Link.isSelected.
image

If you want the default selection Adornment, the blue line following the Link.path, then just remove the setting of Link.selectionAdorned:

image