ScottPlot 读取本地大批量数据绘制尝试 – 三郎君的日常

面试 · 2024年10月21日

ScottPlot 读取本地大批量数据绘制尝试

相关介绍

布局

绘图类型

  • 注释 – 注释是位于数据区域上方的始终可见的文本标签。
  • 箭头 – 箭头指向坐标空间中的某个位置。
  • 轴线 – 轴线表示轴上的位置。
  • 轴跨度 – 轴跨度表示轴的范围。
  • 条形图 – 条形图将值表示为水平或垂直矩形
  • 箱形图 – 箱形图一目了然地显示分布
  • 标注 – 标注显示标签,并通过标记图上点的箭头连接。
  • Coxcomb 图 – Coxcomb 图是一个饼图,其中切片的角度是恒定的,但半径不是。
  • 十字准线 – 十字准线结合了水平轴线和垂直轴线,以在坐标空间中标记位置。
  • 椭圆 – 椭圆是具有定义中心和不同 X 和 Y 半径的曲线。圆是 X 半径等于其 Y 半径的椭圆。
  • 误差线 – 误差线表示测量值的可能范围
  • FillY 图 – FillY 图在定义的 X 位置显示两个 Y 值之间的垂直范围
  • 财务图 – 财务图显示按时间范围分箱的价格数据
  • 函数 – 函数图是一种线图,其中 Y 位置由依赖于 X 的函数定义,而不是离散数据点的集合。
  • 热图 – 热图将 2D 数据中的值显示为具有不同强度的像元的图像
  • 图像 – 可以通过多种方式将图像放置在绘图上
  • Line Plot – 可以使用 Start、End 和可选的 LineStyle 将线图放置在坐标空间中的绘图上。
  • 棒棒糖图 – 棒棒糖图是条形图的一种变体,它使用从基线延伸到标记(头部)的线条(茎)来表示数据点。与传统条形图相比,Lollipop 突出显示单个数据点的视觉混乱程度较低。
  • 标记 – 标记可以放置在坐标空间的绘图上。
  • 相量图 – 相量图在以原点为中心的径向轴上显示矢量
  • 饼图 – 饼图将数字比例说明为圆的切片。
  • 极轴 – 创建极轴并将其添加到绘图中,以在圆形坐标系上显示数据。
  • 总体图 – 总体图显示单个值的集合。
  • 雷达图 – 雷达图(也称为蜘蛛图或星形图)将多轴数据表示为围绕中心点圆周排列的轴上的 2D 形状。
  • 径向仪表 – 径向仪表图将标量数据显示为圆形仪表。
  • 散点图 – 散点图在坐标空间中的 X/Y 位置显示点。
  • 形状 – 可以添加到绘图的基本形状
  • 信号图 – 信号图显示均匀分布的数据
  • SignalConst – SignalConst 是一种信号图,它包含不可变的数据点并占用更多内存,但为超大型数据集提供更高的性能。它很少需要,但最适合用于绘制包含数百万个点的数据。
  • SignalXY 绘图 – SignalXY 是一种高性能绘图类型,针对 X 值始终升序的 X/Y 对进行了优化。对于大型数据集,SignalXY 图的性能比散点图(允许无序的 X 点)高得多,但不如信号图(需要 X 点之间的固定间距)的性能。
  • 文本 – 文本标签可以放置在坐标空间的绘图上
  • Vector Field – 向量场显示以坐标空间中的点为根的向量集合

统计学

  • 回归 – 使线条适合数据的统计操作

杂项

  • 高级样式 – 适用于寻求广泛自定义选项的用户的功能。
  • 国际化 – 跨具有不同文本和数字要求的区域性使用 ScottPlot。
  • 调色板 – 可用于表示分类数据的颜色集合
  • Colormaps – 可用于表示连续数据的颜色渐变

xaml

<Window x:Class="RealTimePlotDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RealTimePlotDemo"
        xmlns:ScottPlot="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"  
        mc:Ignorable="d"
        Title="Real-Time Plot Demo" Height="650" Width="1100">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="4*"/>
            <!-- 折线图区域 -->
            <RowDefinition Height="3*"/>
            <!-- 直方图区域 -->
        </Grid.RowDefinitions>

        <ScottPlot:WpfPlot Name="linePlot" Grid.Row="0" />
        <!-- 折线图 -->
        <ScottPlot:WpfPlot Name="histogramPlot" Grid.Row="1" />
        <!-- 直方图 -->
    </Grid>
</Window>

xaml.cs

using ScottPlot;
using System;
using System.IO;
using System.Linq;
using System.Timers;
using System.Windows;

namespace RealTimePlotDemo {
    public partial class MainWindow : Window {
        // 常量定义
        private const int DataSize = 50000; // 数据点总数
        private const double XInterval = 2.0; // X 值之间的间隔
        private const double YAmplitude = 0.5; // 正弦波的幅度
        private const double NoiseRange = 0.02; // 随机噪声的范围
        private const double UpdateInterval = 100; // 图形更新的时间间隔(毫秒)
        private const double VisibleRange = 50; // 可视范围的长度(X 轴)
        private const int BatchSize = 100; // 每次绘制的数据点数量,设置为 1 确保每个点对应一个矩形

        private Timer _timer; // 定时器
        private double[] _lineDataX; // X 轴数据
        private double[] _lineDataY; // Y 轴数据
        private double[] _histogramData; // 直方图数据
        private double[] _histogramX; // 直方图的 X 轴数据
        private Random _random; // 随机数生成器
        private string _dataFilePath; // 保存数据的文件路径
        private int _currentIndex = 0; // 当前读取的索引

