wpf 高性能自定义chart
上周五,公司要求我们用wpf做一个上波形图数据的控件,试了n个不收费的三方chart控件,功能是真好,但是性能不能,好一点的最多一次能加载几千个点,最后决定自己画。在QA论坛上问了一下,人家推荐用WriteableBitmap画,最后写完发现性能真的还行,10几万个点还算比较流畅,代码记录一下。
using System; using System.Collections.Generic; using System.Drawing.Drawing2D; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace _3.测试自定义控件 { /// <summary> /// CustomerChart.xaml 的交互逻辑 /// </summary> public partial class CustomerChart : UserControl { /// <summary> /// 垂直(纵向)边距(画图区域距离左右两边长度) /// </summary> public double VerticalMargin { get; set; } /// <summary> /// 平行(横向)边距(画图区域距离左右两边长度) /// </summary> public double HorizontalMargin { get; set; } /// <summary> /// 水平刻度间距像素 /// </summary> public double horizontalBetween { get; set; } /// <summary> /// 垂直刻度间距像素 /// </summary> public double verticalBetween { get; set; } /// <summary> /// x轴最大值 /// </summary> public double MaxX { get; set; } /// <summary> /// y轴最大值 /// </summary> public double MaxY { get; set; } /// <summary> /// x轴最小值 /// </summary> public double MinX { get; set; } /// <summary> /// y轴最小值 /// </summary> public double MinY { get; set; } ///// 画图区域起点 ///// </summary> public Point StartPostion { get; set; } /// <summary> /// 画图区域终点 /// </summary> public Point EndPostion { get; set; } /// <summary> /// 点的集合 /// </summary> public System.Drawing.PointF[] points { get; set; } /// <summary> /// 缩放值 /// </summary> public int Scale; public CustomerChart() { InitializeComponent(); HorizontalMargin = 40; VerticalMargin = 40; StartPostion = new Point(HorizontalMargin, VerticalMargin); points = new System.Drawing.PointF[0]; } public void Refresh() { InitCanvas(); //获取y最大值 if (MaxY < 0.0001) { if (points.Length < 2) return; MaxY = points.Max(m => m.Y); } if (MaxX < 0.0001) { if (points.Length < 2) return; MaxX = points.Max(m => m.X); } DrawXAxisTicks(); DrawYAxisTicks(); DrawAxis(); DrawPolyline(); } private void InitCanvas() { MainCanvas.Children.Clear(); EndPostion = new Point(MainCanvas.ActualWidth - HorizontalMargin, MainCanvas.ActualHeight - VerticalMargin); } private void DrawPolyline() { WriteableBitmap _plotBitmap = new WriteableBitmap((int)(MainCanvas.ActualWidth - HorizontalMargin), (int)(MainCanvas.ActualHeight - VerticalMargin), 96, 96, PixelFormats.Rgb24, null); Image image = new Image(); image.MouseWheel += Image_MouseWheel; _plotBitmap.Lock(); var b = new System.Drawing.Bitmap(_plotBitmap.PixelWidth, _plotBitmap.PixelHeight, _plotBitmap.BackBufferStride, System.Drawing.Imaging.PixelFormat.Format24bppRgb, _plotBitmap.BackBuffer); using (var bitmapGraphics = System.Drawing.Graphics.FromImage(b)) { bitmapGraphics.SmoothingMode = SmoothingMode.HighSpeed; bitmapGraphics.InterpolationMode = InterpolationMode.NearestNeighbor; bitmapGraphics.CompositingMode = CompositingMode.SourceCopy; bitmapGraphics.CompositingQuality = CompositingQuality.HighSpeed; System.Drawing.Pen myPen = new System.Drawing.Pen(System.Drawing.Color.Green); for (int t = 0; t < points.Length; t++) { points[t].X = _plotBitmap.PixelWidth * (t * 1.0f / points.Length); points[t].Y = _plotBitmap.PixelHeight *(((float)MaxY - points[t].Y)/(float)MaxY); } bitmapGraphics.DrawLines(myPen, points); } _plotBitmap.AddDirtyRect(new Int32Rect(0, 0, _plotBitmap.PixelWidth, _plotBitmap.PixelHeight)); _plotBitmap.Unlock(); image.Source = _plotBitmap; MainCanvas.Children.Add(image); Canvas.SetBottom(image, VerticalMargin); Canvas.SetLeft(image, HorizontalMargin); } private void Image_MouseWheel(object sender, MouseWheelEventArgs e) { double x = e.GetPosition(sender as Image).X; } private void DrawYAxisTicks() { if (MinY >= MaxY) { return; } if (verticalBetween < 0.0001) { verticalBetween = (MaxY - MinY) / 10; } for (var i = MinY; i <= MaxY + 0.01; i += verticalBetween) { var y = EndPostion.Y - i * (MainCanvas.ActualHeight - VerticalMargin) / (MaxY - MinY); var marker = new Line { X1 = StartPostion.X - 5, Y1 = y, X2 = StartPostion.X, Y2 = y, Stroke = Brushes.Red }; MainCanvas.Children.Add(marker); var gridLine = new Line { X1 = StartPostion.X, Y1 = y, X2 = EndPostion.X, Y2 = y, StrokeThickness = 1, Stroke = new SolidColorBrush(Colors.AliceBlue) }; MainCanvas.Children.Add(gridLine); //画y轴字符 var markText = new TextBlock { Text = i.ToString(CultureInfo.InvariantCulture), Width = 30, Foreground = Brushes.Black, FontSize = 10, HorizontalAlignment = HorizontalAlignment.Right, TextAlignment = TextAlignment.Right }; MainCanvas.Children.Add(markText); Canvas.SetTop(markText, y -3); Canvas.SetLeft(markText, 00); } } /// <summary> /// 画x轴标签 /// </summary> private void DrawXAxisTicks() { if (MinX >= MaxX) { return; } if (horizontalBetween < 0.0001) { horizontalBetween = (MaxX - MinX) / 10; } for (var i = MinX; i <= MaxX + 0.01; i += horizontalBetween) { var x = StartPostion.X + i * (MainCanvas.ActualWidth - HorizontalMargin) / (MaxX - MinX) ; var marker = new Line { X1 = x, Y1 = EndPostion.Y, X2 = x, Y2 = EndPostion.Y + 4, Stroke = Brushes.Red }; MainCanvas.Children.Add(marker); var gridLine = new Line { X1 = x, Y1 = StartPostion.Y, X2 = x, Y2 = EndPostion.Y, StrokeThickness = 1, Stroke = new SolidColorBrush(Colors.AliceBlue) }; MainCanvas.Children.Add(gridLine); //画x轴字符 var text =Convert.ToInt32(i).ToString(CultureInfo.InvariantCulture); var markText = new TextBlock { Text = text, Width = 130, Foreground = Brushes.Black, VerticalAlignment = VerticalAlignment.Top, HorizontalAlignment = HorizontalAlignment.Stretch, TextAlignment = TextAlignment.Left, FontSize = 10 }; //Transform st = new SkewTransform(0, 0); //markText.RenderTransform = st; MainCanvas.Children.Add(markText); Canvas.SetTop(markText, EndPostion.Y + 5); Canvas.SetLeft(markText, x-15); } } /// <summary> /// X轴Y轴 /// </summary> private void DrawAxis() { var xaxis = new Line { X1 = StartPostion.X, Y1 = EndPostion.Y, X2 = EndPostion.X+HorizontalMargin, Y2 = EndPostion.Y, Stroke = new SolidColorBrush(Colors.Black) }; MainCanvas.Children.Add(xaxis); var yaxis = new Line { X1 = StartPostion.X, Y1 = StartPostion.Y-VerticalMargin, X2 = StartPostion.X, Y2 = EndPostion.Y, Stroke = new SolidColorBrush(Colors.Black) }; MainCanvas.Children.Add(yaxis); } private void Window_SizeChanged(object sender, SizeChangedEventArgs e) { Refresh(); } } }