概要
通过阅读ScottPlot官方源码,实现出在WPF里实现实时动态图以及鼠标右键添加VerticalLine作区间展示以及实时更新VerticalLine的显示信息。并支持可移动VerticalLine实时更新区间信息。
效果图展示
技术细节
实现细节:
需要给实时画图页面指定一个具体的Point数,用来显示当前实时数据要绘制的数据点数,并将这个数据点数作为要展示在当前页面的Double数组的长度,然后通过定时器不断轮询吐数据用来绘制实时图。
实现可移动的VerticalLine需要给ScottPlot这个控件添加鼠标事件,右键点击(添加),左键点击(获取),鼠标松开(初始化VerticalLine对象),鼠标移动(移动VerticalLine)
实时画图具体实现:
1.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.csharp文件成员变量
double[] dataY; //实时绘制时的数组
List<Double> listDataY; //所有数据的集合
int index; //要截取的索引
VerticalLine v1; //第一根垂直线
VerticalLine v2; //第二根垂直线
VerticalLine PlottableBeingDragged; //可变化的垂直线对象
int Point; //实时集合的长度以及实时绘制时的数据点数
string deviceId; //要实时绘图的设备号
4. 第一条数据进来时执行的初始化方法
Point = 400;
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轴刻度
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();
鼠标事件具体实现:
//鼠标松开事件
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;
}