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
<Grid Height="400" Width="645">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="330"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition />
</Grid.RowDefinitions>
<j:JLabel Label="企业账号:" Grid.Column="0" Grid.Row="0">
<TextBlock Text="{Binding Userid}" HorizontalAlignment="Left" Foreground="Red"/>
</j:JLabel>
<j:JLabel Label="企业名称:" Grid.Column="1" Grid.Row="0">
<TextBlock Text="{Binding Username}" HorizontalAlignment="Left" Foreground="Red"/>
</j:JLabel>
<j:JLabel Label="总金额(元):" Grid.Column="2" Grid.Row="0">
<TextBlock Text="{Binding Pay_Total}" HorizontalAlignment="Left" Foreground="Red"/>
</j:JLabel>
<Canvas x:Name="chartCanvas" Margin="5" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="4">
</Canvas>
</Grid>
先来画横纵坐标和箭头吧,x1,y1,x2,y2这四个参数是Line在Canvas中的起点终点位置像素值
同样,Line类官方文档:https://msdn.microsoft.com/zh-cn/library/system.windows.shapes.line(v=vs.110).aspx
/// <summary>
/// 生成横纵坐标及箭头
/// </summary>
private void DrawArrow()
{
Line x_axis = new Line();//x轴
Line y_axis = new Line();//y轴
x_axis.Stroke = System.Windows.Media.Brushes.Black;
y_axis.Stroke = System.Windows.Media.Brushes.Black;
x_axis.StrokeThickness = ;
y_axis.StrokeThickness = ;
x_axis.X1 = ;
x_axis.Y1 = ;
x_axis.X2 = ;
x_axis.Y2 = ;
y_axis.X1 = ;
y_axis.Y1 = ;
y_axis.X2 = ;
y_axis.Y2 = ;
this.chartCanvas.Children.Add(x_axis);
this.chartCanvas.Children.Add(y_axis); Line y_scale1 = new Line(); //坐标原点直角
y_scale1.Stroke = System.Windows.Media.Brushes.Black;
y_scale1.StrokeThickness =;
y_scale1.X1 = ;
y_scale1.Y1 = ;
y_scale1.X2 = ;
y_scale1.Y2 = ;
y_scale1.StrokeStartLineCap = PenLineCap.Triangle;
this.chartCanvas.Children.Add(y_scale1); Path x_axisArrow = new Path();//x轴箭头
Path y_axisArrow = new Path();//y轴箭头
x_axisArrow.Fill = new SolidColorBrush(Color.FromRgb(, , ));
y_axisArrow.Fill = new SolidColorBrush(Color.FromRgb(, , ));
PathFigure x_axisFigure = new PathFigure();
x_axisFigure.IsClosed = true;
x_axisFigure.StartPoint = new Point(, ); //路径的起点
x_axisFigure.Segments.Add(new LineSegment(new Point(, ), false)); //第2个点
x_axisFigure.Segments.Add(new LineSegment(new Point(, ), false)); //第3个点
PathFigure y_axisFigure = new PathFigure();
y_axisFigure.IsClosed = true;
y_axisFigure.StartPoint = new Point(, ); //路径的起点
y_axisFigure.Segments.Add(new LineSegment(new Point(, ), false)); //第2个点
y_axisFigure.Segments.Add(new LineSegment(new Point(, ), false)); //第3个点
PathGeometry x_axisGeometry = new PathGeometry();
PathGeometry y_axisGeometry = new PathGeometry();
x_axisGeometry.Figures.Add(x_axisFigure);
y_axisGeometry.Figures.Add(y_axisFigure);
x_axisArrow.Data = x_axisGeometry;
y_axisArrow.Data = y_axisGeometry;
this.chartCanvas.Children.Add(x_axisArrow);
this.chartCanvas.Children.Add(y_axisArrow); TextBlock x_label =new TextBlock();
TextBlock y_label =new TextBlock();
TextBlock o_label =new TextBlock();
x_label.Text = "月";
y_label.Text = "元";
o_label.Text = "";
Canvas.SetLeft(x_label, );
Canvas.SetLeft(y_label, );
Canvas.SetLeft(o_label, );
Canvas.SetTop(x_label, );
Canvas.SetTop(y_label, );
Canvas.SetTop(o_label, );
x_label.FontSize = ;
y_label.FontSize = ;
o_label.FontSize = ;
this.chartCanvas.Children.Add(x_label);
this.chartCanvas.Children.Add(y_label);
this.chartCanvas.Children.Add(o_label); }
标尺,X轴以45为间隔单位,Y轴以10px为单位,且没5格显示一个大标尺
/// <summary>
/// 作出x轴和y轴的标尺
/// </summary>
private void DrawScale()
{
for (int i = ; i < ; i++)//作12个刻度
{
//原点 O=(40,320)
Line x_scale = new Line(); //主x轴标尺
x_scale.StrokeEndLineCap = PenLineCap.Triangle;
x_scale.StrokeThickness = ;
x_scale.Stroke = new SolidColorBrush(Color.FromRgb(, , ));
x_scale.X1 = + i * ;
x_scale.X2 = x_scale.X1;
x_scale.Y1 = ;
x_scale.StrokeThickness = ;
x_scale.Y2 = x_scale.Y1 - ;
this.chartCanvas.Children.Add(x_scale); Line x_in = new Line();//x轴轴辅助标尺
x_in.Stroke = System.Windows.Media.Brushes.LightGray;
x_in.StrokeThickness = 0.5;
x_in.X1 = + i * ;
x_in.Y1 = ;
x_in.X2 = + i * ;
x_in.Y2 = ;
this.chartCanvas.Children.Add(x_in);
}
for (int j = ; j < ; j++ )
{
Line y_scale = new Line(); //主Y轴标尺
y_scale.StrokeEndLineCap = PenLineCap.Triangle;
y_scale.StrokeThickness = ;
y_scale.Stroke = new SolidColorBrush(Color.FromRgb(, , )); y_scale.X1 = ; //原点x=40
if (j % == )
{
y_scale.StrokeThickness = ;
y_scale.X2 = y_scale.X1 + ;//大刻度线
}
else
{
y_scale.X2 = y_scale.X1 + ;//小刻度线
} y_scale.Y1 = - j * ; //每10px作一个刻度
y_scale.Y2 = y_scale.Y1;
this.chartCanvas.Children.Add(y_scale);
}
for (int i = ; i < ; i++)
{
Line y_in = new Line();//y轴辅助标尺
y_in.Stroke = System.Windows.Media.Brushes.LightGray;
y_in.StrokeThickness = 0.5;
y_in.X1 = ;
y_in.Y1 = - i * ;
y_in.X2 = ;
y_in.Y2 = - i * ;
this.chartCanvas.Children.Add(y_in);
} }
刻度标签的话,X轴是固定的,并且其中用到了一个把阿拉伯数字转换为中文的方法 NumberToChinese(),
Y轴标尺标签,是用出入的 list<double>,计算出最大值再向上取100整,再分成五份,每份的值就是五个标签了
list最大值向上取100的方法:(除100向上取整再乘100)
Math.Ceiling(list.Max() / 100) * 100
/// <summary>
/// 添加刻度标签
/// </summary>
private void DrawScaleLabel(List<double> list)
{
for (int i = ; i < ; i++)
{
TextBlock x_ScaleLabel = new TextBlock();
x_ScaleLabel.Text = NumberToChinese(i.ToString());
if (x_ScaleLabel.Text == "一零")
{
x_ScaleLabel.Text = "十";
Canvas.SetLeft(x_ScaleLabel, + * i - );
}
else if (x_ScaleLabel.Text == "一一")
{
x_ScaleLabel.Text = "十一";
Canvas.SetLeft(x_ScaleLabel, + * i - );
} else if (x_ScaleLabel.Text == "一二")
{
x_ScaleLabel.Text = "十二";
Canvas.SetLeft(x_ScaleLabel, + * i - );
}
else
{
Canvas.SetLeft(x_ScaleLabel, + * i - );
}
Canvas.SetTop(x_ScaleLabel, + );
this.chartCanvas.Children.Add(x_ScaleLabel);
} for (int i = ; i < ; i++)
{
TextBlock y_ScaleLabel = new TextBlock();
double max = Math.Ceiling(list.Max() / ) * ;
y_ScaleLabel.Text = (i * (max/)).ToString();
Canvas.SetLeft(y_ScaleLabel, - );
Canvas.SetTop(y_ScaleLabel, - * * i - ); this.chartCanvas.Children.Add(y_ScaleLabel);
}
} /// <summary>
/// 数字转汉字
/// </summary>
/// <param name="numberStr"></param>
/// <returns></returns>
public static string NumberToChinese(string numberStr)
{
string numStr = "";
string chineseStr = "零一二三四五六七八九";
char[] c = numberStr.ToCharArray();
for (int i = ; i < c.Length; i++)
{
int index = numStr.IndexOf(c[i]);
if (index != -)
c[i] = chineseStr.ToCharArray()[index];
}
numStr = null;
chineseStr = null;
return new string(c);
}
接下来就是计算数据点了,难点在于计算像素点,X轴是固定的,所以不用关注
直接算好的X轴十二个数值
double[] left = { 85, 130, 175, 220, 265, 310, 355, 400, 445, 490, 535, 580 };
而Y轴就要自己算了,提供一个思路:
区域总像素 - 区域总像素 * (数值/最大值)

