简介

前段时间帮一个同事的忙,利用WPF实现的一个简单饼图,仅能看饼图的比例,无文字查看功能。效果图如下:

用法:

var sectorParts = new List<SectorPart>();
sectorParts.Add(new SectorPart(, Brushes.Red));
sectorParts.Add(new SectorPart(, Brushes.Green));
sectorParts.Add(new SectorPart(, Brushes.GreenYellow));
sectorParts.Add(new SectorPart(, Brushes.HotPink));
sectorParts.Add(new SectorPart(, Brushes.Yellow)); var ringParts = new List<RingPart>();
ringParts.Add(new RingPart(, , , , Brushes.White)); var shapes = PieChartDrawer.GetEllipsePieChartShapes(midPoint, , , , sectorParts, ringParts);
foreach (var shape in shapes)
{
GrdPie.Children.Add(shape);
}

设置好饼图相关的信息,获取其各个组成部分,再将其添加到容器中。

原理

可以看出简介中的图由一系列中扇形和环组成,计算出扇形和环的形状就可以完成饼图的绘制了。

扇形

一个扇形由两边和一条弧组成,扇形的关键就在已知圆周的圆形和半径,以及扇形的边绕Y轴正向旋转的角度,如何求出扇形在圆周上的点。

在才开始我走了不少弯路,利用矩阵做了许多运算,结果都不对。后面灵机一动,发现不用那么麻烦。直接把圆平移至原点,计算出相应扇形在圆周上的点,再将其平移回来即可。代码如下:

/// <summary>
/// 获取圆周上指定角度的点坐标
/// </summary>
/// <param name="center">圆心</param>
/// <param name="radius">半径</param>
/// <param name="angle">角度,从0到360度,以正北方向为0度,顺时针旋转角度增加</param>
/// <returns>在圆周上旋转角度后的坐标</returns>
public static Point GetCirclePoint(this Point center, double radius, double angle)
{
// 圆心平移到原点后0度所对应的向量
var zeroAngleVector = new Vector(, radius); // 旋转角度所对应的矩阵
var rotateMatrix = new Matrix();
rotateMatrix.Rotate( + angle); // 因旋转的中心点在原点,最后需要平移到实际坐标上
return (zeroAngleVector * rotateMatrix) + center;
}

有了圆的计算方法,椭圆的也就水到渠成了,因为椭圆相当于圆的拉伸或者收缩。

/// <summary>
/// 获取椭圆上指定角度的点坐标
/// </summary>
/// <param name="center">椭圆两焦点的中点</param>
/// <param name="radiusX">长轴</param>
/// <param name="radiusY">短轴</param>
/// <param name="angle">角度,从0到360度,以正北方向为0度,顺时针旋转角度增加</param>
/// <returns>在椭圆上旋转角度后的坐标</returns>
public static Point GetEllipsePoint(this Point center, double radiusX, double radiusY, double angle)
{
// 半径为X半轴的圆圆心平移到原点后0度所对应的向量
var circleZeroAnglePoint = new Vector(, radiusX); // 旋转角度所对应的矩阵
var rotateMatrix = new Matrix();
rotateMatrix.Rotate( + angle); // 圆旋转角度后的坐标
var circlePoint = circleZeroAnglePoint * rotateMatrix; // 将圆拉伸椭圆后的坐标
var ellpseOrigin = new Point(circlePoint.X, circlePoint.Y * radiusY / radiusX); // 将坐标平移至实际坐标
return (Vector)ellpseOrigin + center;
}

环可由两个椭圆计算出来。较简单,就不多说了。

代码

扇形的定义

namespace PieChartTest
{
using System.Windows.Media; /// <summary>
/// 扇形
/// </summary>
public class SectorPart
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="spanAngle">跨越角度</param>
/// <param name="fillBrush">填充画刷</param>
public SectorPart(double spanAngle, Brush fillBrush)
{
this.SpanAngle = spanAngle;
this.FillBrush = fillBrush;
} /// <summary>
/// 跨越角度,单位为角度,取值范围为0到360
/// </summary>
public double SpanAngle { get; set; } /// <summary>
/// 填充画刷
/// </summary>
public Brush FillBrush { get; set; }
}
}

环的定义

