zoukankan      html  css  js  c++  java
  • [深入浅出Windows 10]实现饼图控件

    13.2 实现饼图控件

        上一小节讲解了动态生成折线图和区域图,对于简单的图形这样通过C#代码来生成的方式是很方便的,但是当我们的图表要实现更加复杂的逻辑的时候,这种动态生成的方式就显得力不从心了,那就需要利用控件封装的方式来实现更加强大的图表控件功能。这一小节将来讲解怎样去用封装控件的方式去实现图表,用一个饼图控件作为例子进行分析讲解。

    13.2.1 自定义饼图片形形状

        饼图其实就是把一个圆形分成若干块,每一块代表着一个类别的数据,可以把这每一块的图形看作是饼图片形形状。要实现一个饼图控件,首先需要做的就是要实现饼图片形形状,在第4章里面讲解了实现如何实现自定义的形状,饼图片形形状也可以通过这种方式来实现。饼图片形形状有一些重要的属性,如饼图半径Radius,内圆半径InnerRadius,旋转角度RotationAngle,片形角度WedgeAngle,点innerArcStartPoint,点innerArcEndPoint,点outerArcStartPoint和点outerArcEndPoint等,这些属性的含义如图13.5所示。要绘制出这个饼图片形形状需要计算出4个点的坐标(点innerArcStartPoint,点innerArcEndPoint,点outerArcStartPoint和点outerArcEndPoint),这4的点的坐标需要通过半径和角度相关的属性计算出来。计算出这4个点的坐标的坐标之后,然后通过这4个点创建一个Path图形,这个Path图形由两条直线和两条弧线组成,形成了一个饼图片形形状。通过这种方式不仅仅把这个饼图片形形状创建好了,连这个图形在整个饼图的位置也设置好了。代码如下所示。

    代码清单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代码如下所示:

    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代码如下:

    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上调用饼图控件。

    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方法初始化饼图。代码如下:

    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通用应用开发》

    源代码下载:http://vdisk.weibo.com/u/2186322691

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

    欢迎关注我的微博@WP林政   微信公众号:wp开发(号:wpkaifa)

    Windows10/WP技术交流群:284783431

  • 相关阅读:
    图像切割之(五)活动轮廓模型之Snake模型简单介绍
    拓扑排序的原理及事实上现
    理解class.forName()
    Java中StringBuilder的清空方法比較
    Java实现BASE64编解码
    KMP算法具体解释(贴链接)
    基于PCM2912a的USB声卡设计
    51. 腾讯面试题:一个二叉树,中序遍历,找一个节点的后一个节点
    Handler和HandlerThread
    Nuget-QRCode:jquery-qrcode
  • 原文地址:https://www.cnblogs.com/linzheng/p/5023612.html
Copyright © 2011-2022 走看看