Try this:
<!DOCTYPE html>
<html>
<head>
<title>Rectangular Pack Layout</title>
<!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
<meta name="description" content="RectangularPackLayout fills a rectangular area with nodes that are sized according to their fraction of the total data.size property values." />
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
<script src="../latest/release/go.js"></script>
<script id="code">
// This layout arranges and sizes each Node or simple Part so as to pack a rectangular area.
// This is like the TreeMapLayout extension, but simpler because this only deals in a flat
// collection of nodes, whereas TreeMapLayout operates on a tree structure.
//
// The bounds of each node will have an area proportional to its data.size value relative
// to the sum of all of the data.size values. The layout computes and stores the data.sizeFrac property
// based on each node's data.size fraction of the total.
//
// It is normal to use a "Viewbox" Panel so as to automatically scale what you want to show
// in each node so that it stays legible as the nodes get smaller and smaller for as long as possible.
//
// The layout sorts the nodes by decreasing data.sizeFrac and then arranges and sizes each node in sequence.
// It places nodes in order in the available space in the current direction, either towards the right
// or downwards, sizing each node to fill the available breadth in the other direction.
// When a fraction, determined by the turnSpot property, of the available distance is passed,
// it switches direction and continues in the smaller area.
//
// By default Layout.isViewportSized is true, which will cause the layout to arrange everything
// to fit the viewport (minus Diagram.padding).
// When Layout.isViewportSized is false, this will use the value of the targetSize property,
// which defaults to 600x400.
class RectangularPackLayout extends go.Layout {
constructor(init) {
super();
this.name = "RectangularPack";
this.isViewportSized = true;
this._targetSize = new go.Size(600, 400);
this._spacing = new go.Size(0, 0);
this._turnSpot = new go.Spot(0.3333, 0.3333);
if (init) Object.assign(this, init);
}
// Gets and sets the size of the rectangular area to fill
// when Layout.isViewportSized is false.
// This defaults to 600 x 400.
get targetSize() { return this._targetSize; }
set targetSize(val) {
const old = this._targetSize;
if (!old.equals(val) && val.isReal()) {
this._targetSize = val.copy();
this.invalidateLayout();
}
}
// Gets and sets the space between the nodes.
// This defaults to 0 x 0.
get spacing() { return this._spacing; }
set spacing(val) {
const old = this._spacing;
if (!old.equals(val) && val.isReal()) {
this._spacing = val.copy();
this.invalidateLayout();
}
}
// Gets and sets the Spot specifying the fractions
// of the remaining space, when passed, at which to change direction.
// Depending on the natural aspect ratio of your nodes,
// you may want to adjust this to have different fractions for x and y.
// This defaults to Spot(0.3333, 0.3333).
get turnSpot() { return this._turnSpot; }
set turnSpot(val) {
const old = this._turnSpot;
if (!old.equals(val)) {
this._turnSpot = val.copy();
this.invalidateLayout();
}
}
doLayout(coll) {
const diag = this.diagram;
if (!diag) return;
let w = this.targetSize.width;
let h = this.targetSize.height;
if (this.isViewportSized) {
const vb = diag.viewportBounds.copy();
vb.subtractMargin(diag.padding);
w = vb.width;
h = vb.height;
}
if (w < 1 || h < 1) return;
diag.startTransaction("RectangularPackLayout");
// get collection of Node-like Parts -- ignoring all Links
const blocks = new go.List(this.collectParts(coll).filter(p => !(p instanceof go.Link)));
let tsize = 0.0;
blocks.each(b => tsize += (b.data.size || 1));
blocks.each(b => {
const frac = (b.data.size || 1) / tsize;
diag.model.set(b.data, "sizeFrac", frac);
})
blocks.sort((a, b) => b.data.sizeFrac - a.data.sizeFrac);
const left = this.arrangementOrigin.x;
const top = this.arrangementOrigin.y;
this._layoutRect(blocks, 0, left, top, left + w, top + h, w*h, w > h);
diag.commitTransaction("RectangularPackLayout");
}
_layoutRect(blocks, idx, left, top, right, bottom, area, horiz) {
let x = left;
let y = top;
let i = idx;
while (i < blocks.count) {
const block = blocks.elt(i);
const newarea = block.data.sizeFrac * area;
let w;
let h;
if (horiz) {
h = bottom - y;
w = newarea/h;
} else {
w = right - x;
h = newarea/w;
}
block.desiredSize = new go.Size(Math.max(0, w - this.spacing.width),
Math.max(0, h - this.spacing.height));
block.moveTo(x, y);
let turn = false;
if (horiz) {
x += w;
turn = (x - left) > (right - left) * this.turnSpot.x;
} else {
y += h;
turn = (y - top) > (bottom - top) * this.turnSpot.y;
}
i++;
if (turn) horiz = !horiz;
}
}
}
const myDiagram =
new go.Diagram("myDiagramDiv", {
isReadOnly: true,
layout: new RectangularPackLayout({ spacing: new go.Size(1, 1) })
});
myDiagram.nodeTemplate =
new go.Node("Auto")
.add(
new go.Shape({ fill: null, stroke: "gray", strokeWidth: 0.5 })
.bind("fill", "diff", v => v < 0 ? "orangered" : (v > 0 ? "lightgreen" : "lightgray")),
new go.Panel("Viewbox", { stretch: go.GraphObject.Fill })
.add(
new go.Panel("Vertical")
.add(
new go.TextBlock({ font: "bold 10pt sans-serif" }).bind("text", "name"),
new go.TextBlock().bind("text", "val"),
new go.TextBlock().bind("text", "diff", v => v > 0 ? ("+"+v) : (v < 0 ? v : "=")),
new go.TextBlock().bind("text", "sizeFrac", v => (v*100).toFixed(2) + "%"),
)
)
);
myDiagram.model = new go.Model(
[
// The "size"s are summed and used to calculate the "sizeFrac" percentage;
// the "size" defaults to 1 if not present.
// The rest of the properties do not matter to RectangularPackLayout.
{ name: "Alpha", val: 1234.56, diff: 0.1, size: 17 },
{ name: "Beta", val: 23.45, diff: -2.3, size: 171 },
{ name: "Gamma", val: 34.5678, diff: 1.2, size: 231 },
{ name: "Delta", val: 456.789, diff: 34.56, size: 432 },
{ name: "Epsilon", val: 76.54, diff: -1.2, size: 52 },
{ name: "Zeta", val: 8.76, diff: 0, size: 53 },
{ name: "Eta", val: 9.87, diff: 1.2, size: 26 },
{ name: "Theta", val: 6.54, diff: -1.2, size: 30 },
]);
</script>
</body>
</html>
You can easily change the styling by modifying the node template.