Object location when zooming in/out

I have an html element which renders at the bottom left corner of an object when it’s clicked and I’m trying to make it stick to the object when the canvas is panned or zoomed. The code I have works great when user pans the canvas but it gets wacky when zoomed in or out.

Looking at the below gif, you’ll see the red box:

  • renders fine when canvas is moved
  • gets dislocated when zoomed in or out
  • corrects back to proper location when moved/panned again

gojs_zoom

Here’s part of the code:

JavaScript:

function getNodeTemplate() {
  return new go.Node(go.Panel.Auto, {
    isShadowed: true,
    shadowVisible: true,
    shadowOffset: new go.Point(0, 2),
    shadowColor: 'rgba(0, 0, 0, 0.4)',
    shadowBlur: 1,
    selectionObjectName: 'box',
  })
    .bind(
      new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(
        go.Point.stringify
      )
    )
    .add(
      new go.Shape('RoundedRectangle', {
        name: 'box',
        desiredSize: new go.Size(28, 28),
        fill: 'gold',
      })
    )
    .add(new go.TextBlock('').bind(new go.Binding('text', 'name')));
}

function getLinkTemplate() {
  return new go.Link()
    .add(new go.Shape({ strokeWidth: 2, stroke: 'black' }))
    .add(new go.Shape({ toArrow: 'Standard', alignment: go.Spot.Center }));
}

function getModel() {
  const nodes = [
    { key: 'A', name: 'A', loc: '0 0' },
    { key: 'B', name: 'B', loc: '116 0' },
    { key: 'C', name: 'C', loc: '160 100' },
  ];
  const links = [ { from: 'A', to: 'B' }, { from: 'B', to: 'C' } ];
  return new go.GraphLinksModel(nodes, links);
}

function getPopoverLocation(
  d: go.Diagram,
  obj: go.GraphObject
): go.Point {
  const point = obj.getDocumentPoint(go.Spot.BottomLeft);
  point.y = point.y + 8;
  return d.transformDocToView(point);
}

let clickedObject: go.GraphObject | null = null;
const popover: HTMLDivElement | null = document.querySelector('#popover');

popover?.addEventListener('click', () => {
  popover.style.display = 'none';
});

const d = go.GraphObject.make(go.Diagram, 'box', {
  ObjectSingleClicked: (e) => {
    clickedObject = e?.subject?.part;
    if (clickedObject) {
      const loc = getPopoverLocation(e.diagram, clickedObject);
      if (popover) {
        popover.style.display = 'revert';
        popover.style.left = `${loc.x}px`;
        popover.style.top = `${loc.y}px`;
      }
    }
  },
});

d.nodeTemplate = getNodeTemplate();
d.linkTemplate = getLinkTemplate();

d.model = getModel();

d?.addDiagramListener('ViewportBoundsChanged', (e: go.DiagramEvent) => {
  if (clickedObject) {
    const loc = getPopoverLocation(e.diagram, clickedObject);
    if (popover) {
      popover.style.left = `${loc.x}px`;
      popover.style.top = `${loc.y}px`;
    }
  }
});

HTML:

<body>
    <div class="canvas">
      <div id="box"></div>
      <div id="popover" role="dialog" class="popover">dialog stuff</div>
    </div>
    <script type="module" src="/src/main.ts"></script>
  </body>

Styles:

body {
  background-color: whitesmoke;
  padding: 0px;
  margin: 0px;
}

.canvas {
  width: 100vw;
  height: 100vh;
}

#box {
  width: 100%;
  height: 100%;
  background-color: #dae4e4;
}

.popover {
  left: 0;
  position: absolute;
  display: none;
  top: 0;
  z-index: 10;
  border: 1px solid red;
  border-radius: 5px;
  padding: 4px;
  font-family: monospace;
  font-size: 12px;
  background-color: rgb(255, 190, 190);
  box-shadow: 2px 2px 0px 0px rgb(248, 97, 97);
}

Not sure if this is a bug or I’m doing something wrong.

If you only ever have one of those showing at a time, you could use the toolTip HTMLInfo mechanism instead of implementing it yourself.

As it is, I’m guessing that your getPopoverLocation function should be adding 8 to the point in view coordinates, not in document coordinates.

The problem is absolutely NOT this line:

point.y = point.y + 8;

That’s just to offset the popover location a tad bit below the object. Remove it and the problem I explained persists.

There’s something different between zoom in/out vs panning that doesn’t quite update the object location properly.

OK, we’re looking into it.

Here’s a better demonstration with keyboard interaction:

gojs_zoom_keyboard

Thanks for reporting the bug and providing a sample. This will be fixed in the next release – 3.0.25.