wpfxamlrotatetransformscaletransformtransformgroup

Changing starting angle and direction of a custom doughnut chart


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.

enter image description here

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:

enter image description here


Solution

  • you almost got it

    just use LayoutTransform instead of RenderTransform

    here is a display of EndAngle = 120 with LayoutTransform:

    enter image description here