專案中之前涉及到胎兒心率圖曲線的繪製,最近專案中還需要新增心電曲線和血樣曲線的繪製功能。今天就來分享一下心電曲線的繪製方式;
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、最終實現效果