Detecting changes to TextBlock's lineCount

Is there a way to detect changes to the lineCount property on a TextBlock? What I’m trying to achieve is that when the text changes in the TextBlock (either via the inline editor or a change in the property that its bound to) I want to update the position of another GraphObject. Is this possible?

I’ve been able to execute code when the text changes but at that point the TextBlock hasn’t been rerendered thus the lineCount hasn’t changed.

How is that other GraphObject related to the modified TextBlock?
Are they in the same Node or Link?
If they are in the same Part, are they in the same Panel, and if so, what kind of Panel is it?

Here is the structure;


  • Panel.Spot
    • TextBlock
  • Panel.Vertical (the Panel who’s position I’d like to alter)

I assume there are other GraphObjects in that “Spot” Panel, or else there wouldn’t be any need for having that Panel at all.

You cannot control the position of a GraphObject in a “Vertical” Panel. You can control its X position via its horizontal alignment.

I still don’t understand what it is that you want to do. I’m wondering if there’s a different way to do it.

Fair enough, I’ll start over as my question was poorly worded.

First of all, here’s how it looks with fewer omissions;

 GraphObject.make(Panel, Panel.Vertical,
     GraphObject.make(Panel, Panel.Spot,
         GraphObject.make(Shape, "Circle",
             "width": ...,
              "height": ...,
         // TextBlockA
         GraphObject.make(TextBlock, {
             "name": "NodeLabel",
             "margin": new Margin(3, 0, 0, 0),
             "maxLines": 2,
             "overflow": TextBlock.OverflowEllipsis,
             "stretch": GraphObject.Horizontal,
             "textAlign": "center",
             "width": ...,
             "height" 40,
             "alignment": new Spot(.5, .5, 0, 45),
         // Large gap here when there is no label text, or only a single line of text
        GraphObject.make(Panel, Panel.Vertical,
            {margin: new Margin(4, 0, 0, 0)},
            // TextBlockB
            GraphObject.make(TextBlock, { ...})
            GraphObject.make(Panel, { ...})
            // Attempted solution for 2)
            new Binding("text", "", (data, targetObj) => {
                // Attempt to adjust this panel's margin
                // Doesn't work as lineCount hasn't been updated when this fires        ​
               ​if (targetObj.panel.findObject("NodeLabel").lineCount === 2) {
                   ​ = 4;
               ​else {
                   ​ = -16;

What I have is a shape (Circle) with a TextBlock (TextBlockA) beneath it. Beneath that I have yet another TextBlock (TextBlockB). TextBlockA is limited to 2 lines and has a fixed height. The problem with that fixed height is that it means that when TextBlockA only has one line of text, we end up with a large, unwanted gap between A and B.

Here’s what I’ve tried so far;

  1. Remove the fixed height on TextBlockA so that it grows in height as the user enters text. This results in no gap between the two TextBlocks (great) and pushes TextBlockB down when the text in TextBlockA wraps. This works - except that the resize of TextBlockA results in its position changing and it ends up overlapping the shape above it.

  2. Keep the fixed height on TextBlockA and adjust the top margin on TextBlockB so that it moves as the text in TextBlockA wraps. This almost works - the issue as I mentioned in my first post is that the lineCount isn’t correct at the time the model updates, presumably because TextBlockB has yet to be rerendered. I’m also not happy about using a Binding for this as I don’t believe that this is what it’s intended to be used for.

People reading this thank you for taking the trouble to generalize by removing irrelevant details – I wish everyone did that.

Is the circle’s height <= 50? Did you want to avoid having TextBlockA overlap that circle? If so, could you just place the circle Shape, both TextBlocks, and the last most deeply nested Panel all in the outer Vertical Panel? No need for the nested Spot Panel or nested Vertical Panel.

But if not, that is if you do want some overlap between the circle and TextBlockA, could you change the Spot Panel so that the TextBlockA is the main element and the circle Shape is aligned relative to the TextBlock? Remember to set isPanelMain to true on the TextBlock if it is not the first element of the Spot Panel because you want the TextBlock to be in front of the circle Shape.

Is the circle’s height <= 50?

Yes, 42

Did you want to avoid having TextBlockA overlap that circle?

I do, and in fact there is no overlap with the way it works currently.

If so, could you just place the circle Shape, both TextBlocks, and the last most deeply nested Panel all in the outer Vertical Panel?

I tried this and the issue is movement. I removed the height on TextBlockA and when the text on it wraps it pushes the circle upwards. It looks like the TextBlock grows upwards and downwards on wrap so everything above it gets pushed up, and everything below pushed down. I think my problem would be solved if that additional height was only applied to the bottom of the textblock.

The positions of all elements of a panel are all within that panel’s coordinate system; from a point of view outside of the panel, the positions are all relative to each other.

Instead of setting the height, would it help if you set the maxSize to new go.Size(NaN, ...something...)? But now I’m unclear what it is that you want, so that might be the wrong suggestion. I still think not using a Spot Panel is the right thing to do for your situation.

Yeah my poor explanations definitely aren’t helping! I’ll try something more visual…


You can see in this recording that when more text is added to TextBlockA and it begins to wrap, TextBlockB is pushed downwards (good) and the circle and vertical line above it are pushed upwards (not so good). Is there any way to prevent the circle and line from moving when TextBlockA wraps and increases in height?

I’m not sure what’s going on there. In general I’d say it was the layout’s responsibility for positioning the node as it changes size, but it depends a lot on the circumstances.

Try this example:

<!DOCTYPE html>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
  <button id="myTestButton">Test</button>

  <script src=""></script>
  <script id="code">
function init() {
  const $ = go.GraphObject.make;

  myDiagram =
    $(go.Diagram, "myDiagramDiv",
        "undoManager.isEnabled": true

  myDiagram.nodeTemplate =
    $(go.Node, "Vertical",
      $(go.Shape, "Circle",
        { width: 42, height: 42, fill: "lightgray", portId: "" }),
      // TextBlockA
          margin: new go.Margin(3, 0, 0, 0),
          maxLines: 2,
          overflow: go.TextBlock.OverflowEllipsis,
          stretch: go.GraphObject.Horizontal,
          textAlign: "center"
        new go.Binding("text")),
      // TextBlockB
      $(go.TextBlock, "TextBlockB")

  myDiagram.model = new go.GraphLinksModel(
    { key: 1, text: "Alpha", color: "lightblue" },
    { key: 2, text: "Beta", color: "orange" },
    { key: 3, text: "Gamma", color: "lightgreen" },
    { key: 4, text: "Delta", color: "pink" }
    { from: 1, to: 2 },
    { from: 1, to: 3 },
    { from: 2, to: 2 },
    { from: 3, to: 4 },
    { from: 4, to: 1 }
window.addEventListener('DOMContentLoaded', init);

  e => {
    let node = myDiagram.selection.first();
    if (node === null) node = myDiagram.nodes.first();
    if (node instanceof go.Node) {
      myDiagram.model.commit(m => {
        m.set(, "text", > 12 ? "TextBlockA" : "abcdefghijklmnopqrstuvwxyz");