C#编写多导联扫描式的波形图Demo

news2024/9/29 17:38:46

本代码调用ZedGraph绘图框架,自己先安装好ZedGraph环境,然后拖一个zedGraphControl控件就行了,直接黏贴下面代码

基本代码显示

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;

namespace ECGPlot
{
    public partial class Form1 : Form
    {
        // 定义导联数量和每个导联的电压偏移量
        private const int ChannelCount = 8;
        private const int VoltageOffset = 150; // 每个导联的偏移量

        // 用于存储每个导联的数据点列表和曲线对象
        private PointPairList[] _dataLists;
        private LineItem[] _curves;
        
        // 定时器用于模拟ECG信号数据更新
        private System.Timers.Timer _timer;
        private int _currentIndex = 0;
        private int _maxPoints = 500;
        private double[][] _yValues;
        private double _timeIncrement = 0.1;
        private double _currentTime = 0;
        private Random _random = new Random();

        public Form1()
        {
            InitializeComponent();
            InitializeGraph(); // 初始化图表
            StartTimer(); // 启动定时器
        }

        private void InitializeGraph()
        {
            // 获取图表区域对象
            GraphPane myPane = zedGraphControl1.GraphPane;

            // 设置图表标题和轴标题
            myPane.Title.Text = "ECG Data";
            myPane.XAxis.Title.Text = "Time";
            myPane.YAxis.Title.Text = "Voltage";

            // 初始化数据点列表和曲线数组
            _dataLists = new PointPairList[ChannelCount];
            _curves = new LineItem[ChannelCount];
            _yValues = new double[ChannelCount][];

            for (int i = 0; i < ChannelCount; i++)
            {
                // 为每个导联创建数据点列表和曲线对象,并添加到图表中
                _dataLists[i] = new PointPairList();
                _curves[i] = myPane.AddCurve($"ECG Channel {i + 1}", _dataLists[i], GetColor(i), SymbolType.None);
                _yValues[i] = new double[_maxPoints];
            }

            // 设置X轴和Y轴的范围
            myPane.XAxis.Scale.Min = 0;
            myPane.XAxis.Scale.Max = _maxPoints * _timeIncrement;
            myPane.YAxis.Scale.Min = -600;
            myPane.YAxis.Scale.Max = 600 + VoltageOffset * (ChannelCount - 1);

            // 显示网格线
            myPane.XAxis.MajorGrid.IsVisible = true;
            myPane.YAxis.MajorGrid.IsVisible = true;

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private System.Drawing.Color GetColor(int index)
        {
            // 定义一组颜色用于不同导联的曲线
            System.Drawing.Color[] colors = {
                System.Drawing.Color.Black,
                System.Drawing.Color.Red,
                System.Drawing.Color.Blue,
                System.Drawing.Color.Green,
                System.Drawing.Color.Purple,
                System.Drawing.Color.Orange,
                System.Drawing.Color.Brown,
                System.Drawing.Color.Magenta
            };
            // 根据索引返回颜色
            return colors[index % colors.Length];
        }

        private void StartTimer()
        {
            // 创建并配置定时器
            _timer = new System.Timers.Timer(100); // 100毫秒的更新频率
            _timer.Elapsed += OnTimedEvent; // 绑定定时器事件
            _timer.AutoReset = true; // 自动重置
            _timer.Enabled = true; // 启用定时器
        }

        private void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            // 为每个导联生成模拟ECG信号数据并更新曲线
            for (int i = 0; i < ChannelCount; i++)
            {
                double voltage = _random.Next(-400, 400) + i * VoltageOffset; // 生成带偏移量的电压数据
                _yValues[i][_currentIndex] = voltage;

                if (_dataLists[i].Count < _maxPoints)
                {
                    // 添加新的数据点
                    _dataLists[i].Add(_currentTime, voltage);
                }
                else
                {
                    // 更新现有数据点
                    _dataLists[i][_currentIndex].Y = voltage;
                }
            }

            // 更新时间和当前索引
            _currentTime += _timeIncrement;
            _currentIndex = (_currentIndex + 1) % _maxPoints;

            // 使图表无效以触发重绘
            zedGraphControl1.Invalidate();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 窗体加载事件处理方法(目前为空)
        }
    }
}

注释解释:

  1. 全局变量定义:定义导联数量、每个导联的电压偏移量,以及存储数据和曲线的变量。
  2. 构造函数:调用 InitializeGraphStartTimer 方法初始化图表和启动定时器。
  3. InitializeGraph 方法:初始化图表区域,设置标题和轴标题,创建每个导联的曲线对象,并设置轴的范围和网格。
  4. GetColor 方法:定义一组颜色,根据索引返回颜色用于不同导联的曲线。
  5. StartTimer 方法:创建并配置定时器,设置定时器事件处理方法。
  6. OnTimedEvent 方法:在定时器触发时生成模拟ECG信号数据,为每个导联添加或更新数据点,并刷新图表。
  7. Form1_Load 方法:窗体加载事件处理方法(目前为空)。

在这里插入图片描述

添加了y轴方向的导联标签

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;

namespace ECGPlot
{
    public partial class Form1 : Form
    {
        // 定义导联数量和每个导联的电压偏移量
        private const int ChannelCount = 8;
        private const int VoltageOffset = 500; // 每个导联的偏移量

        // 用于存储每个导联的数据点列表和曲线对象
        private PointPairList[] _dataLists;
        private LineItem[] _curves;

        // 定时器用于模拟ECG信号数据更新
        private System.Timers.Timer _timer;
        private int _currentIndex = 0;
        private int _maxPoints = 500;
        private double[][] _yValues;
        private double _timeIncrement = 0.1;
        private double _currentTime = 0;
        private Random _random = new Random();

        public Form1()
        {
            InitializeComponent();
            InitializeGraph(); // 初始化图表
            StartTimer(); // 启动定时器
        }

        private void InitializeGraph()
        {
            // 获取图表区域对象
            GraphPane myPane = zedGraphControl1.GraphPane;

            // 设置图表标题和轴标题
            myPane.Title.Text = "ECG Data";
            myPane.XAxis.Title.Text = "Time (s)";
            myPane.YAxis.Title.Text = "Voltage";

            // 初始化数据点列表和曲线数组
            _dataLists = new PointPairList[ChannelCount];
            _curves = new LineItem[ChannelCount];
            _yValues = new double[ChannelCount][];

            for (int i = 0; i < ChannelCount; i++)
            {
                // 为每个导联创建数据点列表和曲线对象,并添加到图表中
                _dataLists[i] = new PointPairList();
                _curves[i] = myPane.AddCurve("", _dataLists[i], GetColor(i), SymbolType.None);
                _yValues[i] = new double[_maxPoints];
            }

            // 移除图例
            myPane.Legend.IsVisible = false;

            // 设置X轴和Y轴的范围
            myPane.XAxis.Scale.Min = 0;
            myPane.XAxis.Scale.Max = _maxPoints * _timeIncrement;
            myPane.YAxis.Scale.Min = -800;
            myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);

            // 显示网格线
            myPane.XAxis.MajorGrid.IsVisible = true;
            myPane.YAxis.MajorGrid.IsVisible = true;

            // 隐藏Y=0的实线
            myPane.YAxis.MajorGrid.IsZeroLine = false;

