Move nodes freely in TableCellLayout

Hi all :)

I have this graph:

with this layout:

public initDiagram(): go.Diagram {
      function TableCellLayout() {
        TableLayout.call(this);
        this._cellLayout = null;  // this is applied to each table cell's collection of Parts, if there is more than one
      }
      go.Diagram.inherit(TableCellLayout, TableLayout);

      Object.defineProperty(TableCellLayout.prototype, "cellLayout", {
        get: function() { return this._cellLayout; },
        set: function(val) {
          if (val !== null && !(val instanceof go.Layout)) throw new Error("new TableCellLayout.cellLayout must be a Layout or null");
          if (val !== this._cellLayout) {
            this._cellLayout = val;
            this.invalidateLayout();
          }
        }
      });

      // don't have the cellLayout layout Parts for which this is true --
      // Parts that span cells
      TableCellLayout.prototype.isNotInCell = function(part) {
        return (part.rowSpan > 1 || part.columnSpan > 1);
      }

      TableCellLayout.prototype.beforeMeasure = function(parts, rowcol) {
        var lay = this.cellLayout;
        if (!lay) return;
        lay.diagram = this.diagram;
        var coll = new go.List();
        var bnds = new go.Rect();
        var tmp = new go.Rect();
        // for each row i ...
        for (var i = 0; i < rowcol.length; i++) {
          var rows = rowcol[i];
          if (!rows) continue;
          var rowDef = this.getRowDefinition(i);
          rowDef.originalMinimum = rowDef.minimum;
          rowDef.minimum = 0;
          for (var j = 0; j < rows.length; j++) {
            // for each column j in row i ...
            var parts = rows[j];
            if (!parts) continue;
            if (parts.length === 0) continue;
            // collect the Parts to be laid out within the cell
            coll.clear();
            for (var k = 0; k < parts.length; k++) {
              var part = parts[k];
              if (this.isNotInCell(part)) continue;
              coll.add(part);
              if (part instanceof go.Node) {
                // add Links that connect with another Node in this same cell
                part.findLinksConnected().each(function(link) {
                  if (!link.isLayoutPositioned) return;
                  var other = link.getOtherNode(part);
                  if (other && other.row === i && other.column === j && other.rowSpan === 1 && other.columnSpan === 1) {
                    coll.add(link);
                  }
                })
              }
            }
            if (coll.count === 0) continue;
            if (coll.count === 1) {
              //@ts-ignore
              coll.first().alignment = go.Spot.Default;
              continue;
            }
            lay.doLayout(coll);  // do the layout of just this cell's Parts
            // determine the area occupied by the laid-out parts
            bnds.setTo(0, 0, 0, 0);
            var colDef = this.getColumnDefinition(j);
            colDef.originalMinimum = colDef.minimum;
            colDef.minimum = 0;
            //@ts-ignore
            for (var k = coll.iterator; k.next(); ) {
              //@ts-ignore
              var part = k.value;
              if (part instanceof go.Link) continue;
              tmp.set(part.actualBounds);
              tmp.addMargin(part.margin);
              if (!tmp.isReal()) continue;
              if (k === 0) {
                bnds.set(tmp);
              } else {
                bnds.unionRect(tmp);
              }
            }
            // now make sure the RowColDefinitions are expanded if needed
            rowDef.minimum = Math.max(rowDef.minimum, bnds.height);
            colDef.minimum = Math.max(colDef.minimum, bnds.width);
            // and assign alignment on each of the Parts
            var mx = bnds.centerX;
            var my = bnds.centerY;
            //@ts-ignore
            for (var k = coll.iterator; k.next(); ) {
              //@ts-ignore
              var part = k.value;
              if (part instanceof go.Link) continue;
              part.alignment = new go.Spot(0.5, 0.5, part.actualBounds.centerX - mx, part.actualBounds.centerY - my);
            }
          }
        }
      }

      TableCellLayout.prototype.afterArrange = function(parts, rowcol) {
        if (this.cellLayout) {
          // restore all RowColDefinition.minimum
          for (var i = 0; i < rowcol.length; i++) {
            var columns = rowcol[i];
            if (!columns) continue;
            var rowDef = this.getRowDefinition(i);
            if (typeof rowDef.originalMinimum === "number") rowDef.minimum = rowDef.originalMinimum;
            for (var j = 0; j < columns.length; j++) {
              var parts = columns[j];
              if (!parts) continue;
              if (parts.length <= 1) continue;  // don't bother laying out just one node in a cell
              var colDef = this.getColumnDefinition(j);
              if (typeof colDef.originalMinimum === "number") colDef.minimum = colDef.originalMinimum;
            }
          }
        }
        this.updateTableGrid();
      }

      TableCellLayout.prototype.updateTableGrid = function() {
        var lay = this;
        var part = null;
        lay.diagram.findLayer("Background").parts.each(function(p) { if (p.name === "TABLEGRID") part = p; });
        if (!part) return;
        var numcols = lay.columnCount;
        var numrows = lay.rowCount;
        var firstcolindex = 1;
        var firstrowindex = 1;
        var firstcoldef = lay.getColumnDefinition(firstcolindex);
        var firstrowdef = lay.getRowDefinition(firstrowindex);
        var lastcoldef = lay.getColumnDefinition(numcols-1);
        var lastrowdef = lay.getRowDefinition(numrows-1);
        // determine the extent of the grid
        var left = firstcoldef.position;
        var width = lastcoldef.position + lastcoldef.total - firstcoldef.position;
        var top = firstrowdef.position;
        var height = lastrowdef.position + lastrowdef.total - firstrowdef.position;
        var eltIdx = 0;
        var prevLine = part.elements.count > 0 ? part.elt(0) : new go.Shape();
        function nextLine(fig, w, h) {  // reuse any existing Shapes
          var shp = null;
          if (eltIdx < part.elements.count) {
            shp = part.elt(eltIdx++);
          } else {
            shp = prevLine.copy();
            eltIdx++;
            part.add(shp);
          }
          shp.figure = fig;
          shp.width = w;
          shp.height = h;
          return shp;
        }
        // set up the verticals
        for (var i = firstcolindex; i < numcols; i++) {
          var def = lay.getColumnDefinition(i);
          var shp = nextLine("LineV", 0, height);
          shp.position = new go.Point(def.position, top);
        }
        // final line on right side
        var shp = nextLine("LineV", 0, height);
        shp.position = new go.Point(lastcoldef.position + lastcoldef.total, top);
        // set up the horizontals
        for (var i = firstrowindex; i < numrows; i++) {
          var def = lay.getRowDefinition(i);
          var shp = nextLine("LineH", width, 0);
          shp.position = new go.Point(left, def.position);
        }
        // final line at bottom
        var shp = nextLine("LineH", width, 0);
        shp.position = new go.Point(left, lastrowdef.position + lastrowdef.total);
        // get rid of any unneeded shapes
        while (part.elements.count > eltIdx) part.removeAt(eltIdx);
      }

        const $ = go.GraphObject.make;

      const dia =
        $(go.Diagram,
          {
            layout:
            // @ts-ignore
              $(TableCellLayout,
                $(go.RowColumnDefinition, { row: 0, height: 50, minimum: 50, alignment: go.Spot.Bottom }),
                $(go.RowColumnDefinition, { column: 0, width: 100, minimum: 100, alignment: go.Spot.Right }),
                {
                  cellLayout: $(go.GridLayout, {wrappingColumn: 2, spacing: new go.Size(100, 100) })
                }
              ),
            "SelectionCopied": updateCells,  // reassign cell of each copied or moved node
            "SelectionMoved": updateCells,
            "animationManager.isInitial": true,
            "undoManager.isEnabled": true,
            "initialContentAlignment": go.Spot.Center,
            model: $(go.GraphLinksModel,
              {
                linkToPortIdProperty: 'toPort',
                linkFromPortIdProperty: 'fromPort',
                linkKeyProperty: 'key' // IMPORTANT! must be defined for merges and data sync when using GraphLinksModel
              }
            ),
          });

      function updateCells(e) {
        var lay = e.diagram.layout;
        e.subject.each(function(node) {
          if (node instanceof go.Node) {
            var c = lay.findColumnForDocumentX(node.location.x);
            node.column = Math.min(Math.max(1, c), lay.columnCount-1);  // not into first column
            var r = lay.findRowForDocumentY(node.location.y);
            node.row = Math.min(Math.max(1, r), lay.rowCount-1);  // not into first row
          }
        });
        lay.invalidateLayout();
      }

I want to move nodes freely in their cells OR move it outside the table but NOT move it to another cell?

How can I do that?

Thank you all :)

BR,

Amit.

By default users should b de able to move nodes freely everywhere, unless restricted by settings/bindings on the Node template or settings on the Diagram or DraggingTool.
https://gojs.net/latest/intro/permissions.html#DragAndDropWithinDiagram

To prevent users dropping you have some choices. Do you want to prevent them from dragging into other cells? If so, set dragComputation on the node template.

Do you only want to correct the drop behavior once the drop happens? If so, it is probably easiest to implement a “SelectionMoved” DiagramEvent listener that checks the locations of all of the moved nodes. If you find any that have changed cells, you need to decide where to put them. If you want to put them all back where they started, you can call myDiagram.currentTool.doCancel().

Hi Walter!

Thanks for your answer. I will prevent dragging as you wrote :)

I want to drag nodes freely in each cell - how can I do that?

I tried to find what restrict it and I didn’t find it out.

Thanks,

BR,

Amit.

The “SelectionMoved” listener, updateCells, is updating the row and column for each node after each drag. I suggest that you change that function to do what you want and not call invalidateLayout. That means setting Part.isLayoutPositioned to false after a node has been moved out of the table. And you probably want to set Layout.isOngoing to false.

Thank you so much Walter!

Have a nice day,

Amit.