How to draw arc with radius and start and stop angle

Solution 1:

Providing a custom component turned out to be the best solution. I use it like this in my code

<Controls:Arc Center="{Binding Path=PreviousMousePositionPixels}" 
         Stroke="White" 
         StrokeDashArray="4 4"
         SnapsToDevicePixels="True"
         StartAngle="0" 
         EndAngle="{Binding Path=DeltaAngle}" 
         SmallAngle="True"
         Radius="40" />

SmallAngle when true will render the small angle between the points irrespective of order of StartAngle and EndAngle. When SmallAngle is false the arc is rendered counter clockwise.

The implementation is

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

public sealed class Arc : Shape
{
    public Point Center
    {
        get => (Point)GetValue(CenterProperty);
        set => SetValue(CenterProperty, value);
    }

    // Using a DependencyProperty as the backing store for Center.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CenterProperty = 
        DependencyProperty.Register(nameof(Center), typeof(Point), typeof(Arc), 
            new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.AffectsRender));

    public double StartAngle
    {
        get => (double)GetValue(StartAngleProperty);
        set => SetValue(StartAngleProperty, value);
    }

    // Using a DependencyProperty as the backing store for StartAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StartAngleProperty =
        DependencyProperty.Register(nameof(StartAngle), typeof(double), typeof(Arc),
            new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));

    public double EndAngle
    {
        get => (double)GetValue(EndAngleProperty);
        set => SetValue(EndAngleProperty, value);
    }

    // Using a DependencyProperty as the backing store for EndAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty EndAngleProperty =
        DependencyProperty.Register(nameof(EndAngle), typeof(double), typeof(Arc),
            new FrameworkPropertyMetadata(Math.PI / 2.0, FrameworkPropertyMetadataOptions.AffectsRender));

    public double Radius
    {
        get => (double)GetValue(RadiusProperty);
        set => SetValue(RadiusProperty, value);
    }

    // Using a DependencyProperty as the backing store for Radius.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RadiusProperty =
        DependencyProperty.Register(nameof(Radius), typeof(double), typeof(Arc),
            new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));

    public bool SmallAngle
    {
        get => (bool)GetValue(SmallAngleProperty);
        set => SetValue(SmallAngleProperty, value);
    }

    // Using a DependencyProperty as the backing store for SmallAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SmallAngleProperty =
        DependencyProperty.Register(nameof(SmallAngle), typeof(bool), typeof(Arc),
            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));

    static Arc() => DefaultStyleKeyProperty.OverrideMetadata(typeof(Arc), new FrameworkPropertyMetadata(typeof(Arc)));

    protected override Geometry DefiningGeometry
    {
        get
        {
            double a0 = StartAngle < 0 ? StartAngle + 2 * Math.PI : StartAngle;
            double a1 = EndAngle < 0 ? EndAngle + 2 * Math.PI : EndAngle;

            if (a1 < a0)
                a1 += Math.PI * 2;

            SweepDirection d = SweepDirection.Counterclockwise;
            bool large;

            if (SmallAngle)
            {
                large = false;
                d = (a1 - a0) > Math.PI ? SweepDirection.Counterclockwise : SweepDirection.Clockwise;
            }
            else
                large = (Math.Abs(a1 - a0) < Math.PI);

            Point p0 = Center + new Vector(Math.Cos(a0), Math.Sin(a0)) * Radius;
            Point p1 = Center + new Vector(Math.Cos(a1), Math.Sin(a1)) * Radius;

            List<PathSegment> segments = new List<PathSegment>
            {
                new ArcSegment(p1, new Size(Radius, Radius), 0.0, large, d, true)
            };

            List<PathFigure> figures = new List<PathFigure>
            {
                new PathFigure(p0, segments, true)
                {
                    IsClosed = false
                }
            };

            return new PathGeometry(figures, FillRule.EvenOdd, null);
        }
    }
}

Solution 2:

May I offer a slightly different solution?

