使用ScottPlot在C#的WPF中创建实时动态波形图(示波器图形)并添加可移动的垂线(VerticalLine)

ScottPlot 是一个优秀的开源免费绘图控件,提供的多种图表样式。在测量中,使用ScottPlot绘制实时的示波器图形非常方便、简单以及有效。本文主要介绍在C#WPF中,如何通过ScottPlot实现示波器图形(实时动态波形图)的绘制。

使用ScottPlot在C#的WPF中创建实时动态波形图(示波器图形)并添加可移动的垂线(VerticalLine)

官方Demo下载地址:https://scottplot.net/demo/

ScottPlot

使用ScottPlot 可以做多种图表,

  • 信号图 (Signal Plot:具有均匀分布的Y点。信号图非常快速,可以交互显示数百万个数据点。有许多不同类型的可打印对象,每种对象都有不同的用途,还可以自定义轴标签和刻度范围。
  • 散点图 (Scatter Plot:具有成对的X/Y点; 
  • 柱状图 (Histogram) :一种以长方形的长度为变量的统计图表,其中一个轴表示需要对比的分类维度,另一个轴代表相应的数值,常用于统计数学中,对比分类数据的数值大小;
  • 饼图 (Pie Chart) :是将数字比例表示为圆的切片,可以显示每一数值相对于总数值的大小,其中切片可以用值、百分比和标签以及图例进行标记;
  • 气泡图 (Bubble Plot:气泡图在特定的X/Y位置显示圆圈,每个圆圈都可以单独定制。气泡图与散点图相似,不同之处在于,气泡图允许在图表中额外加入一个表示大小的变量。实际上,这就像以二维方式绘制包含三个变量的图表一样。气泡由大小不同的标记(指示相对重要程度)表示。气泡图还可以与其他图类型组合,以创建更高级的图表;
  • 雷达图 (Radar Chart:也称为蜘蛛图或星图,可以简洁地显示多个值。可以设置没有轴线雷达图,也可以将“轴类型”更改为“多边形”,显示具有直线的雷达图,还可以设置类别标签、将标记值显示在在雷达图的臂上。 
  • 烛台图 (Candlestick Chart:适用于金融行业,ScottPlot可以在X/Y空间的绘图上绘制一些财务指标,可以自定义蜡烛和灯芯的颜色等来实现暗模式财务图;
  • 热图/强度图 (Heatmap) :默认情况下,热图将值显示为带有锐边的矩形。启用平滑功能使用双三次插值将热图显示为值之间的平滑渐变;
ScottPlot图表

下面进入正题,实现在WPF中实时动态图(示波器图形)并鼠标右键添加垂线(VerticalLine)作区间展示以及实时更新VerticalLine的显示信息。支持可移动VerticalLine实时更新区间信息。

效果展示

实现细节

需要定义几个关键点:

1、实时绘图的数组,Y轴数据;

2、所有数据的List,从串口或其他方式来的所有数据;

3、实时绘图区绘制的点数Point,其实就是x轴有多少个点。

实现绘图,将数据点数Point作为要展示在当前页面的Double数组的长度,通过定时器轮询获取新数据来绘制实时图。定时器轮询的频率就是绘图UI的刷新率。

实现可移动的垂线(VerticalLine),需要给ScottPlot这个控件添加鼠标事件,右键点击(添加),左键点击(获取),鼠标松开(初始化VerticalLine对象),鼠标移动(移动VerticalLine)。

具体实现

1、Nuget安装ScottPlot.WPF控件

在NuGet包管理器中,选择“浏览”选项卡。搜索“ScottPlot.WPF”并选择这个包。点击“安装”按钮来添加包到你的项目。

2、Xaml文件添加ScottPlot控件并指定鼠标事件

xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
 
<ScottPlot:WpfPlot Grid.Row="0" Grid.ColumnSpan="3" x:Name="myPlot1" PreviewMouseWheel="Window_PreviewMouseWheel"  MouseLeftButtonDown="formsPlot1_MouseLeftButtonDown" MouseRightButtonDown="formsPlot1_MouseRightButtonDown"  MouseUp="formsPlot1_MouseUp" MouseMove="formsPlot1_MouseMove"/>

3、定义成员变量

double[] dataY; //实时UI绘制时的数组
List<Double> listDataY; //所有数据的集合
int index; //要截取的索引
VerticalLine v1; //第一根垂直线
VerticalLine v2; //第二根垂直线
VerticalLine PlottableBeingDragged; //可变化的垂直线对象
int Point; //实时集合的长度以及实时绘制时的数据点数

4、初始化

Point = 400; //x轴绘制的点数
index = 0;
dataY = new double[Point];
 
//改变控件添加内部的鼠标的事件
ScottPlot.Control.InputBindings customInputBindings = new ScottPlot.Control.InputBindings()
{
    DoubleClickButton = ScottPlot.Control.MouseButton.Right,
    ZoomInWheelDirection = ScottPlot.Control.MouseWheelDirection.Up,
    ZoomOutWheelDirection = ScottPlot.Control.MouseWheelDirection.Down,
    PanLeftWheelDirection = ScottPlot.Control.MouseWheelDirection.Down,
    PanRightWheelDirection = ScottPlot.Control.MouseWheelDirection.Up,
    DragPanButton = ScottPlot.Control.MouseButton.Left,
    ClickAutoAxisButton = ScottPlot.Control.MouseButton.Right,
 
};
 
ScottPlot.Control.Interaction interaction = new ScottPlot.Control.Interaction(myPlot1)
{
    Inputs = customInputBindings,
};
 
myPlot1.Interaction = interaction;
 
//改变控件的背景颜色
var myPlot = myPlot1.Plot;
 
myPlot.ShowLegend();
 
myPlot.YLabel("mA");
myPlot.Title(deviceId);
myPlot.ShowLegend();
 
// change figure colors
myPlot.FigureBackground.Color = Color.FromHex("#181818");
myPlot.DataBackground.Color = Color.FromHex("#1f1f1f");
 
// change axis and grid colors
myPlot.Axes.Color(Color.FromHex("#d7d7d7"));
myPlot.Grid.MajorLineColor = Color.FromHex("#404040");
 
// change legend colors
myPlot.Legend.BackgroundColor = Color.FromHex("#404040");
myPlot.Legend.FontColor = Color.FromHex("#d7d7d7");
myPlot.Legend.OutlineColor = Color.FromHex("#d7d7d7");
 
//给集合赋值
Array.Copy(dataY, 1, dataY, 0, dataY.Length - 1);
 
dataY[dataY.Length - 1] = Double.Parse(data.current);
 
listDataY = dataY.ToList();
 
 
myPlot.Add.Signal(dataY);
 
//自定义X轴刻度ticks
ScottPlot.TickGenerators.NumericManual 
ticks = new ScottPlot.TickGenerators.NumericManual();
 
// add major ticks with their labels
for (int i = 0; i < dataY.Length; i++)
{
    ticks.AddMajor(i, i.ToString() + "ms");
}
// tell the horizontal axis to use the custom tick generator
myPlot.Axes.Bottom.TickGenerator = ticks;
 
//y轴自适应
myPlot.Axes.AutoScaleY();
//清除右键选项
myPlot1.Menu.Clear();
//刷新
myPlot1.Refresh();

5、实时绘图,定时器执行的方法

var myPlot = myPlot1.Plot;
listDataY.Add(Double.Parse(data.current));
ScottPlot.TickGenerators.NumericManual ticks = new ScottPlot.TickGenerators.NumericManual();
 
//每进来一条数据就把数组的第一条数据给截取掉
if (listDataY.Count > int.Parse(Point))
{
    index++;
    int count = 0;
    Array.Copy(listDataY.ToArray(), index, dataY, 0, dataY.Length);
 
    //重绘刻度
    for (int i = index; i < listDataY.Count; i++)
    {
 
        if (count == 0)
        {
            ticks.AddMajor(count, i.ToString() + "ms");
        }
        if ((count + 1) % 10 == 0)
        {
            ticks.AddMajor(count, i.ToString() + "ms");
 
        }
        count++;
    }
}
 
myPlot.Axes.Bottom.TickGenerator = ticks;
myPlot.Axes.AutoScaleY();
myPlot1.Refresh();

6、鼠标事件实现

//鼠标松开事件
private void formsPlot1_MouseUp(object sender, MouseEventArgs e)
{
    PlottableBeingDragged = null;
    myPlot1.Interaction.Enable(); // enable panning again
    myPlot1.Refresh();
}
 
//鼠标移动事件
private void formsPlot1_MouseMove(object sender, MouseEventArgs e)
{
       
    Point p = e.GetPosition(myPlot1);
    Pixel mousePixel = new Pixel(p.X * myPlot1.DisplayScale, p.Y * myPlot1.DisplayScale);
    Coordinates coordinates = myPlot1.Plot.GetCoordinates(mousePixel);
    if (PlottableBeingDragged is null)
    {
        var lineUnderMouse = GetLineUnderMouse(coordinates.X);
        if (lineUnderMouse is null) Cursor = Cursors.Arrow;
        else if (lineUnderMouse.IsDraggable && lineUnderMouse is VerticalLine) Cursor = Cursors.SizeWE;
    }
    else
    {
        if (PlottableBeingDragged is VerticalLine v)
        {
 
            v.X = coordinates.X;
            v.Text = coordinates.X.ToString();
 
        }
    }
        myPlot1.Refresh();
}
 
//左键点击事件
private void formsPlot1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    Point p = e.GetPosition(myPlot1);
    Pixel mousePixel = new Pixel(p.X * myPlot1.DisplayScale, p.Y * myPlot1.DisplayScale);
    Coordinates coordinates = myPlot1.Plot.GetCoordinates(mousePixel);
    var lineUnderMouse = GetLineUnderMouse(coordinates.X);
    if (lineUnderMouse != null)
    {
        PlottableBeingDragged = lineUnderMouse;
        myPlot1.Interaction.Disable(); // disable panning while dragging
    }
}
 
//右键点击事件
private void formsPlot1_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
 
    Point p = e.GetPosition(myPlot1);
    Pixel mousePixel = new Pixel(p.X * myPlot1.DisplayScale, p.Y * myPlot1.DisplayScale);
    Coordinates coordinates = myPlot1.Plot.GetCoordinates(mousePixel);
 
    if (v1 == null)
    {
        v1 = myPlot1.Plot.Add.VerticalLine(coordinates.X);
        v1.IsDraggable = true;
    }
    else
    {
        if (v2 == null)
        {
            v2 = myPlot1.Plot.Add.VerticalLine(coordinates.X);
            v2.IsDraggable = true;
        }
        else
        {
 
            myPlot1.Plot.Remove(v1);
            myPlot1.Plot.Remove(v2);
            v1 = null;
            v2 = null;
        }    
    }
}
 
//获取当前鼠标下的垂直线对象
private VerticalLine GetLineUnderMouse(double x)
 {
 
 
            foreach (VerticalLine axLine in myPlot1.Plot.GetPlottables<VerticalLine>().Reverse())
            {
                double start = axLine.Position - 0.09;
                double end = axLine.Position + 0.09;
                if (x > start && x < end)
                {
                    return axLine;
                }
             }
             return null;
  }

Over

原创文章,作者:Gary,如若转载,请注明出处:https://www.cpw5.top/454.html

淘宝小店:陈皮王五工作室

(0)
GaryGary
上一篇 2024-11-26 下午2:50
下一篇 2024-12-18 下午12:22

相关推荐

  • 燃气表异常事件功能自动化测试平台

    燃气表异常事件检测系统 这是一个检测燃气表异常事件功能的自动化系统。 主要功能: 并发测试3种规格的超声和皮膜燃气表(G2.5、G4、G6)的 大流告警、低电压告警、低电压关阀告警…

    2024-09-16
  • C# WPF 获取屏幕的宽高

    获取主显示器的工作区宽度和高度,当前屏幕选择的分辨率,该分辨率是以像素为单位:SystemParameters.PrimaryScreenWidthSystemParameters…

    2024-08-20
  • https访问中,忽略SSL证书检查

    有时候,有这么一种场景。https接口有SSL证书, 但访问的客户端不能联网验证证书,也不能本地存储证书。 在Python中, 大概如下处理: verify设置为 False,Re…

    上位机 2024-08-05
  • 关于fwrite()的实时写入

    标准C函数fwrite()在写入文件时, 并不是实时写入的,实际是写在内存缓冲区中。 要实时更新写入,需要在fwrite()之后,使用fflush 或 _commit()来实时刷新…

    2024-06-25

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注