            // 自定义Y轴刻度标注
            Scale yScale = myPane.YAxis.Scale;
            yScale.MajorStep = VoltageOffset;
            yScale.MinorStep = VoltageOffset;
            yScale.MajorStepAuto = false;
            yScale.MinorStepAuto = false;
            myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private string FormatYScale(GraphPane pane, Axis axis, double val, int index)
        {
            // 自定义Y轴刻度标注
            int leadIndex = (int)Math.Round(val / VoltageOffset);
            if (leadIndex >= 0 && leadIndex < ChannelCount)
            {
                return $"Lead {leadIndex + 1}";
            }
            return "";
        }

        private System.Drawing.Color GetColor(int index)
        {
            // 定义一组颜色用于不同导联的曲线
            System.Drawing.Color[] colors = {
                System.Drawing.Color.Black,
                System.Drawing.Color.Red,
                System.Drawing.Color.Blue,
                System.Drawing.Color.Green,
                System.Drawing.Color.Purple,
                System.Drawing.Color.Orange,
                System.Drawing.Color.Brown,
                System.Drawing.Color.Magenta
            };
            // 根据索引返回颜色
            return colors[index % colors.Length];
        }

        private void StartTimer()
        {
            // 创建并配置定时器
            _timer = new System.Timers.Timer(10); // 100毫秒的更新频率
            _timer.Elapsed += OnTimedEvent; // 绑定定时器事件
            _timer.AutoReset = true; // 自动重置
            _timer.Enabled = true; // 启用定时器
        }

        private void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            // 为每个导联生成模拟ECG信号数据并更新曲线
            for (int i = 0; i < ChannelCount; i++)
            {
                double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据
                _yValues[i][_currentIndex] = voltage;

                if (_dataLists[i].Count < _maxPoints)
                {
                    // 添加新的数据点
                    _dataLists[i].Add(_currentTime, voltage);
                }
                else
                {
                    // 更新现有数据点
                    _dataLists[i][_currentIndex].Y = voltage;
                }
            }

            // 更新时间和当前索引
            _currentTime += _timeIncrement;
            _currentIndex = (_currentIndex + 1) % _maxPoints;

            // 更新X轴刻度显示
            zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GenerateTimeLabels(_currentTime, _timeIncrement, _maxPoints);

            // 使图表无效以触发重绘
            zedGraphControl1.Invalidate();
        }

        private string[] GenerateTimeLabels(double currentTime, double increment, int maxPoints)
        {
            string[] labels = new string[maxPoints];
            double startTime = currentTime - (maxPoints * increment);
            for (int i = 0; i < maxPoints; i++)
            {
                labels[i] = (startTime + i * increment).ToString("0.0");
            }
            return labels;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 窗体加载事件处理方法(目前为空)
        }
    }
}

在这里插入图片描述

添加了时间刻度跟随时间扫描变化

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;

namespace ECGPlot
{
    public partial class Form1 : Form
    {
        // 定义导联数量和每个导联的电压偏移量
        private const int ChannelCount = 8;
        private const int VoltageOffset = 700; // 每个导联的偏移量

        // 用于存储每个导联的数据点列表和曲线对象
        private PointPairList[] _dataLists;
        private LineItem[] _curves;

        // 定时器用于模拟ECG信号数据更新
        private System.Timers.Timer _timer;
        private int _currentIndex = 0;
        private int _maxPoints = 800;
        private double[][] _yValues;
        private double _timeIncrement = 0.1;
        private double _currentTime = 0;
        private Random _random = new Random();
        private DateTime[] _timeLabels;

        public Form1()
        {
            InitializeComponent();
            InitializeGraph(); // 初始化图表
            StartTimer(); // 启动定时器
        }

        private void InitializeGraph()
        {
            // 获取图表区域对象
            GraphPane myPane = zedGraphControl1.GraphPane;

            // 设置图表标题和轴标题
            myPane.Title.Text = "ECG Data";
            myPane.XAxis.Title.Text = "Time";
            myPane.YAxis.Title.Text = "Voltage";

            // 初始化数据点列表和曲线数组
            _dataLists = new PointPairList[ChannelCount];
            _curves = new LineItem[ChannelCount];
            _yValues = new double[ChannelCount][];
            _timeLabels = new DateTime[_maxPoints];

            for (int i = 0; i < ChannelCount; i++)
            {
                // 为每个导联创建数据点列表和曲线对象,并添加到图表中
                _dataLists[i] = new PointPairList();
                _curves[i] = myPane.AddCurve("", _dataLists[i], GetColor(i), SymbolType.None);
                _yValues[i] = new double[_maxPoints];
            }

            // 移除图例
            myPane.Legend.IsVisible = false;

            // 设置X轴和Y轴的范围
            myPane.XAxis.Scale.Min = 0;
            myPane.XAxis.Scale.Max = _maxPoints;
            myPane.YAxis.Scale.Min = -800;
            myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);

            // 显示网格线
            myPane.XAxis.MajorGrid.IsVisible = true;
            myPane.YAxis.MajorGrid.IsVisible = true;

            // 隐藏Y=0的实线
            myPane.YAxis.MajorGrid.IsZeroLine = false;

            // 自定义Y轴刻度标注
            Scale yScale = myPane.YAxis.Scale;
            yScale.MajorStep = VoltageOffset;
            yScale.MinorStep = VoltageOffset;
            yScale.MajorStepAuto = false;
            yScale.MinorStepAuto = false;
            myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);

            // 设置X轴为文本类型
            myPane.XAxis.Type = AxisType.Text;

            // 设置X轴刻度字体大小
            myPane.XAxis.Scale.FontSpec.Size = 7; // 可以根据需要调整字体大小
            myPane.XAxis.Scale.FontSpec.FontColor = System.Drawing.Color.Black;
            // 初始化时间标签
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < _maxPoints; i++)
            {
                _timeLabels[i] = startTime;
            }

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private string FormatYScale(GraphPane pane, Axis axis, double val, int index)
        {
            // 自定义Y轴刻度标注
            int leadIndex = (int)Math.Round(val / VoltageOffset);
            if (leadIndex >= 0 && leadIndex < ChannelCount)
            {
                return $"Lead {leadIndex + 1}";
            }
            return "";
        }

        private System.Drawing.Color GetColor(int index)
        {
            // 定义一组颜色用于不同导联的曲线
            System.Drawing.Color[] colors = {
                //System.Drawing.Color.Black,
                //System.Drawing.Color.Red,
                //System.Drawing.Color.Blue,
                //System.Drawing.Color.Green,
                //System.Drawing.Color.Purple,
                //System.Drawing.Color.Orange,
                //System.Drawing.Color.Brown,
                //System.Drawing.Color.Magenta

                 System.Drawing.Color.Black,
                System.Drawing.Color.Black,
                System.Drawing.Color.Black,
                System.Drawing.Color.Black,
                System.Drawing.Color.Black,
                System.Drawing.Color.Black,
                System.Drawing.Color.Black,
                System.Drawing.Color.Black
            };
            // 根据索引返回颜色
            return colors[index % colors.Length];
        }

        private void StartTimer()
        {
            // 创建并配置定时器
            _timer = new System.Timers.Timer(1); // 100毫秒的更新频率
            _timer.Elapsed += OnTimedEvent; // 绑定定时器事件
            _timer.AutoReset = true; // 自动重置
            _timer.Enabled = true; // 启用定时器
        }

        private void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            // 记录当前时间
            DateTime currentTime = DateTime.Now;

            // 为每个导联生成模拟ECG信号数据并更新曲线
            for (int i = 0; i < ChannelCount; i++)
            {
                double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据
                _yValues[i][_currentIndex] = voltage;

                if (_dataLists[i].Count < _maxPoints)
                {
                    // 添加新的数据点
                    _dataLists[i].Add(_currentIndex, voltage);
                }
                else
                {
                    // 更新现有数据点
                    _dataLists[i][_currentIndex].Y = voltage;
                }
            }

            // 更新时间标签
            _timeLabels[_currentIndex] = currentTime;

            // 更新时间和当前索引
            _currentTime += _timeIncrement;
            _currentIndex = (_currentIndex + 1) % _maxPoints;

            // 更新X轴刻度显示
            zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GenerateTimeLabels();

            // 使图表无效以触发重绘
            zedGraphControl1.Invalidate();
        }

        private string[] GenerateTimeLabels()
        {
            string[] labels = new string[_maxPoints];
            for (int i = 0; i < _maxPoints; i++)
            {
                labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");
            }
            return labels;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 窗体加载事件处理方法(目前为空)
        }
    }
}

