Page Index Toggle Pages: 1 Send TopicPrint
Normal Topic Is it possible to control where Links join to Nodes when creating the Links programmatically? (Read 157 times)
AR
Junior Member
**
Offline


I Love MindFusion!

Posts: 77
Joined: Jan 23rd, 2015
Is it possible to control where Links join to Nodes when creating the Links programmatically?
Oct 12th, 2026 at 2:54pm
Print Post  
In our application Links have meaning so it is import the User can see all of them.

I am programmatically generating a Diagram with multiple Nodes and Links.

In one situation there are 3 Links originating from the same position on a Node.

Two of these are 'self loops' which completely overlap so that the appear as a single Link.

The third goes to another Node.

What I would ideally like is for the two 'self loops' to be in different positions on the Node, and for the Link to another node to also start from a different position.

Is this possible? If so, can you point me towards how to achieve this?

[In case it matters, the nodes are custom, with an anchor pattern set with 21 anchor points per side]
  
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3529
Joined: Oct 19th, 2005
Re: Is it possible to control where Links join to Nodes when creating the Links programmatically?
Reply #1 - Oct 12th, 2026 at 5:07pm
Print Post  
The library does not handle repeating self-loops at all. Try calling following adjustAllSelfLoops method after building and arranging your diagram. It considers the anchor points of non-self-loop links as occupied (e.g. as they would be assigned by a layout algorithm applied using 'Anchoring.Reassign' option), and then aligns the self-loop links to non-occupied points.

Code
Select All
public void adjustAllSelfLoops()
{
    for (DiagramNode node : diagram.getNodes())
    {
        adjustSelfLoop(node);
    }
}

public void adjustSelfLoop(DiagramNode node)
{
    AnchorPattern ap = node.getAnchorPattern();
    if (ap == null || ap.getPoints().size() == 0)
        return;

    int numPoints = ap.getPoints().size();
    boolean[] occupied = new boolean[numPoints];

    ArrayList<DiagramLink> selfLoops = new ArrayList<>();
    for (DiagramLink link : node.getOutgoingLinks())
    {
        if (link.getDestination() == node)
        {
            selfLoops.add(link);
        }
        else
        {
            // Outgoing non-self-loop
            int anchor = link.getOriginAnchor();
            if (anchor >= 0 && anchor < numPoints)
            {
                occupied[anchor] = true;
            }
            else
            {
                int closest = getClosestAnchorIndex(node, link.getStartPoint());
                if (closest >= 0 && closest < numPoints)
                    occupied[closest] = true;
            }
        }
    }

    for (DiagramLink link : node.getIncomingLinks())
    {
        if (link.getOrigin() != node)
        {
            // Incoming non-self-loop
            int anchor = link.getDestinationAnchor();
            if (anchor >= 0 && anchor < numPoints)
            {
                occupied[anchor] = true;
            }
            else
            {
                int closest = getClosestAnchorIndex(node, link.getEndPoint());
                if (closest >= 0 && closest < numPoints)
                    occupied[closest] = true;
            }
        }
    }

    if (selfLoops.isEmpty())
        return;

    // Move each self-loop to an unoccupied anchor point
    int searchIdx = 0;
    for (DiagramLink selfLoop : selfLoops)
    {
        int freeIdx = -1;
        for (int i = 0; i < numPoints; i++)
        {
            int candidate = (searchIdx + i) % numPoints;
            if (!occupied[candidate])
            {
                freeIdx = candidate;
                break;
            }
        }

        if (freeIdx == -1)
            break;

        occupied[freeIdx] = true;
        // advance search index so subsequent self-loops pick different points
        searchIdx = (freeIdx + 4) % numPoints;

        selfLoop.setOriginAnchor(freeIdx);
        selfLoop.setDestinationAnchor(freeIdx);

        selfLoop.setShape(LinkShape.Bezier);
        selfLoop.setSegmentCount(1);

        AnchorPoint anchorPt = ap.getPoints().get(freeIdx);
        Point2D p = getAnchorPointPos(node, anchorPt);
        float px = (float)p.getX();
        float py = (float)p.getY();

        float loopDist = 14.0f;
        float spread = 9.0f;

        Point2D.Float cp1, cp2;

        // Determine node side associated with the anchor point
        if (anchorPt.getY() <= 0.001f)
        {
            // Top side: curve upwards, middle control points horizontal (parallel to top side)
            cp1 = new Point2D.Float(px - spread, py - loopDist);
            cp2 = new Point2D.Float(px + spread, py - loopDist);
        }
        else if (anchorPt.getX() >= 99.999f)
        {
            // Right side: curve rightwards, middle control points vertical (parallel to right side)
            cp1 = new Point2D.Float(px + loopDist, py - spread);
            cp2 = new Point2D.Float(px + loopDist, py + spread);
        }
        else if (anchorPt.getY() >= 99.999f)
        {
            // Bottom side: curve downwards, middle control points horizontal (parallel to bottom side)
            cp1 = new Point2D.Float(px - spread, py + loopDist);
            cp2 = new Point2D.Float(px + spread, py + loopDist);
        }
        else
        {
            // Left side: curve leftwards, middle control points vertical (parallel to left side)
            cp1 = new Point2D.Float(px - loopDist, py - spread);
            cp2 = new Point2D.Float(px - loopDist, py + spread);
        }

        PointList points = selfLoop.getControlPoints();
        if (points.size() >= 4)
        {
            points.set(0, new Point2D.Float(px, py));
            points.set(1, cp1);
            points.set(2, cp2);
            points.set(3, new Point2D.Float(px, py));
            selfLoop.updateFromPoints();
        }
    }
} 



