Correctly inherit a Link class with shape paths

This is potentially a bug, but more likely this I’m just missing the mark.

I’m attempting to implement the library by extending each of the classes as appropriate to build out the diagram parts in a clean reusable way.

My issue is specifically when I extend the Link class, it doesn’t seem to respect that the child shapes should only have its styles inherited, not actually render it. This is where I’m probably missing something.

Extended Shape
import go from 'gojs';
import theme from '@pkg/mui-theme';

export default class Shape extends go.Shape {
  constructor() {
    super();
    this.strokeWidth = 2;
    this.stroke = theme.palette.primary.main;
  }
}
Extended Link (objective)
import go from 'gojs';
import Menu from './Menu';
import Shape from './Shape';

export default class Link extends go.Link {
  constructor(context) {
    super();

    this.name = 'Link';
    this.corner = 2;
    this.contextMenu = new Menu(context);
    this.routing = go.Link.Orthogonal;
    this.selectionAdorned = false;

    this.add(new Shape());
  }
}
Working alternative
    // inside Diagram.constructor()
    const link = new go.Link();
    link.corner = 2;
    link.contextMenu = new Menu(contexts.link);
    link.routing = go.Link.Orthogonal;
    link.selectionAdorned = false;
    link.add(new Shape());
    this.linkTemplate = link;

The below is what happens when I use the extended version:

The problem is that the constructor is creating and adding a Shape. Panel subclass constructors should not do that.

I believe that defining a subclass without overriding any methods does not provide any advantages. What does your link template look like? Or are you not using templates at all?

If you seek to simplify templates or avoid duplicating code in multiple templates, just define and call helper functions. There are lots of examples – look for functions named “…Style”.

Thanks for getting back to me, Walter!

I’d be happy to dive into the merits of this approach as it works really well for everything else, though I would like to understand why this is happening.

The extended link is the template. The diagram has a similar constructor that assigns the template as follows. This allows us to easily invoke a new diagram as so: const diagram = new Diagram(...args);

Diagram class
import go from 'gojs';
import env from '@beam-australia/react-env';
import TreeLayout from '../../layouts/TreeLayout';
import DraggingTool from '../../tools/DraggingTool';
import Link from '../Link';
import Team from '../Team';

export default class Diagram extends go.Diagram {
  constructor(div, data, contexts, options) {
    super(div);

    this.isReadOnly = options.isReadOnly ?? true;

    this.draggingTool = new DraggingTool();
    this.initialAutoScale = go.Diagram.Uniform;
    this.layout = new TreeLayout();
    this.licenseKey = env('GOJS_LICENSE_KEY');
    this.maxSelectionCount = 1;
    this.model = new go.TreeModel(data, { nodeParentKeyProperty: 'parent' });
    this.padding = 96;
    this.validCycle = go.Diagram.CycleDestinationTree;
    
    this.linkTemplate = new Link(contexts.link);
    this.nodeTemplate = new Team();
  }

  mouseDrop({ diagram }) {
    diagram.layoutDiagram(true);
  }
}

I think to distill this question down to its core, what exactly does the Link do differently with its child shapes? From what I can tell, it seems that unless the parent is strictly an instance of go.Link, then it does not apply whatever side effect that causes the actual shape not to render.

The problem is that copying an instance of the Link will do the wrong thing.

  • First it will make a construct an instance of your Link class, resulting in a visual tree consisting of:
    Link
        Shape
  • Then because a Link is a Panel, the panel will copy its elements, which includes a Shape. That results in:
    Link
        Shape
        Shape

And I’m guessing that the second Shape gets its default size (100x100) and default fill and stroke (black), because it’s not being used as a second link path.

(The default is that there is only one shape used for the link path. If you want more than one link path shape, you need to set isPanelMain on each of them to true. But I’m assuming that’s not the case here.)

Thanks, that managed to help get a working version that doesn’t explicitly extend, though it does feel a little weird comparatively.

Link class
import go from 'gojs';
import Menu from './Menu';
import Shape from './Shape';

export default class Link {
  constructor(context) {
    const link = new go.Link();

    link.contextMenu = new Menu(context);
    link.corner = 2;
    link.routing = go.Link.Orthogonal;
    link.selectionAdorned = false;

    link.add(new Shape());

    return link;
  }
}

This will work for now, so happy to call this solved!

My last question would be, is there a different way I could be “adding” a shape to the Link such that it doesn’t get copied? Should I be making a Panel of type Link and passing that to the linkTemplate instead?

Yes, if you don’t have those classes inheriting from a GraphObject class, that should work.

No, there isn’t any reasonable way to control how Panels copy their elements.