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;
}
}