使用Telerik控件库制作WPF项目中的折线图、柱状图、饼图和甜甜圈图
本博客是基于 .Net Framework 4.6.2 的WPF(MVVM)项目,Telerik版本为 2016.2.613.40。其他版本是否可用不详。
本文章所使用数据均为测试数据,无任何意义。
本博客所有内容是根据本人实际使用情况,面向百度及Telerik官方文档、Telerik官方示例、Telerik官方论坛。如超出需求,可自行查询。
1 xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" 2 xmlns:chartView="clr-namespace:Telerik.Windows.Controls.ChartView;assembly=Telerik.Windows.Controls.Chart" 3 xmlns:animation="clr-namespace:Management.Control"
Palette可选样式: https://docs.telerik.com/devtools/wpf/controls/radchartview/features/palettes/introduction
1.折线图:
1.1 XAML
1 <telerik:RadCartesianChart Margin="10" 2 <!--设置颜色主题--> 3 Palette="Autumn" 4 <!--折线图上鼠标滑过提示的样式--> 5 TrackBallInfoStyle="{StaticResource trackBallInfoStyle}"> 6 7 <telerik:RadCartesianChart.HorizontalAxis> <!-- 水平轴 --> 8 <chartView:CategoricalAxis Title="月份" <!-- 标题 --> 9 GapLength="0.8" <!-- 间隔长度[0-1] --> 10 PlotMode="OnTicksPadded" /> 11 </telerik:RadCartesianChart.HorizontalAxis> 12 13 <telerik:RadCartesianChart.VerticalAxis> <!-- 垂直轴 --> 14 <chartView:LinearAxis Title="案件数量"/> 15 </telerik:RadCartesianChart.VerticalAxis> 16 17 <telerik:RadCartesianChart.Grid> <!-- 控制显示和Y轴垂直的阴影 --> 18 <telerik:CartesianChartGrid MajorLinesVisibility="Y" StripLinesVisibility="Y"/> 19 </telerik:RadCartesianChart.Grid> 20 21 <telerik:RadCartesianChart.Series> 22 <telerik:LineSeries ShowLabels="True" <!-- 显示标签,位于折线拐点处--> 23 ValueBinding="Number" <!-- 水平轴绑定的模型--> 24 CategoryBinding="KeyName" <!-- 垂直轴绑定的模型--> 25 animation:ChartAnimationUtilities.CartesianAnimation="DropWithDelay" <!-- 数据加载动画--> 26 TrackBallInfoTemplate="{StaticResource consumptionTrackBallInfoTemplate}" <!-- 鼠标滑过折线图的提示信息 --> 27 ItemsSource="{Binding MonLawcases}"> <!--绑定的数据源 --> 28 </telerik:LineSeries> 29 </telerik:RadCartesianChart.Series> 30 31 <telerik:RadCartesianChart.Behaviors> 32 <telerik:ChartTrackBallBehavior ShowIntersectionPoints="True" /> <!-- 展示折线拐点 --> 33 <!--<telerik:ChartPanAndZoomBehavior PanMode="Horizontal" DragMode="Pan" ZoomMode="Horizontal"/>--> <!-- 显示水平滚动条 --> 34 </telerik:RadCartesianChart.Behaviors> 35 </telerik:RadCartesianChart>
1.2 资源样式
1 <Style x:Key="trackBallInfoStyle" TargetType="telerik:TrackBallInfoControl"> 2 <Setter Property="Template"> 3 <Setter.Value> 4 <ControlTemplate TargetType="telerik:TrackBallInfoControl"> 5 <Border Background="White" BorderBrush="#FF808080" BorderThickness="1"> 6 <StackPanel Name="panel" Grid.Row="1" Margin="3 0 3 0" /> 7 </Border> 8 </ControlTemplate> 9 </Setter.Value> 10 </Setter> 11 </Style>
1 <DataTemplate x:Key="consumptionTrackBallInfoTemplate"> 2 <Border> 3 <StackPanel Orientation="Vertical"> 4 <StackPanel Orientation="Horizontal"> 5 <TextBlock Text="月份:" /> 6 <TextBlock Text="{Binding DataPoint.Category}" /> 7 </StackPanel> 8 <StackPanel Orientation="Horizontal"> 9 <TextBlock Text="数量:" /> 10 <TextBlock Text="{Binding DataPoint.Value}" /> 11 </StackPanel> 12 </StackPanel> 13 </Border> 14 </DataTemplate>
1.3 数据模型
1 public class LineInfo 2 { 3 public int KeyName { get; set; } 4 5 public int Number { get; set; } 6 } 7 8 private ObservableCollection<LineInfo> _monLawcases; 9 public ObservableCollection<LineInfo> MonLawcases 10 { 11 get { return _monLawcases; } 12 set { SetProperty(ref _monLawcases, value, "MonLawcases"); } 13 }
注:SetProperty(ref _monLawcases, value, “MonLawcases”);为Prism框架写法,可替换为
1 public class XXX:INotifyPropertyChanged 2 { 3 private ObservableCollection<LineInfo> _monLawcases; 4 public ObservableCollection<LineInfo> MonLawcases 5 { 6 public event PropertyChangedEventHandler PropertyChanged; 7 get { return _monLawcases; } 8 set 9 { 10 _monLawcases= value; 11 if (PropertyChanged != null) 12 { 13 PropertyChanged(this, new PropertyChangedEventArgs("MonLawcases")); 14 } 15 } 16 } 17 }
1.4 效果图

