Magnetic force to center

Hello,

Continuing with our goal of migrating our D3.js charts to GoJS, I am reaching out for your assistance regarding forces and collisions.

Here’s some context: We have a list of cryptocurrency holders, each possessing a different amount. The chart draws bubbles for each holder. The size of the bubble varies depending on the amount the holder owns.

Currently, we apply a magnetic force that encourages all nodes to move towards the center based on the available space. If you move a node, a collision effect occurs to “push” the nodes along the path. Again, the nodes will use the freed space to move closer to the center through a slow movement.

I invite you to visit XIDAR resource explorer and select a resource to see the effect in action.

Could you help me replicate this effect?

Here is the code I currently have with GoJS:

const $ = go.GraphObject.make;  // for conciseness in defining templates
    this.diagram = new go.Diagram("holders-chart",  // must name or refer to the DIV HTML element
      {
        allowCopy: false,
        initialContentAlignment: go.Spot.Center,
        layout: $(go.ForceDirectedLayout),
        "commandHandler.copiesTree": false,
        "commandHandler.deletesTree": false,
        "draggingTool.dragsTree": true,
        "undoManager.isEnabled": false
      });

this.diagram.nodeTemplate =
      $(go.Node,
        "Auto",
        {
          desiredSize: new go.Size(this.nodeDefaultSize, this.nodeDefaultSize),
          selectionObjectName: "PANEL",
          isTreeExpanded: false,
          isTreeLeaf: false,
          cursor: "pointer",
          selectionAdorned: false,
          click: (e, obj) => {
            const node = obj.part;
            if (node === null) return;
            e.handled = true;
            const data = node.data;
            this.setHighlightedAddress(data.address);
          }
        },
        new go.Binding("desiredSize", "scale", scale => {
          return new go.Size(this.nodeDefaultSize * scale, this.nodeDefaultSize * scale)
        }),

        $(go.Picture,
          {
            opacity: .5,
            desiredSize: new go.Size(this.nodeDefaultSize, this.nodeDefaultSize),
            mouseEnter: function(e, shape) {
              // @ts-ignore
              shape.opacity = .8;
            },
            mouseLeave: function(e, shape) {
              // @ts-ignore
              shape.opacity = shape.part.isSelected ? .8 : .5;
            }
          },
          new go.Binding("desiredSize", "scale", scale => {
            return new go.Size(this.nodeDefaultSize * scale, this.nodeDefaultSize * scale)
          }),
          new go.Binding("opacity", "isSelected", sel => {
            if (sel) return .8; else return .5;
          }).ofObject(""),
          new go.Binding("source", "address", (address) => {
            return this.convertAddressToImage(self.projects.getProject(address, true));
          })
        )
      );

    this.diagram.model = new go.GraphLinksModel(this.getHoldersToShow());

I was unable to view any interesting diagrams at that XIDAR resource explorer site, after picking many different resources. So I can only guess what you want. Is there any other way you could provide such screenshots?

In the meantime, I’m wondering if you want to produce diagrams such as: GoJS Packed Class Hierarchy
Perhaps without the nesting of groups of nodes.

Ah yes after selecting a resource you don’t see the chart ? That’s strange.

Anyway here a video for you of what I’m looking for : Enregistrement de l’écran 2024-04-11 à 13.31.15.mov - Google Drive

Do not pay attention to colors :)

Do not hesitate if you have any question.

Packed Class Hierarchy is actually very close to what I’m trying to do yes. Maybe that would be better to use this indeed.

Did you want to allow users to drag nodes? If so, I assume you want that move-other-nodes-out-of-the-way behavior too. If not, then use the PackedLayout extension.

Actually no I don’t need to allow users to drag nodes. So the magnetic effect is not needed. I was focus on just migrating as it is. I will try to manage something.

You can disable users from moving nodes by setting Diagram.allowMove to false.
You can disable them from copying nodes by setting Diagram.allowCopy to false.
You can disable them from deleting nodes by setting Diagram.allowDelete to false.
Or maybe you just want to set Diagram.isReadOnly to true.
https://gojs.net/latest/intro/permissions.html

