WPF 使用DrawingVisual绘制高性能曲线图

一、前言

  • 项目中涉及到了心率监测,而且数据量达到了百万级别,通过WPF实现大数据曲线图时,尝试过最基础的Canvas来实现,但是性能堪忧,而且全部画出来也不实际。同时也尝试过找第三方的开源库,但是因为曲线图涉及到很多细节功能,第三方的肯定也没法满足。没办法,只能自己实现,上网查找后发现DrawingVisual这个玩意可以实现高性能画图,同时再搭配局部显示,这样就能实现自己想要的效果。话不多说,今天把大致的实现思路写一下,就不直接把项目的源码贴出来,写个简单的Demo就好了。

二、正文

1、首先新建个项目,然后创建个自定义控件,命名CurveChartDrawingVisual,然后让它继承FrameworkElement。因为要使用DrawingVisual对象的话,需要为它创建一个主机容器。关于其他相关DrawingVisual的细节这里不做过多阐述,不明白的可以去微软官网看。

2、实现的具体代码如下,相关细节有备注标注了。这里记得要重写一下VisualChildrenCount属性和重写 GetVisualChild() 方法,不然图画不出来

public class CruveChartDrawingVisual : FrameworkElement
{private List<Visual> visuals = new List<Visual>();private DrawingVisual Layer;private double offset_x = 0;//滑动条偏移值private double y_scale;//y轴方向缩放比例private List<int> list_points;//曲线数据private static int Top_Val_Max = 100;//y轴最大值private static int Top_Val_Min = 0;//y轴最小值private static int X_Sex = 20;//x轴分度值private static int Y_Sex = 20;//y轴分度值private static int Bottom = 30;//底部x轴坐标显示高度Pen pen = new Pen(Brushes.Green, 2);Pen primarygrid_pen = new Pen(Brushes.Black, 1);Pen secondgrid_pen = new Pen(Brushes.Gray, 1);public CruveChartDrawingVisual(){pen.Freeze();//冻结笔,提高性能关键所在primarygrid_pen.Freeze();secondgrid_pen.Freeze();Layer = new DrawingVisual();visuals.Add(Layer);}public void SetupData(List<int> points){list_points = points;offset_x = 0;DrawContent();}public void OffsetX(double offset){offset_x = offset;DrawContent();InvalidateVisual();}private void DrawContent(){var dc = Layer.RenderOpen();y_scale = (RenderSize.Height - Bottom) / (Top_Val_Max - Top_Val_Min);var mat = new Matrix();mat.ScaleAt(1, -1, 0, RenderSize.Height / 2);mat.OffsetX = -offset_x;dc.PushTransform(new MatrixTransform(mat));//横线for (int y = 0; y <= Top_Val_Max - Top_Val_Min; y += 10){Point point1 = new Point(offset_x, y * y_scale + Bottom);Point point2 = new Point(offset_x + RenderSize.Width, y * y_scale + Bottom);if (y % Y_Sex == 0){dc.DrawLine(primarygrid_pen, point1, point2);continue;}dc.DrawLine(secondgrid_pen, point1, point2);}//竖线与文字for (int i = 0; i <= (offset_x + RenderSize.Width); i += X_Sex * 2){if (i < offset_x){continue;}var point1 = new Point(i, Bottom);var point2 = new Point(i, (Top_Val_Max - Top_Val_Min) * y_scale + Bottom);//y轴文字if (i % 100 == 0){var text1 = new FormattedText(i + "", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Verdana"), 16, Brushes.Black);var mat3 = new Matrix();mat3.ScaleAt(1, -1, i - text1.Width / 2, 8 + text1.Height / 2);dc.PushTransform(new MatrixTransform(mat3));dc.DrawText(text1, new Point(i - text1.Width / 2, 8));dc.Pop();}//表格刻度文字if (i % 100 == 0){for (int y = Top_Val_Min; y <= Top_Val_Max; y += 10){if (y % Y_Sex == 0){var text1 = new FormattedText(y + "", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface("Verdana"), 12, Brushes.Black);var mat3 = new Matrix();mat3.ScaleAt(1, -1, i + 1, (y - Top_Val_Min) * y_scale + Bottom + text1.Height / 2);dc.PushTransform(new MatrixTransform(mat3));dc.DrawText(text1, new Point(i + 1, (y - Top_Val_Min) * y_scale + Bottom));dc.Pop();}}//深色竖线dc.DrawLine(primarygrid_pen, point1, point2);continue;}//浅色竖线dc.DrawLine(secondgrid_pen, point1, point2);}if (list_points != null){for (int i = (int)offset_x; i < list_points.Count - 1; i++){if (i > offset_x + RenderSize.Width){break;}dc.DrawLine(pen, new Point(i, list_points[i] * y_scale + Bottom), new Point(i + 1, list_points[i + 1] * y_scale + Bottom));}}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){DrawContent();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、接着测试一下,打开MainWindow,添加我们的自定义控件,这里局部显示需要搭配一个ScrollViewer来实现,记得这里没有将我们的自定义控件嵌入ScrollViewer,而是放一个Canvas来填充,代码如下

<Grid><local:CruveChartDrawingVisual x:Name="curve" Margin="0,15,0,20" /><ScrollViewerName="scroll"HorizontalScrollBarVisibility="Auto"ScrollChanged="ScrollViewer_ScrollChanged"VerticalScrollBarVisibility="Disabled"><Canvas x:Name="canvas" Height="1" /></ScrollViewer>
</Grid>

4、接着就是后台代码,比较简单,就是自动生成一个List,然后传给自定义控件,Canvas的宽度直接设置为List的长度,因为这里是水平方向一个像素点画一个点,然后滑动条滑动时再将对应的偏移值传递到控件,再通过偏移值更新视图

public partial class MainWindow : Window
{private bool isAdd = true;public MainWindow(){InitializeComponent();}private void Window_Loaded(object sender, RoutedEventArgs e){List<int> lists = new List<int>();int temp = 20;for (int i = 0; i < 60 * 60; i++){if (isAdd){lists.Add(temp);temp ++;}else{lists.Add(temp);temp --;}if (temp == 90) isAdd = false;if (temp == 10) isAdd = true;}canvas.Width = lists.Count;curve.SetupData(lists);}private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e){curve.OffsetX(scroll.HorizontalOffset);}
}

