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;//x轴分度值
    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绘制高性能曲线图的详细内容,更多关于WPF DrawingVisual绘制曲线图的资料请关注我们其它相关文章!

(0)

相关推荐

  • WPF图片按钮的实现方法

    本文实例为大家分享了WPF图片按钮的实现代码,供大家参考,具体内容如下 直接代码 public class ImageButton : System.Windows.Controls.Button { /// <summary> /// 图片 /// </summary> public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", t

  • WPF实现手风琴式轮播图切换效果

    本文实例为大家分享了WPF实现轮播图切换效果的具体代码,供大家参考,具体内容如下 实现效果如下: 步骤: 1.自定义控件MyImageControl 实现图片的裁切和动画的赋值. public partial class MyImageControl : UserControl { public static readonly DependencyProperty ShowImageProperty = DependencyProperty.Register("ShowImage",

  • WPF 自定义雷达图开发实例教程

    自定义雷达图表如下: 1.创建UserControl,名为"RadarChartControl" 前台: <UserControl x:Class="WpfApplication2.RadarChartControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/win

  • WPF实现图片合成或加水印的方法【2种方法】

    本文实例讲述了WPF实现图片合成或加水印的方法.分享给大家供大家参考,具体如下: 最近项目中应用多次应用了图片合成,为了今后方便特此记下. 在WPF下有两种图片合成的方式,一种还是用原来C#提供的GDI+方式,命名空间是System.Drawing 和 System.Drawing.Imaging,另一种是WPF中新添加的API,命名空间是 System.Windows.Media 和 System.Windows.Media.Imaging . 我们来做一个简单的例子,分别用上面的两种方式实现

  • WPF/Silverlight实现图片局部放大的方法分析

    本文实例讲述了WPF/Silverlight实现图片局部放大的方法.分享给大家供大家参考,具体如下: 最近的项目中也要用到一个局部图片放大的功能,前面这篇<silverlight实现图片局部放大效果的方法>虽然已经给出了原理.知识要点.尺寸要点及后端主要代码,但遗憾的是没有给出xaml的代码.这里按照前文中的提示,动手用WPF实践了一下,花了一个小时,终于搞出来了.这篇文章也就算是一个补充吧. 界面如下图所示: 实现的原理和用到的知识点请点击上面的链接,杨大侠已经说的很清楚了.这里主要强调的就

  • WPF开发之利用DrawingVisual绘制高性能曲线图

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

  • 利用python绘制数据曲线图的实现

    "在举国上下万众一心.众志成城做好新冠肺炎疫情防控工作的特殊时刻,我们不能亲临主战场,但我们能坚持在大战中坚定信心.不负韶华." 1.爬取新闻保存为json文件,并将绘图所需数据保存至数据库 数据库表结构: 代码部分: import pymysql import re import sys,urllib,json from urllib import request from datetime import datetime import pandas as pd Today=date

  • WPF+WriteableBitmap实现高性能曲线图的绘制

    目录 一.前言 二.正文 三.运行效果 一.前言 之前分享过一期关于DrawingVisual来绘制高性能曲线的博客,今天再分享一篇通过另一种方式来绘制高性能曲线的方法,也就是通过WriteableBitmap的方式:具体的一些细节这里就不啰嗦了,同样是局部绘制的思想,滚动条拖动到哪里,就只绘制那一部分的曲线,直接贴代码:(该程序在英特尔11代CPU的电脑可能会遇到拖动滚动条曲线图卡住不动的情况,这个是显卡驱动的问题,官方已经修复了,遇到这问题的记得更新一下驱动) 二.正文 1.新建一个类,继承

  • Qt图形图像开发之高性能曲线图模块QCustomplot库详细使用方法与实例(支持动、静曲线图)

    Qt曲线图模块QCustomPlot库介绍 QCustomPlot是一个小型的Qt画图标类,支持绘制静态曲线.动态曲线.多重坐标曲线,柱状图,蜡烛图等 前段时间用QChart模块画图,一条曲线上面放8000条数据就会卡的不行必须要换个其他的控件,后来找到了曲线图模块QCustomplot库 这个库性能非常好,画曲线图折线图柱状图动态静态,放大缩小,都很好用,10w条数据量无压力秒画出来一点也不卡 下载地址 https://www.qcustomplot.com/index.php/downloa

  • Android开发之多线程中实现利用自定义控件绘制小球并完成小球自动下落功能实例

    本文实例讲述了Android开发之多线程中实现利用自定义控件绘制小球并完成小球自动下落功能的方法.分享给大家供大家参考,具体如下: 1.布局界面 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_pare

  • c# 基于wpf,开发OFD电子文档阅读器

    前言 OFD是国家标准版式文档格式,于2016年生效.OFD文档国家标准参见<电子文件存储与交换格式版式文档>.既然是国家标准,OFD随后肯定会首先在政务系统使用,并逐步推向社会各个方面.OFD是在研究当下各类文件格式后,推出的标准,有如下优点: 1 产权属于自主产权 2 具有便携性:文件小,可压缩比率大.测试显示生成的文件体量比PDF还要小. 3 具有开放性:易于入门,对于使用者来说更具开放性. 4 具有扩展性:预留了可扩展入口和自定义标引,设置了非接触式引用机制,为特性化提供支持. 5 呈

  • Android开发中使用achartengine绘制各种图表的方法

    本文实例讲述了Android开发中使用achartengine绘制各种图表的方法.分享给大家供大家参考,具体如下: 1. ABarChart.java package com.anjoyo.achartengine; import java.util.Random; import org.achartengine.ChartFactory; import org.achartengine.chart.BarChart.Type; import org.achartengine.model.Cat

  • 利用Tensorboard绘制网络识别准确率和loss曲线实例

    废话不多说,直接上代码看吧! import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data #载入数据集 mnist = input_data.read_data_sets("MNIST_data",one_hot=True) #每个批次的大小和总共有多少个批次 batch_size = 100 n_batch = mnist.train.num_examples // batch_

  • Python利用matplotlib绘制折线图的新手教程

    前言 matplotlib是Python中的一个第三方库.主要用于开发2D图表,以渐进式.交互式的方式实现数据可视化,可以更直观的呈现数据,使数据更具说服力. 一.安装matplotlib pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple 二.matplotlib图像简介 matplotlib的图像分为三层,容器层.辅助显示层和图像层. 1. 容器层主要由Canvas.Figure.Axes组成. Canvas位

  • 详解如何利用Python绘制迷宫小游戏

    目录 构思 绘制迷宫 走出迷宫 完整代码 更大的挑战 关于坐标系设置 周末在家,儿子闹着要玩游戏,让玩吧,不利于健康,不让玩吧,扛不住他折腾,于是想,不如一起搞个小游戏玩玩! 之前给他编过猜数字 和 掷骰子 游戏,现在已经没有吸引力了,就对他说:“我们来玩个迷宫游戏吧.” 果不其然,有了兴趣,于是和他一起设计实现起来,现在一起看看我们是怎么做的吧,说不定也能成为一个陪娃神器~ 先一睹为快: 构思 迷宫游戏,相对比较简单,设置好地图,然后用递归算法来寻找出口,并将过程显示出来,增强趣味性. 不如想

随机推荐