namespace PieChartTest
{
using System.Windows.Media; /// <summary>
/// 环
/// </summary>
public class RingPart
{
/// <summary>
/// 构造函数,构造里外均为圆的圆环
/// </summary>
/// <param name="radius">里圆半径</param>
/// <param name="spanRadius">里外圆半径差</param>
/// <param name="fillBrush">填充画刷</param>
public RingPart(double radius, double spanRadius, Brush fillBrush)
{
this.RadiusX = radius;
this.RadiusY = radius; this.SpanRadiusX = spanRadius;
this.SpanRadiusY = spanRadius; this.FillBrush = fillBrush;
} /// <summary>
/// 构造函数
/// </summary>
/// <param name="radiusX">里边椭圆的长轴</param>
/// <param name="radiusY">里边椭圆的短轴</param>
/// <param name="spanRadiusX">里外椭圆的长轴差</param>
/// <param name="spanRadiusY">里外椭圆的短轴差</param>
/// <param name="fillBrush">填充画刷</param>
public RingPart(double radiusX, double radiusY, double spanRadiusX, double spanRadiusY, Brush fillBrush)
{
this.RadiusX = radiusX;
this.RadiusY = radiusY; this.SpanRadiusX = spanRadiusX;
this.SpanRadiusY = spanRadiusY; this.FillBrush = fillBrush;
} /// <summary>
/// 长轴
/// </summary>
public double RadiusX { get; set; } /// <summary>
/// 短轴
/// </summary>
public double RadiusY { get; set; } /// <summary>
/// 长轴跨越的距离
/// </summary>
public double SpanRadiusX { get; set; } /// <summary>
/// 短轴跨越的距离
/// </summary>
public double SpanRadiusY { get; set; } /// <summary>
/// 填充画刷
/// </summary>
public Brush FillBrush { get; set; }
}
}

饼图的绘制类

namespace PieChartTest
{
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes; /// <summary>
/// 饼图的绘制类
/// </summary>
public static class PieChartDrawer
{
/// <summary>
/// 获取饼图的形状列表
/// </summary>
/// <param name="center">圆心</param>
/// <param name="radius">圆的半径</param>
/// <param name="offsetAngle">偏移角度,即第一个扇形开始的角度</param>
/// <param name="sectorParts">扇形列表,扇形列表的SpanAngle之和应为360度</param>
/// <param name="ringParts">环列表</param>
/// <returns>构成饼图的形状列表</returns>
public static IEnumerable<Shape> GetPieChartShapes(Point center, double radius, double offsetAngle, IEnumerable<SectorPart> sectorParts, IEnumerable<RingPart> ringParts)
{
return GetEllipsePieChartShapes(center, radius, radius, offsetAngle, sectorParts, ringParts);
} /// <summary>
/// 获取椭圆形状的饼图的形状列表
/// </summary>
/// <param name="center">椭圆两个焦点的中点</param>
/// <param name="radiusX">椭圆的长轴</param>
/// <param name="radiusY">椭圆的短轴</param>
/// <param name="offsetAngle">偏移角度,即第一个扇形开始的角度</param>
/// <param name="sectorParts">扇形列表,扇形列表的SpanAngle之和应为360度</param>
/// <param name="ringParts">环列表</param>
/// <returns>构成饼图的形状列表</returns>
public static IEnumerable<Shape> GetEllipsePieChartShapes(Point center, double radiusX, double radiusY, double offsetAngle, IEnumerable<SectorPart> sectorParts, IEnumerable<RingPart> ringParts)
{
var shapes = new List<Shape>();
double startAngle = offsetAngle; foreach (var sectorPart in sectorParts)
{
// 扇形顺时针方向在椭圆上的第一个点
var firstPoint = center.GetEllipsePoint(radiusX, radiusY, startAngle); startAngle += sectorPart.SpanAngle; // 扇形顺时针方向在椭圆上的第二个点
var secondPoint = center.GetEllipsePoint(radiusX, radiusY, startAngle); var sectorFigure = new PathFigure { StartPoint = center }; // 添加中点到第一个点的弦
sectorFigure.Segments.Add(new LineSegment(firstPoint, false)); // 添加第一个点和第二个点之间的弧
sectorFigure.Segments.Add(
new ArcSegment(secondPoint, new Size(radiusX, radiusY), , false, SweepDirection.Clockwise, false));
var sectorGeometry = new PathGeometry();
sectorGeometry.Figures.Add(sectorFigure); var sectorPath = new Path { Data = sectorGeometry, Fill = sectorPart.FillBrush }; shapes.Add(sectorPath);
} var ringShapes = GetRingShapes(center, ringParts);
shapes.AddRange(ringShapes); return shapes;
} /// <summary>
/// 获取环的形状列表
/// </summary>
/// <param name="center">中心点,为圆表示圆形,为椭圆表示椭圆两个焦点的中点</param>
/// <param name="ringParts">环列表</param>
/// <returns>环的形状列表</returns>
private static IEnumerable<Shape> GetRingShapes(Point center, IEnumerable<RingPart> ringParts)
{
var shapes = new List<Shape>(); foreach (var ringPart in ringParts)
{
var innerEllipse = new EllipseGeometry(center, ringPart.RadiusX, ringPart.RadiusY);
var outterEllipse = new EllipseGeometry(center, ringPart.RadiusX + ringPart.SpanRadiusX, ringPart.RadiusY + ringPart.SpanRadiusY); // 根据里外椭圆求出圆环的形状
var ringGeometry = new CombinedGeometry(GeometryCombineMode.Xor, innerEllipse, outterEllipse);
var ringPath = new Path
{
Data = ringGeometry,
Fill = ringPart.FillBrush
}; shapes.Add(ringPath);
} return shapes;
}
}
}

