WPF开发随笔收录-WriteableBitmap绘制高性能曲线图
一、前言
之前分享过一期关于DrawingVisual来绘制高性能曲线的博客,今天再分享一篇通过另一种方式来绘制高性能曲线的方法,也就是通过WriteableBitmap的方式;具体的一些细节这里就不啰嗦了,同样是局部绘制的思想,滚动条拖动到哪里,就只绘制那一部分的曲线,直接贴代码;
二、正文
1、新建一个类,继承FrameworkElement,然后在里面实现一下绘图的逻辑;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Resources;
using _Font = System.Drawing.Font;
using GDI = System.Drawing; namespace WriteableBitmapDemo.Controls
{
public class CruveWriteableBitmap : FrameworkElement
{
private static PrivateFontCollection pfc = new PrivateFontCollection();
private WriteableBitmap bitmap; private int bitmap_width = 0;
private int bitmap_height = 0; private static _Font font = null;
private static _Font time_font = null; private PointF[][] horizontals = null;
private PointF[][] horizontals_thin = null;
private PointF[][] verticals = null;
private PointF[][] verticals_thin = null; private List<PointF> top_points1;
private List<PointF> top_points2;
private List<PointF> top_points3;
private List<PointF> bottom_points; private List<PointF> labelPosition_up;
private List<string> labelText_up;
private List<PointF> labelPosition_down;
private List<string> labelText_down; private List<PointF> timePosition;
private List<string> timeText; private GDI.Pen blackPen = new GDI.Pen(GDI.Color.Black, 1.5f);
private GDI.Pen grayPen = new GDI.Pen(GDI.Color.Gray, 1f); private GDI.Pen top_pen1 = new GDI.Pen(GDI.Color.Black, 2);
private GDI.Pen top_pen2 = new GDI.Pen(GDI.Color.Orange, 2);
private GDI.Pen top_pen3 = new GDI.Pen(GDI.Color.Purple, 2);public float scaleX { get; set; } = 1f;
private float _ScaleY { get; set; } = 1f;
public float ScaleY
{
get { return _ScaleY; }
set
{
_ScaleY = value;
}
} static CruveWriteableBitmap()
{
var appRootDataDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "msyh.ttf");
if (!File.Exists(appRootDataDir))
{
var key = $"/CurveChartDemo;component/Fonts/msyh.ttf";
StreamResourceInfo info = Application.GetResourceStream(new Uri(key, UriKind.Relative));
using (var stream = info.Stream)
{
byte[] bytes = new byte[stream.Length];
int len = stream.Read(bytes, 0, bytes.Length);
File.WriteAllBytes(appRootDataDir, bytes);
}
}
pfc.AddFontFile(appRootDataDir);
} public CruveWriteableBitmap()
{
time_font = new _Font(pfc.Families[0], 10);
font = new _Font(pfc.Families[0], 8);
} public void DrawPoints()
{
//InitBitmap();
if (this.bitmap == null)
{
return;
} this.bitmap.Lock();
using (Bitmap backBufferBitmap = new Bitmap(this.bitmap_width, this.bitmap_height,
this.bitmap.BackBufferStride, GDI.Imaging.PixelFormat.Format24bppRgb,
this.bitmap.BackBuffer))
{
using (Graphics backBufferGraphics = Graphics.FromImage(backBufferBitmap))
{
backBufferGraphics.SmoothingMode = SmoothingMode.AntiAlias;
backBufferGraphics.CompositingQuality = CompositingQuality.HighSpeed; backBufferGraphics.Clear(GDI.Color.White); //粗横线
if (this.horizontals != null)
{
foreach (var horizontal in this.horizontals)
{
backBufferGraphics.DrawLine(blackPen, horizontal[0], horizontal[1]);
}
}
//细横线
if (this.horizontals_thin != null)
{
foreach (var horizontal in this.horizontals_thin)
{
backBufferGraphics.DrawLine(grayPen, horizontal[0], horizontal[1]);
}
}
//粗竖线
if (this.verticals != null)
{
foreach (var vertical in this.verticals)
{
backBufferGraphics.DrawLine(blackPen, vertical[0], vertical[1]);
}
}
//细竖线
if (this.verticals_thin != null)
{
foreach (var vertical in this.verticals_thin)
{
backBufferGraphics.DrawLine(grayPen, vertical[0], vertical[1]);
}
}
//上图曲线1
if (this.top_points1 != null && this.top_points1.Count > 0)
{
backBufferGraphics.DrawLines(top_pen1, top_points1.ToArray());
}
//上图曲线2
if (this.top_points2 != null && this.top_points2.Count > 0)
{
backBufferGraphics.DrawLines(top_pen2, this.top_points2.ToArray());
}
//上图曲线3
if (this.top_points3 != null && this.top_points3.Count > 0)
{
backBufferGraphics.DrawLines(top_pen3, this.top_points3.ToArray());
}
//下图曲线
if (this.bottom_points != null && this.bottom_points.Count > 0)
{
backBufferGraphics.DrawLines(top_pen1, this.bottom_points.ToArray());
} //文本
if (labelPosition_up != null && labelPosition_up.Count > 0)
{
SizeF fontSize = backBufferGraphics.MeasureString(labelText_up[0], font);
for (int i = 0; i < labelPosition_up.Count; ++i)
{
backBufferGraphics.DrawString(labelText_up[i], font, GDI.Brushes.Black, labelPosition_up[i].X, labelPosition_up[i].Y - fontSize.Height);
}
}
if (labelPosition_down != null && labelPosition_down.Count > 0)
{
for (int i = 0; i < labelPosition_down.Count; ++i)
{
backBufferGraphics.DrawString(labelText_down[i], font, GDI.Brushes.Black, labelPosition_down[i].X, labelPosition_down[i].Y);
}
}
if (timePosition != null && timePosition.Count > 0)
{
for (int i = 0; i < timePosition.Count; ++i)
{
if (i == 0)
backBufferGraphics.DrawString(timeText[i], time_font, GDI.Brushes.Black, timePosition[i].X, timePosition[i].Y);
else
{
SizeF fontSize = backBufferGraphics.MeasureString(timeText[i], time_font);
backBufferGraphics.DrawString(timeText[i], time_font, GDI.Brushes.Black, timePosition[i].X - fontSize.Width / 2, timePosition[i].Y);
} }
} backBufferGraphics.Flush();
}
}
this.bitmap.AddDirtyRect(new Int32Rect(0, 0, this.bitmap_width, this.bitmap_height));
this.bitmap.Unlock();
}public void UpdateTimeLabel(List<PointF> timePosition, List<string> timeText)
{
this.timePosition = timePosition;
this.timeText = timeText;
}
public void UpdatePosition(List<PointF> fhr1_points, List<PointF> fhr2_points, List<PointF> fhr3_points, List<PointF> toco_points)
{
this.top_points1 = fhr1_points;
this.top_points2 = fhr2_points;
this.top_points3 = fhr3_points;
this.bottom_points = toco_points;
} public void UpdateLabelPosition(List<PointF> labelPosition_up, List<string> labelText_up, List<PointF> labelPosition_down, List<string> labelText_down)
{
this.labelPosition_up = labelPosition_up;
this.labelText_up = labelText_up;
this.labelPosition_down = labelPosition_down;
this.labelText_down = labelText_down;
} public void UpdateHorizontalLine(PointF[][] horizontals, PointF[][] horizontals_thin)
{
this.horizontals = horizontals;
this.horizontals_thin = horizontals_thin;
} public void UpdateVerticalLine(PointF[][] verticals, PointF[][] verticals_thin)
{
this.verticals = verticals;
this.verticals_thin = verticals_thin;
} protected override void OnRender(DrawingContext dc)
{
InitBitmap();
if (this.bitmap != null)
{
dc.DrawImage(bitmap, new Rect(0, 0, RenderSize.Width, RenderSize.Height));
}
base.OnRender(dc);
} private void InitBitmap()
{
if (bitmap == null || this.bitmap.Width != (int)this.ActualWidth || this.bitmap.Height != (int)this.ActualHeight)
{
if ((int)this.ActualWidth > 0 && (int)this.ActualHeight > 0)
{
this.bitmap_width = (int)this.ActualWidth;
this.bitmap_height = (int)this.ActualHeight;
this.bitmap = new WriteableBitmap(bitmap_width, bitmap_height, 96, 96, PixelFormats.Bgr24, null);
this.bitmap.Lock();
using (Bitmap backBufferBitmap = new Bitmap(bitmap_width, bitmap_height,
this.bitmap.BackBufferStride, GDI.Imaging.PixelFormat.Format24bppRgb,
this.bitmap.BackBuffer))
{
using (Graphics backBufferGraphics = Graphics.FromImage(backBufferBitmap))
{
backBufferGraphics.SmoothingMode = SmoothingMode.HighSpeed;
backBufferGraphics.CompositingQuality = CompositingQuality.HighSpeed;
backBufferGraphics.Clear(GDI.Color.White);
backBufferGraphics.Flush();
}
}
this.bitmap.AddDirtyRect(new Int32Rect(0, 0, bitmap_width, bitmap_height));
this.bitmap.Unlock();
}
}
}
}
}
2、主窗口添加该控件,并添加滚动条那些
<Window
x:Class="WriteableBitmapDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ct="clr-namespace:WriteableBitmapDemo.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WriteableBitmapDemo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="1500"
Height="450"
Loaded="Window_Loaded"
mc:Ignorable="d">
<Grid>
<ct:CruveWriteableBitmap x:Name="curve" Margin="0,0,0,20" />
<ScrollViewer
Name="scroll"
HorizontalScrollBarVisibility="Auto"
ScrollChanged="ScrollViewer_ScrollChanged"
VerticalScrollBarVisibility="Disabled">
<Canvas x:Name="canvas" Height="1" />
</ScrollViewer>
<Canvas
x:Name="CanvasPanel"
Margin="0,0,0,20"
Background="Transparent" />
</Grid>
</Window>
3、主窗口后台添加曲线数值生成方法和更新视图数据方法
using System.Collections.Generic;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; namespace WriteableBitmapDemo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private bool isAdd = true; private Dictionary<int, int> dicTopPoints = new Dictionary<int, int>();
private Dictionary<int, int> dicBottomPoints = new Dictionary<int, int>(); private float y_scale; private static int Top_Val_Max = 240;
private static int Top_Val_Min = 30;
private static int Top_X_Sex = 20;
private static int Bottom = 100;
private static int Center = 25;
private static int BottomOffset = 0; private double offset = -1; public MainWindow()
{
InitializeComponent(); CanvasPanel.MouseMove += delegate (object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (Mouse.Captured == null) Mouse.Capture(CanvasPanel); if (offset >= 0 && offset <= CanvasPanel.ActualWidth)
{
scroll.ScrollToHorizontalOffset(scroll.HorizontalOffset - (e.GetPosition(this).X - offset));
}
offset = e.GetPosition(this).X;
}
else
{
offset = -1;
Mouse.Capture(null); // 释放鼠标捕获
}
};
} private void Window_Loaded(object sender, RoutedEventArgs e)
{
//生成曲线数据
int temp = 50;
for (int i = 0; i < 24 * 60 * 60 * 4; i++)
{
if (isAdd)
{
dicTopPoints.Add(i, temp);
temp += 2;
}
else
{
dicTopPoints.Add(i, temp);
temp -= 2;
} if (temp == 210) isAdd = false;
if (temp == 50) isAdd = true;
}
temp = 0;
for (int i = 0; i < 24 * 60 * 60 * 4; i++)
{
if (isAdd)
{
dicBottomPoints.Add(i, temp);
temp += 2;
}
else
{
dicBottomPoints.Add(i, temp);
temp -= 2;
} if (temp == 100) isAdd = false;
if (temp == 0) isAdd = true;
}
//初始化滚动条和触发曲线绘制
canvas.Width = dicTopPoints.Count;
scroll.ScrollToLeftEnd();
} private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
InitChartData((float)scroll.HorizontalOffset);
} /// <summary>
/// 根据滚动条偏移量更新需要绘制的数据
/// </summary>
/// <param name="offset"></param>
private void InitChartData(float offset)
{
y_scale = (float)((curve.ActualHeight - Center) / (Top_Val_Max - Top_Val_Min + Bottom)); //上图横线
List<PointF[]> horizontalList = new List<PointF[]>();
List<PointF[]> horizontalList_thin = new List<PointF[]>();
for (int y = 0; y <= Top_Val_Max - Top_Val_Min; y += 10)
{
float currentHeight = (float)(curve.ActualHeight - (y + Bottom) * y_scale - Center);
PointF point1 = new PointF(0, currentHeight);
PointF point2 = new PointF((float)curve.ActualWidth, currentHeight);
if (y % 30 == 0)
horizontalList.Add(new PointF[] { point1, point2 });
else
horizontalList_thin.Add(new PointF[] { point1, point2 });
}
for (int y = 0; y <= Bottom; y += 10)
{
float currentHeight = (float)(curve.ActualHeight - y * y_scale - BottomOffset);
PointF point1 = new PointF(0, currentHeight);
PointF point2 = new PointF((float)curve.ActualWidth, currentHeight);
if (y % 20 == 0)
horizontalList.Add(new PointF[] { point1, point2 });
else
horizontalList_thin.Add(new PointF[] { point1, point2 });
} //竖线与文字
List<PointF[]> verticals = new List<PointF[]>();
List<PointF[]> verticals_thin = new List<PointF[]>(); List<PointF> timePosition = new List<PointF>();
List<string> timeText = new List<string>(); List<PointF> labelPosition_up = new List<PointF>();
List<string> labelText_up = new List<string>(); List<PointF> labelPosition_down = new List<PointF>();
List<string> labelText_down = new List<string>(); for (int i = 0; i < offset + curve.ActualWidth; i += Top_X_Sex * 2)
{
if (i < offset) continue;
//下竖线
PointF point1 = new PointF(i - offset, (float)(curve.ActualHeight - BottomOffset));
PointF point2 = new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - BottomOffset));
//上竖线
PointF point3 = new PointF(i - offset, 0);
PointF point4 = new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - Center)); if ((i + (60 * 2)) % (60 * 2) == 0)
{
verticals.Add(new PointF[] { point1, point2 });
verticals.Add(new PointF[] { point3, point4 });
}
else
{
verticals_thin.Add(new PointF[] { point1, point2 });
verticals_thin.Add(new PointF[] { point3, point4 });
} if (i % 240 == 0)
{
timeText.Add(i + "");
timePosition.Add(new PointF(i - offset, (float)(curve.ActualHeight - Bottom * y_scale - Center)));
} if ((i + (60 * 2)) % (120 * 2) == 0)
{
for (int y = Top_Val_Min; y <= Top_Val_Max; y += 10)
{
if (y % 30 == 0)
{
labelText_up.Add(y + "");
labelPosition_up.Add(new PointF(i - offset, (float)(curve.ActualHeight - (Bottom + y - Top_Val_Min) * y_scale - Center)));
}
}
for (int y = 20; y <= 100; y += 10)
{
if (y % 20 == 0)
{
labelText_down.Add(y + "");
labelPosition_down.Add(new PointF(i - offset, (float)(curve.ActualHeight - y * y_scale)));
}
}
}
} List<PointF> top_points1 = new List<PointF>();
for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
{
top_points1.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] + 100 - Top_Val_Min) * y_scale) - Center));
} List<PointF> top_points2 = new List<PointF>();
for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
{
top_points2.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] + 20 + 100 - Top_Val_Min) * y_scale) - Center));
} List<PointF> top_points3 = new List<PointF>();
for (int i = (int)offset, j = 0; i < dicTopPoints.Count && j < curve.ActualWidth; i++, j++)
{
top_points3.Add(new PointF(j, (float)(curve.ActualHeight - (dicTopPoints[i] - 20 + 100 - Top_Val_Min) * y_scale) - Center));
} List<PointF> bottom_points = new List<PointF>();
for (int i = (int)offset, j = 0; i < dicBottomPoints.Count && j < curve.ActualWidth; i++, j++)
{
bottom_points.Add(new PointF(j, (float)(curve.ActualHeight - dicBottomPoints[i] * y_scale - BottomOffset)));
} curve.UpdateHorizontalLine(horizontalList.ToArray(), horizontalList_thin.ToArray());
curve.UpdateVerticalLine(verticals.ToArray(), verticals_thin.ToArray());
curve.UpdatePosition(top_points1, top_points2, top_points3, bottom_points);
curve.UpdateTimeLabel(timePosition, timeText);
curve.UpdateLabelPosition(labelPosition_up, labelText_up, labelPosition_down, labelText_down);
curve.DrawPoints();
}
}
}
4、运行效果如下,欢迎各位大佬指点
WPF开发随笔收录-WriteableBitmap绘制高性能曲线图的更多相关文章
- WPF开发随笔收录-DrawingVisual绘制高性能曲线图
一.前言 项目中涉及到了心率监测,而且数据量达到了百万级别,通过WPF实现大数据曲线图时,尝试过最基础的Canvas来实现,但是性能堪忧,而且全部画出来也不实际.同时也尝试过找第三方的开源库,但是因为 ...
- WPF开发随笔收录-心电图曲线绘制
一.前言 项目中之前涉及到胎儿心率图曲线的绘制,最近项目中还需要添加心电曲线和血样曲线的绘制功能.今天就来分享一下心电曲线的绘制方式: 二.正文 1.胎儿心率曲线的绘制是通过DrawingVisual ...
- WPF开发随笔收录-ScrollViewer滑块太小解决方案
一.前言 在WPF开发过程中,ScrollViewer是一个很常使用到的控件,在自己工作的项目中,收到一个反馈就是当ScrollViewer里面的内容太长时,滚动条的滑块就会变得很小,然后导致点击起来 ...
- WPF开发随笔收录-仿安卓Toast
一.前言 在项目中,经常需要用到消息提醒功能,在以前接触安卓开发那会使用过Toast,于是打算在WPF上也来模仿一个,话不多说,撸起袖子干起来! 二.正文 1.首先新建一个工程,工程的目录如下 2.编 ...
- WPF开发随笔收录-唯一标识符GUID
一.前言 该系列博客用于记录本人在WPF开发过程中遇到的各种知识点 二.正文 1.在工作的项目中,软件需要用到在线升级功能,由于第一次弄,在下载服务端的文件到本地时,文件的名称我选择直接生成为固定的格 ...
- WPF开发随笔收录-获取软件当前目录的坑
一.唠唠叨叨 软件开发过程中,经常需要使用到获取exe当前目录这个功能,前同事在实现这个需求时使用的是Directory.GetCurrentDirectory()这个方法,但再最近的测试中,突然发现 ...
- WPF开发随笔收录-DataAnnotations实现数据校验(MVVM架构下)
一.前言 在自己的项目中挺多地方需要涉及到数据验证的,初期的实现方式都是通过点击确定后再逐个验证数据是否符合要求,但这种方式会让后台代码变得很多很乱.于是就开始在网上需求好的解决方式,刚好看到了一个大 ...
- WPF开发随笔收录-报警闪烁效果实现
一.前言 工作中目前经手的项目是医疗相关的监护软件,所以会涉及到一些报警效果的实现,今天在这里就简单分享一下实现方式 二.正文 1.实现的方式比较的简单,就是通过一个Border控件,然后搭配Data ...
- WPF开发随笔收录-带递增递减按钮TextBox
一.前言 今天分享一下如何实现带递增递减按钮的TextBox控件 二.正文 1.之前的博客分享了一篇自定义XamlIcon控件的文章,这次就直接在那个项目的基础上实现今天这个自定义控件 2.首先添加两 ...
随机推荐
- 129_Power Pivot&Power BI DAX不同维度动态展示&动态坐标轴
博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 一.背景 某天在和那还是叫我大铁吧 交流关于季度&月度同时展示的问题,感概中国式报表真的需求很微妙. 下面来看看到 ...
- 「ABC 249Ex」Dye Color
考虑停时定理. 初始势能为 \(\sum \Phi(cnt_i)\),末势能为 \(\Phi(n)\),我们希望构造这样一个 \(\Phi:Z\to Z\) 函数,使得每一次操作期望势能变化量为常数. ...
- django框架3
内容概要 注册登录功能编写 django请求生命周期流程图 路由层相关知识 1.路由匹配 2.无名有名分组 3.反向解析 4.名称空间 5.路由分发 内容详情 注册登录功能编写 1.使用自带的sqli ...
- unittest框架里的常用断言方法:用于检查数据
1.unittest框架里的常用断言方法:用于检查数据. (1)assertEqual(x,y) 检查两个参数类型相同并且值相等.(2)assertTrue(x) 检查唯一的参数值等于True(3)a ...
- CabloyJS实现了一款基于X6的工作流可视化编辑器
介绍 文档演示:CMS审批工作流演示了如何通过JSON来直接创建一个工作流定义,通常用于为具体的业务数据生成预定义或内置审批工作流的场景 CabloyJS 4.8.0采用X6 图编辑引擎实现了一款工作 ...
- 2021.05.29【NOIP提高B组】模拟 总结
T1 题意:给你一个图,可以不花代价经过 \(K\) 条边,问从起点到终点的最短路 考试的想法:设 \(dis_{i,j}\) 表示从起点免费了 \(j\) 条边到 \(i\) 的最短路 然后直接跑 ...
- 【生成对抗网络学习 其三】BiGAN论文阅读笔记及其原理理解
参考资料: 1.https://github.com/dragen1860/TensorFlow-2.x-Tutorials 2.<Adversarial Feature Learning> ...
- 【转载】k8s入坑之路(2)kubernetes架构详解
每个微服务通过 Docker 进行发布,随着业务的发展,系统中遍布着各种各样的容器.于是,容器的资源调度,部署运行,扩容缩容就是我们要面临的问题. 基于 Kubernetes 作为容器集群的管理平台被 ...
- IE让我首次遭受了社会的毒打
2022年6月15日,微软终止对IE的支持,自此IE走入历史,可以说这是一个时代的终结. 自己在 2011 年刚从业时,IE 在国内的市场占有率可是遥遥领先的,下图来自于 StatCounter 网站 ...
- 如何使用lerna进行多包(package)管理
为什么要用lerna 将大型代码仓库分割成多个独立版本化的 软件包(package)对于代码共享来说非常有用.但是,如果某些更改 跨越了多个代码仓库的话将变得很 麻烦 并且难以跟踪,并且, 跨越多个代 ...