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集合中的最大值進行相應的調整。