在WPF中使用ScottPlot库,进行简单的Binding封装

发布时间 2023-06-21 09:35:14作者: 八爪鱼~

封装支持Binding

  正好项目有个需求,需要使用图表库。在了解一番后,选择使用ScottPlot库,据说性能很不错,就是不支持Binding。想着在该基础上进行封装一下,对此进行简单记录一下。希望有了解这块的小伙伴,指导一下!只是使用,如果需要相关的库案例及API,可以看一下官网教程:ScottPlot

原理:
  • 创建一个自定义控件,继承Control

    • 前台Xaml文件中自定义样式,在样式中包一个WPFScottPlot控件,并且给该控件一个命名

    • 在后台VS文件中通过OnApplyTemplate()方法,找到前台Xaml中的WPFScottplot控件,给该控件进行一系列的需求配置

    • 定义相关依赖属性,如果依赖属性是用来配置折线图的话,可以在OnRender()方法中进行初始化。并且,在该依赖属性中使用new FrameworkPropertyMetadata()参数进行初始化,该方法中设置属性FrameworkPropertyMetadataOptions.AffectsRender,这样每次Xaml中改变该依赖属性的时候,都会调用后台cs文件中的OnRender()方法。从而立马改变初始化的设置样式。

  • ViewModel中的double属性值,在Xaml中进行Binding

    • 后台cs文件中,创建一个double类型的依赖属性,用于接收ViewModel中的属性值

    • 在该依赖属性中,设置回调函数,ViewModel中属性值发生改变的时候,触发此回调函数

    • 回调函数中接收此次改变的值,将此值存储在一个集合中

    • 开辟一个DispatcherTimer线程,进行数据的渲染(设置好渲染的间隔时间)

    • (如果ViewModel中的数据变化周期时间,符合自己的预期时间,可以直接在依赖属性中的回调方法中进行渲染)

 相关代码:
using Octopus.Shared.Extension;
using ScottPlot;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.IO;
using Octopus.Shared.Components.Fields;
using System.Windows.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

namespace Octopus.UI.Controls
{
    /// <summary>
    /// 基于ScottPlot图表库封装自己的图表库(支持MVVM-binding)
    /// </summary>
    public class OctPlot : Control
    {
        //  xaml中的图表对象
        private WpfPlot _plot;
        //  存放实时时间/Y轴值的集合
        private OctRealTimePlotList<double> _realTimeList;
        //  存放实时时间/Y轴值的集合
        private OctRealTimePlotList<double> _realTimeList2;
        //  将数据渲染至UI画面
        private DispatcherTimer _renderTimer;
        //  折线1的颜色
        private System.Drawing.Color _lineColor1;
        //  折线2的颜色
        private System.Drawing.Color _lineColor2;

