zoukankan      html  css  js  c++  java
  • 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 欢迎交流学习

  • 相关阅读:
    所有的工作目录 都要svn_开头,并且要进行svn同步,你能保证你不删除,你保证不了非你!
    火狐删除配置文件 会删除目录下所有文件 切记不要把配置文件建立在桌面 恢复软件:易我数据恢复向导 9.0 DiskGenius500
    谷歌全屏脚本 start chrome.exe --kiosk http://www.baidu.com
    bat2exe 就是这么简单 白研究半天VC++了
    火狐加载用户配置文件 "C:XXXMozilla Firefoxfirefox.exe" http://192.168.1.1:8080 -profile ../kkk
    https://quotefancy.com/ 经典句子(英语) 真是特别好~
    (function(){})() 立即执行函数
    jekyll 将纯文本转化为静态网站和博客 静态网站生成器
    vue 发布build 本地设置 相对路径 两个地方 一个根目录用./ css文件里面用../../ 【也不好用,还是得手改】
    iview构建 初始化的时候不要装ESlint 太烦人了
  • 原文地址:https://www.cnblogs.com/AloneZ/p/7521109.html
Copyright © 2011-2022 走看看