最近在WPF中做一個需要實現統計的功能,其中需要用到統計圖,之前也沒有接觸過,度娘上大多都是各種收費或者免費的第三方控件,不想用第三方控件那就自己畫一個吧。
在園子還找到一篇文章,思路來自這篇文章,文章鏈接:http://www.cnblogs.com/endlesscoding/p/6670432.html
不過根據我的需求,數據每次都在變化,所以都只能從后台綁定,先來看一下完成后的效果吧
可以看到,數據源是一年內一到十二月的金額,所以X軸是固定的,而Y軸標尺是根據數據源的最大值向上取100。再來計算每個標尺該顯示的數值。
數據點的顯示,也是根據提供數據的比例,來計算出像素值的
從頭開始吧,先來說xaml,xaml中需要一個Canvas控件,之后所有的圖形就是畫在這里面
不會用Canvas的話可以先學習下官方文檔:https://msdn.microsoft.com/zh-cn/library/system.windows.controls.canvas(v=vs.110).aspx
1 <Grid Height="400" Width="645"> 2 <Grid.ColumnDefinitions> 3 <ColumnDefinition Width="150" /> 4 <ColumnDefinition Width="330"/> 5 <ColumnDefinition Width="*"/> 6 </Grid.ColumnDefinitions> 7 <Grid.RowDefinitions> 8 <RowDefinition Height="25" /> 9 <RowDefinition /> 10 </Grid.RowDefinitions> 11 <j:JLabel Label="企業賬號:" Grid.Column="0" Grid.Row="0"> 12 <TextBlock Text="{Binding Userid}" HorizontalAlignment="Left" Foreground="Red"/> 13 </j:JLabel> 14 <j:JLabel Label="企業名稱:" Grid.Column="1" Grid.Row="0"> 15 <TextBlock Text="{Binding Username}" HorizontalAlignment="Left" Foreground="Red"/> 16 </j:JLabel> 17 <j:JLabel Label="總金額(元):" Grid.Column="2" Grid.Row="0"> 18 <TextBlock Text="{Binding Pay_Total}" HorizontalAlignment="Left" Foreground="Red"/> 19 </j:JLabel> 20 <Canvas x:Name="chartCanvas" Margin="5" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="4"> 21 </Canvas> 22 </Grid>
先來畫橫縱坐標和箭頭吧,x1,y1,x2,y2這四個參數是Line在Canvas中的起點終點位置像素值
同樣,Line類官方文檔:https://msdn.microsoft.com/zh-cn/library/system.windows.shapes.line(v=vs.110).aspx
1 /// <summary> 2 /// 生成橫縱坐標及箭頭 3 /// </summary> 4 private void DrawArrow() 5 { 6 Line x_axis = new Line();//x軸 7 Line y_axis = new Line();//y軸 8 x_axis.Stroke = System.Windows.Media.Brushes.Black; 9 y_axis.Stroke = System.Windows.Media.Brushes.Black; 10 x_axis.StrokeThickness = 3; 11 y_axis.StrokeThickness = 3; 12 x_axis.X1 = 40; 13 x_axis.Y1 = 320; 14 x_axis.X2 = 600; 15 x_axis.Y2 = 320; 16 y_axis.X1 = 40; 17 y_axis.Y1 = 320; 18 y_axis.X2 = 40; 19 y_axis.Y2 = 30; 20 this.chartCanvas.Children.Add(x_axis); 21 this.chartCanvas.Children.Add(y_axis); 22 23 Line y_scale1 = new Line(); //坐標原點直角 24 y_scale1.Stroke = System.Windows.Media.Brushes.Black; 25 y_scale1.StrokeThickness =1; 26 y_scale1.X1 = 40; 27 y_scale1.Y1 = 310; 28 y_scale1.X2 = 44; 29 y_scale1.Y2 = 310; 30 y_scale1.StrokeStartLineCap = PenLineCap.Triangle; 31 this.chartCanvas.Children.Add(y_scale1); 32 33 Path x_axisArrow = new Path();//x軸箭頭 34 Path y_axisArrow = new Path();//y軸箭頭 35 x_axisArrow.Fill = new SolidColorBrush(Color.FromRgb(0, 0, 0)); 36 y_axisArrow.Fill = new SolidColorBrush(Color.FromRgb(0, 0, 0)); 37 PathFigure x_axisFigure = new PathFigure(); 38 x_axisFigure.IsClosed = true; 39 x_axisFigure.StartPoint = new Point(600, 316); //路徑的起點 40 x_axisFigure.Segments.Add(new LineSegment(new Point(600, 324), false)); //第2個點 41 x_axisFigure.Segments.Add(new LineSegment(new Point(610, 320), false)); //第3個點 42 PathFigure y_axisFigure = new PathFigure(); 43 y_axisFigure.IsClosed = true; 44 y_axisFigure.StartPoint = new Point(36, 30); //路徑的起點 45 y_axisFigure.Segments.Add(new LineSegment(new Point(44, 30), false)); //第2個點 46 y_axisFigure.Segments.Add(new LineSegment(new Point(40, 20), false)); //第3個點 47 PathGeometry x_axisGeometry = new PathGeometry(); 48 PathGeometry y_axisGeometry = new PathGeometry(); 49 x_axisGeometry.Figures.Add(x_axisFigure); 50 y_axisGeometry.Figures.Add(y_axisFigure); 51 x_axisArrow.Data = x_axisGeometry; 52 y_axisArrow.Data = y_axisGeometry; 53 this.chartCanvas.Children.Add(x_axisArrow); 54 this.chartCanvas.Children.Add(y_axisArrow); 55 56 TextBlock x_label =new TextBlock(); 57 TextBlock y_label =new TextBlock(); 58 TextBlock o_label =new TextBlock(); 59 x_label.Text = "月"; 60 y_label.Text = "元"; 61 o_label.Text = "0"; 62 Canvas.SetLeft(x_label, 610); 63 Canvas.SetLeft(y_label, 20); 64 Canvas.SetLeft(o_label, 20); 65 Canvas.SetTop(x_label, 317); 66 Canvas.SetTop(y_label, 4); 67 Canvas.SetTop(o_label, 312); 68 x_label.FontSize = 14; 69 y_label.FontSize = 14; 70 o_label.FontSize = 14; 71 this.chartCanvas.Children.Add(x_label); 72 this.chartCanvas.Children.Add(y_label); 73 this.chartCanvas.Children.Add(o_label); 74 75 }
標尺,X軸以45為間隔單位,Y軸以10px為單位,且沒5格顯示一個大標尺
1 /// <summary> 2 /// 作出x軸和y軸的標尺 3 /// </summary> 4 private void DrawScale() 5 { 6 for (int i = 1; i < 13; i++)//作12個刻度 7 { 8 //原點 O=(40,320) 9 Line x_scale = new Line(); //主x軸標尺 10 x_scale.StrokeEndLineCap = PenLineCap.Triangle; 11 x_scale.StrokeThickness = 1; 12 x_scale.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0)); 13 x_scale.X1 = 40 + i * 45; 14 x_scale.X2 = x_scale.X1; 15 x_scale.Y1 = 320; 16 x_scale.StrokeThickness = 3; 17 x_scale.Y2 = x_scale.Y1 - 8; 18 this.chartCanvas.Children.Add(x_scale); 19 20 Line x_in = new Line();//x軸軸輔助標尺 21 x_in.Stroke = System.Windows.Media.Brushes.LightGray; 22 x_in.StrokeThickness = 0.5; 23 x_in.X1 = 40 + i * 45; 24 x_in.Y1 = 320; 25 x_in.X2 = 40 + i * 45; 26 x_in.Y2 = 30; 27 this.chartCanvas.Children.Add(x_in); 28 } 29 for (int j = 0; j < 30; j++ ) 30 { 31 Line y_scale = new Line(); //主Y軸標尺 32 y_scale.StrokeEndLineCap = PenLineCap.Triangle; 33 y_scale.StrokeThickness = 1; 34 y_scale.Stroke = new SolidColorBrush(Color.FromRgb(0, 0, 0)); 35 36 y_scale.X1 = 40; //原點x=40 37 if (j % 5 == 0) 38 { 39 y_scale.StrokeThickness = 3; 40 y_scale.X2 = y_scale.X1 + 8;//大刻度線 41 } 42 else 43 { 44 y_scale.X2 = y_scale.X1 + 4;//小刻度線 45 } 46 47 y_scale.Y1 = 320 - j * 10; //每10px作一個刻度 48 y_scale.Y2 = y_scale.Y1; 49 this.chartCanvas.Children.Add(y_scale); 50 } 51 for (int i = 1; i < 6; i++) 52 { 53 Line y_in = new Line();//y軸輔助標尺 54 y_in.Stroke = System.Windows.Media.Brushes.LightGray; 55 y_in.StrokeThickness = 0.5; 56 y_in.X1 = 40; 57 y_in.Y1 = 320 - i * 50; 58 y_in.X2 = 600; 59 y_in.Y2 = 320 - i * 50; 60 this.chartCanvas.Children.Add(y_in); 61 } 62 63 }
刻度標簽的話,X軸是固定的,並且其中用到了一個把阿拉伯數字轉換為中文的方法 NumberToChinese(),
Y軸標尺標簽,是用出入的 list<double>,計算出最大值再向上取100整,再分成五份,每份的值就是五個標簽了
list最大值向上取100的方法:(除100向上取整再乘100)
Math.Ceiling(list.Max() / 100) * 100
1 /// <summary> 2 /// 添加刻度標簽 3 /// </summary> 4 private void DrawScaleLabel(List<double> list) 5 { 6 for (int i = 1; i < 13; i++) 7 { 8 TextBlock x_ScaleLabel = new TextBlock(); 9 x_ScaleLabel.Text = NumberToChinese(i.ToString()); 10 if (x_ScaleLabel.Text == "一零") 11 { 12 x_ScaleLabel.Text = "十"; 13 Canvas.SetLeft(x_ScaleLabel, 40 + 45 * i - 6); 14 } 15 else if (x_ScaleLabel.Text == "一一") 16 { 17 x_ScaleLabel.Text = "十一"; 18 Canvas.SetLeft(x_ScaleLabel, 40 + 45 * i - 10); 19 } 20 21 else if (x_ScaleLabel.Text == "一二") 22 { 23 x_ScaleLabel.Text = "十二"; 24 Canvas.SetLeft(x_ScaleLabel, 40 + 45 * i - 10); 25 } 26 else 27 { 28 Canvas.SetLeft(x_ScaleLabel, 40 + 45 * i - 6); 29 } 30 Canvas.SetTop(x_ScaleLabel, 320 + 2); 31 this.chartCanvas.Children.Add(x_ScaleLabel); 32 } 33 34 for (int i = 1; i < 6; i++) 35 { 36 TextBlock y_ScaleLabel = new TextBlock(); 37 double max = Math.Ceiling(list.Max() / 100) * 100; 38 y_ScaleLabel.Text = (i * (max/5)).ToString(); 39 Canvas.SetLeft(y_ScaleLabel, 40 - 30); 40 Canvas.SetTop(y_ScaleLabel, 320 - 5 * 10 * i - 6); 41 42 this.chartCanvas.Children.Add(y_ScaleLabel); 43 } 44 } 45 46 /// <summary> 47 /// 數字轉漢字 48 /// </summary> 49 /// <param name="numberStr"></param> 50 /// <returns></returns> 51 public static string NumberToChinese(string numberStr) 52 { 53 string numStr = "0123456789"; 54 string chineseStr = "零一二三四五六七八九"; 55 char[] c = numberStr.ToCharArray(); 56 for (int i = 0; i < c.Length; i++) 57 { 58 int index = numStr.IndexOf(c[i]); 59 if (index != -1) 60 c[i] = chineseStr.ToCharArray()[index]; 61 } 62 numStr = null; 63 chineseStr = null; 64 return new string(c); 65 }
接下來就是計算數據點了,難點在於計算像素點,X軸是固定的,所以不用關注
直接算好的X軸十二個數值
double[] left = { 85, 130, 175, 220, 265, 310, 355, 400, 445, 490, 535, 580 };
而Y軸就要自己算了,提供一個思路:
區域總像素 - 區域總像素 * (數值/最大值)
1 /// <summary> 2 /// 計算數據點並添加 3 /// </summary> 4 /// <param name="list"></param> 5 private void DrawPoint(List<double> list) 6 { 7 double[] left = { 85, 130, 175, 220, 265, 310, 355, 400, 445, 490, 535, 580 }; 8 List<double> leftlist = new List<double>(); 9 leftlist.AddRange(left); 10 11 for (int i = 1; i < 13; i++) 12 { 13 Ellipse Ellipse = new Ellipse(); 14 Ellipse .Fill = new SolidColorBrush(Color.FromRgb(0, 0, 0xff)); 15 Ellipse .Width = 8; 16 Ellipse .Height = 8; 17 Canvas.SetLeft(Ellipse,leftlist[i-1]- 4); 18 double y_Max = Math.Ceiling(list.Max() / 100) * 100; 19 Canvas.SetTop(Ellipse, 320 - 250 * (list[i-1] / y_Max) - 4); 20 coordinatePoints.Add(new Point(leftlist[i-1], 320 - 250 * (list[i-1] / y_Max))); 21 this.chartCanvas.Children.Add(Ellipse); 22 //值顯示 23 TextBlock EP_Label = new TextBlock(); 24 EP_Label.Foreground = System.Windows.Media.Brushes.Red; 25 EP_Label.Text = list[i-1].ToString(); 26 Canvas.SetLeft(EP_Label, leftlist[i-1] - 10); 27 Canvas.SetTop(EP_Label, 320 - 250 * (list[i-1] / y_Max) - 20); 28 this.chartCanvas.Children.Add(EP_Label); 29 } 30 }
在繪制數據點的時候,每一次的位置都保存了: coordinatePoints.Add(new Point(leftlist[i-1], 320 - 250 * (list[i-1] / y_Max)));
先得定義:
/// <summary> /// 折線圖坐標點 /// </summary> private PointCollection coordinatePoints = new PointCollection();
最后直接連連看就好了
1 /// <summary> 2 /// 繪制連接折線 3 /// </summary> 4 private void DrawCurve() 5 { 6 Polyline curvePolyline = new Polyline(); 7 8 curvePolyline.Stroke = Brushes.Green; 9 curvePolyline.StrokeThickness = 2; 10 11 curvePolyline.Points = coordinatePoints; 12 this.chartCanvas.Children.Add(curvePolyline); 13 }
由於我項目的關系,數據是從DataGrid控件行數據來的,所以每一次都不一樣,只能在彈出窗體時調用這幾個方法
由於每一都不一樣,在窗口關閉時需要清空畫布內的所有控件,否則畫布內控件會一直覆蓋
chartCanvas.Children.Clear();
coordinatePoints.Clear();
我的郵箱:alonezying@163.com 歡迎交流學習