Header fixed and Dynamic link routing

Please forgive me for not being able to speak English well.

Q1. We need fixed swimlane Header. when scrolling Right and left.

I tried Diagram.addDiagramListener(“ViewportBoundsChanged”) , but I don’t know how to give the position value.

        myDiagram.addDiagramListener("ViewportBoundsChanged", e => {
            e.diagram.commit(dia => {
                dia.groupTemplateMap.each(group => {
                    // what logic for header fixed ... ?

                })
            }, null);  // set skipsUndoManager to true, to avoid recording these changes
        });
       // Left side Group Panel (Header) Area
        myDiagram.groupTemplateMap.add("Pool",
            $(go.Group, "Auto", groupStyle(),
                { // use a simple layout that ignores links to stack the "lane" Groups on top of each other
                    layout: $(PoolLayout, { spacing: new go.Size(0, 0) }),  // no space between lanes
                },
                $(go.Panel, "Table", { margin: 8 },
                    $(go.Shape, "RoundedRectangle", roundedRectangleParams,
                        {
                            strokeWidth: 0,
                            column: 0,
                            margin: 4,
                            stretch: go.GraphObject.Fill,
                        },
                        new go.Binding("fill", "color"),
                    ),
                    $(go.Panel, "Horizontal",
                        $(go.TextBlock,
                            {
                                // font: "bold 16pt sans-serif",
                                font: "16px noto-sans",
                                stroke: "white", // fontColor
                                textAlign: "center",
                                margin: 8,
                                maxSize: new go.Size(120, NaN),
                                minSize: new go.Size(120, NaN),
                                // margin: new go.Margin(2, 30, 0, 0),
                                // strokeWidth: 0,
                                // fill: "#2E8DEF",
                                //   stretch: go.GraphObject.Fill
                                // stretch: go.GraphObject.Vertical
                            },
                            new go.Binding("text").makeTwoWay()),
                        // new go.Binding("isFixed", "isFixed"), // delete soon
                    ),
                    $(go.Placeholder,
                        { column: 1 })
                )
            ));

→ environment : react, gojs 2.3.*
→ layout : PoolLayout
→ structure : Group, Lane

Q2. Links must pass through empty nodes But Non-empty nodes must not be traversed.

I want the link (going across the node) to move towards the left link.
(In the picture, blue circle on the right)

→ my link options.

linkTemplate = $(go.Link, {
                    routing: go.Link.AvoidsNodes, 
                    fromSpot: go.Spot.Bottom,
                    toSpot: go.Spot.Top,
                    ...
     })

Any advice, thanks!

Q1: I’ll look into this. Which sample did you start from?

Could you put the “headers” in a separate Diagram? Note how these timeline samples do it:
Basic Month Timeline
Basic Week/Day Infinite Timeline
Also try zooming in and out. These show more than you are asking for because both the row headers and the column headers are “fixed”.

Q2: I think you want to set or bind the Node.avoidable property so that it is false when the node is “empty”.

Thank you for answer. But it’s still a work in progress.

