一、前言

项目中涉及到了心率监测,而且数据量达到了百万级别,通过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" />
<ScrollViewer
Name="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、运行效果如下,滑动条拖到哪里就显示哪里,这样就算数据量再大也没问题,这种曲线图跟常规的曲线图有点差别,这里更多的是提供一种思路

 

WPF开发随笔收录-DrawingVisual绘制高性能曲线图的更多相关文章

  1. WPF开发随笔收录-WriteableBitmap绘制高性能曲线图

    一.前言 之前分享过一期关于DrawingVisual来绘制高性能曲线的博客,今天再分享一篇通过另一种方式来绘制高性能曲线的方法,也就是通过WriteableBitmap的方式:具体的一些细节这里就不 ...

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

    一.前言 项目中之前涉及到胎儿心率图曲线的绘制,最近项目中还需要添加心电曲线和血样曲线的绘制功能.今天就来分享一下心电曲线的绘制方式: 二.正文 1.胎儿心率曲线的绘制是通过DrawingVisual ...

  3. WPF开发随笔收录-ScrollViewer滑块太小解决方案

    一.前言 在WPF开发过程中,ScrollViewer是一个很常使用到的控件,在自己工作的项目中,收到一个反馈就是当ScrollViewer里面的内容太长时,滚动条的滑块就会变得很小,然后导致点击起来 ...

  4. WPF开发随笔收录-仿安卓Toast

    一.前言 在项目中,经常需要用到消息提醒功能,在以前接触安卓开发那会使用过Toast,于是打算在WPF上也来模仿一个,话不多说,撸起袖子干起来! 二.正文 1.首先新建一个工程,工程的目录如下 2.编 ...

  5. WPF开发随笔收录-唯一标识符GUID

    一.前言 该系列博客用于记录本人在WPF开发过程中遇到的各种知识点 二.正文 1.在工作的项目中,软件需要用到在线升级功能,由于第一次弄,在下载服务端的文件到本地时,文件的名称我选择直接生成为固定的格 ...

  6. WPF开发随笔收录-获取软件当前目录的坑

    一.唠唠叨叨 软件开发过程中,经常需要使用到获取exe当前目录这个功能,前同事在实现这个需求时使用的是Directory.GetCurrentDirectory()这个方法,但再最近的测试中,突然发现 ...

  7. WPF开发随笔收录-DataAnnotations实现数据校验(MVVM架构下)

    一.前言 在自己的项目中挺多地方需要涉及到数据验证的,初期的实现方式都是通过点击确定后再逐个验证数据是否符合要求,但这种方式会让后台代码变得很多很乱.于是就开始在网上需求好的解决方式,刚好看到了一个大 ...

  8. WPF开发随笔收录-报警闪烁效果实现

    一.前言 工作中目前经手的项目是医疗相关的监护软件,所以会涉及到一些报警效果的实现,今天在这里就简单分享一下实现方式 二.正文 1.实现的方式比较的简单,就是通过一个Border控件,然后搭配Data ...

  9. WPF开发随笔收录-带递增递减按钮TextBox

    一.前言 今天分享一下如何实现带递增递减按钮的TextBox控件 二.正文 1.之前的博客分享了一篇自定义XamlIcon控件的文章,这次就直接在那个项目的基础上实现今天这个自定义控件 2.首先添加两 ...

随机推荐

  1. Redis4.0.14 迁槽失败

    线上一个redis集群中主节点使用的内存达到了9.78g,按照redis单个实例最大内存不要超出10g的规范,扩容操作就放在了今天晚上进行.因为之前redis迁槽都是采用 redis-trib.rb ...

  2. 1903021116-吉琛-Java第四周作业-程序编写

    项目 内容 课程班级博客链接 19级信计班 这个作业要求链接 Java分支语句学习 https://edu.cnblogs.com/campus/pexy/19xj/homework/12563 我的 ...

  3. 2021.11.09 P2292 [HNOI2004]L语言(trie树+AC自动机)

    2021.11.09 P2292 [HNOI2004]L语言(trie树+AC自动机) https://www.luogu.com.cn/problem/P2292 题意: 标点符号的出现晚于文字的出 ...

  4. 2021.08.16 P1300 城市街道交通费系统(dfs)

    2021.08.16 P1300 城市街道交通费系统(dfs) P1300 城市街道交通费系统 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 城市街道交费系统最近创立了.一 ...

  5. python学习-Day38-HTML

    目录 前端 简介 什么是前端 什么是后端 前端特点 前端主要技术: 前端相关技能 关于 HTTP 的知识在这里 HTML 什么是HTML HTML 实例 实例解析 HTML 标签 HTML 元素 HT ...

  6. 基于Koa与umi实现服务端(SSR)渲染

    工具: umijs:react前端应用框架. koa:基于 Node.js 平台的web 开发框架. 介绍: 本文主要是简单介绍,利用umi开发前端页面,打包成服务端渲染工程包.由Koa实现服务端渲染 ...

  7. CentOS 8及以上版本配置IP的方法,你 get 了吗

    接上篇文章讲了 Ubuntu 18及以上版本的配置方法,本文再来讲讲 CentOS 8 及以上版本配置 IP 的方法. Centos/Redhat(8.x) 配置 IP 方法 说明:CentOS 8 ...

  8. 震惊--Nginx的map指令还能这样用

    map指令简单介绍 当然这里写的都是官方文档是已经写过的,我简单抄一下哈. map指令来自于 ngx_http_map_module 模块,提供的核心能力是 基于一个变量创建一个新变量,大概是这意思. ...

  9. WinUI迁移到即将"过时"的.NET MAUI个人体验

    迁移的初衷 本人平时是做.net相关的工作,对于.net技术栈也有一些了解,自从新的.net能够跨平台之后,之前也有跨平台的ui框架Xamarin,现在微软推出了.NET MAUI这个说是 统一了开发 ...

  10. 1903021121—刘明伟—Java第四周作业—java分支语句学习

    项目 内容 课程班级博客链接 19信计班(本) 作业要求链接 第四周作业 要求 每道题要有题目,代码(使用插入代码,不会插入代码的自己查资料解决,不要直接截图代码!!),截图(只截运行结果). 扩展阅 ...