WPF实现的简单饼图的更多相关文章

  1. WPF 3D:简单的Point3D和Vector3D动画创造一个旋转的正方体

    原文:WPF 3D:简单的Point3D和Vector3D动画创造一个旋转的正方体 运行结果: 事实上很简单,定义好一个正方体,处理好纹理.关于MeshGeometry3D的正确定义和纹理这里就不多讲 ...

  2. Wpf(Storyboard)动画简单实例

    原文:Wpf(Storyboard)动画简单实例 动画的三种变换方式 RotateTransform:旋转变换变化值:CenterX围绕转的圆心横坐标 CenterY纵坐标 Angle旋转角度(角度正 ...

  3. Prism for WPF 搭建一个简单的模块化开发框架 (一个节点)

    原文:Prism for WPF 搭建一个简单的模块化开发框架 (一个节点) 这里我就只贴图不贴代码了,看看这个节点之前的效果 觉得做的好的地方可以范之前的文章看看 有好的建议也可以说说   填充数据 ...

  4. Prism for WPF 搭建一个简单的模块化开发框架(六)隐藏菜单、导航

    原文:Prism for WPF 搭建一个简单的模块化开发框架(六)隐藏菜单.导航 这个实际上是在聊天之前做的,一起写了,也不分先后了 看一下效果图,上面是模块主导航,左侧是模块内菜单,现在加一下隐藏 ...

  5. Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务、WCF消息头添加安全验证Token

    原文:Prism for WPF 搭建一个简单的模块化开发框架(四)异步调用WCF服务.WCF消息头添加安全验证Token 为什么选择wcf?   因为好像wcf和wpf就是哥俩,,, 为什么选择异步 ...

  6. Prism for WPF 搭建一个简单的模块化开发框架(五)添加聊天、消息模块

    原文:Prism for WPF 搭建一个简单的模块化开发框架(五)添加聊天.消息模块 中秋节假期没事继续搞了搞 做了各聊天的模块,需要继续优化 第一步画页面 页面参考https://github.c ...

  7. Prism for WPF 搭建一个简单的模块化开发框架(三) 给TreeView加样式做成菜单

    原文:Prism for WPF 搭建一个简单的模块化开发框架(三) 给TreeView加样式做成菜单 昨天晚上把TreeView的样式做了一下,今天给TreeView绑了数据,实现了切换页面功能 上 ...

  8. Prism for WPF 搭建一个简单的模块化开发框架(二)

    原文:Prism for WPF 搭建一个简单的模块化开发框架(二) 今天又有时间了,再改改,加了一些控件全局的样式 样式代码 <ResourceDictionary xmlns="h ...

  9. Prism for WPF 搭建一个简单的模块化开发框架(一)

    原文:Prism for WPF 搭建一个简单的模块化开发框架(一) 最近闲来无事又想搞搞WPF..... 做个框架吧,可能又是半途而废....总是坚持不下来 不废话了, 先看一下工程结构 布局大概是 ...

随机推荐

  1. SoundPool

    如果应用程序经常播放密集.急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了.因为MediaPlayer存在如下缺点: 1) 延时时间较长,且资源占用率高. 2) 不支持 ...

  2. let、const和var的区别

    1.let是块级作用域,函数内部使用let定义后,对函数外部无影响. <!DOCTYPE html> <html lang="en"> <head&g ...

  3. BZOJ3729: Gty的游戏(伪ETT)

    题面 传送门 前置芝士 巴什博奕 \(Nim\)游戏的改版,我们现在每次最多只能取走\(k\)个石子,那么\(SG\)函数很容易写出来 \[SG(x)=mex_{i=1}^{\min(x,k)}SG( ...

  4. Get 和 Post 方法的选择和URL的设计

    原文链接:http://yifei.me/note/540 HTTP 中常用的方法有 GET/POST/PUT/DELETE 等,在设计API或者表单的时候我们需要选择合适的方法.一般有两种方案: 只 ...

  5. grunt 常用插件

    grunt-contrib-uglify:代码压缩 grunt-contrib-jshint:检查js拼写错误 csslint:检查css语法错误

  6. vue $emit 父组件与子组件之间的通信(父组件向子组件传参)

    1.首先新建一个子页面为 env.vue的文件(名字这里大家可以自取) 2.然后把子页面引入父页面,代码如图: import env from '@/components/common/env' ex ...

  7. webpack 中文文档

    webpack 最强最详细中文文档 https://doc.webpack-china.org/guides/getting-started/#- webpack多页应用架构系列 http://web ...

  8. jmeter结果分析(图形报表和聚合报告)

    采用Jmeter测试工具对web系统作的负载测试,得出的响应报表,数据比较难懂,现作一具体说明.以下是在一次具体负载测试中得出的具体数值,测试线程设置情况为:线程数:200,等待时间(ramp-up) ...

  9. Myeclipse修改项目名称发布后web Context root名称无法修改

    选中项目,右键--->Properties--->在搜索框搜索:deployment as  然后点击显示出的搜索项.修改右侧视图的Web Context Root名称即可. 如图:

  10. SAE实践——用SVN命令行同步/提交代码

    1. 同步应用到本地 注:首次使用svn需要输入安全认证密码 在终端输入以下命令 svn co https://svn.sinaapp.com/nyhello nyhello替换为自己的应用名称. 用 ...