Animation using position vs point in dragging tool

Hi I’m trying to animate nodes. If I’m dragging multiple nodes under a specific place, the nodes should all clump up together.
I overrode DraggingTool doMouseMove (I also super.doMouseMove()).
I loop through draggedParts then move all the nodes to the currentPart node.
After the nodes clump together I want the nodes to move together when I move my mouse.
This is a code snippet (the code is in a function which is called in doMouseMove)
mainPartPoint is currentPart

this.draggedParts.each((node) => {

    if (node.key !== this.diagram.toolManager.draggingTool.currentPart) {

        new TWEEN.Tween(node.value.point)
                x: mainPartPoint.value.point.x + 8,
                y: mainPartPoint.value.point.y + 8
                , 250)

When i use node.value.point the animation works perfectly but when i move the cursor, one node moves and the others don’t or sometimes all move , its very inconsistent.

When I use node.key.position or node.key.location, the animation is not smooth, the nodes don’t move smoothly towards the main point, they just appear under the main point. but once I start moving my mouse they all move as one (same behavior when I select multiple nodes on the canvas and move the mouse, they all move together).

Is there a way to have a smooth animation and move the nodes together after they’ve clumped up together.

How can this expression ever be true?

I see you are using a different animation mechanism. Does it work better if you use the built-in animation provided by GoJS? GoJS Animation -- Northwoods Software

It does become true, node.key is a normal Part (with go.Part properties).

And no, our team uses tween, using GoJS animation doesnt work for some reason.

In Models, the key is expected to be a string or a number, not a reference to a GraphObject.
Part | GoJS API
Model | GoJS API

But if it’s not working with an external library either… What did you try using GoJS Animation?

const draggedNodes = [];
let tempNode = null;
let mainNode: NodeData = null;
this.draggedParts.each((part) => {
    tempNode = this.diagram.findNodeForData(;
    if (tempNode.part === this.currentPart { mainNode = tempNode };

Looping through all nodes

var animation = new go.Animation();
animation.add(node, "position", node.position, new go.Point(mainNode.position.x, mainNode.position.y));

Took adaptation from Animation | GoJS API

Yea sounds right but when i inspect or console log the node.key it’s a go.Part

Have you thought about adapting the implementation of the NonRealtimeDraggingTool extension? Non-Realtime Dragging

I still don’t have a clear idea of what you want to accomplish.

No thats not what I want.

While dragging all the nodes (the node with the arrow being the mainNode) and when it reaches the X point I want the nodes to animate to the mainNodes position (maybe with an offset, but thats not the main concern). When I move the nodes after they clumped up, they dont move together.

There are still a lot of ambiguities.

So do you want all of the selected nodes to be dragged as they normally are, until the mouse gets to that red X point in the document? How close does the mouse need to get to that red X?

When the mouse has gotten to that red X, only then do the dragged nodes coalesce? And you want to animate that motion?

What happens when a mouse-up happens? Before reaching the red X? After reaching the red X? After reaching the red X and then moving away from that point before the mouse is released?

Do those dragged-to-red-X nodes remain “clumped” together, or are they still independent nodes that can be selected and move separately from each other?

Yes dragged normally, when they get to the X point the nodes not-on-the-cursor go to the node under the cursor. Basically the X point is a multistep, and we check if this.dropTarget is of type multistep.

yes and yes

So like I mentioned the X is a multistep (can think of it as a group), the dragged nodes will be part of the multistep when dropped on it . Before reaching a multistep the nodes act normally, as if they were moved from one point of the canvas to another. So when they are on the multistep, the nodes clump together, when they move away from the multistep without being released, they “unclump”.

When it moves away from the X they “unclump”. When they are dropped on the multistep they are individual nodes.

OK, that seems understandable now. I’ll see if I can come up with an example for you.

thanks walter

Maybe this does what you want: [EDIT: replaced by code shown below]

Hi Walter, thanks for replying.
Please see codepen
Is this the behavior you intended ? Delta just becomes gamma when i click on the green part, I’m confused.

No, it isn’t. You didn’t try the page as written?

If you are going to use v2.3 you need to replace the bind calls with:

    .bind("location", "loc", go.Point.parse, go.Point.stringify)
    .bind(new go.Binding("layerName", "isSelected", s => s ? "Foreground" : "").ofObject())

Actually you don’t need the binding on Node.location at all for this sample.

Oh i see, thanks i’ll try it out.

Hi Walter. One more thing; I’m trying to make the nodes have a cascading effect but doesn’t seem to work. Please look at codepen again

Otherwise, is everything working the way you wanted?

Yes thanks, just having trouble with the offset but the animation is working the way I wanted.

<!DOCTYPE html>
  <title>Collapsing/expanding dragged nodes when dragging over a node</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>

  <script src=""></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv",
      "undoManager.isEnabled": true

myDiagram.nodeTemplate =
  new go.Node("Auto", {
      locationSpot: go.Spot.Center,
      // Note that the following code to collapse/expand dragged nodes
      // does not handle links that connect with the dragged nodes.
      mouseDragEnter: (e, node, prev) => {
        if (prev && prev.isContainedBy(node)) return;
        const tool = e.diagram.toolManager.draggingTool;
        if (!tool.draggedParts || tool.draggedParts.count < 2) return;
        // remember where nodes were at time of collapse
        const locmap = new go.Map();
        // need to update draggedParts Map of original locations
        const infomap = new go.Map();
        const nloc = node.location;
        const cloc = tool.draggedParts.get(tool.currentPart).point;
        const an = new go.Animation();
        an.duration = 200;
        // pretend each node started at the same location as currentPart
        let i = 0;
        tool.draggedParts.each(kvp => {
          const part = kvp.key;
          if (part instanceof go.Link) return;
          locmap.set(part, part.location.copy());
          // compute shifted locations for each dragged node
          const sloc = new go.Point(cloc.x + i*-10, cloc.y + i*10);
          infomap.set(part, new go.DraggingInfo(sloc));
          an.add(part, "location", part.location, new go.Point(nloc.x + i*-10, nloc.y + i*10));
          part.move(sloc, true);
        tool._savedLocs = locmap;
        tool._savedInfos = tool.draggedParts;
        tool.draggedParts = infomap;
      mouseDragLeave: (e, node, next) => {
        if (next && next.isContainedBy(node)) return;
        const tool = e.diagram.toolManager.draggingTool;
        if (tool._savedInfos && !e.up) {  // don't move everything back on mouse-up
          // restore original draggedParts Map
          tool.draggedParts = tool._savedInfos;
          // move each node back to its original location at time of collapse
          const an = new go.Animation();
          an.duration = 200;
          tool._savedLocs.each(kvp => {
            const part = kvp.key;
            const loc = kvp.value;
            an.add(part, "location", part.location, loc);
            part.move(loc, true);
        tool._savedLocs = null;
        tool._savedInfos = null;
    .bindObject("layerName", "isSelected", s => s ? "Foreground" : "")
      new go.Shape({ fill: "white" })
        .bind("fill", "color"),
      new go.TextBlock({ margin: 8 })

myDiagram.model = new go.GraphLinksModel(
  { key: 1, text: "Alpha", color: "lightblue" },
  { key: 2, text: "Beta", color: "orange" },
  { key: 3, text: "Gamma", color: "lightgreen" },
  { key: 4, text: "Delta", color: "pink" }