WPF在Canvas中绘图实现折线统计图

最近在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 欢迎交流学习

 

posted @ 2017-09-14 15:52  Louis_giao  阅读(8291)  评论(5编辑  收藏  举报