[深刻淺出Windows 10]實現餅圖控件

13.2 實現餅圖控件

    上一小節講解了動態生成折線圖和區域圖,對於簡單的圖形這樣經過C#代碼來生成的方式是很方便的,可是當咱們的圖表要實現更加複雜的邏輯的時候,這種動態生成的方式就顯得力不從心了,那就須要利用控件封裝的方式來實現更增強大的圖表控件功能。這一小節未來講解怎樣去用封裝控件的方式去實現圖表,用一個餅圖控件做爲例子進行分析講解。html

13.2.1 自定義餅圖片形形狀

    餅圖其實就是把一個圓形分紅若干塊,每一塊表明着一個類別的數據,能夠把這每一塊的圖形看做是餅圖片形形狀。要實現一個餅圖控件,首先須要作的就是要實現餅圖片形形狀,在第4章裏面講解了實現如何實現自定義的形狀,餅圖片形形狀也能夠經過這種方式來實現。餅圖片形形狀有一些重要的屬性,如餅圖半徑Radius,內圓半徑InnerRadius,旋轉角度RotationAngle,片形角度WedgeAngle,點innerArcStartPoint,點innerArcEndPoint,點outerArcStartPoint和點outerArcEndPoint等,這些屬性的含義如圖13.5所示。要繪製出這個餅圖片形形狀須要計算出4個點的座標(點innerArcStartPoint,點innerArcEndPoint,點outerArcStartPoint和點outerArcEndPoint),這4的點的座標須要經過半徑和角度相關的屬性計算出來。計算出這4個點的座標的座標以後,而後經過這4個點建立一個Path圖形,這個Path圖形由兩條直線和兩條弧線組成,造成了一個餅圖片形形狀。經過這種方式不單單把這個餅圖片形形狀建立好了,連這個圖形在整個餅圖的位置也設置好了。代碼以下所示。express

代碼清單5-2餅圖圖表(源代碼:第5章\Examples_5_2)微信

