Multiple Labels for Nodes and Links

Is there a way to attach multiple labels (perhaps represented as nodes) to a link and have their placement be relative to the link itself via segmentOffset?

I’ve taken a look at a few samples, but it’s not immediately clear how to do this; I was thinking something involving itemArrays and maybe the linkLabelKeysProperty (as an array) on the link, but I’m not sure how to bind their location and update it when the link is moved/rerouted.

If you could point me in the right direction, that would be excellent. Thank you!

Look at the new Link Label On Path Dragging Tool and sample, Draggable Link Labels That Stay On Path.

You can easily add link labels to the link template. For example, for three of them, each individually draggable:

    myDiagram.linkTemplate =
      $(go.Link,
        {
          routing: go.Link.AvoidsNodes,
          corner: 5,
          relinkableFrom: true,
          relinkableTo: true,
          reshapable: true,
          resegmentable: true
        },
        $(go.Shape),
        $(go.Shape, { toArrow: "OpenTriangle" }),
        $(go.Panel, "Auto",
          { _isLinkLabel: true },  // marks this Panel as being a draggable label
          $(go.Shape, { fill: "white" }),
          $(go.TextBlock, "?", { margin: 3 },
            new go.Binding("text", "color")),
          // remember any modified segment properties in the link data object
          new go.Binding("segmentIndex").makeTwoWay(),
          new go.Binding("segmentFraction").makeTwoWay()
        ),
        $(go.Panel, "Auto",
          { _isLinkLabel: true, segmentIndex: 1, segmentFraction: 0.5 },  // marks this Panel as being a draggable label
          $(go.Shape, { fill: "pink" }),
          $(go.TextBlock, "?", { margin: 3 },
            new go.Binding("text", "color2")),
          // remember any modified segment properties in the link data object
          new go.Binding("segmentIndex", "segmentIndex2").makeTwoWay(),
          new go.Binding("segmentFraction", "segmentFraction2").makeTwoWay()
        ),
        $(go.Panel, "Auto",
          { _isLinkLabel: true, segmentIndex: 3, segmentFraction: 0.5 },  // marks this Panel as being a draggable label
          $(go.Shape, { fill: "lightgreen" }),
          $(go.TextBlock, "?", { margin: 3 },
            new go.Binding("text", "color3")),
          // remember any modified segment properties in the link data object
          new go.Binding("segmentIndex", "segmentIndex3").makeTwoWay(),
          new go.Binding("segmentFraction", "segmentFraction3").makeTwoWay()
        )
      );

I think you can have a variable number by binding Panel.itemArray, although you might need to include any arrowhead(s) in your items.

The link to that sample is broken. Does it still exist?

Yeah, I would like a dynamic number of labels on a given link, which the user can add/remove and re-position.

I was thinking of creating nodes with a linkLabel category (as outlined in other samples) which have bindings to things like text and location. However, I’m unsure how to (if it’s even possible) to correlate something like segmentOffset of a link to the position of a node.

Am I thinking about this in the right manner? Or is this totally the wrong way to go about it?

Edit: I also have requirements of “attaching” labels to nodes and to the diagram itself. These labels should exist on a “label” layer as well.

Oops, sorry about that; try this one: Draggable Link Labels That Stay On Path

That demos a new extension that is a variation of the LinkLabelDraggingTool: State Chart with Draggable Link Labels.

You could implement them as separate Nodes, but that’s more work and “heavier” to implement. The main reason for having Nodes as labels on Links is so that other Links can connect with such Nodes, effectively connecting Links with Links.

Right, I understand that labels as nodes will be more involved to implement, but I’m not convinced that I’ll be able to build the functionality I require in a reasonable way without them.

To give more context, a user should be able to create a “Data Annotation” (often called a “label”) on the diagram. This Annotation is bound to data that changes based on the state of the business model. An annotation can be either placed directly on the diagram in a single position - this is obviously a simple node and trivial to implement.

However, some Annotations should be “linked” with another Node or Link and move relatively to the position of the master node or link. These Annotations can be very complex as well (think data-tables, sparklines, etc).

I guess my question is: “Given these sorts of requirements, does it make sense to implement every ‘data annotation’ as a node; if so, how do I go about binding a relative location?” I would prefer to keep my API simple and not have 3 separate implementations of how annotations work when pinned to the diagram vs to a node vs to a link.

Ah, OK, if those annotations are “heavier” objects, it might make sense to implement them as separate Nodes. That would also be necessary if you wanted to allow users to select them – say for multiple selection or for deleting or copying.

The information for the layout of simple labels on Links is exactly the same as for label Nodes on Links – the list of GraphObject properties described at GoJS Link Labels -- Northwoods Software, most of which have names that start with “segment…”.

So for Binding purposes you would use the same expressions whether defining simple labels within a Link or label Nodes on a Link. If you’re going to have a variable number of them, the bookkeeping is slightly different – a Panel.itemArray on the Link or just a random collection of node data whose keys are referenced in the Array that is the value of GraphLinksModel.getLabelKeysForLinkData.

Thanks Walter.

If I understand correctly, a Panel cannot exist on a separate layer from the rest of the template (since it’s a GraphObject and not a Part). This means that using a Panel.itemArray is not possible given my requirements.

A random collection of node data would probably work by referencing them via GraphLinksModel.getLabelKeysForLinkData, however, is there something similar for nodes?

At the model level, GraphLinksModel.getLabelKeysForLinkData returns an Array of node keys. You’ll need to set GraphLinksModel.linkLabelKeysProperty first.

At the diagram level, Link.labelNodes returns a collection of Nodes.

Sorry, what I meant to ask is since it’s relatively straight-forward to associate a collection of nodes with a link via linkLabelKeysProperty, is there also a way to associate a collection of nodes with another node?

Well, there are Groups. GoJS Groups -- Northwoods Software

But note that just as each Link “owns” all of its Link.labelNodes, each Group “owns” all of its Group.memberParts. Deleting or copying a Link will automatically delete or copy its label Nodes, and deleting or copying a Group will automatically delete or copy its member Nodes and Links.

Any Node can belong to at most one Link (Node.labeledLink), and any Part can belong to at most one Group (Part.containingGroup). At the model level, use GraphLinksModel.getGroupKeyForNodeData and the corresponding setGroupKeyForNodeData method.

Ah, that’s clever! I’ll give it a try. Thanks!