Angle-changing tree layout

I’m quite new with GoJS and I would like to make an UML class diagram. I’m using the sample to begin. To make the diagram as easy to read as possible, I would like the generalizations to use a vertical tree layout (angle = 90°) and the aggregations to use an horizontal tree layout (angle = 0°), like in this picture (where I have just moved Course class manually for the example) :

The Course class would be in a tree with an 0° angle.

I could make tree layouts on aggregations by changing the convertIsTreeLink function like this:

function convertIsTreeLink(r) {
     return (r === "generalization" || r === "aggregation");
}

but I could not change the angle only for aggregations without alternate generalizations too.

Is it possible to use both angles like in the pictures above?

Thanks for your help

I really don’t know UML, so pardon me if I am making some invalid assumptions or conclusions.

I do not think that what you are asking for is well-specified. After all, Students can have a bunch of Courses too. Perhaps you are thinking about the “composition” relationship, which this sample does not bother to implement.

TreeLayout cannot do exactly what you are asking for, if I understand you correctly, at least not without some effort. However it is relatively easy to get part of the way there.

  function TwoWayTreeLayout() {
    go.TreeLayout.call(this);
  }
  go.Diagram.inherit(TwoWayTreeLayout, go.TreeLayout);

  TwoWayTreeLayout.prototype.assignTreeVertexValues = function(v) {
    var pedge = v.destinationEdges.first();
    v.angle = (pedge && pedge.link.data.relationship === "aggregation") ? 0 : 90;
  };

  TwoWayTreeLayout.prototype.commitLayout = function() {
    go.TreeLayout.prototype.commitLayout.call(this);
    var fdlay = new TwoWayForceDirectedLayout();
    fdlay.doLayout(this.diagram);
  };

  function TwoWayForceDirectedLayout() {
    go.ForceDirectedLayout.call(this);
  }
  go.Diagram.inherit(TwoWayForceDirectedLayout, go.ForceDirectedLayout);

  TwoWayForceDirectedLayout.prototype.isFixed = function(v) {
    return v.edges.any(function(e) { return e.link.data.relationship === "generalization"; });
  };

  TwoWayForceDirectedLayout.prototype.electricalCharge = function(v) {
    return (v.edgesCount === 0) ? 5 : 25;
  };

This is a custom TreeLayout that arranges subtrees of “generalization” related nodes with angle 90 and subtrees of “aggregation” related nodes with angle 0. But it does not arrange the root of an “aggregation” tree to be immediately to the right of a node that was laid out in a tree related by “generalization”.

Furthermore, after the TreeLayout is done it uses a custom ForceDirectedLayout to leave the nodes connected by “generalization” alone and move the other nodes so that they are closer to what they are related to, because there might be multiple “aggregation” relationships for any node.

I installed the custom layout in the Diagram initialization as follows:

          layout: $(TwoWayTreeLayout,
                    {
                      path: go.TreeLayout.PathSource,  // links go from child to parent
                      setsPortSpot: false,  // keep Spot.AllSides for link connection spot
                      setsChildPortSpot: false,  // keep Spot.AllSides
                      // nodes not connected by "generalization" links are laid out horizontally
                      arrangement: go.TreeLayout.ArrangementVertical
                    })

I also removed the Binding of Link.isLayoutPositioned:

//new go.Binding("isLayoutPositioned", "relationship", convertIsTreeLink),

Then I deleted the “BankAccount” node and added some simple nodes and aggregation links:

      },
      { key: 15, name: "CoursePart1" },
      { key: 16, name: "CoursePart2" },
      { key: 17, name: "CoursePiece" }
    ];
    var linkdata = [
      { from: 12, to: 11, relationship: "generalization" },
      { from: 13, to: 11, relationship: "generalization" },
      { from: 14, to: 13, relationship: "aggregation" },
      { from: 14, to: 12, relationship: "aggregation" },
      { from: 15, to: 14, relationship: "aggregation" },
      { from: 16, to: 14, relationship: "aggregation" },
      { from: 17, to: 15, relationship: "aggregation" }
    ];

The result is:

Works like a charm, thanks for your help :)