class ArcII:FrameworkElement
{
    /// <summary>
    /// Center point of Arc.
    /// </summary>
    [Category("Arc")]
    public Point Center
    {
        get { return (Point)GetValue(CenterProperty); }
        set { SetValue(CenterProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Center.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty CenterProperty =
        DependencyProperty.Register("Center", typeof(Point), typeof(ArcII), new FrameworkPropertyMetadata(new Point(0, 0), FrameworkPropertyMetadataOptions.AffectsRender));
    
    /// <summary>
    /// Forces the Arc to the center of the Parent container.
    /// </summary>
    [Category("Arc")]
    public bool OverrideCenter
    {
        get { return (bool)GetValue(OverrideCenterProperty); }
        set { SetValue(OverrideCenterProperty, value); }
    }

    // Using a DependencyProperty as the backing store for OverrideCenter.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty OverrideCenterProperty =
        DependencyProperty.Register("OverrideCenter", typeof(bool), typeof(ArcII), new FrameworkPropertyMetadata((bool)false, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// Start angle of arc, using standard coordinates. (Zero is right, CCW positive direction)
    /// </summary>
    [Category("Arc")]
    public double StartAngle
    {
        get { return (double)GetValue(StartAngleProperty); }
        set { SetValue(StartAngleProperty, value); }
    }

    // Using a DependencyProperty as the backing store for StartAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StartAngleProperty =
        DependencyProperty.Register("StartAngle", typeof(double), typeof(ArcII), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// Length of Arc in degrees.
    /// </summary>
    [Category("Arc")]
    public double SweepAngle
    {
        get { return (double)GetValue(SweepAngleProperty); }
        set { SetValue(SweepAngleProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SweepAngle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SweepAngleProperty =
        DependencyProperty.Register("SweepAngle", typeof(double), typeof(ArcII), new FrameworkPropertyMetadata((double)180, FrameworkPropertyMetadataOptions.AffectsRender));

    /// <summary>
    /// Size of Arc.
    /// </summary>
    [Category("Arc")]
    public double Radius
    {
        get { return (double)GetValue(RadiusProperty); }
        set { SetValue(RadiusProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Radius.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty RadiusProperty =
        DependencyProperty.Register("Radius", typeof(double), typeof(ArcII), new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));

    [Category("Arc")]
    public Brush Stroke
    {
        get { return (Brush)GetValue(StrokeProperty); }
        set { SetValue(StrokeProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Stroke.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StrokeProperty =
        DependencyProperty.Register("Stroke", typeof(Brush), typeof(ArcII), new FrameworkPropertyMetadata((Brush)Brushes.Black,FrameworkPropertyMetadataOptions.AffectsRender));

    [Category("Arc")]
    public double StrokeThickness
    {
        get { return (double)GetValue(StrokeThicknessProperty); }
        set { SetValue(StrokeThicknessProperty, value); }
    }

    // Using a DependencyProperty as the backing store for StrokeThickness.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StrokeThicknessProperty =
        DependencyProperty.Register("StrokeThickness", typeof(double), typeof(ArcII), new FrameworkPropertyMetadata((double)1,FrameworkPropertyMetadataOptions.AffectsRender));

    protected override void OnRender(DrawingContext dc)
    {
        base.OnRender(dc);
        Draw(dc);
    }

    private void Draw(DrawingContext dc)
    {
        Point center = new Point();
        if (OverrideCenter)
        {
            Rect rect = new Rect(RenderSize);
            center = Polar.CenterPointFromRect(rect);
        }
        else
        {
            center = Center;
        }

        Point startPoint = Polar.PolarToCartesian(StartAngle, Radius, center);
        Point endPoint = Polar.PolarToCartesian(StartAngle + SweepAngle, Radius, center);
        Size size = new Size(Radius, Radius);

        bool isLarge = (StartAngle + SweepAngle) - StartAngle > 180;

        List<PathSegment> segments = new List<PathSegment>(1);
        segments.Add(new ArcSegment(endPoint, new Size(Radius, Radius), 0.0, isLarge, SweepDirection.Clockwise, true));

        List<PathFigure> figures = new List<PathFigure>(1);
        PathFigure pf = new PathFigure(startPoint, segments, true);
        pf.IsClosed = false;
        figures.Add(pf);
        Geometry g = new PathGeometry(figures, FillRule.EvenOdd, null);

        dc.DrawGeometry(null, new Pen(Stroke,StrokeThickness), g);
    }
}

Usage:

    <!--Centerd on Parent-->
    <local:ArcII Center="0,0"
                 OverrideCenter="True"
                 StartAngle="150"
                 SweepAngle="240"
                 Radius="100"
                 Stroke="Red"
                 StrokeThickness="3"
                 />

    <!--Centerd on Parent-->
    <local:ArcII Center="0,0"
                 OverrideCenter="True"
                 StartAngle="150"
                 SweepAngle="240"
                 Radius="95"
                 Stroke="Red"
                 StrokeThickness="3"
                 />

    <!--Centerd on Parent-->
    <local:ArcII Center="0,0"
                 OverrideCenter="True"
                 StartAngle="150"
                 SweepAngle="240"
                 Radius="90"
                 Stroke="Red"
                 StrokeThickness="3"
                 />

    <!--Centerd on Point-->
    <local:ArcII Center="0,150"
                 OverrideCenter="False"
                 StartAngle="270"
                 SweepAngle="180"
                 Radius="100"
                 />

    <!--Centerd on Point-->
    <local:ArcII Center="525,150"
                 OverrideCenter="False"
                 StartAngle="90"
                 SweepAngle="180"
                 Radius="100"
                 />

Notes: A) This will not do a 360 SweepAngle, for that use an ellipse. B) OverrideCenter: This puts the center of the Arc in the center of its parent. Note that elements like a Grid that can be partitioned, still have a center, which may not be the column or row the Arc is in.

Sorry, been awhile since I've been here. Updated to provide the polar coordinates class...

public static class Polar
{
    /// <summary>
    /// Given the center of a circle and its radius, along with the angle 
    /// corresponding to the point, find the coordinates.  In other words, 
    /// convert from polar to rectangular coordinates.
    /// </summary>
    /// <param name="angle"></param>
    /// <param name="radius"></param>
    /// <param name="center"></param>
    /// <returns></returns>
    public static Point PolarToCartesian(double angle, double radius, Point center)
    {
        return new Point((center.X + (radius * Math.Cos(DegreesToRadian(angle)))), (center.Y + (radius * Math.Sin(DegreesToRadian(angle)))));
    }


    /// <summary>
    /// Given a center point and radius, find the top left point for a rectangle and its size.
    /// </summary>
    /// <param name="centerPoint"></param>
    /// <param name="radius"></param>
    /// <returns></returns>
    public static Rect RectFromCenterPoint(Point centerPoint, int radius)
    {
        Point p = new Point(centerPoint.X - radius, centerPoint.Y - radius);
        return new Rect(p, new Size(radius * 2, radius * 2));
    }

    /// <summary>
    /// Finds the center point of a Rect
    /// </summary>
    /// <param name="rect"></param>
    /// <returns></returns>
    public static Point CenterPoint(Rect rect)
    {
        return new Point(rect.Width / 2, rect.Height / 2);
    }

    /// <summary>
    /// Returns a radius value equal to the smallest side.
    /// </summary>
    /// <param name="rect"></param>
    /// <returns></returns>
    public static double Radius(Rect rect)
    {
        double dbl = Math.Min(rect.Width, rect.Height);
        return dbl / 2;
    }


    /// <summary>
    /// Since Windows Forms consider an Angle of Zero to be at the 3:00 position and an Angle of 90
    /// to be at the 12:00 position, it is sometimes difficult to visualize where 
    /// 
    /// </summary>
    /// <param name="Angle"></param>
    /// <param name="Offset"></param>
    /// <returns></returns>
    /// <remarks></remarks>
    public static float ReversePolarDirection(float Angle, int Offset)
    {
        return ((360 - Angle) + Offset) % 360;
    }

    /// <summary>
    /// Circumference: C = 2*Pi*r = Pi*d; r=Radius, d=Diameter
    /// </summary>
    /// <param name="Diameter"></param>
    /// <returns></returns>
    /// <remarks></remarks>
    public static double CircumferenceD(double Diameter)
    {
        return Diameter * Math.PI;
    }
    /// <summary>
    /// Circumference: C = 2*Pi*r = Pi*d; r=Radius, d=Diameter
    /// </summary>
    /// <param name="Radius"></param>
    /// <returns></returns>
    /// <remarks></remarks>
    public static double CircumferenceR(double Radius)
    {
        return Radius * Math.PI;
    }
    public static double ScaleWithParam(double Input, double InputMin, double InputMax, double ScaledMin, double ScaledMax)
    {
        //Out = (((ScMax-ScMin)/(InMax-InMin))*Input)+(ScMin-(InMin*((ScMax-ScMin)/(InMax-InMin))
        return (((ScaledMax - ScaledMin) / (InputMax - InputMin)) * Input) + (ScaledMin - (InputMin * ((ScaledMax - ScaledMin) / (InputMax - InputMin))));

    }
    public static double DegreesToRadian(double degrees)
    {
        //Return 2 * Math.PI * degrees / 360.0
        return degrees * (Math.PI / 180);
    }
    private static double RadianToDegrees(double radian)
    {
        return radian * 180 / Math.PI;
    }

    public static double ArcLength(double radius, double radian)
    {
        return radius * radian;
    }
}