Q1.
We started based on the swimLanes sample. I want the blue lane nodes (category: “Pool”, isGroup: true) to be fixed when scrolling. I am implementing the sample you provided, and I hope the node sizes are synchronized. How can I do this? The size of the node changes when i click the expand button.
(The node size*(width, height) was not set, but was determined in layout)

        //header diagram div
        const myRowHeaders =
            new go.Diagram("myRowHeadersDiv",
                {
                    isReadOnly: true,
                    "animationManager.isEnabled": false,
                    allowHorizontalScroll: false,
                    contentAlignment: go.Spot.Top,
                    padding: 0,
                    layout: $(PoolLayout, {
                    }),
                    // adjust the height of the document bounds to match that of the main diagram
                    computeBounds: function () {  // method override
                        const diagb = go.Diagram.prototype.computeBounds.call(this);
                        const mainb = myDiagram.documentBounds;

                        if (mainb.isReal()) {
                            return new go.Rect(diagb.x, 0, diagb.width, mainb.bottom);
                        } else {
                            return diagb;
                        }
                    },
                    "ViewportBoundsChanged": e => {  // scroll the main diagram vertically precisely the same way
                        myDiagram.scale = e.diagram.scale;
                        myDiagram.position = new go.Point(myDiagram.viewportBounds.x, e.diagram.position.y);
                    },
                    nodeTemplate:
                        $(go.Node, "Auto", // groupStyle(),
                            { selectionChanged: onSelectionChanged },
                            $(go.Shape, "RoundedRectangle", roundedRectangleParams,
                                new go.Binding("fill", "color"),
                            ),
                        ),
                    model: new go.GraphLinksModel(
                        {
                            nodeKeyProperty: 'key',
                            linkKeyProperty: 'key',
                            linkFromKeyProperty: 'from',
                            linkToKeyProperty: 'to',
                            nodeDataArray: props.nodeDataAry, // Same as main diagram (myDiagram)
                            linkDataArray: props.linkDataAry // Same as main diagram (myDiagram)
                        }),
                });

        myRowHeaders.groupTemplateMap = myDiagram.groupTemplateMap;

        function initRowHeaders() {
            myDiagram.addDiagramListener("ViewportBoundsChanged", e => {
                // Automatically synchronize this diagram's Y position with the Y position of the main diagram, and the scale too.
                myRowHeaders.scale = myDiagram.scale;
                myRowHeaders.position = new go.Point(0, myDiagram.position.y);
            });

            myDiagram.addDiagramListener("DocumentBoundsChanged", e => {
                // The row headers document bounds height needs to be the union of what's in this diagram itself and
                // the what's in the main diagram; but the width doesn't matter.
                myRowHeaders.fixedBounds = new go.Rect(0, myDiagram.documentBounds.y, 0, myDiagram.documentBounds.height)
                    .unionRect(myRowHeaders.computePartsBounds(myRowHeaders.parts));
            });
        }
        initRowHeaders();

Q2.
In the case of links going from bottom to top, this was temporarily solved by changing it to (link.fromSpot = go.Spot.Right). I gave the *(routing: go.Link.AvoidsNodes) option, but passing the empty nodes, seems to be a bug. Any other advice?

Q1: Yes, when someone changes the height of a row by expanding a Node you’ll need to either expand or just change the height of the corresponding Part or Node in the row headers Diagram.

You’re not necessarily going to get a “ViewportBoundsChanged” DiagramEvent when a node changes size, so you’ll need to depend on either a “SubGraphCollapsed” or “SubGraphExpanded” event or you’ll need to add some code to the button click event handler. GoJS Events -- Northwoods Software GoJS Events -- Northwoods Software
That code will need to find the corresponding row header Part and set its height or do whatever is appropriate depending on how the template is defined. In those samples those node templates have bindings on the position – maybe you also want a binding on the height. Then you won’t need to set the Diagram.layout on the row headers diagram. But setting the layout might work as long as you can get the details to have the same behavior as in the main diagram’s layout. Hmmm, you also have the row headers diagram sharing all of the group templates with the main diagram. Were you planning on those row header nodes being Groups or just plain Nodes? If you’re not going to have any Groups there you don’t need to share the Diagram.groupTemplateMap. Oh, wait, now I see that you said that you wanted those blue nodes to be groups. I don’t believe that that need be the case since you have specified the row headers Diagram.nodeTemplate. But it’s your choice how you want to implement it.

Also I suggest that you do what those two samples I gave you do regarding the document bounds of the row headers diagram – implement a “DocumentBoundsChanged” listener on the main Diagram that sets the Diagram.fixedBounds of the row headers diagram with the appropriate height. So you won’t need to override Diagram.computeBounds.

Both of those listeners are defined in the initRowHeaders function of those samples. In fact those functions look exactly the same for both of those samples.

By the way, if you aren’t planning on having any Links in the row headers Diagram, you don’t need to set those link… properties on the model.

Q2: Did you bind the Part.avoidable property on the template(s) whose instances that you want the links to go through? If you debug the bindings conversion function, is it returning the correct value appropriately?