Location binding doesn't work

Hi, I am a new user and I am using the free evaluation version of GoJS.
I am experimenting with GoJS since a while and now I am trying to add location binding to my diagram but it doesn’t work. I read carefully the ‘dataBinding.html’ tutorial and I added the parse function to my nodeTemplate:

new go.Binding("location", "loc",go.Point.parse)

but the nodes position is not affected. If I put some garbage data for loc property I can see the errors in the browser console, I think this means the binding is set up correctly, but with real Point values the nodes aren’t positioned in the way I expected.

Can someone give me some hints? Following is my full code:

`
function draw_chart() {
var $ = go.GraphObject.make;
myDiagram = $(go.Diagram, “nosOrganigrammaDiv”,
{
initialContentAlignment: go.Spot.TopCenter, //center the diagram
“undoManager.isEnabled”:true, /activate Ctrl-Z/
allowMove: false,
}); //end diagram

    var sfondoSfumato=$(go.Brush, "Linear", { 0: "white", 1: "#e4e8f0" });
    myDiagram.nodeTemplate= 
	    $(go.Node, "Auto",          			
		/* add Bindings here */
		// example Node binding sets Node.location to the value of Node.data.loc
		new go.Binding("location", "loc",go.Point.parse), 
	    
		$(go.Panel,"Auto", 
		    $(go.Shape, "RoundedRectangle",
			{stroke:"#A2B6DA", strokeWidth: 2 },
			    new go.Binding("figure","structureFigure"),
			    // rosso se highlighted, sfumato altrimenti 
			    new go.Binding("fill", "isHighlighted", function(h) { return h ? "#F44336" : sfondoSfumato; }).ofObject()
		      ),
			
		    $(go.Panel, "Table",
			{ defaultAlignment: go.Spot.Left, margin: 4 },
			$(go.RowColumnDefinition, { column: 2, width: 4 }),            	   

			$(go.TextBlock, "Nome Struttura",
			  {row:0, column:0, columnSpan:2, alignment:go.Spot.Center},
			  {margin: 2, stroke: "#6485C1", font: "bold 16px Roboto Regular" },
			  new go.Binding("text","structureName")),
				    
			$(go.Picture,
			  {row:1, column:0,alignment:go.Spot.Left},
			  {margin: 5, width: 50, height: 50, background: "white"},
			  new go.Binding("source")),
				  
			$(go.TextBlock, "Nome manager",
			  {row:1, column:1, columnSpan:2, alignment:go.Spot.Center},
			  {margin: 2, stroke: "#888888", font: "normal 16px Roboto Regular" },
			  new go.Binding("text","managerName"))	         		  
		    ),
		    $("TreeExpanderButton",
			{ alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Top },
			{ visible: true })
		    )  
	    );      				
		    
		    
    var myModel=new go.GraphLinksModel(); // $(go.TreeModel);
    myModel.nodeDataArray=
	    [{key:"3503",name:"StruturaWf2",loc:go.Point(600, 300),structureFigure:"StopSign",structureName:"StruturaWf2",type:"0",staff:false},
	      {key:"3603",name:"Struttura wf3",loc:go.Point(300, 500),structureFigure:"Ellipse",structureName:"Struttura wf3",type:"0",staff:false},
	      {key:"3502",name:"StruturaWf1",loc:go.Point(100, 100),structureFigure:"RoundedRectangle",structureName:"StruturaWf1",type:"0",staff:false},
	      {key:"3602",parent:"3502",name:"Figlio1",loc:go.Point(500, 500),structureFigure:"Rectangle",structureName:"Figlio1",type:"0",staff:false}
	      ];     		
    
    // define a Link template that routes orthogonally, with no arrowhead
    myDiagram.linkTemplate =
      $(go.Link,
	{ routing: go.Link.Orthogonal, corner: 3 },
	$(go.Shape, { strokeWidth: 1, stroke: "#414853" })); // the link shape
	    
	myDiagram.linkTemplateMap.add("Support",
	  $(go.Link, 
	    {routing: go.Link.AvoidsNodes, corner: 15, curve: go.Link.JumpOver},
	    { isLayoutPositioned: false, isTreeLink: false, curviness: -75 },
	    { relinkableFrom: false, relinkableTo: false },
	    $(go.Shape,
	      { stroke: "green", strokeWidth: 2 }),
	    $(go.Shape,
	      { toArrow: "OpenTriangle", stroke: "green", strokeWidth: 2 }),
	    $(go.TextBlock,
	      new go.Binding("text", "text"),
	      { stroke: "green", background: "rgba(255,255,255,0.75)",
		maxSize: new go.Size(80, NaN)
	      })));

    /* get array for links*/
    myModel.linkDataArray=<%=chart.getJsonStringLinkDataArray(themeDisplay)%>;
    
    myDiagram.addDiagramListener("ObjectDoubleClicked",
	  function(e) {
	    var part = e.subject.part;
	    if (!(part instanceof go.Link)) { 
		    <portlet:namespace/>viewStructure(part.data.key,part.data.structureName);
	    }
	  });   
    
    myDiagram.addDiagramListener("InitialLayoutCompleted", function (e) {
	    console.info("inside InitialLayoutCompleted");
	    var nodes=e.diagram.nodes;
	      console.info("nodes.first="+nodes.first().data.name);
	    //nodes.first().expandTree(2); 
	    var roots=e.diagram.findTreeRoots();
	    console.info("roots="+roots.count);
    roots.each(function (r) { r.expandTree(2); });
});
    
myDiagram.model=myModel;

// zoom
new go.Overview("nosOrganigrammaOverviewDiv").observed = myDiagram;
    
}    

);
`