5、运行效果如下,滑动条拖到哪里就显示哪里,这样就算数据量再大也没问题,这种曲线图跟常规的曲线图有点差别,这里更多的是提供一种思路。

567ad062bfb54a2ede0db6b8ea9c7ce5.gif

[1]

参考资料

[1]

链接: https://www.cnblogs.com/cong2312/p/15921146.html

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

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

相关文章

数据结构(Java)——迭代器和列表的实例

感谢Java软件结构与数据结构 John Lewis Joseph chase 著 金名译 0. 迭代器关键概念&#xff08;补充理解&#xff09; 【1】迭代器是一个对象&#xff0c;它提供了一种依次访问集合中每个元素的方式。 【2】经常把集合定义为Iterable的&#xff0c;说明需要时可以提供一个迭代…

地理信息科学前沿-[热词]

1. LBS Location Based Service&#xff1a;基于位置的服务&#xff0c;它是通过电信移动运营商的无线电通讯网络&#xff08;如GSM网、CDMA网&#xff09;或外部定位方式(如GPS)获取移动终端用户的位置信息&#xff08;地理坐标&#xff0c;或大地坐标&#xff09;&#xff0c…

《vSphere性能设计:性能密集场景下CPU、内存、存储及网络的最佳设计实践》一1.2.2 内存...

本节书摘来华章计算机《vSphere性能设计&#xff1a;性能密集场景下CPU、内存、存储及网络的最佳设计实践》一书中的第1章 &#xff0c;第1.2.2节&#xff0c;[美] 克里斯托弗库塞克&#xff08;Christopher Kusek&#xff09; 著 吕南德特施皮斯&#xff08;Rynardt Spies&a…

如何检查服务已在依赖注入容器中注册

前言依赖关系注入(DI)&#xff0c;是一种在类及其依赖项之间实现控制反转(IoC)的技术。在ASP.NET Core中&#xff0c;依赖关系注入是“一等公民”&#xff0c;被大量使用。但是有时&#xff0c;我们仅仅只需要知道服务是否在依赖注入容器中已注册。比如&#xff0c;不注册使用分…

iOS9 Storyboard unwind segue反回传递事件时机详细步骤

当返回上一个界面且需要上一个界面做某事时&#xff0c;用unwind segue实现起来比delegate简单许多&#xff0c;甚至有时不适合用delegate来实现&#xff0c;那么我们就用unwind segue吧&#xff0c;而且像1->2->3这样的跳转,3视图可以通过unwind segue方便的返回到1、2任…

ios俩个APP之间跳转、传值

