aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/SideChains/RealTimeGraphX/GraphRendererBase.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/SideChains/RealTimeGraphX/GraphRendererBase.cs')
-rw-r--r--Software/Visual_Studio/SideChains/RealTimeGraphX/GraphRendererBase.cs573
1 files changed, 573 insertions, 0 deletions
diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphX/GraphRendererBase.cs b/Software/Visual_Studio/SideChains/RealTimeGraphX/GraphRendererBase.cs
new file mode 100644
index 000000000..fb4824837
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/RealTimeGraphX/GraphRendererBase.cs
@@ -0,0 +1,573 @@
+using RealTimeGraphX.EventArguments;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace RealTimeGraphX
+{
+ /// <summary>
+ /// Represents an <see cref="IGraphRenderer{XDataPoint, YDataPoint}"/> base class.
+ /// </summary>
+ /// <typeparam name="TDataSeries">The type of the graph data series.</typeparam>
+ /// <typeparam name="XDataPoint">The type of the x-axis data point.</typeparam>
+ /// <typeparam name="YDataPoint">The type of the y-axis data point.</typeparam>
+ /// <seealso cref="RealTimeGraphX.GraphInputOutputComponentBase{RealTimeGraphX.IGraphController{XDataPoint, YDataPoint}, RealTimeGraphX.IGraphPainter}" />
+ /// <seealso cref="RealTimeGraphX.IGraphRenderer{XDataPoint, YDataPoint}" />
+ /// <seealso cref="RealTimeGraphX.IGraphRenderer" />
+ public abstract class GraphRendererBase<TDataSeries, XDataPoint, YDataPoint> :
+ GraphInputOutputComponentBase<IGraphController<TDataSeries, XDataPoint, YDataPoint>, IGraphPainter<TDataSeries>>,
+ IGraphRenderer<TDataSeries, XDataPoint, YDataPoint>,
+ IGraphRenderer<TDataSeries>
+ where XDataPoint : GraphDataPointBase
+ where YDataPoint : GraphDataPointBase
+ where TDataSeries : IGraphDataSeries
+ {
+ private GraphDataQueue<PendingSeries> _pending_series_collection;
+ private Dictionary<TDataSeries, PendingSeries> _to_render;
+ private DateTime _last_render_time;
+ private object _render_lock = new object();
+ private Thread _render_thread;
+
+ protected YDataPoint _current_min_y;
+ protected YDataPoint _current_max_y;
+
+ private PidController _pid_controller;
+ private DateTime _last_pid_sample_time = DateTime.Now;
+
+ #region Pending Series Class
+
+ protected class PendingSeries
+ {
+ public TDataSeries Series { get; set; }
+ public List<XDataPoint> XX { get; set; }
+ public List<YDataPoint> YY { get; set; }
+
+ public int NewItemsCount
+ {
+ get { return XX.Count - RenderedItems; }
+ }
+
+ public int RenderedItems { get; set; }
+ public bool IsInvalidationSeries { get; set; }
+ }
+
+ #endregion
+
+ #region Events
+
+ /// <summary>
+ /// Occurs when the current effective x-axis minimum/maximum has changed.
+ /// </summary>
+ public event EventHandler<RangeChangedEventArgs> EffectiveRangeXChanged;
+
+ /// <summary>
+ /// Occurs when the current effective y-axis minimum/maximum has changed.
+ /// </summary>
+ public event EventHandler<RangeChangedEventArgs> EffectiveRangeYChanged;
+
+ /// <summary>
+ /// Occurs when the connected input controller has changed.
+ /// </summary>
+ event EventHandler<InputChangedEventArgs<IGraphController<TDataSeries>>> IGraphInputComponent<IGraphController<TDataSeries>>.InputChanged
+ {
+ add
+ {
+ InputChanged += new EventHandler<InputChangedEventArgs<IGraphController<TDataSeries, XDataPoint, YDataPoint>>>((sender, e) => { value.Invoke(sender, new InputChangedEventArgs<IGraphController<TDataSeries>>(e.Input)); });
+ }
+
+ remove
+ {
+ InputChanged -= new EventHandler<InputChangedEventArgs<IGraphController<TDataSeries, XDataPoint, YDataPoint>>>((sender, e) => { value.Invoke(sender, new InputChangedEventArgs<IGraphController<TDataSeries>>(e.Input)); });
+ }
+ }
+
+ #endregion
+
+ #region Properties
+
+ private TimeSpan _refreshRate;
+ /// <summary>
+ /// Gets or sets the renderer refresh rate.
+ /// Higher rate requires more CPU time.
+ /// </summary>
+ public TimeSpan RefreshRate
+ {
+ get
+ {
+ return _refreshRate;
+ }
+ set
+ {
+ _refreshRate = value;
+ RaisePropertyChangedAuto();
+ }
+ }
+
+ private GraphDataPointBase _effectiveMinimumX;
+ /// <summary>
+ /// Gets the current effective x-axis minimum.
+ /// </summary>
+ public GraphDataPointBase EffectiveMinimumX
+ {
+ get
+ {
+ return _effectiveMinimumX;
+ }
+ private set
+ {
+ _effectiveMinimumX = value;
+ RaisePropertyChangedAuto();
+ }
+ }
+
+ private GraphDataPointBase _effectiveMaximumX;
+ /// <summary>
+ /// Gets the current effective x-axis maximum.
+ /// </summary>
+ public GraphDataPointBase EffectiveMaximumX
+ {
+ get
+ {
+ return _effectiveMaximumX;
+ }
+ private set
+ {
+ _effectiveMaximumX = value;
+ RaisePropertyChangedAuto();
+ }
+ }
+
+ private GraphDataPointBase _effectiveMinimumY;
+ /// <summary>
+ /// Gets the current effective y-axis minimum.
+ /// </summary>
+ public GraphDataPointBase EffectiveMinimumY
+ {
+ get
+ {
+ return _effectiveMinimumY;
+ }
+ private set
+ {
+ _effectiveMinimumY = value;
+ RaisePropertyChangedAuto();
+ }
+ }
+
+ private GraphDataPointBase _effectiveMaximumY;
+ /// <summary>
+ /// Gets the current effective y-axis maximum.
+ /// </summary>
+ public GraphDataPointBase EffectiveMaximumY
+ {
+ get
+ {
+ return _effectiveMaximumY;
+ }
+ private set
+ {
+ _effectiveMaximumY = value;
+ RaisePropertyChangedAuto();
+ }
+ }
+
+ /// <summary>
+ /// Gets the current data point x position.
+ /// </summary>
+ public double CurrentXPosition { get; protected set; }
+
+ private bool _isPaused;
+ /// <summary>
+ /// Gets or sets a value indicating whether to pause the graph movement.
+ /// </summary>
+ public bool IsPaused
+ {
+ get { return _isPaused; }
+ set { _isPaused = value; RaisePropertyChangedAuto(); }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GraphRendererBase{XDataPoint, YDataPoint}"/> class.
+ /// </summary>
+ public GraphRendererBase()
+ {
+ EffectiveMinimumX = GraphDataPointHelper.Init<XDataPoint>();
+ EffectiveMinimumY = GraphDataPointHelper.Init<YDataPoint>();
+ EffectiveMaximumX = GraphDataPointHelper.Init<XDataPoint>();
+ EffectiveMaximumY = GraphDataPointHelper.Init<YDataPoint>();
+ _last_render_time = DateTime.Now;
+ _to_render = new Dictionary<TDataSeries, PendingSeries>();
+ _pending_series_collection = new GraphDataQueue<PendingSeries>();
+ RefreshRate = TimeSpan.FromMilliseconds(50);
+
+ _pid_controller = new PidController(10, 10, -10);
+ }
+
+ #endregion
+
+ #region Render Thread
+
+ /// <summary>
+ /// The rendering thread method.
+ /// </summary>
+ private void RenderThreadMethod()
+ {
+ while (Output != null)
+ {
+ if (!IsPaused)
+ {
+ for (int i = 0; i < Input.DataSeriesCollection.Count; i++)
+ {
+ var series = _pending_series_collection.BlockDequeue();
+
+ if (!series.IsInvalidationSeries)
+ {
+ if (_to_render.ContainsKey(series.Series))
+ {
+ var s = _to_render[series.Series];
+ s.XX.AddRange(series.XX);
+ s.YY.AddRange(series.YY);
+ }
+ else
+ {
+ _to_render[series.Series] = series;
+ }
+
+ if (i == Input.DataSeriesCollection.Count - 1)
+ {
+ if (DateTime.Now > _last_render_time.AddMilliseconds(RefreshRate.TotalMilliseconds))
+ {
+ if (Input.Range.AutoY)
+ {
+ _current_min_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Min();
+ _current_max_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Max();
+ }
+
+ _last_render_time = DateTime.Now;
+ RenderAndDraw(_to_render.ToList());
+ }
+ }
+ }
+ else
+ {
+ RenderAndDraw(_to_render.ToList());
+ }
+ }
+ }
+ else
+ {
+ RenderAndDraw(_to_render.ToList());
+ Thread.Sleep(50);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ /// <summary>
+ /// Renders the specified data points collection.
+ /// • The number of data series of x and y must match.
+ /// • The number of x and y collections must match.
+ /// • The number of x and y data points must match.
+ /// </summary>
+ /// <param name="xxxx">The x-axis series collection.</param>
+ /// <param name="yyyy">The y-axis series collection.</param>
+ /// <exception cref="ArgumentOutOfRangeException">When pushing data to a multi series renderer, each series must contain the same amount of data.</exception>
+ public void Render(IEnumerable<TDataSeries> seriesCollection, IEnumerable<IEnumerable<XDataPoint>> xxxx, IEnumerable<IEnumerable<YDataPoint>> yyyy)
+ {
+ List<List<XDataPoint>> xxxxList = xxxx.Select(x => x.ToList()).ToList();
+ List<List<YDataPoint>> yyyyList = yyyy.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 = seriesCollection.ToList();
+
+ for (int i = 0; i < list.Count; i++)
+ {
+ _pending_series_collection.BlockEnqueue(new PendingSeries()
+ {
+ Series = list[i],
+ XX = xxxxList[i].ToList(),
+ YY = yyyyList[i].ToList(),
+ });
+ }
+ }
+
+ /// <summary>
+ /// Invalidates the graph.
+ /// </summary>
+ public virtual void Invalidate()
+ {
+ _pending_series_collection.BlockEnqueue(new PendingSeries() { IsInvalidationSeries = true });
+ }
+
+ /// <summary>
+ /// Connects this renderer to an <see cref="IGraphPainter"/>.
+ /// </summary>
+ /// <param name="painter">The graph painter.</param>
+ /// <param name="fromOutput">Specifies whether this call was made from an <see cref="IGraphPainter"/> component.</param>
+ public override void ConnectOutput(IGraphPainter<TDataSeries> painter, bool fromOutput = false)
+ {
+ painter.ThrowIfNull("Cannot connect a null painter.");
+
+ DisconnectOutput();
+
+ Output = painter;
+
+ if (!fromOutput)
+ {
+ Output.ConnectInput(this, true);
+ }
+
+ _render_thread = new Thread(RenderThreadMethod);
+ _render_thread.IsBackground = true;
+ _render_thread.Name = "IGraphRenderer Render Thread";
+ _render_thread.Start();
+
+ OnEffectiveRangeYChanged(Input.Range.MinimumY, Input.Range.MaximumY);
+ }
+
+ /// <summary>
+ /// Disconnects this renderer from the connected <see cref="IGraphPainter"/>.
+ /// </summary>
+ /// <param name="fromOutput">Specifies whether this call was made from an <see cref="IGraphPainter"/> component.</param>
+ public override void DisconnectOutput(bool fromOutput = false)
+ {
+ if (Output != null)
+ {
+ if (!fromOutput)
+ {
+ Output.DisconnectInput(true);
+ }
+
+ Output = null;
+ }
+ }
+
+ /// <summary>
+ /// Connects this renderer to the specified <see cref="IGraphController{XDataPoint, YDataPoint}"/>.
+ /// </summary>
+ /// <param name="controller">The controller.</param>
+ /// <param name="fromInput">Specifies whether this call was made from an <see cref="IGraphController{XDataPoint, YDataPoint}"/> component.</param>
+ public override void ConnectInput(IGraphController<TDataSeries, XDataPoint, YDataPoint> controller, bool fromInput = false)
+ {
+ controller.ThrowIfNull("Cannot connect a null controller.");
+
+ DisconnectInput();
+
+ if (!fromInput)
+ {
+ controller.ConnectOutput(this, true);
+ }
+
+ Input = controller;
+
+ Input.RangeChanged += Input_RangeChanged;
+
+ OnEffectiveRangeYChanged(Input.Range.MinimumY, Input.Range.MaximumY);
+ }
+
+ /// <summary>
+ /// Disconnects this renderer from the current connected <see cref="IGraphController{XDataPoint, YDataPoint}"/>.
+ /// </summary>
+ /// <param name="fromInput">Specifies whether this call was made from an <see cref="IGraphController{XDataPoint, YDataPoint}"/> component.</param>
+ public override void DisconnectInput(bool fromInput = false)
+ {
+ if (Input != null)
+ {
+ if (!fromInput)
+ {
+ (Input as IGraphOutputComponent<IGraphRenderer<TDataSeries, XDataPoint, YDataPoint>>).DisconnectOutput(true);
+ }
+
+ Input.RangeChanged -= Input_RangeChanged;
+
+ Input = null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the connected input controller.
+ /// </summary>
+ IGraphController<TDataSeries> IGraphInputComponent<IGraphController<TDataSeries>>.Input
+ {
+ get
+ {
+ return Input;
+ }
+ }
+
+ /// <summary>
+ /// Connected this renderer to the specified graph controller.
+ /// </summary>
+ /// <param name="controller">The controller.</param>
+ /// <param name="fromInput">Specifies whether this call was made from an <see cref="IGraphController"/> component.</param>
+ void IGraphInputComponent<IGraphController<TDataSeries>>.ConnectInput(IGraphController<TDataSeries> controller, bool fromInput)
+ {
+ ConnectInput(controller as IGraphController<TDataSeries, XDataPoint, YDataPoint>, fromInput);
+ }
+
+ /// <summary>
+ /// Clears the graph data.
+ /// </summary>
+ public void Clear()
+ {
+ while (_pending_series_collection.Count > 0)
+ {
+ _pending_series_collection.BlockDequeue();
+ }
+
+ _to_render.Clear();
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ /// <summary>
+ /// Returns an array of absolute graph data points to render.
+ /// This method is called per data series.
+ /// </summary>
+ /// <param name="seriesCollection">A collection of all data series that is currently in the rendering pass.</param>
+ /// <param name="series">The current data series to render.</param>
+ /// <param name="toRender">Pending data series object to render.</param>
+ /// <returns></returns>
+ protected abstract List<GraphPoint> OnRender(IEnumerable<PendingSeries> seriesCollection, TDataSeries series, PendingSeries toRender);
+
+ /// <summary>
+ /// Converts the specified relative x position to graph absolute position.
+ /// </summary>
+ /// <param name="x">The relative x position.</param>
+ /// <returns></returns>
+ protected virtual float ConvertXValueToRendererValue(double x)
+ {
+ return (float)(x * Output.SurfaceWidth / 100);
+ }
+
+ /// <summary>
+ /// Converts the specified relative y position to graph absolute position.
+ /// </summary>
+ /// <param name="y">The relative y position.</param>
+ /// <returns></returns>
+ protected virtual float ConvertYValueToRendererValue(double y)
+ {
+ return (float)(Output.SurfaceHeight - (y * Output.SurfaceHeight / 100));
+ }
+
+ /// <summary>
+ /// Raises the <see cref="EffectiveRangeXChanged"/> event.
+ /// </summary>
+ /// <param name="minimumX">The minimum x.</param>
+ /// <param name="maximumX">The maximum x.</param>
+ protected virtual void OnEffectiveRangeXChanged(GraphDataPointBase minimumX, GraphDataPointBase maximumX)
+ {
+ EffectiveMinimumX = minimumX;
+ EffectiveMaximumX = maximumX;
+
+ EffectiveRangeXChanged?.Invoke(this, new RangeChangedEventArgs()
+ {
+ Maximum = minimumX,
+ Minimum = maximumX,
+ });
+ }
+
+ /// <summary>
+ /// Raises the <see cref="EffectiveRangeYChanged"/> event.
+ /// </summary>
+ /// <param name="minimumY">The minimum y.</param>
+ /// <param name="maximumY">The maximum y.</param>
+ protected virtual void OnEffectiveRangeYChanged(GraphDataPointBase minimumY, GraphDataPointBase maximumY)
+ {
+ EffectiveMinimumY = minimumY;
+ EffectiveMaximumY = maximumY;
+
+ EffectiveRangeYChanged?.Invoke(this, new RangeChangedEventArgs()
+ {
+ Maximum = minimumY,
+ Minimum = maximumY,
+ });
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ /// <summary>
+ /// Renders the and draws a complete set of data series and their data points.
+ /// </summary>
+ /// <param name="toRender">To render.</param>
+ private void RenderAndDraw(List<KeyValuePair<TDataSeries, PendingSeries>> toRender)
+ {
+ lock (_render_lock)
+ {
+ var all_series = toRender.Select(x => x.Value);
+
+ foreach (var series in toRender)
+ {
+ var points = OnRender(all_series, series.Key, series.Value);
+
+ series.Value.RenderedItems = series.Value.XX.Count;
+
+ if (points.Count > 1)
+ {
+ Output.Draw(all_series.Select(x => x.Series), series.Value.Series, points);
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region Event Handlers
+
+ /// <summary>
+ /// Handles the controller range changed event.
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The event arguments.</param>
+ private void Input_RangeChanged(object sender, GraphRange<XDataPoint, YDataPoint> e)
+ {
+ OnEffectiveRangeYChanged(Input.Range.MinimumY, Input.Range.MaximumY);
+ OnEffectiveRangeXChanged(EffectiveMinimumX, Input.Range.MaximumX);
+ }
+
+ #endregion
+ }
+}