• WPF实现的简单饼图


    简介

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

    Snap1Snap2

    Snap3Snap4

    用法:

    var sectorParts = new List<SectorPart>();
                sectorParts.Add(new SectorPart(90, Brushes.Red));
                sectorParts.Add(new SectorPart(30, Brushes.Green));
                sectorParts.Add(new SectorPart(120, Brushes.GreenYellow));
                sectorParts.Add(new SectorPart(70, Brushes.HotPink));
                sectorParts.Add(new SectorPart(50, Brushes.Yellow));
    
                var ringParts = new List<RingPart>();
                ringParts.Add(new RingPart(40, 20, 40, 20, Brushes.White));
    
                var shapes = PieChartDrawer.GetEllipsePieChartShapes(midPoint, 180, 90, 30, 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(0, radius);
    
                // 旋转角度所对应的矩阵
                var rotateMatrix = new Matrix();
                rotateMatrix.Rotate(180 + 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(0, radiusX);
    
                // 旋转角度所对应的矩阵
                var rotateMatrix = new Matrix();
                rotateMatrix.Rotate(180 + 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), 0, 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;
            }
        }
    }
  • 相关阅读:
    函数即变量
    装饰器模型
    团队配合指令
    三元指令
    虚实之门
    for的逻辑
    我写的第4个程序(日志最近行读取函数)
    还在用WebBrowser吗?你out了!
    关于打印机共享的注意事项——又被叫去修电脑了
    MVVM转换器Int2StringConverter基础类
  • 原文地址:https://www.cnblogs.com/yiyan127/p/WPF-Simple-PieChart.html
Copyright © 2020-2023  润新知