2.柱状图:
2.1 XAML
1 <telerik:RadCartesianChart Grid.Column="2" 2 Margin="10" 3 HoverMode="FadeOtherSeries" 4 TrackBallInfoStyle="{StaticResource trackBallInfoStyle}" > 5 <telerik:RadCartesianChart.HorizontalAxis> 6 <telerik:CategoricalAxis Title="单位名称" GapLength="0.5"/> 7 </telerik:RadCartesianChart.HorizontalAxis> 8 9 <telerik:RadCartesianChart.VerticalAxis> 10 <telerik:LinearAxis Title="检材数量" 11 SmartLabelsMode="SmartStepAndRange"/> 12 </telerik:RadCartesianChart.VerticalAxis> 13 14 <telerik:RadCartesianChart.Grid> 15 <telerik:CartesianChartGrid MajorLinesVisibility="Y" StripLinesVisibility="Y"/> 16 </telerik:RadCartesianChart.Grid> 17 18 <telerik:RadCartesianChart.Behaviors> 19 <telerik:ChartTrackBallBehavior /> 20 <!--<telerik:ChartPanAndZoomBehavior PanMode="Horizontal" DragMode="Pan" ZoomMode="Horizontal"/>--> 21 </telerik:RadCartesianChart.Behaviors> 22 23 <telerik:RadCartesianChart.Series> 24 <telerik:BarSeries ValueBinding="Count" 25 ShowLabels="True" 26 CategoryBinding="Name" 27 animation:ChartAnimationUtilities.CartesianAnimation="RiseWithDelay" <!-- 加载动画 --> 28 TrackBallInfoTemplate="{StaticResource consumptionTrackBallInfoTemplate1}" 29 ItemsSource="{Binding UnitDevices}"> 30 </telerik:BarSeries> 31 </telerik:RadCartesianChart.Series> 32 </telerik:RadCartesianChart>
2.2 资源样式
1 <Style x:Key="trackBallInfoStyle" TargetType="telerik:TrackBallInfoControl"> 2 <Setter Property="Template"> 3 <Setter.Value> 4 <ControlTemplate TargetType="telerik:TrackBallInfoControl"> 5 <Border Background="White" BorderBrush="#FF808080" BorderThickness="1"> 6 <StackPanel Name="panel" Grid.Row="1" Margin="3 0 3 0" /> 7 </Border> 8 </ControlTemplate> 9 </Setter.Value> 10 </Setter> 11 </Style>
1 <DataTemplate x:Key="consumptionTrackBallInfoTemplate1"> 2 <Border> 3 <StackPanel Orientation="Vertical"> 4 <StackPanel Orientation="Horizontal"> 5 <TextBlock Text="单位:" /> 6 <TextBlock Text="{Binding DataPoint.Category}" /> 7 </StackPanel> 8 <StackPanel Orientation="Horizontal"> 9 <TextBlock Text="检材数量:" /> 10 <TextBlock Text="{Binding DataPoint.Value}" /> 11 </StackPanel> 12 </StackPanel> 13 </Border> 14 </DataTemplate>
2.3 数据模型
1 public class BarChartParam 2 { 3 public string Name { get; set; } 4 public int Count { get; set; } 5 } 6 7 private ObservableCollection<BarChartParam> _unitDevices; 8 public ObservableCollection<BarChartParam> UnitDevices 9 { 10 get { return _unitDevices; } 11 set { SetProperty(ref _unitDevices, value, "UnitDevices"); } 12 }
2.4 效果图

3 饼图
3.1 XAML
1 <!-- 饼图 --> 2 <telerik:RadPieChart Palette="Summer" x:Name="PieChart"> 3 4 <!-- 控制饼图标签显示位置 DisplayMode 可选--> 5 <telerik:RadPieChart.SmartLabelsStrategy > 6 <telerik:PieChartSmartLabelsStrategy DisplayMode="SpiderUnaligned"/> 7 </telerik:RadPieChart.SmartLabelsStrategy> 8 9 <!-- 控制饼图选择模式 此处为当单选 DataPointSelectionMode可选--> 10 <telerik:RadPieChart.Behaviors> 11 <telerik:ChartSelectionBehavior DataPointSelectionMode="Single"/> 12 </telerik:RadPieChart.Behaviors> 13 14 <telerik:RadPieChart.Series> 15 <!--LabelFormat="##.##%" ShowLabels="True"--> <!-- 标签显示样式, ##.##% : 例 7.00% --> 16 <telerik:PieSeries ItemsSource="{Binding UnitDevicePercentage}" 17 animation:ChartAnimationUtilities.PieAnimation="Slice" <!-- 加载动画,注意和折线图的区别 --> 18 ValueBinding="Number"> <!-- 饼图数据绑定的模型 --> 19 <telerik:PieSeries.LegendSettings> 20 <telerik:DataPointLegendSettings TitleBinding="Name" /> 21 </telerik:PieSeries.LegendSettings> 22 23 <!-- 饼图标签和饼图的连接线,此处使用默认的灰色 --> 24 <telerik:PieSeries.LabelConnectorsSettings> 25 <telerik:ChartSeriesLabelConnectorsSettings /> 26 </telerik:PieSeries.LabelConnectorsSettings> 27 28 <!-- 饼图标签显示样式 --> 29 <telerik:PieSeries.LabelDefinitions> 30 <telerik:ChartSeriesLabelDefinition Template="{StaticResource PieLableTemplate}"/> 31 </telerik:PieSeries.LabelDefinitions> 32 </telerik:PieSeries> 33 </telerik:RadPieChart.Series> 34 </telerik:RadPieChart>
1 <!--图例--> 2 3 <telerik:RadLegend Grid.Column="1" 4 HorizontalAlignment="Center" 5 VerticalAlignment="Bottom" 6 Items="{Binding LegendItems, ElementName=PieChart}"/>
$标签注意 若使用模板的写法,数据源需要是计算好的百分比(如 4.45),若不想计算百分比可按 代码中注释的 LabelFormat写法,控件库可自行计算百分比,此时不能使用模板样式 -------------甜甜圈图同样。
$图例注意绑定的Items 为饼图的名称x:Name=“PieChart”。
3.2 资源样式
1 //饼图标签显示样式模板 2 <DataTemplate x:Key="PieLableTemplate"> 3 <StackPanel Orientation="Horizontal"> 4 <TextBlock Text="{Binding DataItem.Name}"/> 5 <TextBlock Text=":"/> 6 <TextBlock Text="{Binding DataItem.Number,StringFormat=\{0:n2\}}"/> 7 <TextBlock Text="%"/> 8 </StackPanel> 9 </DataTemplate>
3.3 数据模型
1 public class PieInfo 2 { 3 public string Name { get; set; } 4 public double Number { get; set; } 5 } 6 7 private ObservableCollection<PieInfo> _unitDevicePercentage; 8 public ObservableCollection<PieInfo> UnitDevicePercentage 9 { 10 get { return _unitDevicePercentage; } 11 set { SetProperty(ref _unitDevicePercentage, value, "UnitDevicePercentage"); } 12 }
3.4 效果图

