using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using Telerik.Charting; using Telerik.Windows.Controls; using Telerik.Windows.Controls.ChartView; namespace Animations { public static class ChartAnimationUtilities { public static readonly DependencyProperty CartesianAnimationProperty = DependencyProperty.RegisterAttached( "CartesianAnimation", typeof(CartesianAnimation), typeof(ChartAnimationUtilities), new PropertyMetadata(CartesianAnimation.None, CartesianAnimationChanged)); public static readonly DependencyProperty PieAnimationProperty = DependencyProperty.RegisterAttached( "PieAnimation", typeof(PieAnimation), typeof(ChartAnimationUtilities), new PropertyMetadata(PieAnimation.None, PieAnimationChanged)); private static readonly DependencyProperty RunningAnimationsCountProperty = DependencyProperty.RegisterAttached( "RunningAnimationsCount", typeof(int), typeof(ChartAnimationUtilities), new PropertyMetadata(0)); private static readonly DependencyProperty StartAngleProperty = DependencyProperty.RegisterAttached( "StartAngle", typeof(double), typeof(ChartAnimationUtilities), new PropertyMetadata(double.NaN, StartAngleChanged)); private static readonly DependencyProperty SweepAngleProperty = DependencyProperty.RegisterAttached( "SweepAngle", typeof(double), typeof(ChartAnimationUtilities), new PropertyMetadata(double.NaN, SweepAngleChanged)); private static readonly DependencyProperty SeriesScaleTransformXProperty = DependencyProperty.RegisterAttached( "SeriesScaleTransformX", typeof(double), typeof(ChartAnimationUtilities), new PropertyMetadata(double.NaN, SeriesScaleTransformXChanged)); private static readonly DependencyProperty SeriesScaleTransformYProperty = DependencyProperty.RegisterAttached( "SeriesScaleTransformY", typeof(double), typeof(ChartAnimationUtilities), new PropertyMetadata(double.NaN, SeriesScaleTransformYChanged)); private static readonly DependencyProperty BarScaleTransformXProperty = DependencyProperty.RegisterAttached( "BarScaleTransformX", typeof(double), typeof(ChartAnimationUtilities), new PropertyMetadata(double.NaN, BarScaleTransformXChanged)); private static readonly DependencyProperty BarScaleTransformYProperty = DependencyProperty.RegisterAttached( "BarScaleTransformY", typeof(double), typeof(ChartAnimationUtilities), new PropertyMetadata(double.NaN, BarScaleTransformYChanged)); private static readonly DependencyProperty SliceScaleTransformXYProperty = DependencyProperty.RegisterAttached( "SliceScaleTransformXY", typeof(double), typeof(ChartAnimationUtilities), new PropertyMetadata(double.NaN, SliceScaleTransformXYChanged)); private static readonly DependencyProperty SeriesTranslateTransformYProperty = DependencyProperty.RegisterAttached( "SeriesTranslateTransformY", typeof(double), typeof(ChartAnimationUtilities), new PropertyMetadata(double.NaN, SeriesTranslateTransformYChanged)); private const int DelayInMilliseconds = 1000; private const int PieDelayInMilliseconds = 300; private const int BarDelayInMilliseconds = 200; private static Duration AnimationDuration = new Duration(TimeSpan.FromMilliseconds(1500)); private static Duration PieSliceAnimationDuration = new Duration(TimeSpan.FromMilliseconds(500)); private static Duration BarAnimationDuration = new Duration(TimeSpan.FromMilliseconds(500)); private static object locker = new object(); public static CartesianAnimation GetCartesianAnimation(DependencyObject obj) { return (CartesianAnimation)obj.GetValue(CartesianAnimationProperty); } public static void SetCartesianAnimation(DependencyObject obj, CartesianAnimation value) { obj.SetValue(CartesianAnimationProperty, value); } public static PieAnimation GetPieAnimation(DependencyObject obj) { return (PieAnimation)obj.GetValue(PieAnimationProperty); } public static void SetPieAnimation(DependencyObject obj, PieAnimation value) { obj.SetValue(PieAnimationProperty, value); } public static void DispatchRunAnimations(RadChartBase chart) { IEnumerable series = null; RadCartesianChart cartesianChart = chart as RadCartesianChart; if (cartesianChart != null) { series = cartesianChart.Series; } RadPieChart pieChart = chart as RadPieChart; if (pieChart != null) { series = pieChart.Series; } if (series.Any(s => (int)s.GetValue(ChartAnimationUtilities.RunningAnimationsCountProperty) > 0)) { return; } foreach (ChartSeries s in series) { DispatchRunSeriesAnimations(s); } } private static void CartesianAnimationChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { CartesianSeries series = (CartesianSeries)target; if ((CartesianAnimation)args.NewValue == CartesianAnimation.None) { series.Loaded -= ChartSeries_Loaded; series.DataBindingComplete -= ChartSeries_DataBindingComplete; } if ((CartesianAnimation)args.OldValue == CartesianAnimation.None) { series.Loaded += ChartSeries_Loaded; series.DataBindingComplete += ChartSeries_DataBindingComplete; } } private static void PieAnimationChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { PieSeries series = (PieSeries)target; if ((PieAnimation)args.NewValue == PieAnimation.None) { series.Loaded -= ChartSeries_Loaded; series.DataBindingComplete -= ChartSeries_DataBindingComplete; } if ((PieAnimation)args.OldValue == PieAnimation.None) { series.Loaded += ChartSeries_Loaded; series.DataBindingComplete += ChartSeries_DataBindingComplete; } } private static void StartAngleChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { PieSeries series = (PieSeries)target; double startAngle = (double)args.NewValue; if (double.IsNaN(startAngle) || series.AngleRange.StartAngle == startAngle) { return; } series.AngleRange = new AngleRange(startAngle, series.AngleRange.SweepAngle, series.AngleRange.SweepDirection); } private static void SweepAngleChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { PieSeries series = (PieSeries)target; double sweepAngle = (double)args.NewValue; if (double.IsNaN(sweepAngle) || series.AngleRange.SweepAngle == sweepAngle) { return; } series.AngleRange = new AngleRange(series.AngleRange.StartAngle, sweepAngle, series.AngleRange.SweepDirection); } private static void SeriesScaleTransformXChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { ChartSeries series = (ChartSeries)target; double scaleX = (double)args.NewValue; if (!double.IsNaN(scaleX)) { ScaleTransform transform = (ScaleTransform)series.RenderTransform; transform.ScaleX = scaleX; } } private static void SeriesScaleTransformYChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { ChartSeries series = (ChartSeries)target; double scaleY = (double)args.NewValue; if (!double.IsNaN(scaleY)) { ScaleTransform transform = (ScaleTransform)series.RenderTransform; transform.ScaleY = (double)args.NewValue; } } private static void BarScaleTransformXChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { FrameworkElement bar = (FrameworkElement)target; double scaleX = (double)args.NewValue; if (!double.IsNaN(scaleX)) { ScaleTransform transform = (ScaleTransform)bar.RenderTransform; transform.ScaleX = scaleX; } } private static void BarScaleTransformYChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { FrameworkElement bar = (FrameworkElement)target; double scaleY = (double)args.NewValue; if (!double.IsNaN(scaleY)) { ScaleTransform transform = (ScaleTransform)bar.RenderTransform; transform.ScaleY = (double)args.NewValue; } } private static void SliceScaleTransformXYChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { Path slice = (Path)target; double scale = (double)args.NewValue; if (!double.IsNaN(scale)) { ScaleTransform transform = (ScaleTransform)slice.RenderTransform; transform.ScaleX = scale; transform.ScaleY = scale; } } private static void SeriesTranslateTransformYChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) { ChartSeries series = (ChartSeries)target; double transformY = (double)args.NewValue; if (!double.IsNaN(transformY)) { TranslateTransform transform = (TranslateTransform)series.RenderTransform; transform.Y = (double)args.NewValue; } } private static void ChartSeries_Loaded(object sender, RoutedEventArgs e) { RunOrDispatchAnimations((ChartSeries)sender); } private static void ChartSeries_DataBindingComplete(object sender, EventArgs e) { DispatchRunSeriesAnimations((ChartSeries)sender); } private static void RunOrDispatchAnimations(ChartSeries series) { bool started = TryRunSeriesAnimation(series); if (!started) { DispatchRunSeriesAnimations(series); } } private static void DispatchRunSeriesAnimations(ChartSeries series) { series.Dispatcher.BeginInvoke((Action)(() => TryRunSeriesAnimation(series))); } private static bool HasDataPointsInPlotRange(ChartSeries series) { IList dataPoints = GetDataPoints(series); foreach (DataPoint dp in dataPoints) { if (dp.IsInPlotRange) { return true; } } return false; } private static bool TryRunSeriesAnimation(ChartSeries series) { if (!HasDataPointsInPlotRange(series)) { return false; } int count = (int)series.GetValue(ChartAnimationUtilities.RunningAnimationsCountProperty); if (count > 0) { return false; } bool started = false; CartesianSeries cartesianSeries = series as CartesianSeries; if (cartesianSeries != null) { CartesianAnimation animation = GetCartesianAnimation(cartesianSeries); if (animation == CartesianAnimation.Drop || animation == CartesianAnimation.DropWithDelay) { bool useDelay = animation == CartesianAnimation.DropWithDelay; started |= TryRunDropAnimtation(cartesianSeries, useDelay); } if (animation == CartesianAnimation.Rise || animation == CartesianAnimation.RiseWithDelay) { bool useDelay = animation == CartesianAnimation.RiseWithDelay; started |= TryRunRiseAnimtation(cartesianSeries, useDelay); } if (animation == CartesianAnimation.Stretch) { started |= TryRunStretchAnimtation(cartesianSeries); } if (animation == CartesianAnimation.StackedBars) { started |= TryRunStackedBarsAnimtation(cartesianSeries); } } PieSeries pieSeries = series as PieSeries; if (pieSeries != null) { PieAnimation animation = GetPieAnimation(pieSeries); if (animation.HasFlag(PieAnimation.RadiusFactor)) { started |= TryRunRadiusFactorAnimtation(pieSeries); } if (animation.HasFlag(PieAnimation.Slice)) { started |= TryRunSliceAnimtation(pieSeries); } if (animation.HasFlag(PieAnimation.StartAngle)) { started |= TryRunStartAngleAnimtation(pieSeries); } if (animation.HasFlag(PieAnimation.SweepAngle)) { started |= TryRunSweepAngleAnimtation(pieSeries); } } return started; } private static bool TryRunRadiusFactorAnimtation(PieSeries series) { DoubleAnimation animation = new DoubleAnimation(); animation.From = 0.0; animation.Duration = AnimationDuration; animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut, }; Storyboard.SetTargetProperty(animation, new PropertyPath(PieSeries.RadiusFactorProperty)); Storyboard.SetTarget(animation, series); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(animation); return Run(storyboard, series); } private static bool TryRunSliceAnimtation(PieSeries series) { Canvas renderSurface = Telerik.Windows.Controls.ChildrenOfTypeExtensions.FindChildByType(series); List slices = new List(); foreach (UIElement uiElement in renderSurface.Children) { Path slice = uiElement as Path; if (slice != null && slice.DataContext is PieDataPoint) { slices.Add(slice); } } Storyboard storyboard = new Storyboard(); Point center = new Point(series.Chart.ActualWidth / 2, series.Chart.ActualHeight / 2); TimeSpan? beginTime = null; for (int i = 0; i < slices.Count; i++) { var animation = BuildSliceAnimation(slices[i], beginTime, PieSliceAnimationDuration, center, series); storyboard.Children.Add(animation); beginTime = new TimeSpan(0, 0, 0, 0, PieDelayInMilliseconds * (i + 1) / slices.Count); } bool showLabels = series.ShowLabels; series.ShowLabels = false; Action completed = () => series.ShowLabels = showLabels; bool started = Run(storyboard, series, completed); if (!started) { completed(); } return started; } private static bool TryRunStartAngleAnimtation(PieSeries series) { double startAngle = (double)series.GetAnimationBaseValue(ChartAnimationUtilities.StartAngleProperty); if (double.IsNaN(startAngle)) { series.SetValue(ChartAnimationUtilities.StartAngleProperty, series.AngleRange.StartAngle); startAngle = series.AngleRange.SweepAngle; } DoubleAnimation animation = new DoubleAnimation(); animation.From = startAngle - 90; animation.To = startAngle; animation.Duration = AnimationDuration; animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut, }; Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.StartAngleProperty)); Storyboard.SetTarget(animation, series); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(animation); return Run(storyboard, series); } private static bool TryRunSweepAngleAnimtation(PieSeries series) { double sweepAngle = (double)series.GetAnimationBaseValue(ChartAnimationUtilities.SweepAngleProperty); if (double.IsNaN(sweepAngle)) { series.SetValue(ChartAnimationUtilities.SweepAngleProperty, series.AngleRange.SweepAngle); sweepAngle = series.AngleRange.SweepAngle; } DoubleAnimation animation = new DoubleAnimation(); animation.From = 0.0; animation.To = sweepAngle; animation.Duration = AnimationDuration; animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut, }; Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.SweepAngleProperty)); Storyboard.SetTarget(animation, series); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(animation); return Run(storyboard, series); } private static bool TryRunRiseAnimtation(CartesianSeries series, bool useDelay) { RadRect plotAreClip = series.Chart.PlotAreaClip; bool isHorizontalBar = !IsSeriesHorizontal(series); bool isInverse = IsNumericalAxisInverse(series); double centerX = 0; double centerY = 0; if (isHorizontalBar) { centerX = isInverse ? plotAreClip.Right : plotAreClip.X; } else { centerY = isInverse ? plotAreClip.Y : plotAreClip.Bottom; } var scaleTransform = new ScaleTransform(); scaleTransform.ScaleX = isHorizontalBar ? 0 : 1; scaleTransform.ScaleY = isHorizontalBar ? 1 : 0; scaleTransform.CenterX = centerX; scaleTransform.CenterY = centerY; series.RenderTransform = scaleTransform; TimeSpan? beginTime = useDelay ? CalculateBeginTime(series) : null; DoubleAnimation animation = new DoubleAnimation(); animation.From = 0; animation.To = 1; animation.Duration = AnimationDuration; if (beginTime != null) { animation.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut, }; } else { animation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut, }; } Storyboard.SetTarget(animation, series); if (isHorizontalBar) { Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.SeriesScaleTransformXProperty)); } else { Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.SeriesScaleTransformYProperty)); } Storyboard storyboard = new Storyboard(); storyboard.Children.Add(animation); if (beginTime != null) { storyboard.BeginTime = beginTime; } return Run(storyboard, series); } private static bool TryRunStretchAnimtation(CartesianSeries series) { RadRect plotAreClip = series.Chart.PlotAreaClip; bool isHorizontal = IsSeriesHorizontal(series); ScaleTransform transform = new ScaleTransform(); transform.ScaleX = isHorizontal ? 1 : 0; transform.ScaleY = isHorizontal ? 0 : 1; transform.CenterX = isHorizontal ? 0 : CalculateHorizontalSeriesMiddle(series); transform.CenterY = isHorizontal ? CalculateVerticalSeriesMiddle(series) : 0; if (!IsValidNumber(transform.CenterX) || !IsValidNumber(transform.CenterY)) { return false; } series.RenderTransform = transform; DoubleAnimation animation = new DoubleAnimation(); animation.From = 0; animation.To = 1; animation.Duration = AnimationDuration; animation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut, }; DependencyProperty prop = isHorizontal ? ChartAnimationUtilities.SeriesScaleTransformYProperty : ChartAnimationUtilities.SeriesScaleTransformXProperty; Storyboard.SetTargetProperty(animation, new PropertyPath(prop)); Storyboard.SetTarget(animation, series); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(animation); return Run(storyboard, series); } private static bool TryRunDropAnimtation(CartesianSeries series, bool useDelay) { IList dataPoints = GetDataPoints(series); bool isInverse = IsNumericalAxisInverse(series); double offsetY = isInverse ? double.PositiveInfinity : double.NegativeInfinity; foreach (DataPoint dp in dataPoints) { if (isInverse && dp.IsInPlotRange && dp.LayoutSlot.Center.Y < offsetY) { offsetY = dp.LayoutSlot.Center.Y; } else if (!isInverse && dp.IsInPlotRange && offsetY < dp.LayoutSlot.Center.Y) { offsetY = dp.LayoutSlot.Center.Y; } } if (!IsValidNumber(offsetY)) { return false; } RadRect plotAreClip = series.Chart.PlotAreaClip; offsetY = (isInverse ? 1 : -1) * (plotAreClip.Height / 2); TranslateTransform transform = new TranslateTransform(); transform.Y = offsetY; series.RenderTransform = transform; TimeSpan? beginTime = useDelay ? CalculateBeginTime(series) : null; series.Opacity = 0; DoubleAnimation transformAnimation = new DoubleAnimation(); transformAnimation.From = offsetY; transformAnimation.To = 0; transformAnimation.Duration = AnimationDuration; transformAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut, }; Storyboard.SetTargetProperty(transformAnimation, new PropertyPath(ChartAnimationUtilities.SeriesTranslateTransformYProperty)); Storyboard.SetTarget(transformAnimation, series); DoubleAnimation opacityAnimation = new DoubleAnimation(); opacityAnimation.From = 0; opacityAnimation.To = 1; opacityAnimation.Duration = AnimationDuration; opacityAnimation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut, }; Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath(ChartSeries.OpacityProperty)); Storyboard.SetTarget(opacityAnimation, series); Storyboard storyboard = new Storyboard(); storyboard.Children.Add(transformAnimation); storyboard.Children.Add(opacityAnimation); if (beginTime != null) { storyboard.BeginTime = beginTime; } Action completed = () => series.Opacity = 1; bool started = Run(storyboard, series, completed); if (!started) { completed(); } return started; } private static bool TryRunStackedBarsAnimtation(CartesianSeries series) { Canvas renderSurface = Telerik.Windows.Controls.ChildrenOfTypeExtensions.FindChildByType(series); List bars = new List(); foreach (FrameworkElement uiElement in renderSurface.Children) { Border bar = uiElement as Border; ContentPresenter cp = uiElement as ContentPresenter; if ((bar != null && (bar.DataContext is CategoricalDataPoint)) || (cp != null && cp.Content is CategoricalDataPoint)) { bars.Add(uiElement); } } Storyboard storyboard = new Storyboard(); RadCartesianChart chart = (RadCartesianChart)series.Chart; int initialDelay = (int)(BarAnimationDuration.TimeSpan.Milliseconds * chart.Series.IndexOf(series)); TimeSpan? beginTime = TimeSpan.FromMilliseconds(initialDelay); for (int i = 0; i < bars.Count; i++) { var animation = BuildStackedBarAnimation(bars[i], beginTime, BarAnimationDuration, series); storyboard.Children.Add(animation); beginTime = new TimeSpan(0, 0, 0, 0, initialDelay + (BarDelayInMilliseconds * (i + 1))); } return Run(storyboard, series); } private static bool Run(Storyboard storyboard, ChartSeries series, Action completed = null) { if (storyboard.Children.Count == 0) { return false; } storyboard.Completed += (s, e) => { storyboard.Stop(); lock (locker) { int count = (int)series.GetValue(ChartAnimationUtilities.RunningAnimationsCountProperty); count--; series.SetValue(ChartAnimationUtilities.RunningAnimationsCountProperty, count); } if (completed != null) { completed(); } }; storyboard.Begin(); lock (locker) { int count = (int)series.GetValue(ChartAnimationUtilities.RunningAnimationsCountProperty); count++; series.SetValue(ChartAnimationUtilities.RunningAnimationsCountProperty, count); } return true; } private static DoubleAnimation BuildSliceAnimation(Path path, TimeSpan? beginTime, Duration duration, Point center, PieSeries series) { var scaleTransform = new ScaleTransform(); scaleTransform.ScaleX = 0; scaleTransform.ScaleY = 0; scaleTransform.CenterX = center.X; scaleTransform.CenterY = center.Y; path.RenderTransform = scaleTransform; DoubleAnimation animation = new DoubleAnimation(); animation.From = 0; animation.To = 1; animation.Duration = duration; animation.EasingFunction = new BackEase() { EasingMode = EasingMode.EaseOut, Amplitude = 0.4 }; if (beginTime != null) { animation.BeginTime = beginTime; } Storyboard.SetTarget(animation, path); Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.SliceScaleTransformXYProperty)); return animation; } private static DoubleAnimation BuildStackedBarAnimation(FrameworkElement bar, TimeSpan? beginTime, Duration duration, CartesianSeries series) { bool isHorizontalBar = !IsSeriesHorizontal(series); bool isInverse = IsNumericalAxisInverse(series); double centerX = 0; double centerY = 0; DataPoint dp = bar.DataContext as DataPoint; if (dp == null) { dp = (DataPoint)((bar as ContentPresenter).Content); } if (isHorizontalBar) { centerX = isInverse ? dp.LayoutSlot.Width : 0; } else { centerY = isInverse ? 0 : dp.LayoutSlot.Height; } var scaleTransform = new ScaleTransform(); scaleTransform.ScaleX = isHorizontalBar ? 0 : 1; scaleTransform.ScaleY = isHorizontalBar ? 1 : 0; scaleTransform.CenterX = centerX; scaleTransform.CenterY = centerY; bar.RenderTransform = scaleTransform; DoubleAnimation animation = new DoubleAnimation(); animation.From = 0; animation.To = 1; animation.Duration = duration; if (beginTime != null) { animation.BeginTime = beginTime; } Storyboard.SetTarget(animation, bar); if (isHorizontalBar) { Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.BarScaleTransformXProperty)); } else { Storyboard.SetTargetProperty(animation, new PropertyPath(ChartAnimationUtilities.BarScaleTransformYProperty)); } return animation; } private static TimeSpan? CalculateBeginTime(CartesianSeries series) { RadCartesianChart chart = (RadCartesianChart)series.Chart; if (chart.Series.Count == 1) { return null; } int delay = DelayInMilliseconds * chart.Series.IndexOf(series) / chart.Series.Count; return new TimeSpan(0, 0, 0, 0, delay); } private static IList GetDataPoints(ChartSeries series) { PieSeries pieSeries = series as PieSeries; if (pieSeries != null) { return (IList)pieSeries.DataPoints; } CategoricalSeries categoricalSeries = series as CategoricalSeries; if (categoricalSeries != null) { return categoricalSeries.DataPoints; } RangeSeries rangeSeries = series as RangeSeries; if (rangeSeries != null) { return rangeSeries.DataPoints; } OhlcSeries ohlcSeries = series as OhlcSeries; if (ohlcSeries != null) { return ohlcSeries.DataPoints; } ScatterPointSeries scatterPointSeries = (ScatterPointSeries)series; return scatterPointSeries.DataPoints; } private static bool IsNumericalAxisInverse(CartesianSeries series) { NumericalAxis axis = series.VerticalAxis as NumericalAxis; if (axis != null) { return axis.IsInverse; } axis = ((RadCartesianChart)series.Chart).VerticalAxis as NumericalAxis; if (axis != null) { return axis.IsInverse; } axis = series.HorizontalAxis as NumericalAxis; if (axis != null) { return axis.IsInverse; } axis = ((RadCartesianChart)series.Chart).HorizontalAxis as NumericalAxis; if (axis != null) { return axis.IsInverse; } return false; } private static bool IsSeriesHorizontal(CartesianSeries series) { NumericalAxis axis = series.VerticalAxis as NumericalAxis; if (axis != null) { return true; } axis = ((RadCartesianChart)series.Chart).VerticalAxis as NumericalAxis; if (axis != null) { return true; } return false; } private static double CalculateHorizontalSeriesMiddle(CartesianSeries series) { return CalculateSeriesMiddle(series, dp => dp.LayoutSlot.Center.X); } private static double CalculateVerticalSeriesMiddle(CartesianSeries series) { return CalculateSeriesMiddle(series, dp => dp.LayoutSlot.Center.Y); } private static double CalculateSeriesMiddle(CartesianSeries series, Func position) { double min = double.PositiveInfinity; double max = double.NegativeInfinity; IList dataPoints = GetDataPoints(series); foreach (DataPoint dp in dataPoints) { if (dp.IsInPlotRange) { double pos = position(dp); if (pos < min) { min = pos; } if (max < pos) { max = pos; } } } return (min + max) / 2; } private static bool IsValidNumber(double value) { return double.MinValue <= value && value <= double.MaxValue; } } }