If you use a conversion function such as Point.parse, the property value must be a string in a particular syntax (two real numbers separated by a space). But your node data has property values that are not strings but instances of Point.

So you need to choose which type of data property value you will use and define your Binding accordingly.

Hi Walter, I am really sorry for this… :-(
I pasted my last try where I (intentionally) put wrong data, now I switched back to my original nodeDataArray

[{key:"3503",name:"StruturaWf2",loc:"600 300",structureFigure:"StopSign",structureName:"StruturaWf2",type:"0",staff:false}, {key:"3603",name:"Struttura wf3",loc:"300 500",structureFigure:"Ellipse",structureName:"Struttura wf3",type:"0",staff:false}, {key:"3502",name:"StruturaWf1",loc:"100 100",structureFigure:"RoundedRectangle",structureName:"StruturaWf1",type:"0",staff:false}, {key:"3602",parent:"3502",name:"Figlio1",loc:"500 500",structureFigure:"Rectangle",structureName:"Figlio1",type:"0",staff:false} ];

but the result is the same: node are disposed simply one beside other.

Marco

I just copy-and-pasted your code, renamed the DIV element, commented out the linkDataArray assignment and the ObjectDoubleClicked listener and the Overview, replaced the node data with your correct code (where data.loc are strings), and it worked exactly as I think you intended, with the nodes spread out.

So I don’t know what to say.

Hi Walter, I really don’t know what happened, after your reply I did exactly the same things (commented, replaced, …) and it worked… but the strange thing is that also after switched back to original code it continues to work… I did hundreds of tries (maybe too many) and it never worked… may was a cache problem? I really don’t know. I am sorry.
Ok, now that the location seems to work, I have to make it works together with a Layout… I’d like that layout doesn’t touch nodes that have been positioned by the user. To achieve this I am going to set isLayoutPositioned = false for some nodes in my nodeDataArray, is this the correct approach? On a first try it seems not working, because each time I expand / collapse a node, all my nodes are rearranged by the layout. Is there any other properties that I have to set?
Thank you very much for you support

Marco

Yes, setting or binding Part.isLayoutPositioned to false will cause the Layout to ignore that Part. Such Node had better have a real location or position, otherwise it won’t be drawn.

You can set the property in a “SelectionMoved” DiagramEvent listener. And you can remember it with a TwoWay Binding.

But note that if a Node is completely ignored by a Layout, the node cannot affect the Layout in any manner. That means that the node may overlap with nodes positioned by the layout.

Hi Walter, I managed to set isLayoutPositioned property adding a listener as you suggested:

myDiagram.addDiagramListener("SelectionMoved", function(e) { if (e.subject !=null ) { var part = e.subject.first(); if (part instanceof go.Node) { myDiagram.startTransaction("selection-moved"); part.isLayoutPositioned=false; myDiagram.commitTransaction("selection-moved"); } } });
and it works… but I still don’t understand why setting the same property in the nodeDataArray doesn’t work. I mean that if I set the property to false in the array, the layout initially dispose all the nodes, apparently ignoring the property. Following is my node, is it correct ?
{key:"3502",name:"StruturaWf1",isLayoutPositioned:false, loc:"100 200", ....

Have I to set some additional property on my layout?

Concerning your last note, is there a way to have some nodes in fixed positions and have the layout take them into account during automatic disposition or this would require a completely new custom Layout?

Marco

Do you have a Binding on Part.isLayoutPositioned? If so, yes you could call Model.setDataProperty and modify the model data. Then having the Binding be TwoWay would no longer be necessary.

Regarding your other question, what would you expect the layout to do? I don’t even know what kind of layout you might be using. Unless, it’s the default Layout, which I think does behave the way that you want.

Hi Walter, sorry for the delay. I implemented the two-way binding and all the logic to save/load nodes’s position and the result is quite good, except for the layout. I am using the SideTreeLayout because I need of the side positioning for staff/assistant nodes. Before manual positioning been inplemented everything was coerent but now, if I expand a collapsed node, the resulting layout can be really bad, with the expanded tree completely overlapping some fixed node even if there is a lot of extra space where the expanded nodes could have been positioned. How can I have a layout algorithm that take into account also of the fixed position nodes?
Following images should clarify the problem

before…

and after …

thanks for your help
Marco

TreeLayout does not take into account any Nodes or Links that it does not lay out. It’s as if those Nodes or Links do not exist. Sorry.

Maybe you shouldn’t be setting or binding isLayoutPositioned at all. Instead, have a “SelectionMoved” DiagramEvent listener record the offset from the originally laid-out location for each moved node. Then customize the layout to do its normal thing and then in an override of TreeLayout.commitLayout call the super method and then shift those nodes for which you have recorded that the user has manually shifted.

So initially the normal layout happens and the user hasn’t moved anything. When the user moves a node, you record the offset from its original location. (If a node is moved multiple times, just add up the offsets.) That way when a layout happens again, the node will be placed normally but then your code will shift it by the amount that the user had shifted it. That won’t prevent overlapping nodes, since the rest of the tree may have been reshaped, but for small movements whatever the user did will be remembered.

Hi Walter, sorry for the delay, your suggestion is good but as you said it doesn’t solve completely the problem, because a simple expansion of a node could lead to an overlapping, because the entire graph could have been moved on top of some fixed nodes. Since this is a really common problem when Organization charts are involved, do you think Noortwoods in going to face it and develop a layout that can handle nodes with fixed location and nodes laid automatically considering the fixed nodes?
Another common problem is having lots of node at same level. At the moment GoJs place them on a single row that can be very long instead of arrange them on rows. How could this be achieved with GoJS?
Thank you in advance,
Marco

I can’t promise anything within a reasonable time from your point of view. But for TreeLayout you could try setting TreeLayout | GoJS API to some appropriate value for your graphs.

That, along with many other properties, can be set on individual TreeVertexes by overriding TreeLayout.assignTreeVertexValues.

Walter , thank you very much. Your suggestions looks very promising, I will look into it.

Marco

Hi Walter, in these days we are discussing with some (possible) customers about the possibility of having a mix of fixed and automatic positioned nodes correctly laid out by the GoJs layout. I’d like to know if, in the meantime, something is changed on this subject. If not, I am going to try to develop a custom layout by myself and I’d like if you can give me some pointers from where I can start from.
For example, besides of you previous suggestion, I am wondering if another approach could be to assign a sort of repulsion force or weight to the fixed nodes, somewhat similar to what is done in the ForceDirectedLayout that I recently discovered. If this can be a reasonable way of proceeding, how can I combine all this with the OrgChart Assistant layout that we are already using?
Thank you very much for your assistance
Marco

There are a lot of possibilities, and I cannot tell what you want. I don’t even know which kind of layout you are using and how much you want to ensure that the results match the results of a complete layout.

One possibility is demonstrated by this sample: Automatically Shift Nodes Aside Righward to Make Room. Note that this only moves nodes towards the right; you might be interested in only shifting downwards, or both rightwards and downwards.

Or did you want something else? If you provided before-and-after screenshots of what you are looking for, that would help.