        /// <summary>
        /// ctor    
        /// </summary>
        static OctPlot()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(OctPlot), new FrameworkPropertyMetadata(typeof(OctPlot)));
        }

        public CornerRadius BorderHostCornerRadius
        {
            get { return (CornerRadius)GetValue(BorderHostCornerRadiusProperty); }
            set { SetValue(BorderHostCornerRadiusProperty, value); }
        }


        public string PlotTitle
        {
            get { return (string)GetValue(PlotTitleProperty); }
            set { SetValue(PlotTitleProperty, value); }
        }


        public string XAxisLogo
        {
            get { return (string)GetValue(XAxisLogoProperty); }
            set { SetValue(XAxisLogoProperty, value); }
        }


        public string YAxisLogo
        {
            get { return (string)GetValue(YAxisLogoProperty); }
            set { SetValue(YAxisLogoProperty, value); }
        }


        public Brush AxisBackground
        {
            get { return (Brush)GetValue(AxisBackgroundProperty); }
            set { SetValue(AxisBackgroundProperty, value); }
        }


        public Brush DataDailBackground
        {
            get { return (Brush)GetValue(DataDailBackgroundProperty); }
            set { SetValue(DataDailBackgroundProperty, value); }
        }


        public Brush GridLineBackground
        {
            get { return (Brush)GetValue(GridLineBackgroundProperty); }
            set { SetValue(GridLineBackgroundProperty, value); }
        }


        public Brush AxisTicksBackground
        {
            get { return (Brush)GetValue(AxisTicksBackgroundProperty); }
            set { SetValue(AxisTicksBackgroundProperty, value); }
        }


        public Brush AxisTitleBackground
        {
            get { return (Brush)GetValue(AxisTitleBackgroundProperty); }
            set { SetValue(AxisTitleBackgroundProperty, value); }
        }


        public Brush PlotTitleBackground
        {
            get { return (Brush)GetValue(PlotTitleBackgroundProperty); }
            set { SetValue(PlotTitleBackgroundProperty, value); }
        }


        public double YValue
        {
            get { return (double)GetValue(YValueProperty); }
            set { SetValue(YValueProperty, value); }
        }


        public double YAxisMaxLimit
        {
            get { return (double)GetValue(YAxisMaxLimitProperty); }
            set { SetValue(YAxisMaxLimitProperty, value); }
        }


        public double YAxisMinLimit
        {
            get { return (double)GetValue(YAxisMinLimitProperty); }
            set { SetValue(YAxisMinLimitProperty, value); }
        }


        public double XAxisMaxLimit
        {
            get { return (double)GetValue(XAxisMaxLimitProperty); }
            set { SetValue(XAxisMaxLimitProperty, value); }
        }


        public double XAxisMinLimit
        {
            get { return (double)GetValue(XAxisMinLimitProperty); }
            set { SetValue(XAxisMinLimitProperty, value); }
        }


        public bool IsSetYAxisLimit
        {
            get { return (bool)GetValue(IsSetYAxisLimitProperty); }
            set { SetValue(IsSetYAxisLimitProperty, value); }
        }


        public bool IsSetXAxisLimit
        {
            get { return (bool)GetValue(IsSetXAxisLimitProperty); }
            set { SetValue(IsSetXAxisLimitProperty, value); }
        }


        public int DataLineCount
        {
            get { return (int)GetValue(DataLineCountProperty); }
            set { SetValue(DataLineCountProperty, value); }
        }


        public int DataPointCount
        {
            get { return (int)GetValue(DataPointCountProperty); }
            set { SetValue(DataPointCountProperty, value); }
        }


        public double YValueLine2
        {
            get { return (double)GetValue(YValueLine2Property); }
            set { SetValue(YValueLine2Property, value); }
        }


        public Brush YLineColor1
        {
            get { return (Brush)GetValue(YLineColor1Property); }
            set { SetValue(YLineColor1Property, value); }
        }


        public Brush YLineColor2
        {
            get { return (Brush)GetValue(YLineColor2Property); }
            set { SetValue(YLineColor2Property, value); }
        }

        //  折线2的颜色
        public static readonly DependencyProperty YLineColor2Property =
            DependencyProperty.Register("YLineColor2", typeof(Brush), typeof(OctPlot), new PropertyMetadata(Brushes.LightBlue));


        //  折线1的颜色
        public static readonly DependencyProperty YLineColor1Property =
            DependencyProperty.Register("YLineColor1", typeof(Brush), typeof(OctPlot), new PropertyMetadata(Brushes.Black));


        //  第二条折线的Y值
        public static readonly DependencyProperty YValueLine2Property =
            DependencyProperty.Register("YValueLine2", typeof(double), typeof(OctPlot), new PropertyMetadata(0d, OnYValueLine2Changed));


        //  图表中数据点数上限,超过此点数就保存成图片
        public static readonly DependencyProperty DataPointCountProperty =
            DependencyProperty.Register("DataPointCount", typeof(int), typeof(OctPlot), new PropertyMetadata(3600));


        //  渲染的数据线的数量
        public static readonly DependencyProperty DataLineCountProperty =
            DependencyProperty.Register("DataLineCount", typeof(int), typeof(OctPlot), new FrameworkPropertyMetadata(1
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  X轴是否设置限制并且不能随意拖动(限制的值,为坐标轴的最大最小值)
        public static readonly DependencyProperty IsSetXAxisLimitProperty =
            DependencyProperty.Register("IsSetXAxisLimit", typeof(bool), typeof(OctPlot), new FrameworkPropertyMetadata(default(bool)
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  Y轴是否设置限制并且不能随意拖动(限制的值,为坐标轴的最大最小值)
        public static readonly DependencyProperty IsSetYAxisLimitProperty =
            DependencyProperty.Register("IsSetYAxisLimit", typeof(bool), typeof(OctPlot), new FrameworkPropertyMetadata(default(bool)
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  设置X轴显示的最小值
        public static readonly DependencyProperty XAxisMinLimitProperty =
            DependencyProperty.Register("XAxisMinLimit", typeof(double), typeof(OctPlot), new FrameworkPropertyMetadata(0d
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  设置X轴显示的最大值
        public static readonly DependencyProperty XAxisMaxLimitProperty =
            DependencyProperty.Register("XAxisMaxLimit", typeof(double), typeof(OctPlot), new FrameworkPropertyMetadata(100d
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  设置Y轴显示的最小值
        public static readonly DependencyProperty YAxisMinLimitProperty =
            DependencyProperty.Register("YAxisMinLimit", typeof(double), typeof(OctPlot), new FrameworkPropertyMetadata(0d
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  设置Y轴显示的最大值
        public static readonly DependencyProperty YAxisMaxLimitProperty =
            DependencyProperty.Register("YAxisMaxLimit", typeof(double), typeof(OctPlot), new FrameworkPropertyMetadata(100d
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  图表Y轴的数据(单个数值)
        public static readonly DependencyProperty YValueProperty =
            DependencyProperty.Register("YValue", typeof(double), typeof(OctPlot), new PropertyMetadata(default(double), OnYValueChanged));


        //  图表的标题前景色
        public static readonly DependencyProperty PlotTitleBackgroundProperty =
            DependencyProperty.Register("PlotTitleBackground", typeof(Brush), typeof(OctPlot), new FrameworkPropertyMetadata(Brushes.Transparent
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  坐标轴标题的前景色
        public static readonly DependencyProperty AxisTitleBackgroundProperty =
            DependencyProperty.Register("AxisTitleBackground", typeof(Brush), typeof(OctPlot), new FrameworkPropertyMetadata(Brushes.Transparent
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  坐标轴刻度的前景色
        public static readonly DependencyProperty AxisTicksBackgroundProperty =
            DependencyProperty.Register("AxisTicksBackground", typeof(Brush), typeof(OctPlot), new FrameworkPropertyMetadata(Brushes.Transparent
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  表盘的网格线的前景色
        public static readonly DependencyProperty GridLineBackgroundProperty =
            DependencyProperty.Register("GridLineBackground", typeof(Brush), typeof(OctPlot), new FrameworkPropertyMetadata(Brushes.Transparent
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  折线表盘的背景色
        public static readonly DependencyProperty DataDailBackgroundProperty =
            DependencyProperty.Register("DataDailBackground", typeof(Brush), typeof(OctPlot), new FrameworkPropertyMetadata(Brushes.LightBlue
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  坐标轴前景色
        public static readonly DependencyProperty AxisBackgroundProperty =
            DependencyProperty.Register("AxisBackground", typeof(Brush), typeof(OctPlot), new FrameworkPropertyMetadata(Brushes.Aqua
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  Plot的纵坐标轴标识
        public static readonly DependencyProperty YAxisLogoProperty =
            DependencyProperty.Register("YAxisLogo", typeof(string), typeof(OctPlot), new FrameworkPropertyMetadata(string.Empty
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  Plot的横坐标轴标识
        public static readonly DependencyProperty XAxisLogoProperty =
            DependencyProperty.Register("XAxisLogo", typeof(string), typeof(OctPlot), new FrameworkPropertyMetadata(string.Empty
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  Plot的标题
        public static readonly DependencyProperty PlotTitleProperty =
            DependencyProperty.Register("PlotTitle", typeof(string), typeof(OctPlot), new FrameworkPropertyMetadata(string.Empty
                , FrameworkPropertyMetadataOptions.AffectsRender));


        //  外层Border的圆角
        public static readonly DependencyProperty BorderHostCornerRadiusProperty =
            DependencyProperty.Register("BorderHostCornerRadius", typeof(CornerRadius), typeof(OctPlot), new PropertyMetadata(new CornerRadius(0d)));

        /// <summary>
        /// 第二条折线的值
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        /// <exception cref="NotImplementedException"></exception>
        private static void OnYValueLine2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is OctPlot plot)
            {
                _tempList2.Add(Math.Round(plot.YValueLine2, 3));

                if (_tempList2.Count >= 10)
                {
                    //  抽稀算法,得到平均值
                    var res = GetThinningData(_tempList2);

                    //  向集合中添加Y轴的数据
                    plot._realTimeList2.GetYs().Add(res);

                    //  向集合中添加X轴的数据
                    plot._realTimeList2.GetXs().Add(DateTime.Now.ToOADate());

                    _tempList2.Clear();
                }
            }
        }

        //  存放实际读到的Y值1
        private static List<double> _tempList1;

        //  存放实际读到的Y值2
        private static List<double> _tempList2;


        /// <summary>
        /// 当Y轴的数据变化的回调
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void OnYValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is OctPlot plot)
            {
                Stopwatch stopwatch = Stopwatch.StartNew();



                _tempList1.Add(Math.Round(plot.YValue, 3));

                if (_tempList1.Count >= 9)
                {
                    //  抽稀算法,得到平均值
                    var res = GetThinningData(_tempList1);

                    //  向集合中添加Y轴的数据
                    plot._realTimeList.GetYs().Add(res);

                    //  向集合中添加X轴的数据
                    plot._realTimeList.GetXs().Add(DateTime.Now.ToOADate());

                    _tempList1.Clear();
                }


                stopwatch.Stop();

                Console.WriteLine($"时间:--{stopwatch.Elapsed}");
            }
        }

        /// <summary>
        /// 当子类使用模板的时候进行一系列的逻辑(相关数据的初始化)
        /// </summary>
        public override void OnApplyTemplate()
        {
            //  获取Xaml中的图表对象
            _plot = this.GetTemplateChild("RealTimePlot") as WpfPlot;
            _plot.Plot.Width = 600;
            _plot.Plot.Height = 600;
            //  实时数据集合(没有使用官方的数组类型,使用集合的方法进行实现)
            _realTimeList = new OctRealTimePlotList<double>();
            _realTimeList2 = new OctRealTimePlotList<double>();
            _tempList1 = new List<double>();
            _tempList2 = new List<double>();

            _lineColor1 = MediaBrushToDrawingColor(YLineColor1);
            ValidateColorData(_lineColor1);
            _lineColor2 = MediaBrushToDrawingColor(YLineColor2);
            ValidateColorData(_lineColor2);

            _renderTimer = new DispatcherTimer();
            _renderTimer.Interval = TimeSpan.FromSeconds(1);
            _renderTimer.Tick -= RenderTimer_Tick;
            _renderTimer.Tick += RenderTimer_Tick;
            _renderTimer.Start();

            base.OnApplyTemplate();
        }

        /// <summary>
        /// 抽稀算法
        /// </summary>
        /// <param name="datas"></param>
        /// <returns></returns>
        private static double GetThinningData(List<double> datas)
        {
            return datas.Sum() / datas.Count;
        }

        /// <summary>
        /// 将数据渲染至UI画面
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        /// <exception cref="NotImplementedException"></exception>
        private void RenderTimer_Tick(object sender, EventArgs e)
        {
            //  至少有一个数据
            if (_realTimeList.Count > 0 && _realTimeList2.Count > 0)
            {

                //  折线盘清盘
                _plot.Plot.Clear();

                //  添加折线1的数据
                _plot.Plot.AddScatter(_realTimeList.GetXs().ToArray()
                    , _realTimeList.GetYs().ToArray()
                    , label: "小车电流"
                    , color: _lineColor1);

                //  添加折线2的数据
                _plot.Plot.AddScatter(_realTimeList2.GetXs().ToArray()
                    , _realTimeList2.GetYs().ToArray()
                    , label: "喷头电流"
                    , color: _lineColor2);

                //  折线标识说明
                _plot.Plot.Legend();

                _plot.Refresh();

                //  图表中得数据大于DataPointCount值,将此数据保存成图片,保存在路径中
                if (_realTimeList.Count > DataPointCount)
                {
                    //  图表得宽度
                    int plotWidth = (int)Math.Ceiling(_plot.Plot.Width);

                    //  图表得高度
                    int plotHeight = (int)Math.Ceiling(_plot.Plot.Height);

                    var bmp = new System.Drawing.Bitmap(plotWidth, plotHeight);
                    _plot.Plot.Render(bmp);

                    try
                    {
                        string path = Fields._logDataDirectory + "\\" + DateTime.Now.ToString("D") + "\\" + "DataImages" + "\\"
                            + DateTime.Now.ToString("D") + "\\";
                        string imageName = +DateTime.Now.Minute + "h.png";

                        if (!Directory.Exists(path))
                        {
                            Directory.CreateDirectory(path);
                        }
                        bmp.Save(path + imageName);

                    }
                    catch (Exception)
                    {
                        throw;
                    }

                    //  Path = "Fields._logDataDirectory + "\\" + DateTime.Now.ToString("D") + "\\" + DataImage"
                    //var str = plot._plot.Plot.SaveFig(Fields._logDataDirectory + "\\" + DateTime.Now.ToString("D") + "\\" + "DataImages"
                    //    , width: plotWidth
                    //    , height: plotHeight
                    //    , lowQuality: false
                    //    , scale: 1);

                    //  在折线图中数据点的数量达到设定值后,将值清空,绘制新值
                    _realTimeList.GetXs().Clear();
                    _realTimeList.GetYs().Clear();
                    _realTimeList2.GetXs().Clear();
                    _realTimeList2.GetYs().Clear();
                }
            }
        }


        //  OnApplyTemplate()方法先执行,然后执行OnRender()方法
        /// <summary>
        /// UI渲染
        /// </summary>
        /// <param name="drawingContext"></param>
        protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
        {
            if (_plot != null)
            {
                _plot.Plot.Title(PlotTitle);
                _plot.Plot.XLabel(XAxisLogo);
                _plot.Plot.YLabel(YAxisLogo);
                _plot.Plot.XAxis.DateTimeFormat(true);

                //  判断X轴限制值是否相等
                ValidateAxisLimit(XAxisMinLimit, XAxisMaxLimit);

                if (XAxisMinLimit > XAxisMaxLimit)
                {
                    _plot.Plot.SetAxisLimitsX(XAxisMaxLimit, XAxisMinLimit);
                    SetXAxisLimit(_plot, XAxisMaxLimit, XAxisMinLimit);
                }
                else
                {
                    _plot.Plot.SetAxisLimitsX(XAxisMinLimit, XAxisMaxLimit);
                    SetXAxisLimit(_plot, XAxisMinLimit, XAxisMaxLimit);
                }

                //  判断Y轴限制值是否相等
                ValidateAxisLimit(YAxisMinLimit, YAxisMaxLimit);

                if (YAxisMinLimit > YAxisMaxLimit)
                {
                    _plot.Plot.SetAxisLimitsY(YAxisMaxLimit, YAxisMinLimit);
                    SetYAxisLimit(_plot, YAxisMaxLimit, YAxisMinLimit);
                }
                else
                {
                    _plot.Plot.SetAxisLimitsY(YAxisMinLimit, YAxisMaxLimit);
                    SetYAxisLimit(_plot, YAxisMinLimit, YAxisMaxLimit);
                }


                #region 设置坐标轴部分/折线盘背景颜色
                var axisBackground = MediaBrushToDrawingColor(AxisBackground);
                ValidateColorData(axisBackground);
                var dataDailBackground = MediaBrushToDrawingColor(DataDailBackground);
                ValidateColorData(dataDailBackground);
                var gridLineBackground = MediaBrushToDrawingColor(GridLineBackground);
                ValidateColorData(gridLineBackground);
                var axisTicksBackground = MediaBrushToDrawingColor(AxisTicksBackground);
                ValidateColorData(axisTicksBackground);
                var axisTitleBackground = MediaBrushToDrawingColor(AxisTitleBackground);
                ValidateColorData(axisTicksBackground);
                var plotTitleBackground = MediaBrushToDrawingColor(PlotTitleBackground);
                ValidateColorData(plotTitleBackground);
                _plot.Plot.Style(figureBackground: axisBackground
                    , dataBackground: dataDailBackground
                    , grid: gridLineBackground
                    , tick: axisTicksBackground
                    , axisLabel: axisTitleBackground
                    , titleLabel: plotTitleBackground
                    , dataBackgroundImage: null
                    , figureBackgroundImage: null);
                #endregion





                _plot.Refresh();
            }

            base.OnRender(drawingContext);
        }

        /// <summary>
        /// 将System.Windows.Media.Brush类型转换为System.Drawing.Color类型
        /// </summary>
        /// <param name="brush"></param>
        /// <returns></returns>
        private System.Drawing.Color MediaBrushToDrawingColor(Brush brush)
        {
            if (brush is SolidColorBrush solidBrush)
            {
                System.Windows.Media.Color mc = solidBrush.Color;

                return System.Drawing.Color.FromArgb(mc.A, mc.R, mc.G, mc.B);
            }
            else
                return System.Drawing.Color.Empty;

        }

        /// <summary>
        /// 转换结果校正
        /// </summary>
        /// <param name="color"></param>
        /// <exception cref="InvalidCastException"></exception>
        private void ValidateColorData(System.Drawing.Color color)
        {
            if (color == System.Drawing.Color.Empty)
                throw new InvalidCastException("颜色转换失败");
        }

        private void ValidateAxisLimit(double minLimit, double maxLimit)
        {
            if (minLimit == maxLimit)
            {
                throw new ArgumentOutOfRangeException("限制范围错误");
            }
        }

        /// <summary>
        /// 是否设置Y轴防滚动限制
        /// </summary>
        /// <param name="plot"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        private void SetYAxisLimit(WpfPlot plot, double min, double max)
        {
            if (IsSetYAxisLimit)
            {
                plot.Plot.YAxis.SetBoundary(min, max);
            }
        }

        /// <summary>
        /// 是否设置X轴防滚动限制
        /// </summary>
        /// <param name="plot"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        private void SetXAxisLimit(WpfPlot plot, double min, double max)
        {
            if (IsSetXAxisLimit)
            {
                plot.Plot.XAxis.SetBoundary(min, max);
            }
        }
    }
}

用于盛放渲染数据的集合:

    /// <summary>
    /// 应用于实时折现数据表的集合
    /// </summary>
    public class OctRealTimePlotList<T> : ScatterPlotList<T>
    {
        /// <summary>
        /// 返回X轴点的集合
        /// </summary>
        /// <returns></returns>
        public List<T> GetXs()
        {
            //  将父类中的集合Xs返回
            return Xs;
        }

        /// <summary>
        /// 返回Y轴点的集合
        /// </summary>
        /// <returns></returns>
        public List<T> GetYs()
        {
            //  将父类中的集合Ys返回
            return Ys;
        }
    }

继承ScatterPlotList<T>类,可以使用集合样式,进行数据的增删。ScottPlot官方使用数组进行数据的存储及渲染。在应广大开发者的需求后,开放了ScatterPlotList<T>类,使用集合进行数据的存取,在进行渲染的时候,将集合转换为数据 ToArrary()方法。

 

问题点:
  1. 软件关闭的时候,存储渲染数据的集合有没有释放

  2. 多条渲染折线,横坐标(时间轴),保证统一,待解决

  3. 渲染期间,我想要观看历史数据,待解决