TreeLayout

I have a treeLayout and all arrows point to their relevant children. Let’s say we have parent node that points to two different types of children, related and foster children. When they are foster children I want to treat them differently depending on the easiest implementation.

How do I implement placing the foster children in a group of the parent node instead of a child of the parent? Or If I can have a special port coming from specific location(top) of the parent node allocated for foster children only.

I don’t understand what you want. A sketch would help. Where did you want to place the foster children?

Actually, if you want to use different ports at the parent node for the different kinds of children, that’s pretty easy to arrange. You need to make sure there are actually two output ports on each parent node, and you need to bind the Link’s fromPortId to choose one port name or the other depending on the child node’s type.

What I was originally expecting you to ask for was sorting the children so that all of one type were first and all of the other type were last. You can do that by setting TreeLayout.sorting and TreeLayout.comparer.

From the above screenshot, I would like the FOSTER nodes to expand from the top of the parent when clicked on. A second option would be to have a treeexpander inside of the parent that would expand the FOSTER children when clicked on. The CHILD nodes would remain the same.

function init(){

var $ = go.GraphObject.make;

window.onload = function() { appendStyle(styles) };

var div = document.createElement(“div”);
div.id = ‘cssStyle’;
div.innerHTML=‘style=“display: none; font: bold”’;
document.body.appendChild(div);

var myDiagram =
$(go.Diagram, “myDiagram”,

<span =“Apple-tab-span” style=“white-space:pre”> { <span =“Apple-tab-span” style=“white-space:pre”> layout:
<span =“Apple-tab-span” style=“white-space:pre”> $(go.TreeLayout,{ angle: 180, nodeSpacing: 10 })

});

var sngModel = $(go.TreeModel);

sngModel.nodeDataArray = [
{key: 0, name: “SOCIALNETWORK”, nodeType: “DEPLOYMENT”, parent:0},
{key: 1, name: “PARENT_A”, nodeType: “WINDOW”, parent: 0},
{key: 2, name: “CHILD_A”, nodeType: “TABLE”, parent: 1},
{key: 3, name: “FOSTER_A”, nodeType: “SCENARIO”, parent: 1},
{key: 4, name: “FOSTER_B”, nodeType: “SCENARIO”, parent: 1},
<span =“Apple-tab-span” style=“white-space:pre”> {key: 5, name: “PARENT_B”, nodeType: “WINDOW”, parent: 0},
<span =“Apple-tab-span” style=“white-space:pre”> {key: 6, name: “CHILD_A”, nodeType: “TABLE”,parent: 5},
{key: 7, name: “FOSTER_A”, nodeType: “SCENARIO”, parent: 5},
{key: 8, name: “PARENT_C”, nodeType: “WINDOW”, parent: 5},
<span =“Apple-tab-span” style=“white-space:pre”> {key: 9, name: “CHILD_C”, nodeType: “TABLE”,parent: 8 },
{key: 10, name: “FOSTER_A”, nodeType: “SCENARIO”, parent: 8},
{key: 11, name: “FOSTER_B”, nodeType: “SCENARIO”, parent: 8},
<span =“Apple-tab-span” style=“white-space:pre”> {key: 12, name: “FOSTER_C”, nodeType: “SCENARIO”,parent: 8}
];

myDiagram.model = sngModel;

function tooltipNameConverter(node) {
<span =“Apple-tab-span” style=“white-space:pre”> return node.nodeType + ': ’ + node.name;}

function textStyle() {
<span =“Apple-tab-span” style=“white-space:pre”> return{

<span =“Apple-tab-span” style=“white-space:pre”> font: “bold 8pt Helvetica, bold Arial, sans-serif”,
<span =“Apple-tab-span” style=“white-space:pre”> alignment: go.Spot.Left,
<span =“Apple-tab-span” style=“white-space:pre”> margin: 16,
<span =“Apple-tab-span” style=“white-space:pre”> width: 145
<span =“Apple-tab-span” style=“white-space:pre”> };
<span =“Apple-tab-span” style=“white-space:pre”> }

function tooltipStyle() {
<span =“Apple-tab-span” style=“white-space:pre”> return{

<span =“Apple-tab-span” style=“white-space:pre”> font: “bold 8pt Helvetica, bold Arial, sans-serif”,
<span =“Apple-tab-span” style=“white-space:pre”> alignment: go.Spot.Left,
<span =“Apple-tab-span” style=“white-space:pre”> margin: 16,
<span =“Apple-tab-span” style=“white-space:pre”> width: 175
<span =“Apple-tab-span” style=“white-space:pre”> };
<span =“Apple-tab-span” style=“white-space:pre”> }

function nodeTypeBrushConverter(nodeType) {
<span =“Apple-tab-span” style=“white-space:pre”> var returnColor = “#E8EBEE”;
<span =“Apple-tab-span” style=“white-space:pre”> return returnColor;
<span =“Apple-tab-span” style=“white-space:pre”> }

myDiagram.nodeTemplate =
<span =“Apple-tab-span” style=“white-space:pre”> $(go.Node, “Auto”,
<span =“Apple-tab-span” style=“white-space:pre”> { deletable: false},
<span =“Apple-tab-span” style=“white-space:pre”> $(go.Shape, “RoundedRectangle”,
<span =“Apple-tab-span” style=“white-space:pre”> <span =“Apple-tab-span” style=“white-space:pre”> {fill: “#E8EBEE”,
<span =“Apple-tab-span” style=“white-space:pre”> stroke: “#99A2A7”,
<span =“Apple-tab-span” style=“white-space:pre”> width: 175,
<span =“Apple-tab-span” style=“white-space:pre”> height: 80
}, new go.Binding(“fill”, “nodeType”, nodeTypeBrushConverter)),
$(go.Picture,
<span =“Apple-tab-span” style=“white-space:pre”> new go.Binding(“source”, “”, function (data) {
<span =“Apple-tab-span” style=“white-space:pre”> var path = “images/”;
<span =“Apple-tab-span” style=“white-space:pre”> var url = path + data.nodeType + “.png”;
<span =“Apple-tab-span” style=“white-space:pre”> return url;
<span =“Apple-tab-span” style=“white-space:pre”> }),
<span =“Apple-tab-span” style=“white-space:pre”> {alignment: go.Spot.TopRight,
<span =“Apple-tab-span” style=“white-space:pre”> width: 24,
<span =“Apple-tab-span” style=“white-space:pre”> height: 24
}),
<span =“Apple-tab-span” style=“white-space:pre”> $(go.TextBlock,
new go.Binding(“text”, “name”)
),

 <span ="Apple-tab-span" style="white-space:pre">	</span>$(go.Panel,

<span =“Apple-tab-span” style=“white-space:pre”> { height: 15,
<span =“Apple-tab-span” style=“white-space:pre”> alignment: go.Spot.Left,
<span =“Apple-tab-span” style=“white-space:pre”> alignmentFocus: go.Spot.Center,
<span =“Apple-tab-span” style=“white-space:pre”> },
<span =“Apple-tab-span” style=“white-space:pre”> $(“TreeExpanderButton”)
)
<span =“Apple-tab-span” style=“white-space:pre”> );

myDiagram.linkTemplate =
<span =“Apple-tab-span” style=“white-space:pre”> $(go.Link, // the whole link panel
<span =“Apple-tab-span” style=“white-space:pre”> $(go.Shape, // the link path shape
{ isPanelMain: true, stroke: “#99A2A7”, strokeWidth: 1 }),
$(go.Shape, // the arrowhead
{ fromArrow: “backward”, stroke: null, fill: “#99A2A7”}));

}
init();

Could you point me to a simple example of using Link’s fromPortId to choose one port name or the other depending on the node type?

Well, I suppose the Decision Tree sample does that: Decision Tree. Each node has two output ports.

Note that if you want to expand/collapse different subtrees from a given node, you won’t be able to use a standard “TreeExpanderButton”. That’s why Decision Tree has to use a regular “Button” with a click event handler that uses the particular port to decide what to “collapse”.

But I suspect you could do something a lot simpler than what the Decision Tree sample does.

I noticed that once I configure my own ‘Button’ to expand/collapse the subtrees does this mean I now must create my own linkDataArray because the ‘parent’ keyword in the nodeDataArray is not longer being used?

Can you give me any advice on recreating what ‘parent’ does for the nodeDataArray?

You’re using a TreeModel, not a GraphLinksModel, right? And you’d like to continue to do so? That’s OK. Notice that the “buttonExpandCollapse” function in the Decision Tree sample is passed a port element, so it knows which port’s links to follow.

I’m trying to follow the Decision Tree example but getting lost with the reference to the GraphLinksModel and building of the linkDataArray.

I would like to continue using the TreeModel but I’m not clear how to use an additional port with the TreeModel.

Could you provide a simplified example? See the sketch above. I just desire to add a port at the top of the node for foster nodes.

var model =
      $(go.GraphLinksModel,
        { linkFromPortIdProperty: "fromport" });
<pre -="" =" hljs " style=“font-size: 9pt; padding: 0.5em; color: rgb230, 225, 220; line-height: normal; : rgb35, 35, 35;”><pre -="" =" hljs " style=“font-size: 9pt; padding: 0.5em; -: initial; -attachment: initial; -size: initial; -origin: initial; -clip: initial; -: initial; -repeat: initial;”><span =“hljs-”><span =“hljs-keyword” style=“color: rgb194, 98, 48;”>function <span =“hljs-title” style=“color: rgb255, 198, 109;”>makeLinks<span =“hljs-s” style=“color: rgb208, 208, 255;”>(model) {
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>var linkDataArray = [];
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>var nda = model.nodeDataArray;
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>for (<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>var i = <span =“hljs-number” style=“color: rgb165, 194, 97;”>0; i < nda.length; i++) {
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>var key = nda.key;
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>if (key === <span =“hljs-” style=“color: rgb165, 194, 97;”>“Start” || key.length === <span =“hljs-number” style=“color: rgb165, 194, 97;”>0) <span =“hljs-keyword” style=“color: rgb194, 98, 48;”>continue;
<span =“hljs-comment” style=“color: rgb188, 148, 88; font-style: italic;”>// e.g., if key==“INTJ”, we want: prefix=“INT” and letter=“J”
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>var prefix = key.slice(<span =“hljs-number” style=“color: rgb165, 194, 97;”>0, key.length-<span =“hljs-number” style=“color: rgb165, 194, 97;”>1);
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>var letter = key.charAt(key.length-<span =“hljs-number” style=“color: rgb165, 194, 97;”>1);
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>if (prefix.length === <span =“hljs-number” style=“color: rgb165, 194, 97;”>0) prefix = <span =“hljs-” style=“color: rgb165, 194, 97;”>“Start”;
<span =“hljs-keyword” style=“color: rgb194, 98, 48;”>var obj = { from: prefix, fromport: letter, to: key };
linkDataArray.push(obj);
}
model.linkDataArray = linkDataArray;
}

OK, I copied the DOM Tree sample, HTML DOM Tree, and modified it so that each node has two buttons, each of which is a port. The top one is named “T” and the bottom one is named “B”. Each button’s click event handler calls a function to toggle the state of the subtree connecting with that port. I didn’t bother giving the buttons any content, so the buttons are empty looking, but they still work.

I changed the node data by adding an “element” property that keeps a reference to the HTML DOM element in the page that the diagram is displaying. That allowed the addition of a Binding in the Link template so that the “fromPortId” would be bound to “T” if the child node’s element is a “DIV” element and “B” otherwise. And I had the TreeLayout sort the children so that the “DIV” children would come first, i.e. on top.

Here’s just the code that was modified:

[code] myDiagram =
$(go.Diagram, “myDiagram”,
{
initialAutoScale: go.Diagram.UniformToFill,
// define the layout for the diagram
layout: $(go.TreeLayout,
{ nodeSpacing: 5, layerSpacing: 30,
sorting: go.TreeLayout.SortingAscending,
comparer: function(v, w) {
if (v.node.data.element.nodeName === “DIV”) return -1;
if (w.node.data.element.nodeName === “DIV”) return 1;
return 0;
}
}
)
});

// Define a simple node template consisting of text followed by an expand/collapse button
myDiagram.nodeTemplate =
  $(go.Node, <font color="#FF0000">"Vertical",</font>
    { selectionChanged: nodeSelectionChanged },  // this event handler is defined below
    <font color="#FF0000">{ defaultAlignment: go.Spot.Right },
    $("Button", { portId: "T", click: collapseExpand, width: 8, height: 8 }),</font>
    $(go.Panel, "Auto",
      $(go.Shape, { fill: "darkslategray", stroke: null }),
      $(go.TextBlock,
        { font: "bold 13px Helvetica, bold Arial, sans-serif",
          stroke: "white", margin: 3 },
        new go.Binding("text", "key"))
    ),
    <font color="#FF0000">$("Button", { portId: "B", click: collapseExpand, width: 8, height: 8 })</font>
  );

function collapseExpand(e, port) {
var portid = port.portId;
var node = port.part;
node.diagram.startTransaction(“collapse/expand subtree”);
var vis = node.findLinksOutOf(portid).any(function(l) { return l.isVisible(); });
node.findNodesOutOf(portid).each(function(n) { n.isTreeExpanded = !vis; n.visible = !vis; });
node.diagram.commitTransaction(“collapse/expand subtree”);
}

// Define a trivial link template with no arrowhead.
myDiagram.linkTemplate =
  $(go.Link,
    { selectable: false<font color="#FF0000">, fromPortId: "B"</font> },
   <font color="#FF0000"> new go.Binding("fromPortId", "element", function(e) { return e.nodeName === "DIV" ? "T" : "B"; }),</font>
    $(go.Shape));  // the link shape


var nodeDataArray = [];

// Walk the DOM, starting at document
function traverseDom(node, parentName) {
  // skip everything but HTML Elements
  if (!(node instanceof Element)) return;
  // Ignore the menu on the left of the page
  if (node.id === "menu") return;
  // add this node to the nodeDataArray
  var name = getName(node);
  var data = { key: name, name: name<font color="#FF0000">, element: node</font> };
  nodeDataArray.push(data);
  // add a link to its parent
  if (parentName !== null) {
    data.parent = parentName;
  }
  // find all children
  var l = node.childNodes.length;
  for (var i = 0; i < l; i++) {
    traverseDom(node.childNodes<em>, name);
  }
}[/code]

I have marked the added code in red.

I am totally baffled how to control the location of the buttons. I tried placing the button inside the a PANEL type of Table but still cannot get a button to the topMiddle of a node that still expands. Any pointers?

var tooltipTemplate =
<span =“Apple-tab-span” style=“white-space:pre”> $(go.Adornment, “Auto”,
<span =“Apple-tab-span” style=“white-space:pre”> $(go.Shape, “Rectangle”,
<span =“Apple-tab-span” style=“white-space:pre”> { fill: “#E8EBEE”, stroke: “#99A2A7” }),
<span =“Apple-tab-span” style=“white-space:pre”> $(go.TextBlock,
<span =“Apple-tab-span” style=“white-space:pre”> new go.Binding(“text”, “”, tooltipNameConverter))
<span =“Apple-tab-span” style=“white-space:pre”> );
function nodeTypeBrushConverter(nodeType) {
var returnColor = “#E8EBEE”;
return returnColor;
}

myDiagram.nodeTemplate =
$(go.Node, “Horizontal”,
{ selectionChanged: nodeSelectionChanged },
{ defaultAlignment: go.Spot.Right,
fromSpot: go.Spot.RightSide,
toSpot: go.Spot.LeftSide},

             $("Button",
            { portId: "T",
              name: "ButtonA",
              alignment: go.Spot.Center,
              click: function (e, obj) { buttonExpandCollapse(obj); },
              toolTip: tooltipTemplate
            },
            new go.Binding("portId", "T"),
            $(go.TextBlock,
              new go.Binding("text", "aText"))
          ),  $(go.Panel, "Auto",
                    $(go.Shape, "RoundedRectangle",
                        {
                            fill: "#E8EBEE",
                            stroke: "#99A2A7",
                            /*portId: "", cursor: "pointer",*/
                            fromLinkable: false,
                            toLinkable: false,
                            width: 175,
                            height: 80
                        }) //end shape
                        ) ,// end  Panel
            $("Button",

<span =“Apple-tab-span” style=“white-space:pre”> { portId: “B”,
<span =“Apple-tab-span” style=“white-space:pre”> name: “ButtonB”,
<span =“Apple-tab-span” style=“white-space:pre”> alignment: go.Spot.Center,

<span =“Apple-tab-span” style=“white-space:pre”> click: function (e, obj) { buttonExpandCollapse(obj); },
<span =“Apple-tab-span” style=“white-space:pre”> toolTip: tooltipTemplate
<span =“Apple-tab-span” style=“white-space:pre”> },
<span =“Apple-tab-span” style=“white-space:pre”> new go.Binding(“portId”, “B”),
<span =“Apple-tab-span” style=“white-space:pre”> $(go.TextBlock,
<span =“Apple-tab-span” style=“white-space:pre”> new go.Binding(“text”, “bText”))
)

                        );//end go.Node

Well, the whole Node is a Horizontal Panel, so that means the elements are positioned from left to right. In this case it appears that the Button that is the “T” port will be on the left, followed by a TextBlock, followed by a Panel containing only one element, a RoundedRectangle Shape. Most panels only make sense when there are multiple elements in them.

I do recommend learning about panels at GoJS Panels -- Northwoods Software, and the following page about Table Panels.