Hm I think I need to learn a bit more the library. I can’t manage to do what I need now.
In the meantime, @walter , does Northwoods Software propose development services ? I would be definitely interested to pay for this service. Spending too much time on something and expert can easily create.

Best regards,

Cédric

I’ll create a sample for you tomorrow when I get some time.

Do you have some data that I can work with? Or should I just make it up?

That would be very helpful, thank you very much !

Here is a dataset we use for our tool :

[
    {
        "position": 1,
        "key": "account_rdx1683u2tm2v5uyn8xsd9g79zvcnztduvqg7w9rfep3ua0pqyaaqqwlmc",
        "address": "account_rdx1683u2tm2v5uyn8xsd9g79zvcnztduvqg7w9rfep3ua0pqyaaqqwlmc",
        "addressTrunc": "acc...qqwlmc",
        "balance": 2400000000.1,
        "freeToUse": 2400000000.1,
        "staking": 0,
        "inPool": 0,
        "project": "radix",
        "share": 20.39525185598152,
        "scale": 1
    },
    {
        "position": 2,
        "key": "account_rdx16969vklwncle0t7r7a3ur0svj0vldupmvrp00xdv942c4n6sf2er08",
        "address": "account_rdx16969vklwncle0t7r7a3ur0svj0vldupmvrp00xdv942c4n6sf2er08",
        "addressTrunc": "acc...f2er08",
        "balance": 744768510.1084037,
        "freeToUse": 744768510.1084037,
        "staking": 0,
        "inPool": 0,
        "project": "instabridge",
        "share": 6.329058890596712,
        "scale": 0.3103202125322382
    },
    {
        "position": 3,
        "key": "account_rdx168vjll6crfsyyxew9t73vtr5vacxp43wrmww9pkavx8m9zqjskz6xh",
        "address": "account_rdx168vjll6crfsyyxew9t73vtr5vacxp43wrmww9pkavx8m9zqjskz6xh",
        "addressTrunc": "acc...skz6xh",
        "balance": 588149542.5657548,
        "freeToUse": 588149542.5657548,
        "staking": 0,
        "inPool": 0,
        "project": "radix",
        "share": 4.998107520462118,
        "scale": 0.2450623093921869
    },
    {
        "position": 4,
        "key": "account_rdx16y76fepuvxqpv6gp6qswqymwhj5ng6sduugj4z6yysccvdg95g0dtr",
        "address": "account_rdx16y76fepuvxqpv6gp6qswqymwhj5ng6sduugj4z6yysccvdg95g0dtr",
        "addressTrunc": "acc...5g0dtr",
        "balance": 576816480.0767598,
        "freeToUse": 350113779.0767599,
        "staking": 226702701,
        "inPool": 0,
        "project": "radix",
        "share": 4.901798910565035,
        "scale": 0.2403402000219691
    },
    {
        "position": 5,
        "key": "account_rdx16yvycd79gwj2lezn5ag426z6qf78dmzmtvq3pacgxl7x3rl3em7xz3",
        "address": "account_rdx16yvycd79gwj2lezn5ag426z6qf78dmzmtvq3pacgxl7x3rl3em7xz3",
        "addressTrunc": "acc...em7xz3",
        "balance": 298239888.15022194,
        "freeToUse": 298239888.15022194,
        "staking": 0,
        "inPool": 0,
        "project": "radix",
        "share": 2.5344490133625377,
        "scale": 0.1242666200574147
    },
    {
        "position": 6,
        "key": "account_rdx168ng3fma0wu3d72x8jvfsz6xptu9h77q7v9spyxvxh004afwysvhxh",
        "address": "account_rdx168ng3fma0wu3d72x8jvfsz6xptu9h77q7v9spyxvxh004afwysvhxh",
        "addressTrunc": "acc...ysvhxh",
        "balance": 229213754.04813936,
        "freeToUse": 229213754.04813936,
        "staking": 0,
        "inPool": 0,
        "project": "kucoin",
        "share": 1.9478634343633419,
        "scale": 0.095505730849412
    },
    {
        "position": 7,
        "key": "account_rdx1c96afecrpe94j3kxgqy7k0mws5kyp35dxnd3azqdqy4vlu33ll7cex",
        "address": "account_rdx1c96afecrpe94j3kxgqy7k0mws5kyp35dxnd3azqdqy4vlu33ll7cex",
        "addressTrunc": "acc...ll7cex",
        "balance": 212739523.2388918,
        "freeToUse": 212739523.2388918,
        "staking": 0,
        "inPool": 0,
        "project": null,
        "share": 1.8078650649991024,
        "scale": 0.08864146801251153
    },
    {
        "position": 8,
        "key": "account_rdx16ynzsmy4j3f6w5fa5kkpgt8zff2hwvsextgyx30ud8pnk5cct9azt8",
        "address": "account_rdx16ynzsmy4j3f6w5fa5kkpgt8zff2hwvsextgyx30ud8pnk5cct9azt8",
        "addressTrunc": "acc...t9azt8",
        "balance": 208388659.60156685,
        "freeToUse": 208388659.60156685,
        "staking": 0,
        "inPool": 0,
        "project": "radix",
        "share": 1.7708913318030288,
        "scale": 0.08682860816370166
    },
    {
        "position": 9,
        "key": "account_rdx168r4nevmyfhcysrnczmdfzdds6ld5pkuvxm3vd2750gelrqfz2n4c5",
        "address": "account_rdx168r4nevmyfhcysrnczmdfzdds6ld5pkuvxm3vd2750gelrqfz2n4c5",
        "addressTrunc": "acc...z2n4c5",
        "balance": 186382401.96401644,
        "freeToUse": 197939.96401642682,
        "staking": 186184462,
        "inPool": 0,
        "project": null,
        "share": 1.5838816789252135,
        "scale": 0.07765933414843772
    },
    {
        "position": 10,
        "key": "account_rdx16y08t386cl8tsmp72052pxnnrrqtuq7sp7c8m2huptp6c200lj5yfl",
        "address": "account_rdx16y08t386cl8tsmp72052pxnnrrqtuq7sp7c8m2huptp6c200lj5yfl",
        "addressTrunc": "acc...lj5yfl",
        "balance": 170822797.64869004,
        "freeToUse": 2747.6486900560753,
        "staking": 170820050,
        "inPool": 0,
        "project": null,
        "share": 1.4516558252680152,
        "scale": 0.07117616568398852
    }
]

