Layout overlap of link text boxes

Hi,

When we recently added more text to the link's midlabel gotext,
the layout (GoLayoutLayeredDigraph) would allow overlapping of the text.
With NO text on the links, the layout works very nicely.
I realise from the forum articles, that this is a tricky (ie. complex) issue.
I think I have 2 options viz:
1] Try to fix the layout so that overlapping does not occur (and risk spending too much time...)
2] Add a click event handler to open a small window with the text for each link.
Please help me to decide, which of the above solutions would be optimal.
Thanks

It’s probably easiest to just treat each link label as if they were “nodes” in their own right. So, something like:

[code] GoLayoutLayeredDigraph layout = new GoLayoutLayeredDigraph();
layout.Document = myView.Document;

// create a normal network …
GoLayoutLayeredDigraphNetwork net = layout.CreateNetwork();
net.AddNodesAndLinksFromCollection(myView.Document, true);

// but then add fake nodes for the link labels
foreach (GoObject obj in myView.Document) {
GoLabeledLink ll = obj as GoLabeledLink;
if (ll != null && ll.MidLabel != null && !ll.RealLink.Orthogonal) {
GoLayoutLayeredDigraphLink nl = net.FindLink(ll);
if (nl == null) continue;
ll.Initializing = true;
GoLayoutLayeredDigraphNode from = nl.FromNode;
GoLayoutLayeredDigraphNode to = nl.ToNode;
GoLayoutLayeredDigraphNode nn = net.AddNode(ll.MidLabel);
net.LinkNodes(from, nn, null);
net.LinkNodes(nn, to, null);
net.DeleteLink(nl);
}
}
layout.Network = net;

layout.PerformLayout();

// now update the stroke path for the links, to position the labels
foreach (GoObject obj in myView.Document) {
GoLabeledLink ll = obj as GoLabeledLink;
if (ll != null && ll.MidLabel != null && !ll.RealLink.Orthogonal) {
int numpts = ll.RealLink.PointsCount;
ll.RealLink.InsertPoint(numpts/2, ll.MidLabel.Center);
GoPort p = ll.FromPort as GoPort;
if (p != null)
ll.RealLink.SetPoint(0, p.GetFromLinkPoint(ll));
p = ll.ToPort as GoPort;
if (p != null)
ll.RealLink.SetPoint(ll.RealLink.PointsCount-1, p.GetToLinkPoint(ll));
ll.Initializing = false;
}
}[/code]

Note: this code just works for “direct” links, not for Bezier style nor for Orthogonal links.

Cool.
Thanks for this code.
I will try it!

This works well. Thank you.

Now when the user, moves a component the new node-label location (on the link line) must be recalculated, to center it.
(as in the stroke path update above...)
How do I implement this? Or rather where?

The label automatically gets repositioned as either node is moved. This is because GoLabeledLink.LayoutChildren will call GoLabeledLink.LayoutMidLabel which will call GoLabeledLink.PositionMidLabel.

Or are you saying this is not what is happening?

No, it is not happening correctly. (I will send details and screen shots later…)

