using System;
using System.Windows;
using System.Windows.Input;
using SilverTrack.View;
using TelemetryData.Telemetry;
using Visiblox.Charts;
using System.ComponentModel;
using Snippets;
namespace SilverTrack.ViewModel
{
/// <summary>
/// Provides the data context for a TelemetryChannelView and contains two data series to display
/// on the chart from that view. Also contains behaviours and operations to modify, remove and add
/// data series to the chart.
/// </summary>
[SnippetINotifyPropertyChanged]
[SnippetPropertyINPC(property="SelectedSecondaryIndex", type="int", field="_selectedSecondaryIndex", defaultValue="1",
summary="The Index of the selected series in the secondary combo box.")]
[SnippetPropertyINPC(property="SelectedPrimaryIndex", type="int", field="_selectedPrimaryIndex", defaultValue="1",
summary="The Index of the selected series in the primary combo box.")]
[SnippetPropertyINPC(property="Behaviour", type="BehaviourManager", field="_behaviour",
summary="The Behaviour Manager which contains the trackball and the XAxisZoomBehaviour.")]
[SnippetPropertyINPC(property="XAxisVisible", type="bool", field="_xAxisVisibile", defaultValue="false",
summary="Whether this chart's X-Axis is visible.")]
[SnippetPropertyINPC(property="LivePrimaryChartDataSeries", type="DataSeries<DateTime, double>", field="_livePrimaryData",
summary="The Live Updating DataSeries that is always displayed on the chart's primary y-axis.")]
[SnippetPropertyINPC(property="LiveSecondaryChartDataSeries", type="DataSeries<DateTime, double>", field="_liveSecondaryData",
summary="The Live Updating DataSeries that is always displayed on the chart's secondary y-axis.")]
public partial class TelemetryChannelViewModel : INotifyPropertyChanged
{
#region partial methods
partial void OnSelectedSecondaryIndexChanged(int value)
{
ModifySecondaryChannel(ParentTelemetryViewModel.Channels[SelectedSecondaryIndex]);
}
partial void OnSelectedPrimaryIndexChanged(int value)
{
ModifyPrimaryChannel(ParentTelemetryViewModel.Channels[SelectedPrimaryIndex]);
}
#endregion
#region Fields
#region Private Fields
private DataSeries<DateTime, double> _primaryData = null;
private DataSeries<DateTime, double> _secondaryData = null;
private DateTimeRange _chartRange;
private XAxisZoomBehaviour _xZoom;
private TrackballBehaviour _trackball;
private bool _running;
#endregion
#endregion
#region Properties
/// <summary>
/// The TelemetryViewModel which created this TelemetryChannelViewModel.
/// </summary>
public TelemetryViewModel ParentTelemetryViewModel { get; set; }
/// <summary>
/// The X-Axis range for all the chart.
/// </summary>
public DateTimeRange ChartRange
{
get { return _chartRange; }
}
/// <summary>
/// The Y-Axis range for the primary data series.
/// </summary>
public DoubleRange PrimaryYRange
{
get
{
return CalculateRange(ParentTelemetryViewModel.DataProvider.FindSeriesFromName(_primaryData.Title));
}
}
/// <summary>
/// The Y-Axis range for the secondary data series.
/// </summary>
public DoubleRange SecondaryYRange
{
get
{
return CalculateRange(ParentTelemetryViewModel.DataProvider.FindSeriesFromName(_secondaryData.Title));
}
}
/// <summary>
/// The Current selected point index on the series by the trackball.
/// </summary>
public int CurrentSelectedPoint
{
get
{
if (_trackball != null && _trackball.CurrentPoints.Count != 0)
{
if (_trackball.CurrentPoints[0] != null)
{
return GetTrackballDataPoint(0);
}
else if (_trackball.CurrentPoints[1] != null)
{
return GetTrackballDataPoint(1);
}
}
return -1;
}
}
/// <summary>
/// The length of this data series.
/// </summary>
public int Length
{
get
{
if (_primaryData != null)
return _primaryData.Count;
else if (_secondaryData != null)
return _secondaryData.Count;
else
return -1;
}
}
/// <summary>
/// The command which will remove this Chart from the application.
/// </summary>
public ICommand RemoveCurrentChartViewCommand
{
get { return new RelayCommand<string>(p => RemoveCurrentChartView(p)); }
}
#endregion
#region Methods
#region Constructors
public TelemetryChannelViewModel() { }
/// <summary>
/// Creates a TelemetryChannelViewModel with two initial data series and calculates the X-Axis range.
/// Also creates a XAxisZoomBehaviour and a Trackball.
/// </summary>
/// <param name="PrimaryData">The initial primary data series.</param>
/// <param name="SecondaryData">The initial secondary data series.</param>
/// <param name="telemetryViewModel">The TelemetryViewModel which created this TelemetryChannelViewModel object.</param>
public TelemetryChannelViewModel(TelemetryViewModel telemetryViewModel)
{
ParentTelemetryViewModel = telemetryViewModel;
_primaryData = new DataSeries<DateTime, double>("NONE", new DataSeries<DateTime, double>());
_livePrimaryData = new DataSeries<DateTime, double>("NONE", new DataSeries<DateTime, double>());
_secondaryData = new DataSeries<DateTime, double>("NONE", new DataSeries<DateTime, double>());
_liveSecondaryData = new DataSeries<DateTime, double>("NONE", new DataSeries<DateTime, double>());
_behaviour = new BehaviourManager();
_behaviour.AllowMultipleEnabled = true;
_xZoom = new XAxisZoomBehaviour();
_xZoom.IsEnabled = true;
_trackball = new TrackballBehaviour();
_trackball.IsEnabled = true;
_behaviour.Behaviours.Add(_xZoom);
_behaviour.Behaviours.Add(_trackball);
_xZoom.ZoomStarted += new EventHandler(xZoom_ZoomStarted);
Random RandomGenerator = new Random();
SelectedPrimaryIndex = RandomGenerator.Next(1, ParentTelemetryViewModel.DataProvider.SeriesList.Count-1);
SelectedSecondaryIndex = RandomGenerator.Next(1, ParentTelemetryViewModel.DataProvider.SeriesList.Count - 1);
ModifyPrimaryChannel("NONE");
ModifySecondaryChannel("NONE");
}
#endregion
#region Public
/// <summary>
/// Updates the chart to a given time from a start point given by counter.
/// </summary>
/// <param name="counter">The start point in the data to update from.</param>
/// <param name="currentTime">The representation of the current time of the lap.</param>
/// <returns>Returns the counter value which represents how far through the data series the update reached.</returns>
public int UpdateChart(int counter, DateTime currentTime)
{
//Set running to true so that the render loop knows it has data to add.
_running = true;
//Render loop. Adds data points to the chart based on the current time.
while (_running)
{
//Check that the counter is not at the end of the list. Else restart.
if (counter < _primaryData.Count - 1)
{
if (_primaryData[counter + 1].X < currentTime)
{
//Increment the counter then Add the data point
counter++;
if (_primaryData != null && _primaryData.Count > 0)
_livePrimaryData.Add(_primaryData[counter]);
if (_secondaryData != null && _secondaryData.Count > 0)
_liveSecondaryData.Add(_secondaryData[counter]);
}
else
{
//Next point is in the future, set running to false.
_running = false;
}
}
else if (counter < _secondaryData.Count - 1)
{
if (_secondaryData[counter + 1].X < currentTime)
{
//Increment the counter then Add the data point
counter++;
if (_primaryData != null && _primaryData.Count > 0)
_livePrimaryData.Add(_primaryData[counter]);
if (_secondaryData != null && _secondaryData.Count > 0)
_liveSecondaryData.Add(_secondaryData[counter]);
}
else
{
//Next point is in the future, set running to false.
_running = false;
}
}
else
{
//Reached the end of the data for the lap. Pause the chart and stop it from running.
_running = false;
ParentTelemetryViewModel.Paused = true;
}
}
return counter;
}
/// <summary>
/// Fills the live primary and secondary data from the primary and secondary data, and returns the
/// counter of the endpoint.
/// </summary>
/// <returns>Returns the counter value which represents how far through the data series the update reached.</returns>
public int UpdateFullChart()
{
if (_primaryData.Title != "NONE")
{
_livePrimaryData.AddRange(_primaryData);
}
else
{
_livePrimaryData.Clear();
}
if (_secondaryData.Title != "NONE")
{
_liveSecondaryData.AddRange(_secondaryData);
}
else
{
_liveSecondaryData.Clear();
}
_running = false;
ParentTelemetryViewModel.Paused = true;
return _primaryData.Count - 1;
}
/// <summary>
/// Removes the data points from both series on the chart.
/// </summary>
public void Clear()
{
_livePrimaryData.Clear();
_liveSecondaryData.Clear();
}
/// <summary>
/// Causes the chart to zoom to the given offset and the given scale.
/// </summary>
/// <param name="zoomOffset">The offset to zoom to.</param>
/// <param name="zoomScale">The scale to zoom to.</param>
public void ZoomTo(double zoomOffset, double zoomScale)
{
_xZoom.ZoomToScaleWithOffset(zoomScale, zoomOffset);
}
/// <summary>
/// Causes the mouse move event to be called on the trackball at the given X position, and 0.0 Y position.
/// The given Y value is ignored as charts can be different heights, causing the mouse move to be ignored when
/// the the Y value is greater than the height of the chart.
/// </summary>
/// <param name="mousePosition">The position to move to (NOTE: the Y value is ignored).</param>
public void MouseMove(Point mousePosition)
{
_trackball.MouseMove(new Point(mousePosition.X, 0.0));
}
#endregion
#region Private/Internal
private int GetTrackballDataPoint(int channelIndex)
{
DataPoint<DateTime, double> t = (DataPoint<DateTime, double>)_trackball.CurrentPoints[channelIndex];
return (int)t.Tag;
}
private DoubleRange CalculateRange(TelemetrySeries telemetrySeries)
{
if (telemetrySeries.Name.Equals("NONE"))
{
return new DoubleRange(0.0, 1.0);
}
return new DoubleRange(telemetrySeries.Minimum, telemetrySeries.Maximum);
}
private void xZoom_ZoomStarted(object sender, EventArgs e)
{
ParentTelemetryViewModel.ZoomAll(_xZoom.Offset, _xZoom.Scale);
}
private void ModifyPrimaryChannel(string channel)
{
if (channel != null)
{
DataSeries<DateTime, double> tmpPrimary = ParentTelemetryViewModel.GetChannelData(channel);
_primaryData = tmpPrimary;
_primaryData.Title = tmpPrimary.Title;
Clear();
_chartRange = new DateTimeRange(_primaryData[0].X, _primaryData[_primaryData.Count - 1].X);
ParentTelemetryViewModel.FullDataCommand.Execute("");
OnPropertyChanged(LivePrimaryChartDataSeriesProperty);
OnPropertyChanged("PrimaryYRange");
}
}
private void ModifySecondaryChannel(string channel)
{
if (channel != null)
{
DataSeries<DateTime, double> tmpSecondary = ParentTelemetryViewModel.GetChannelData(channel);
_secondaryData = tmpSecondary;
_secondaryData.Title = tmpSecondary.Title;
Clear();
ParentTelemetryViewModel.FullDataCommand.Execute("");
OnPropertyChanged(LiveSecondaryChartDataSeriesProperty);
OnPropertyChanged("SecondaryYRange");
}
}
private void RemoveCurrentChartView(string channel)
{
if (ParentTelemetryViewModel.ChannelViewModelCollection.ChannelViewModels.Count > 1)
{
ParentTelemetryViewModel.RemoveChannelView(this);
}
}
#endregion
#endregion
}
}