You could do something like this:
function init() {
var $ = go.GraphObject.make;
myDiagram =
$(go.Diagram, "myDiagramDiv",
{
"undoManager.isEnabled": true,
layout: $(go.TreeLayout)
}
);
// define a simple Node template
myDiagram.nodeTemplate =
$(go.Node, "Spot", // the Shape will go around the TextBlock
new go.Binding("visible", "isOther", function (o) { return !o; }),
$(go.Panel, "Auto",
$(go.Shape, "RoundedRectangle", { strokeWidth: 0 },
// Shape.fill is bound to Node.data.color
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8 }, // some room around the text
// TextBlock.text is bound to Node.data.key
new go.Binding("text", "key"))
),
makeCustomExpander(go.Spot.Right, false),
makeCustomExpander(go.Spot.TopRight, true)
);
function makeCustomExpander(align, others) {
return $("Button",
{
alignment: align,
click: function(e, obj) {
var node = obj.part; // get the Node containing this Button
if (node === null) return;
e.handled = true;
expandNode(node, others);
}
},
$(go.Shape, // the icon
{
name: 'ButtonIcon',
figure: 'MinusLine',
stroke: '#424242',
strokeWidth: 2,
desiredSize: new go.Size(8, 8)
},
// bind the Shape.figure to the node.data._othersExpanded value using this converter:
new go.Binding('figure', others ? '_othersExpanded' : '_primariesExpanded',
function (exp, shape) {
var but = shape.panel;
return exp ? 'MinusLine' : 'PlusLine';
}
)
),
// assume initially not visible because there are no links coming out
{ visible: false },
// bind the button visibility to whether it's not a leaf node
new go.Binding('visible', 'isTreeLeaf',
function (leaf) { return !leaf; }
).ofObject()
);
}
function expandNode(node, others) {
var diagram = node.diagram;
var nd = node.data;
diagram.startTransaction("CollapseExpandTree");
var children = node.findTreeChildrenNodes();
children.each(function(c) {
var cd = c.data;
if (others && !cd.isOther) return;
if (!others && cd.isOther) return;
if (c.visible) {
c.collapseTree();
c.visible = false;
} else {
c.visible = true;
c.expandTree();
}
});
if (others) {
myDiagram.model.set(node.data, '_othersExpanded', !node.data._othersExpanded);
} else {
myDiagram.model.set(node.data, '_primariesExpanded', !node.data._primariesExpanded);
}
diagram.commitTransaction("CollapseExpandTree");
}
// create the model data that will be represented by Nodes and Links
myDiagram.model = new go.TreeModel(
[
{ key: "Alpha", _othersExpanded: false, _primariesExpanded: true, color: "lightblue" },
{ key: "Beta", _othersExpanded: false, _primariesExpanded: true, color: "orange", parent: "Alpha" },
{ key: "Gamma", _othersExpanded: false, _primariesExpanded: true, color: "lightgreen", parent: "Alpha" },
{ key: "Delta", _othersExpanded: false, _primariesExpanded: true, color: "pink", isOther: true, parent: "Alpha" }
]);
}
One button expands/collapses any nodes that are not “isOther”, while the other does the opposite. The “_primariesExpanded” and “_othersExpanded” properties keep track of whether the subtrees are expanded or not, similar to the isTreeExpanded property for a typical tree.
Demo: https://codepen.io/jhardy/pen/eYWrVGr?editors=1010