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;
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM