本文經原作者授權以原創方式二次分享,歡迎轉載、分享。
原文作者:流浪g
原文地址:https://www.cnblogs.com/cong2312/p/16411637.html
一、前言
項目中之前涉及到胎兒心率圖曲線的繪制,最近項目中還需要添加心電曲線和血樣曲線的繪制功能。今天就來分享一下心電曲線的繪制方式;
二、正文
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)最終實現效果;

