赞
踩
首先:简单 介绍下Scottplot这个免费的开源图标组件库。
ScottPlot 是一个 .NET 图表组件, 主要有以下特点:
其次:说一下使用的体验。
介绍方法前,可以先看下效果,如下图:
下图的数据来自于传感器,通过USB转RS232来读取传感器的值来进行实时显示。
再次:我们先看下官方给的几个方案:
Plot Live, Changing Data - ScottPlot FAQ
方案一:Changing Fixed-Length Data
简介:通过一个定时器不断的更新一个固定大小的double数组,来完成实时刷新Y轴的值。
- readonly double[] Values = new double[25];
- readonly Stopwatch Stopwatch = Stopwatch.StartNew();
-
- public Form1()
- {
- InitializeComponent();
- UpdateValues();
- formsPlot1.Plot.AddSignal(Values);
- }
-
- public void UpdateValues()
- {
- double phase = Stopwatch.Elapsed.TotalSeconds;
- double multiplier = 2 * Math.PI / Values.Length;
- for (int i = 0; i < Values.Length; i++)
- Values[i] = Math.Sin(i * multiplier + phase);
- }
-
- private void timer1_Tick(object sender, EventArgs e)
- {
- UpdateValues();
- formsPlot1.Render();
- }
这个方案的缺点:
使用固定大小的数组的话,初始化的时候,在没有数据的时候,会显示一条和数组大小相等长度的直线,然后数据上来后,会从数组的最后开始更新数据。所以前面会看到一条直线。
如何解决这个问题:
1. 需要配合IPlottable具体实现类中的MaxRenderIndex来去掉那条线条,例如刚开始初始话的时候,设置:MaxRenderIndex = 0;
2. 然后在实时值上来时候,更新MaxRenderIndex,并更新Y轴中的值。
3. 如果MaxRenderIndex 的值大于数组的大小了,就让它等于数组的大小。注意这个大小不能超过数组的大小,否则会报数组越界异常。
- 部分代码:
-
- private static SignalPlot RealTimeSignalPlot;
-
- RealTimeSignalPlot = RealTimeContentPlot.Plot.AddSignal(LiveData);
-
- RealTimeSignalPlot.MaxRenderIndex = 0;
-
- public void UpdateDataDopplerRadar(object currentValue)
- {
- //要对这个方法进行扩展
- // 1. 这里面判断数组实时值的个数是否大于数组大小,如果小于数组大小,就从数组当前大小开始更新。
- // 对 RealTimeSignalPlot.MaxRenderIndex 赋值未实时值得个数。
- if (ApplicationContext.LiveDataCount < ApplicationContext.LiveDataLength)
- {
- ApplicationContext.LiveData[ApplicationContext.LiveDataCount] = (double)currentValue;
- RealTimeSignalPlot.MaxRenderIndex = ApplicationContext.LiveDataCount;
- ApplicationContext.LiveDataCount++;
- }
- else
- {
- // 2. 如果实时值的个数等于了数组大小,就执行下面这部分代码,然后对RealTimeSignalPlot.MaxRenderIndex 进行重新赋值。
-
- //"scroll" the whole chart to the left
- Array.Copy(ApplicationContext.LiveData, 1, ApplicationContext.LiveData, 0, ApplicationContext.LiveData.Length - 1);
- //place the newest data point at the end
- ApplicationContext.LiveData[ApplicationContext.LiveData.Length - 1] = (double)currentValue;
-
- }
- if (RealTimeSignalPlot.MaxRenderIndex >= ApplicationContext.LiveDataLength)
- {
- RealTimeSignalPlot.MaxRenderIndex = ApplicationContext.LiveDataLength - 1;
- }
-
- RealTimeContentPlot.Refresh();
- }
-
-
-
方案二:Growing Data with Partial Array Rendering
代码自己看,就不解释了。和方案一基本差不多。
- readonly double[] Values = new double[100_000];
- readonly ScottPlot.Plottable.SignalPlot SignalPlot;
- int NextPointIndex = 0;
-
- public Form1()
- {
- InitializeComponent();
- SignalPlot = formsPlot1.Plot.AddSignal(Values);
- formsPlot1.Plot.SetAxisLimits(0, 100, -2, 2);
- }
-
- // This timer adds data frequently (1000 times / second)
- private void timer1_Tick(object sender, EventArgs e)
- {
- Values[NextPointIndex] = Math.Sin(NextPointIndex * .05);
- SignalPlot.MaxRenderIndex = NextPointIndex;
- NextPointIndex += 1;
- }
-
- // This timer renders infrequently (10 times per second)
- private void timer2_Tick(object sender, EventArgs e)
- {
- // adjust the axis limits only when needed
- double currentRightEdge = formsPlot1.Plot.GetAxisLimits().XMax;
- if (NextPointIndex > currentRightEdge)
- formsPlot1.Plot.SetAxisLimits(xMax: currentRightEdge + 100);
-
- formsPlot1.Render();
- }
接下来我们重点来看基于官方代码进行扩展的方案三:
官方给的实时绘图都是基于double数组的。但是留意到官方最后说了一下 ScatterPlotList。
于是自己就去看了下 ScatterPlotList 这个类。
看下这个类的简介:
注意里面提到的 has Add() methods to easily add data.
然后看到类名有个List,那就说明它可以用类似List中Add的方法来给图上的曲线增加一个数据。
- /// <summary>
- /// A collection of X/Y coordinates that can be displayed as markers and/or connected lines.
- /// Unlike the regular ScatterPlot, this plot type has Add() methods to easily add data.
- /// </summary>
- public class ScatterPlotList<T> : IPlottable
看到这里,突然发现,如有有List的方法,那实现动态折线图不是很简单了吗?
接着继续看代码,发现这个类里面就有一个public void Add(T x, T y) 和 public void Clear()方法。
没有类似RemoveAt()和Remove的方法。什么意思?
问题一:不能动态Remove掉Xs里面值。
- /// <summary>
- /// Clear the list of points
- /// </summary>
- public void Clear()
- {
- Xs.Clear();
- Ys.Clear();
- }
-
- /// <summary>
- /// Add a single point to the list
- /// </summary>
- public void Add(T x, T y)
- {
- Xs.Add(x);
- Ys.Add(y);
- }
心不甘,继续看代码:
这个类里面的Xs,Ys都是一个List,而且是 protected的,
问题二:ScatterPlotList<T> 这个类没有提供访问的方法。怎么办?
既然不让我在父类直接访问,那我就直接继承这个类ScatterPlotList<T>来访问Xs和Ys.
不就可以解决了。
-
- protected readonly List<T> Xs = new();
- protected readonly List<T> Ys = new();
问题三:自己写的继承类,如何生成图表呢?
还得继续看代码:
看下 Plot.AddScatterList<double>() 这个方法怎么实现的。
源代码里面直接new一个ScatterPlotList对象,然后Add里面,就返回了这个对象。
- /// <summary>
- /// Scatter plot with Add() and Clear() methods for updating data
- /// </summary>
- public ScatterPlotList<double> AddScatterList(
- Color? color = null,
- float lineWidth = 1,
- float markerSize = 5,
- string label = null,
- MarkerShape markerShape = MarkerShape.filledCircle,
- LineStyle lineStyle = LineStyle.Solid)
- {
- var spl = new ScatterPlotList<double>()
- {
- Color = color ?? GetNextColor(),
- LineWidth = lineWidth,
- MarkerSize = markerSize,
- Label = label,
- MarkerShape = markerShape,
- LineStyle = lineStyle
- };
-
- Add(spl);
- return spl;
- }
问题四:这个Add方法做了什么呢?
源码如下:很简单,而且是个public的。
- /// <summary>
- /// Add a plottable to the plot
- /// </summary>
- /// <param name="plottable">a plottable the user created</param>
- public void Add(IPlottable plottable)
- {
- settings.Plottables.Add(plottable);
- }
看到这的话,那我想,我自己写个类,继承这个类:public class ScatterPlotList<T> : IPlottable
然后在使用的时候,我new一个自己的这个类,再通过Plot.Add 加进去,不就可以了。
于是有了下面这些代码,来实现文章开头的实时动态折线图的效果:
- public class ScatterPlotListDouble<T> : ScatterPlotList<T>
- {
- public List<T> GetXs()
- {
- return Xs;
- }
-
- public List<T> GetYs()
- {
- return Ys;
- }
- }
初始化的时候和串口有数据的时候,调用下面这部分代码:
- ApplicationContext定义的几个变量
- public static double[] LiveData = new double[] { };
- public static double[] xs = new double[] { };
- public static int LiveDataLength = 1000;
-
-
- public partial class DataView
- {
- private static ScatterPlotListDouble<double> RealTimeSignalPlot;
-
- public DataView()
- {
- RealTimeSignalPlot = RealTimeSignalPlot ?? new ScatterPlotListDouble<double>()
- {
- Color = Color.FromArgb(68, 114, 196),
- MarkerSize = 3,
- Smooth = false
- };
-
- if (RealTimeSignalPlot.Count != 0)
- {
- ApplicationContext.xs = RealTimeSignalPlot.GetXs().ToArray();
- ApplicationContext.LiveData = RealTimeSignalPlot.GetYs().ToArray();
- RealTimeSignalPlot.Clear();
- }
- else
- {
- RealTimeSignalPlot.Add(DateTime.Now.ToOADate(), 0);
- }
-
- RealTimeContentPlot.Plot.Add(RealTimeSignalPlot);
- RealTimeSignalPlot.AddRange(ApplicationContext.xs, ApplicationContext.LiveData);
-
- RealTimeContentPlot.Plot.XAxis.DateTimeFormat(true);
-
- RealTimeContentPlot.Plot.AxisAuto();
- RealTimeContentPlot.Refresh();
- }
-
-
- // 这个方法是外部接口,每次串口有数据了,就调用这个来更新数据。
- public void UpdateData(object currentValue, DateTime now)
- {
-
- RealTimeSignalPlot.Add(now.ToOADate(), (double)currentValue);
-
- if (RealTimeSignalPlot.GetXs().Count > ApplicationContext.LiveDataLength)
- {
- RealTimeSignalPlot.GetXs().RemoveAt(0);
- RealTimeSignalPlot.GetYs().RemoveAt(0);
- }
-
- Dispatcher.Invoke(() =>
- {
- RealTimeContentPlot.Plot.AxisAuto();
- RealTimeContentPlot.Refresh();
- });
- }
- }
完结,通过以上方法,来使用List实现一个实时的动态效果图,比较方便。
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。