However, I have the following issue that is a show stopper for me currently, and I would appreciate any example code very much...
This is how my screen looks with left to right layout (current.jpg) and how I would like it to look (nice.jpg)
I need to get 'fg1' (process) node under the corresponding 'loc' node, if at all possible.
Here is my c# layout code that we are using:
#region Layout Diagram public void LayoutDiagramSync() { GoLayout Layout = null; if (LayoutType == Resources.LeftToRight) { Layout = new GoLayoutLayeredDigraph(); (Layout as GoLayoutLayeredDigraph).DirectionOption = GoLayoutDirection.Right; } else if (LayoutType == Resources.RightToLeft) { Layout = new GoLayoutLayeredDigraph(); (Layout as GoLayoutLayeredDigraph).DirectionOption = GoLayoutDirection.Left; } else if (LayoutType == Resources.TopToBottom) { Layout = new GoLayoutLayeredDigraph(); (Layout as GoLayoutLayeredDigraph).DirectionOption = GoLayoutDirection.Down; } else if (LayoutType == Resources.BottomToTop) { Layout = new GoLayoutLayeredDigraph(); (Layout as GoLayoutLayeredDigraph).DirectionOption = GoLayoutDirection.Up; } else if (LayoutType == Resources.Magneticforce) { Layout = new GoLayoutForceDirected(); } //foreach (IDetailedGoNode gn in Document) //{ // if (gn is LocationView) // { // (gn as DiagramBase).MainPort.FromSpot = GoObject.NoSpot; // (gn as DiagramBase).MainPort.ToSpot = GoObject.NoSpot; // } //} //Layout.Progress += new GoLayoutProgressEventHandler(Layout_Progress); Layout.Document = Document; Layout.View = this; //Update the Link Midlabel GoText objects to nodes for better layout if (Layout.GetType() == typeof(GoLayoutLayeredDigraph)) { // create a normal network ... GoLayoutLayeredDigraphNetwork net = (Layout as GoLayoutLayeredDigraph).CreateNetwork(); net.AddNodesAndLinksFromCollection(Document, true); // but then add fake nodes for the link labels foreach (GoObject obj in Document) { GoLabeledLink ll = obj as GoLabeledLink; if (ll != null && ll.MidLabel != null && !ll.RealLink.Orthogonal && (ll.MidLabel as GoText).Visible) { GoLayoutLayeredDigraphLink nl = net.FindLink(ll); if (nl == null) continue; ll.Initializing = true; GoLayoutLayeredDigraphNode from = nl.FromNode; GoLayoutLayeredDigraphNode to = nl.ToNode; GoLayoutLayeredDigraphNode nn = net.AddNode(ll.MidLabel); net.LinkNodes(from, nn, null); net.LinkNodes(nn, to, null); net.DeleteLink(nl); } } (Layout as GoLayoutLayeredDigraph).Network = net; (Layout as GoLayoutLayeredDigraph).Iterations = 100; Layout.PerformLayout(); // now update the stroke path for the links, to position the labels foreach (GoObject obj in Document) { GoLabeledLink ll = obj as GoLabeledLink; if (ll != null && (ll.FromNode is ProcessView || ll.ToNode is ProcessView)) { if (ll.ToNode is ProcessView) { //foreach (GoPort GP in (ll.ToNode as ProcessView).Ports) //{ // GP.FromSpot = GoPort.TopCenter; // GP.ToSpot = GoPort.TopCenter; //} GoPort gp = new GoPort(); gp.Height = (ll.FromNode as LocationView).Height; gp.Width = (ll.FromNode as LocationView).Width; gp.Position = (ll.FromNode as LocationView).Position; (ll.FromNode as LocationView).Add(gp); gp.FromSpot = GoPort.BottomCenter; gp.ToSpot = GoPort.BottomCenter; gp.Visible = false; ll.FromPort = gp; } if (ll.FromNode is ProcessView) { //(ll.FromNode as ProcessView).Left = (ll.ToNode as LocationView).Left; //(ll.FromNode as ProcessView).Top = (ll.ToNode as LocationView).Bottom + 100; //foreach (GoPort GP in (ll.FromNode as ProcessView).Ports) //{ // GP.FromSpot = GoPort.TopCenter; // GP.ToSpot = GoPort.TopCenter; //} GoPort gp = new GoPort(); gp.Height = (ll.ToNode as LocationView).Height; gp.Width = (ll.ToNode as LocationView).Width; gp.Position = (ll.ToNode as LocationView).Position; (ll.ToNode as LocationView).Add(gp); gp.FromSpot = GoPort.BottomCenter; gp.ToSpot = GoPort.BottomCenter; gp.Visible = false; ll.ToPort = gp; } ll.AdjustingStyle = GoLinkAdjustingStyle.Calculate; continue; } if (ll != null && ll.MidLabel != null && !ll.RealLink.Orthogonal) { int numpts = ll.RealLink.PointsCount; //int MidPointIndex = numpts / 2; //float StartX = ll.RealLink.GetPoint(0).X; //float StartY = ll.RealLink.GetPoint(0).Y; //float XOffSet = (ll.MidLabel.Center.X - StartX) / MidPointIndex; //float YOffSet = (ll.MidLabel.Center.Y - StartY) / MidPointIndex; //ll.RealLink.SetPoint(MidPointIndex, ll.MidLabel.Center); //for (int I = 1; I < MidPointIndex; I++) //{ // ll.RealLink.SetPoint(I, new PointF(StartX + (XOffSet * (I + 1)), StartY + (YOffSet * (I + 1)))); //} //StartX = ll.MidLabel.Center.X; //StartY = ll.MidLabel.Center.Y; //XOffSet = (ll.RealLink.GetPoint(ll.RealLink.PointsCount - 1).X - StartX) / MidPointIndex; //YOffSet = (ll.RealLink.GetPoint(ll.RealLink.PointsCount - 1).Y - StartY) / MidPointIndex; //for (int I = MidPointIndex + 1; I < ll.RealLink.PointsCount - 1; I++) //{ // ll.RealLink.SetPoint(I, new PointF(StartX + (XOffSet * (I + 1)), StartY + (YOffSet * (I + 1)))); //} ll.RealLink.SetPoint(numpts / 2, ll.MidLabel.Center); GoPort p = ll.FromPort as GoPort; if (p != null) ll.RealLink.SetPoint(0, p.GetFromLinkPoint(ll)); p = ll.ToPort as GoPort; if (p != null) ll.RealLink.SetPoint(ll.RealLink.PointsCount - 1, p.GetToLinkPoint(ll)); ll.LayoutChildren(ll); ll.Initializing = false; } } } else { Layout.PerformLayout(); } //goViewMain.RescaleToFit(); //if (goViewMain.DocScale < 0.5) //{ // goViewMain.DocScale = 0.5F; //} EndUpdate(); Invalidate(true); Visible = true; //if (GoOverviewForm.Instance.Observed == goViewMain) //{ // GoOverviewForm.Instance.EndUpdate(); //} //SessionSupport.Instance.EndProgress(); } #endregion