By default we set node size to 100x100 . Multiplied by scale to get the final size of each node. From what I’m trying to do following our discussion, user can’t drag nodes. Only zoom is possible with by default auto fitting all nodes to the available space.

Do not hesitate if you need more info. Thanks again for all the efforts !

Cédric

OK, using your data and the node template shown below, here’s the result, including showing a tooltip when over the node at the bottom. (But I didn’t know what you’d want to show.) Those nodes are actually centered in the viewport / HTMLDivElement – I just took a snapshot including the tooltip.

The complete stand-alone code follows. Note that it’s using GoJS v3.0 beta, which should be released for real very soon. You’ll want to use the non-beta release in the long run, and not loaded from gojs.net.

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:600px; height:600px"></div>

  <!-- This should use a CDN instead of references to gojs.net -->
  <script src="https://gojs.net/beta/release/go.js"></script>
  <script src="https://gojs.net/beta/extensions/QuadTree.js"></script>
  <script src="https://gojs.net/beta/extensions/PackedLayout.js"></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv",
    {
      isReadOnly: true,
      initialAutoScale: go.Diagram.Uniform,
      layout: new PackedLayout()
    });

myDiagram.nodeTemplate =
  new go.Node("Spot", {
      toolTip: go.GraphObject.build("ToolTip").add(
        new go.Panel("Vertical").add(
          new go.TextBlock().bind("text", "project"),
          new go.TextBlock().bind("text", "address"),
          new go.TextBlock().bind("text", "balance"),
          new go.TextBlock().bind("text", "share"),
        )
      )
    })
    .bind("scale")
    .add(
      new go.Shape("Circle", { fill: "lightgreen" })
        .bind("fill", "scale", s => go.Brush.lightenBy("green", s)),
      new go.TextBlock()
        .bind("text", "addressTrunc")
    );

