using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Drawing;
using RealTimeGraphX.EventArguments;
using RealTimeGraphX.Renderers;
using System.Diagnostics;
namespace RealTimeGraphX
{
///
/// Represents an base class.
///
/// The type of the data series.
/// The type of the x data point.
/// The type of the y data point.
///
///
public abstract class GraphController : GraphObject, IGraphController
where TXDataPoint : GraphDataPoint
where TYDataPoint : GraphDataPoint
where TDataSeries : IGraphDataSeries
{
private GraphDataQueue> _pending_series_collection;
private Dictionary _to_render;
private DateTime _last_render_time;
private object _render_lock = new object();
private Thread _render_thread;
#region Pending Series Class
protected class PendingSeries
{
public TDataSeries Series { get; set; }
public List XX { get; set; }
public List YY { get; set; }
public int NewItemsCount
{
get { return XX.Count - RenderedItems; }
}
public int RenderedItems { get; set; }
public bool IsClearSeries { get; set; }
}
#endregion
#region Events
///
/// Occurs when the current effective minimum/maximum has changed.
///
public event EventHandler EffectiveRangeChanged;
///
/// Occurs when the current virtual (effective minimum/maximum after transformation) minimum/maximum has changed.
///
public event EventHandler VirtualRangeChanged;
#endregion
#region Properties
///
/// Gets or sets the controller refresh rate.
/// Higher rate requires more CPU time.
///
public TimeSpan RefreshRate { get; set; }
///
/// Gets or sets a value indicating whether to pause rendering.
///
public bool IsPaused { get; set; }
///
/// Gets the data series collection.
///
public ObservableCollection DataSeriesCollection { get; }
private IGraphRenderer _renderer;
///
/// Gets or sets the graph renderer.
///
public IGraphRenderer Renderer
{
get
{
return _renderer;
}
set
{
_renderer = value; RaisePropertyChangedAuto();
}
}
private IGraphSurface _surface;
///
/// Gets or sets the rendering surface.
///
public IGraphSurface Surface
{
get { return _surface; }
set
{
_surface = value;
RequestVirtualRangeChange();
}
}
private GraphRange _range;
///
/// Gets or sets the graph range (data point boundaries).
///
public GraphRange Range
{
get
{
return _range;
}
set
{
_range = value; RaisePropertyChangedAuto();
}
}
///
/// Gets the current effective x-axis minimum.
///
public GraphDataPoint EffectiveMinimumX { get; private set; }
///
/// Gets the current effective x-axis maximum.
///
public GraphDataPoint EffectiveMaximumX { get; private set; }
///
/// Gets the current effective y-axis minimum.
///
public GraphDataPoint EffectiveMinimumY { get; private set; }
///
/// Gets the current effective y-axis maximum.
///
public GraphDataPoint EffectiveMaximumY { get; private set; }
///
/// Gets the current virtual (effective minimum/maximum after transformation) x-axis minimum.
///
public GraphDataPoint VirtualMinimumX { get; private set; }
///
/// Gets the current virtual (effective minimum/maximum after transformation) x-axis maximum.
///
public GraphDataPoint VirtualMaximumX { get; private set; }
///
/// Gets the current virtual (effective minimum/maximum after transformation) y-axis minimum.
///
public GraphDataPoint VirtualMinimumY { get; private set; }
///
/// Gets the current virtual (effective minimum/maximum after transformation) y-axis maximum.
///
public GraphDataPoint VirtualMaximumY { get; private set; }
#endregion
#region Commands
///
/// Gets the clear command.
///
public GraphCommand ClearCommand { get; private set; }
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
public GraphController()
{
Renderer = new ScrollingLineRenderer();
DataSeriesCollection = new ObservableCollection();
Range = new GraphRange();
_last_render_time = DateTime.Now;
_to_render = new Dictionary();
_pending_series_collection = new GraphDataQueue>();
RefreshRate = TimeSpan.FromMilliseconds(50);
ClearCommand = new GraphCommand(Clear);
_render_thread = new Thread(RenderThreadMethod);
_render_thread.IsBackground = true;
_render_thread.Start();
}
#endregion
#region Render Thread
///
/// The rendering thread method.
///
private void RenderThreadMethod()
{
while (true)
{
if (!IsPaused)
{
try
{
var pending_list = _pending_series_collection.BlockDequeue();
foreach (var pending_series in pending_list)
{
if (pending_series.IsClearSeries)
{
_pending_series_collection = new GraphDataQueue>();
_to_render.Clear();
break;
}
if (_to_render.ContainsKey(pending_series.Series))
{
var s = _to_render[pending_series.Series];
s.XX.AddRange(pending_series.XX);
s.YY.AddRange(pending_series.YY);
}
else
{
_to_render[pending_series.Series] = pending_series;
}
}
if (DateTime.Now > _last_render_time.AddMilliseconds(RefreshRate.TotalMilliseconds) && _to_render.Count > 0)
{
GraphDataPoint min_x = _range.MaximumX - _range.MaximumX;
GraphDataPoint max_x = _range.MaximumX;
GraphDataPoint min_y = _range.MinimumY;
GraphDataPoint max_y = _range.MaximumY;
min_x = _to_render.First().Value.XX.First();
max_x = _to_render.First().Value.XX.Last();
if (_range.AutoY)
{
min_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Min();
max_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Max();
}
if (min_y == max_y)
{
min_y = _range.MinimumY;
max_y = _range.MaximumY;
}
EffectiveMinimumX = min_x;
EffectiveMaximumX = max_x;
EffectiveMinimumY = min_y;
EffectiveMaximumY = max_y;
VirtualMinimumX = EffectiveMinimumX;
VirtualMaximumX = EffectiveMaximumX;
VirtualMinimumY = EffectiveMinimumY;
VirtualMaximumY = EffectiveMaximumY;
_last_render_time = DateTime.Now;
if (Surface != null)
{
var surface_size = Surface.GetSize();
var zoom_rect = Surface.GetZoomRect();
Surface.BeginDraw();
if (zoom_rect.Width > 0 && zoom_rect.Height > 0)
{
var zoom_rect_top_percentage = zoom_rect.Top / surface_size.Height;
var zoom_rect_bottom_percentage = zoom_rect.Bottom / surface_size.Height;
var zoom_rect_left_percentage = zoom_rect.Left / surface_size.Width;
var zoom_rect_right_percentage = zoom_rect.Right / surface_size.Width;
VirtualMinimumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_bottom_percentage);
VirtualMaximumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_top_percentage);
VirtualMinimumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_left_percentage);
VirtualMaximumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_right_percentage);
GraphTransform transform = new GraphTransform();
var scale_x = (float)(surface_size.Width / zoom_rect.Width);
var scale_y = (float)(surface_size.Height / zoom_rect.Height);
var translate_x = (float)-zoom_rect.Left * scale_x;
var translate_y = (float)-zoom_rect.Top * scale_y;
transform = new GraphTransform();
transform.TranslateX = translate_x;
transform.TranslateY = translate_y;
transform.ScaleX = scale_x;
transform.ScaleY = scale_y;
Surface.SetTransform(transform);
}
List>> to_draw = new List>>();
var to_render = _to_render.Select(x => x.Value).ToList();
foreach (var item in to_render)
{
var points = Renderer.Render(Surface, item.Series, _range, item.XX, item.YY, min_x, max_x, min_y, max_y);
to_draw.Add(new Tuple>(item.Series, points));
}
for (int i = 0; i < to_draw.Count; i++)
{
if (to_draw[i].Item2.Count() > 2)
{
if (to_draw[i].Item1.IsVisible)
{
Renderer.Draw(Surface, to_draw[i].Item1, to_draw[i].Item2, i, to_draw.Count);
}
}
}
Surface.EndDraw();
}
OnEffectiveRangeChanged(EffectiveMinimumX, EffectiveMaximumX, EffectiveMinimumY, EffectiveMaximumY);
OnVirtualRangeChanged(VirtualMinimumX, VirtualMaximumX, VirtualMinimumY, VirtualMaximumY);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in RealTimeGraphX:\n{ex.ToString()}");
}
}
else
{
Thread.Sleep(RefreshRate);
}
}
}
#endregion
#region Protected Methods
///
/// Raises the event.
///
/// The minimum x.
/// The maximum x.
/// The minimum y.
/// The maximum y.
protected virtual void OnEffectiveRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY)
{
EffectiveRangeChanged?.Invoke(this, new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY));
}
///
/// Raises the event.
///
/// The minimum x.
/// The maximum x.
/// The minimum y.
/// The maximum y.
protected virtual void OnVirtualRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY)
{
var range = new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY);
VirtualRangeChanged?.Invoke(this, range);
}
///
/// Converts the specified relative x position to graph absolute position.
///
/// The relative x position.
///
protected virtual float ConvertXValueToRendererValue(double x)
{
return (float)(x * Surface.GetSize().Width / 100);
}
///
/// Converts the specified relative y position to graph absolute position.
///
/// The relative y position.
///
protected virtual float ConvertYValueToRendererValue(double y)
{
return (float)(Surface.GetSize().Height - (y * Surface.GetSize().Height / 100));
}
#endregion
#region Public Methods
///
/// Submits the specified x and y data points.
/// If the controller has more than one data series the data points will be duplicated.
///
/// X data point.
/// Y data point.
public void PushData(TXDataPoint x, TYDataPoint y)
{
if (DataSeriesCollection.Count == 0) return;
List> xxxx = new List>();
List> yyyy = new List>();
foreach (var series in DataSeriesCollection.ToList())
{
xxxx.Add(new List() { x });
yyyy.Add(new List() { y });
}
PushData(xxxx, yyyy);
}
///
/// Submits the specified collections of x and y data points.
/// If the controller has more than one data series the data points will be distributed evenly.
///
/// X data point collection.
/// Y data point collection.
public void PushData(IEnumerable xx, IEnumerable yy)
{
if (DataSeriesCollection.Count == 0) return;
var xList = xx.ToList();
var yList = yy.ToList();
List> xxxx = new List>();
List> yyyy = new List>();
foreach (var series in DataSeriesCollection.ToList())
{
xxxx.Add(new List());
yyyy.Add(new List());
}
int counter = 0;
for (int i = 0; i < xList.Count; i++)
{
xxxx[counter].Add(xList[i]);
yyyy[counter].Add(yList[i]);
counter++;
if (counter >= xxxx.Count)
{
counter = 0;
}
}
PushData(xxxx, yyyy);
}
///
/// Submits a matrix of x and y data points. Meaning each data series should process a single collection of x/y data points.
///
/// X matrix.
/// Y matrix.
public void PushData(IEnumerable> xxxx, IEnumerable> yyyy)
{
if (DataSeriesCollection.Count == 0) return;
IEnumerable> xxxxI = xxxx.Select(x => x.ToList()).ToList();
IEnumerable> yyyyI = yyyy.Select(x => x.ToList()).ToList();
List> xxxxList = xxxxI.Select(x => x.ToList()).ToList();
List> yyyyList = yyyyI.Select(x => x.ToList()).ToList();
int first_count_x = xxxxList[0].Count;
int first_count_y = yyyyList[0].Count;
bool is_data_valid = true;
for (int i = 0; i < xxxxList.Count; i++)
{
if (xxxxList[0].Count != first_count_x)
{
is_data_valid = false;
break;
}
if (xxxxList[0].Count != yyyyList[0].Count)
{
is_data_valid = false;
break;
}
}
if (!is_data_valid)
{
throw new ArgumentOutOfRangeException("When pushing data to a multi series renderer, each series must contain the same amount of data.");
}
var list = DataSeriesCollection.ToList();
var pending_list = new List();
for (int i = 0; i < list.Count; i++)
{
pending_list.Add(new PendingSeries()
{
Series = list[i],
XX = xxxxList[i].ToList(),
YY = yyyyList[i].ToList(),
});
}
_pending_series_collection.BlockEnqueue(pending_list);
}
///
/// Clears all data points from this controller.
///
public void Clear()
{
_pending_series_collection.BlockEnqueue(new List()
{
new PendingSeries()
{
IsClearSeries = true
},
});
}
///
/// Requests the controller to invoke a virtual range change event.
///
public void RequestVirtualRangeChange()
{
OnVirtualRangeChanged(Range.MaximumX, Range.MaximumX, Range.MinimumY, Range.MaximumY);
}
#endregion
}
}