images

You understand why it’s doing what it’s doing, right? There’s a link from “Loc1” to “fg1”, so naturally it assumed that “fg1” should be in a layer after the “Loc1” layer. The same is true for “Loc2” and its “fg1”.

And if you just removed the link from “Loc1” to “fg1”, but left the link from “fg1” to “Loc1” in the network, then it would naturally think that “fg1” ought to be in a layer before the “Loc1” layer.

So you need to replace both of the GoLayoutLayeredDigraphLinks connecting the GoLayoutLayeredDigraphNodes corresponding to “Loc1” and “fg1”, with new GoLayoutLayeredDigraphLinks that connect “Sup1” to “fg1” and “fg1” to “Loc2”. (And of course the same goes for the two links connecting “Loc2” with its “fg1”.) You can use GoLayoutLayeredDigraphNetwork methods to accomplish this. But there won’t be any guarantee that the “Loc1” and its “fg1” nodes will be next to each other in the layer. (Of course if there were more than two such nodes, that would be impossible anyway.)

Thanks for the explanation. I now have a pretty good idea how the node layout positions are calculated. (and no; I did not understand this properly before - thanks again.)

The problem that I am groping with is still the same as this thread basically. If I put text onto the midlabel of the 'fg1' links, the diagram becomes messy - and linking the links to the midlabel (nodes - as defined by the network) becomes a nightmare! (Ugly points appear in the links and the bezier curves go all over the show.) This seems to happen only on these Nodes that have a 1 to 1 from - to relationship between each other.
What I am thinking - hopeing is that if I could switch the layout style to top to bottom for these 2 nodes only, while keeping the left to right layout for the rest of the diagram, that should make it very neat.
Please tell me if it is possible to have the layout set to left to right for the document WHILE also having a sub-'layer/group' that does top to bottom layout overlapping with one specific node? If so where do I start?
Thanks - I do appreciate your effort

OK, here’s one way to associate one node with another node on the same layer. Just ignore that extra “fg” node, and pretend the “Loc” node is taller than it really is, to make room for positioning the “fg” node afterwards.

