一个基于 ScottPlot 5.0 的详细 C# 示例代码,展示如何使用 ScottPlot.WinForms 绘制电压曲线,结合 ResamplerMgr 类进行重采样,确保负值电压区域的曲线平滑
以下是一个基于 ScottPlot 5.0.55 的详细 C# 示例代码,展示如何使用 ScottPlot.WinForms 绘制电压曲线,结合 ResamplerMgr 类进行重采样,确保负值电压区域的曲线平滑连续。示例包括模拟电压数据(正弦波 + 噪声,包含负值)、重采样处理、绘制原始数据(散点)和重采样数据(平滑曲线),并提供交互功能(如鼠标显示坐标)。电压(y):正弦波(5.0 * Mat
以下是一个基于 ScottPlot 5.0.55 的详细 C# 示例代码,展示如何使用 ScottPlot.WinForms 绘制电压曲线,结合 ResamplerMgr 类进行重采样,确保负值电压区域的曲线平滑连续。示例包括模拟电压数据(正弦波 + 噪声,包含负值)、重采样处理、绘制原始数据(散点)和重采样数据(平滑曲线),并提供交互功能(如鼠标显示坐标)。代码使用 Windows Forms 平台,包含清晰的中文注释,解释每个部分的功能、参数和逻辑,并针对 ScottPlot 5.0.55 的 API 进行优化。
环境准备
-
NuGet 包安装:
-
安装特定版本的 ScottPlot.WinForms 和 MathNet.Numerics:
bash
dotnet add package ScottPlot.WinForms --version 5.0.55 dotnet add package MathNet.Numerics --version 5.0.0 -
确保项目文件中包含:
xml
<PackageReference Include="ScottPlot.WinForms" Version="5.0.55" /> <PackageReference Include="MathNet.Numerics" Version="5.0.0" />
-
-
项目类型:
-
Windows Forms 应用程序(.NET 8.0 或 .NET Framework 4.8 均可)。
-
示例使用 FormsPlot 控件,适合 Windows 环境。
-
-
功能概述:
-
模拟电压数据:正弦波(±5V)加随机噪声,包含负值。
-
使用 ResamplerMgr 类重采样,生成平滑曲线。
-
使用 ScottPlot 5.0.55 的 FormsPlot 绘制:
-
蓝色散点:原始数据。
-
红色折线:重采样数据。
-
-
添加交互功能:鼠标移动时显示坐标。
-
保存图像为 PNG 文件。
-
ScottPlot 5.0.55 特性
ScottPlot 5.0.55 是 ScottPlot 5.x 系列的一个稳定版本,改进了性能和 API 设计。以下是本示例中用到的关键特性:
-
FormsPlot 控件:用于 Windows Forms,替代旧版 PlotView,提供高效渲染。
-
Plot API:
-
Add.Scatter:绘制散点图或折线图,支持自定义颜色、线宽、标记大小。
-
Title, XLabel, YLabel:设置图表标题和轴标签。
-
Legend:显示图例。
-
AutoScale:自动调整轴范围,适合负值数据。
-
SavePng:保存图表为 PNG 文件。
-
-
交互支持:
-
通过 MouseMove 事件获取鼠标坐标,显示实时数据点信息。
-
-
性能优化:
-
支持大数据集(本例数据较小,使用 Scatter 即可;若数据量大,可用 Signal)。
-
完整示例代码
csharp
using MathNet.Numerics;
using MathNet.Numerics.Interpolation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using ScottPlot.WinForms; // ScottPlot 5.0.55 的 Windows Forms 命名空间
namespace ResamplerLib
{
/// <summary>
/// 重采样管理类,用于对电压数据进行插值重采样,确保负值区域平滑连续。
/// 使用 MathNet.Numerics 的样条插值,支持均匀采样和指定点采样。
/// </summary>
public static class ResamplerMgr
{
/// <summary>
/// 均匀重采样,生成指定数量的采样点。
/// 使用 Cubic Spline 插值,确保负值电压曲线平滑。
/// </summary>
/// <param name="sampleCount">目标采样点数量,必须大于 0</param>
/// <param name="x">输入自变量列表(时间),需严格递增</param>
/// <param name="y">输入因变量列表(电压),与 x 长度相同</param>
/// <param name="xout">输出重采样的自变量列表</param>
/// <param name="yout">输出重采样的因变量列表</param>
public static void Resample(int sampleCount, List<double> x, List<double> y, out List<double> xout, out List<double> yout)
{
// 初始化输出列表
xout = new List<double>();
yout = new List<double>();
// 验证输入数据
if (!ValidateInput(x, y, sampleCount))
return;
try
{
// 创建样条插值对象,优化负值区域平滑性
IInterpolation ip = Interpolate.CubicSpline(x.ToArray(), y.ToArray());
// 获取时间范围
double start = x[0];
double stop = x[x.Count - 1];
// 处理特殊情况:单点或时间范围为 0
if (sampleCount == 1 || Math.Abs(stop - start) < 1e-10)
{
xout.Add(start);
yout.Add(y[0]);
return;
}
// 计算步长,确保采样点均匀分布
double step = (stop - start) / (sampleCount - 1);
// 逐点插值生成重采样数据
for (double sx = start; sx <= stop + 1e-10; sx += step)
{
double sy = ip.Interpolate(sx);
// 处理插值异常(NaN 或 Infinity)
if (double.IsNaN(sy) || double.IsInfinity(sy))
{
sy = sx <= x[0] ? y[0] : y[y.Count - 1];
Console.WriteLine($"警告:sx={sx:F3} 处插值异常,使用边界值 {sy:F3}");
}
xout.Add(sx);
yout.Add(sy);
}
}
catch (Exception ex)
{
// 异常处理:返回原始数据并记录错误
Console.WriteLine($"重采样失败:{ex.Message}");
xout = new List<double>(x);
yout = new List<double>(y);
}
}
/// <summary>
/// 根据指定的自变量列表进行重采样。
/// </summary>
/// <param name="sampleCount">目标采样点数量(占位参数,不实际使用)</param>
/// <param name="x">输入自变量列表(时间),需严格递增</param>
/// <param name="y">输入因变量列表(电压),与 x 长度相同</param>
/// <param name="xx">目标自变量列表,用于插值</param>
/// <param name="yy">输出重采样的因变量列表</param>
public static void Resample(int sampleCount, List<double> x, List<double> y, List<double> xx, out List<double> yy)
{
// 初始化输出列表
yy = new List<double>();
// 验证输入数据
if (!ValidateInput(x, y, 1) || xx == null || xx.Count == 0)
{
Console.WriteLine("无效输入:xx 必须非空且有效");
return;
}
try
{
// 创建样条插值对象
IInterpolation ip = Interpolate.CubicSpline(x.ToArray(), y.ToArray());
// 对每个目标点进行插值
foreach (double sx in xx)
{
double sy = ip.Interpolate(sx);
// 处理插值异常
if (double.IsNaN(sy) || double.IsInfinity(sy))
{
sy = sx <= x[0] ? y[0] : y[y.Count - 1];
Console.WriteLine($"警告:sx={sx:F3} 处插值异常,使用边界值 {sy:F3}");
}
yy.Add(sy);
}
}
catch (Exception ex)
{
// 异常处理:记录错误
Console.WriteLine($"重采样失败:{ex.Message}");
}
}
/// <summary>
/// 两次重采样以提高曲线平滑性。
/// </summary>
/// <param name="sampleCount">目标采样点数量</param>
/// <param name="x">输入自变量列表(时间)</param>
/// <param name="y">输入因变量列表(电压)</param>
/// <param name="xout">输出重采样的自变量列表</param>
/// <param name="yout">输出重采样的因变量列表</param>
public static void Resample2(int sampleCount, List<double> x, List<double> y, out List<double> xout, out List<double> yout)
{
// 初始化输出列表
xout = new List<double>();
yout = new List<double>();
// 第一次重采样:使用两倍采样点
Resample(2 * sampleCount, x, y, out List<double> xTemp, out List<double> yTemp);
// 合并原始数据和第一次重采样结果
List<double> x2 = new List<double>(x);
List<double> y2 = new List<double>(y);
x2.AddRange(xTemp);
y2.AddRange(yTemp);
// 第二次重采样
Resample(sampleCount, x2, y2, out xout, out yout);
}
/// <summary>
/// 验证输入数据的有效性。
/// </summary>
/// <param name="x">自变量列表(时间)</param>
/// <param name="y">因变量列表(电压)</param>
/// <param name="sampleCount">采样点数量</param>
/// <returns>是否有效</returns>
private static bool ValidateInput(List<double> x, List<double> y, int sampleCount)
{
// 检查输入是否为空或长度不一致
if (x == null || y == null || x.Count != y.Count || x.Count < 2 || sampleCount <= 0)
{
Console.WriteLine("无效输入:x 和 y 必须非空、长度相等、至少 2 个点,且 sampleCount > 0");
return false;
}
// 检查是否包含 NaN 或 Infinity
if (x.Any(double.IsNaN) || y.Any(double.IsNaN) || x.Any(double.IsInfinity) || y.Any(double.IsInfinity))
{
Console.WriteLine("无效输入:x 和 y 不能包含 NaN 或 Infinity");
return false;
}
// 检查 x 是否严格递增
for (int i = 1; i < x.Count; i++)
{
if (x[i] <= x[i - 1])
{
Console.WriteLine("无效输入:x 必须严格递增");
return false;
}
}
return true;
}
}
/// <summary>
/// 主程序:模拟电压数据,执行重采样,使用 ScottPlot 5.0.55 的 FormsPlot 绘制曲线。
/// 支持鼠标交互,显示坐标,保存图像为 PNG。
/// </summary>
class Program
{
[STAThread]
static void Main()
{
// 启用 Windows Forms 视觉样式
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// 创建主窗体
var form = new Form
{
Text = "电压曲线展示 - ScottPlot 5.0.55",
Width = 800,
Height = 600
};
// 创建 ScottPlot FormsPlot 控件
var formsPlot = new FormsPlot
{
Dock = DockStyle.Fill // 填充整个窗体
};
form.Controls.Add(formsPlot);
// 模拟电压数据:正弦波 + 随机噪声,包含负值
List<double> x = new List<double>(); // 时间 (秒)
List<double> y = new List<double>(); // 电压 (伏特)
Random rand = new Random(42); // 固定种子,确保结果可重复
for (double t = -5.0; t <= 5.0; t += 0.5) // 时间范围 [-5, 5]
{
x.Add(t);
// 电压 = 5 * sin(t) + 噪声 (±0.25V)
double voltage = 5.0 * Math.Sin(t) + (rand.NextDouble() - 0.5) * 0.5;
y.Add(voltage);
}
// 进行重采样
int sampleCount = 100; // 生成 100 个采样点
ResamplerMgr.Resample(sampleCount, x, y, out List<double> xout, out List<double> yout);
// 配置 ScottPlot 图表
var plt = formsPlot.Plot;
// 添加原始数据(蓝色散点)
plt.Add.Scatter(x.ToArray(), y.ToArray(),
color: System.Drawing.Color.Blue,
markerSize: 5,
label: "原始数据");
// 添加重采样数据(红色平滑曲线)
plt.Add.Scatter(xout.ToArray(), yout.ToArray(),
color: System.Drawing.Color.Red,
markerSize: 0, // 无散点,仅绘制折线
lineWidth: 2,
label: "重采样数据");
// 设置图表标题和轴标签
plt.Title("电压曲线(原始 vs 重采样)");
plt.XLabel("时间 (秒)");
plt.YLabel("电压 (伏特)");
// 显示图例(右上角)
plt.Legend();
// 自动调整轴范围,确保负值电压可见
plt.AutoScale();
// 添加鼠标交互:显示坐标
formsPlot.MouseMove += (s, e) =>
{
// 获取鼠标在图表中的坐标
var coords = formsPlot.Plot.GetCoordinates(e.Location);
// 更新窗体标题,显示时间和电压
form.Text = $"电压曲线展示 - 时间: {coords.X:F3} 秒, 电压: {coords.Y:F3} 伏特";
};
// 保存图像到文件
try
{
plt.SavePng("voltage_curve.png", 800, 600);
Console.WriteLine("图像已保存为 voltage_curve.png");
}
catch (Exception ex)
{
Console.WriteLine($"保存图像失败:{ex.Message}");
}
// 刷新图表
formsPlot.Refresh();
// 运行窗体
Application.Run(form);
}
}
}
代码中文解释
1. ResamplerMgr 类
-
功能:实现电压数据的重采样,确保负值电压区域的曲线平滑连续。
-
关键特性:
-
使用 MathNet.Numerics 的 CubicSpline 插值,替代线性插值,优化负值区域的平滑性。
-
验证输入数据:确保 x 严格递增,无 NaN 或无穷大。
-
处理插值异常:当结果为 NaN 或无穷大时,使用边界值(y[0] 或 y[y.Count - 1])。
-
支持三种重采样方式:
-
均匀采样(Resample)。
-
指定点采样(Resample 重载)。
-
两次重采样(Resample2)。
-
-
-
方法详解:
-
Resample(int sampleCount, List<double> x, List<double> y, out List<double> xout, out List<double> yout):
-
输入:采样点数(sampleCount)、时间(x)、电压(y)。
-
输出:重采样的时间(xout)和电压(yout)。
-
逻辑:
-
验证输入。
-
创建样条插值对象。
-
计算步长:(stop - start) / (sampleCount - 1)。
-
逐点插值,处理负值异常。
-
-
-
Resample(int sampleCount, List<double> x, List<double> y, List<double> xx, out List<double> yy):
-
根据指定时间点(xx)插值,生成电压(yy)。
-
-
Resample2:
-
先用两倍采样点重采样,再用目标采样点重采样,增强平滑性。
-
-
ValidateInput:
-
检查输入:非空、长度一致、递增、无异常值。
-
-
-
代码片段:
csharp
IInterpolation ip = Interpolate.CubicSpline(x.ToArray(), y.ToArray()); double step = (stop - start) / (sampleCount - 1); for (double sx = start; sx <= stop + 1e-10; sx += step) { double sy = ip.Interpolate(sx); if (double.IsNaN(sy) || double.IsInfinity(sy)) { sy = sx <= x[0] ? y[0] : y[y.Count - 1]; } xout.Add(sx); yout.Add(sy); }
2. 模拟电压数据
-
生成逻辑:
-
时间(x):从 -5 到 5 秒,步长 0.5,共 21 个点。
-
电压(y):正弦波(5.0 * Math.Sin(t))加随机噪声(±0.25V),模拟真实交流信号,包含负值。
-
使用固定种子(Random(42))确保结果可重复。
-
-
代码:
csharp
for (double t = -5.0; t <= 5.0; t += 0.5) { x.Add(t); double voltage = 5.0 * Math.Sin(t) + (rand.NextDouble() - 0.5) * 0.5; y.Add(voltage); }
3. 重采样
-
参数:sampleCount = 100,生成 100 个均匀采样点。
-
调用:ResamplerMgr.Resample(sampleCount, x, y, out xout, out yout)。
-
效果:
-
原始数据:21 个点,可能在负值区域不连续。
-
重采样数据:100 个点,平滑连续,负值区域无断点。
-
4. ScottPlot 5.0.55 FormsPlot 控件
-
创建控件:
-
使用 FormsPlot,设置 Dock = DockStyle.Fill 填充窗体。
-
-
绘图配置:
-
Add.Scatter:
-
原始数据:蓝色散点(markerSize=5)。
-
重采样数据:红色折线(markerSize=0,lineWidth=2)。
-
-
图表设置:
-
Title:电压曲线(原始 vs 重采样)。
-
XLabel:时间 (秒)。
-
YLabel:电压 (伏特)。
-
Legend:显示图例。
-
AutoScale:自动调整轴范围,确保负值可见。
-
-
-
交互功能:
-
鼠标移动时,通过 GetCoordinates 获取图表坐标,更新窗体标题显示时间和电压。
-
-
保存图像:
-
使用 SavePng 保存为 800x600 像素的 PNG 文件。
-
-
代码:
csharp
var plt = formsPlot.Plot; plt.Add.Scatter(x.ToArray(), y.ToArray(), color: System.Drawing.Color.Blue, markerSize: 5, label: "原始数据"); plt.Add.Scatter(xout.ToArray(), yout.ToArray(), color: System.Drawing.Color.Red, markerSize: 0, lineWidth: 2, label: "重采样数据"); plt.Title("电压曲线(原始 vs 重采样)"); plt.XLabel("时间 (秒)"); plt.YLabel("电压 (伏特)"); plt.Legend(); plt.AutoScale(); formsPlot.MouseMove += (s, e) => { var coords = formsPlot.Plot.GetCoordinates(e.Location); form.Text = $"电压曲线展示 - 时间: {coords.X:F3} 秒, 电压: {coords.Y:F3} 伏特"; }; plt.SavePng("voltage_curve.png", 800, 600); formsPlot.Refresh();
5. 窗体显示
-
创建 800x600 像素的窗体,标题为“电压曲线展示 - ScottPlot 5.0.55”。
-
添加 FormsPlot 控件,运行应用程序。
运行结果
-
输出:
-
显示一个窗体,包含:
-
蓝色散点:原始数据(21 点,包含噪声,可能在负值区域不连续)。
-
红色曲线:重采样数据(100 点,平滑连续,负值区域无断点)。
-
-
X 轴:时间从 -5 到 5 秒。
-
Y 轴:电压从约 -5.5 到 5.5 伏特。
-
图例:区分“原始数据”和“重采样数据”。
-
鼠标移动:窗体标题动态显示鼠标位置的坐标(时间和电压)。
-
保存图像:生成 voltage_curve.png 文件。
-
-
负值电压处理:
-
样条插值(CubicSpline)确保负值区域平滑。
-
边界处理:插值异常时使用边界值。
-
输入验证:排除 NaN、无穷大、非递增等问题。
-
使用说明
-
运行步骤:
-
创建 Windows Forms 项目。
-
安装 NuGet 包:
bash
dotnet add package ScottPlot.WinForms --version 5.0.55 dotnet add package MathNet.Numerics --version 5.0.0 -
复制代码到 Program.cs,运行程序。
-
-
调整参数:
-
采样点数量:修改 sampleCount(例如 200)以提高曲线分辨率。
-
数据模拟:更改正弦波或噪声:
csharp
double voltage = 10.0 * Math.Sin(t * 2) + (rand.NextDouble() - 0.5) * 1.0; // 双倍频率,更大噪声
-
-
调试负值问题:
-
检查控制台输出,确认是否触发 NaN 警告。
-
添加详细日志:
csharp
xout.Add(sx); yout.Add(sy); Console.WriteLine($"点:sx={sx:F3}, sy={sy:F3}");
-
-
扩展功能:
-
保存其他格式:
csharp
plt.SaveSvg("voltage_curve.svg", 800, 600); -
大数据优化:若数据量大(>10,000 点),使用 Signal:
csharp
plt.Add.Signal(yout.ToArray(), secondsPerPoint: (xout[1] - xout[0])); -
WPF 支持:安装 ScottPlot.WPF(5.0.55),替换 FormsPlot 为 WpfPlot:
csharp
using ScottPlot.WPF; var wpfPlot = new WpfPlot { Dock = DockStyle.Fill };
-
ScottPlot 5.0.55 常见问题
-
“FormsPlot 未找到”:
-
确保安装 ScottPlot.WinForms 而非 ScottPlot(核心库)。
-
检查命名空间:using ScottPlot.WinForms;。
-
-
性能问题:
-
本例数据量小(100 点),使用 Scatter 足够。若数据量大,使用 Signal 或 SignalXY:
csharp
plt.Add.SignalXY(xout.ToArray(), yout.ToArray());
-
-
负值区域不连续:
-
确保 ResamplerMgr 的输入数据(x, y)有效。
-
检查控制台日志,确认插值异常。
-
增加 sampleCount 或使用 Resample2 增强平滑性。
-
-
跨平台需求:
-
ScottPlot 5.0.55 支持 WPF、Avalonia、Maui 等平台,需安装对应包(如 ScottPlot.WPF)。
-
负值问题解决总结
-
问题:负值电压导致曲线不连续,通常由插值 NaN 或输入数据异常引起。
-
解决方案:
-
使用 CubicSpline 插值,提高负值区域平滑性。
-
边界处理:插值异常时使用边界值。
-
输入验证:确保 x 递增、无 NaN 或无穷大。
-
增加采样点(sampleCount = 100),提高分辨率。
-
日志记录:便于调试异常点。
-
扩展与参考
-
ScottPlot 5.0.55 文档:ScottPlot 5.0 Cookbook
-
示例项目:ScottPlot 官方 GitHub 提供 Demo(https://github.com/ScottPlot/ScottPlot)。
-
WPF 示例:若需要 WPF 版本,替换 FormsPlot 为 WpfPlot,我可提供代码。
-
大数据优化:参考 ScottPlot 文档中的 Signal 和 SignalXY 示例。
如果遇到特定问题(如输入数据导致不连续)或需要其他平台(如 WPF、Avalonia)的代码,请提供详情,我可以提供定制化解决方案!
更多推荐



所有评论(0)