在这里插入图片描述

添加了个竖线,扫描分界线

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;

namespace ECGPlot
{
    public partial class Form1 : Form
    {
        // 定义导联数量和每个导联的电压偏移量
        private const int ChannelCount = 8;
        private const int VoltageOffset = 500; // 每个导联的偏移量

        // 用于存储每个导联的数据点列表和曲线对象
        private PointPairList[] _dataLists;
        private LineItem[] _curves;

        // 定时器用于模拟ECG信号数据更新
        private System.Timers.Timer _timer;
        private int _currentIndex = 0;
        private int _maxPoints = 500;
        private double[][] _yValues;
        private double _timeIncrement = 0.1;
        private double _currentTime = 0;
        private Random _random = new Random();
        private DateTime[] _timeLabels;
        private LineObj _scanLine;

        public Form1()
        {
            InitializeComponent();
            InitializeGraph(); // 初始化图表
            StartTimer(); // 启动定时器
        }

        private void InitializeGraph()
        {
            // 获取图表区域对象
            GraphPane myPane = zedGraphControl1.GraphPane;

            // 设置图表标题和轴标题
            myPane.Title.Text = "ECG Data";
            myPane.XAxis.Title.Text = "Time (hh:mm:ss.fff)";
            myPane.YAxis.Title.Text = "Voltage";

            // 初始化数据点列表和曲线数组
            _dataLists = new PointPairList[ChannelCount];
            _curves = new LineItem[ChannelCount];
            _yValues = new double[ChannelCount][];
            _timeLabels = new DateTime[_maxPoints];

            for (int i = 0; i < ChannelCount; i++)
            {
                // 为每个导联创建数据点列表和曲线对象,并添加到图表中
                _dataLists[i] = new PointPairList();
                _curves[i] = myPane.AddCurve("", _dataLists[i], GetColor(i), SymbolType.None);
                _yValues[i] = new double[_maxPoints];
            }

            // 移除图例
            myPane.Legend.IsVisible = false;

            // 设置X轴和Y轴的范围
            myPane.XAxis.Scale.Min = 0;
            myPane.XAxis.Scale.Max = _maxPoints;
            myPane.YAxis.Scale.Min = -800;
            myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);

            // 显示网格线
            myPane.XAxis.MajorGrid.IsVisible = true;
            myPane.YAxis.MajorGrid.IsVisible = true;

            // 隐藏Y=0的实线
            myPane.YAxis.MajorGrid.IsZeroLine = false;

            // 自定义Y轴刻度标注
            Scale yScale = myPane.YAxis.Scale;
            yScale.MajorStep = VoltageOffset;
            yScale.MinorStep = VoltageOffset;
            yScale.MajorStepAuto = false;
            yScale.MinorStepAuto = false;
            myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);

            // 设置X轴为文本类型
            myPane.XAxis.Type = AxisType.Text;

            // 设置X轴刻度字体大小
            myPane.XAxis.Scale.FontSpec.Size = 10; // 可以根据需要调整字体大小

            // 初始化时间标签
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < _maxPoints; i++)
            {
                _timeLabels[i] = startTime;
            }

            // 初始化扫描竖线
            _scanLine = new LineObj(System.Drawing.Color.Black, 0, -800, 0, 800 + VoltageOffset * (ChannelCount - 1));
            _scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
            _scanLine.IsClippedToChartRect = true;
            myPane.GraphObjList.Add(_scanLine);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private string FormatYScale(GraphPane pane, Axis axis, double val, int index)
        {
            // 自定义Y轴刻度标注
            int leadIndex = (int)Math.Round(val / VoltageOffset);
            if (leadIndex >= 0 && leadIndex < ChannelCount)
            {
                return $"Lead {leadIndex + 1}";
            }
            return "";
        }

        private System.Drawing.Color GetColor(int index)
        {
            // 定义一组颜色用于不同导联的曲线
            System.Drawing.Color[] colors = {
                System.Drawing.Color.Gray,
                System.Drawing.Color.Gray,
                System.Drawing.Color.Gray,
                System.Drawing.Color.Gray,
                System.Drawing.Color.Gray,
                System.Drawing.Color.Gray,
                System.Drawing.Color.Gray,
                System.Drawing.Color.Gray,
            };
            // 根据索引返回颜色
            return colors[index % colors.Length];
        }

        private void StartTimer()
        {
            // 创建并配置定时器
            _timer = new System.Timers.Timer(50); // 100毫秒的更新频率
            _timer.Elapsed += OnTimedEvent; // 绑定定时器事件
            _timer.AutoReset = true; // 自动重置
            _timer.Enabled = true; // 启用定时器
        }

        private void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            // 记录当前时间
            DateTime currentTime = DateTime.Now;

            // 为每个导联生成模拟ECG信号数据并更新曲线
            for (int i = 0; i < ChannelCount; i++)
            {
                double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据
                _yValues[i][_currentIndex] = voltage;

                if (_dataLists[i].Count < _maxPoints)
                {
                    // 添加新的数据点
                    _dataLists[i].Add(_currentIndex, voltage);
                }
                else
                {
                    // 更新现有数据点
                    _dataLists[i][_currentIndex].Y = voltage;
                }
            }

            // 更新时间标签
            _timeLabels[_currentIndex] = currentTime;

            // 更新时间和当前索引
            _currentTime += _timeIncrement;
            _currentIndex = (_currentIndex + 1) % _maxPoints;

            // 更新X轴刻度显示
            zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GenerateTimeLabels();

            // 更新扫描竖线位置
            UpdateScanLine();

            // 使图表无效以触发重绘
            zedGraphControl1.Invalidate();
        }

        private string[] GenerateTimeLabels()
        {
            string[] labels = new string[_maxPoints];
            for (int i = 0; i < _maxPoints; i++)
            {
                labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");
            }
            return labels;
        }

        private void UpdateScanLine()
        {
            // 移除旧的扫描竖线
            zedGraphControl1.GraphPane.GraphObjList.Remove(_scanLine);

            // 添加新的扫描竖线
            _scanLine = new LineObj(System.Drawing.Color.Black, _currentIndex, -800, _currentIndex, 800 + VoltageOffset * (ChannelCount - 1));
            _scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
            _scanLine.IsClippedToChartRect = true;
            zedGraphControl1.GraphPane.GraphObjList.Add(_scanLine);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 窗体加载事件处理方法(目前为空)
        }
    }
}

在这里插入图片描述

我修改了扫描线左边和右边是不同颜色

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;

namespace ECGPlot
{
    public partial class Form1 : Form
    {
        // 定义导联数量和每个导联的电压偏移量
        private const int ChannelCount = 8;
        private const int VoltageOffset = 500; // 每个导联的偏移量

