使用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>
XAML

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  }
View Code

注: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  }
View Code

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>
View Code

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  }
View Code

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>
View Code
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>
View Code

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  }
View Code

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>
View Code
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         }
View Code

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>
View Code

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>
View Code

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 }
View Code

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 }
View Code
posted @ 2021-12-31 17:39  If_I_Could_Tell_You  阅读(678)  评论(0)    收藏  举报