Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/wpf/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Wpf 绘制饼图_Wpf_Canvas_Pie Chart_Dependency Properties - Fatal编程技术网

Wpf 绘制饼图

Wpf 绘制饼图,wpf,canvas,pie-chart,dependency-properties,Wpf,Canvas,Pie Chart,Dependency Properties,在查看了几个创建piechart的示例后,我为其制作了以下UserControl: public partial class PieChart : UserControl { #region DependencyProperties public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(PieCh

在查看了几个创建piechart的示例后,我为其制作了以下
UserControl

public partial class PieChart : UserControl
{
    #region DependencyProperties
    public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register("Radius", typeof(double), typeof(PieChart), new PropertyMetadata(0d));
    public static readonly DependencyProperty SeriesProperty = DependencyProperty.Register("Series", typeof(List<PieSeries>), typeof(PieChart), new PropertyMetadata(null, Draw));

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

    public List<PieSeries> Series {
        get { return (List<PieSeries>)GetValue(SeriesProperty); }
        set { SetValue(SeriesProperty, value); }
    }

    static void Draw(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var control = d as PieChart;
        control.AddPie(control.chartArea);
    }
    #endregion

    Brush[] colors = new Brush[] { Brushes.Gray, Brushes.Green, Brushes.Blue, Brushes.LightGray, Brushes.AntiqueWhite };
    double xCenter, yCenter;
    public PieChart()
    {
        InitializeComponent();
    }

    void AddPie(Canvas canvas)
    {
        canvas.Width = 300; canvas.Height = 300;
        xCenter = canvas.Width / 2;
        yCenter = canvas.Height / 2;

        double sum, startAngle, sweepAngle;
        sum = Series.Sum(x => x.Value);
        startAngle = sweepAngle = 0.0;

        for (int i = 0; i < Series.Count; i++)
        {
            var brush = colors[i];
            startAngle += sweepAngle;
            sweepAngle = 2 * Math.PI * Series[i].Value / sum;
            DrawSegments(canvas, brush, startAngle, startAngle + sweepAngle);
        }
    }

    void DrawSegments(Canvas canvas, Brush fillColor, double startAngle, double endAngle)
    {
        var line1 = new LineSegment() { Point = new Point(xCenter + Radius * Math.Cos(startAngle), yCenter + Radius * Math.Sin(startAngle)) };
        var line2 = new LineSegment() { Point = new Point(xCenter + Radius * Math.Cos(endAngle), yCenter + Radius * Math.Sin(endAngle)) };
        var arc = new ArcSegment()
        {
            SweepDirection = SweepDirection.Clockwise,
            Size = new Size(Radius, Radius),
            Point = new Point(xCenter + Radius * Math.Cos(endAngle), yCenter + Radius * Math.Sin(endAngle))
        };
        var figure = new PathFigure() { IsClosed = true, StartPoint = new Point(xCenter, yCenter), Segments = { line1, arc, line2 } };
        var geometry = new PathGeometry() { Figures = { figure } };
        var path = new Path() { Fill = fillColor, Data = geometry };
        canvas.Children.Add(path);
    }
}
不确定这是否是正确的方法,但它是有效的。问题在于
AddPie
方法。我必须设置
画布
宽度
高度
,否则
主窗口中不会显示任何内容。下面是我在
main窗口中如何使用它的:

<Window>
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <local:PieChart Grid.Row="1"
                        Radius="100"
                        Series="{Binding Series}"/>
    </Grid>
</Window>
Draw
回调中,我总是得到
0
作为
Canvas
ActualHeight
ActualWidth
,名为
chartArea
,因此调整窗口大小时饼图不会自动调整大小

如何解决这个问题

有没有更好的方法来画简单的饼图?在我的情况下,
列表
可能包含1到5项

编辑 有了评论中建议的方法,就简单多了!在我的ViewModel中,
VM
,我有以下内容:

public class VM : INotifyPropertyChanged
{
    public ObservableCollection<ShapeData> Series { get; set; } = new ObservableCollection<ShapeData>();
    double[] array = { 30, 10, 15, 20, 25};
    Brush[] brushes = new Brush[] { Brushes.Gray, Brushes.Green, Brushes.Blue, Brushes.LightGray, Brushes.AntiqueWhite };
    double radius, xCenter, yCenter;

    public Command Resized { get; set; }
    public VM()
    {
        radius = 100;
        Resized = new Command(resized, (o) => true);
    }

    void resized(object obj)
    {
        var canvas = obj as Canvas;
        xCenter = canvas.ActualWidth / 2;
        yCenter = canvas.ActualHeight / 2;
        Series.Clear();
        DrawPie(array, brushes, radius);
    }

    void DrawPie(double[] values, Brush[] colors, double radius)
    {
        var sum = values.Sum();
        double startAngle, sweepAngle;
        startAngle = sweepAngle = 0;

        for (int i = 0; i < values.Length; i++)
        {
            startAngle += sweepAngle;
            sweepAngle = 2 * Math.PI * values[i] / sum;

            var line1 = new LineSegment() { Point = new Point(xCenter + radius * Math.Cos(startAngle), yCenter + radius * Math.Sin(startAngle)) };
            var line2 = new LineSegment() { Point = new Point(xCenter + radius * Math.Cos(startAngle + sweepAngle), yCenter + radius * Math.Sin(startAngle + sweepAngle)) };
            var arc = new ArcSegment()
            {
                SweepDirection = SweepDirection.Clockwise,
                Size = new Size(radius, radius),
                Point = new Point(xCenter + radius * Math.Cos(startAngle + sweepAngle), yCenter + radius * Math.Sin(startAngle + sweepAngle))
            };
            var figure = new PathFigure() { IsClosed = true, StartPoint = new Point(xCenter, yCenter), Segments = { line1, arc, line2 } };

            Series.Add(new ShapeData()
            {
                Geometry = new PathGeometry() { Figures = { figure } },
                Fill = colors[i],
                Stroke = Brushes.Red,
                StrokeThickness = 1
            });
        }
    }

    #region Notify Property Changed Members
    public event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    #endregion
}
xaml
中,我在
标签上得到一个警告

找不到类型的默认构造函数 “System.Windows.Interactivity.TriggerCollection”。你可以使用 用于构造此类型的参数或FactoryMethod指令


请注意,当
chartArea
是PieChart的成员时,将其作为参数传递给AddPie方法似乎是毫无意义的。此外,当您将
var control=d写入PieChart
时,应检查结果是否为
null
。最好使用显式cast,比如
var-control=(PieChart)d
因为如果d不是PieChart,它将正确地为您提供InvalidCastException(而不是不正确的NullReferenceException)。此外,
Series
属性是在执行布局之前设置的。控件大小更改时需要重新绘制自身。AddPie不是静态方法。我会覆盖OnRenderSizeChanged。在我看来,手动创建和添加这些路径也很奇怪。您已经知道ItemsControl的工作原理。只需使用画布作为其ItemsPanel,以及ItemTemplate中的路径,该路径的数据属性仅绑定到ItemsSource集合中的几何体。
Series = new List<PieSeries>()
{
    new PieSeries("A", 30),
    new PieSeries("B", 20),
    new PieSeries("C", 10),
    new PieSeries("D", 15),
    new PieSeries("E", 25)
};
public class VM : INotifyPropertyChanged
{
    public ObservableCollection<ShapeData> Series { get; set; } = new ObservableCollection<ShapeData>();
    double[] array = { 30, 10, 15, 20, 25};
    Brush[] brushes = new Brush[] { Brushes.Gray, Brushes.Green, Brushes.Blue, Brushes.LightGray, Brushes.AntiqueWhite };
    double radius, xCenter, yCenter;

    public Command Resized { get; set; }
    public VM()
    {
        radius = 100;
        Resized = new Command(resized, (o) => true);
    }

    void resized(object obj)
    {
        var canvas = obj as Canvas;
        xCenter = canvas.ActualWidth / 2;
        yCenter = canvas.ActualHeight / 2;
        Series.Clear();
        DrawPie(array, brushes, radius);
    }

    void DrawPie(double[] values, Brush[] colors, double radius)
    {
        var sum = values.Sum();
        double startAngle, sweepAngle;
        startAngle = sweepAngle = 0;

        for (int i = 0; i < values.Length; i++)
        {
            startAngle += sweepAngle;
            sweepAngle = 2 * Math.PI * values[i] / sum;

            var line1 = new LineSegment() { Point = new Point(xCenter + radius * Math.Cos(startAngle), yCenter + radius * Math.Sin(startAngle)) };
            var line2 = new LineSegment() { Point = new Point(xCenter + radius * Math.Cos(startAngle + sweepAngle), yCenter + radius * Math.Sin(startAngle + sweepAngle)) };
            var arc = new ArcSegment()
            {
                SweepDirection = SweepDirection.Clockwise,
                Size = new Size(radius, radius),
                Point = new Point(xCenter + radius * Math.Cos(startAngle + sweepAngle), yCenter + radius * Math.Sin(startAngle + sweepAngle))
            };
            var figure = new PathFigure() { IsClosed = true, StartPoint = new Point(xCenter, yCenter), Segments = { line1, arc, line2 } };

            Series.Add(new ShapeData()
            {
                Geometry = new PathGeometry() { Figures = { figure } },
                Fill = colors[i],
                Stroke = Brushes.Red,
                StrokeThickness = 1
            });
        }
    }

    #region Notify Property Changed Members
    public event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    #endregion
}
<ItemsControl Grid.Row="1" ItemsSource="{Binding Series}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Path Data="{Binding Geometry}"
                    Fill="{Binding Fill}"
                    Stroke="{Binding Stroke}"
                    StrokeThickness="{Binding StrokeThickness}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas x:Name="panel">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SizeChanged">
                        <i:InvokeCommandAction Command="{Binding Resized}"
                                                CommandParameter="{Binding ElementName=panel}"/>
                    </i:EventTrigger>
                    <i:EventTrigger EventName="Loaded">
                        <i:InvokeCommandAction Command="{Binding Resized}"
                                                CommandParameter="{Binding ElementName=panel}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Canvas>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>