How to change position and order Nodes for Assistants

Hello,
I took an example of adding Assistants from your site ( Org Chart Editor with Assistants )
I need to change alignment and ordering for this.

  1. Alignment
    When even elements Alignment is OK.
    Alignment is staggered when odd elements.
    I need that elements on the left side will be on the contrary of right side.
    How to do it?

  2. Order
    Order changes when I add new Assistant. There is different order for even and odd elements. There is wrong order for even elements.
    for 1 element order is OK
    for 3 elements order is OK
    for 2 elements order is WRONG
    for 4 elements order is WRONG

what should I do to set right order for even elements?
2 -
4 -

You could test it here Plunker - Gojs Assistant (click right mouse button on Node and add Assistant)

I think that’s a matter of ordering the nodes. I’ll work on a modified sample.

Here’s a version of SideTreeLayout that sorts assistants.

  // This is a custom TreeLayout that knows about "assistants".
  // A Node for which isAssistant(n) is true will be placed at the side below the parent node
  // but above all of the other child nodes.
  // An assistant node may be the root of its own subtree.
  // An assistant node may have its own assistant nodes.
  function SideTreeLayout() {
    go.TreeLayout.call(this);
  }
  go.Diagram.inherit(SideTreeLayout, go.TreeLayout);

  SideTreeLayout.prototype.makeNetwork = function(coll) {
    var net = go.TreeLayout.prototype.makeNetwork.call(this, coll);
    // copy the collection of TreeVertexes, because we will modify the network
    var vertexcoll = new go.Set(go.TreeVertex);
    vertexcoll.addAll(net.vertexes);
    for (var it = vertexcoll.iterator; it.next() ;) {
      var parent = it.value;
      // count the number of assistants
      var acount = 0;
      var ait = parent.destinationVertexes;
      while (ait.next()) {
        if (isAssistant(ait.value.node)) acount++;
      }
      // if a vertex has some number of children that should be assistants
      if (acount > 0) {
        // remember the assistant edges and the regular child edges
        var asstedges = new go.Set(go.TreeEdge);
        var childedges = new go.Set(go.TreeEdge);
        var eit = parent.destinationEdges;
        while (eit.next()) {
          var e = eit.value;
          if (isAssistant(e.toVertex.node)) {
            asstedges.add(e);
          } else {
            childedges.add(e);
          }
        }
        // first remove all edges from PARENT
        eit = asstedges.iterator;
        while (eit.next()) { parent.deleteDestinationEdge(eit.value); }
        eit = childedges.iterator;
        while (eit.next()) { parent.deleteDestinationEdge(eit.value); }
        // if the number of assistants is odd, add a dummy assistant, to make the count even
        if (acount % 2 == 1) {
          var dummy = net.createVertex();
          dummy.bounds = asstedges.first().toVertex.bounds;
          net.addVertex(dummy);
          net.linkVertexes(parent, dummy, asstedges.first().link);
        }
        // now PARENT should get all of the assistant children
        eit = asstedges.iterator;
        while (eit.next()) {
          parent.addDestinationEdge(eit.value);
        }
        // create substitute vertex to be new parent of all regular children
        var subst = net.createVertex();
        net.addVertex(subst);
        // reparent regular children to the new substitute vertex
        eit = childedges.iterator;
        while (eit.next()) {
          var ce = eit.value;
          ce.fromVertex = subst;
          subst.addDestinationEdge(ce);
        }
        // finally can add substitute vertex as the final odd child,
        // to be positioned at the end of the PARENT's immediate subtree.
        var newedge = net.linkVertexes(parent, subst, null);
      }
    }
    return net;
  };

  SideTreeLayout.prototype.assignTreeVertexValues = function(v) {
    // if a vertex has any assistants, use Bus alignment
    var any = false;
    var children = v.children;
    for (var i = 0; i < children.length; i++) {
      var c = children[i];
      if (isAssistant(c.node)) {
        any = true;
        break;
      }
    }
    if (any) {
      // this is the parent for the assistant(s)
      v.alignment = go.TreeLayout.AlignmentBus;  // this is required
      v.nodeSpacing = 50; // control the distance of the assistants from the parent's main links
    } else if (v.node == null && v.childrenCount > 0) {
      // found the substitute parent for non-assistant children
      //v.alignment = go.TreeLayout.AlignmentCenterChildren;
      //v.breadthLimit = 3000;
      v.layerSpacing = 0;
    }
  };

  SideTreeLayout.prototype.sortTreeVertexChildren = function(v) {
    var arr = [];  // assistants
    var other = [];  // non-assistants
    var children = v.children;
    for (var i = 0; i < children.length; i++) {
      var c = children[i];
      if (isAssistant(c.node)) arr.push(c); else other.push(c);
    }
    // only sort assistants
    arr.sort(function(va, vb) {
      if (va.node === null) return 1;
      var da = va.node.data;
      if (da === null) return 1;
      if (vb.node === null) return -1;
      var db = vb.node.data;
      if (db === null) return -1;
      if (da.key < db.key) return -1;
      if (da.key > db.key) return 1;
      return 0;
    });
    for (var i = 0; i < arr.length; i++) {
      children[i] = arr[i];
    }
    for (var j = 0; j < other.length; j++) {
      children[i++] = other[j];
    }
  };

  SideTreeLayout.prototype.setPortSpots = function(v) {
    // if a vertex has any assistants, use Bus alignment
    var any = false;
    var children = v.children;
    var childrenLength = children.length;
    for (var i = 0; i < childrenLength; i++) {
      var c = children[i];
      if (isAssistant(c.node)) {
        any = true;
        break;
      }
    }
    if (any) {
      var angle = v.angle;
      var parentspot;
      switch (angle) {
        case 0: parentspot = go.Spot.MiddleRight; break;
        case 90: parentspot = go.Spot.MiddleBottom; break;
        case 180: parentspot = go.Spot.MiddleLeft; break;
        default: parentspot = go.Spot.MiddleTop; break;
      }
      for (var i = 0; i < childrenLength; i++) {
        var c = children[i];
        var e = c.sourceEdges.first();
        if (c.node === null || e === null || e.link === null) continue;
        var childspot = (angle === 90 || angle === 270) ? go.Spot.MiddleLeft : go.Spot.MiddleTop;
        if (childrenLength === 1 || (i === childrenLength - 1 && childrenLength % 2 === 1)) {
          switch (angle) {
            case 0: childspot = go.Spot.MiddleLeft; break;
            case 90: childspot = go.Spot.MiddleTop; break;
            case 180: childspot = go.Spot.MiddleRight; break;
            default: childspot = go.Spot.MiddleBottom; break;
          }
        } else {
          if (i % 2 === 0) {
            childspot = (angle === 90 || angle === 270) ? go.Spot.MiddleRight : go.Spot.MiddleBottom;
          }
        }
        if (v.setsPortSpot) e.link.fromSpot = parentspot;
        if (v.setsChildPortSpot) e.link.toSpot = childspot;
      }
    } else {
      go.TreeLayout.prototype.setPortSpots.call(this, v);
    }
  };

  SideTreeLayout.prototype.commitLinks = function() {
    go.TreeLayout.prototype.commitLinks.call(this);
    // make sure the middle segment of an orthogonal link does not cross over the assistant subtree
    var eit = this.network.edges.iterator;
    while (eit.next()) {
      var e = eit.value;
      if (e.link == null) continue;
      var r = e.link;
      // does this edge come from a substitute parent vertex?
      var subst = e.fromVertex;
      if (subst.node == null && r.routing == go.Link.Orthogonal) {
        r.updateRoute();
        r.startRoute();
        // middle segment goes from point 2 to point 3
        var p1 = subst.center;  // assume artificial vertex has zero size
        var p2 = r.getPoint(2).copy();
        var p3 = r.getPoint(3).copy();
        var p5 = r.getPoint(r.pointsCount - 1);
        var dist = 10;
        if (subst.angle == 270 || subst.angle == 180) dist = -20;
        if (subst.angle == 90 || subst.angle == 270) {
          p2.y = p5.y - dist; // (p1.y+p5.y)/2;
          p3.y = p5.y - dist; // (p1.y+p5.y)/2;
        } else {
          p2.x = p5.x - dist; // (p1.x+p5.x)/2;
          p3.x = p5.x - dist; // (p1.x+p5.x)/2;
        }
        r.setPoint(2, p2);
        r.setPoint(3, p3);
        r.commitRoute();
      }
    }
  };  // end of SideTreeLayout

