Monday, 10 May 2010

Drawing WPF Curves with Arrow heads

Having drawn the shapes, the curves and found their intersection point, I finally needed to draw an arrow head. In order to draw the arrow at the right angle we need to know the tangent of the Bezier curve at the intersection point. Since that is beyond my maths capability I chose to find the intersection point between the line and an imaginary, slightly larger, rectangle that surrounds the target shape. The difference between the two intersection points provides the required angle for the arrow head.
private static void DrawArrowHead(Canvas canvas, PathGeometry linePath, Rect shapeRect, Color color)
// Get the intersection point of the imaginary, slightly
// larger rectangle that surrounds the targer shape.
Rect outerRect = new Rect(shapeRect.Left - 10, shapeRect.Top - 10, shapeRect.Width + 20, shapeRect.Height + 20);

RectangleGeometry shapeGeometry = new RectangleGeometry(shapeRect);
Point[] intersectPoints = GetIntersectionPoints(linePath, shapeGeometry);

double innerLeft = intersectPoints[0].X;
double innerTop = intersectPoints[0].Y;

shapeGeometry = new RectangleGeometry(outerRect);
intersectPoints = GetIntersectionPoints(linePath, shapeGeometry);

double outerLeft = intersectPoints[0].X;
double outerTop = intersectPoints[0].Y;

Polygon arrowHead = new Polygon();
arrowHead.Points = new PointCollection();
arrowHead.Points.Add(new Point(innerLeft, innerTop));
arrowHead.Points.Add(new Point(innerLeft + 10, innerTop + 5));
arrowHead.Points.Add(new Point(innerLeft + 10, innerTop - 5));
arrowHead.Points.Add(new Point(innerLeft, innerTop));
arrowHead.Stroke = new SolidColorBrush(color);
arrowHead.Fill = new SolidColorBrush(color);

// The differences between the intersection points on
// the inner and outer shapes gives us the base and
// perpendicular of the right-angled triangle
double baseSize = innerLeft - outerLeft;
double perpSize = innerTop - outerTop;
// Calculate the angle in degrees using ATan
double angle = Math.Atan(perpSize / baseSize) * 180 / Math.PI;

// Rotate another 180 degrees for lines in the 3rd & 4th quadrants
if (baseSize >= 0) angle += 180;

// Apply the rotation to the arrow head
RotateTransform rt = new RotateTransform(angle, innerLeft, innerTop);
arrowHead.RenderTransform = rt;

// Arrow heads are drawn over the lines but
// under the shapes
Canvas.SetZIndex(arrowHead, (int)Layer.Arrow);


No comments: