1. ScottPlot 概述
- 简介:
- ScottPlot 是一个 .NET 开源绘图库,专注于简单、高性能的 2D 图表绘制,支持折线图、散点图、柱状图、热图等。
- 开源(MIT 许可证),始于 2018 年,社区活跃,更新频繁。
ScottPlot 作为 .NET 开源 2D 绘图库的核心特性、架构及跨平台支持。重点展示了如何使用 ScottPlot 5.0.55 结合 ResamplerMgr 类绘制电压曲线,通过 CubicSpline 插值处理负值数据以确保平滑连续。内容涵盖 API 使用方法、性能分析、与 OxyPlot 和 LiveCharts2 的对比,以及完整的 C# 代码示例和负值问题解决总结。
plt.Add.Scatter(x, y)。ScottPlot 采用简洁的分层设计:
var plt = new ScottPlot.Plot();plt.Add.Scatter(x, y);plt.Axes.Left.Label.Text = "电压";plt.Save("chart.png", 800, 600);ScottPlot.WinForms.FormsPlot。ScottPlot.Wpf.WpfPlot。ScottPlot.Maui.MauiPlot。以下是使用 ScottPlot 5.0.55 绘制图表的基本步骤:
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
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} 伏特";
};
formsPlot.Refresh()。ScottPlot 5.0.55 支持多种平台:
ScottPlot.WinForms.FormsPlot。ScottPlot.Wpf.WpfPlot。ScottPlot.Maui.MauiPlot。ScottPlot.Avalonia.AvaloniaPlot。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;
以下是基于 ScottPlot 5.0.55 的完整示例代码,与 OxyPlot 和 LiveCharts2 示例功能一致,绘制电压曲线(原始 vs 重采样),支持负值电压,确保平滑连续。代码基于 Windows Forms,包含鼠标交互(窗体标题显示坐标)、图像导出,并结合 ResamplerMgr 类。
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];
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()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form { Text = "电压曲线展示 - ScottPlot 5.0.55", Width = 800, Height = 600 };
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);
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);
}
}
}
ScottPlot.WinForms.FormsPlot,设置 Dock = DockStyle.Fill。| 维度 | 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 需额外配置 |
voltage_curve_scottplot.png 输出。sampleCount(例如 200)提高分辨率。ScottPlot.Wpf.WpfPlot。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online