【ScottPlot】使用ScottPlot创建实时动态图
首先:简单 介绍下Scottplot这个免费的开源图标组件库。
ScottPlot 是一个 .NET 图表组件, 主要有以下特点:
- 适用范围广:同时适用于 WinForms, WPF, Avalonia, Console, 支持 .NET Framework 4.6.1 及以上, NET Core 2.0 至 .NET 5。
- 上手简单:只需几行代码即可创建折线图、条形图、饼图、散点图等。
- 性能强悍:千万级数据处理无压力, 媲美 Python Matplotlib。
- 可交互:支持用户和图表数据进行交互, 注入灵魂。
- 开源免费:基于MIT开源协议, 已经开源近5年, 不存在版权和收费问题
- 组件丰富:图表组件非常全面,可满足各种场景下的展示需求。
其次:说一下使用的体验。
- 性能的确非常强悍,亲测百万数据非常流程。
- 重点是这个库是开源的,看官方介绍,说scottplot 5 的版本性能会比4更强劲,期待中。
- 图表种类非常丰富。重点是2D图表库。
- 代码下载下来后,可以使用VS2022进行编译,注意:如果使用VS2019的话,需要支持.net6.
- 【缺点1】:不支持MVVM模式
- 【缺点2】:在图表上标注每个点的数据,没有其他图表库方便。
- 【缺点3】:要绘实时折线图表没有其他图表库方便。
- 下面介绍使用个人认为比较简单的方法在Scottplot实现实时动态图表的方法。
介绍方法前,可以先看下效果,如下图:
下图的数据来自于传感器,通过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 leftArray.Copy(ApplicationContext.LiveData, 1, ApplicationContext.LiveData, 0, ApplicationContext.LiveData.Length - 1);//place the newest data point at the endApplicationContext.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 neededdouble 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实现一个实时的动态效果图,比较方便。
【ScottPlot】使用ScottPlot创建实时动态图相关推荐
- R语言编写自定义函数、创建使用ggplot2生成图标(icon)的主题(theme)函数、使用ggplot2以及自定义的图标主题函数创建箱图(boxplot)图标、ggsave保存图标(png、svg
R语言编写自定义函数.创建使用ggplot2生成图标(icon)的主题(theme)函数.使用ggplot2以及自定义的图标主题函数创建箱图(boxplot)图标.ggsave保存图标(png.svg ...
- R语言使用forestplot包绘制森林图:编码创建森林图仿真数据、汇总线修改、元素位置调整、垂直线、字体、风格、置信区间、线型、图例、刻度、标签等
R语言使用forestplot包绘制森林图:编码创建森林图仿真数据.汇总线修改.元素位置调整.垂直线.字体.风格.置信区间.线型.图例.刻度.标签等 目录
- 使用StarUML创建类图
1.综述(What) StarUML是一种生成类图和其他类型的UML图表的工具.本文是一个使用StarUML创建类图(Java语言描述)的简明手册. StarUML(简称SU),是一种创建UML类图, ...
- lmbs PHP,PHP的GD2函数创建折线图源码示例
PHP的GD2函数创建折线图源码示例 代码来自 codego.net/tags/4/1/ if(!is_numeric($data[$i])) die("error id:1"); ...
- ITK:从给定的seeds创建距离图
ITK:从给定的seeds创建距离图 内容提要 C++实现代码 内容提要 从给定的seeds创建距离图 C++实现代码 #include "itkFastMarchingImageToNod ...
- Python | 使用matplotlib.pyplot创建线图
Problem statement: Write a program in python (using matplotlib.pyplot) to create a line plot. 问题陈述:用 ...
- Eclipse 答疑:Eclipse 使用 Amateras UML 创建类图点击 Finish 没反应解决方式汇总
文章目录 前言 一.问题产生场景 1.1.Amateras UML 创建类图没反应 二.问题原因分析 三.问题定位及解决 3.1.定位检查版本支持信息 3.2.问题确认过程 3.3.解决方式验证 四. ...
- python画误差图_Python数据可视化:如何创建误差图
一图胜千言,使用Python的matplotlib库,可以快速创建高质量的图形. 这是Python数据可视化的系列短篇,针对初级和中级用户,将理论和示例代码相结合,使用matplotlib, seab ...
- d3.js折线图_学习使用D3.js创建折线图
d3.js折线图 by Sohaib Nehal 通过Sohaib Nehal 学习使用D3.js创建折线图 (Learn to create a line chart using D3.js) 使用 ...
最新文章
- python-Django-01基础配置
- 谷歌三驾马车将成历史,创始人退位,皮查伊兼任母公司CEO
- struts2的date标签和其他标签
- 2000年考研英语阅读理解文章二
- mysql sql先后执行_MySQL中SQL语句执行顺序
- 【ElasticSearch】Es 源码之 SettingsModule 源码解读
- 【CCCC】L2-021 点赞狂魔 (25分),,模拟水题,map数组,间接排序
- discuz开发,登录次数过多,锁定解决方法
- Entity Framework之IQueryable和list本地集合
- linux系统 ghost,Linux下用GHOST来做系统备份
- WebUI自动化测试框架搭建从0到1(完整源码)更新完毕
- 了解下HTML5大前端是什么
- 计算机控制的仓库定位系统,(边江文档教材)智能立体仓库物品定位的plc自动控制系统.doc...
- 第五次:对比分析《大唐仙妖劫》和《梦幻西游》
- mysql 100w 查询耗时4秒_MySql百万数据0秒筛选查询
- UVA1592数据库
- Hexo-Matery主题细致美化
- 关于软件功能点评估的问题(一)
- 大文件如何传输,大文件的传输方式有哪些?
- 怎样用 16.7 个小时做 40 小时的工作