两个APP之间的跳转是通过[[UIApplication sharedApplication] openURL:url]这种方式来实现的。 1.首先设置第一个APP的url地址 2.接着设置第二个APP的url地址 3.需要跳转的时候 NSString *urlString [NSString stringWithFormat:"AppJumpSecond://%",textField.tex…

.Net Core手撸一个基于Token的权限认证

说明权限认证是确定用户身份的过程。可确定用户是否有访问资源的权力今天给大家分享一下类似JWT这种基于token的鉴权机制基于token的鉴权机制&#xff0c;它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用,不需要去考虑用户在哪一台服务器…

Mybatis-Generator(MBG)教程与Idea的MBG插件

简介 Mybatis Generator(MBG)&#xff0c;下面我们统称为MBG&#xff0c;是一个Mybatis和iBatis的代码生成器。他可以内省数据库的表&#xff08;或多个表&#xff09;然后生成可以用来访问&#xff08;多个&#xff09;表的基础对象。这样减少了项目新建时各种配置对象&#x…

Windows Server 2008 RemoteApp---发布应用程序

本章节一起来体验RemoteApp应用程序发布功能&#xff0c;本功能利用了微软应用程序虚拟化技术&#xff0c;打个比方&#xff0c;我这台电脑上并没有安装Excel2010&#xff0c;但我现再要用Excel2010该怎么办&#xff1f;难道去找Office2010的光盘来安装吗&#xff1f;不用这么麻…

.NET 中密封类的性能优势

.NET 中密封类的性能优势Intro最近看到一篇文章 Performance benefits of sealed class in .NET&#xff0c;觉得写得不错&#xff0c;翻译一下&#xff0c;分享给大家。目前看到的一些类库中其实很多并没有考虑使用密封类&#xff0c;如果你的类型是不希望被继承的&#xff0c…

jQuery-1.9.1源码分析系列(十) 事件系统——事件绑定

事件绑定的方式有很多种。使用了jQuery那么原来那种绑定方式&#xff08;elem.click function(){...})就不推荐了&#xff0c;原因&#xff1f; 最主要的一个原因是elem.click fn这种方式只能绑定一个事件处理&#xff0c;多次绑定的只会保留最后一次绑定的结果。 看一下jQue…

Windows 8系统平台上应用软件安装心得

1.ArcGIS 10.2安装 需要单独安装.NET 3.5,GIS软件自带的.NET系统不识别&#xff0c;点击360云盘地址进行下载&#xff08;提取码为&#xff1a;1ed3&#xff09;。&#xff08;另外&#xff0c;Win8系统上安装.NET可以参考&#xff1a;http://blog.csdn.net/aijavaer/article/d…

Android视图绘制流程完全解析,带你一步步深入了解View(二)

转自&#xff1a;http://blog.csdn.net/guolin_blog/article/details/16330267 在上一篇文章中&#xff0c;我带着大家一起剖析了一下LayoutInflater的工作原理&#xff0c;可以算是对View进行深入了解的第一步吧。那么本篇文章中&#xff0c;我们将继续对View进行深入探究&…

C# 线程问题之死锁

过多的锁定也会有麻烦。在死锁中&#xff0c;至少有两个线程被挂起&#xff0c;并等待对方解除锁定。由于两个线程都在等待对方&#xff0c;就出现了死锁&#xff0c;线程将无限等待下去。为了说明死锁&#xff0c;下面实例化 StateObject 类型的两个对象&#xff0c;并把它们传…

Matlab图形绘制

1.正余弦曲线 例如自变量从0到10&#xff0c;间隔为0.1的曲线代码如下&#xff1a; 正弦 t 0:.1:10; y sin(t); plot(t,y); 余弦 t 0:.1:10; y cos(t); plot(t,y); 正余弦图形显示如下&#xff1a;

Blazor University (5)组件 — 字面量、表达式和指令

原文链接&#xff1a;https://blazor-university.com/components/literals-expressions-and-directives/字面量、表达式和指令源代码[1]请注意&#xff0c;本节一般不涵盖 Razor 标记。它不会涵盖诸如条件输出、循环等内容。该主题在网络和书籍中的其他地方得到了广泛的介绍。使…

.NET6之MiniAPI(二十七):Metrics

应用的各种Metrics是保证应用健康稳定运行的基础&#xff0c;特别对于一些可用性有所要求的应用&#xff0c;本文介绍prometheus-net这个三方指示库。prometheus-net的工作原理是&#xff0c;在应用内部埋点&#xff0c;通过prometheus采集数据&#xff0c;然后通过grafana把采…

回溯算法之布罗夫卫队(最大团问题)

1、问题 在原始部落中,由于食物缺乏,部落居民经常因为争夺猎物发生冲突,几乎每个居民都 有自己的仇敌。部落酋长为了组织一支保卫部落的卫队,希望从居民中选出最多的居民加入 卫队,并保证卫队中任何两个人都不是仇敌。假设已给定部落中居民间的仇敌关系图,编程 计算构建部落护…

microdot - 一个开源 .NET 微服务框架。

简介Microdot 是一个开源 .NET 框架&#xff0c;可满足轻松创建微服务的许多需求。它的一些主要特点•用于托管微服务的服务容器•服务间 RPC&#xff0c;便于基于接口的服务通信•服务之间的客户端透明响应缓存•日志记录和分布式跟踪支持•客户端负载均衡和服务发现•详细的健…

slider控件控制文本框字体大小

1.控件代码 <pre name"code" class"csharp"> <Slider x:Name"slider1" HorizontalAlignment"Left" Margin"0,261,0,0" VerticalAlignment"Top" Width"446" ValueChanged"Slid…