        // 用于存储每个导联的数据点列表和曲线对象
        private PointPairList[] _dataListsLeft;
        private PointPairList[] _dataListsRight;
        private LineItem[] _curvesLeft;
        private LineItem[] _curvesRight;

        // 定时器用于模拟ECG信号数据更新
        private System.Timers.Timer _timer;
        private int _currentIndex = 0;
        private int _maxPoints = 500;
        private double[][] _yValues;
        private double _timeIncrement = 0.1;
        private double _currentTime = 0;
        private Random _random = new Random();
        private DateTime[] _timeLabels;
        private LineObj _scanLine;

        public Form1()
        {
            InitializeComponent();
            InitializeGraph(); // 初始化图表
            StartTimer(); // 启动定时器
        }

        private void InitializeGraph()
        {
            // 获取图表区域对象
            GraphPane myPane = zedGraphControl1.GraphPane;

            // 设置图表标题和轴标题
            myPane.Title.Text = "ECG Data";
            myPane.XAxis.Title.Text = "Time";
            myPane.YAxis.Title.Text = "Voltage";

            // 初始化数据点列表和曲线数组
            _dataListsLeft = new PointPairList[ChannelCount];
            _dataListsRight = new PointPairList[ChannelCount];
            _curvesLeft = new LineItem[ChannelCount];
            _curvesRight = new LineItem[ChannelCount];
            _yValues = new double[ChannelCount][];
            _timeLabels = new DateTime[_maxPoints];

            for (int i = 0; i < ChannelCount; i++)
            {
                // 为每个导联创建数据点列表和曲线对象,并添加到图表中
                _dataListsLeft[i] = new PointPairList();
                _dataListsRight[i] = new PointPairList();
                _curvesLeft[i] = myPane.AddCurve("", _dataListsLeft[i], System.Drawing.Color.Black, SymbolType.None);
                _curvesRight[i] = myPane.AddCurve("", _dataListsRight[i], System.Drawing.ColorTranslator.FromHtml("#CCCCCC"), SymbolType.None);
                _yValues[i] = new double[_maxPoints];

                // 初始化右边灰色波形
                for (int j = 0; j < _maxPoints; j++)
                {
                    _dataListsRight[i].Add(j, double.NaN); // 初始化为NaN,表示没有数据
                }
            }

            // 移除图例
            myPane.Legend.IsVisible = false;

            // 设置X轴和Y轴的范围
            myPane.XAxis.Scale.Min = 0;
            myPane.XAxis.Scale.Max = _maxPoints;
            myPane.YAxis.Scale.Min = -800;
            myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);

            // 显示网格线
            myPane.XAxis.MajorGrid.IsVisible = true;
            myPane.YAxis.MajorGrid.IsVisible = true;

            // 隐藏Y=0的实线
            myPane.YAxis.MajorGrid.IsZeroLine = false;

            // 自定义Y轴刻度标注
            Scale yScale = myPane.YAxis.Scale;
            yScale.MajorStep = VoltageOffset;
            yScale.MinorStep = VoltageOffset;
            yScale.MajorStepAuto = false;
            yScale.MinorStepAuto = false;
            myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);

            // 设置X轴为文本类型
            myPane.XAxis.Type = AxisType.Text;

            // 设置X轴刻度字体大小
            myPane.XAxis.Scale.FontSpec.Size = 4; // 可以根据需要调整字体大小

            // 初始化时间标签
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < _maxPoints; i++)
            {
                _timeLabels[i] = startTime;
            }

            // 初始化扫描竖线
            _scanLine = new LineObj(System.Drawing.Color.Black, 0, -800, 0, 800 + VoltageOffset * (ChannelCount - 1));
            _scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
            _scanLine.IsClippedToChartRect = true;
            myPane.GraphObjList.Add(_scanLine);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private string FormatYScale(GraphPane pane, Axis axis, double val, int index)
        {
            // 自定义Y轴刻度标注
            int leadIndex = (int)Math.Round(val / VoltageOffset);
            if (leadIndex >= 0 && leadIndex < ChannelCount)
            {
                return $"Lead {leadIndex + 1}";
            }
            return "";
        }

        private void StartTimer()
        {
            // 创建并配置定时器
            _timer = new System.Timers.Timer(50); // 50毫秒的更新频率
            _timer.Elapsed += OnTimedEvent; // 绑定定时器事件
            _timer.AutoReset = true; // 自动重置
            _timer.Enabled = true; // 启用定时器
        }

        private void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            // 记录当前时间
            DateTime currentTime = DateTime.Now;

            // 为每个导联生成模拟ECG信号数据并更新曲线
            for (int i = 0; i < ChannelCount; i++)
            {
                double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据
                _yValues[i][_currentIndex] = voltage;

                if (_dataListsLeft[i].Count < _maxPoints)
                {
                    // 添加新的数据点
                    _dataListsLeft[i].Add(_currentIndex, voltage);
                }
                else
                {
                    // 更新现有数据点
                    _dataListsLeft[i][_currentIndex].Y = voltage;
                }

                // 更新右边灰色波形数据点
                _dataListsRight[i][_currentIndex].Y = voltage;
            }

            // 更新时间标签
            _timeLabels[_currentIndex] = currentTime;

            // 更新时间和当前索引
            _currentTime += _timeIncrement;
            _currentIndex = (_currentIndex + 1) % _maxPoints;

            // 更新曲线数据
            UpdateCurves();

            // 更新X轴刻度显示
            zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GenerateTimeLabels();

            // 更新扫描竖线位置
            UpdateScanLine();

            // 使图表无效以触发重绘
            zedGraphControl1.Invalidate();
        }

        private void UpdateCurves()
        {
            for (int i = 0; i < ChannelCount; i++)
            {
                // 更新左边(黑色)部分
                PointPairList leftPoints = _curvesLeft[i].Points as PointPairList;
                leftPoints.Clear();
                for (int j = 0; j < _maxPoints; j++)
                {
                    if (j <= _currentIndex)
                    {
                        leftPoints.Add(j, _yValues[i][j]);
                    }
                    else
                    {
                        leftPoints.Add(j, double.NaN);
                    }
                }
            }
        }

        private string[] GenerateTimeLabels()
        {
            string[] labels = new string[_maxPoints];
            for (int i = 0; i < _maxPoints; i++)
            {
                labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");
            }
            return labels;
        }

        private void UpdateScanLine()
        {
            // 移除旧的扫描竖线
            zedGraphControl1.GraphPane.GraphObjList.Remove(_scanLine);

            // 添加新的扫描竖线
            _scanLine = new LineObj(System.Drawing.Color.Black, _currentIndex, -800, _currentIndex, 800 + VoltageOffset * (ChannelCount - 1));
            _scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
            _scanLine.IsClippedToChartRect = true;
            zedGraphControl1.GraphPane.GraphObjList.Add(_scanLine);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 窗体加载事件处理方法(目前为空)
        }
    }
}

在这里插入图片描述

第一次扫描时间刻度的设置

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Timers;
using System.Drawing;
using System.IO;

namespace ECGPlot
{
    public partial class Form1 : Form
    {
        // 定义导联数量和每个导联的电压偏移量
        private const int ChannelCount = 8;
        private const int VoltageOffset = 500; // 每个导联的偏移量

        // 用于存储每个导联的数据点列表和曲线对象
        private PointPairList[] _dataListsLeft;
        private PointPairList[] _dataListsRight;
        private LineItem[] _curvesLeft;
        private LineItem[] _curvesRight;