myDiagram.model = new go.GraphLinksModel(
  [
    {
        "position": 1,
        "key": "account_rdx1683u2tm2v5uyn8xsd9g79zvcnztduvqg7w9rfep3ua0pqyaaqqwlmc",
        "address": "account_rdx1683u2tm2v5uyn8xsd9g79zvcnztduvqg7w9rfep3ua0pqyaaqqwlmc",
        "addressTrunc": "acc...qqwlmc",
        "balance": 2400000000.1,
        "freeToUse": 2400000000.1,
        "staking": 0,
        "inPool": 0,
        "project": "radix",
        "share": 20.39525185598152,
        "scale": 1
    },
    {
        "position": 2,
        "key": "account_rdx16969vklwncle0t7r7a3ur0svj0vldupmvrp00xdv942c4n6sf2er08",
        "address": "account_rdx16969vklwncle0t7r7a3ur0svj0vldupmvrp00xdv942c4n6sf2er08",
        "addressTrunc": "acc...f2er08",
        "balance": 744768510.1084037,
        "freeToUse": 744768510.1084037,
        "staking": 0,
        "inPool": 0,
        "project": "instabridge",
        "share": 6.329058890596712,
        "scale": 0.3103202125322382
    },
    {
        "position": 3,
        "key": "account_rdx168vjll6crfsyyxew9t73vtr5vacxp43wrmww9pkavx8m9zqjskz6xh",
        "address": "account_rdx168vjll6crfsyyxew9t73vtr5vacxp43wrmww9pkavx8m9zqjskz6xh",
        "addressTrunc": "acc...skz6xh",
        "balance": 588149542.5657548,
        "freeToUse": 588149542.5657548,
        "staking": 0,
        "inPool": 0,
        "project": "radix",
        "share": 4.998107520462118,
        "scale": 0.2450623093921869
    },
    {
        "position": 4,
        "key": "account_rdx16y76fepuvxqpv6gp6qswqymwhj5ng6sduugj4z6yysccvdg95g0dtr",
        "address": "account_rdx16y76fepuvxqpv6gp6qswqymwhj5ng6sduugj4z6yysccvdg95g0dtr",
        "addressTrunc": "acc...5g0dtr",
        "balance": 576816480.0767598,
        "freeToUse": 350113779.0767599,
        "staking": 226702701,
        "inPool": 0,
        "project": "radix",
        "share": 4.901798910565035,
        "scale": 0.2403402000219691
    },
    {
        "position": 5,
        "key": "account_rdx16yvycd79gwj2lezn5ag426z6qf78dmzmtvq3pacgxl7x3rl3em7xz3",
        "address": "account_rdx16yvycd79gwj2lezn5ag426z6qf78dmzmtvq3pacgxl7x3rl3em7xz3",
        "addressTrunc": "acc...em7xz3",
        "balance": 298239888.15022194,
        "freeToUse": 298239888.15022194,
        "staking": 0,
        "inPool": 0,
        "project": "radix",
        "share": 2.5344490133625377,
        "scale": 0.1242666200574147
    },
    {
        "position": 6,
        "key": "account_rdx168ng3fma0wu3d72x8jvfsz6xptu9h77q7v9spyxvxh004afwysvhxh",
        "address": "account_rdx168ng3fma0wu3d72x8jvfsz6xptu9h77q7v9spyxvxh004afwysvhxh",
        "addressTrunc": "acc...ysvhxh",
        "balance": 229213754.04813936,
        "freeToUse": 229213754.04813936,
        "staking": 0,
        "inPool": 0,
        "project": "kucoin",
        "share": 1.9478634343633419,
        "scale": 0.095505730849412
    },
    {
        "position": 7,
        "key": "account_rdx1c96afecrpe94j3kxgqy7k0mws5kyp35dxnd3azqdqy4vlu33ll7cex",
        "address": "account_rdx1c96afecrpe94j3kxgqy7k0mws5kyp35dxnd3azqdqy4vlu33ll7cex",
        "addressTrunc": "acc...ll7cex",
        "balance": 212739523.2388918,
        "freeToUse": 212739523.2388918,
        "staking": 0,
        "inPool": 0,
        "project": null,
        "share": 1.8078650649991024,
        "scale": 0.08864146801251153
    },
    {
        "position": 8,
        "key": "account_rdx16ynzsmy4j3f6w5fa5kkpgt8zff2hwvsextgyx30ud8pnk5cct9azt8",
        "address": "account_rdx16ynzsmy4j3f6w5fa5kkpgt8zff2hwvsextgyx30ud8pnk5cct9azt8",
        "addressTrunc": "acc...t9azt8",
        "balance": 208388659.60156685,
        "freeToUse": 208388659.60156685,
        "staking": 0,
        "inPool": 0,
        "project": "radix",
        "share": 1.7708913318030288,
        "scale": 0.08682860816370166
    },
    {
        "position": 9,
        "key": "account_rdx168r4nevmyfhcysrnczmdfzdds6ld5pkuvxm3vd2750gelrqfz2n4c5",
        "address": "account_rdx168r4nevmyfhcysrnczmdfzdds6ld5pkuvxm3vd2750gelrqfz2n4c5",
        "addressTrunc": "acc...z2n4c5",
        "balance": 186382401.96401644,
        "freeToUse": 197939.96401642682,
        "staking": 186184462,
        "inPool": 0,
        "project": null,
        "share": 1.5838816789252135,
        "scale": 0.07765933414843772
    },
    {
        "position": 10,
        "key": "account_rdx16y08t386cl8tsmp72052pxnnrrqtuq7sp7c8m2huptp6c200lj5yfl",
        "address": "account_rdx16y08t386cl8tsmp72052pxnnrrqtuq7sp7c8m2huptp6c200lj5yfl",
        "addressTrunc": "acc...lj5yfl",
        "balance": 170822797.64869004,
        "freeToUse": 2747.6486900560753,
        "staking": 170820050,
        "inPool": 0,
        "project": null,
        "share": 1.4516558252680152,
        "scale": 0.07117616568398852
    }
]);
  </script>
