Wpf 绘制饼图
在查看了几个创建piechart的示例后,我为其制作了以下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
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>