I'm trying to create a doughnut chart in WPF and using this helpful previous answer.
However, currently the StartAngle starts from the 3 o'clock position e.g.
How can I change this code so that the StartAngle starts from the 12 o'clock position and the angle measures clockwise e.g.
1] StartAngle = 0, EndAngle = 90 => covers top right corner.
2] StartAngle = 0, EndAngle = 180 => covers right half.
3] StartAngle = 0, EndAngle = 270 => covers three quarters of the chart.
I've tried changing the SweepDirection.Counterclockwise
to SweepDirection.Clockwise
but that doesn't work. I've also tried adding a ScaleTransform
and RotateTransform
in my Arc class as shown below.
WPF code:
<Viewbox Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid Height="100" Width="200">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Rectangle x:Name="divider" Fill="DimGray" Width="2" VerticalAlignment="Stretch" Height="70" Grid.Column="1" Grid.RowSpan="2"/>
<notifications1:Arc
Grid.Column="0"
Stroke="DimGray"
StrokeThickness="5"
StartAngle="0" EndAngle="359.999"
Margin="20"/>
<notifications1:Arc
Grid.Column="0"
Stroke="{Binding DaySalesPercentage, Converter={StaticResource StorePerformanceToColourConverter}}"
StrokeThickness="5"
StartAngle="0" EndAngle="{Binding DaySalesPercentage, Converter={StaticResource StorePerformanceToAngleConverter}}"
Margin="20"/>
<Label Content="sales remaining today"
x:Name="testing"
Foreground="{Binding IsDaySalesPercentageAvailable, Converter={StaticResource UnavailableStorePerformanceToColourConverter}}"
HorizontalContentAlignment="Center"
FontSize="9"
FontWeight="DemiBold"
VerticalAlignment="Top"
Margin="0,0,0,0"
Grid.Column="0"/>
<Label Content="{Binding DaySalesPercentageLabel}"
x:Name="DaySalesPercentageLabel1"
Foreground="{Binding IsDaySalesPercentageAvailable, Converter={StaticResource UnavailableStorePerformanceToColourConverter}}"
HorizontalContentAlignment="Center"
FontSize="20"
FontWeight="DemiBold"
VerticalAlignment="Top"
Margin="0,28,0,0"
Grid.Column="0"/>
<Label Content="-£60"
x:Name="DaySalesPercentageLabel2"
Foreground="{Binding IsDaySalesPercentageAvailable, Converter={StaticResource UnavailableStorePerformanceToColourConverter}}"
HorizontalContentAlignment="Center"
FontSize="10"
FontWeight="DemiBold"
VerticalAlignment="Top"
Margin="0,50,0,0"
Grid.Column="0"/>
</Grid>
</Viewbox>
Arc.cs:
public class Arc : Shape
{
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(Arc), new PropertyMetadata(0.0, AnglesChanged));
public double EndAngle
{
get { return (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("EndAngle", typeof(double), typeof(Arc), new PropertyMetadata(0.0, AnglesChanged));
protected override Geometry DefiningGeometry
{
get
{
return GetArcGeometry();
}
}
private static void AnglesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var arc = d as Arc;
if (arc != null)
arc.InvalidateVisual();
}
private Geometry GetArcGeometry()
{
Point startPoint = PointAtAngle(Math.Min(StartAngle, EndAngle));
Point endPoint = PointAtAngle(Math.Max(StartAngle, EndAngle));
Size arcSize = new Size(Math.Max(0, (RenderSize.Width - StrokeThickness) / 2),
Math.Max(0, (RenderSize.Height - StrokeThickness) / 2));
bool isLargeArc = Math.Abs(EndAngle - StartAngle) > 180;
StreamGeometry geom = new StreamGeometry();
using (StreamGeometryContext context = geom.Open())
{
context.BeginFigure(startPoint, false, false);
context.ArcTo(endPoint, arcSize, 0, isLargeArc,
SweepDirection.Counterclockwise, true, false);
}
geom.Transform = new TranslateTransform(StrokeThickness / 2, StrokeThickness / 2);
return geom;
}
private Point PointAtAngle(double angle)
{
double radAngle = angle * (Math.PI / 180);
double xRadius = (RenderSize.Width - StrokeThickness) / 2;
double yRadius = (RenderSize.Height - StrokeThickness) / 2;
double x = xRadius + xRadius * Math.Cos(radAngle);
double y = yRadius - yRadius * Math.Sin(radAngle);
return new Point(x, y);
}
}
I've tried adding the following:
<notifications1:Arc
Grid.Column="0"
Stroke="{Binding DaySalesPercentage, Converter={StaticResource StorePerformanceToColourConverter}}"
StrokeThickness="5"
StartAngle="0" EndAngle="{Binding DaySalesPercentage, Converter={StaticResource StorePerformanceToAngleConverter}}"
Margin="20">
<notifications1:Arc.RenderTransform>
<TransformGroup>
<!-- Triple the size (scale) of the button in the Y direction. -->
<ScaleTransform ScaleX="-1" ScaleY="1" />
<!-- Rotate the button by 45 degrees. -->
<RotateTransform Angle="90" />
</TransformGroup>
</notifications1:Arc.RenderTransform>
</notifications1:Arc>
Which rotates and moves the arc like this:
you almost got it
just use LayoutTransform
instead of RenderTransform
here is a display of EndAngle = 120 with LayoutTransform: