跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C#算法

ScottPlot .NET 开源绘图库核心特性与电压曲线示例

ScottPlot 作为 .NET 开源 2D 绘图库的核心特性、架构及跨平台支持。重点展示了如何使用 ScottPlot 5.0.55 结合 ResamplerMgr 类绘制电压曲线,通过 CubicSpline 插值处理负值数据以确保平滑连续。内容涵盖 API 使用方法、性能分析、与 OxyPlot 和 LiveCharts2 的对比,以及完整的 C# 代码示例和负值问题解决总结。

静心发布于 2026/3/28更新于 2026/10/20122 浏览

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。
  • Interaction:
    • 内置鼠标交互(缩放、平移、右键菜单)。
    • 支持自定义事件(如 MouseMove 获取坐标)。
  • 3. ScottPlot API 使用方法

    以下是使用 ScottPlot 5.0.55 绘制图表的基本步骤:

    1. 安装 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
      
    2. 创建 Plot:
      var plt = new ScottPlot.Plot();
      
    3. 添加数据系列:
      plt.Add.Scatter(x, y);
      
    4. 配置轴和样式:
      plt.Title("电压曲线");
      plt.Axes.Left.Label.Text = "电压 (伏特)";
      plt.Axes.Bottom.Label.Text = "时间 (秒)";
      
    5. 绑定到控件:
      var formsPlot = new FormsPlot { Dock = DockStyle.Fill };
      formsPlot.Plot.Add.Scatter(x, y);
      
    6. 交互与导出:
      // 导出图像
      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];
                    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);
            }
        }
    }
    

    8. 示例代码说明

    1. ResamplerMgr 类:
      • 功能:实现电压数据的重采样,确保负值电压区域平滑连续。
      • 关键特性:
        • 使用 MathNet.Numerics 的 CubicSpline 插值,优化负值区域平滑性。
        • 验证输入:确保 x 严格递增,无 NaN 或无穷大。
        • 处理插值异常:使用边界值。
        • 支持均匀采样(Resample)、指定点采样(Resample 重载)和两次重采样(Resample2)。
    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:原始数据(蓝色散点),重采样数据(红色折线)。
        • Axes:X 轴时间,Y 轴电压。
        • Title:设置标题。
        • Legend:右上角显示。
        • AutoScale:自动调整轴范围。
      • 交互:MouseMove 显示坐标。
      • 保存:Save 方法保存为 PNG。

    9. ScottPlot vs OxyPlot vs LiveCharts2 对比

    维度ScottPlot 5.0.55OxyPlot 2.1.0LiveCharts2 2.0.0-rc2
    功能专注 2D 图表(散点、线、信号),功能较少丰富(折线、散点、热图、3D 等高线)丰富(折线、柱状、饼图、热图、仪表盘)
    性能优异,Signal 适合 >10 亿点中等,适合 <10,000 点优异,Backers 包支持 >50M 点,<1ms 响应
    易用性API 简洁,学习曲线低,文档示例丰富API 复杂,学习曲线高中等,MVVM 友好,学习曲线适中
    交互性基础(缩放、平移、右键菜单),需手动实现复杂交互强大(内置 Tracker、缩放、选择)强大(Tooltip、缩放、平移),WinForms 稍弱
    跨平台WinForms、WPF、MAUI、Avalonia、BlazorWinForms、WPF、Avalonia、MauiWPF、WinForms、MAUI、Uno、Avalonia、Blazor
    定制化灵活,但复杂图表支持有限高度灵活(多轴、注解、主题)高度灵活(主题、动画、多轴)
    社区与生态活跃,更新频繁稳定,更新较慢活跃,快速更新,Beta 阶段
    适用性适合简单曲线、大数据、快速开发适合复杂交互、工程应用适合实时、跨平台、动画丰富的场景
    代码复杂度低,单行 API 即可绘图高,需配置 PlotModel中等,MVVM 需额外配置
    • ScottPlot 优势:
      • API 极简,适合快速开发。
      • 大数据性能优(Signal 模式)。
      • 轻量级,依赖少。
    • OxyPlot 优势:
      • 内置 Tracker,交互开箱即用。
      • 支持复杂图表(多轴、热图)。
    • LiveCharts2 优势:
      • 跨平台支持强大,动画流畅。
      • MVVM 友好,适合动态数据。
    • 推荐:
      • ScottPlot:适合本例(电压曲线)中简单、高效的绘制需求,特别适合大数据和快速原型。
      • OxyPlot:适合需要高级交互和复杂图表的工程应用。
      • LiveCharts2:适合跨平台、实时更新、动画丰富的场景。

    10. 使用说明与优化

    1. 运行示例:
      • 创建 Windows Forms 项目。
      • 复制代码到 Program.cs,运行。
      • 检查 voltage_curve_scottplot.png 输出。
    2. 调整参数:
      • 采样点:修改 sampleCount(例如 200)提高分辨率。
    3. 调试负值问题:
      • 检查控制台日志,确认 NaN 警告。
    4. 扩展:
      • WPF:使用 ScottPlot.Wpf.WpfPlot。
      • 性能优化:禁用交互或使用 Signal 模式。
      • 动态更新:修改 Data 属性并调用 Refresh。
      • 多轴:添加 Secondary Axis。

    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。

    目录

    1. 1. ScottPlot 概述
    2. 2. ScottPlot 架构
    3. 3. ScottPlot API 使用方法
    4. 4. ScottPlot 性能分析
    5. 5. 跨平台支持
    6. 6. 定制化能力
    7. 7. ScottPlot 示例代码
    8. 环境要求
    9. 代码
    10. 8. 示例代码说明
    11. 9. ScottPlot vs OxyPlot vs LiveCharts2 对比
    12. 10. 使用说明与优化
    13. 11. 负值问题总结
    14. 12. 参考资料

    更多推荐文章

    查看全部
    • 前端技术趋势:React 18 并发模式与 Server Components
    • llama.cpp 量化模型部署实战:从模型转换到 API 服务
    • DeepSeek 与通义万相结合实现 AI 视频生成流程指南
    • IQuest-Coder-V1 vs Meta-Llama-Code:指令模型精度实测
    • 整车质量估计算法:从原理到实车应用
    • Webnovel Writer:基于 Claude Code 的长篇网文 AI 创作系统
    • 前端国际化最佳实践指南
    • Bing Webmaster 工具使用指南:站点验证与收录提交
    • C++ 与 Linux 内核:深入剖析 open() 系统调用与文件描述符机制
    • Android 工程师面试准备指南:核心知识点与实战技巧
    • Docker 部署 OpenClaw 常见问题排查与自定义模型配置
    • HarmonyOS 6.0 PC 端高性能图像展示器开发:ArkUI 适配与跨设备流转
    • ESP32 驱动 OV7670 摄像头实现简易照相机系统
    • 基于 LangChain 实现数据库问答机器人
    • FastGPT 结合 MCP 协议实现工具增强型智能体构建
    • 计算机网络:陆地无线电信道概述
    • Revit 模型 Web 端轻量化展示与 GLTF 转换实践
    • Python 爬虫实战:使用 Requests 与 Parsel 下载图片
    • Stable Diffusion v1.5 Archive 跨平台效果一致性保障与复现验证
    • FPGA 商用级 ISP:动态坏点校正 DPCC 的滑窗架构与并行判决实现

    相关免费在线工具

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

    • Markdown转HTML

      将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

    • HTML转Markdown

      将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online