[深入浅出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

时间: 2024-10-09 04:47:32

[深入浅出Windows 10]实现饼图控件的相关文章

[深入浅出Windows 10]分屏控件(SplitView)

4.18 分屏控件(SplitView) 分屏控件(SplitView)是Windows 10新增的控件类型,也是Windows 10通用应用程序主推的交互控件,通常和一个汉堡按钮搭配作为一种抽屉式菜单来进行呈现.控件的XAML语法如下: <SplitView> <SplitView.Pane > ……菜单面板的内容 </SplitView.Pane > ……主体内容 </SplitView> SplitView控件主要由两部分组成,一部分是菜单的面板,另一

[深入浅出Windows 10]布局原理

5.2 布局原理 很多时候在编写程序界面的时候都会忽略了应用布局的重要性,仅仅只是把布局看作是对UI元素的排列,只要能实现布局的效果就可以了,但是在实际的产品开发中这是远远不够的,你可能面临要实现的布局效果要比常规布局更加复杂,这就需要对布局的技术知识有深入的理解和掌握才能够实现.要实现一个布局的效果,可能会有很多总布局方案,我们该怎么去选择实现的方法?如果要实现的一个布局效果是比较复杂的,我们该怎么去对这种布局规律进行封装?要解决这些问题,首要的问题就是需要我们对程序的布局原理有着深入的理解.

[深入浅出Windows 10]应用实战:Bing在线壁纸

本章介绍一个使用Bing搜索引擎背景图接口实现的一个应用——Bing在线壁纸,讲解如何使用网络的接口来实现一个壁纸下载,壁纸列表展示和网络请求封装的内容.通过该例子我们可以学习到如何使用网络编程的知识和开放的接口来实现一些有趣的应用程序,如何在项目中封装相关的功能模块,从而进一步地了解Windows 10应用程序开发的过程. 23.1 应用实现的功能 微软的Bing搜索引擎每天都会挑选出一张图片作为今天的主题,并且会对图片的含义或者图片所代表的意思进行一番解说,每天的图片和故事都不一样,并且有时

深入Windows窗体原理及控件重绘技巧

之前有学MFC的同学告诉我觉得Windows的控件重绘难以理解,就算重绘成功了还是有些地方不明白,我觉得可能很多人都有这样的问题,在这里我从Windows窗体的最基本原理来讲解,如果你有类似的疑惑希望这篇文章可以帮你解惑. 1.Windows窗体原理 首先,如果看过Win32 SDK编程的都知道Windows的三大核心系统:负责窗口对象产生和消息分发的USER模块,负责图像显示绘制的GDI模块,负责内存.进程.IO管理的KERNEL模块.试想象一下如何在一个像素阵列上产生窗口对象,其实就是使用G

Android PieChart 饼图控件

今天写一个饼图自定义View的文章.由于公司的项目需要用到饼图,UI给的设计图和自己找的一个饼图框架的标题位置不符,所以就自己画了一个. 1,使用预览 PieChart mChart mChart = (PieChart) findViewById(R.id.pieChar); mChart = (PieChart) findViewById(R.id.pieChar); String[] titles = new String[] {"钱包余额","金钱袋资产",

Windows Phone 7 LongListSelector控件实现分类列表和字母索引

在wp7手机里面的联系人列表和程序里面里面我们可以看到一个根据字母索引来定位联系人或者应用程序的控件,那么这个控件就是LongListSelector控件了. LongListSelector是一种比ListBox更加强大的列表控件,你可以根据你列表的信息来分类排列,根据类别快速定位到你选中的类别的列表下,在数据量很大的情况下这种分类的优势很明显.LongListSelector可以自定义列表头,列表尾.类表头.列别尾等的样式和数据,可以实现各种个性化的列表样式和不同的数据的展现方式.Windo

Windows 8.1 新控件和功能:

将 XAML 树呈现为位图: 适用于 Windows 8.1 的 Windows 运行时为 Windows.UI.Xaml.Media.Imaging 命名空间添加了一种新类型:RenderTargetBitmap. 此类型提供了两个关键方法: RenderTargetBitmap.RenderAsync,用于提取 XAML 可视化树 并为其创建位图表示. 注意  此操作采用异步方式,将给定的 XAML 元素树呈现为位图. 此方法与屏幕刷新不同步,不能保证精确的帧计时,因此该位图可能在假定捕获时

[深入浅出Windows 10]模拟实现微信的彩蛋动画

9.7 模拟实现微信的彩蛋动画 大家在玩微信的时候有没有发现节日的时候发一些节日问候语句如“情人节快乐”,这时候会出现很多爱心形状从屏幕上面飘落下来,我们这小节就是要模拟实现这样的一种动画效果.可能微信里面实现的动画效果都是采用固定的小图片来最为动画的对象,但是我们这小节要对该动画效果增加一些改进,也就是要实现的彩蛋动画的针对的图形形状是动态随机生成的,所以看到从屏幕上面飘落的图形都是形状不太一样的.下面来看一下如何实现星星飘落动画. 9.7.1 实现的思路 首先,我们来分析一下星星飘落动画的实

[深入浅出WIndows 10]网络编程之HttpClient类

14.2 网络编程之HttpClient类 除了可以使用HttpWebRequest类来实现HTTP网络请求之外,还可以使用HttpClient类来实现.对于基本的请求操作,HttpClient类提供了一个简单的接口来处理最常见的任务,并为身份验证提供了适用于大多数方案的合理的默认设置.对于较为复杂的 HTTP 操作,更多的功能包括:执行常见操作(DELETE.GET.PUT 和 POST)的方法:获取.设置和删除 Cookie 的功能:支持常见的身份验证设置和模式:异步方法上提供的 HTTP