Facing issue where the diagram and its content shift

we have a diagram like the above. second image show the drawer expanded state.

the blue bordersection is a grouptemplate

the green section is a taygroup template
within the green section there is trayRowGroup template (eg 12p, 22p)
within the trayRowGroup we have the positionCell template (small white squares)
within each posioncell we can drag and drop products.

the red section is the drawer group template
within that each row is a DrawerRowGroup Template
each row is expandable.
once we expand we have the drawer content group section.
within that we have positionCell template within each posioncell we can drag and drop products.

when we expan and collapse the diagram keep shifting to the left.
when we hover over the products in the palette we highlight the positions that it can be placed in. For the first time it works fine.(pic 3)
But after expanding and collapsing the drawer the diagram gets distorted.(pic 4)

after click on copy icon after expanding the drawer the diagram gets distorted.(pic 5)

private addBorderGroupTemplate(): void {

this.groupTemplateMap.add(Categories.BorderGroup,

  $(go.Group, go.Group.Vertical, {

    selectable: false,

    movable: false,

    //  layout: $(TableLayout),

    mouseDragEnter: (e: go.InputEvent, obj: go.GraphObject): void => {

      e.diagram.currentCursor = 'not-allowed';

      return;

    },

    mouseDrop: (e: go.InputEvent, obj: go.GraphObject): void => {

      e.diagram.currentTool.doCancel();

    }

  },

    new go.Binding('layout', '', data => {

      return this.sensorLayoutService.is10KMachine ? $(TableLayout) : undefined;

    }),



    $(go.Panel, go.Panel.Auto, {},

      $(go.Shape, 'RoundedRectangle', {

        fill: COLORS.primary,

        stroke: null,

        parameter1: 5

      }),

      $(go.Placeholder, {

        padding: new go.Margin(15, 0, 10, 0)// Default padding,

      },

        new go.Binding('padding', '', data => {



          return this.sensorLayoutService.is10KMachine &&

            this.sensorLayoutService.currentLayout!.vendingFixtureLayout.length > 1 ? new go.Margin(10, 0, 10, 0) : new go.Margin(15, 10, 10, 15);

        }),

      ),

    ),

    $(go.Panel, go.Panel.Table, {

      stretch: go.GraphObject.Horizontal

    },

      $(go.RowColumnDefinition, { sizing: go.RowColumnDefinition.None }),

      $(go.Shape, 'Rectangle', { column: 0, height: 30, width: 15, stroke: null, fill: COLORS.primary }),

      $(go.Shape, 'Rectangle', { column: 1, height: 30, width: 15, stroke: null, fill: COLORS.primary }),

      new go.Binding('visible', '', data => {

        return !this.sensorLayoutService.is10KMachine;

      }),

    ),



    //group type binding

    new go.Binding('type', 'groupType', this.parseGroupType),

  )

);

}

private addTraysGroupTemplate(): void {

this.groupTemplateMap.add('TraysGroup',

  $(go.Group, go.Group.Auto,

    {

      selectable: false,

      movable: false,

      row: 0,

      margin: new go.Margin(0, 0, 10, 0),

      stretch: go.GraphObject.Horizontal,

      background: 'green',

      alignment: go.Spot.Left,

    },

    new go.Placeholder()

  ),

);

}