</body>
</html>
1 Like

Thank you very much !
I’m trying to integrate it in my Angular App but I have the following error :

gojs_extensionsTS_PackedLayout__WEBPACK_IMPORTED_MODULE_4_.PackedLayout is not a constructor

Here is my code :

import * as go from "gojs/release/go-module";
import {PackedLayout} from "gojs/extensionsTS/PackedLayout";
this.diagram = new go.Diagram("holders-chart",
      {
        isReadOnly: true,
        initialAutoScale: go.Diagram.Uniform,
        layout: new PackedLayout()
      });

this.diagram.nodeTemplate = new go.Node("Spot", {
      // @ts-ignore
      toolTip: go.GraphObject.build("ToolTip").add(
        new go.Panel("Vertical").add(
          new go.TextBlock().bind("text", "project"),
          new go.TextBlock().bind("text", "address"),
          new go.TextBlock().bind("text", "balance"),
          new go.TextBlock().bind("text", "share"),
        )
      )
    })
      .bind("scale")
      .add(
        new go.Shape("Circle", { fill: "lightgreen" })
          .bind("fill", "scale", s => go.Brush.lightenBy("green", s)),
        new go.TextBlock()
          .bind("text", "addressTrunc")
      );

this.diagram.model = new go.GraphLinksModel(this.getHoldersToShow());

Previous message edited. I’m using the version 2.3.17 . Do I need to use the beta version ?

Copy the extensionsJSM/PackedLayout.ts or .js file into your project.

BTW, we’re deleting the “extensionsTS” directory in v3.0, since most people don’t use require any more.


That’s perfect ! Thank you very much !

All the code is ready for v3.0 :)