It throws error somewhere.

Is it enough to catch the bug?

No, that’s not enough for me to catch the bug. I notice it’s happening when expanding a Group in your _subGraphToggle code – what does that code do?

_subGraphToggle (e, button) {
       let group = button.part;
       if (group instanceof go.Adornment) group = group.adornedPart;
       if (!(group instanceof go.Group)) return;
       let diagram = group.diagram;
       if (diagram === null) return;
       
       group.memberParts.each((position) => {
           if (position.isSelected) {
               diagram.clearSelection();
           }
       });

       let cmd = diagram.commandHandler;
       if (group.isSubGraphExpanded) {
           if (!cmd.canCollapseSubGraph(group)) return;
       } else {
           if (!cmd.canExpandSubGraph(group)) return;
       }
       e.handled = true;
       if (group.isSubGraphExpanded) {
           cmd.collapseSubGraph(group);
       } else {
           cmd.expandSubGraph(group); // <- this is the beginning of error
       }
       const rect = group.actualBounds;
       let height = Math.min(rect.height, group.diagram.viewportBounds.height);
       group.diagram.scrollToRect(new go.Rect(rect.x, rect.y, rect.width, height));
   }

without functions sortTreeVertexChildren and setPortSpots it works fine

That’s odd – the error is not happening inside the execution of SideTreeLayout at all.