private addTrayRowGroupTemplate(): void {

this.groupTemplateMap.add(Categories.TrayRowGroup,

  $(go.Group, go.Group.Horizontal, {

    selectable: false,

    mouseDragEnter: (e: go.InputEvent, obj: go.GraphObject): void => {

      e.diagram.currentCursor = 'not-allowed';

      return;

    },

    mouseDrop: (e: go.InputEvent, obj: go.GraphObject): void => {

      e.diagram.currentTool.doCancel();

    }

  },

    // Layout name label - positioned outside the white tray, only visible when layout is selected

    $(go.TextBlock, {

      stroke: COLORS.secondary,

      font: 'bold 1rem verdana',

      width: 23,

      height: 23,

      textAlign: 'center',

      verticalAlignment: go.Spot.Center,

      background: 'transparent'

    },

      new go.Binding('visible', 'selectedModule', data => (data === '' ? false : true)),

      new go.Binding('text', 'selectedModule')

    ),



    // white tray

    $(go.Panel, go.Panel.Auto,

      $(go.Shape, 'RoundedRectangle', {

        name: 'Row_Group_Shape',

        stroke: null,

        fill: COLORS.secondary,

        parameter1: 3

      },

        new go.Binding('desiredSize', 'dimensions', dimensions => convertSize(dimensions,

          {

            widthScale: this.sensorLayoutService.widthScale,

            heightScale: this.sensorLayoutService.heightScale

          }

        )),

        new go.Binding('fill', '', data => {

          return data.isLayoutAdded ? 'transparent' : COLORS.secondary;

        }),

      ),



      //Add Button

      $('Button', {

        margin: new go.Margin(0, 0, 0, 2),

        'ButtonBorder.stroke': COLORS.primary,

        '\_buttonStrokeOver': COLORS.primary,

        '\_buttonFillPressed': COLORS.light,

        click: (e, obj: go.GraphObject) => {

          e.handled = true;

          if (!this.sensorLayoutService.inCopyMode) {

            this.eventBusService.emit({ name: Actions.SelectLayoutSlider, value: obj.part?.data } as EventData);

          }



        },

      },

        $(go.TextBlock, 'ADD TRAY MODULE', {

          stroke: COLORS.primary,

          stretch: go.GraphObject.Fill,

          font: 'bold 1rem verdana',

          margin: new go.Margin(5, 5, 5, 5),

        }),

        // Hide the entire panel when no layout is selected

        new go.Binding('visible', 'isLayoutAdded', isLayoutAdded => !isLayoutAdded)

      ),



      $(go.Placeholder, { alignment: go.Spot.Left, padding: 0, margin: 0 })

    ),



    // edit button

    $(go.Panel, 'Auto', { width: 26 },



      $(go.TextBlock, {

        stroke: COLORS.secondary,

        font: 'bold 1rem verdana',

        width: 25,

        height: 25,

        textAlign: 'center',

        verticalAlignment: go.Spot.Center,

      },

        new go.Binding('text', 'row'),

        new go.Binding('visible', 'isLayoutAdded', isLayoutAdded => !isLayoutAdded)

      ),



      $('Button', {

        margin: new go.Margin(0, 0, 0, 2),

        alignment: go.Spot.RightCenter,

        alignmentFocus: go.Spot.LeftCenter,

        'ButtonBorder.figure': 'Circle',

        'ButtonBorder.stroke': COLORS.primary,

        '\_buttonStrokeOver': COLORS.primary,

        '\_buttonFillPressed': COLORS.light,

        width: 25,

        click: (e, obj: go.GraphObject) => {

          e.handled = true;

          if (!this.sensorLayoutService.inCopyMode) {

            this.eventBusService.emit({ name: Actions.SelectLayoutSlider, value: obj.part?.data } as EventData);

          }

        },

      },

        $(go.TextBlock, '\\ue3c9', {

          stroke: COLORS.primary,

          stretch: go.GraphObject.Fill,

          font: '1.5rem "Material Icons"',

          margin: new go.Margin(3, 0, 0, 2),

        }),

        // Hide the edit button when no layout is selected

        new go.Binding('visible', 'isLayoutAdded')

      ),

    ),

    // binding group type

    new go.Binding('type', 'diagramLayoutType', this.parseGroupType),

    // ).bindTwoWay('position', '', this.toLocation.bind(this), this.fromLocation.bind(this))

  ).bindTwoWay('position', '', this.toLocation.bind(this), this.fromLocation.bind(this))

);

}

