几种常见的 .NET 绘图库(包括 ScottPlot 5.0.55、OxyPlot、LiveCharts 2 和 Plotly.NET)在绘制电压曲线(如本例中的正负值电压数据)场景下的详细比较
几种常见的 .NET 绘图库(包括 ScottPlot 5.0.55、OxyPlot、LiveCharts 2 和 Plotly.NET)在绘制电压曲线(如本例中的正负值电压数据)场景下的详细比较,结合 ResamplerMgr 类重采样需求,分析其功能、性能、易用性、跨平台支持和适用性。示例基于 Windows Forms 环境,但也会提及其他平台支持(如 WPF、Avalonia)。最后提供一个使用 OxyPlot 的完整示例代码,与 ScottPlot 示例对比,包含清晰的中文注释。
绘图库比较
以下比较基于以下维度:
-
功能:支持的图表类型、交互功能、自定义能力。
-
性能:处理大数据集(如数千点以上)的效率。
-
易用性:API 设计、学习曲线、文档质量。
-
跨平台支持:支持 Windows Forms、WPF、Avalonia、Maui 等。
-
社区与生态:社区活跃度、更新频率、开源许可。
-
适用性:适合电压曲线(正负值、平滑重采样)的特点。
1. ScottPlot 5.0.55
-
功能:
-
支持散点图、折线图、信号图(Signal)、热图等,适合科学数据可视化。
-
交互功能:鼠标缩放、拖动、右键菜单、坐标显示(通过 GetCoordinates)。
-
自定义能力:支持颜色、线宽、标记形状、图例、轴标签等。
-
图像导出:支持 PNG、SVG、JPEG、BMP。
-
-
性能:
-
优异,尤其在大数据集(>10,000 点)时,使用 Signal 或 SignalXY 可高效渲染。
-
本例(100 点)使用 Scatter 足够,性能无瓶颈。
-
-
易用性:
-
API 简洁,代码量少,易于上手。
-
学习曲线低,适合快速开发。
-
-
跨平台支持:
-
支持 Windows Forms(FormsPlot)、WPF(WpfPlot)、Avalonia、Maui、Blazor。
-
需安装对应包(如 ScottPlot.WinForms、ScottPlot.WPF)。
-
-
社区与生态:
-
开源(MIT 许可),社区活跃,GitHub 仓库更新频繁。
-
版本 5.0.55(2023 年发布)优化了性能和 API。
-
-
适用性:
-
非常适合电压曲线场景,负值区域通过 AutoScale 自动适配。
-
结合 ResamplerMgr,平滑曲线渲染良好,支持交互(鼠标坐标显示)。
-
适合快速原型开发和科学数据可视化。
-
-
不足:
-
复杂图表(如 3D 或动态仪表盘)支持有限。
-
交互功能较基础,高级交互需自定义。
-
2. OxyPlot
-
功能:
-
支持广泛的图表类型:折线图、散点图、柱状图、饼图、热图、3D 图等。
-
交互功能:缩放、平移、悬停显示数据点、选择区域。
-
自定义能力:高度可配置,支持自定义轴、样式、注解。
-
图像导出:支持 PNG、SVG、PDF。
-
-
性能:
-
中等,适合中小数据集(<10,000 点)。大数据集可能需要优化。
-
本例(100 点)性能良好,无明显延迟。
-
-
易用性:
-
API 较为复杂,需创建 PlotModel 和系列(如 LineSeries)。
-
学习曲线稍陡,但文档全面(https://oxyplot.github.io/)。
-
提供设计器支持,适合拖放开发。
-
-
跨平台支持:
-
支持 Windows Forms、WPF、Avalonia、Xamarin、Maui。
-
需安装对应包(如 OxyPlot.WindowsForms、OxyPlot.Wpf)。
-
-
社区与生态:
-
开源(MIT 许可),社区活跃但更新频率低于 ScottPlot。
-
长期稳定,适合传统 .NET 应用。
-
-
适用性:
-
适合电压曲线,支持负值和重采样数据。
-
提供更丰富的交互(如数据点跟踪器)和自定义选项。
-
适合需要复杂图表或高可配置性的场景。
-
-
不足:
-
API 复杂,代码量较多。
-
大数据集性能不如 ScottPlot 的 Signal。
-
3. LiveCharts 2
-
功能:
-
专注于动态和交互式图表,支持折线图、柱状图、饼图、仪表盘等。
-
交互功能:动画、工具提示、缩放、平移,适合现代 UI。
-
自定义能力:支持主题、动画、数据绑定。
-
图像导出:支持 PNG、SVG。
-
-
性能:
-
优化动态数据,适合实时更新场景。
-
本例(100 点)性能优秀,但大数据集需测试。
-
-
易用性:
-
API 现代化,基于 MVVM 模式,适合 WPF 和 Maui。
-
学习曲线中等,文档完善(https://livecharts.dev/)。
-
Windows Forms 支持较弱,需额外配置。
-
-
跨平台支持:
-
主要支持 WPF、Maui、Avalonia,Windows Forms 支持有限。
-
需安装 LiveChartsCore.SkiaSharpView.WinForms。
-
-
社区与生态:
-
开源(MIT 许可),社区较新但快速增长。
-
版本 2.x 重构了 API,现代化但生态较年轻。
-
-
适用性:
-
适合动态电压曲线(如实时监控),负值区域支持良好。
-
动画效果适合现代 UI,但本例静态数据无需动画。
-
Windows Forms 集成复杂,推荐 WPF。
-
-
不足:
-
Windows Forms 支持不完善,配置复杂。
-
文档示例偏向 WPF 和 Maui。
-
4. Plotly.NET
-
功能:
-
基于 Plotly.js,支持丰富的图表类型:折线图、散点图、3D 图、地图、统计图。
-
交互功能:缩放、平移、悬停提示、导出交互式 HTML。
-
自定义能力:高度灵活,支持 JSON 配置和动态样式。
-
图像导出:支持 PNG、JPEG、SVG、HTML。
-
-
性能:
-
适合中小数据集(<10,000 点),大数据集需优化。
-
本例(100 点)性能无问题。
-
-
易用性:
-
API 基于函数式编程,语法较独特,学习曲线较陡。
-
文档详细(https://plotly.net/),但 C# 示例较少。
-
-
跨平台支持:
-
主要通过 HTML 输出,支持所有平台(需浏览器或 WebView)。
-
Windows Forms/WPF 需嵌入 WebView 或导出静态图像。
-
-
社区与生态:
-
开源(MIT 许可),依托 Plotly.js 生态,社区活跃。
-
C# 绑定较新,生态不如 JavaScript 原生。
-
-
适用性:
-
适合交互式电压曲线,负值区域支持良好。
-
HTML 输出适合 Web 应用,但 Windows Forms 集成复杂。
-
结合 ResamplerMgr,可生成平滑曲线。
-
-
不足:
-
C# 集成复杂,需额外处理 HTML 或 WebView。
-
性能不如 ScottPlot 和 OxyPlot。
-
比较总结
|
维度 |
ScottPlot 5.0.55 |
OxyPlot |
LiveCharts 2 |
Plotly.NET |
|---|---|---|---|---|
|
功能 |
散点图、折线图、信号图,交互基础 |
丰富图表类型,交互强大 |
动态图表,动画支持 |
丰富图表,交互式 HTML 输出 |
|
性能 |
优异( Signal 适合大数据) |
中等(中小数据集) |
适合动态数据,静态稍逊 |
中等(依赖 Web 渲染) |
|
易用性 |
简洁,学习曲线低,文档完善 |
稍复杂,文档全面 |
MVVM 友好,WinForms 复杂 |
独特 API,学习曲线陡 |
|
跨平台支持 |
WinForms、WPF、Avalonia、Maui |
WinForms、WPF、Avalonia、Maui |
WPF、Maui 为主,WinForms 有限 |
HTML 输出,需 WebView |
|
社区与生态 |
开源,活跃,更新频繁 |
开源,稳定,更新较慢 |
开源,较新,快速增长 |
开源,依赖 Plotly.js 生态 |
|
适用性 |
电压曲线理想,负值平滑,快速开发 |
适合复杂图表,负值支持良好 |
动态场景优,WinForms 不佳 |
交互式 Web 图表,集成复杂 |
|
不足 |
复杂图表支持有限 |
API 复杂,性能中等 |
WinForms 支持弱,配置复杂 |
C# 集成复杂,性能一般 |
推荐选择:
-
ScottPlot 5.0.55:适合本例(电压曲线、负值数据、Windows Forms),API 简洁,性能优异,负值区域平滑,易于集成。
-
OxyPlot:适合需要复杂图表或高级交互的场景,Windows Forms 支持良好。
-
LiveCharts 2:推荐用于 WPF 或动态数据场景,Windows Forms 不理想。
-
Plotly.NET:适合 Web 应用或交互式 HTML 输出,桌面应用集成复杂。
OxyPlot 示例代码
以下是使用 OxyPlot(最新版本,例如 2.1.0)的完整示例代码,与 ScottPlot 示例功能一致,展示电压曲线(原始 vs 重采样),支持负值电压,确保平滑连续。代码基于 Windows Forms,使用 OxyPlot.WindowsForms,包含鼠标交互和图像保存。
安装 OxyPlot
bash
dotnet add package OxyPlot.WindowsForms --version 2.1.0
dotnet add package MathNet.Numerics --version 5.0.0
代码
csharp
using MathNet.Numerics;
using MathNet.Numerics.Interpolation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using OxyPlot;
using OxyPlot.Series;
using OxyPlot.WindowsForms; // OxyPlot 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];
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);
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;
}
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;
}
for (int i = 1; i < x.Count; i++)
{
if (x[i] <= x[i - 1])
{
Console.WriteLine("无效输入:x 必须严格递增");
return false;
}
}
return true;
}
}
/// <summary>
/// 主程序:模拟电压数据,执行重采样,使用 OxyPlot 2.1.0 绘制曲线。
/// 支持鼠标交互(显示坐标),保存图像为 PNG。
/// </summary>
class Program
{
[STAThread]
static void Main()
{
// 启用 Windows Forms 视觉样式
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// 创建主窗体
var form = new Form
{
Text = "电压曲线展示 - OxyPlot 2.1.0",
Width = 800,
Height = 600
};
// 创建 OxyPlot PlotView 控件
var plotView = new PlotView
{
Dock = DockStyle.Fill // 填充整个窗体
};
form.Controls.Add(plotView);
// 模拟电压数据:正弦波 + 随机噪声,包含负值
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);
// 配置 OxyPlot 图表
var plotModel = new PlotModel
{
Title = "电压曲线(原始 vs 重采样)",
DefaultFont = "Arial",
TitleFontSize = 14
};
// 添加 X 轴(时间)
plotModel.Axes.Add(new OxyPlot.Axes.LinearAxis
{
Position = OxyPlot.Axes.AxisPosition.Bottom,
Title = "时间 (秒)",
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
});
// 添加 Y 轴(电压)
plotModel.Axes.Add(new OxyPlot.Axes.LinearAxis
{
Position = OxyPlot.Axes.AxisPosition.Left,
Title = "电压 (伏特)",
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
});
// 添加原始数据(蓝色散点)
var scatterSeries = new ScatterSeries
{
Title = "原始数据",
MarkerType = MarkerType.Circle,
MarkerSize = 5,
MarkerFill = OxyColors.Blue
};
for (int i = 0; i < x.Count; i++)
{
scatterSeries.Points.Add(new ScatterPoint(x[i], y[i]));
}
plotModel.Series.Add(scatterSeries);
// 添加重采样数据(红色折线)
var lineSeries = new LineSeries
{
Title = "重采样数据",
Color = OxyColors.Red,
LineStyle = LineStyle.Solid,
StrokeThickness = 2
};
for (int i = 0; i < xout.Count; i++)
{
lineSeries.Points.Add(new DataPoint(xout[i], yout[i]));
}
plotModel.Series.Add(lineSeries);
// 设置图例
plotModel.LegendPosition = LegendPosition.RightTop;
plotModel.LegendBackground = OxyColors.White;
plotModel.LegendBorder = OxyColors.Black;
// 绑定 PlotModel 到 PlotView
plotView.Model = plotModel;
// 添加鼠标交互:显示坐标
plotView.MouseMove += (s, e) =>
{
var mousePos = plotView.InverseTransform(e.Location);
form.Text = $"电压曲线展示 - 时间: {mousePos.X:F3} 秒, 电压: {mousePos.Y:F3} 伏特";
};
// 保存图像到文件
try
{
using (var stream = System.IO.File.Create("voltage_curve_oxyplot.png"))
{
var exporter = new PngExporter { Width = 800, Height = 600 };
exporter.Export(plotModel, stream);
Console.WriteLine("图像已保存为 voltage_curve_oxyplot.png");
}
}
catch (Exception ex)
{
Console.WriteLine($"保存图像失败:{ex.Message}");
}
// 运行窗体
Application.Run(form);
}
}
}
OxyPlot 示例中文解释
1. ResamplerMgr 类
-
功能:与 ScottPlot 示例一致,使用 CubicSpline 插值进行重采样,确保负值电压区域平滑。
-
方法:Resample、Resample2、ValidateInput,逻辑相同,处理负值异常。
2. 模拟电压数据
-
生成逻辑:正弦波(±5V)加噪声(±0.25V),时间范围 -5 到 5 秒,步长 0.5。
-
代码:与 ScottPlot 示例一致,生成 21 个点,包含负值。
3. OxyPlot PlotView 控件
-
创建控件:
-
使用 OxyPlot.WindowsForms.PlotView,设置 Dock = DockStyle.Fill。
-
-
绘图配置:
-
创建 PlotModel,设置标题(“电压曲线(原始 vs 重采样)”)。
-
添加轴:
-
X 轴(LinearAxis):时间,带网格线。
-
Y 轴(LinearAxis):电压,带网格线。
-
-
添加系列:
-
ScatterSeries:原始数据,蓝色圆点(MarkerType=Circle)。
-
LineSeries:重采样数据,红色折线(StrokeThickness=2)。
-
-
设置图例:右上角,带白色背景和黑色边框。
-
-
交互功能:
-
使用 MouseMove 和 InverseTransform 获取鼠标坐标,更新窗体标题。
-
-
保存图像:
-
使用 PngExporter 保存为 800x600 像素 PNG 文件。
-
-
代码:
csharp
var plotModel = new PlotModel { Title = "电压曲线(原始 vs 重采样)" }; var scatterSeries = new ScatterSeries { Title = "原始数据", MarkerType = MarkerType.Circle, MarkerSize = 5, MarkerFill = OxyColors.Blue }; var lineSeries = new LineSeries { Title = "重采样数据", Color = OxyColors.Red, LineStyle = LineStyle.Solid, StrokeThickness = 2 };
4. 窗体显示
-
创建 800x600 像素窗体,添加 PlotView 控件,运行应用程序。
ScottPlot vs OxyPlot 示例对比
|
特性 |
ScottPlot 5.0.55 |
OxyPlot 2.1.0 |
|---|---|---|
|
控件名称 |
FormsPlot |
PlotView |
|
代码量 |
较少,API 简洁 |
较多,需配置 PlotModel 和系列 |
|
绘图代码 |
plt.Add.Scatter 直接添加数据 |
需创建 ScatterSeries 和 LineSeries |
|
交互实现 |
GetCoordinates 简单获取鼠标坐标 |
InverseTransform 获取坐标,需手动处理 |
|
图像导出 |
SavePng 简单调用 |
使用 PngExporter ,需创建流 |
|
负值支持 |
AutoScale 自动适配 |
需手动配置轴范围,负值支持良好 |
|
性能 |
优异,适合大数据( Signal ) |
中等,中小数据集表现良好 |
|
易用性 |
更简单,适合快速开发 |
较复杂,适合高级自定义 |
选择建议:
-
ScottPlot:推荐用于本例(电压曲线、Windows Forms),API 简洁,性能优,负值区域平滑。
-
OxyPlot:适合需要复杂图表(如多轴、3D)或高级交互的场景,代码稍复杂。
使用说明
-
运行 ScottPlot 示例:
-
安装 ScottPlot.WinForms (5.0.55) 和 MathNet.Numerics。
-
复制 ScottPlot 代码到 Program.cs,运行 Windows Forms 项目。
-
检查 voltage_curve.png 输出。
-
-
运行 OxyPlot 示例:
-
安装 OxyPlot.WindowsForms (2.1.0) 和 MathNet.Numerics。
-
复制 OxyPlot 代码,运行项目。
-
检查 voltage_curve_oxyplot.png 输出。
-
-
调试负值问题:
-
若负值区域不连续,检查控制台日志(NaN 警告)。
-
增加 sampleCount(例如 200)或使用 Resample2。
-
-
扩展:
-
动态数据:LiveCharts 2 更适合实时更新。
-
Web 输出:Plotly.NET 适合 HTML 交互图表。
-
WPF:可提供 ScottPlot 或 OxyPlot 的 WPF 版本代码。
-
总结
-
ScottPlot 5.0.55:简洁高效,适合电压曲线快速可视化,负值区域平滑,推荐本例使用。
-
OxyPlot:功能丰富,交互强大,适合复杂场景,代码稍复杂。
-
LiveCharts 2:动态图表优,Windows Forms 支持弱。
-
Plotly.NET:交互式 Web 图表,桌面集成复杂。
-
负值处理:所有库支持负值,结合 ResamplerMgr 的样条插值和边界处理,确保曲线连续。
如果需要 LiveCharts 2 或 Plotly.NET 示例代码,或特定数据测试,请提供详情,我可提供定制化实现!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)