Create Crypto HEATmap

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.

1 Like