        public MainWindow() {
            InitializeComponent();

            _random = new Random();
            _lineDataX = new double[DataSize];
            _lineDataY = new double[DataSize];
            _histogramData = new double[DataSize]; // 直方图数据长度,修改为与数据点数相同
            _histogramX = new double[DataSize]; // 直方图的 X 轴数据

            // 保存数据的文件路径
            _dataFilePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "data.csv");

            // 生成所有数据并保存到本地
            GenerateAndSaveData();

            // 初始化折线图和直方图
            InitializePlots();

            // 设置定时器,每隔指定的时间间隔更新图形
            _timer = new Timer(UpdateInterval);
            _timer.Elapsed += UpdatePlots;
            _timer.Start();
        }

        // 生成数据并保存到文件
        private void GenerateAndSaveData() {
            using (var writer = new StreamWriter(_dataFilePath)) {
                for (int i = 0; i < DataSize; i++) {
                    _lineDataX[i] = i * XInterval; // 增加 X 值之间的间隔

                    // 生成 Y 值,使用正弦函数,并添加随机噪声
                    double noise = _random.NextDouble() * NoiseRange - (NoiseRange / 2); // 随机噪声范围
                    _lineDataY[i] = YAmplitude * Math.Sin(_lineDataX[i]) + noise; // Y 值变化幅度减小

                    // 保存数据到文件
                    writer.WriteLine($"{_lineDataX[i]},{_lineDataY[i]}");
                }
            }
        }

        // 初始化折线图和直方图
        private void InitializePlots() {
            linePlot.Plot.Axes.SetLimitsX(0, VisibleRange); // X 轴范围
            linePlot.Plot.Axes.SetLimitsY(0, 1.2); // Y 轴范围
            histogramPlot.Plot.Axes.SetLimitsX(0, VisibleRange); // 直方图 X 轴范围
            histogramPlot.Plot.Axes.SetLimitsY(0, 1.2); // 直方图 Y 轴范围
            linePlot.Refresh();
            histogramPlot.Refresh();
        }

        // 更新图形
        private void UpdatePlots(object sender, ElapsedEventArgs e) {
            // 检查是否读取完所有数据
            if (_currentIndex >= DataSize) {
                _timer.Stop();
                return;
            }

            // 逐渐读取并更新折线图
            ReadData();
            Dispatcher.Invoke(() => {
                RefreshLinePlot();
                RefreshHistogramPlot();
                UpdateAxisLimits(); // 更新坐标轴限制
            });
        }

        // 逐步读取数据
        private void ReadData() {
            // 逐步读取数据
            int endIndex = Math.Min(_currentIndex + BatchSize, DataSize);

            for (int i = _currentIndex; i < endIndex; i++) {
                _lineDataX[i] = i * XInterval; // 假设每个点间隔为 XInterval
                _lineDataY[i] = _random.NextDouble(); // 随机生成 Y 值
            }

            _currentIndex = endIndex; // 更新当前索引
        }

        // 刷新折线图
        private void RefreshLinePlot() {
            linePlot.Plot.Clear();
            linePlot.Plot.Add.Scatter(_lineDataX.Take(_currentIndex).ToArray(), _lineDataY.Take(_currentIndex).ToArray());
            linePlot.Refresh();
        }

        // 刷新直方图
        private void RefreshHistogramPlot() {
            histogramPlot.Plot.Clear();

            // 使用已读取的所有数据来绘制直方图,而不是只绘制最近的 BatchSize 个点
            int barCount = _currentIndex; // 直方图的条数为当前读取的所有点
            double[] sampledHistogramX = new double[barCount]; // 每个点对应一个矩形
            double[] sampledHistogramData = new double[barCount];

            // 填充直方图数据
            for (int i = 0; i < barCount; i++) {
                sampledHistogramX[i] = _lineDataX[i]; // 对应的 X 值
                sampledHistogramData[i] = _lineDataY[i]; // 对应的 Y 值
            }

            // 添加矩形到图表中
            histogramPlot.Plot.Add.Bars(sampledHistogramX, sampledHistogramData); // 矩形宽度可以根据需要调整

            // 更新坐标轴范围
            UpdateHistogramAxisLimits(sampledHistogramData);

            histogramPlot.Refresh();
        }



        // 更新坐标轴限制
        private void UpdateAxisLimits() {
            if (_currentIndex > 0) {
                // 计算当前X轴的最小值和最大值
                double minX = _lineDataX[Math.Max(0, _currentIndex - (int)VisibleRange)];
                double maxX = _lineDataX[_currentIndex - 1];

                // 更新 X 轴范围
                linePlot.Plot.Axes.SetLimitsX(minX, maxX);
                histogramPlot.Plot.Axes.SetLimitsX(minX, maxX); // 更新直方图 X 轴范围
            }
        }

        // 更新直方图的Y轴限制
        private void UpdateHistogramAxisLimits(double[] sampledHistogramData) {
            if (sampledHistogramData.Length > 0) {
                // 更新Y轴范围
                double maxY = sampledHistogramData.Max();
                histogramPlot.Plot.Axes.SetLimitsY(0, maxY * 1.1); // 给 Y 轴留一点空间
            }
        }
    }

    // 随机数扩展方法
    public static class RandomExtensions {
        public static double[] NextDoubles(this Random rand, int count) {
            return Enumerable.Range(0, count).Select(_ => rand.NextDouble()).ToArray();
        }
    }
}