[code] public class TDKLayout : GoLayoutLayeredDigraph {
public override void PerformLayout() {
GoLayoutLayeredDigraphNetwork net = this.Network;
if (net == null) net = new GoLayoutLayeredDigraphNetwork(this.Document);
foreach (GoObject obj in this.Document) {
GoTextNode tn = obj as GoTextNode;
if (tn == null) continue;
if (tn.UserObject == null) continue;
GoTextNode near = (GoTextNode)tn.UserObject;
// delete all of those “fg” nodes from the network
net.DeleteNode(tn);
// increase the height of the “Loc” nodes to include the “fg” node
GoLayoutLayeredDigraphNode nearnode = net.FindNode(near);
if (nearnode != null) {
RectangleF bounds = near.Bounds;
bounds.Height += 20 + tn.Height;
nearnode.Bounds = bounds;
}
}
this.Network = net;
base.PerformLayout();
// now position all of those “fg” nodes
foreach (GoObject obj in this.Document) {
GoTextNode tn = obj as GoTextNode;
if (tn == null) continue;
if (tn.UserObject == null) continue;
GoTextNode near = (GoTextNode)tn.UserObject;
tn.SetSpotLocation(GoObject.MiddleTop, near, GoObject.MiddleBottom, 0, 20);
}
}

// to keep your code better organized, you'll probably want to put
// that other code for adding fake nodes for MidLabels, and for
// positioning those MidLabels afterwards, into the method above.

// just some test case initialization code...
public static void InitDoc(GoDocument doc) {
  GoTextNode sup1 = new GoTextNode();
  sup1.Text = "Sup1";
  doc.Add(sup1);
  GoTextNode loc1 = new GoTextNode();
  loc1.Text = "Loc1";
  doc.Add(loc1);
  GoTextNode loc2 = new GoTextNode();
  loc2.Text = "Loc2";
  doc.Add(loc2);
  GoTextNode dploc2 = new GoTextNode();
  dploc2.Text = "DPLoc2";
  doc.Add(dploc2);

  GoTextNode fg1 = new GoTextNode();
  fg1.Text = "fg1";
  fg1.UserObject = loc1;
  doc.Add(fg1);

  GoTextNode fg2 = new GoTextNode();
  fg2.Text = "fg2";
  fg2.UserObject = loc2;
  doc.Add(fg2);

  doc.Add(MakeLink(sup1.RightPort, loc1.LeftPort));
  doc.Add(MakeLink(loc1.RightPort, loc2.LeftPort));
  doc.Add(MakeLink(loc2.RightPort, dploc2.LeftPort));
  doc.Add(MakeLink(loc1.BottomPort, fg1.LeftPort));
  doc.Add(MakeLink(fg1.RightPort, loc1.BottomPort));
  doc.Add(MakeLink(loc2.BottomPort, fg2.LeftPort));
  doc.Add(MakeLink(fg2.RightPort, loc2.BottomPort));
}

public static GoLink MakeLink(GoPort a, GoPort b) {
  GoLink l = new GoLink();
  l.ToArrow = true;
  l.Style = GoStrokeStyle.Bezier;
  l.FromPort = a;
  l.ToPort = b;
  return l;
}

}[/code]

Usage:

  TDKLayout.InitDoc(doc);  // create test case
  TDKLayout layout = new TDKLayout();
  layout.Document = doc;
  layout.PerformLayout();

Result in version 3.0:

You might get different (vertical) alignment in version 2.x in more complicated cases than this simple one. (Sorry, I haven’t tried it in v2.6.)

Of course you can extend this to support additional associated nodes like the “fg” ones for each regular node like “Loc”. You’ll need to decide where to position those nodes relative to the main node.

Also, if you are using GoTextNode, remember that if you position a “fg” node above its “Loc” node, to avoid having the two connecting links cross over the “Loc” node, you’ll probably want to reconnect the two links so that they connect to the “Loc” node’s TopPort instead of its BottomPort as shown in the above example.

You have clearly spent some time on this! Thank you!

I will try this, it sounds like a perfect sollution.

Extending the bounds of a node on the network (to fit another node below the original) sounds asif the layout would use the extended bounds and draw the next line beyond those bounds. However …

The network bounds for l1 encompass the 'l1 Production' node as well (in height at least). [I have double checked my code it is correct as suggested. The l1 node and 'l1 Production' node share the same center bottom (as related to the network).]
But on layout it appears asif the network is being ignored.
I have tried to use column spaceing to force a boundary, but this does not work in all cases (e.g. where Production nodes are not used).
Damn! That seemed like a supper idea!
Any ideas, on how I could force the next line to be lower than the 'l1 Production' node?

Does the code I posted work for you? I just tried several variations of the graph, in version 2.6.2, and it produced good results for all cases.

It works.

I have to use culumn spaceing to space the rows properly though.
I am probably doing something wrong...