• WPF开发随笔收录-心电图曲线绘制


    一、前言

    项目中之前涉及到胎儿心率图曲线的绘制,最近项目中还需要添加心电曲线和血样曲线的绘制功能。今天就来分享一下心电曲线的绘制方式;

    二、正文

    1、胎儿心率曲线的绘制是通过DrawingVisual来实现的,这里的心电曲线我也是采用差不多相同的方式来实现的,只是两者曲线的数据有所区别。心电图的数据服务器端每秒发送至客户端一个数据包,一个数据包钟心电的数据大概一百个左右,看过心电图的应该知道,心电图的效果是匀速绘制出来的,而不是一次性将一百个点绘制出来;项目中是通过将数据存到数据缓冲区,然后通过线程定时推送数据到绘图端,线程里会根据缓冲区现有数据量来动态控制数据的快慢;这里的例子我就直接通过定时推数据来直接演示如何实现;

    2、新建个项目,添加一个类继承FrameworkElement,然后加上对应的数据接收和绘制功能,这里直接贴出所有代码,具体细节之前写绘制高性能曲线时写过了,不清楚的可以参考之前的;(实际上绘图部分用Canvas实现也可以,用DrawingVisual其实每次推送了一个数据,整个视图都重新绘制了,我之所以用这个是因为我要支持自动缩放功能)

    复制代码
    public class EcgDrawingVisual : FrameworkElement
    {
        private readonly List<Visual> visuals = new List<Visual>();
        private DrawingVisual Layer;
    
        private Pen ecg_pen = new Pen(Brushes.Orange, 1.5);
    
        private int?[] ecg_points = new int?[2000];
    
        private int currentStart = 0;
    
        private double y_offset = 0;
    
        private int ecg_max = 60;
        private int ecg_min = -25;
    
        public EcgDrawingVisual()
        {
            ecg_pen.Freeze();
    
            Layer = new DrawingVisual();
            visuals.Add(Layer);
        }
    
        public void SetupData(int ecg)
        {
            ecg_points[currentStart] = ecg;
            for (int i = 1; i <= 20; i++)
            {
                ecg_points[currentStart + i] = null;
            }
    
            currentStart++;
            if (currentStart >= RenderSize.Width / 2)
            {
                currentStart = 0;
            }
    
            DrawEcgLine();
            InvalidateVisual();
        }
    
        private void DrawEcgLine()
        {
            var scale = RenderSize.Height / (ecg_max - ecg_min);
            y_offset = ecg_min * -scale;
    
            DrawingContext dc = Layer.RenderOpen();
            Matrix mat = new Matrix();
            mat.ScaleAt(1, -1, 0, RenderSize.Height / 2);
            dc.PushTransform(new MatrixTransform(mat));
    
            for (int i = 0, left = 0; left < RenderSize.Width; i++, left += 2)
            {
                if (ecg_points[i] == null || ecg_points[i + 1] == null) continue;
                dc.DrawLine(ecg_pen, new Point(left, ecg_points[i].Value * scale + y_offset), new Point(left + 2, ecg_points[i + 1].Value * scale + y_offset));
            }
    
            dc.Pop();
            dc.Close();
        }
    
        protected override int VisualChildrenCount => visuals.Count;
        protected override Visual GetVisualChild(int index)
        {
            return visuals[index];
        }
    
        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
        }
    
        protected override void OnRender(DrawingContext drawingContext)
        {
            drawingContext.DrawRectangle(Brushes.White, null, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
            base.OnRender(drawingContext);
        }
    }
    复制代码

    3、主界面添加这个控件,然后后台添加对应的推送数据的线程,这里我是定时每隔十毫秒推送一个数据给到绘图端。

    复制代码
    public partial class MainWindow : Window
    {
        private List<int> points = new List<int>() { 4, 4, 3, -1, -2, -2, -2, -2, -2, -2, -2, -2, -4, -3, 25, 37, 8, -7, -5, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2, -2, -1, -1, 3, 5, 8, 9, 9, 10, 9, 7, 5, 1, -1, -4, -4, -4, -4, -4, -4, -4, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -1, 1, 3 };
        private bool flag = true;
        private int currentIndex = 0;
    
        public MainWindow()
        {
            InitializeComponent();
    
            new Thread(() =>
            {
                while (flag)
                {
                    Thread.Sleep(10);
                    this.Dispatcher.BeginInvoke(new Action(() =>
                    {
                        if (currentIndex == points.Count) currentIndex = 0;
                        ecgDrawingVisual.SetupData(points[currentIndex]);
                        currentIndex++;
                    }));
                }
            }).Start();
        }
    
        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);
            flag = false;
        }
    }
    复制代码

    4、最终实现效果

     

     

  • 相关阅读:
    2022千元无线蓝牙耳机,音质超高的千元蓝牙耳机品牌
    如何在手机或平板上编写代码?
    添加docker容器数据卷
    Python提取pdf中的表格数据(附实战案例)
    python:关于函数内 * 和 / 是什么意思?
    数据库 — 增删查改
    【知识图谱论文】视觉语境对知识图谱真的有帮助吗?表征学习视角
    1.5python 文件操作_python量化实用版教程(初级)
    C语言详解系列——指针与结构体
    搜维尔科技:Varjo-探讨汽车工业使用虚拟现实/XR的可能性
  • 原文地址:https://www.cnblogs.com/cong2312/p/16411637.html