/// <summary>
/// 计算数据点并添加
/// </summary>
/// <param name="list"></param>
private void DrawPoint(List<double> list)
{
double[] left = { , , , , , , , , , , , };
List<double> leftlist = new List<double>();
leftlist.AddRange(left); for (int i = ; i < ; i++)
{
Ellipse Ellipse = new Ellipse();
Ellipse .Fill = new SolidColorBrush(Color.FromRgb(, , 0xff));
Ellipse .Width = ;
Ellipse .Height = ;
Canvas.SetLeft(Ellipse,leftlist[i-]- );
double y_Max = Math.Ceiling(list.Max() / ) * ;
Canvas.SetTop(Ellipse, - * (list[i-] / y_Max) - );
coordinatePoints.Add(new Point(leftlist[i-], - * (list[i-] / y_Max)));
this.chartCanvas.Children.Add(Ellipse);
//值显示
TextBlock EP_Label = new TextBlock();
EP_Label.Foreground = System.Windows.Media.Brushes.Red;
EP_Label.Text = list[i-].ToString();
Canvas.SetLeft(EP_Label, leftlist[i-] - );
Canvas.SetTop(EP_Label, - * (list[i-] / y_Max) - );
this.chartCanvas.Children.Add(EP_Label);
}
}
在绘制数据点的时候,每一次的位置都保存了: coordinatePoints.Add(new Point(leftlist[i-1], 320 - 250 * (list[i-1] / y_Max)));
先得定义:
/// <summary>
/// 折线图坐标点
/// </summary>
private PointCollection coordinatePoints = new PointCollection();
最后直接连连看就好了
/// <summary>
/// 绘制连接折线
/// </summary>
private void DrawCurve()
{
Polyline curvePolyline = new Polyline(); curvePolyline.Stroke = Brushes.Green;
curvePolyline.StrokeThickness = ; curvePolyline.Points = coordinatePoints;
this.chartCanvas.Children.Add(curvePolyline);
}
由于我项目的关系,数据是从DataGrid控件行数据来的,所以每一次都不一样,只能在弹出窗体时调用这几个方法
由于每一都不一样,在窗口关闭时需要清空画布内的所有控件,否则画布内控件会一直覆盖
chartCanvas.Children.Clear();
coordinatePoints.Clear();
我的邮箱:alonezying@163.com 欢迎交流学习
WPF在Canvas中绘图实现折线统计图的更多相关文章
- WPF笔记(1.10 绘图)——Hello,WPF!
原文:WPF笔记(1.10 绘图)--Hello,WPF! 书中的代码语法过时了,改写为以下(测试通过): <Button> <Button.L ...
- 年度巨献-WPF项目开发过程中WPF小知识点汇总(原创+摘抄)
WPF中Style的使用 Styel在英文中解释为”样式“,在Web开发中,css为层叠样式表,自从.net3.0推出WPF以来,WPF也有样式一说,通过设置样式,使其WPF控件外观更加美化同时减少了 ...
- HTML5在canvas中绘制复杂形状附效果截图
HTML5在canvas中绘制复杂形状附效果截图 一.绘制复杂形状或路径 在简单的矩形不能满足需求的情况下,绘图环境提供了如下方法来绘制复杂的形状或路径. beginPath() : 开始绘制一个新路 ...
- WPF的二维绘图(一)——DrawingContext
DrawingContext比较类似WinForm中的Graphics 类,是基础的绘图对象,用于绘制各种图形,它主要API有如下几种: 绘图API 绘图API一般形为DrawingXXX系列,常用的 ...
- 讲解Canvas中的一些重要方法
Canvas所提供的各种方法根据功能来看大致可以分为几类: 第一是以drawXXX为主的绘制方法: 第二是以clipXXX为主的裁剪方法: 第三是以scale.skew.translate和rotat ...
- WPF技巧-Canvas转为位图
转自:http://www.cnblogs.com/tmywu/archive/2010/09/14/1825650.html 在WPF中我们可以将Canvas当成一种画布,将Canvas中的控件当成 ...
- 结合ItemsControl在Canvas中动态添加控件的最MVVM的方式
今天很开心的收获: ItemsControl 中 ItemsPanel的重定义和 ItemContainerStyle 以及 ItemTemplate 三者的巧妙结合,在后台代码不实例化任何控件的前提 ...
- HTML5 Canvas 2D绘图
为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/4851774. ...
- Wpf 之Canvas介绍
从这篇文章开始是对WPF中的界面如何布局做一个较简单的介绍,大家都知道:UI是做好一个软件很重要的因素,如果没有一个漂亮的UI,功能做的再好也无法吸引很多用户使用,而且没有漂亮的界面,那么普通用户会感 ...
随机推荐
- (网页)html5 canvas清空画布方法(转)
总结以下三种清空canvas画布的方式: 1. 最简单的方法:由于canvas每当高度或宽度被重设时,画布内容就会被清空,因此可以用以下方法清空: function clearCanvas() { v ...
- seajs的原理以及基本使用
seajs模块化开发 模块化开发,把整个文件分割成一个一个小文件. 使用方法 使用方法特别简单,首先在官网中下载sea.js,然后在页面中引入. index.html // 1.路径 // 2.回调 ...
- 云ERP真的靠谱吗?
现在几乎每个IT系统或项目都要跟云挂上钩,跟数码产品必与“智能”扯上关系一样,否则在外行甚至同行眼里就是“矮小搓”.ERP领域也悄然刮起了云端化.国内ERP产品也借此机会想弯道超车,通过云化来抢夺被S ...
- 监控mysql主从同步
1,昨天看到shell一道面试题,需求如下: 监控MySQL主从同步是否异常,如果异常,则发送短信或者邮件给管理员.提示:如果没主从同步环境,可以用下面文本放到文件里读取来模拟:阶段1:开发一个守护进 ...
- C#-类(九)
类的定义 类是描述具有相同特征与行为的事物的抽象,类内部包含类的特征和类的行为 类支持继承 类的定义是关键字class为标志 类的格式 访问标识符 class 类名 { 类主体 } 访问标识符:指定了 ...
- vs添加到附加进程调试(IIS页面调试)
有时候单元测试不是很方便,通过页面调试接口会更直观,也跟容易发现问题(尤其是在页面传参的时候),这时vs添加到附加进程的调试方式就显得尤为重要了! 步骤如下: 1.首先是通过IIS建立网站,(前提是要 ...
- 登录views
import osimport hashlibfrom django.shortcuts import render,render_to_response,redirect,HttpResponseR ...
- django加密解密api
分别给出了两个API,一个创造密码,一个验证密码正好满足需求.于是赶紧试试: 首先,引入模块: 1 >>> from django.contrib.auth.hashers impo ...
- nmap参数原理抓包分析
nmap参数原理抓包分析 实验环境: Nmap7.70 实验步骤: 1.主机发现 2.端口扫描 3.服务版本探测 一.主机发现 主机发现,如果主机活跃,扫描1000个常用的tcp端口 1.Nmap i ...
- LeetCode算法题-Invert Binary Tree
这是悦乐书的第194次更新,第199篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第55题(顺位题号是226).反转二叉树.例如: 输入: 4 / \ 2 7 / \ / ...