4 甜甜圈图
4.1 XAML
1 telerik:RadPieChart Palette="Windows8" 2 Grid.Row="0" 3 Grid.Column="0" 4 Grid.RowSpan="2" 5 x:Name="DoughnutSeries"> 6 7 <telerik:RadPieChart.SmartLabelsStrategy> 8 <telerik:PieChartSmartLabelsStrategy DisplayMode="SpiderUnaligned"/> 9 </telerik:RadPieChart.SmartLabelsStrategy> 10 11 <telerik:RadPieChart.Series> 12 <telerik:DoughnutSeries ItemsSource="{Binding PhoneBrands}" 13 InnerRadiusFactor="0.65" <!--内圆半径--> 14 animation:ChartAnimationUtilities.PieAnimation="RadiusFactor" 15 ValueBinding="Number"> 16 17 <telerik:DoughnutSeries.LegendSettings> 18 <telerik:DataPointLegendSettings TitleBinding="Name" /> 19 </telerik:DoughnutSeries.LegendSettings> 20 21 <telerik:DoughnutSeries.LabelConnectorsSettings> 22 <telerik:ChartSeriesLabelConnectorsSettings /> 23 </telerik:DoughnutSeries.LabelConnectorsSettings> 24 25 <telerik:DoughnutSeries.LabelDefinitions> 26 <telerik:ChartSeriesLabelDefinition Template="{StaticResource PieLableTemplate}"/> 27 </telerik:DoughnutSeries.LabelDefinitions> 28 </telerik:DoughnutSeries> 29 </telerik:RadPieChart.Series> 30 </telerik:RadPieChart>
1 <!-- 图例 --> 2 <telerik:RadLegend Grid.Column="1" 3 Grid.Row="0" 4 HorizontalAlignment="Center" 5 VerticalAlignment="Bottom" 6 Items="{Binding LegendItems, ElementName=DoughnutSeries}"/>
4.2 资源样式
1 <DataTemplate x:Key="PieLableTemplate"> 2 <StackPanel Orientation="Horizontal"> 3 <TextBlock Text="{Binding DataItem.Name}"/> 4 <TextBlock Text=":"/> 5 <TextBlock Text="{Binding DataItem.Number,StringFormat=\{0:n2\}}"/> 6 <TextBlock Text="%"/> 7 </StackPanel> 8 </DataTemplate>
4.3 数据模型
1 public class PieInfo 2 { 3 public string Name { get; set; } 4 public double Number { get; set; } 5 } 6 7 private ObservableCollection<PieInfo> _phoneBrands; 8 public ObservableCollection<PieInfo> PhoneBrands 9 { 10 get { return _phoneBrands; } 11 set { SetProperty(ref _phoneBrands, value, "PhoneBrands"); } 12 }
4.4 效果图

