深入了解 ScottPlot 的兴趣!ScottPlot 是一个功能强大、轻量级的 .NET 开源绘图库,专为快速、高效的 2D 数据可视化设计
深入了解 ScottPlot 的兴趣!ScottPlot 是一个功能强大、轻量级的 .NET 开源绘图库,专为快速、高效的 2D 数据可视化设计,广泛应用于科学、工程和数据分析领域。以下是对 ScottPlot 的全面介绍,涵盖其核心特性、架构、API 使用方法、性能分析、跨平台支持、定制化能力,以及一个基于 ScottPlot 5.0.55 的完整示例代码,展示如何绘制电压曲线(结合 ResamplerMgr 类处理负值电压,确保平滑连续)
1. ScottPlot 概述
- 简介:
- ScottPlot 是一个 .NET 开源绘图库,专注于简单、高性能的 2D 图表绘制,支持折线图、散点图、柱状图、热图等。
- 开源(MIT 许可证),始于 2018 年,社区活跃,更新频繁。
- GitHub 仓库:https://github.com/ScottPlot/ScottPlot
- 最新版本(截至 2025 年 6 月 22 日):5.0.55(2025 年 5 月发布)。
- 官网:ScottPlot - Interactive Plotting Library for .NET
- 特点:API 简洁,性能优异,适合大数据和快速开发。
- 核心特性:
- 丰富的图表类型:支持折线图(Scatter)、信号图(Signal)、柱状图(Bar)、饼图(Pie)、热图(Heatmap)等。
- 高性能:Signal 模式优化大数据(支持 >10 亿点),渲染时间 <1ms。
- 跨平台:支持 Windows Forms、WPF、MAUI、Avalonia、Blazor、Console。
- 交互功能:支持缩放、平移、鼠标事件,内置右键菜单(放大、自动缩放、保存图像)。
- 图像导出:支持 PNG、JPEG、SVG、BMP 等格式。
- 简单 API:单行代码即可绘制图表,例如 plt.Add.Scatter(x, y)。
- 轻量级:依赖少,适合嵌入式和快速原型开发。
- 适用场景:
- 科学数据可视化(如电压曲线、传感器数据)。
- 大数据绘制(如时间序列、频谱分析)。
- 简单交互和快速开发的桌面或控制台应用。
- 跨平台数据可视化(MAUI、Avalonia)。
2. ScottPlot 架构
ScottPlot 采用简洁的分层设计:
- Plot:
- 核心对象,表示整个图表,包含所有绘图元素(系列、轴、标题、图例)。
- 示例:var plt = new ScottPlot.Plot();
- Plottables:
- 数据系列,表示图表中的可视化元素。
- 常见类型:
- Scatter:散点图/折线图,适合中小数据集。
- Signal:高效信号图,适合大数据(>百万点)。
- Bar:柱状图。
- Heatmap:热图。
- 示例:plt.Add.Scatter(x, y);
- Axes:
- 坐标轴,控制 X/Y 轴的范围、标签、网格线。
- 支持多轴(Axes.AddAxis)和自定义样式。
- 示例:plt.Axes.Left.Label.Text = "电压";
- Renderers:
- 负责将 Plot 渲染为图像。
- 基于 SkiaSharp(跨平台 2D 图形库),支持硬件加速。
- 示例:plt.Save("chart.png", 800, 600);
- Controls:
- 平台特定的控件,负责 UI 显示和交互。
- 示例:
- WinForms:ScottPlot.WinForms.FormsPlot。
- WPF:ScottPlot.Wpf.WpfPlot。
- MAUI:ScottPlot.Maui.MauiPlot。
- Interaction:
- 内置鼠标交互(缩放、平移、右键菜单)。
- 支持自定义事件(如 MouseMove 获取坐标)。
3. ScottPlot API 使用方法
以下是使用 ScottPlot 5.0.55 绘制图表的基本步骤:
- 安装 NuGet 包:
- 创建 Plot:
- 添加数据系列:
- 配置轴和样式:
- 绑定到控件:
- 交互与导出:
导出图像:csharp
plt.Save("chart.png", 800, 600, ImageFormat.Png);添加鼠标交互:csharp
formsPlot.MouseMove += (s, e) => { var coords = formsPlot.Plot.GetCoordinates(new Pixel(e.Location.X, e.Location.Y)); form.Text = $"时间: {coords.X:F3} 秒, 电压: {coords.Y:F3} 伏特"; };在 WinForms 中使用 FormsPlot:csharp
var formsPlot = new FormsPlot { Dock = DockStyle.Fill }; formsPlot.Plot.Add.Scatter(x, y);设置标题和轴标签:csharp
plt.Title("电压曲线"); plt.Axes.Left.Label.Text = "电压 (伏特)"; plt.Axes.Bottom.Label.Text = "时间 (秒)";添加散点图或折线图:csharp
var scatter = plt.Add.Scatter(x, y); scatter.MarkerStyle.Size = 5; scatter.LineStyle.Width = 2;初始化 Plot 对象:csharp
var plt = new ScottPlot.Plot();WPF(替代示例):bash
dotnet add package ScottPlot.Wpf --version 5.0.55Windows Forms:bash
dotnet add package ScottPlot.WinForms --version 5.0.55 dotnet add package MathNet.Numerics --version 5.0.0 dotnet add package System.Drawing.Common --version 8.0.04. ScottPlot 性能分析
- 中小数据集(<100,000 点):
- 性能极佳,渲染和交互无延迟,适合本例(100 点)。
- 大数据集(>100,000 点):
- Signal 模式使用高效算法,渲染 >10 亿点只需数毫秒。
- 优于 OxyPlot,接近 LiveCharts2 的 Backers 包。
- 动态更新:
- 支持实时更新,但需手动调用 formsPlot.Refresh()。
- 性能依赖于数据量和刷新频率。
- 局限:
- 复杂交互(如内置 Tooltip)需手动实现。
- 复杂图表(如 3D、热图)支持有限。
5. 跨平台支持
ScottPlot 5.0.55 支持多种平台:
- Windows Forms:
- 使用 ScottPlot.WinForms.FormsPlot。
- 适合传统桌面应用,交互简单。
- WPF:
- 使用 ScottPlot.Wpf.WpfPlot。
- 支持 MVVM,但交互仍需手动实现。
- MAUI:
- 使用 ScottPlot.Maui.MauiPlot。
- 支持 iOS、Android、Windows、macOS。
- Avalonia:
- 使用 ScottPlot.Avalonia.AvaloniaPlot。
- 跨平台桌面应用。
- Blazor:
- 支持 Web 应用,生成静态图像。
- Console:
- 支持生成图像到文件,适合脚本和服务器端。
- 局限:
- WinForms 和 WPF 交互不如 LiveCharts2 丰富。
- MAUI 和 Avalonia 支持较新,文档较少。
6. 定制化能力
ScottPlot 提供灵活的定制化,但以简单性为优先:
- 样式:
- 多轴:
- 交互:
- 内置右键菜单(放大、自动缩放、保存)。
- 主题:
- 局限:
- 复杂交互(如 Tooltip)需手动实现。
- 不支持动态主题切换。
支持自定义背景、网格、字体:csharp
plt.FigureBackground.Color = Colors.White; plt.Grid.MajorLineColor = Colors.Gray;自定义鼠标事件:csharp
formsPlot.MouseMove += (s, e) => { /* 自定义逻辑 */ };支持添加额外轴:csharp
var secondaryAxis = plt.Axes.AddLeftAxis(); secondaryAxis.Label.Text = "功率";自定义颜色、线型、标记:csharp
scatter.Color = Colors.Blue; scatter.MarkerStyle.Size = 5; scatter.LineStyle.Width = 2;7. ScottPlot 示例代码
以下是基于 ScottPlot 5.0.55 的完整示例代码,与 OxyPlot 和 LiveCharts2 示例功能一致,绘制电压曲线(原始 vs 重采样),支持负值电压,确保平滑连续。代码基于 Windows Forms,包含鼠标交互(窗体标题显示坐标)、图像导出,并结合 ResamplerMgr 类。
环境要求
- 项目:Windows Forms,.NET 8.0 或 .NET Framework 4.8。
NuGet 包:bash
dotnet add package ScottPlot.WinForms --version 5.0.55 dotnet add package MathNet.Numerics --version 5.0.0 dotnet add package System.Drawing.Common --version 8.0.0代码
csharp
using MathNet.Numerics; using MathNet.Numerics.Interpolation; using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using System.Drawing; // 添加 System.Drawing 引用 using ScottPlot; using ScottPlot.WinForms; namespace ResamplerLib { /// <summary> /// 重采样管理类,用于对电压数据进行插值重采样,确保负值区域平滑连续。 /// 使用 MathNet.Numerics 的样条插值,支持均匀采样和指定点采样。 /// </summary> public static class ResamplerMgr { 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); } } 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}"); } } 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); } 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> /// 主程序:模拟电压数据,执行重采样,使用 ScottPlot 5.0.55 绘制曲线。 /// 支持鼠标交互(显示坐标),保存图像为 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) { x.Add(t); double voltage = 5.0 * Math.Sin(t) + (rand.NextDouble() - 0.5) * 0.5; y.Add(voltage); } // 进行重采样 int sampleCount = 100; ResamplerMgr.Resample(sampleCount, x, y, out List<double> xout, out List<double> yout); // 配置 ScottPlot 图表 var plt = formsPlot.Plot; // 添加原始数据(蓝色散点) var scatterOriginal = plt.Add.Scatter(x.ToArray(), y.ToArray(), color: Colors.Blue); scatterOriginal.MarkerStyle.Size = 5; scatterOriginal.Label = "原始数据"; // 添加重采样数据(红色折线) var scatterResampled = plt.Add.Scatter(xout.ToArray(), yout.ToArray(), color: Colors.Red); scatterResampled.MarkerStyle.Size = 0; // 无散点 scatterResampled.LineStyle.Width = 2; scatterResampled.Label = "重采样数据"; // 设置图表标题和轴标签 plt.Title("电压曲线(原始 vs 重采样)", size: 14); plt.Axes.Bottom.Label.Text = "时间 (秒)"; plt.Axes.Left.Label.Text = "电压 (伏特)"; // 显示图例 plt.Legend.IsVisible = true; plt.Legend.Alignment = Alignment.UpperRight; // 自动调整轴范围 plt.Axes.AutoScale(); // 添加鼠标交互:显示坐标 formsPlot.MouseMove += (s, e) => { var pixel = new Pixel(e.Location.X, e.Location.Y); var coords = formsPlot.Plot.GetCoordinates(pixel); form.Text = $"电压曲线展示 - 时间: {coords.X:F3} 秒, 电压: {coords.Y:F3} 伏特"; }; // 保存图像到文件 try { plt.Save("voltage_curve_scottplot.png", 800, 600, ImageFormat.Png); Console.WriteLine("图像已保存为 voltage_curve_scottplot.png"); } catch (Exception ex) { Console.WriteLine($"保存图像失败:{ex.Message}"); } // 刷新图表 formsPlot.Refresh(); // 运行窗体 Application.Run(form); } } }示例代码中文解释
1. ResamplerMgr 类
- 功能:实现电压数据的重采样,确保负值电压区域平滑连续。
- 关键特性:
- 使用 MathNet.Numerics 的 CubicSpline 插值,优化负值区域平滑性。
- 验证输入:确保 x 严格递增,无 NaN 或无穷大。
- 处理插值异常:使用边界值。
- 支持均匀采样(Resample)、指定点采样(Resample 重载)和两次重采样(Resample2)。
- 方法详解:
- Resample:生成均匀采样点。
- Resample(重载):根据指定 xx 插值。
- Resample2:两次重采样,增强平滑性。
- ValidateInput:检查输入有效性。
2. 模拟电压数据
- 生成逻辑:
- 时间(x):-5 到 5 秒,步长 0.5,共 21 个点。
- 电压(y):正弦波(5.0 * Math.Sin(t))加噪声(±0.25V),包含负值。
- 固定种子(Random(42))确保可重复。
3. ScottPlot FormsPlot 控件
- 创建控件:
- 使用 ScottPlot.WinForms.FormsPlot,设置 Dock = DockStyle.Fill。
- 绘图配置:
- Scatter:
- 原始数据:蓝色散点,MarkerStyle.Size = 5。
- 重采样数据:红色折线,MarkerStyle.Size = 0,LineStyle.Width = 2。
- Axes:
- X 轴:时间,Axes.Bottom.Label.Text。
- Y 轴:电压,Axes.Left.Label.Text。
- Title:设置标题,Title 方法。
- Legend:右上角,Legend.IsVisible = true。
- AutoScale:自动调整轴范围。
- Scatter:
- 交互:
- MouseMove:将 System.Drawing.Point 转换为 Pixel,显示坐标。
- 保存:
- 使用 Save 方法保存为 800x600 PNG。
代码:csharp
var scatterOriginal = plt.Add.Scatter(x.ToArray(), y.ToArray(), color: Colors.Blue); scatterOriginal.MarkerStyle.Size = 5; scatterOriginal.Label = "原始数据"; var scatterResampled = plt.Add.Scatter(xout.ToArray(), yout.ToArray(), color: Colors.Red); scatterResampled.MarkerStyle.Size = 0; scatterResampled.LineStyle.Width = 2; scatterResampled.Label = "重采样数据";4. 运行结果
- 输出:
- 显示 800x600 窗体,包含:
- 蓝色散点:原始数据(21 点,含噪声)。
- 红色曲线:重采样数据(100 点,平滑,负值区域无断点)。
- X 轴:时间 -5 到 5 秒。
- Y 轴:电压约 -5.5 到 5.5 伏特。
- 交互:窗体标题显示鼠标位置坐标,右键菜单支持缩放/保存。
- 保存:生成 voltage_curve_scottplot.png。
- 显示 800x600 窗体,包含:
- 负值处理:
- CubicSpline 插值确保负值区域平滑。
- Axes.AutoScale() 自动包含负值。
8. ScottPlot vs OxyPlot vs LiveCharts2 对比
维度 | ScottPlot 5.0.55 | OxyPlot 2.1.0 | LiveCharts2 2.0.0-rc2 |
|---|---|---|---|
功能 | 专注 2D 图表(散点、线、信号),功能较少 | 丰富(折线、散点、热图、3D 等高线) | 丰富(折线、柱状、饼图、热图、仪表盘) |
性能 | 优异, Signal 适合 >10亿点 | 中等,适合 <10,000 点 | 优异,Backers 包支持 >50M 点,<1ms 响应 |
易用性 | API 简洁,学习曲线低,文档示例丰富 | API 复杂,学习曲线高 | 中等,MVVM 友好,学习曲线适中 |
交互性 | 基础(缩放、平移、右键菜单),需手动实现复杂交互 | 强大(内置 Tracker、缩放、选择) | 强大(Tooltip、缩放、平移),WinForms 稍弱 |
跨平台 | WinForms、WPF、MAUI、Avalonia、Blazor | WinForms、WPF、Avalonia、Maui | WPF、WinForms、MAUI、Uno、Avalonia、Blazor |
定制化 | 灵活,但复杂图表支持有限 | 高度灵活(多轴、注解、主题) | 高度灵活(主题、动画、多轴) |
社区与生态 | 活跃,更新频繁 | 稳定,更新较慢 | 活跃,快速更新,Beta 阶段 |
适用性 | 适合简单曲线、大数据、快速开发 | 适合复杂交互、工程应用 | 适合实时、跨平台、动画丰富的场景 |
代码复杂度 | 低,单行 API 即可绘图 | 高,需配置 PlotModel | 中等,MVVM 需额外配置 |
- ScottPlot 优势:
- API 极简,适合快速开发。
- 大数据性能优(Signal 模式)。
- 轻量级,依赖少。
- OxyPlot 优势:
- 内置 Tracker,交互开箱即用。
- 支持复杂图表(多轴、热图)。
- LiveCharts2 优势:
- 跨平台支持强大,动画流畅。
- MVVM 友好,适合动态数据。
- 推荐:
- ScottPlot:适合本例(电压曲线)中简单、高效的绘制需求,特别适合大数据和快速原型。
- OxyPlot:适合需要高级交互和复杂图表的工程应用。
- LiveCharts2:适合跨平台、实时更新、动画丰富的场景。
9. 使用说明与优化
- 运行示例:
- 创建 Windows Forms 项目。
- 复制代码到 Program.cs,运行。
- 检查 voltage_curve_scottplot.png 输出。
- 调整参数:
- 采样点:修改 sampleCount(例如 200)提高分辨率。
- 调试负值问题:
- 检查控制台日志,确认 NaN 警告。
- 扩展:
- WPF:
- 性能优化:
禁用交互:csharp
formsPlot.Configuration.Zoom = false; formsPlot.Configuration.Pan = false;大数据:使用 Signal:csharp
plt.Add.SignalXY(xout.ToArray(), yout.ToArray());使用 WpfPlot:csharp
using ScottPlot.Wpf; var wpfPlot = new WpfPlot { Width = 800, Height = 600 };安装 ScottPlot.Wpf:bash
dotnet add package ScottPlot.Wpf --version 5.0.55动态更新:csharp
scatterOriginal.Data.X = newX; scatterOriginal.Data.Y = newY; formsPlot.Refresh();多轴:csharp
var secondaryAxis = plt.Axes.AddLeftAxis(); secondaryAxis.Label.Text = "功率"; var powerSeries = plt.Add.Scatter(x, powerData); powerSeries.Axes.YAxis = secondaryAxis;添加日志:csharp
xout.Add(sx); yout.Add(sy); Console.WriteLine($"点:sx={sx:F3}, sy={sy:F3}");数据模拟:csharp
double voltage = 10.0 * Math.Sin(t * 2) + (rand.NextDouble() - 0.5) * 1.0;安装 NuGet 包:bash
dotnet add package ScottPlot.WinForms --version 5.0.55 dotnet add package MathNet.Numerics --version 5.0.0 dotnet add package System.Drawing.Common --version 8.0.010. 负值问题解决总结
- 问题:负值电压可能导致曲线不连续,通常由插值 NaN 或输入异常引起。
- 解决:
- 使用 CubicSpline 插值,增强负值区域平滑性。
- 边界处理:插值异常时使用边界值。
- 输入验证:确保 x 递增、无 NaN。
- 增加采样点(sampleCount = 100)。
- 日志记录:便于调试。
11. 资源与参考
- 官方文档:ScottPlot - Interactive Plotting Library for .NET
- GitHub:https://github.com/ScottPlot/ScottPlot
- NuGet:https://www.nuget.org/packages/ScottPlot.WinForms/5.0.55
- Cookbook:ScottPlot 5.0 Cookbook
- 社区:Stack Overflow(#ScottPlot)、GitHub Issues、Discord。
如果需要 WPF/MAUI 版本、动态数据示例、或特定场景优化(如多轴、大数据),请提供详情,我将提供定制代码!