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编辑  收藏  举报

导航