一、前言
项目中涉及到了心率监测,而且数据量达到了百万级别,通过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、运行效果如下,滑动条拖到哪里就显示哪里,这样就算数据量再大也没问题,这种曲线图跟常规的曲线图有点差别,这里更多的是提供一种思路。
[1]
参考资料
[1]
链接: https://www.cnblogs.com/cong2312/p/15921146.html