/// <summary>
        /// Raises the <see cref="VirtualRangeChanged"/> event.
        /// </summary>
        /// <param name="minimumX">The minimum x.</param>
        /// <param name="maximumX">The maximum x.</param>
        /// <param name="minimumY">The minimum y.</param>
        /// <param name="maximumY">The maximum y.</param>
        protected virtual void OnVirtualRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY)
        {
            var range = new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY);

            VirtualRangeChanged?.Invoke(this, range);
        }
 /// <summary>
 /// Raises the <see cref="EffectiveRangeChanged"/> event.
 /// </summary>
 /// <param name="minimumX">The minimum x.</param>
 /// <param name="maximumX">The maximum x.</param>
 /// <param name="minimumY">The minimum y.</param>
 /// <param name="maximumY">The maximum y.</param>
 protected virtual void OnEffectiveRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY)
 {
     EffectiveRangeChanged?.Invoke(this, new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY));
 }
        /// <summary>
        /// The rendering thread method.
        /// </summary>
        private void RenderThreadMethod()
        {
            while (true)
            {
                if (!IsPaused)
                {
                    var pending_list = _pending_series_collection.BlockDequeue();

                    foreach (var pending_series in pending_list)
                    {
                        if (pending_series.IsClearSeries)
                        {
                            _pending_series_collection = new GraphDataQueue <List <PendingSeries> >();
                            _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 <Tuple <TDataSeries, IEnumerable <PointF> > > to_draw = new List <Tuple <TDataSeries, IEnumerable <PointF> > >();

                            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 <TDataSeries, IEnumerable <PointF> >(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);
                    }
                }
                else
                {
                    Thread.Sleep(RefreshRate);
                }
            }
        }
        private void Render()
        {
            if (_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;

                if (_to_render.Count > 0 && _to_render.First().Value.XX.Count > 0)
                {
                    min_x = _to_render.First().Value.XX.First();
                    max_x = _to_render.First().Value.XX.Last();
                }
                else
                {
                    return;
                }

                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)
                {
                    if (_range.AutoYFallbackMode == GraphRangeAutoYFallBackMode.MinMax)
                    {
                        min_y = _range.MinimumY;
                        max_y = _range.MaximumY;
                    }
                    else if (_range.AutoYFallbackMode == GraphRangeAutoYFallBackMode.Margins)
                    {
                        min_y -= _range.AutoYFallbackMargins;
                        max_y += _range.AutoYFallbackMargins;
                    }
                }

                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();

                    if (surface_size.Width > 0 && surface_size.Height > 0)
                    {
                        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 <Tuple <TDataSeries, IEnumerable <PointF> > > to_draw = new List <Tuple <TDataSeries, IEnumerable <PointF> > >();

                        var to_render = _to_render.Select(x => x.Value).ToList();

                        foreach (var item in to_render)
                        {
                            if (item.YY.Count > 0)
                            {
                                item.Series.CurrentValue = item.YY.Last().GetValue();
                            }

                            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 <TDataSeries, IEnumerable <PointF> >(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);
            }
        }
 /// <summary>
 /// Arranges the series of data points and returns a series of drawing points.
 /// </summary>
 /// <param name="surface">The target graph surface.</param>
 /// <param name="series">The instance of the current rendered data series.</param>
 /// <param name="range">Instance of graph range.</param>
 /// <param name="xx">Collection of x coordinates.</param>
 /// <param name="yy">Collection of y coordinates.</param>
 /// <param name="minimumX">The minimum x coordinates value.</param>
 /// <param name="maximumX">The maximum x coordinates value.</param>
 /// <param name="minimumY">The minimum y coordinates value.</param>
 /// <param name="maximumY">The maximum y coordinates value.</param>
 /// <returns></returns>
 public abstract IEnumerable <PointF> Render(IGraphSurface <TDataSeries> surface, TDataSeries series, IGraphRange range, List <GraphDataPoint> xx, List <GraphDataPoint> yy, GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY);