C#使用Oxyplot绘制监控界面
C#中可选的绘图工具有很多,除了Oxyplot还有DynamicDataDisplay(已经改名为InteractiveDataDisplay)等等。不过由于笔者这里存在一些环境上的特殊要求,.Net Framework的版本被限制在4,而InteractiveDataPlay要求的最低版本是4.5.2。所以选择了对版本要求较低的Oxyplot。
IDE为VS2012,创建工程后首先通过NuGet程序包管理器控制台,通过下述命令添加对Oxyplot的引用:
PM> Install-Package Oxyplot.Core
PM> Install-Package Oxyplot.Wpf
XAML里添加上必要的引用:
<Window x:Class="MonitorForm.MonitorForm" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:MonitorForm" xmlns:oxy="http://oxyplot.org/wpf" Title="MainWindow" Height="350" Width="525"> <Grid> <oxy:PlotView Model="{Binding Path= SimplePlotModel}"></oxy:PlotView> </Grid> </Window>
笔者的开发场景是需要统计四类数据,因此开放四个公共方法供外部调用:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.ComponentModel; using System.Threading; namespace MonitorForm { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MonitorForm : Window { private PlotViewModel _viewModel; public MonitorForm() { InitializeComponent(); _viewModel = new PlotViewModel(); this.DataContext = _viewModel; } public void addSendCount(int iCount) { _viewModel.addSendCount(iCount); } public void addUnsendCount(int iCount) { _viewModel.addUnsendCount(iCount); } public void addConfirmCount(int iCount) { _viewModel.addConfirmCount(iCount); } public void addDealCount(int iCount) { _viewModel.addDealCount(iCount); } } }
MVVM模式,需要添加ViewModel层:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using OxyPlot; using OxyPlot.Series; using OxyPlot.Axes; using System.Threading.Tasks; using System.Threading; using System.Collections.Concurrent; namespace MonitorForm { public class PlotViewModel { /// <summary> /// 画直线 /// </summary> public PlotModel SimplePlotModel { get; set; } //每条线对应一个队列用作实时数据统计 private ConcurrentQueue<int> queueSend = new ConcurrentQueue<int>(); private ConcurrentQueue<int> queueUnsend = new ConcurrentQueue<int>(); private ConcurrentQueue<int> queueConfirm = new ConcurrentQueue<int>(); private ConcurrentQueue<int> queueDeal = new ConcurrentQueue<int>(); public void addSendCount(int iCount) { queueSend.Enqueue(iCount); } public void addUnsendCount(int iCount) { queueUnsend.Enqueue(iCount); } public void addConfirmCount(int iCount) { queueConfirm.Enqueue(iCount); } public void addDealCount(int iCount) { queueDeal.Enqueue(iCount); } public int getSendCount() { int iSingle = 0; int iTotal = 0; while (queueSend.TryDequeue(out iSingle)) { iTotal += iSingle; } return iTotal; } public int getUnsendCount() { int iSingle = 0; int iTotal = 0; while (queueUnsend.TryDequeue(out iSingle)) { iTotal += iSingle; } return iTotal; } public int getConfirmCount() { int iSingle = 0; int iTotal = 0; while (queueConfirm.TryDequeue(out iSingle)) { iTotal += iSingle; } return iTotal; } public int getDealCount() { int iSingle = 0; int iTotal = 0; while (queueDeal.TryDequeue(out iSingle)) { iTotal += iSingle; } return iTotal; } public PlotViewModel() { SimplePlotModel = new PlotModel(); //创建于建立初始化数据节点 var lineSend = new LineSeries() { Title = "报送" }; lineSend.Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), 0)); SimplePlotModel.Series.Add(lineSend); var lineUnsend = new LineSeries() { Title = "待报送" }; lineUnsend.Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), 0)); SimplePlotModel.Series.Add(lineUnsend); var lineConfirm = new LineSeries() { Title = "确认" }; lineConfirm.Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), 0)); SimplePlotModel.Series.Add(lineConfirm); var lineDeal = new LineSeries() { Title = "成交" }; lineDeal.Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), 0)); SimplePlotModel.Series.Add(lineDeal); //定义y轴 LinearAxis leftAxis = new LinearAxis() { Position = AxisPosition.Left, Minimum = 0, Maximum = 100, Title = "笔数",//显示标题内容 TitlePosition = 0,//显示标题位置 MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.None, }; //定义x轴 报盘监控界面x轴统一为时间 DateTimeAxis bottomAxis = new DateTimeAxis() { Position = AxisPosition.Bottom, StringFormat = "hh:mm:ss", Minimum = DateTimeAxis.ToDouble(DateTime.Now), Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddMinutes(1)), Title = "时间", TitlePosition = 0, IntervalLength = 60, MinorIntervalType = DateTimeIntervalType.Seconds, IntervalType = DateTimeIntervalType.Seconds, MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.None, }; SimplePlotModel.Axes.Add(leftAxis); SimplePlotModel.Axes.Add(bottomAxis); bool bToMove = false; Task.Factory.StartNew(() => { while (true) { lineSend.Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), getSendCount())); lineUnsend.Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), getUnsendCount())); lineConfirm.Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), getConfirmCount())); lineDeal.Points.Add(new DataPoint(DateTimeAxis.ToDouble(DateTime.Now), getDealCount())); if (!bToMove) { //当前时间减去起始时间达到30秒后开始左移时间轴 TimeSpan timeSpan = DateTime.Now - DateTimeAxis.ToDateTime(bottomAxis.Minimum); if (timeSpan.TotalSeconds >= 30) { bToMove = true; } } else { //左移时间轴,跨度维持在60秒 bottomAxis.Minimum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(-30)); bottomAxis.Maximum = DateTimeAxis.ToDouble(DateTime.Now.AddSeconds(30)); //删除历史节点,防止DataPoint过多影响效率,也防止出现内存泄漏 lineSend.Points.RemoveAt(0); lineConfirm.Points.RemoveAt(0); lineUnsend.Points.RemoveAt(0); lineDeal.Points.RemoveAt(0); } //根据报单笔数判断是否需要更新y轴刻度 //首先找出四条统计线中当前最大的节点 double iMax = lineSend.MaxY; if (iMax < lineConfirm.MaxY) { iMax = lineConfirm.MaxY; } if (iMax < lineUnsend.MaxY) { iMax = lineUnsend.MaxY; } if (iMax < lineDeal.MaxY) { iMax = lineDeal.MaxY; } //如果当前的y轴最大刻度小于数据集中的最大值,放大 leftAxis.Maximum = iMax + (100 - iMax % 100); leftAxis.IntervalLength = leftAxis.Maximum / 5; //每秒刷新一次视图 SimplePlotModel.InvalidatePlot(true); Thread.Sleep(1000); } }); } } }
从上述代码可以看出,功能主要为实现了Y轴为笔数统计,而X轴为时间轴且当线画到了中间时开始左移时间轴。统计的间隔为1秒。
如果要导出DLL封装给其他程序使用的话,在项目属性中将输出类型调整为类库即可。这时可能会有例如不存在InitializeComponent()之类的报错,将App.xmal属性中的生成操作修改成无即可顺利导出。
自己写了个小DEMO调用该DLL,不停往里添加数据,效果图如下:
时间轴会左移,而Y轴的笔数统计也会根据当前DataPoint集合中的最大值进行相应的调整。
posted on 2018-09-13 09:55 DluT_eDdy 阅读(11971) 评论(0) 编辑 收藏 举报