Auto Alignment of TextBlock at the center but within boundaries

I have a polygonal group for which I want its TextBlock to stay at the calculated center but within boundaries at all times, basically while creating and resizing.
Currently I have fixed it at the center but can be outside in odd shapes.

If a logical center inside boundaries is too typical to calculate, i can imagine it being anywhere but necessarily inside boundaries.

It happens something in the below situation -

image

Did you have a question?

I am sorry, i wanted to know if TextBlock could be positioned through custom logic or can there be a straightforward solution to my problem, if yes how ?

Your requirements conflict with each other, but you are saying that having the text within the polygon is more important. So you are asking for a way to position and wrap the text so it fits inside the polygon. But what about when it cannot fit?

There is no easy solution. If you search the web you can find some suggestions for how to find the most promising areas in a polygon.

my TextBlock is of fixed size, and the Group has a minimum size bigger than TextBlock.
will this make it any easier to keep the TextBlock inside the boundaries, if yes how ?

Not if the user has reshaped the geometry so that it has become thinner than the area of the text.

Could resizing the group also check if the TextBlock always stays inside ?

In one line, I want to keep TextBlock inside the polygonal group while creating and resizing. Period.

I am starting to think why has this not been asked earlier in the forums. Is this a very rare situation ?

Well, a quick search reveals: Move the parts with group while resizing to samller dimension

So, yes, this requirement seems to be unusual. I’ll see what I can come up with, but
I don’t know if or when.

Yes, but this one talks about the node staying inside while resizing.

Thanks @walter, I will be waiting for it surely.

https://gojs.net/temp/geometryContains2.html

It uses https://gojs.net/temp/GeometryHelper.js and the standard GeometryResizingTool extension.

The idea of making the TextBlock Node draggable will work for me, but you have declared the groupTemplate and TextBlock Node separately. What I am looking for is to incorporate this TextBlock in the default groupTemplate so that it becomes a part of it and appears as a default entity even when I create a zone using the polyline tool.

I tried putting the exact node template in place of my current TextBlock Panel but the error says “use a Panel instead.” . And when I change it to Panel, it says "Panel does not have a property of dragcomputation"

Below is my groupTemplate for reference.

 this.floorPlan.groupTemplate = $(
      go.Group,
      go.Group.Spot,
      {
        layerName: "BlockLayer",
        locationSpot: go.Spot.TopLeft
      },
      {
        // This is to prevent the overlap both while creating and dragging
        dragComputation: avoidNodeOverlap
      },
      {
        selectionAdorned: true,
        selectionObjectName: "SHAPE",
        // custom selection adornment: a blue rectangle
        selectionAdornmentTemplate: $(
          go.Adornment,
          "Auto",
          $(go.Shape, { stroke: "dodgerblue", fill: null }),
          $(go.Placeholder, { margin: -1 })
        )
      },
      { resizable: true, resizeObjectName: "SHAPE" },
      { reshapable: true },
      $(
        go.Shape,
        {
          name: "SHAPE",
          fill: groupFill,
          stroke: groupStroke
        },
        new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(
          go.Size.stringify
        ),
        new go.Binding("angle").makeTwoWay(),
        new go.Binding("geometryString", "geo").makeTwoWay()
      ),
      {
        resizable: true,
        resizeObjectName: "SHAPE"
        // because the gridSnapCellSpot is Center, offset the Group's location
        // locationSpot: new go.Spot(0, 0, CellSize.width / 2, CellSize.height / 2),
        // zOrder:1
      },
      // always save/load the point that is the top-left corner of the node, not the location
      new go.Binding("position", "pos", go.Point.parse).makeTwoWay(
        go.Point.stringify
      ),
      {
        // what to do when a drag-over or a drag-drop occurs on a Group
        mouseDragEnter(e, grp, prev) {
          highlightGroup(grp, true);
        },
        mouseDragLeave(e, grp, next) {
          highlightGroup(grp, false);
        },
        click(e, grp) {
          if (
            parentctxt.stampingMode &&
            e.diagram.toolManager.clickCreatingTool.archetypeNodeData &&
            Object.entries(
              e.diagram.toolManager.clickCreatingTool.archetypeNodeData
            ).length
          ) {
            e.diagram.startTransaction("stamp product");
            const p = e.diagram.toolManager.clickCreatingTool.insertPart(
              new go.Point(e.documentPoint.x + 15, e.documentPoint.y + 15)
            );
            p.data.zoneId = grp["data"].groupId;
            let list = new go.List<go.Part>();
            list.add(p);
            grp["addMembers"](list, true);
            e.diagram.commitTransaction("stamp product");
            parentctxt.updateBOM(p, 1);
          }
        },
        contextClick(e) { }
      },
      $(
        go.Panel,
        "Table",
        $(
          go.TextBlock,
          {
            overflow: go.TextBlock.OverflowEllipsis,
            textAlign: "left",
            font: "11pt Proxima Nova Helvetica Neue Helvetica Arial sans-serif",
            stroke: "red",
            editable: true,
            isMultiline: false,
            textValidation: this.isZoneNameValid,
            },
          },
          new go.Binding("text", "key").makeTwoWay()
        )
      ),
    );

