views:

796

answers:

1

I have a canvas for diagramming, and want to join nodes in the diagram by directed lines (arrow ends). I tried the anchor approach, where lines only attach at specific points on the nodes but that did not work for me, it looked like crap.

I simply want a line from the centre of each object to the other, and stop the line at the nodes' edge in order for the arrow end to show properly. But finding the edge of a canvas element to test intersections against has proven difficult.

Any ideas?

+1  A: 

I got a method working using the bounding box of the element. It is not perfect, since my elements are not perfectly rectangular, but it looks OK.

Basically I find the bounding box of the element in Canvas coordinates by:

    private static Rect GetBounds(FrameworkElement element, UIElement visual)
    {
        return new Rect(
            element.TranslatePoint(new Point(0, 0), visual),
            element.TranslatePoint(new Point(element.ActualWidth, element.ActualHeight), visual));
    }

Then I find the intersection of the centre-to-centre line against each of the four sides of the bounding box, and use that intersection point to connect the two elements by a Line shape.

I found the intersection code at Third Party Ninjas: http://thirdpartyninjas.com/blog/2008/10/07/line-segment-intersection/

private void ProcessIntersection()
    {
        float ua = (point4.X - point3.X) * (point1.Y - point3.Y) - (point4.Y - point3.Y) * (point1.X - point3.X);
        float ub = (point2.X - point1.X) * (point1.Y - point3.Y) - (point2.Y - point1.Y) * (point1.X - point3.X);
        float denominator = (point4.Y - point3.Y) * (point2.X - point1.X) - (point4.X - point3.X) * (point2.Y - point1.Y);

        intersection = coincident = false;

        if (Math.Abs(denominator) <= 0.00001f)
        {
            if (Math.Abs(ua) <= 0.00001f && Math.Abs(ub) <= 0.00001f)
            {
                intersection = coincident = true;
                intersectionPoint = (point1 + point2) / 2;
            }
        }
        else
        {
            ua /= denominator;
            ub /= denominator;

            if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
            {
                intersection = true;
                intersectionPoint.X = point1.X + ua * (point2.X - point1.X);
                intersectionPoint.Y = point1.Y + ua * (point2.Y - point1.Y);
            }
        }
    }

And voilá! The lines are now drawn as if they go from the centre of each node to the other, but stops approximately at the node's edge so the arrow end is visible.

An improvement of this method would be to test against the actual edge of the node itself, such as for elliptical nodes, but I have yet to find a WPF method that provides me with a Geometry or Path that I can test against.

Andreas Larsen