在查看了创建饼图的几个示例之后,我为此做了以下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);
}
}
在xaml
中有此内容:
<UserControl ...>
<Grid>
<Canvas x:Name="chartArea" Margin="10"/>
</Grid>
</UserControl>
不确定这样做是否正确,但是否可行。问题在于AddPie
方法中。我必须设置Width
的{{1}}和Height
,否则Canvas
中什么也没有出现。这是我在MainWindow
中使用它的方式:
MainWindow
在ViewModel的<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>
中,我创建了constructor
:
Series
在Series = new List<PieSeries>()
{
new PieSeries("A", 30),
new PieSeries("B", 20),
new PieSeries("C", 10),
new PieSeries("D", 15),
new PieSeries("E", 25)
};
回调中,我总是得到Draw
作为0
中ActualHeight
的{{1}}和ActualWidth
的名字,{Pet}调整窗口大小时会自动调整大小!
该如何解决?
是否有更好的方法绘制简单的饼图?就我而言,Canvas
中可能有1到5项。
使用注释中建议的方法,它要简单得多!在我的ViewModel chartArea
中,这些是:
List<PieSeries>
和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
中使用它,我在<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>
标签上收到警告
找不到类型的默认构造函数 'System.Windows.Interactivity.TriggerCollection'。您可以使用 用于构造此类型的参数或FactoryMethod指令。