OK, so you don’t want that text object to be a separate Node because its lifetime should be exactly the same as that of the Group.

Then you’ll need to change the customizations of the ResizingTool and the GeometryReshapingTool to consider the area of that group’s TextBlock, in document coordinates by calling GraphObject.getDocumentBounds, rather than considering the Node.actualBounds of all of the Group.memberParts that are Nodes.

Hmmm, I guess you have removed the custom Node.dragComputation because you have removed that template. But if you still want the user to move that TextBlock interactively, you can implement a custom tool that is much like https://gojs.net/latest/extensions/NodeLabelDragging.html but that overrides doMouseMove to be smarter about where the TextBlock can be dragged.

By the way, there’s no point in having a “Table” Panel (or “Horizontal” or “Vertical” Panel) with only one element in it.

But I still dont understand how will that TextBlock node be automatically created as soon as I finish making the shape using polyline tool ?

I thought you didn’t want that text to be implemented as a Node.

If the TextBlock is in the Group template, it will automatically be created. I was assuming the polygonal Shape will also be created every time, and that it would start off as a rectangle, so that you could be sure that positioning the TextBlock in the middle will be OK.

But if you don’t want that to be the case, OK, but then there will be some time at which you will need to make sure that the TextBlock is within the polygon Shape. I can’t help you with that.

Correct, I dont need it unless it is the only way.

No, acutally we create a polygon of any shape using the polyline tool.

OK, just have that TextBlock and that Shape in the Group template start off as not visible. Make the Shape visible only when the Shape in the Group has been drawn, and make the TextBlock visible when you know where to place it within the polygon.

What you mentioned here looks like the best and the most straightforward solution for me, I tried _isNodeLabel: true over my TextBlock, and removed the Panel encasing it as you suggested, and it works correctly and as i needed. Although it could be dragged anywhere even outside the group. How can I restrict this to be dragged only inside the group and how do i position it to be inside the group intially (when the group is created) ?

That’s what I was saying about overriding doMouseMove.

I understand it will be similar to the dragComputation you did like below ,

 dragComputation: function(part, pt, gridpt) {
              var g = part.containingGroup;
              if (g === null) return pt;  // allow top-level Nodes to move freely
              var b = part.actualBounds.copy();
              b.x += (pt.x-part.location.x);
              b.y += (pt.y-part.location.y);
              // OK to move if new location is still within the Group's SHAPE's polygonal Geometry
              if (GeometryHelper.shapeContainsRect(g.findObject("SHAPE"), b)) return pt;
              // otherwise leave the Node where it was
              return part.location;
            }

But the parameters arent same.

That’s right – you’ll need to adapt the code.