        // 定时器用于模拟ECG信号数据更新
        private System.Timers.Timer _timer;
        private int _currentIndex = 0;
        private int _maxPoints = 1000;
        private double[][] _yValues;
        private double _timeIncrement = 0.1;
        private double _currentTime = 0;
        private Random _random = new Random();
        private DateTime[] _timeLabels;
        private LineObj _scanLine;
        private bool _firstScan = true;

        public Form1()
        {
            InitializeComponent();
            InitializeGraph(); // 初始化图表
            InitializeDataFile(); // 初始化数据文件
            StartTimer(); // 启动定时器
        }

        private void InitializeGraph()
        {
            // 获取图表区域对象
            GraphPane myPane = zedGraphControl1.GraphPane;

            // 设置图表标题和轴标题
            myPane.Title.Text = "ECG Data";
            myPane.XAxis.Title.Text = "Time";
            myPane.YAxis.Title.Text = "Voltage";

            // 初始化数据点列表和曲线数组
            _dataListsLeft = new PointPairList[ChannelCount];
            _dataListsRight = new PointPairList[ChannelCount];
            _curvesLeft = new LineItem[ChannelCount];
            _curvesRight = new LineItem[ChannelCount];
            _yValues = new double[ChannelCount][];
            _timeLabels = new DateTime[_maxPoints];

            for (int i = 0; i < ChannelCount; i++)
            {
                // 为每个导联创建数据点列表和曲线对象,并添加到图表中
                _dataListsLeft[i] = new PointPairList();
                _dataListsRight[i] = new PointPairList();
                _curvesLeft[i] = myPane.AddCurve("", _dataListsLeft[i], System.Drawing.Color.Black, SymbolType.None);
                _curvesRight[i] = myPane.AddCurve("", _dataListsRight[i], Color.FromArgb(100, Color.Black), SymbolType.None);
                _yValues[i] = new double[_maxPoints];

                // 初始化右边灰色波形
                for (int j = 0; j < _maxPoints; j++)
                {
                    _dataListsRight[i].Add(j, double.NaN); // 初始化为NaN,表示没有数据
                }
            }

            // 移除图例
            myPane.Legend.IsVisible = false;

            // 设置X轴和Y轴的范围
            myPane.XAxis.Scale.Min = 0;
            myPane.XAxis.Scale.Max = _maxPoints;
            myPane.YAxis.Scale.Min = -800;
            myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);

            // 网格线
            myPane.XAxis.MajorGrid.IsVisible = false; // 关闭纵向主要网格线
            myPane.XAxis.MinorGrid.IsVisible = false; // 关闭纵向次要网格线
            myPane.YAxis.MajorGrid.IsVisible = true;

            // 隐藏Y=0的实线
            myPane.YAxis.MajorGrid.IsZeroLine = false;

            // 自定义Y轴刻度标注
            Scale yScale = myPane.YAxis.Scale;
            yScale.MajorStep = VoltageOffset;
            yScale.MinorStep = VoltageOffset;
            yScale.MajorStepAuto = false;
            yScale.MinorStepAuto = false;
            myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);
            myPane.YAxis.Scale.FontSpec.Size = 8;
            myPane.YAxis.Scale.FontSpec.Family = "Times New Roman"; // 设置字体为

            // 设置X轴为文本类型
            myPane.XAxis.Type = AxisType.Text;

            // 设置X轴刻度字体大小
            myPane.XAxis.Scale.FontSpec.Size = 5; // 可以根据需要调整字体大小
            myPane.XAxis.Scale.FontSpec.Family = "Times New Roman"; // 设置字体为