5 甜甜圈内嵌饼图
5.1 XAML
1 <Grid Grid.Row="2" 2 Grid.Column="2"> 3 4 <Grid.ColumnDefinitions> 5 <ColumnDefinition/> 6 <ColumnDefinition Width="auto"/> 7 </Grid.ColumnDefinitions> 8 <Grid.RowDefinitions> 9 <RowDefinition/> 10 <RowDefinition/> 11 </Grid.RowDefinitions> 12 13 <!--甜甜圈图-外--> 14 <telerik:RadPieChart Palette="Windows8" 15 Grid.Row="0" 16 Grid.Column="0" 17 Grid.RowSpan="2" 18 x:Name="DoughnutSeries"> 19 20 <telerik:RadPieChart.SmartLabelsStrategy> 21 <telerik:PieChartSmartLabelsStrategy DisplayMode="SpiderUnaligned"/> 22 </telerik:RadPieChart.SmartLabelsStrategy> 23 24 <telerik:RadPieChart.Series> 25 <telerik:DoughnutSeries ItemsSource="{Binding PhoneBrands}" 26 InnerRadiusFactor="0.65" 27 animation:ChartAnimationUtilities.PieAnimation="RadiusFactor" 28 ValueBinding="Number"> 29 30 <telerik:DoughnutSeries.LegendSettings> 31 <telerik:DataPointLegendSettings TitleBinding="Name" /> 32 </telerik:DoughnutSeries.LegendSettings> 33 34 <telerik:DoughnutSeries.LabelConnectorsSettings> 35 <telerik:ChartSeriesLabelConnectorsSettings /> 36 </telerik:DoughnutSeries.LabelConnectorsSettings> 37 38 <telerik:DoughnutSeries.LabelDefinitions> 39 <telerik:ChartSeriesLabelDefinition Template="{StaticResource PieLableTemplate}"/> 40 </telerik:DoughnutSeries.LabelDefinitions> 41 </telerik:DoughnutSeries> 42 </telerik:RadPieChart.Series> 43 </telerik:RadPieChart> 44 45 <!--外图例--> 46 <telerik:RadLegend Grid.Column="1" 47 Grid.Row="0" 48 HorizontalAlignment="Center" 49 VerticalAlignment="Bottom" 50 Items="{Binding LegendItems, ElementName=DoughnutSeries}"/> 51 52 53 <!---甜甜圈-内--> 54 <telerik:RadPieChart Palette="Summer" 55 x:Name="innerPieSeries" 56 Grid.Row="0" 57 Grid.Column="0" 58 Grid.RowSpan="2"> 59 60 <telerik:RadPieChart.Behaviors> 61 <telerik:ChartSelectionBehavior DataPointSelectionMode="Single" 62 SelectionChanged="ChartSelectionBehavior_SelectionChanged" /> <!-- 饼图选择改变事件,根据内饼图选择的块,动态改变外甜甜圈的数据 --> 63 </telerik:RadPieChart.Behaviors> 64 65 <telerik:RadPieChart.Series> 66 <telerik:PieSeries RadiusFactor="0.55" 67 Loaded="PieSeries_Loaded" <!-- 数据加载完毕触发自动选中第一块饼图--> 68 animation:ChartAnimationUtilities.PieAnimation="StartAngle" 69 ItemsSource="{Binding UnitDevicePercentage}" 70 ValueBinding="Number"> 71 72 <telerik:PieSeries.LegendSettings> 73 <telerik:DataPointLegendSettings TitleBinding="Name" /> 74 </telerik:PieSeries.LegendSettings> 75 76 <telerik:PieSeries.LabelConnectorsSettings> 77 <telerik:ChartSeriesLabelConnectorsSettings /> 78 </telerik:PieSeries.LabelConnectorsSettings> 79 80 <telerik:PieSeries.LabelDefinitions> 81 <telerik:ChartSeriesLabelDefinition Template="{StaticResource PieLableTemplate}"/> 82 </telerik:PieSeries.LabelDefinitions> 83 </telerik:PieSeries> 84 </telerik:RadPieChart.Series> 85 </telerik:RadPieChart> 86 87 <!--内图例--> 88 89 <telerik:RadLegend Grid.Column="1" 90 Grid.Row="1" 91 HorizontalAlignment="Center" 92 VerticalAlignment="Bottom" 93 Items="{Binding LegendItems, ElementName=innerPieSeries}"/> 94 </Grid>
5.2 资源样式
1 <!--饼图标签模板--> 2 <DataTemplate x:Key="PieLableTemplate"> 3 <StackPanel Orientation="Horizontal"> 4 <TextBlock Text="{Binding DataItem.Name}"/> 5 <TextBlock Text=":"/> 6 <TextBlock Text="{Binding DataItem.Number,StringFormat=\{0:n2\}}"/> 7 <TextBlock Text="%"/> 8 </StackPanel> 9 </DataTemplate>
5.3 数据模型
1 public class PieInfo 2 { 3 public string Name { get; set; } 4 public double Number { get; set; } 5 } 6 7 private ObservableCollection<PieInfo> _phoneBrands; 8 public ObservableCollection<PieInfo> PhoneBrands 9 { 10 get { return _phoneBrands; } 11 set { SetProperty(ref _phoneBrands, value, "PhoneBrands"); } 12 } 13 14 15 private ObservableCollection<PieInfo> _unitDevicePercentage; 16 public ObservableCollection<PieInfo> UnitDevicePercentage 17 { 18 get { return _unitDevicePercentage; } 19 set { SetProperty(ref _unitDevicePercentage, value, "UnitDevicePercentage"); } 20 }
5.4 数据加载事件与饼图点击事件
1 private void ChartSelectionBehavior_SelectionChanged(object sender, ChartSelectionChangedEventArgs e) 2 { 3 try 4 { 5 var selectedPoint = this.innerPieSeries.SelectedPoints; 6 7 if (selectedPoint == null || selectedPoint.Count <= 0) 8 { 9 _dataAnalysisPageViewModel.PhoneBrands.Clear(); 10 return; 11 } 12 13 if (!(selectedPoint[0].DataItem is PieInfo)) 14 return; 15 16 var pieInfo = selectedPoint[0].DataItem as PieInfo; 17 18 if (pieInfo == null) 19 return; 20 21 //此处根据 pieInfo.Name 查询对应数据 22 //数据查询比较耗时,此处可另开线程 23 Task.Run(() => 24 { 25 //操作UI需通知到主线程 26 // App.Current.Dispatcher.Invoke(() => { XXX }); 27 }); 28 29 } 30 catch (Exception ex) 31 { 32 LoggerService.Error($"ChartSelectionBehavior_SelectionChanged Error{ex.ToString()}", ex); 33 } 34 }
1 private void PieSeries_Loaded(object sender, RoutedEventArgs e) 2 { 3 try 4 { 5 var allSeries = this.innerPieSeries.Series; 6 if (allSeries == null || allSeries.Count <= 0) 7 return; 8 9 var allDataPoints = allSeries.FirstOrDefault()?.DataPoints; 10 11 if (allDataPoints == null || allDataPoints.Count <= 0) 12 return; 13 var dataPoints = allDataPoints.FirstOrDefault(); 14 if (dataPoints == null) 15 return; 16 17 // 默认选中第一个饼块 18 dataPoints.IsSelected = true; 19 20 // 简单粗暴的写法 21 //this.innerPieSeries.Series[0].DataPoints[0].IsSelected = true; 22 } 23 catch (Exception ex) 24 { 25 LoggerService.Error($"PieSeries_Loaded Error{ex.ToString()}", ex); 26 } 27 }
5.5 效果图

6 数据加载动画
动画使用时引入相应的命名空间即可,使用方法如上例。
1 xmlns:animation="clr-namespace:Management.Control"
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Windows; 7 using System.Windows.Controls; 8 using System.Windows.Media; 9 using System.Windows.Media.Animation; 10 using System.Windows.Shapes; 11 using Telerik.Charting; 12 using Telerik.Windows.Controls; 13 using Telerik.Windows.Controls.ChartView; 14 15 namespace Management.Control 16 { 17 //饼图动画枚举 18 public enum PieAnimation 19 { 20 None = 0, 21 StartAngle = 1, 22 SweepAngle = 2, 23 RadiusFactor = 4, 24 Slice = 8, 25 } 26 27 //折线图、柱状图动画枚举 28 public enum CartesianAnimation 29 { 30 None, 31 Drop,//下降 32 DropWithDelay, 33 Rise,//上升 34 RiseWithDelay, 35 Stretch, 36 StackedBars, 37 } 38 39 /// <summary> 40 /// 图表动画 41 /// </summary> 42 public class ChartAnimationUtilities 43 { 44 public static readonly DependencyProperty CartesianAnimationProperty = DependencyProperty.RegisterAttached( 45 "CartesianAnimation", 46 typeof(CartesianAnimation), 47 typeof(ChartAnimationUtilities), 48 new PropertyMetadata(CartesianAnimation.None, CartesianAnimationChanged)); 49 50 public static readonly DependencyProperty PieAnimationProperty = DependencyProperty.RegisterAttached( 51 "PieAnimation", 52 typeof(PieAnimation), 53 typeof(ChartAnimationUtilities), 54 new PropertyMetadata(PieAnimation.None, PieAnimationChanged)); 55 56 private static readonly DependencyProperty RunningAnimationsCountProperty = DependencyProperty.RegisterAttached( 57 "RunningAnimationsCount", 58 typeof(int), 59 typeof(ChartAnimationUtilities), 60 new PropertyMetadata(0)); 61 62 private static readonly DependencyProperty StartAngleProperty = DependencyProperty.RegisterAttached( 63 "StartAngle", 64 typeof(double), 65 typeof(ChartAnimationUtilities), 66 new PropertyMetadata(double.NaN, StartAngleChanged)); 67 68 private static readonly DependencyProperty SweepAngleProperty = DependencyProperty.RegisterAttached( 69 "SweepAngle", 70 typeof(double), 71 typeof(ChartAnimationUtilities), 72 new PropertyMetadata(double.NaN, SweepAngleChanged)); 73 74 private static readonly DependencyProperty SeriesScaleTransformXProperty = DependencyProperty.RegisterAttached( 75 "SeriesScaleTransformX", 76 typeof(double), 77 typeof(ChartAnimationUtilities), 78 new PropertyMetadata(double.NaN, SeriesScaleTransformXChanged)); 79 80 private static readonly DependencyProperty SeriesScaleTransformYProperty = DependencyProperty.RegisterAttached( 81 "SeriesScaleTransformY", 82 typeof(double), 83 typeof(ChartAnimationUtilities), 84 new PropertyMetadata(double.NaN, SeriesScaleTransformYChanged)); 85 86 private static readonly DependencyProperty BarScaleTransformXProperty = DependencyProperty.RegisterAttached( 87 "BarScaleTransformX", 88 typeof(double), 89 typeof(ChartAnimationUtilities), 90 new PropertyMetadata(double.NaN, BarScaleTransformXChanged)); 91 92 private static readonly DependencyProperty BarScaleTransformYProperty = DependencyProperty.RegisterAttached( 93 "BarScaleTransformY", 94 typeof(double), 95 typeof(ChartAnimationUtilities), 96 new PropertyMetadata(double.NaN, BarScaleTransformYChanged)); 97 98 private static readonly DependencyProperty SliceScaleTransformXYProperty = DependencyProperty.RegisterAttached( 99 "SliceScaleTransformXY", 100 typeof(double), 101 typeof(ChartAnimationUtilities), 102 new PropertyMetadata(double.NaN, SliceScaleTransformXYChanged)); 103 104 private static readonly DependencyProperty SeriesTranslateTransformYProperty = DependencyProperty.RegisterAttached( 105 "SeriesTranslateTransformY", 106 typeof(double), 107 typeof(ChartAnimationUtilities), 108 new PropertyMetadata(double.NaN, SeriesTranslateTransformYChanged)); 109 110 private const int DelayInMilliseconds = 1000; 111 private const int PieDelayInMilliseconds = 300; 112 private const int BarDelayInMilliseconds = 200; 113 private static Duration AnimationDuration = new Duration(TimeSpan.FromMilliseconds(1500)); 114 private static Duration PieSliceAnimationDuration = new Duration(TimeSpan.FromMilliseconds(500)); 115 private static Duration BarAnimationDuration = new Duration(TimeSpan.FromMilliseconds(500)); 116 private static object locker = new object(); 117 118 public static CartesianAnimation GetCartesianAnimation(DependencyObject obj) 119 { 120 return (CartesianAnimation)obj.GetValue(CartesianAnimationProperty); 121 } 122 123 public static void SetCartesianAnimation(DependencyObject obj, CartesianAnimation value) 124 { 125 obj.SetValue(CartesianAnimationProperty, value); 126 } 127 128 public static PieAnimation GetPieAnimation(DependencyObject obj) 129 { 130 return (PieAnimation)obj.GetValue(PieAnimationProperty); 131 } 132 133 public static void SetPieAnimation(DependencyObject obj, PieAnimation value) 134 { 135 obj.SetValue(PieAnimationProperty, value); 136 } 137 138 public static void DispatchRunAnimations(RadChartBase chart) 139 { 140 IEnumerable<ChartSeries> series = null; 141 142 RadCartesianChart cartesianChart = chart as RadCartesianChart; 143 if (cartesianChart != null) 144 { 145 series = cartesianChart.Series; 146 } 147 148 RadPieChart pieChart = chart as RadPieChart; 149 if (pieChart != null) 150 { 151 series = pieChart.Series; 152 } 153 154 if (series.Any(s => (int)s.GetValue(ChartAnimationUtilities.RunningAnimationsCountProperty) > 0)) 155 { 156 return; 157 } 158 159 foreach (ChartSeries s in series) 160 { 161 DispatchRunSeriesAnimations(s); 162 } 163 } 164 165 private static void CartesianAnimationChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 166 { 167 CartesianSeries series = (CartesianSeries)target; 168 169 if ((CartesianAnimation)args.NewValue == CartesianAnimation.None) 170 { 171 series.Loaded -= ChartSeries_Loaded; 172 series.DataBindingComplete -= ChartSeries_DataBindingComplete; 173 } 174 175 if ((CartesianAnimation)args.OldValue == CartesianAnimation.None) 176 { 177 series.Loaded += ChartSeries_Loaded; 178 series.DataBindingComplete += ChartSeries_DataBindingComplete; 179 } 180 } 181 182 private static void PieAnimationChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 183 { 184 PieSeries series = (PieSeries)target; 185 186 if ((PieAnimation)args.NewValue == PieAnimation.None) 187 { 188 series.Loaded -= ChartSeries_Loaded; 189 series.DataBindingComplete -= ChartSeries_DataBindingComplete; 190 } 191 192 if ((PieAnimation)args.OldValue == PieAnimation.None) 193 { 194 series.Loaded += ChartSeries_Loaded; 195 series.DataBindingComplete += ChartSeries_DataBindingComplete; 196 } 197 } 198 199 private static void StartAngleChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 200 { 201 PieSeries series = (PieSeries)target; 202 double startAngle = (double)args.NewValue; 203 if (double.IsNaN(startAngle) || series.AngleRange.StartAngle == startAngle) 204 { 205 return; 206 } 207 208 series.AngleRange = new AngleRange(startAngle, series.AngleRange.SweepAngle, series.AngleRange.SweepDirection); 209 } 210 211 private static void SweepAngleChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 212 { 213 PieSeries series = (PieSeries)target; 214 double sweepAngle = (double)args.NewValue; 215 if (double.IsNaN(sweepAngle) || series.AngleRange.SweepAngle == sweepAngle) 216 { 217 return; 218 } 219 220 series.AngleRange = new AngleRange(series.AngleRange.StartAngle, sweepAngle, series.AngleRange.SweepDirection); 221 } 222 223 private static void SeriesScaleTransformXChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 224 { 225 ChartSeries series = (ChartSeries)target; 226 double scaleX = (double)args.NewValue; 227 228 if (!double.IsNaN(scaleX)) 229 { 230 ScaleTransform transform = (ScaleTransform)series.RenderTransform; 231 transform.ScaleX = scaleX; 232 } 233 } 234 235 private static void SeriesScaleTransformYChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 236 { 237 ChartSeries series = (ChartSeries)target; 238 double scaleY = (double)args.NewValue; 239 240 if (!double.IsNaN(scaleY)) 241 { 242 ScaleTransform transform = (ScaleTransform)series.RenderTransform; 243 transform.ScaleY = (double)args.NewValue; 244 } 245 } 246 247 private static void BarScaleTransformXChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 248 { 249 FrameworkElement bar = (FrameworkElement)target; 250 double scaleX = (double)args.NewValue; 251 252 if (!double.IsNaN(scaleX)) 253 { 254 ScaleTransform transform = (ScaleTransform)bar.RenderTransform; 255 transform.ScaleX = scaleX; 256 } 257 } 258 259 private static void BarScaleTransformYChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 260 { 261 FrameworkElement bar = (FrameworkElement)target; 262 double scaleY = (double)args.NewValue; 263 264 if (!double.IsNaN(scaleY)) 265 { 266 ScaleTransform transform = (ScaleTransform)bar.RenderTransform; 267 transform.ScaleY = (double)args.NewValue; 268 } 269 } 270 271 private static void SliceScaleTransformXYChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 272 { 273 Path slice = (Path)target; 274 double scale = (double)args.NewValue; 275 276 if (!double.IsNaN(scale)) 277 { 278 ScaleTransform transform = (ScaleTransform)slice.RenderTransform; 279 transform.ScaleX = scale; 280 transform.ScaleY = scale; 281 } 282 } 283 284 private static void SeriesTranslateTransformYChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) 285 { 286 ChartSeries series = (ChartSeries)target; 287 double transformY = (double)args.NewValue; 288 289 if (!double.IsNaN(transformY)) 290 { 291 TranslateTransform transform = (TranslateTransform)series.RenderTransform; 292 transform.Y = (double)args.NewValue; 293 } 294 } 295 296 private static void ChartSeries_Loaded(object sender, RoutedEventArgs e) 297 { 298 RunOrDispatchAnimations((ChartSeries)sender); 299 } 300 301 private static void ChartSeries_DataBindingComplete(object sender, EventArgs e) 302 { 303 DispatchRunSeriesAnimations((ChartSeries)sender); 304 } 305 306 private static void RunOrDispatchAnimations(ChartSeries series) 307 { 308 bool started = TryRunSeriesAnimation(series); 309 if (!started) 310 { 311 DispatchRunSeriesAnimations(series); 312 } 313 } 314 315 private static void DispatchRunSeriesAnimations(ChartSeries series) 316 { 317 series.Dispatcher.BeginInvoke((Action)(() => TryRunSeriesAnimation(series))); 318 } 319 320 private static bool HasDataPointsInPlotRange(ChartSeries series) 321 { 322 IList<DataPoint> dataPoints = GetDataPoints(series); 323 324 foreach (DataPoint dp in dataPoints) 325 { 326 if (dp.IsInPlotRange) 327 { 328 return true; 329 } 330 } 331 332 return false; 333 } 334 335 private static bool TryRunSeriesAnimation(ChartSeries series) 336 { 337 if (!HasDataPointsInPlotRange(series)) 338 { 339 return false; 340 } 341 342 int count = (int)series.GetValue(ChartAnimationUtilities.RunningAnimationsCountProperty); 343 if (count > 0) 344 { 345 return false; 346 } 347 348 bool started = false; 349 350 CartesianSeries cartesianSeries = series as CartesianSeries; 351 if (cartesianSeries != null) 352 { 353 CartesianAnimation animation = GetCartesianAnimation(cartesianSeries); 354 if (animation == CartesianAnimation.Drop || animation == CartesianAnimation.DropWithDelay) 355 { 356 bool useDelay = animation == CartesianAnimation.DropWithDelay; 357 started |= TryRunDropAnimtation(cartesianSeries, useDelay); 358 } 359 if (animation == CartesianAnimation.Rise || animation == CartesianAnimation.RiseWithDelay) 360 { 361 bool useDelay = animation == CartesianAnimation.RiseWithDelay; 362 started |= TryRunRiseAnimtation(cartesianSeries, useDelay); 363 } 364 if (animation == CartesianAnimation.Stretch) 365 { 366 started |= TryRunStretchAnimtation(cartesianSeries); 367 } 368 if (animation == CartesianAnimation.StackedBars) 369 { 370 started |= TryRunStackedBarsAnimtation(cartesianSeries); 371 } 372 } 373 374 PieSeries pieSeries = series as PieSeries; 375 if (pieSeries != null) 376 { 377 PieAnimation animation = GetPieAnimation(pieSeries); 378 if (animation.HasFlag(PieAnimation.RadiusFactor)) 379 { 380 started |= TryRunRadiusFactorAnimtation(pieSeries); 381 } 382 if (animation.HasFlag(PieAnimation.Slice)) 383 { 384 started |= TryRunSliceAnimtation(pieSeries); 385 } 386 if (animation.HasFlag(PieAnimation.StartAngle)) 387 { 388 started |= TryRunStartAngleAnimtation(pieSeries); 389 } 390 if (animation.HasFlag(PieAnimation.SweepAngle)) 391 { 392 started |= TryRunSweepAngleAnimtation(pieSeries); 393 } 394 } 395 396 return started; 397 } 398 399 private static bool TryRunRadiusFactorAnimtation(PieSeries series) 400 { 401 DoubleAnimation animation = new DoubleAnimation(); 402 animation.From = 0.0; 403 animation.Duration = AnimationDuration; 404 animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut, }; 405 406 Storyboard.SetTargetProperty(animation, new PropertyPath(PieSeries.RadiusFactorProperty)); 407 Storyboard.SetTarget(animation, series); 408 Storyboard storyboard = new Storyboard(); 409 storyboard.Children.Add(animation); 410 411 return Run(storyboard, series); 412 } 413 414 private static bool TryRunSliceAnimtation(PieSeries series) 415 { 416 Canvas renderSurface = Telerik.Windows.Controls.ChildrenOfTypeExtensions.FindChildByType<Canvas>(series); 417 List<Path> slices = new List<Path>(); 418 foreach (UIElement uiElement in renderSurface.Children) 419 { 420 Path slice = uiElement as Path; 421 if (slice != null && slice.DataContext is PieDataPoint) 422 { 423 slices.Add(slice); 424 } 425 } 426 427 Storyboard storyboard = new Storyboard(); 428 Point center = new Point(series.Chart.ActualWidth / 2, series.Chart.ActualHeight / 2); 429 TimeSpan? beginTime = null; 430 for (int i = 0; i < slices.Count; i++) 431 { 432 var animation = BuildSliceAnimation(slices[i], beginTime, PieSliceAnimationDuration, center, series); 433 storyboard.Children.Add(animation); 434 beginTime = new TimeSpan(0, 0, 0, 0, PieDelayInMilliseconds * (i + 1) / slices.Count); 435 } 436 437 bool showLabels = series.ShowLabels; 438 series.ShowLabels = false; 439 Action completed = () => series.ShowLabels = showLabels; 440 bool started = Run(storyboard, series, completed); 441 if (!started) 442 { 443 completed(); 444 } 445 return started; 446 } 447 448 private static bool TryRunStartAngleAnimtation(PieSeries series) 449 { 450 double startAngle = (double)series.GetAnimationBaseValue(ChartAnimationUtilities.StartAngleProperty); 451 if (double.IsNaN(startAngle)) 452 { 453 series.SetValue(ChartAnimationUtilities.StartAngleProperty, series.AngleRange.StartAngle); 454 startAngle = series.AngleRange.SweepAngle; 455 } 456 457 DoubleAnimation animation = new DoubleAnimation(); 458 animation.From = startAngle - 90; 459 animation.To = startAngle; 460 animation.Duration = AnimationDuration; 461 animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut, }; 462 463 Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.StartAngleProperty)); 464 Storyboard.SetTarget(animation, series); 465 Storyboard storyboard = new Storyboard(); 466 storyboard.Children.Add(animation); 467 return Run(storyboard, series); 468 } 469 470 private static bool TryRunSweepAngleAnimtation(PieSeries series) 471 { 472 double sweepAngle = (double)series.GetAnimationBaseValue(ChartAnimationUtilities.SweepAngleProperty); 473 if (double.IsNaN(sweepAngle)) 474 { 475 series.SetValue(ChartAnimationUtilities.SweepAngleProperty, series.AngleRange.SweepAngle); 476 sweepAngle = series.AngleRange.SweepAngle; 477 } 478 479 DoubleAnimation animation = new DoubleAnimation(); 480 animation.From = 0.0; 481 animation.To = sweepAngle; 482 animation.Duration = AnimationDuration; 483 animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut, }; 484 485 Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.SweepAngleProperty)); 486 Storyboard.SetTarget(animation, series); 487 Storyboard storyboard = new Storyboard(); 488 storyboard.Children.Add(animation); 489 return Run(storyboard, series); 490 } 491 492 private static bool TryRunRiseAnimtation(CartesianSeries series, bool useDelay) 493 { 494 RadRect plotAreClip = series.Chart.PlotAreaClip; 495 496 bool isHorizontalBar = !IsSeriesHorizontal(series); 497 bool isInverse = IsNumericalAxisInverse(series); 498 499 double centerX = 0; 500 double centerY = 0; 501 if (isHorizontalBar) 502 { 503 centerX = isInverse ? plotAreClip.Right : plotAreClip.X; 504 } 505 else 506 { 507 centerY = isInverse ? plotAreClip.Y : plotAreClip.Bottom; 508 } 509 510 var scaleTransform = new ScaleTransform(); 511 scaleTransform.ScaleX = isHorizontalBar ? 0 : 1; 512 scaleTransform.ScaleY = isHorizontalBar ? 1 : 0; 513 scaleTransform.CenterX = centerX; 514 scaleTransform.CenterY = centerY; 515 516 series.RenderTransform = scaleTransform; 517 518 TimeSpan? beginTime = useDelay ? CalculateBeginTime(series) : null; 519 520 DoubleAnimation animation = new DoubleAnimation(); 521 animation.From = 0; 522 animation.To = 1; 523 animation.Duration = AnimationDuration; 524 if (beginTime != null) 525 { 526 animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut, }; 527 } 528 else 529 { 530 animation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut, }; 531 } 532 533 Storyboard.SetTarget(animation, series); 534 if (isHorizontalBar) 535 { 536 Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.SeriesScaleTransformXProperty)); 537 } 538 else 539 { 540 Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.SeriesScaleTransformYProperty)); 541 } 542 543 Storyboard storyboard = new Storyboard(); 544 storyboard.Children.Add(animation); 545 if (beginTime != null) 546 { 547 storyboard.BeginTime = beginTime; 548 } 549 return Run(storyboard, series); 550 } 551 552 private static bool TryRunStretchAnimtation(CartesianSeries series) 553 { 554 RadRect plotAreClip = series.Chart.PlotAreaClip; 555 556 bool isHorizontal = IsSeriesHorizontal(series); 557 ScaleTransform transform = new ScaleTransform(); 558 transform.ScaleX = isHorizontal ? 1 : 0; 559 transform.ScaleY = isHorizontal ? 0 : 1; 560 transform.CenterX = isHorizontal ? 0 : CalculateHorizontalSeriesMiddle(series); 561 transform.CenterY = isHorizontal ? CalculateVerticalSeriesMiddle(series) : 0; 562 563 if (!IsValidNumber(transform.CenterX) || !IsValidNumber(transform.CenterY)) 564 { 565 return false; 566 } 567 568 series.RenderTransform = transform; 569 570 DoubleAnimation animation = new DoubleAnimation(); 571 animation.From = 0; 572 animation.To = 1; 573 animation.Duration = AnimationDuration; 574 animation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut, }; 575 DependencyProperty prop = isHorizontal ? ChartAnimationUtilities.SeriesScaleTransformYProperty : ChartAnimationUtilities.SeriesScaleTransformXProperty; 576 Storyboard.SetTargetProperty(animation, new PropertyPath(prop)); 577 Storyboard.SetTarget(animation, series); 578 579 Storyboard storyboard = new Storyboard(); 580 storyboard.Children.Add(animation); 581 return Run(storyboard, series); 582 } 583 584 private static bool TryRunDropAnimtation(CartesianSeries series, bool useDelay) 585 { 586 IList<DataPoint> dataPoints = GetDataPoints(series); 587 588 bool isInverse = IsNumericalAxisInverse(series); 589 590 double offsetY = isInverse ? double.PositiveInfinity : double.NegativeInfinity; 591 foreach (DataPoint dp in dataPoints) 592 { 593 if (isInverse && dp.IsInPlotRange && dp.LayoutSlot.Center.Y < offsetY) 594 { 595 offsetY = dp.LayoutSlot.Center.Y; 596 } 597 else if (!isInverse && dp.IsInPlotRange && offsetY < dp.LayoutSlot.Center.Y) 598 { 599 offsetY = dp.LayoutSlot.Center.Y; 600 } 601 } 602 603 if (!IsValidNumber(offsetY)) 604 { 605 return false; 606 } 607 608 RadRect plotAreClip = series.Chart.PlotAreaClip; 609 offsetY = (isInverse ? 1 : -1) * (plotAreClip.Height / 2); 610 TranslateTransform transform = new TranslateTransform(); 611 transform.Y = offsetY; 612 series.RenderTransform = transform; 613 614 TimeSpan? beginTime = useDelay ? CalculateBeginTime(series) : null; 615 616 series.Opacity = 0; 617 618 DoubleAnimation transformAnimation = new DoubleAnimation(); 619 transformAnimation.From = offsetY; 620 transformAnimation.To = 0; 621 transformAnimation.Duration = AnimationDuration; 622 transformAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut, }; 623 Storyboard.SetTargetProperty(transformAnimation, new PropertyPath(ChartAnimationUtilities.SeriesTranslateTransformYProperty)); 624 Storyboard.SetTarget(transformAnimation, series); 625 626 DoubleAnimation opacityAnimation = new DoubleAnimation(); 627 opacityAnimation.From = 0; 628 opacityAnimation.To = 1; 629 opacityAnimation.Duration = AnimationDuration; 630 opacityAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut, }; 631 Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath(ChartSeries.OpacityProperty)); 632 Storyboard.SetTarget(opacityAnimation, series); 633 634 Storyboard storyboard = new Storyboard(); 635 storyboard.Children.Add(transformAnimation); 636 storyboard.Children.Add(opacityAnimation); 637 638 if (beginTime != null) 639 { 640 storyboard.BeginTime = beginTime; 641 } 642 643 Action completed = () => series.Opacity = 1; 644 bool started = Run(storyboard, series, completed); 645 if (!started) 646 { 647 completed(); 648 } 649 return started; 650 } 651 652 private static bool TryRunStackedBarsAnimtation(CartesianSeries series) 653 { 654 Canvas renderSurface = Telerik.Windows.Controls.ChildrenOfTypeExtensions.FindChildByType<Canvas>(series); 655 List<FrameworkElement> bars = new List<FrameworkElement>(); 656 foreach (FrameworkElement uiElement in renderSurface.Children) 657 { 658 Border bar = uiElement as Border; 659 ContentPresenter cp = uiElement as ContentPresenter; 660 if ((bar != null && (bar.DataContext is CategoricalDataPoint)) || 661 (cp != null && cp.Content is CategoricalDataPoint)) 662 { 663 bars.Add(uiElement); 664 } 665 } 666 667 Storyboard storyboard = new Storyboard(); 668 RadCartesianChart chart = (RadCartesianChart)series.Chart; 669 int initialDelay = (int)(BarAnimationDuration.TimeSpan.Milliseconds * chart.Series.IndexOf(series)); 670 TimeSpan? beginTime = TimeSpan.FromMilliseconds(initialDelay); 671 for (int i = 0; i < bars.Count; i++) 672 { 673 var animation = BuildStackedBarAnimation(bars[i], beginTime, BarAnimationDuration, series); 674 storyboard.Children.Add(animation); 675 beginTime = new TimeSpan(0, 0, 0, 0, initialDelay + (BarDelayInMilliseconds * (i + 1))); 676 } 677 678 return Run(storyboard, series); 679 } 680 681 private static bool Run(Storyboard storyboard, ChartSeries series, Action completed = null) 682 { 683 if (storyboard.Children.Count == 0) 684 { 685 return false; 686 } 687 688 storyboard.Completed += (s, e) => 689 { 690 storyboard.Stop(); 691 692 lock (locker) 693 { 694 int count = (int)series.GetValue(ChartAnimationUtilities.RunningAnimationsCountProperty); 695 count--; 696 series.SetValue(ChartAnimationUtilities.RunningAnimationsCountProperty, count); 697 } 698 699 if (completed != null) 700 { 701 completed(); 702 } 703 }; 704 705 storyboard.Begin(); 706 lock (locker) 707 { 708 int count = (int)series.GetValue(ChartAnimationUtilities.RunningAnimationsCountProperty); 709 count++; 710 series.SetValue(ChartAnimationUtilities.RunningAnimationsCountProperty, count); 711 } 712 713 return true; 714 } 715 716 private static DoubleAnimation BuildSliceAnimation(Path path, TimeSpan? beginTime, Duration duration, Point center, PieSeries series) 717 { 718 var scaleTransform = new ScaleTransform(); 719 scaleTransform.ScaleX = 0; 720 scaleTransform.ScaleY = 0; 721 scaleTransform.CenterX = center.X; 722 scaleTransform.CenterY = center.Y; 723 724 path.RenderTransform = scaleTransform; 725 726 DoubleAnimation animation = new DoubleAnimation(); 727 animation.From = 0; 728 animation.To = 1; 729 animation.Duration = duration; 730 animation.EasingFunction = new BackEase() { EasingMode = EasingMode.EaseOut, Amplitude = 0.4 }; 731 if (beginTime != null) 732 { 733 animation.BeginTime = beginTime; 734 } 735 736 Storyboard.SetTarget(animation, path); 737 Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.SliceScaleTransformXYProperty)); 738 return animation; 739 } 740 741 private static DoubleAnimation BuildStackedBarAnimation(FrameworkElement bar, TimeSpan? beginTime, Duration duration, CartesianSeries series) 742 { 743 bool isHorizontalBar = !IsSeriesHorizontal(series); 744 bool isInverse = IsNumericalAxisInverse(series); 745 746 double centerX = 0; 747 double centerY = 0; 748 DataPoint dp = bar.DataContext as DataPoint; 749 if (dp == null) 750 { 751 dp = (DataPoint)((bar as ContentPresenter).Content); 752 } 753 754 if (isHorizontalBar) 755 { 756 centerX = isInverse ? dp.LayoutSlot.Width : 0; 757 } 758 else 759 { 760 centerY = isInverse ? 0 : dp.LayoutSlot.Height; 761 } 762 763 var scaleTransform = new ScaleTransform(); 764 scaleTransform.ScaleX = isHorizontalBar ? 0 : 1; 765 scaleTransform.ScaleY = isHorizontalBar ? 1 : 0; 766 scaleTransform.CenterX = centerX; 767 scaleTransform.CenterY = centerY; 768 769 bar.RenderTransform = scaleTransform; 770 771 DoubleAnimation animation = new DoubleAnimation(); 772 animation.From = 0; 773 animation.To = 1; 774 animation.Duration = duration; 775 if (beginTime != null) 776 { 777 animation.BeginTime = beginTime; 778 } 779 780 Storyboard.SetTarget(animation, bar); 781 if (isHorizontalBar) 782 { 783 Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.BarScaleTransformXProperty)); 784 } 785 else 786 { 787 Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.BarScaleTransformYProperty)); 788 } 789 790 return animation; 791 } 792 793 private static TimeSpan? CalculateBeginTime(CartesianSeries series) 794 { 795 RadCartesianChart chart = (RadCartesianChart)series.Chart; 796 797 if (chart.Series.Count == 1) 798 { 799 return null; 800 } 801 802 int delay = DelayInMilliseconds * chart.Series.IndexOf(series) / chart.Series.Count; 803 return new TimeSpan(0, 0, 0, 0, delay); 804 } 805 806 private static IList<DataPoint> GetDataPoints(ChartSeries series) 807 { 808 PieSeries pieSeries = series as PieSeries; 809 if (pieSeries != null) 810 { 811 return (IList<DataPoint>)pieSeries.DataPoints; 812 } 813 814 CategoricalSeries categoricalSeries = series as CategoricalSeries; 815 if (categoricalSeries != null) 816 { 817 return categoricalSeries.DataPoints; 818 } 819 820 RangeSeries rangeSeries = series as RangeSeries; 821 if (rangeSeries != null) 822 { 823 return rangeSeries.DataPoints; 824 } 825 826 OhlcSeries ohlcSeries = series as OhlcSeries; 827 if (ohlcSeries != null) 828 { 829 return ohlcSeries.DataPoints; 830 } 831 832 ScatterPointSeries scatterPointSeries = (ScatterPointSeries)series; 833 return scatterPointSeries.DataPoints; 834 } 835 836 private static bool IsNumericalAxisInverse(CartesianSeries series) 837 { 838 NumericalAxis axis = series.VerticalAxis as NumericalAxis; 839 if (axis != null) 840 { 841 return axis.IsInverse; 842 } 843 844 axis = ((RadCartesianChart)series.Chart).VerticalAxis as NumericalAxis; 845 if (axis != null) 846 { 847 return axis.IsInverse; 848 } 849 850 axis = series.HorizontalAxis as NumericalAxis; 851 if (axis != null) 852 { 853 return axis.IsInverse; 854 } 855 856 axis = ((RadCartesianChart)series.Chart).HorizontalAxis as NumericalAxis; 857 if (axis != null) 858 { 859 return axis.IsInverse; 860 } 861 862 return false; 863 } 864 865 private static bool IsSeriesHorizontal(CartesianSeries series) 866 { 867 NumericalAxis axis = series.VerticalAxis as NumericalAxis; 868 if (axis != null) 869 { 870 return true; 871 } 872 873 axis = ((RadCartesianChart)series.Chart).VerticalAxis as NumericalAxis; 874 if (axis != null) 875 { 876 return true; 877 } 878 879 return false; 880 } 881 882 private static double CalculateHorizontalSeriesMiddle(CartesianSeries series) 883 { 884 return CalculateSeriesMiddle(series, dp => dp.LayoutSlot.Center.X); 885 } 886 887 private static double CalculateVerticalSeriesMiddle(CartesianSeries series) 888 { 889 return CalculateSeriesMiddle(series, dp => dp.LayoutSlot.Center.Y); 890 } 891 892 private static double CalculateSeriesMiddle(CartesianSeries series, Func<DataPoint, double> position) 893 { 894 double min = double.PositiveInfinity; 895 double max = double.NegativeInfinity; 896 897 IList<DataPoint> dataPoints = GetDataPoints(series); 898 foreach (DataPoint dp in dataPoints) 899 { 900 if (dp.IsInPlotRange) 901 { 902 double pos = position(dp); 903 if (pos < min) 904 { 905 min = pos; 906 } 907 if (max < pos) 908 { 909 max = pos; 910 } 911 } 912 } 913 914 return (min + max) / 2; 915 } 916 917 private static bool IsValidNumber(double value) 918 { 919 return double.MinValue <= value && value <= double.MaxValue; 920 } 921 } 922 }

浙公网安备 33010602011771号