Margins of shapes don't overlap

I have an implementation that has nodes with expandable sections.
In the expansion area there is a list of items made of shapes.
if its the first item it uses RoundedTopRectangle, if its the last item it uses RoundedBottomRectangle else it uses Rectangle. The default stroke width is 1. The default margin is 0 for the first item else go.Margin(-1,0,0,0). The default works fine and the margins overlap each other. The problem arises when the items are selected. The selection dynamically changes the stroke color and the stroke width to 2. Since the stroke width changes to 2, the margin top and bottom need to change. For instance if the are 3 items and the 3rd item is selected, the first item takes margin 0 the second item takes margin top -1 and should take margin bottom -2 and the third item should take margin 0 (margin top 0) but what happens is the margin between the second and third item does not overlap.


Another example if all items are selected, the first item takes margin 0, the second margin -2 top and 0 bottom, the third item -2 top and 0 bottom. The first and second item overlaps correctly but the second and third item leaves a gap.
image

To help explain the problem, the ‘RoundedTopRectangle’, & ‘RoundedBottomRectangle’ are shapes that are used for the opposite ends of a repeating (itemArray) structure. However, with shapes adjacent to each other, the strokes/borders appear doubled. The use of a negative margin is to make the borders appear collapsed.

Attempts to only apply a stroke to select sides of the shapes, have proven unsuccessful.

Here is the unwanted display:
image

Here is the expected display:
image

Here’s a complete stand-alone sample that I think demonstrates what you want.
I didn’t bother with the rounded rectangles – just about the overlapping margins.
Basically I think you need to account for all four possible combinations of selected items to determine the top margin.

<!DOCTYPE html>
<html>
<head>
  <title>Overlapping Items</title>
  <!-- Copyright 1998-2024 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">
const myDiagram =
  new go.Diagram("myDiagramDiv",
    {
      initialScale: 3,
      "undoManager.isEnabled": true,
      "ModelChanged": e => {     // just for demonstration purposes,
        if (e.isTransactionFinished) {  // show the model data in the page's TextArea
          document.getElementById("mySavedModel").textContent = e.model.toJson();
        }
      }
    });

const NORMAL = 1;
const SELECTED = 3;
myDiagram.nodeTemplate =
  new go.Node("Vertical", {
      selectionAdorned: false
    })
    .add(
      new go.TextBlock()
        .bind("text"),
      new go.Panel("Vertical", {
          itemTemplate:
            new go.Panel("Auto", {
                stretch: go.GraphObject.Horizontal,
                click: (e, pan) => {
                  e.diagram.model.commit(m => {
                    m.set(pan.data, "selected", !pan.data.selected);
                  });
                }
              })
              .bind(new go.Binding("margin", "itemIndex", i => new go.Margin(i > 0 ? -NORMAL : 0, 0, 0, 0)).ofObject())
              .bind("margin", "selected", (s, pan) => updateMargins(pan))
              .add(
                new go.Shape({ fill: "white" })
                  .bind("stroke", "selected", s => s ? "dodgerblue" : "black")
                  .bind("strokeWidth", "selected", s => s ? SELECTED : NORMAL),
                new go.TextBlock({ margin: new go.Margin(4, 4, 3, 4) })
                  .bind("text")
              )
        })
        .bind("itemArray", "items")
    );

function updateMargins(pan) {
  const idx = pan.itemIndex;
  const container = pan.panel;
  const next = (idx+1 <= container.elements.count-1) ? container.elt(idx+1) : null;
  if (next) next.margin = marg(pan.data.selected, next.data.selected);
  const prev = (idx > 0) ? container.elt(idx-1) : null;
  if (prev) return marg(prev.data.selected, pan.data.selected);
  return new go.Margin(0);
}

function marg(a, b) {
  if (a && b) return new go.Margin(-SELECTED, 0, 0, 0);
  if (a && !b) return new go.Margin(0);
  if (!a && b) return new go.Margin(0);
  return new go.Margin(-NORMAL, 0, 0, 0);
}

myDiagram.model = new go.GraphLinksModel(
[
  { key: 1, text: "Alpha", items:
    [
      { text: "first" },
      { text: "second" },
      { text: "third" },
      { text: "fourth" },
    ]
  },
]);
  </script>
</body>
</html>

Hi walter, the above works well if all items are selected. I made this change if (!a && b) return new go.Margin(-SELECTED, 0, 0, 0); which allows for an overlap if prev is not selected and current is. Nothing i do allows a proper overlap for when current is selected and next isnt OR current is selected and both previous and next arent. Do you have any suggestions ?
image

What I implemented was my best guess for providing what I thought was the best experience – there is overlap when consecutive items have the same selected status, but none when they differ. Basically I thought you could get whatever behavior you wanted just by controlling the top margin of the next item.

If you want the behavior that you show, where it overlaps (too much) when transitioning from not selected to selected, but there’s no overlap when transitioning from selected to not selected, that’s great. I guess I do not understand what you mean by “proper overlap”.

So if you look at the last image, the margin between the third and the fourth item doesnt overlap, the third item just sits on top of the fourth item. The behaviour i would want is that the margin top on the fourth item would not show/be overlapped but the third item.

I don’t understand this. Are you saying that you don’t want there to be overlap between the two borders when the top item is selected and the bottom item is not selected? It sounds as if you want there to be no overlap between the selected third item and the unselected fourth item, which is exactly the behavior I provided in the marg function for the (a && !b) case.

But if I misunderstand you, perhaps you want to change the returned Margin for that case too.

No I want there to be an overlap (like the one seen between the first and the second item ). When I try to change the returned for that case (a && !b) it doesn’t work.

What are you returning in that (a && !b) case?

Instead of returning a zero Margin as I did in the code above, you could return new go.Margin(-NORMAL, 0, 0, 0) or new go.Margin(-SELECTED, 0, 0, 0). I suppose you could return something other than those three choices, but it would be weird.

I tried both new go.Margin(-NORMAL, 0, 0, 0) and new go.Margin(-SELECTED, 0, 0, 0). using -NORMAL makes no difference; using -SELECTED creates this weird behaviour
image
I tried making the bottom margin -Selected and the next top margin 0 but that also didnt work :/

Well, if you don’t like -SELECTED top margin, don’t use it. But that is the correct appearance if you have chosen that top margin.

However, using -NORMAL top margin does make a difference, but it’s only one pixel at the default scale, so perhaps you didn’t notice it.

I still think that the code I gave you above is the most sensible for most people.

Remember that each element in a panel is drawn in the order in which they appear in the panel’s Panel.elements list. So if there is overlap, the next item will appear in front of the previous item.

So you’re saying there’s no way to make the first item appear in front of the next item?

After some experimentation I have determined that is possible, but to what end? If they are always drawn in the opposite order, and if you change the bottom margin instead of the top margin, you still have the same problem for the other case where consecutive items have opposite selected values.

Maybe you always want all selected items to drawn after all unselected items? I don’t think you can control the drawing order with that kind of specificity for the “Vertical” Panel. Maybe you could do that with a “Table” Panel, but that would require some work.