LinkLabelDraggingTool - Adding a line between a link and its floating label

Hello Walter,

I have the same requirement for link. I already use ‘LinkLabelDraggingTool’.
Do I need to wrap my link inside a Panel or something like that?

Could you help me for this please?

Best Regards,
Martin

No, the Link cannot be a “DecoratedLine” Panel – it has to be a “Link” Panel.

But what result do you want? Could you please sketch where the line should go? I assume one end of the line should be at the middle of the label, but where should the other end be?

Something like that, line from the middle of the label to the middle of the link. Is it possible?

Capture

One solution is just to implement the labels as separate nodes, and the lines as separate links that connect to label nodes that are at the middle of links. Links to Links

I can work on a solution that is more like the “DecoratedLine” Panel but for links. I’m thinking it might be best if it were a customization of the “Link” Panel, but I don’t know how customizable that is. I cannot promise anything yet.

Yes, something like the “DecoratedLine” is better because the label is tied to our link model data.

I have created a new sample at Link Label Dragging with a tether line to the label

Thank Walter, fantastic!

Hi Walter,

Hope you are well.

We are trying to migrate from GoJS v2 to v3 and need to update the old code using

go.Diagram.inherit(PanelLayoutLinkDecoratedLine, PanelLayoutLink);

We saw that the code has been updated to V3 in your sample and uses the extends method as follow:

var PanelLayoutLink = new go.Link().type.constructor;

class PanelLayoutLinkDecoratedLine extends PanelLayoutLink {
  arrange(panel, elements, union) {
    super.arrange(panel, elements, union);
    // Remove for brevity
    this.measureElement(line, w, h, 0, 0);
    this.arrangeElement(line, x, y, line.measuredBounds.width, line.measuredBounds.height);
  }
}
go.Panel.definePanelLayout("LinkDecoratedLine", new PanelLayoutLinkDecoratedLine());

The problem is, we use TypeScript and not JavaScript in our project, and the converted code doesn’t seem to work (we can’t access the PanelLayoutLink class, as it is hidden).

We tried different solutions:

import * as go from 'gojs';

const PanelLayoutLink = new go.Link().constructor;

class PanelLayoutLinkDecoratedLine extends PanelLayoutLink { // Type 'Function' is not a constructor function type.
  constructor() {
    super();
  }

  arrange(panel: go.Panel, elements: go.GraphObject[], union: go.Rect): void {
    super.arrange(panel, elements, union);
    // Removed for brevity
    this.measureElement(line, w, h, 0, 0);
    this.arrangeElement(line, x, y, line.measuredBounds.width, line.measuredBounds.height);
  }
}

// Define the custom panel layout
go.Panel.definePanelLayout('LinkDecoratedLine', new PanelLayoutLinkDecoratedLine());

We also tried this:

class PanelLayoutLinkDecoratedLine extends go.Link {
  constructor() {
    super();
  }

  arrange(panel: go.Panel, elements: go.GraphObject[], union: go.Rect): void {
    super.arrange(panel, elements, union);
    // Removed for brevity
    this.measureElement(line, w, h, 0, 0);
    this.arrangeElement(line, x, y, line.measuredBounds.width, line.measuredBounds.height);
  }
}

// Define the custom panel layout
go.Panel.definePanelLayout('LinkDecoratedLine', new PanelLayoutLinkDecoratedLine());

This code doesn’t build because Link doesn’t contain the methods.
We also tried using PanelLayout, and the code builds but doesn’t work as we extend from go.PanelLayout and not go.PanelLayoutLink.

Do you have any ideas on how we can do this?
Thank you

Actually, the extras have not yet been updated for v3, but they do use the modern class syntax and some other updates.

That’s interesting. Yes, PanelLayoutLink is not documented or exported or declared in go.d.ts. So the only way to get that class is via a Link’s Panel.type’s constructor.

I’ll look into possibilities.

This worked for me:

import * as go from "gojs";

class PanelLayoutLink extends go.PanelLayout {}
Object.setPrototypeOf(PanelLayoutLink.prototype, new go.Link().type);

class PanelLayoutLinkDecoratedLine extends PanelLayoutLink {
  override arrange(panel: go.Panel, elements: go.GraphObject[], union: go.Rect): void {
    super.arrange(panel, elements, union);
    const link = panel.part as go.Link;
    const line = link.findObject("LINE");
    const icon = link.findObject("ICON");
    const label = link.findObject("LABEL");
    if (!(line instanceof go.Shape) || !icon || !label) return;
    const ibnds = icon.actualBounds;
    const lbnds = label.actualBounds;
    const x0 = ibnds.centerX;
    const y0 = ibnds.centerY;
    const x1 = lbnds.centerX;
    const y1 = lbnds.centerY;
    const w = Math.max(Math.abs(x1 - x0), 1);
    const h = Math.max(Math.abs(y1 - y0), 1);
    const x = (x0 < x1) ? x0 : x0 - w;
    const y = (y0 < y1) ? y0 : y0 - h;
    const opp = (x === x0) !== (y === y0);
    const xa = opp ? w : 0;
    const ya = 0;
    const xb = opp ? 0 : w;
    const yb = h;
    const oldgeo = line.geometry;
    if (oldgeo === null ||  // only if geometry needs changing
      Math.abs(xa - oldgeo.startX) > 0.01 ||
      Math.abs(ya - oldgeo.startY) > 0.01 ||
      Math.abs(xb - oldgeo.endX) > 0.01 ||
      Math.abs(yb - oldgeo.endY) > 0.01) {
      const geo = new go.Geometry(go.GeometryType.Line);
      geo.startX = xa;
      geo.startY = ya;
      geo.endX = xb;
      geo.endY = yb;
      line.geometry = geo;
    }
    this.measureElement(line, w, h, 0, 0);
    this.arrangeElement(line, x, y, line.measuredBounds.width, line.measuredBounds.height);
  }
}
// end PanelLayoutLinkDecoratedLine

go.Panel.definePanelLayout("LinkDecoratedLine", new PanelLayoutLinkDecoratedLine());

Thank you so much. It works perfectly. Have a nice day Walter.