            // 初始化时间标签
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < _maxPoints; i++)
            {
                _timeLabels[i] = startTime;
            }

            // 初始化扫描竖线
            _scanLine = new LineObj(System.Drawing.Color.Black, 0, -800, 0, 800 + VoltageOffset * (ChannelCount - 1));
            _scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
            _scanLine.IsClippedToChartRect = true;
            myPane.GraphObjList.Add(_scanLine);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private string FormatYScale(GraphPane pane, Axis axis, double val, int index)
        {
            // 自定义Y轴刻度标注
            int leadIndex = (int)Math.Round(val / VoltageOffset);
            if (leadIndex >= 0 && leadIndex < ChannelCount)
            {
                return $"Lead {leadIndex + 1}";
            }
            return "";
        }

        private void StartTimer()
        {
            // 创建并配置定时器
            _timer = new System.Timers.Timer(50); // 50毫秒的更新频率
            _timer.Elapsed += OnTimedEvent; // 绑定定时器事件
            _timer.AutoReset = true; // 自动重置
            _timer.Enabled = true; // 启用定时器
        }

        private void OnTimedEvent(Object source, ElapsedEventArgs e)
        {
            // 记录当前时间
            DateTime currentTime = DateTime.Now;

            // 为每个导联生成模拟ECG信号数据并更新曲线
            for (int i = 0; i < ChannelCount; i++)
            {
                double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据
                _yValues[i][_currentIndex] = voltage;

                if (_dataListsLeft[i].Count < _maxPoints)
                {
                    // 添加新的数据点
                    _dataListsLeft[i].Add(_currentIndex, voltage);
                }
                else
                {
                    // 更新现有数据点
                    _dataListsLeft[i][_currentIndex].Y = voltage;
                }

                // 更新右边灰色波形数据点
                _dataListsRight[i][_currentIndex].Y = voltage;
            }

            // 更新时间标签
            _timeLabels[_currentIndex] = currentTime;

            // 实时保存数据到文件
            SaveCurrentDataToFile(_currentIndex, currentTime);

            // 更新时间和当前索引
            _currentTime += _timeIncrement;
            _currentIndex = (_currentIndex + 1) % _maxPoints;

            // 更新曲线数据
            UpdateCurves();

            // 更新X轴刻度显示
            zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GeneratePartialTimeLabels();

            // 更新扫描竖线位置
            UpdateScanLine();

            // 使图表无效以触发重绘
            zedGraphControl1.Invalidate();
        }

        private void UpdateCurves()
        {
            for (int i = 0; i < ChannelCount; i++)
            {
                // 更新左边(黑色)部分
                PointPairList leftPoints = _curvesLeft[i].Points as PointPairList;
                leftPoints.Clear();
                for (int j = 0; j < _maxPoints; j++)
                {
                    if (j <= _currentIndex)
                    {
                        leftPoints.Add(j, _yValues[i][j]);
                    }
                    else
                    {
                        leftPoints.Add(j, double.NaN);
                    }
                }
            }
        }

        private string[] GeneratePartialTimeLabels()
        {
            string[] labels = new string[_maxPoints];
            for (int i = 0; i < _maxPoints; i++)
            {
                if (i <= _currentIndex || !_firstScan)
                {
                    labels[i] = _timeLabels[i].ToString("HH:mm:ss.fff");
                }
                else
                {
                    labels[i] = ""; // 未扫描到的位置使用空字符串
                }
            }

            // 仅在第一次扫描后将_firstScan设为false
            if (_currentIndex == _maxPoints - 1)
            {
                _firstScan = false;
            }

            return labels;
        }

        private void UpdateScanLine()
        {
            // 移除旧的扫描竖线
            zedGraphControl1.GraphPane.GraphObjList.Remove(_scanLine);

            // 添加新的扫描竖线
            _scanLine = new LineObj(System.Drawing.Color.Black, _currentIndex, -800, _currentIndex, 800 + VoltageOffset * (ChannelCount - 1));
            _scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Solid;
            _scanLine.IsClippedToChartRect = true;
            zedGraphControl1.GraphPane.GraphObjList.Add(_scanLine);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private void SaveCurrentDataToFile(int index, DateTime time)
        {
            string filePath = "ECGData.txt"; // 可以修改为需要保存的文件路径

            try
            {
                using (StreamWriter writer = new StreamWriter(filePath, true)) // 以追加模式打开文件
                {
                    // 写入索引
                    writer.Write($"{index},");

                    // 写入8个导联的数据
                    for (int j = 0; j < ChannelCount; j++)
                    {
                        writer.Write($"{_yValues[j][index]},");
                    }

                    // 写入时间
                    writer.WriteLine(time.ToString("HH:mm:ss.fff"));
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error saving data: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void InitializeDataFile()
        {
            string filePath = "ECGData.txt"; // 可以修改为需要保存的文件路径

            try
            {
                using (StreamWriter writer = new StreamWriter(filePath, false)) // 以覆盖模式打开文件
                {
                    // 写入表头
                    writer.WriteLine("Index,Lead1,Lead2,Lead3,Lead4,Lead5,Lead6,Lead7,Lead8,Time");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error initializing data file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void btnSaveData_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog saveFileDialog = new SaveFileDialog())
            {
                saveFileDialog.Filter = "Text files (*.txt)|*.txt";
                saveFileDialog.Title = "Save ECG Data";
                if (saveFileDialog.ShowDialog() == DialogResult.OK)
                {
                    SaveDataToFile(saveFileDialog.FileName);
                }
            }
        }

        private void SaveDataToFile(string filePath)
        {
            try
            {
                using (StreamWriter writer = new StreamWriter(filePath))
                {
                    // 写入表头
                    writer.WriteLine("Index,Lead1,Lead2,Lead3,Lead4,Lead5,Lead6,Lead7,Lead8,Time");

                    for (int i = 0; i < _maxPoints; i++)
                    {
                        // 写入索引
                        writer.Write($"{i},");

                        // 写入8个导联的数据
                        for (int j = 0; j < ChannelCount; j++)
                        {
                            writer.Write($"{_yValues[j][i]},");
                        }

                        // 写入时间
                        writer.WriteLine(_timeLabels[i].ToString("HH:mm:ss.fff"));
                    }
                }

                MessageBox.Show("Data saved successfully.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error saving data: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 窗体加载事件处理方法(目前为空)
        }
    }
}

在这里插入图片描述

提高响应速度

在Windows窗体应用程序中使用System.Timers.Timer,其回调在不同于UI线程的线程上运行。如果你要更新UI控件(如ZedGraphControl),最好使用System.Windows.Forms.Timer,因为它在UI线程上执行。下面是你如何改进代码以使用System.Windows.Forms.Timer来避免线程间的调度问题并提高响应速度。
此外,确保图表在更新时不会频繁刷新,这样可以减少卡顿。

修改后的代码

using System;
using System.Windows.Forms;
using ZedGraph;
using System.Drawing;
using System.IO;

namespace ECGPlot
{
    public partial class Form1 : Form
    {
        // 定义导联数量和每个导联的电压偏移量
        private const int ChannelCount = 8;
        private const int VoltageOffset = 500; // 每个导联的偏移量

        // 用于存储每个导联的数据点列表和曲线对象
        private PointPairList[] _dataListsLeft;
        private PointPairList[] _dataListsRight;
        private LineItem[] _curvesLeft;
        private LineItem[] _curvesRight;

        // 定时器用于模拟ECG信号数据更新
        private System.Windows.Forms.Timer _timer;
        private int _currentIndex = 0;
        private int _maxPoints = 1101;
        private double[][] _yValues;
        private double _timeIncrement = 0.1;
        private double _currentTime = 0;
        private Random _random = new Random();
        private DateTime[] _timeLabels;
        private LineObj _scanLine;
        private bool _firstScan = true;

        public Form1()
        {
            InitializeComponent();
            InitializeGraph(); // 初始化图表
            InitializeDataFile(); // 初始化数据文件
            StartTimer(); // 启动定时器
        }

        private void InitializeGraph()
        {
            // 获取图表区域对象
            GraphPane myPane = zedGraphControl1.GraphPane;

            // 设置图表标题和轴标题
            myPane.Title.Text = "ECG Data";
            myPane.XAxis.Title.Text = "Time";
            myPane.YAxis.Title.Text = "Voltage";

            // 初始化数据点列表和曲线数组
            _dataListsLeft = new PointPairList[ChannelCount];
            _dataListsRight = new PointPairList[ChannelCount];
            _curvesLeft = new LineItem[ChannelCount];
            _curvesRight = new LineItem[ChannelCount];
            _yValues = new double[ChannelCount][];
            _timeLabels = new DateTime[_maxPoints];

            for (int i = 0; i < ChannelCount; i++)
            {
                // 为每个导联创建数据点列表和曲线对象,并添加到图表中
                _dataListsLeft[i] = new PointPairList();
                _dataListsRight[i] = new PointPairList();
                _curvesLeft[i] = myPane.AddCurve("", _dataListsLeft[i], System.Drawing.Color.Black, SymbolType.None);
                _curvesRight[i] = myPane.AddCurve("", _dataListsRight[i], Color.FromArgb(100, Color.Black), SymbolType.None);
                _yValues[i] = new double[_maxPoints];

                // 初始化右边灰色波形
                for (int j = 0; j < _maxPoints; j++)
                {
                    _dataListsRight[i].Add(j, double.NaN); // 初始化为NaN,表示没有数据
                }
            }

            // 移除图例
            myPane.Legend.IsVisible = false;

            // 设置X轴和Y轴的范围
            myPane.XAxis.Scale.Min = 0;
            myPane.XAxis.Scale.Max = _maxPoints;
            myPane.YAxis.Scale.Min = -800;
            myPane.YAxis.Scale.Max = 800 + VoltageOffset * (ChannelCount - 1);

            // 设置X轴主刻度步长,使刻度标签不那么密集
            myPane.XAxis.Scale.MajorStep = 100; // 可以根据需要调整这个值

            // 网格线
            myPane.XAxis.MajorGrid.IsVisible = false; // 关闭纵向主要网格线
            myPane.XAxis.MinorGrid.IsVisible = false; // 关闭纵向次要网格线
            myPane.YAxis.MajorGrid.IsVisible = true;

            // 隐藏Y=0的实线
            myPane.YAxis.MajorGrid.IsZeroLine = false;

            // 自定义Y轴刻度标注
            Scale yScale = myPane.YAxis.Scale;
            yScale.MajorStep = VoltageOffset;
            yScale.MinorStep = VoltageOffset;
            yScale.MajorStepAuto = false;
            yScale.MinorStepAuto = false;
            myPane.YAxis.ScaleFormatEvent += new Axis.ScaleFormatHandler(FormatYScale);
            myPane.YAxis.Scale.FontSpec.Size = 8;
            myPane.YAxis.Scale.FontSpec.Family = "Times New Roman"; // 设置字体为

            // 设置X轴为文本类型
            myPane.XAxis.Type = AxisType.Text;

            // 设置X轴刻度字体大小
            myPane.XAxis.Scale.FontSpec.Size = 8; // 可以根据需要调整字体大小
            myPane.XAxis.Scale.FontSpec.Family = "Times New Roman"; // 设置字体为

            // 初始化时间标签
            DateTime startTime = DateTime.Now;
            for (int i = 0; i < _maxPoints; i++)
            {
                _timeLabels[i] = startTime;
            }

            // 初始化扫描竖线
            _scanLine = new LineObj(System.Drawing.Color.Black, 0, -800, 0, 800 + VoltageOffset * (ChannelCount - 1));
            _scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Dash;
            _scanLine.IsClippedToChartRect = true;
            myPane.GraphObjList.Add(_scanLine);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }


        private string FormatYScale(GraphPane pane, Axis axis, double val, int index)
        {
            // 自定义Y轴刻度标注
            int leadIndex = (int)Math.Round(val / VoltageOffset);
            if (leadIndex >= 0 && leadIndex < ChannelCount)
            {
                return $"Lead {leadIndex + 1}";
            }
            return "";
        }

        private void StartTimer()
        {
            // 创建并配置定时器
            _timer = new System.Windows.Forms.Timer();
            _timer.Interval = 50; // 50毫秒的更新频率
            _timer.Tick += OnTimedEvent; // 绑定定时器事件
            _timer.Start(); // 启用定时器
        }

        private void OnTimedEvent(Object source, EventArgs e)
        {
            // 记录当前时间
            DateTime currentTime = DateTime.Now;

            // 为每个导联生成模拟ECG信号数据并更新曲线
            for (int i = 0; i < ChannelCount; i++)
            {
                double voltage = _random.Next(-200, 200) + i * VoltageOffset; // 生成带偏移量的电压数据
                _yValues[i][_currentIndex] = voltage;

                if (_dataListsLeft[i].Count < _maxPoints)
                {
                    // 添加新的数据点
                    _dataListsLeft[i].Add(_currentIndex, voltage);
                }
                else
                {
                    // 更新现有数据点
                    _dataListsLeft[i][_currentIndex].Y = voltage;
                }

                // 更新右边灰色波形数据点
                _dataListsRight[i][_currentIndex].Y = voltage;
            }

            // 更新时间标签
            _timeLabels[_currentIndex] = currentTime;

            // 实时保存数据到文件
            SaveCurrentDataToFile(_currentIndex, currentTime);

            // 更新时间和当前索引
            _currentTime += _timeIncrement;
            _currentIndex = (_currentIndex + 1) % _maxPoints;

            // 更新曲线数据
            UpdateCurves();

            // 更新X轴刻度显示
            zedGraphControl1.GraphPane.XAxis.Scale.TextLabels = GeneratePartialTimeLabels();

            // 更新扫描竖线位置
            UpdateScanLine();

            // 使图表无效以触发重绘
            zedGraphControl1.Invalidate();
        }

        private void UpdateCurves()
        {
            for (int i = 0; i < ChannelCount; i++)
            {
                // 更新左边(黑色)部分
                PointPairList leftPoints = _curvesLeft[i].Points as PointPairList;
                leftPoints.Clear();
                for (int j = 0; j < _maxPoints; j++)
                {
                    if (j <= _currentIndex)
                    {
                        leftPoints.Add(j, _yValues[i][j]);
                    }
                    else
                    {
                        leftPoints.Add(j, double.NaN);
                    }
                }
            }
        }

        private string[] GeneratePartialTimeLabels()
        {
            string[] labels = new string[_maxPoints];
            for (int i = 0; i < _maxPoints; i++)
            {
                if (i <= _currentIndex || !_firstScan)
                {
                    labels[i] = _timeLabels[i].ToString("HH:mm

:ss.fff");
                }
                else
                {
                    labels[i] = ""; // 未扫描到的位置使用空字符串
                }
            }

            // 仅在第一次扫描后将_firstScan设为false
            if (_currentIndex == _maxPoints - 1)
            {
                _firstScan = false;
            }

            return labels;
        }

        private void UpdateScanLine()
        {
            // 移除旧的扫描竖线
            zedGraphControl1.GraphPane.GraphObjList.Remove(_scanLine);

            // 添加新的扫描竖线
            _scanLine = new LineObj(System.Drawing.Color.Black, _currentIndex, -800, _currentIndex, 800 + VoltageOffset * (ChannelCount - 1));
            _scanLine.Line.Style = System.Drawing.Drawing2D.DashStyle.Solid;
            _scanLine.IsClippedToChartRect = true;
            zedGraphControl1.GraphPane.GraphObjList.Add(_scanLine);

            // 应用更改并刷新图表
            zedGraphControl1.AxisChange();
        }

        private void SaveCurrentDataToFile(int index, DateTime time)
        {
            string filePath = "ECGData.txt"; // 可以修改为需要保存的文件路径

            try
            {
                using (StreamWriter writer = new StreamWriter(filePath, true)) // 以追加模式打开文件
                {
                    // 写入索引
                    writer.Write($"{index},");

                    // 写入8个导联的数据
                    for (int j = 0; j < ChannelCount; j++)
                    {
                        writer.Write($"{_yValues[j][index]},");
                    }

                    // 写入时间
                    writer.WriteLine(time.ToString("HH:mm:ss.fff"));
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error saving data: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void InitializeDataFile()
        {
            string filePath = "ECGData.txt"; // 可以修改为需要保存的文件路径

            try
            {
                using (StreamWriter writer = new StreamWriter(filePath, false)) // 以覆盖模式打开文件
                {
                    // 写入表头
                    writer.WriteLine("Index,Lead1,Lead2,Lead3,Lead4,Lead5,Lead6,Lead7,Lead8,Time");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error initializing data file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void btnSaveData_Click(object sender, EventArgs e)
        {
            using (SaveFileDialog saveFileDialog = new SaveFileDialog())
            {
                saveFileDialog.Filter = "Text files (*.txt)|*.txt";
                saveFileDialog.Title = "Save ECG Data";
                if (saveFileDialog.ShowDialog() == DialogResult.OK)
                {
                    SaveDataToFile(saveFileDialog.FileName);
                }
            }
        }

        private void SaveDataToFile(string filePath)
        {
            try
            {
                using (StreamWriter writer = new StreamWriter(filePath))
                {
                    // 写入表头
                    writer.WriteLine("Index,Lead1,Lead2,Lead3,Lead4,Lead5,Lead6,Lead7,Lead8,Time");

                    for (int i = 0; i < _maxPoints; i++)
                    {
                        // 写入索引
                        writer.Write($"{i},");

                        // 写入8个导联的数据
                        for (int j = 0; j < ChannelCount; j++)
                        {
                            writer.Write($"{_yValues[j][i]},");
                        }

                        // 写入时间
                        writer.WriteLine(_timeLabels[i].ToString("HH:mm:ss.fff"));
                    }
                }

                MessageBox.Show("Data saved successfully.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error saving data: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // 窗体加载事件处理方法(目前为空)
        }
    }
}

解释

  1. 将定时器从System.Timers.Timer更改为System.Windows.Forms.Timer,使其在UI线程上执行更新操作。
  2. System.Windows.Forms.Timer的回调在UI线程上运行,消除了跨线程更新控件的问题。
  3. 调整_timer.Interval来控制刷新频率,这里设置为50毫秒。
  4. OnTimedEvent方法中直接更新UI,消除了跨线程调用的开销。
    能够改善响应速度和卡顿问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1980286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu24.04编译FFmpeg6.1(支持x264、x265、fdk-acc)

FFmpeg是一个开源的多媒体处理工具集&#xff0c;可以用于处理音频、视频和图片等多种媒体格式。由于其强大的功能和灵活性&#xff0c;FFmpeg被广泛应用在多媒体处理领域&#xff0c;包括音视频编解码、流媒体服务器、视频转码等。FFmpeg7.0 版本移除了 6.0 之前已弃用的 API&…

Java面试题--JVM大厂篇之Parallel GC与其他垃圾回收器的对比与选择

目录 引言: 正文: 结束语: 引言: Java工程师们&#xff0c;当我们在谈论JVM性能优化时&#xff0c;垃圾回收器&#xff08;GC&#xff09;无疑是一个绕不开的话题。而在所有的垃圾回收器中&#xff0c;Parallel GC无疑是一个备受关注的角色。今天&#xff0c;我们就来一探究…

浅谈wampserver安装配置的注意事项

目录 前言1. 基本知识2. 通过localhost或者ip地址访问根文件3. 更改服务器默认文件夹4. 彩蛋 前言 此处偏向思路讲解&#xff0c;详细的细节此处不提供&#xff08;安装过程忘记截图&#xff09; 主要以后期运维为主 详细安装配置过程推荐阅读&#xff1a;WampServer超级超级…

Java重修笔记 第二十九天 枚举类

枚举类 1. 定义&#xff1a;枚举属于一种特殊的类&#xff0c;里面只包含一组有限的特定的对象&#xff0c;枚举对象通常为只读&#xff0c;不能修改。枚举对象名通常为大写&#xff0c;静态不可修改。 2. 运用场景&#xff1a;需要特定对象的场景&#xff0c;例如四季只有春…

Java二十三种设计模式-桥接模式(10/23)

桥接模式&#xff1a;解耦抽象与实现的灵活设计 引言 桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;用于将抽象部分与其实现部分分离&#xff0c;使它们可以独立地变化。它是一种对象结构型模式&#xff0c;又称为柄体(Handle and Body)模…

PCL1.11.0下载安装(Windows)

PCL1.11.0下载安装&#xff08;Windows&#xff09; PCL安装需要的几个模块如下图所示&#xff1a; 一、PCL1.11.0下载 PCL以1.11.0版本为例&#xff0c;打开下载连接&#xff08;PCL下载&#xff09; 下载PCL-1.11.0-AllInOne-msvc2019-win64.exe和pcl-1.11.0-pdb-msvc2019-…

Springboot指定扫描路径

方式一&#xff1a;通过在启动类的SpringbootApplication中指定包扫描或类扫描 指定需要扫描的包 scanBasePackages{"待扫描包1","待扫描包2", . . . ," "} 指定需要扫描的类 scanBasePackageClasses{类1.class,类2.class,...} 方式二&#xff…

Linux系统编程 --- 动静态库

一、回顾&#xff0c;制作一个库 libXXX.a --- 静态链接 libYYY.so --- 动态链接 设计一个库&#xff1a; 把我们提供的方法&#xff0c;给别人用&#xff1a; 1、把源文件直接给他 2、把我们的源代码打包成库 库 头文件。 原理&#xff1a;把所有的.o文件打包成.a文件也…

(免费领源码)python#Django#MYSQL医院预约挂号系统24802-计算机毕业设计项目选题推荐

摘 要 随着互联网时代的到来&#xff0c;同时计算机网络技术高速发展&#xff0c;网络管理运用也变得越来越广泛。因此&#xff0c;建立一个基于django 医院预约挂号系统 &#xff0c;会使&#xff1b;医院预约挂号系统的管理工作系统化、规范化&#xff0c;也会提高平台形象&a…

宝塔企业版安装命令

使用方法 安装脚本: wget -O install.sh https://www.xpnal.cn/install/install_6.0.sh \&\& bash install.sh 更新脚本: curl https://www.xpnal.cn/install/update6.sh|bash 脚本说明: 没有安装过宝塔直接执行安装脚本&#xff0c;安装过执行更新脚本。 提醒 …

【Material-UI】多选模式(Multiple values)与Autocomplete的高级用法解析

文章目录 一、多选模式的基本用法二、高级用法1. 固定选项&#xff08;Fixed options&#xff09;2. 复选框&#xff08;Checkboxes&#xff09;3. 限制标签显示数量&#xff08;Limit tags&#xff09; 三、性能优化与最佳实践1. 筛选已选项&#xff08;filterSelectedOptions…

Scratch的下载与安装

如果你在某搜索引擎用这个&#xff0c;就是离被骗钱不远了 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 废话不多说&#xff0c;直接放软件安装包。重要的事情说三遍&#xff0c;收费的全是骗人的&#xff01;收费的全是骗人的&#xff01;收费的全是…

【C++题解】1015. 鸡兔同笼问题

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1015. 鸡兔同笼问题 类型&#xff1a;简单穷举 题目描述&#xff1a; 鸡兔同笼问题&#xff1a;一个笼子里面有鸡若干只&#xff0c;兔若干只。共有头 50 个&#xff0c;共有腿 16…

C语言的回调函数(详解qsort函数)

在上一篇文章中我们学习了指针与数组之间的关系&#xff0c;学习了指针数组&#xff0c;数组指针变量&#xff0c;函数指针变量和函数指针数组。那么接下来我们要学习的是一个利用函数指针变量去解决和简化各种问题的回调函数。 一、回调函数 回调函数在编程中是一种很常见的…

pip install -r requirements.txt下载某个.whl文件较慢的问题

访问PYPI官网。搜索该文件名字 如果不是自己要的版本&#xff0c;点击左边的Release history。 选择版本后再次在Download files页面下载.whl文件 下载完之后切换至自己的项目解释器虚拟环境。 python -m pip install 文件名.whl 如果你改名python.exe为python3.exe&#xff…

【消息队列】RabbitMQ 面试篇

&#x1f397;️ 主页&#xff1a;小夜时雨 &#x1f397;️专栏&#xff1a;消息队列 &#x1f397;️如何活着&#xff0c;是我找寻的方向 目录 1. RabbitMQ 有哪些工作模式&#xff1f;2. 如何保证消息不丢失&#xff08;可靠性&#xff09;3. 什么是死信队列&#xff0c;如…

基于K210智能人脸识别+车牌识别系统(完整工程资料源码)

运行效果&#xff1a; 基于K210的智能人脸与车牌识别系统工程 目录&#xff1a; 运行效果&#xff1a; 目录&#xff1a; 前言&#xff1a; 一、国内外研究现状与发展趋势 二、相关技术基础 2.1 人脸识别技术 2.2 车牌识别技术 三、智能小区门禁系统设计 3.1 系统设计方案 3.2 …

【网络编程】网络原理(一)

系列文章目录 1、 初识网络 2、网络编程的基础使用&#xff08;一&#xff09; 文章目录 系列文章目录前言一、端口号的使用二、UDP报文学习1.报文格式2.MD5算法 总结 前言 在前文中&#xff0c;主要对UDP和TCP协议有了简单的了解&#xff0c;而这两种协议是负责传输层的内容…

组件设计原则

state数据结构设计 用数据描述所有内容数据要结构化&#xff0c;易于程序操作&#xff08;遍历、查找&#xff09;数据要可扩展&#xff0c;以便增加新的功能 组件设计组件通讯 从功能上拆分层次尽量让组件原子化容器组件&#xff08;只管理数据&#xff09;& UI组件&am…

【MySQL】库操作,数据类型

目录 MySQL简介SQL语句分类库操作语句展示数据库创建数据库使用数据库删除数据库 数据类型整型浮点型字符串日期类型 MySQL简介 数据库有关系型数据库和非关系型数据库。 关系型数据库&#xff1a;是指采用了关系模型来组织数据的数据库。 简单来说&#xff0c;关系模型指的就…