using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using Infragistics.Controls.Charts;
namespace Infragistics.Samples.Common.Frameworks
{
/// <summary>
/// xamDataChart コントロールでデータ ポイントのアニメーションを提供します
/// </summary>
public class MotionFrameworkManger : ObservableModel
{
public MotionFrameworkManger()
{
_transitionDuration = TimeSpan.FromMilliseconds(1000);
_transitionFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut };
this.AutosetAxisMargin = new Thickness(0, 0.5, 0, 0.5);
this.AutosetAxisRange = true;
this.DataUpdateTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1000) };
this.DataUpdateTimer.Tick += OnDataUpdateTimerTick;
}
#region Fields
protected DispatcherTimer DataUpdateTimer;
protected int ElementsMaxCount;
#endregion
#region Events
public delegate void CurrentDateChangedEventHandler(object sender, DateTimeChangedEventArgs e);
public event CurrentDateChangedEventHandler CurrentDateChanged;
protected virtual void OnCurrentDateChanged(DateTimeChangedEventArgs e)
{
if (CurrentDateChanged != null)
CurrentDateChanged(this, e);
}
public delegate void PlaybackStoppedEventHandler(object sender, EventArgs e);
public event PlaybackStoppedEventHandler PlaybackStopped;
protected virtual void OnPlaybackStopped(EventArgs e)
{
if (PlaybackStopped != null)
PlaybackStopped(this, e);
}
public delegate void PlaybackStartedEventHandler(object sender, EventArgs e);
public event PlaybackStartedEventHandler PlaybackStarted;
protected virtual void OnPlaybackStarted(EventArgs e)
{
if (PlaybackStarted != null)
PlaybackStarted(this, e);
}
public class DateTimeChangedEventArgs : EventArgs
{
public DateTime CurrentDate { get; set; }
}
#endregion
#region Event Handlers
private void OnDateTimeSliderValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!this.IsPlaying)
{
if (!this.IsInitialized && this.RequiredConditionsCheck())
{
this.InitializeDataChart();
this.IsInitialized = true;
}
this.CurrentElementShown = (int)e.NewValue;
if (this.CurrentElementShown < this.ElementsMaxCount)
this.RefreshSeries();
}
}
private void OnDataUpdateTimerTick(object sender, EventArgs e)
{
if (this.CurrentElementShown < this.ElementsMaxCount)
{
this.RefreshSeries();
this.CurrentElementShown++;
if (this.DateTimeSlider != null)
{
this.DateTimeSlider.Value = this.CurrentElementShown;
this.DateTimeSlider.Tag = this.CurrentDateTime.ToShortDateString();
}
}
else
{
this.Pause();
}
}
#endregion
#region Private Methods
private bool RequiredConditionsCheck()
{
bool ret = this.Chart != null;
ret = (ret && this.DataSources != null);
ret = (ret && this.XAxisName != null);
ret = (ret && this.YAxisName != null);
ret = (ret && this.SeriesXMemberPath != null);
ret = (ret && this.SeriesYMemberPath != null);
ret = (ret && this.SeriesRadiusMemberPath != null);
return ret;
}
private string GetSeriesName(int key, bool isUnique = false)
{
if (isUnique)
return string.Format("{0}|Series {0}", key);
return string.Format("Series{0}", key);
}
private int GetNumDigits(double max, double min)
{
double error = (max - min) / 100;
int numDigits = 0;
while (error < 1)
{
error *= 10;
++numDigits;
}
return numDigits;
}
private void InitializeDataChart()
{
if (this.RequiredConditionsCheck())
{
this.Chart.Series.Clear();
this.ElementsMaxCount = this.DataSources[0].Count;
this.CurrentElementShown = 0;
double minX = Double.PositiveInfinity;
double minY = Double.PositiveInfinity;
double maxX = Double.NegativeInfinity;
double maxY = Double.NegativeInfinity;
DateTime minDateTime = DateTime.MaxValue;
DateTime maxDateTime = DateTime.MinValue;
foreach (var item in this.DataSources)
{
this.InitializeSeries(this.GetSeriesName(item.Key), item.Key);
//自動設定が有効である場合、このシリーズのすべてのデータ ポイントはトラバースされ
//X および Y に沿って最小値/最大値を探します
#region Find Min/Max Values
if (this.AutosetAxisRange)
{
//item.value リストが datetime プロパティで並べ替えられていることを確認します
foreach (var seriesPoint in item.Value)
{
double xValue = (double)seriesPoint.GetType().GetProperty(this.SeriesXMemberPath).GetValue(seriesPoint, null);
double yValue = (double)seriesPoint.GetType().GetProperty(this.SeriesYMemberPath).GetValue(seriesPoint, null);
DateTime dateTimeValue = (DateTime)seriesPoint.GetType().GetProperty(this.SeriesTimeMemberPath).GetValue(seriesPoint, null);
if (xValue < minX) minX = xValue;
if (xValue > maxX) maxX = xValue;
if (yValue < minY) minY = yValue;
if (yValue > maxY) maxY = yValue;
if (dateTimeValue < minDateTime) minDateTime = dateTimeValue;
if (dateTimeValue > maxDateTime) maxDateTime = dateTimeValue;
}
}
#endregion
}
#region Set range of axes based on min/max values
if (this.AutosetAxisRange)
{
NumericXAxis xAxis = this.Chart.FindName(XAxisName) as NumericXAxis;
NumericYAxis yAxis = this.Chart.FindName(YAxisName) as NumericYAxis;
double xAxisMarginLeft = ((maxX - minX) * this.AutosetAxisMargin.Left) / 2;
double xAxisMarginRight = ((maxX - minX) * this.AutosetAxisMargin.Right) / 2;
if (xAxis != null)
{
xAxis.MinimumValue = System.Math.Round(((float)minX - (float)xAxisMarginLeft), GetNumDigits(maxX, minX));
xAxis.MaximumValue = System.Math.Round(((float)maxX + (float)xAxisMarginRight), GetNumDigits(maxX, minX));
}
double yAxisMarginTop = ((maxY - minY) * this.AutosetAxisMargin.Top) / 2;
double yAxisMarginBottom = ((maxY - minY) * this.AutosetAxisMargin.Bottom) / 2;
if (yAxis != null)
{
yAxis.MinimumValue = System.Math.Round(((float)minY - (float)yAxisMarginBottom), GetNumDigits(maxY, minY));
yAxis.MaximumValue = System.Math.Round(((float)maxY + (float)yAxisMarginTop), GetNumDigits(maxY, minY));
}
}
#endregion
if (this.DateTimeSlider != null)
{
this.MinDateTime = minDateTime;
this.MaxDateTime = maxDateTime;
this.DateTimeSlider.Minimum = 0;
this.DateTimeSlider.Maximum = this.ElementsMaxCount - 1;
}
}
}
private void InitializeSeries(string name, int dataOrder, IEnumerable dataSource = null)
{
BubbleSeries motionSeries = new BubbleSeries();
motionSeries.Name = this.GetSeriesName(dataOrder);
motionSeries.YMemberPath = this.SeriesYMemberPath;
motionSeries.XMemberPath = this.SeriesXMemberPath;
motionSeries.RadiusMemberPath = this.SeriesRadiusMemberPath;
motionSeries.TransitionDuration = this.TransitionDuration;
motionSeries.TransitionEasingFunction = this.TransitionFunction;
motionSeries.Title = name;
motionSeries.XAxis = this.Chart.FindName(XAxisName) as NumericXAxis;
motionSeries.YAxis = this.Chart.FindName(YAxisName) as NumericYAxis;
motionSeries.MarkerTemplate = this.MarkerTemplate;
if (this.ChartLegend != null) motionSeries.Legend = this.ChartLegend;
if (this.LegendItemTemplate != null) motionSeries.LegendItemTemplate = this.LegendItemTemplate;
// アニメーション化するために変更される単一のデータ ポイントを追加します
List<MotionDataPoint> newDataSource = new List<MotionDataPoint>();
newDataSource.Add(new MotionDataPoint { ValueX = 0, ValueY = 0, ValueR = 0 });
motionSeries.ItemsSource = newDataSource;
if (dataSource != null)
motionSeries.ItemsSource = dataSource;
ScatterSplineSeries trailSeries = CopySeries(motionSeries);
trailSeries.Thickness = 2;
motionSeries.Tag = trailSeries;
this.Chart.Series.Add(trailSeries);
this.Chart.Series.Add(motionSeries);
}
private ScatterSplineSeries CopySeries(BubbleSeries series)
{
ScatterSplineSeries trailSeries = new ScatterSplineSeries();
trailSeries.MarkerType = MarkerType.None;
trailSeries.YMemberPath = series.YMemberPath;
trailSeries.XMemberPath = series.XMemberPath;
trailSeries.TransitionDuration = series.TransitionDuration;
trailSeries.Title = series.Title;
trailSeries.XAxis = series.XAxis;
trailSeries.YAxis = series.YAxis;
MotionDataSource<DataPoint> ds = new MotionDataSource<DataPoint>();
trailSeries.ItemsSource = ds;
return trailSeries;
}
private void UpdateMotionSeries(Series series, DataPoint dataPoint)
{
((IList<MotionDataPoint>)series.ItemsSource)[0].ValueX = dataPoint.ValueX;
((IList<MotionDataPoint>)series.ItemsSource)[0].ValueY = dataPoint.ValueY;
((IList<MotionDataPoint>)series.ItemsSource)[0].ValueR = dataPoint.ValueR;
((IList<MotionDataPoint>)series.ItemsSource)[0].ToolTip = dataPoint.ToolTip;
}
private void UpdateTrailSeries(Series series)
{
ScatterLineSeries trailSeries = series.Tag as ScatterLineSeries;
if (trailSeries != null)
{
trailSeries.Brush = series.ActualBrush;
trailSeries.MarkerBrush = series.ActualBrush;
MotionDataSource<DataPoint> dm = trailSeries.ItemsSource as MotionDataSource<DataPoint>;
if (dm != null)
{
dm.Add(new DataPoint
{
ValueX = ((IList<MotionDataPoint>)series.ItemsSource)[0].ValueX,
ValueY = ((IList<MotionDataPoint>)series.ItemsSource)[0].ValueY,
ToolTip = ((IList<MotionDataPoint>)series.ItemsSource)[0].ToolTip
});
}
}
}
private void UpdateTrailSeries(Series series, IEnumerable<DataPoint> dataSource)
{
ScatterSplineSeries trailSeries = series.Tag as ScatterSplineSeries;
if (trailSeries != null)
{
trailSeries.Brush = series.ActualBrush;
trailSeries.MarkerBrush = series.ActualBrush;
trailSeries.ItemsSource = dataSource;
}
}
private void UpdateSeriesTransitions()
{
if (this.IsInitialized)
{
this.Chart.Series.Clear();
// 現在の Transition パラメーターですべてのシリーズを更新します
foreach (var item in this.DataSources)
{
string seriesName = this.GetSeriesName(item.Key);
this.InitializeSeries(seriesName, item.Key);
DataPoint dataPoint;
List<DataPoint> trailsDataSource;
if (this.CurrentElementShown < this.ElementsMaxCount)
{
dataPoint = (DataPoint)item.Value[this.CurrentElementShown];
trailsDataSource = ((List<DataPoint>)item.Value).GetRange(0, this.CurrentElementShown + 1);
}
else
{
dataPoint = (DataPoint)item.Value[this.ElementsMaxCount - 1];
trailsDataSource = ((List<DataPoint>)item.Value).GetRange(0, this.CurrentElementShown);
}
this.UpdateMotionSeries(this.Chart.Series[seriesName], dataPoint);
if (this.ShowTrails)
{
// 表示されている現在の要素まで、すべてのデータ ポイントで軌跡シリーズを更新します
this.UpdateTrailSeries(this.Chart.Series[seriesName], trailsDataSource);
}
}
}
}
private void RefreshSeries()
{
foreach (var item in this.DataSources)
{
// 表示されている現在の要素で、モーション シリーズを更新します
string seriesName = this.GetSeriesName(item.Key);
DataPoint dataPoint = (DataPoint)item.Value[this.CurrentElementShown];
this.UpdateMotionSeries(this.Chart.Series[seriesName], dataPoint);
if (this.ShowTrails)
{
// 表示されている現在の要素まで、すべてのデータ ポイントで軌跡シリーズを更新します
List<DataPoint> trailsDataSource = ((List<DataPoint>)item.Value).GetRange(0, this.CurrentElementShown + 1);
this.UpdateTrailSeries(this.Chart.Series[seriesName], trailsDataSource);
}
}
}
#endregion
#region Public Methods
/// <summary>
/// MotionFramework の要素を初期化します
/// </summary>
public void Initialize()
{
if (this.RequiredConditionsCheck())
{
this.InitializeDataChart();
this.DataUpdateTimer.Interval = this.DataUpdateInterval;
this.CurrentElementShown = 0;
this.IsInitialized = true;
}
}
/// <summary>
/// xamDataChart コントロールでデータ ポイントのアニメーションを開始します
/// </summary>
public void Play()
{
if (!this.IsInitialized) this.Initialize();
//再生が終了した時に再生を開始すると、表示されている現在の要素が 0にリセットされます
if (this.CurrentElementShown >= this.ElementsMaxCount - 1)
{
this.CurrentElementShown = 0;
if (this.DateTimeSlider != null) this.DateTimeSlider.Value = 0;
}
if (this.IsInitialized && this.CurrentElementShown == 0)
{
//再生が先頭から開始される時にスキャッター シリーズをクリアします
foreach (Series series in this.Chart.Series)
{
ScatterLineSeries trailSeries = series as ScatterLineSeries;
if (trailSeries != null)
{
if (trailSeries.ItemsSource != null) ((IList)trailSeries.ItemsSource).Clear();
}
}
}
if (this.DateTimeSlider != null) this.DateTimeSlider.IsEnabled = false;
this.DataUpdateTimer.Start();
OnPlaybackStarted(new EventArgs());
}
/// <summary>
/// xamDataChart コントロールでデータ ポイントのアニメーションを停止します
/// </summary>
public void Pause()
{
if (this.DataUpdateTimer.IsEnabled)
{
if (this.DateTimeSlider != null) this.DateTimeSlider.IsEnabled = true;
this.DataUpdateTimer.Stop();
OnPlaybackStopped(new EventArgs());
}
}
#endregion
#region Properties
public bool IsInitialized { get; private set; }
/// <summary>
/// MotionFramework がデータ ポイントのアニメーションを再生しているかどうかを確認します
/// </summary>
public bool IsPlaying { get { return this.DataUpdateTimer.IsEnabled; } }
/// <summary>
/// データ ポイントのアニメーションの更新のタイマー間隔を表します
/// </summary>
public TimeSpan DataUpdateInterval
{
get { return this.DataUpdateTimer.Interval; }
set { this.DataUpdateTimer.Interval = value; }
}
private TimeSpan _transitionDuration;
public TimeSpan TransitionDuration
{
get { return _transitionDuration; }
set
{
if (_transitionDuration == value) return;
_transitionDuration = value;
this.UpdateSeriesTransitions();
this.OnPropertyChanged("TransitionDuration");
}
}
private EasingFunctionBase _transitionFunction;
public EasingFunctionBase TransitionFunction
{
get { return _transitionFunction; }
set
{
if (_transitionFunction == value) return;
_transitionFunction = value;
this.UpdateSeriesTransitions();
this.OnPropertyChanged("TransitionFunction");
}
}
private int _currentElementShown;
/// <summary>
/// データ ポイントのアニメーションの現在のインデックスを表します
/// </summary>
public int CurrentElementShown
{
get { return _currentElementShown; }
set
{
_currentElementShown = value;
this.CurrentDateTime = this.CurrentDataPoint.ValueDateTime;
this.CurrentDateTimeString = this.CurrentDateTime.ToShortDateString();
}
}
private string _currentDateTimeString;
/// <summary>
/// データ ポイントのアニメーションの現在の日付時間を文字列で表します
/// </summary>
public string CurrentDateTimeString
{
get
{
return _currentDateTimeString;
}
set
{
if (_currentDateTimeString == value) return;
_currentDateTimeString = value;
this.OnPropertyChanged("CurrentDateTimeString");
}
}
private DateTime _currentDateTime;
/// <summary>
/// データ ポイントのアニメーションの現在の日付時間を表します
/// </summary>
public DateTime CurrentDateTime
{
get
{
return _currentDateTime;
}
set
{
if (_currentDateTime == value) return;
_currentDateTime = value;
this.OnPropertyChanged("CurrentDateTime");
OnCurrentDateChanged(new DateTimeChangedEventArgs { CurrentDate = this.CurrentDateTime });
}
}
protected DataPoint CurrentDataPoint
{
get
{
if (this.CurrentElementShown < this.ElementsMaxCount)
return ((DataPoint)this.DataSources[0][this.CurrentElementShown]);
return ((DataPoint)this.DataSources[0][this.ElementsMaxCount - 1]);
}
}
public TimeSpan CurrentTimeSpan
{
get
{
return this.CurrentDateTime.Subtract(this.MinDateTime);
}
}
public DateTime MinDateTime { get; private set; }
public DateTime MaxDateTime { get; private set; }
public Thickness AutosetAxisMargin { get; set; }
public bool AutosetAxisRange { get; set; }
public bool ShowTrails { get; set; }
private Slider _dateTimeSlider;
/// <summary>
/// データ ポイントのアニメーションを制御するスライダーを表します
/// </summary>
public Slider DateTimeSlider
{
get
{
return _dateTimeSlider;
}
set
{
if (_dateTimeSlider != null)
_dateTimeSlider.ValueChanged -= OnDateTimeSliderValueChanged;
_dateTimeSlider = value;
if (_dateTimeSlider != null)
_dateTimeSlider.ValueChanged += OnDateTimeSliderValueChanged;
}
}
public Legend ChartLegend { get; set; }
public XamDataChart Chart { get; set; }
private Dictionary<int, IList> _dataSource;
/// <summary>
/// xamDataChart のすべてのシリーズのデータ ソースを表します
/// </summary>
public Dictionary<int, IList> DataSources
{
get { return _dataSource; }
set
{
if (_dateTimeSlider == null) return;
_dataSource = value;
}
}
public string XAxisName { get; set; }
public string YAxisName { get; set; }
public string SeriesXMemberPath { get; set; }
public string SeriesYMemberPath { get; set; }
public string SeriesRadiusMemberPath { get; set; }
public string SeriesTimeMemberPath { get; set; }
public DataTemplate MarkerTemplate { get; set; }
public DataTemplate LegendItemTemplate { get; set; }
#endregion
}
public class DataSourceGenerator
{
protected static Random Random;
static DataSourceGenerator()
{
Random = new Random();
}
public static IList<DataPoint> GetRandomData(int maxValues, int order)
{
List<DataPoint> result = new List<DataPoint>(maxValues);
DateTime valueDateTime = DateTime.Now.AddDays(maxValues * -1);
double valueModifier = (order % 2 == 0) ? 1 : -1;
double valueMultiplier = (order % 2 == 0) ? order : order - 1;
for (int i = 0; i < maxValues; i++)
{
double x = Convert.ToDouble(i) / 100f;
double y = valueModifier * System.Math.Round(System.Math.Sin(x * (valueMultiplier + 1)), 2);
double r = 10 + (Convert.ToDouble(i) / 25f) * (order + 1);
result.Add(new DataPoint
{
ValueX = x,
ValueY = y,
ValueR = r,
ValueDateTime = valueDateTime
});
valueDateTime = valueDateTime.AddDays(1);
}
return result;
}
public static Dictionary<int, IList> GetDataSources(int maxSeries, int maxDataPerSeries)
{
Dictionary<int, IList> dict = new Dictionary<int, IList>();
for (int i = 0; i < maxSeries; i++)
{
dict.Add(i, (IList)GetRandomData(maxDataPerSeries, i));
}
return dict;
}
}
public class DataPoint
{
public DateTime ValueDateTime { get; set; }
public string ToolTip { get; set; }
public double ValueX { get; set; }
public double ValueY { get; set; }
public double ValueR { get; set; }
}
public class MotionDataSource<T> : ObservableCollection<T>
{ }
public class MotionDataPoint : ObservableModel
{
private double _valueX;
private double _valueY;
private double _valueR;
public DateTime ValueDateTime { get; set; }
public string ToolTip { get; set; }
public static string PropertyNameValueX = "ValueX";
public static string PropertyNameValueY = "ValueY";
public static string PropertyNameValueR = "ValueR";
public static string PropertyNameValueDateTime = "ValueDateTime";
public double ValueX
{
get { return _valueX; }
set { if (_valueX == value) return; _valueX = value; this.OnPropertyChanged(PropertyNameValueX); }
}
public double ValueY
{
get { return _valueY; }
set { if (_valueY == value) return; _valueY = value; this.OnPropertyChanged(PropertyNameValueY); }
}
public double ValueR
{
get { return _valueR; }
set { if (_valueR == value) return; _valueR = value; this.OnPropertyChanged(PropertyNameValueR); }
}
}
public abstract class ObservableModel : INotifyPropertyChanged
{
#region Event Handlers
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected void OnPropertyChanged(PropertyChangedEventArgs propertyChangedEventArgs)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
handler(this, propertyChangedEventArgs);
}
#endregion
}
}