If you only comment-out the override SideTreeLayout.setPortSpots, does the error still happen?

If I comment out only SideTreeLayout.setPortSpots error still exist. If to comment out SideTreeLayout.sortTreeVertexChildren it works fine.

If you remove the call to Diagram.clearSelection, does it work? (Keeping setPortSpots in SideTreeLayout.)

Error still exist

Before I gave you the code above, and just now, I used the enhanced SideTreeLayout in my modified Org Chart Assistants sample. I tried a lot of graphs to make sure that everything worked. Or at least seemed to, without any errors.

So I don’t know how to reproduce the problem. Can you give me a reproducible case? Precisely which version of GoJS are you using?

I can’t reproduce this bug at plunker too.

It seems this bug is thrown by another place. I need to debug my app to understand what part do it.
I will write when found the reason.

I’m using 1.8.2

This is part of my project

Click on “Child” and on icon above then look at console. You will see an error

I got an error in sortTreeVertexChildren. My JavaScript code (above) was:

    for (var i = 0; i < arr.length; i++) {
      children[i] = arr[i];
    }
    for (var j = 0; j < other.length; j++) {
      children[i++] = other[j];
    }

Your EcmaScript 2015 code is:

          for (let i = 0; i < arr.length; i++) {
              children[i] = arr[i];
          }
          for (let j = 0; j < other.length; j++) {
              children[i++] = other[j];
          }

See the difference with the scope of the variable i? I’m surprised you didn’t get a compiler error there.

I’m going to mark this topic “Solved”.

Thanks @walter You are right, it solved my problem.

How to change direction of positions?
I’d like to add first Assistant on the right side of Parent

Change the order?

Yes, order. How it described on my first topic

Did you not read the code I gave you?

I read it and I use it also I add it to plunker.

Now first assistant is created on Left side

I want to see first assistant on Right side

You could check it