The searchIdx = (freeIdx + 4) % numPoints is to advance to a further point so that self-loops aren't that close to each other with 21 points per side. You might want to adjust that if you have AnchorPatterns with less points.

Result looks as in attached image in my test.

Regards,
Slavcho
Mindfusion
  

Untitled_014.png ( 6 KB | 9 Downloads )
Untitled_014.png
Back to top
 
IP Logged
 
AR
Junior Member
**
Offline


I Love MindFusion!

Posts: 77
Joined: Jan 23rd, 2015
Re: Is it possible to control where Links join to Nodes when creating the Links programmatically?
Reply #2 - Oct 13th, 2026 at 10:43am
Print Post  
Your example image looks great.

However the posted code references two methods which it does not include - where can I find them?

The methods are:

getClosestAnchorIndex(node, link.getEndPoint())
getAnchorPointPos( node, anchorPt )
 
 

  
Back to top
 
IP Logged
 
Slavcho
YaBB Moderator
*****
Offline


tech.support

Posts: 3529
Joined: Oct 19th, 2005
Re: Is it possible to control where Links join to Nodes when creating the Links programmatically?
Reply #3 - Oct 13th, 2026 at 10:48am
Print Post  
sorry, I missed these ones:

Code
Select All
Point2D getAnchorPointPos(DiagramNode node, AnchorPoint ap)
{
	Rectangle2D r = node.getBounds();
	double x = r.getX() + r.getWidth() * (ap.getX() / 100.0);
	double y = r.getY() + r.getHeight() * (ap.getY() / 100.0);
	return new Point2D.Double(x, y);
}

int getClosestAnchorIndex(DiagramNode node, Point2D pt)
{
	AnchorPattern ap = node.getAnchorPattern();
	if (ap == null || ap.getPoints().size() == 0 || pt == null)
		return -1;

	int bestIdx = -1;
	double bestDist = Double.MAX_VALUE;

	for (int i = 0; i < ap.getPoints().size(); i++)
	{
		AnchorPoint p = ap.getPoints().get(i);
		Point2D pos = getAnchorPointPos(node, p);
		double dist = pos.distance(pt);
		if (dist < bestDist)
		{
			bestDist = dist;
			bestIdx = i;
		}
	}
	return bestIdx;
}

 

  
Back to top
 
IP Logged
 
Page Index Toggle Pages: 1
Send TopicPrint