1. ScottPlot 概述
- 简介:
- ScottPlot 是一个 .NET 开源绘图库,专注于简单、高性能的 2D 图表绘制,支持折线图、散点图、柱状图、热图等。
- 开源(MIT 许可证),始于 2018 年,社区活跃,更新频繁。
- GitHub 仓库:https://github.com/ScottPlot/ScottPlot
- 最新版本(截至 2025 年 6 月 22 日):5.0.55(2025 年 5 月发布)。
- 官网:https://scottplot.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。
- WinForms:
- Interaction:
- 内置鼠标交互(缩放、平移、右键菜单)。
- 支持自定义事件(如 MouseMove 获取坐标)。
3. ScottPlot API 使用方法
以下是使用 ScottPlot 5.0.55 绘制图表的基本步骤:
- 安装 NuGet 包:
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 - 创建 Plot:
var plt = new ScottPlot.Plot(); - 添加数据系列:
plt.Add.Scatter(x, y); - 配置轴和样式:
plt.Title("电压曲线"); plt.Axes.Left.Label.Text = "电压 (伏特)"; plt.Axes.Bottom.Label.Text = "时间 (秒)"; - 绑定到控件:
var formsPlot = new FormsPlot { Dock = DockStyle.Fill }; formsPlot.Plot.Add.Scatter(x, y); - 交互与导出:
// 导出图像 plt.Save("chart.png", 800, 600, ImageFormat.Png); // 添加鼠标交互 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} 伏特"; };
4. 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 提供灵活的定制化,但以简单性为优先:
- 样式:
- 支持自定义背景、网格、字体。
plt.FigureBackground.Color = Colors.White; plt.Grid.MajorLineColor = Colors.Gray; - 多轴:
- 支持添加额外轴。
var secondaryAxis = plt.Axes.AddLeftAxis(); secondaryAxis.Label.Text = "功率"; - 交互:
- 内置右键菜单(放大、自动缩放、保存)。
- 自定义鼠标事件。
formsPlot.MouseMove += (s, e) => { /* 自定义逻辑 */ }; - 颜色与标记:
scatter.Color = Colors.Blue; scatter.MarkerStyle.Size = 5; scatter.LineStyle.Width = 2; - 局限:
- 复杂交互(如 Tooltip)需手动实现。
- 不支持动态主题切换。
7. ScottPlot 示例代码
以下是基于 ScottPlot 5.0.55 的完整示例代码,与 OxyPlot 和 LiveCharts2 示例功能一致,绘制电压曲线(原始 vs 重采样),支持负值电压,确保平滑连续。代码基于 Windows Forms,包含鼠标交互(窗体标题显示坐标)、图像导出,并结合 ResamplerMgr 类。
环境要求
- 项目:Windows Forms,.NET 8.0 或 .NET Framework 4.8。
- NuGet 包:
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
代码
using MathNet.Numerics;
using MathNet.Numerics.Interpolation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using 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];
(sampleCount == || Math.Abs(stop - start) < )
{
xout.Add(start);
yout.Add(y[]);
;
}
step = (stop - start) / (sampleCount - );
( sx = start; sx <= stop + ; sx += step)
{
sy = ip.Interpolate(sx);
(.IsNaN(sy) || .IsInfinity(sy))
{
sy = sx <= x[] ? y[] : y[y.Count - ];
Console.WriteLine();
}
xout.Add(sx);
yout.Add(sy);
}
}
(Exception ex)
{
Console.WriteLine();
xout = List<>(x);
yout = List<>(y);
}
}
{
yy = List<>();
(!ValidateInput(x, y, ) || xx == || xx.Count == )
{
Console.WriteLine();
;
}
{
IInterpolation ip = Interpolate.CubicSpline(x.ToArray(), y.ToArray());
( sx xx)
{
sy = ip.Interpolate(sx);
(.IsNaN(sy) || .IsInfinity(sy))
{
sy = sx <= x[] ? y[] : y[y.Count - ];
Console.WriteLine();
}
yy.Add(sy);
}
}
(Exception ex)
{
Console.WriteLine();
}
}
{
xout = List<>();
yout = List<>();
Resample( * sampleCount, x, y, List<> xTemp, List<> yTemp);
List<> x2 = List<>(x);
List<> y2 = List<>(y);
x2.AddRange(xTemp);
y2.AddRange(yTemp);
Resample(sampleCount, x2, y2, xout, yout);
}
{
(x == || y == || x.Count != y.Count || x.Count < || sampleCount <= )
{
Console.WriteLine();
;
}
(x.Any(.IsNaN) || y.Any(.IsNaN) || x.Any(.IsInfinity) || y.Any(.IsInfinity))
{
Console.WriteLine();
;
}
( i = ; i < x.Count; i++)
{
(x[i] <= x[i - ])
{
Console.WriteLine();
;
}
}
;
}
}
{
[]
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault();
form = Form { Text = , Width = , Height = };
formsPlot = FormsPlot { Dock = DockStyle.Fill };
form.Controls.Add(formsPlot);
List<> x = List<>();
List<> y = List<>();
Random rand = Random();
( t = ; t <= ; t += )
{
x.Add(t);
voltage = * Math.Sin(t) + (rand.NextDouble() - ) * ;
y.Add(voltage);
}
sampleCount = ;
ResamplerMgr.Resample(sampleCount, x, y, List<> xout, List<> yout);
plt = formsPlot.Plot;
scatterOriginal = plt.Add.Scatter(x.ToArray(), y.ToArray(), color: Colors.Blue);
scatterOriginal.MarkerStyle.Size = ;
scatterOriginal.Label = ;
scatterResampled = plt.Add.Scatter(xout.ToArray(), yout.ToArray(), color: Colors.Red);
scatterResampled.MarkerStyle.Size = ;
scatterResampled.LineStyle.Width = ;
scatterResampled.Label = ;
plt.Title(, size: );
plt.Axes.Bottom.Label.Text = ;
plt.Axes.Left.Label.Text = ;
plt.Legend.IsVisible = ;
plt.Legend.Alignment = Alignment.UpperRight;
plt.Axes.AutoScale();
formsPlot.MouseMove += (s, e) =>
{
pixel = Pixel(e.Location.X, e.Location.Y);
coords = formsPlot.Plot.GetCoordinates(pixel);
form.Text = ;
};
{
plt.Save(, , , ImageFormat.Png);
Console.WriteLine();
}
(Exception ex)
{
Console.WriteLine();
}
formsPlot.Refresh();
Application.Run(form);
}
}
}
8. 示例代码说明
- ResamplerMgr 类:
- 功能:实现电压数据的重采样,确保负值电压区域平滑连续。
- 关键特性:
- 使用 MathNet.Numerics 的 CubicSpline 插值,优化负值区域平滑性。
- 验证输入:确保 x 严格递增,无 NaN 或无穷大。
- 处理插值异常:使用边界值。
- 支持均匀采样(Resample)、指定点采样(Resample 重载)和两次重采样(Resample2)。
- 模拟电压数据:
- 生成逻辑:
- 时间(x):-5 到 5 秒,步长 0.5,共 21 个点。
- 电压(y):正弦波(5.0 * Math.Sin(t))加噪声(±0.25V),包含负值。
- 固定种子(Random(42))确保可重复。
- 生成逻辑:
- ScottPlot FormsPlot 控件:
- 创建控件:使用
ScottPlot.WinForms.FormsPlot,设置Dock = DockStyle.Fill。 - 绘图配置:
- Scatter:原始数据(蓝色散点),重采样数据(红色折线)。
- Axes:X 轴时间,Y 轴电压。
- Title:设置标题。
- Legend:右上角显示。
- AutoScale:自动调整轴范围。
- 交互:MouseMove 显示坐标。
- 保存:Save 方法保存为 PNG。
- 创建控件:使用
9. 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:适合跨平台、实时更新、动画丰富的场景。
10. 使用说明与优化
- 运行示例:
- 创建 Windows Forms 项目。
- 复制代码到 Program.cs,运行。
- 检查
voltage_curve_scottplot.png输出。
- 调整参数:
- 采样点:修改
sampleCount(例如 200)提高分辨率。
- 采样点:修改
- 调试负值问题:
- 检查控制台日志,确认 NaN 警告。
- 扩展:
- WPF:使用
ScottPlot.Wpf.WpfPlot。 - 性能优化:禁用交互或使用 Signal 模式。
- 动态更新:修改 Data 属性并调用 Refresh。
- 多轴:添加 Secondary Axis。
- WPF:使用
11. 负值问题总结
- 问题:负值电压可能导致曲线不连续,通常由插值 NaN 或输入异常引起。
- 解决:
- 使用 CubicSpline 插值,增强负值区域平滑性。
- 边界处理:插值异常时使用边界值。
- 输入验证:确保 x 递增、无 NaN。
- 增加采样点(sampleCount = 100)。
- 日志记录:便于调试。
12. 参考资料
- 官方文档:https://scottplot.net/
- GitHub:https://github.com/ScottPlot/ScottPlot
- NuGet:https://www.nuget.org/packages/ScottPlot.WinForms/5.0.55
- Cookbook:https://scottplot.net/cookbook/5.0/
- 社区:Stack Overflow(#ScottPlot)、GitHub Issues、Discord。