PiePiece.cs文件代碼:自定義的餅圖片形形狀
------------------------------------------------------------------------------------------------------------------
    using System;
    using Windows.Foundation;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Shapes;
    namespace PieChartDemo
    {
        /// <summary>
        /// 自定義的餅圖片形形狀
        /// </summary>
        class PiePiece : Path
        {
            #region 依賴屬性
            // 註冊半徑屬性
            public static readonly DependencyProperty RadiusProperty =
                DependencyProperty.Register("RadiusProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));
            // 餅圖半徑
            public double Radius
            {
                get { return (double)GetValue(RadiusProperty); }
                set { SetValue(RadiusProperty, value); }
            }
            // 註冊餅圖片形點擊後推出的距離
            public static readonly DependencyProperty PushOutProperty =
                DependencyProperty.Register("PushOutProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));

            // 距離餅圖中心的距離
            public double PushOut
            {
                get { return (double)GetValue(PushOutProperty); }
                set { SetValue(PushOutProperty, value); }
            }
            // 註冊餅圖內圓半徑屬性
            public static readonly DependencyProperty InnerRadiusProperty =
                DependencyProperty.Register("InnerRadiusProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));

            // 餅圖內圓半徑
            public double InnerRadius
            {
                get { return (double)GetValue(InnerRadiusProperty); }
                set { SetValue(InnerRadiusProperty, value); }
            }
            // 註冊餅圖片形的角度屬性
            public static readonly DependencyProperty WedgeAngleProperty =
                DependencyProperty.Register("WedgeAngleProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));

            // 餅圖片形的角度
            public double WedgeAngle
            {
                get { return (double)GetValue(WedgeAngleProperty); }
                set
                {
                    SetValue(WedgeAngleProperty, value);
                    this.Percentage = (value / 360.0);

                }
            }
            // 註冊餅圖片形旋轉角度的屬性
            public static readonly DependencyProperty RotationAngleProperty =
                DependencyProperty.Register("RotationAngleProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));

            // 旋轉的角度
            public double RotationAngle
            {
                get { return (double)GetValue(RotationAngleProperty); }
                set { SetValue(RotationAngleProperty, value); }
            }
            // 註冊中心點的X座標屬性
            public static readonly DependencyProperty CentreXProperty =
                DependencyProperty.Register("CentreXProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));

            // 中心點的X座標
            public double CentreX
            {
                get { return (double)GetValue(CentreXProperty); }
                set { SetValue(CentreXProperty, value); }
            }
            // 註冊中心點的Y座標屬性
            public static readonly DependencyProperty CentreYProperty =
                DependencyProperty.Register("CentreYProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));

            // 中心點的Y座標
            public double CentreY
            {
                get { return (double)GetValue(CentreYProperty); }
                set { SetValue(CentreYProperty, value); }
            }
            // 註冊該餅圖片形所佔餅圖的百分比屬性
            public static readonly DependencyProperty PercentageProperty =
                DependencyProperty.Register("PercentageProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));

            // 餅圖片形所佔餅圖的百分比
            public double Percentage
            {
                get { return (double)GetValue(PercentageProperty); }
                private set { SetValue(PercentageProperty, value); }
            }

            // 註冊該餅圖片形所表明的數值屬性
            public static readonly DependencyProperty PieceValueProperty =
                DependencyProperty.Register("PieceValueProperty", typeof(double), typeof(PiePiece),
                new PropertyMetadata(0.0));

            // 該餅圖片形所表明的數值
            public double PieceValue
            {
                get { return (double)GetValue(PieceValueProperty); }
                set { SetValue(PieceValueProperty, value); }
            }
            #endregion
            public PiePiece()
            {
                CreatePathData(0, 0);
            }

            private double lastWidth = 0;
            private double lastHeight = 0;
            private PathFigure figure;
            // 在圖形中添加一個點
            private void AddPoint(double x, double y)
            {
                LineSegment segment = new LineSegment();
                segment.Point = new Point(x + 0.5 * StrokeThickness,
                    y + 0.5 * StrokeThickness);
                figure.Segments.Add(segment);
            }
            // 在圖形中添加一條線段
            private void AddLine(Point point)
            {
                LineSegment segment = new LineSegment();
                segment.Point = point;
                figure.Segments.Add(segment);
            }
            // 在圖形中添加一個圓弧
            private void AddArc(Point point, Size size, bool largeArc, SweepDirection sweepDirection)
            {
                ArcSegment segment = new ArcSegment();
                segment.Point = point;
                segment.Size = size;
                segment.IsLargeArc = largeArc;
                segment.SweepDirection = sweepDirection;
                figure.Segments.Add(segment);
            }

            private void CreatePathData(double width, double height)
            {
                // 用於退出佈局的循環邏輯
                if (lastWidth == width && lastHeight == height) return;
                lastWidth = width;
                lastHeight = height;

                Point startPoint = new Point(CentreX, CentreY);
                // 計算餅圖片形內圓弧的開始點
                Point innerArcStartPoint = ComputeCartesianCoordinate(RotationAngle, InnerRadius);
                // 根據中心點來校訂座標的位置
                innerArcStartPoint = Offset(innerArcStartPoint,CentreX, CentreY);
                // 計算餅圖片形內圓弧的結束點
                Point innerArcEndPoint = ComputeCartesianCoordinate(RotationAngle + WedgeAngle, InnerRadius);
                innerArcEndPoint = Offset(innerArcEndPoint, CentreX, CentreY);
                // 計算餅圖片形外圓弧的開始點
                Point outerArcStartPoint = ComputeCartesianCoordinate(RotationAngle, Radius);
                outerArcStartPoint = Offset(outerArcStartPoint, CentreX, CentreY);
                // 計算餅圖片形外圓弧的結束點
                Point outerArcEndPoint = ComputeCartesianCoordinate(RotationAngle + WedgeAngle, Radius);
                outerArcEndPoint = Offset(outerArcEndPoint, CentreX, CentreY);
                // 判斷餅圖片形的角度是否大於180度
                bool largeArc = WedgeAngle > 180.0;
                // 把扇面餅圖往偏離中心點推出一部分
                if (PushOut > 0)
                {
                    Point offset = ComputeCartesianCoordinate(RotationAngle + WedgeAngle / 2, PushOut);

                    // 根據偏移量來從新設置圓弧的座標
                    innerArcStartPoint = Offset(innerArcStartPoint,offset.X, offset.Y);
                    innerArcEndPoint = Offset(innerArcEndPoint,offset.X, offset.Y);
                    outerArcStartPoint = Offset(outerArcStartPoint,offset.X, offset.Y);
                    outerArcEndPoint = Offset(outerArcEndPoint,offset.X, offset.Y);
                }
                // 外圓的大小
                Size outerArcSize = new Size(Radius, Radius);
                // 內圓的大小
                Size innerArcSize = new Size(InnerRadius, InnerRadius);
                var geometry = new PathGeometry();
                figure = new PathFigure();
                // 從內圓開始座標開始畫一個閉合的扇形圖形
                figure.StartPoint = innerArcStartPoint;
                AddLine(outerArcStartPoint);
                AddArc(outerArcEndPoint, outerArcSize, largeArc, SweepDirection.Clockwise);
                AddLine(innerArcEndPoint);
                AddArc(innerArcStartPoint, innerArcSize, largeArc, SweepDirection.Counterclockwise);
                figure.IsClosed = true;
                geometry.Figures.Add(figure);
                this.Data = geometry;
            }

            protected override Size MeasureOverride(Size availableSize)
            {
                return availableSize;
            }

            protected override Size ArrangeOverride(Size finalSize)
            {
                CreatePathData(finalSize.Width, finalSize.Height);
                return finalSize;
            }
            //把點進行偏移轉換
            private Point Offset(Point point, double offsetX, double offsetY)
            {
                point.X += offsetX;
                point.Y += offsetY;
                return point;
            }
            /// <summary>
            /// 根據角度和半徑來計算出圓弧上的點的座標
            /// </summary>
            /// <param name="angle">角度</param>
            /// <param name="radius">半徑</param>
            /// <returns>圓弧上的點座標</returns>
            private Point ComputeCartesianCoordinate(double angle, double radius)
            {
                // 轉換成弧度單位
                double angleRad = (Math.PI / 180.0) * (angle - 90);
                double x = radius * Math.Cos(angleRad);
                double y = radius * Math.Sin(angleRad);
                return new Point(x, y);
            }
        }
    }

13.2.2 封裝餅圖控件

    建立好了PiePiece形狀以後,下面就要開始建立利用PiePiece形狀來建立餅圖控件了。建立餅圖控件是經過UserControl控件來實現,UserControl控件的XAML代碼裏面只有一個Grid面板,是用來加載PiePiece形狀來組成餅圖。XAML代碼以下所示:app

PiePlotter.xaml文件代碼
------------------------------------------------------------------------------------------------------------------
    <UserControl x:Class="PieChartDemo.PiePlotter"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        FontFamily="{StaticResource PhoneFontFamilyNormal}"
        FontSize="{StaticResource PhoneFontSizeNormal}"
        Foreground="{StaticResource PhoneForegroundBrush}"
        d:DesignHeight="480" d:DesignWidth="480" >
        <Grid x:Name="LayoutRoot"></Grid>
    </UserControl>

    在實現餅圖以前,須要知道餅圖裏面的數據集合的,還須要用一個實體類PieDataItem來表示餅圖的數據項,有兩個屬性一個是表示圖形的數值Value屬性,另一個是表示餅圖片形塊的顏色Brush屬性。PieDataItem代碼以下:ide

PieDataItem.cs文件代碼
------------------------------------------------------------------------------------------------------------------
    using Windows.UI.Xaml.Media;
    namespace PieChartDemo
    {
        /// <summary>
        /// 餅圖數據實體
        /// </summary>
        public class PieDataItem
        {
            public double Value { get; set; }
            public SolidColorBrush Brush { get; set; }
        }
    }

    下面來實現餅圖控件加載的邏輯,在餅圖控件裏面還須要自定義一些相關的屬性,用來傳遞相關的參數。屬性HoleSize表示餅圖內圓的大小,按照比例來計算;屬性PieWidth表示餅圖的寬度。餅圖的數據集合是經過控件的數據上下文屬性DataContext屬性來傳遞,在初始化餅圖的時候須要把DataContext的數據讀取出來而後再建立PiePiece圖形。每一個PiePiece圖形都添加了Tap事件,用來實現當用戶點擊餅圖的時候,相應的某一塊回往外推出去。代碼以下所示:佈局

PiePlotter.xaml.cs文件代碼
------------------------------------------------------------------------------------------------------------------
    using System.Collections.Generic;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Input;

    namespace PieChartDemo
    {
        /// <summary>
        /// 餅圖控件
        /// </summary>
        public partial class PiePlotter : UserControl
        {

            #region dependency properties
            // 註冊內圓大小屬性
            public static readonly DependencyProperty HoleSizeProperty = 
                           DependencyProperty.Register("HoleSize", typeof(double), typeof(PiePlotter), new PropertyMetadata(0.0));
            // 內圓的大小,按照比例來計算
            public double HoleSize
            {
                get { return (double)GetValue(HoleSizeProperty); }
                set
                {
                    SetValue(HoleSizeProperty, value);
                }
            }
            // 註冊餅圖寬度屬性
            public static readonly DependencyProperty PieWidthProperty =
                   DependencyProperty.Register("PieWidth", typeof(double), typeof(PiePlotter), new PropertyMetadata(0.0));

            // 餅圖寬度
            public double PieWidth
            {
                get { return (double)GetValue(PieWidthProperty); }
                set { SetValue(PieWidthProperty, value); }
            }

            #endregion
            // 餅圖的片形PiePiece的集合
            private List<PiePiece> piePieces = new List<PiePiece>();
            // 選中的當前餅圖的數據項
            private PieDataItem CurrentItem;

            public PiePlotter()
            {
                InitializeComponent();
            }

            // 初始化展現餅圖的方法
            public void ShowPie()
            {
                // 獲取控件的數據上下文,轉化成數據集合
                List<PieDataItem> myCollectionView = (List<PieDataItem>)this.DataContext;
                if (myCollectionView == null)
                    return;
                // 半徑的大小
                double halfWidth = PieWidth / 2; 
                // 內圓半徑大小
                double innerRadius = halfWidth * HoleSize;
                // 計算圖表數據的總和
                double total = 0;
                foreach (PieDataItem item in myCollectionView)
                {
                    total += item.Value;
                }
                // 經過PiePiece構建餅圖
                LayoutRoot.Children.Clear();
                piePieces.Clear();
                double accumulativeAngle = 0;
                foreach (PieDataItem item in myCollectionView)
                {
                    bool selectedItem = item == CurrentItem;
                    double wedgeAngle = item.Value * 360 / total;
                    // 根據數據來建立餅圖的每一塊圖形
                    PiePiece piece = new PiePiece()
                    {
                        Radius = halfWidth,
                        InnerRadius = innerRadius,
                        CentreX = halfWidth,
                        CentreY = halfWidth,
                        PushOut = (selectedItem ? 10.0 : 0),
                        WedgeAngle = wedgeAngle,
                        PieceValue = item.Value,
                        RotationAngle = accumulativeAngle,
                        Fill = item.Brush,
                        Tag = item
                    };
                    // 添加餅圖片形的點擊事件
                    piece.Tapped += piece_Tapped;
                    piePieces.Add(piece);
                    LayoutRoot.Children.Add(piece);
                    accumulativeAngle += wedgeAngle;
                }
            }
        
            void piece_Tapped(object sender, TappedRoutedEventArgs e)
            {
                PiePiece piePiece = sender as PiePiece;
                CurrentItem = piePiece.Tag as PieDataItem;
                ShowPie();
            }
        }
    }

    在調用餅圖控件時須要引用控件所屬的空間,而後在XAML上調用餅圖控件。this

MainPage.xaml文件主要代碼
------------------------------------------------------------------------------------------------------------------
    ……省略若干代碼
xmlns:local="using:PieChartDemo"
    ……省略若干代碼
    <local:PiePlotter x:Name="piePlotter" Width="400" Height="400" PieWidth="400" HoleSize="0.2"></local:PiePlotter>

    在C#代碼裏面,對餅圖的DataContext屬性進行賦值餅圖的數據集合,而後調用ShowPie方法初始化餅圖。代碼以下:spa

MainPage.xaml.cs文件主要代碼
------------------------------------------------------------------------------------------------------------------
    public MainPage()
    {
        InitializeComponent();
        List<PieDataItem> datas=new List<PieDataItem>();
        datas.Add(new PieDataItem{ Value=30, Brush = new SolidColorBrush(Colors.Red)});
        datas.Add(new PieDataItem { Value = 40, Brush = new SolidColorBrush(Colors.Orange) });
        datas.Add(new PieDataItem { Value = 50, Brush = new SolidColorBrush(Colors.Blue) });
        datas.Add(new PieDataItem { Value = 30, Brush = new SolidColorBrush(Colors.LightGray) });
        datas.Add(new PieDataItem { Value = 20, Brush = new SolidColorBrush(Colors.Purple) });
        datas.Add(new PieDataItem { Value = 40, Brush = new SolidColorBrush(Colors.Green) });
        piePlotter.DataContext = datas;
        piePlotter.ShowPie();
    }

本文來源於《深刻淺出Windows 10通用應用開發》code

源代碼下載:http://vdisk.weibo.com/u/2186322691orm

目錄:http://www.cnblogs.com/linzheng/p/5021428.html

歡迎關注個人微博@WP林政   微信公衆號:wp開發(號:wpkaifa)

Windows10/WP技術交流羣:284783431

相關文章
相關標籤/搜索