private addSensorPositionsTemplate(): void {

this.groupTemplateMap.add(Categories.SensorPositions,

  $(go.Group, go.Group.Spot, {

    selectable: false,

    movable: false,

    selectionAdorned: false,

    alignment: go.Spot.Left,

    mouseDrop: this.handleMouseDrop.bind(this),

    mouseDragEnter: (e: go.InputEvent, obj: go.GraphObject) => {

      this.updateSensorPositionCellFill(obj, COLORS.lightGray, 'SensorPositionCellShape');

    },



    mouseDragLeave: (e: go.InputEvent, obj: go.GraphObject) => {

      this.updateSensorPositionCellFill(obj, COLORS.secondary, 'SensorPositionCellShape');

    }



  },

    $(go.Panel, go.Panel.Auto,

      // white rectangle

      $(go.Shape, 'RoundedRectangle', {

        name: 'SensorPositionCellShape',

        fill: COLORS.secondary,

        stroke: null

      },

        new go.Binding('fill', 'highlight', highlight => {

          return highlight ? COLORS.positionHighlight : COLORS.secondary;

        }

        ),

      ),

      new go.Binding('desiredSize', 'dimensions', dimensions => convertSize(dimensions,

        {

          widthScale: this.sensorLayoutService.widthScale,

          heightScale: this.sensorLayoutService.heightScale

        }

      )),



      this.addSkuPanel(),

    ),

    // binding group type

    new go.Binding('type', 'diagramLayoutType', this.parseGroupType),

  ).bindTwoWay('position', '', this.toLocation.bind(this), this.fromLocation.bind(this))

);

}

private createProductNodeTemplate(): go.Node {

return $(go.Node, go.Node.Vertical,

  {

    selectionAdorned: false,

    background: 'transparent',

    mouseDrop: (e: go.InputEvent) => { e.diagram.currentTool.doCancel(); },

    mouseEnter: (e: go.InputEvent, obj: go.GraphObject): void => {

      this.mouseEnterCell(e, obj);

      this.showDeleteIcon(e, obj);

    },

    mouseLeave: (e: go.InputEvent, obj: go.GraphObject, next: go.GraphObject) => {

      this.mouseLeaveCell(e, obj, next),

        this.hideDeleteIcon(e, obj);

    }

  },

  // binding this to show data in the center

  new go.Binding('desiredSize', 'dimensions', dimensions =>

    convertSize(dimensions,

      {

        widthScale: this.sensorLayoutService.widthScale,

        heightScale: this.sensorLayoutService.heightScale

      }

    )

  ),

  $(go.Panel, go.Panel.Auto, {

    margin: new go.Margin(2, 1, 1, 1),

    background: 'transparent',

  },

    $(go.Picture, {

      name: 'Picture',

      imageStretch: go.GraphObject.Uniform,

      alignment: go.Spot.Center,

      desiredSize: new go.Size(40, 40),

      errorFunction: (pic: go.Picture, e: Event) => {

        pic.diagram?.commit(function () {

          pic.source = CONSTANTS.NO_IMAGE_PATH;

        }, null);  // null means temporarily set skipsUndoManager to true

      }

    },

      new go.Binding('source', 'imageThumbnail'),

      new go.Binding('desiredSize', 'dimensions', dimensions =>

        convertImageSize(dimensions,

          {

            widthScale: this.sensorLayoutService.widthScale,

            heightScale: this.sensorLayoutService.heightScale



          }

        )

      ),

    ),

    this.displayCapacity(),

    this.displayRemoveIcon(),

  )

);

}

private toLocation(data: go.ObjectData): go.Point {

// console.log(data);

const x = inchesToScaledGoJSUnits(data.location?.x ?? 0, this.sensorLayoutService.widthScale);

const y = inchesToScaledGoJSUnits(data.location?.y ?? 0, this.sensorLayoutService.heightScale);

const loc = new go.Point(x, y);



if (data.group !== undefined) {

  const groupdata = this.model.findNodeDataForKey(data.group);

  if (groupdata) {

    loc.add(this.toLocation(groupdata));

  }

}



return loc;

}

private fromLocation(location: go.ObjectData, data: go.ObjectData): void {

if (data.group !== undefined) {

  const group = this.findNodeForKey(data.group);

  if (group) {

    const relativeLoc = location.copy().subtract(group.location);

    const x = scaledGoJSToInches(relativeLoc.x, this.sensorLayoutService.widthScale);

    const y = scaledGoJSToInches(relativeLoc.y, this.sensorLayoutService.heightScale);

    data.location = { x, y };

  }

} else {

  const x = scaledGoJSToInches(location.x, this.sensorLayoutService.widthScale);

  const y = scaledGoJSToInches(location.y, this.sensorLayoutService.heightScale);

  data.location = { x, y };

}



// Also update \`loc\` string if needed

data.loc = \`${location.x.toFixed(2)} ${location.y.toFixed(2)}\`;

}

private parseGroupType(type: string): go.PanelLayout {

switch (type) {

  case 'Vertical': return go.Group.Vertical;

  case 'Horizontal': return go.Group.Horizontal;

  case 'Spot': return go.Group.Spot;

  default: return go.Group.Auto;

}

}

private addDrawersGroupTemplate(): void {

this.groupTemplateMap.add('DrawersGroup',

  $(go.Group, go.Group.Auto,

    {

      selectable: false,

      movable: false,

      layout: $(TableLayout),

      row: 1,

      background: 'red',

      stretch: go.GraphObject.Horizontal,

    },

    new go.Placeholder({ alignment: go.Spot.LeftCenter })

  ),

);

}

private addDrawerRowGroupTemplate(): void {

// Row group template (expandable/collapsible drawers)

this.groupTemplateMap.add(Categories.DrawerRowGroup,

  $(go.Group, go.Group.Horizontal, {

    selectable: false,

    margin: new go.Margin(5, 0, 5, 0),

    mouseDragEnter: ((e: go.InputEvent, obj: go.GraphObject) => {

      e.diagram.currentCursor = 'not-allowed';

      return;

    }),

    mouseDrop: (e: go.InputEvent, obj: go.GraphObject) => e.diagram.currentTool.doCancel()

  },

    // Position and Size bindings

    new go.Binding('row'),

    new go.Binding('isSubGraphExpanded', 'isSubGraphExpanded').makeTwoWay(),



    // Layout name label - positioned outside the white tray, only visible when layout is selected

    $(go.TextBlock, {

      stroke: COLORS.secondary,

      font: 'bold 1rem verdana',

      textAlign: 'center',

      margin: 2,

      verticalAlignment: go.Spot.Center,

      background: 'transparent'

    },

      new go.Binding('text', 'drawerNo')

    ),



    // Content panel

    $(go.Panel, go.Panel.Auto,

      $(go.Shape, 'RoundedRectangle', {

        stroke: COLORS.primary,

        fill: COLORS.secondary,

        margin: new go.Margin(0, 15, 0, 0),

        parameter1: 3,

        spot1: new go.Spot(0, 0, 0, -1),

        spot2: new go.Spot(1, 1, 0, -1),

      },

        new go.Binding('width', 'dimensions', dimensions => inchesToScaledGoJSUnits(dimensions\[0\].width)),

        new go.Binding('fill', 'isDrawerOpenable', isDrawerOpenable => (isDrawerOpenable ? COLORS.secondary : '#6698CA'))

      ),

      $(go.Panel, go.Panel.Vertical, {

        name: DiagramGraphObjectNames.drawerPanel

      },

        new go.Binding('minSize', 'dimensions', dimensions => convertSize(dimensions,

          {

            widthScale: this.sensorLayoutService.widthScale,

            heightScale: this.sensorLayoutService.heightScale

          }

        )),



        // Header panel

        $(go.Panel, go.Panel.Horizontal, {

          name: DiagramGraphObjectNames.headerPanel,

          alignment: go.Spot.TopLeft,

          stretch: go.GraphObject.Fill

        },



          // Bin count label

          $(go.TextBlock, {

            name: 'bin_name',

            verticalAlignment: go.Spot.Center,

            stroke: COLORS.primary,

            margin: new go.Margin(0, 5, 0, 5),

            font: 'bold 1.2rem verdana'

          },

            new go.Binding('minSize', 'dimensions', dimensions => {

              return new go.Size(inchesToScaledGoJSUnits(dimensions\[0\].width / 2) - 45, inchesToScaledGoJSUnits(dimensions\[0\].height));

            }),

            new go.Binding('text', 'bin', bin => \`${bin} BINS\`),

          ),



          // Expand/collapse button

          $(go.Shape, 'Rectangle', {

            stroke: null,

            name: 'EXPAND_BUTTON',

            strokeWidth: 0,

            fill: COLORS.primary,

            width: 60,

            height: 12,

            alignment: go.Spot.TopCenter,

            margin: 0,

            cursor: 'pointer',

            mouseEnter: function (e: go.InputEvent, obj: go.GraphObject) {

              (obj as go.Shape).fill = COLORS.primaryLight;

            },

            mouseLeave: function (e: go.InputEvent, obj: go.GraphObject) {

              (obj as go.Shape).fill = COLORS.primary;

            },

            click: this.toggleDrawer.bind(this)

          }),

          $(go.Panel, go.Panel.Table,

            new go.Binding('minSize', 'dimensions', dimensions => {

              return new go.Size(inchesToScaledGoJSUnits(dimensions\[0\].width / 2) - 40, inchesToScaledGoJSUnits(dimensions\[0\].height));

            }),

            this.createButton(

              '\\ue5cf',

              COLORS.lightGray,

              (e: go.InputEvent, obj: go.ObjectData) => this.toggleDrawer(e, obj.part),

            )

          )

        ).bind('visible', 'isDrawerOpenable'),

        // Content placeholder

        $(go.Placeholder, {

          alignment: go.Spot.LeftCenter,

          padding: 0,

        })

      )

    ),

  )

);

}

public drawerContentGroup(): void {

// Drawer group template (contains the grid and action buttons)

this.groupTemplateMap.add(Categories.DrawerGroupContent,

  $(go.Group, 'Vertical', {

    selectable: false,

    margin: 0,

    mouseDrop: (e: go.InputEvent, obj: go.GraphObject) => e.diagram.currentTool.doCancel(),

    mouseDragEnter: (e: go.InputEvent, obj: go.GraphObject): void => {

      e.diagram.currentCursor = 'not-allowed';

      return;

    },

  },

    // Grid placeholder

    $(go.Placeholder, {

      padding: 1,

      background: COLORS.primary

    }),



    // Action buttons panel

    $(go.Panel, go.Panel.Table, {

      margin: 10,

      stretch: go.GraphObject.Fill,

    },

      $(go.Panel, {

        alignment: go.Spot.Left

      },

        $(go.TextBlock, {

          alignment: go.Spot.Left,

          stroke: COLORS.primary,

          font: 'bold 1.2rem verdana',

        }).bind('text', 'bin', bin => \`${bin} BINS\`),

      ),

      $(go.Panel, {

        alignment: go.Spot.Right,

      },

        this.createButton(

          '\\ue5ce',

          COLORS.primary,

          (e: go.InputEvent, obj: go.ObjectData) => this.toggleDrawer(e, obj.part.containingGroup),

        )

      )

    )

  )

);

}

public setupDrawerPosition(): void {

this.groupTemplateMap.add(Categories.DrawerGroupPositions,

  $(go.Group, 'Auto', {

    stretch: go.GraphObject.Horizontal,

    selectable: false,

    cursor: 'pointer',

    background: COLORS.primary,

    // Drag and drop functionality

    mouseDrop: this.handleMouseDrop.bind(this),



    mouseDragEnter: (e: go.InputEvent, obj: go.GraphObject) => {

      this.updateSensorPositionCellFill(obj, COLORS.lightGray, 'Cell_Shape');

    },

    mouseDragLeave: (e: go.InputEvent, obj: go.GraphObject) => {

      this.updateSensorPositionCellFill(obj, COLORS.secondary, 'Cell_Shape');

    }

  },

    // Position bindings

    new go.Binding('minSize', 'dimensions', dimensions => convertSize(dimensions,

      {

        widthScale: this.sensorLayoutService.widthScale,

        heightScale: this.sensorLayoutService.heightScale

      }

    )),

    new go.Binding('location', 'location', location => convertPoint(location, {

      widthScale: this.sensorLayoutService.widthScale,

      heightScale: this.sensorLayoutService.heightScale

    })),



    // Inner area

    $(go.Shape, 'RoundedRectangle', {

      fill: COLORS.secondary,

      stroke: COLORS.primary,

      parameter1: 3,

      name: 'Cell_Shape'

    },

      new go.Binding('fill', 'highlight', highlight => {

        return highlight ? COLORS.positionHighlight : COLORS.secondary;

      }

      ),

    ),



    this.addSkuPanel()

  )

);

}

Please can you help provide solution for the above issue. I have also attached sample code

@walter Do you have any solution to it?

What version of GoJS are you using?

If you make a minimal reproduction, we might be able to look at this, but its hard to debug just by eyeballing the code. There might be a binding that’s causing everything to re-measure and re-layout that’s the problem, but its hard to say.

Do you have a Diagram.layout? Or is everything placed by location?

We are using “gojs”: “^2.3.9”, “gojs-angular”: “2.0.7”,

Everythig is bound by location/position. for expanding and collapsing we are using the below method

export const toggleDrawer = (button: go.GraphObject, sensorLayoutService: SensorLayoutService): void => {

const diagram = button.diagram;
if (!diagram) { return; }
if (sensorLayoutService.inCopyMode) {
  return;
}

diagram.animationManager.isEnabled = false;
const group = button.part as go.Group;
if (!group) { return; }
diagram.startTransaction(‘toggle drawer subgroup’);
// Toggle the group’s expanded state
const isExpanding = !group.isSubGraphExpanded;
group.isSubGraphExpanded = isExpanding;
sensorLayoutService.expandedGroupKey = group.data.key;
diagram.nodes.each((node: go.Node) => {


if (node instanceof go.Group && node.category === Categories.DrawerRowGroup) {
  const headerPanel = node.findObject(DiagramGraphObjectNames.headerPanel);
  const drawerPanel = node.findObject(DiagramGraphObjectNames.drawerPanel);
  const isOpenable = node.data.isDrawerOpenable;
  const isCurrent = node.data.row === group.data.row;


  if (headerPanel) {
    headerPanel.visible = isCurrent ? !isExpanding : false;
  }


  if (drawerPanel) {
    const originalDims = node.data?.dimensions?.\[0\];
    const originalWidth = drawerPanel.minSize.width;


    if (isExpanding) {
      // Shrink all other groups
      if (!isCurrent) {
        drawerPanel.minSize = new go.Size(originalWidth, 12);
      }
    } else {
      // Restore all groups to original height
      if (originalDims && headerPanel) {
        headerPanel.visible = isOpenable;
        const restoredHeight = inchesToScaledGoJSUnits(originalDims.height);
        const restoredWidth = inchesToScaledGoJSUnits(originalDims.width);
        drawerPanel.minSize = new go.Size(restoredWidth, restoredHeight);
      }
    }
  }
}


});

diagram.commitTransaction(‘toggle drawer subgroup’);
diagram.layoutDiagram(true);
diagram.animationManager.isEnabled = false;

}

for DrawerRowGroup template we are using binding row

It’s probably not the GoJS version that’s causing the issue, though we’ve had issues with that in the past.

In general, if you’re doing things that might modify the size of Nodes or GraphObjects within nodes, its quite likely that this is causing the shift. What happens if you remove these changes to minSize?

What is your locationSpot on these Parts? Does the problem go away if its set to Spot.TopLeft?

I have kept all the drawers expanded by default (so not calling the toggle method ). The issue still persists while highlighting the cells. The diagram gets distorted. The distorted portions are the sensorPosition template

private addSensorPositionsTemplate(): void { this.groupTemplateMap.add(Categories.SensorPositions,…..

for highlighting we are setting the highlight property to true. if highlight property is true we are applying fill as yellow.

this.diagram?.model.commit(model => {

      supportedPositions.forEach((node) => {

        model.setDataProperty(node, CONSTANTS.HIGHLIGHT_COMPATIBLE_PROPERTY, data.enable);

        

      });

    }, 'highlight supported positions');

I think it’s going to be difficult to help here unless this can be reproduced outside of your app. Feel free to continue here or via email, but I think we will need to see an example of it that we can investigate, possibly by extracting some of your data and your node template.

Alternatively, you could try to narrow down the issue by removing various pieces:

  • Bindings
  • All Group and Diagram layouts
  • Making the data as small as possible while still presenting the issue, or even commenting out portions of the node template

And so on. Then we would at least have something to go by.