WPF 玫瑰图绘制

1.前言:

一直在从事CS应用程序开发工作,随着工作需求,要对部分数据进行可视化展示,UI设计稿其中就有玫瑰图、雷达图的展示。

花了一个下午回溯原来丢掉的数学知识点。。特此将实现方法记录下。

2.效果图:

3.数据对象(RadarObj)

每个图都是由一个数据集合对象组成,从而绘制出对应的效果,对象最基本的属性要有某一维度的数值,用于在图像中展示。

    public class RadarObj
    {
        public string RColor { get; set; }
        public string Name { get; set; }
        public int DataValue { get; set; }
        public double DataRaidus { get; set; }

        /// <summary>
        /// Series stroke
        /// </summary>
        public Brush Stroke
        {
            get
            {
                return new SolidColorBrush((Color)ColorConverter.ConvertFromString(RColor)); ;
            }
        }

        /// <summary>
        /// Series Fill
        /// </summary>
        public Brush Fill
        {
            get
            {
                return new SolidColorBrush((Color)ColorConverter.ConvertFromString(RColor));
            }
        }
    }

  

4.绘制玫瑰图

核心逻辑在于,将玫瑰图先理解为一个饼图,然后根据数值计算出在饼图中占用的角度,以及对应的扇面半径,改动每个扇面的半径就成了玫瑰图

其中需要使用到几何的一些基本概念,工作这么多年,忘记了蛮多的,后面各种恶补。直接上代码吧。

<UserControl x:Class="Painter.NightingaleRose"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:Painter"
             mc:Ignorable="d"
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Canvas x:Name="CanvasPanel" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Gray" >
        </Canvas>
    </Grid>
</UserControl>
 public partial class NightingaleRose : UserControl
    {
        public NightingaleRose()
        {
            InitializeComponent();
        }

        #region Property

        /// <summary>
        /// 数据
        /// </summary>
        public List<RadarObj> Datas
        {
            get { return (List<RadarObj>)GetValue(DatasProperty); }
            set { SetValue(DatasProperty, value); }
        }

        /// <summary>
        /// 数值的总数
        /// </summary>
        public int Count
        {
            get
            {
                return Datas.Sum(i => i.DataValue);
            }
        }

        public static readonly DependencyProperty DatasProperty = DependencyProperty.Register("Datas", typeof(List<RadarObj>),
        typeof(NightingaleRose), new PropertyMetadata(new List<RadarObj>()));

        /// <summary>
        /// 当前绘制大区域
        /// </summary>
        private double MaxSize
        {
            get
            {
                var par = this.Parent as FrameworkElement;
                return par.ActualHeight > par.ActualWidth ? par.ActualWidth : par.ActualHeight;
            }
        }

        /// <summary>
        /// 停靠间距
        /// </summary>
        public int RoseMargin
        {
            get { return (int)GetValue(RoseMarginProperty); }
            set { SetValue(RoseMarginProperty, value); }
        }

        public static readonly DependencyProperty RoseMarginProperty = DependencyProperty.Register("RoseMargin", typeof(int),
        typeof(NightingaleRose), new PropertyMetadata(50));

        /// <summary>
        /// 空心内环半径
        /// </summary>
        public int RoseInsideMargin
        {
            get { return (int)GetValue(RoseInsideMarginProperty); }
            set { SetValue(RoseInsideMarginProperty, value); }
        }

        public static readonly DependencyProperty RoseInsideMarginProperty = DependencyProperty.Register("RoseInsideMargin", typeof(int),
        typeof(NightingaleRose), new PropertyMetadata(20));

        /// <summary>
        /// 显示值标注
        /// </summary>
        public bool ShowValuesLabel
        {
            get { return (bool)GetValue(ShowValuesLabelProperty); }
            set { SetValue(ShowValuesLabelProperty, value); }
        }

        public static readonly DependencyProperty ShowValuesLabelProperty = DependencyProperty.Register("ShowValuesLabel", typeof(bool),
        typeof(NightingaleRose), new PropertyMetadata(true));

        public static readonly DependencyProperty ShowToolTipProperty = DependencyProperty.Register("ShowToolTip", typeof(bool),
        typeof(NightingaleRose), new PropertyMetadata(false));

        /// <summary>
        /// 延伸线长
        /// </summary>
        public int LabelPathLength
        {
            get { return (int)GetValue(LabelPathLengthProperty); }
            set { SetValue(LabelPathLengthProperty, value); }
        }

        public static readonly DependencyProperty LabelPathLengthProperty = DependencyProperty.Register("LabelPathLength", typeof(int),
        typeof(NightingaleRose), new PropertyMetadata(50));

        #endregion Property

        #region Method

        /// <summary>
        /// 初始化数据
        /// </summary>
        private void initData()
        {
            CanvasPanel.Children.Clear();
            if (this.Datas != null && this.Datas.Count > 0)
            {
                this.CanvasPanel.Width = this.CanvasPanel.Height = 0;

                //求角度比例尺  (每个值占多大的角度  可以算到每一块图所占的角度)
                var angelScale = 360.00 / Datas.Sum(i => i.DataValue);

                //最大半径
                var maxRadius = (MaxSize / 2) - RoseMargin - (ShowValuesLabel ? LabelPathLength : 0);

                //半径比例尺  (值和比例尺相乘等于每一块图的半径)
                var radiusScale = maxRadius / Datas.Max(o => o.DataValue);

                //计算半径宽度值
                for (int i = 0; i < Datas.Count; i++)
                {
                    Datas[i].DataRaidus = Datas[i].DataValue * radiusScale;
                }
                //扇形角度初始化
                double angleSectorStart = 0;
                double angleSectorEnd = 0;

                //循环绘制扇形区域
                int scaleTimeSpan = 0;
                int pathTimespan = 0;
                int textTimeSpan = 0;
                for (int index = 0; index < Datas.Count; index++)
                {
                    //计算扇形角度
                    if (index == 0)
                    {
                        angleSectorStart = 0;
                        angleSectorEnd = Datas[index].DataValue * angelScale;
                    }
                    else if (index + 1 == Datas.Count)
                    {
                        angleSectorStart += Datas[index - 1].DataValue * angelScale;
                        angleSectorEnd = 360;
                    }
                    else
                    {
                        angleSectorStart += Datas[index - 1].DataValue * angelScale;
                        angleSectorEnd = angleSectorStart + Datas[index].DataValue * angelScale;
                    }
                    var currentRadius = RoseInsideMargin + Datas[index].DataRaidus;
                    //计算扇形点位,用于绘制PATH
                    Point ptOutSideStart = GetPoint(currentRadius, angleSectorStart * Math.PI / 180);
                    Point ptOutSideEnd = GetPoint(currentRadius, angleSectorEnd * Math.PI / 180);
                    Point ptInSideStart = GetPoint(RoseInsideMargin, angleSectorStart * Math.PI / 180);
                    Point ptInSideEnd = GetPoint(RoseInsideMargin, angleSectorEnd * Math.PI / 180);
                    if (string.IsNullOrEmpty(Datas[index].RColor) )
                        Datas[index].RColor = ChartColorPool.ColorStrings[index];
                    Path pthSector = new Path() { Fill = Datas[index].Fill };
                    //PATH数据格式 M0,100  L50,100 A50,50 0 0 1 100,50 L100,0 A100,100 0 0 0 0,100 Z
                    StringBuilder datastrb = new StringBuilder();

                    #region BuilderPathData

                    datastrb.Append("M");
                    datastrb.Append(ptOutSideStart.X.ToString());
                    datastrb.Append(",");
                    datastrb.Append(ptOutSideStart.Y.ToString());
                    datastrb.Append(" L");
                    datastrb.Append(ptInSideStart.X.ToString());
                    datastrb.Append(",");
                    datastrb.Append(ptInSideStart.Y.ToString());
                    datastrb.Append(" A");
                    datastrb.Append(RoseInsideMargin.ToString());
                    datastrb.Append(",");
                    datastrb.Append(RoseInsideMargin.ToString());
                    datastrb.Append(" 0 0 1 ");
                    datastrb.Append(ptInSideEnd.X.ToString());
                    datastrb.Append(",");
                    datastrb.Append(ptInSideEnd.Y.ToString());
                    datastrb.Append(" L");
                    datastrb.Append(ptOutSideEnd.X.ToString());
                    datastrb.Append(",");
                    datastrb.Append(ptOutSideEnd.Y.ToString());
                    datastrb.Append(" A");
                    datastrb.Append(currentRadius.ToString());
                    datastrb.Append(",");
                    datastrb.Append(currentRadius.ToString());
                    datastrb.Append(" 0 0 0 ");
                    datastrb.Append(ptOutSideStart.X.ToString());
                    datastrb.Append(",");
                    datastrb.Append(ptOutSideStart.Y.ToString());
                    datastrb.Append(" Z");

                    #endregion BuilderPathData
                    try
                    {
                        pthSector.Data = (Geometry)new GeometryConverter().ConvertFromString(datastrb.ToString());
                    }
                    catch (Exception exp)
                    { }
                    //设置扇形显示的动画
                    AnimationUtils.FloatElement(pthSector,1, 200, pathTimespan += 200);
                    AnimationUtils.ScaleRotateEasingAnimationShow(pthSector,0.1,1,1500, scaleTimeSpan += 200,null );

                    CanvasPanel.Children.Add(pthSector);
                    if (ShowValuesLabel)
                    {
                        //计算延伸线角度
                        double lbPathAngle = angleSectorStart + (angleSectorEnd - angleSectorStart) / 2;
                        //起点
                        Point ptLbStart = GetPoint(currentRadius, lbPathAngle * Math.PI / 180);
                        //终点
                        Point ptLbEnd = GetPoint(maxRadius + LabelPathLength, lbPathAngle * Math.PI / 180);
                        Path pthLb = new Path() { Stroke = Datas[index].Stroke, StrokeThickness = 1 };
                        pthLb.Data = (Geometry)new GeometryConverter().ConvertFromString(string.Format("M{0},{1} {2},{3}", ptLbStart.X.ToString(), ptLbStart.Y.ToString(), ptLbEnd.X.ToString(), ptLbEnd.Y.ToString()));
                        double dur = (textTimeSpan += 200) + 1500;
                        AnimationUtils.CtrlDoubleAnimation(pthLb, 1000, dur);
                        CanvasPanel.Children.Add(pthLb);
                        SetLabel(Datas[index], ptLbEnd, dur);
                    }
                }
                this.SizeChanged -= RadarControl_SizeChanged;
                this.SizeChanged += RadarControl_SizeChanged;
            }
        }

        public void InitalControl()
        {
        }

        /// <summary>
        /// 初始化数据
        /// </summary>
        /// <param name="dataobj"></param>
        public void SetData(object dataobj)
        {
            this.Datas = (dataobj) as List<RadarObj>;
            this.initData();
        }

        private void RadarControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            initData();
        }

        #endregion Method

        #region Compare

        /// <summary>
        /// 计算点位
        /// </summary>
        /// <param name="radius"></param>
        /// <param name="angel"></param>
        /// <returns></returns>
        private Point GetPoint(double radius, double angel)
        {
            return new Point(radius * Math.Cos(angel), radius * Math.Sin(angel));
        }

        private void SetLabel(RadarObj obj, Point location,double duration)
        {
            //计算偏移量
            bool x = true;
            bool y = true;

            if (location.X < 0)
                x = false;
            if (location.Y < 0)
                y = false;
            //obj.Name + " " +
            TextBlock txb = new TextBlock() { Text = " "+obj.Name+" "+Getbfb(Count.ToString(), obj.DataValue.ToString(), 2)+ " ", Foreground = this.Foreground, FontSize = this.FontSize };
            Size s = ControlSizeUtils.GetTextAreaSize(txb.Text, this.FontSize);
            CanvasPanel.Children.Add(txb);
            AnimationUtils.CtrlDoubleAnimation(txb, 1000, duration);
            if (location.X > -5 && location.X < 5)
                Canvas.SetLeft(txb, location.X - (s.Width / 2));
            else
                Canvas.SetLeft(txb, location.X + (x ? 0 : -(s.Width)));

            if (location.Y > -5 && location.Y < 5)
                Canvas.SetTop(txb, location.Y - (s.Height / 2));
            else
                Canvas.SetTop(txb, location.Y + (y ? 0 : -(s.Height)));
        }
        /// <summary>
        /// 计算百分比
        /// </summary>
        /// <param name="zs">总数</param>
        /// <param name="tj">当前项的值</param>
        /// <param name="num">保留的小数点几位</param>
        /// <returns></returns>
        public static string Getbfb(string zs, string tj, int num)
        {
            try
            {
                if (zs.Equals("0"))
                {
                    return "0";
                }
                double bfb = (double.Parse(tj) / double.Parse(zs)) * 100;
                if (bfb >= 100)
                {
                    bfb = 100;
                }

                return Math.Round(bfb, num).ToString() + "%";
            }
            catch (Exception ex)
            {
                return "0%";
            }
        }

        #endregion Compare

    }

 

调用示例

在主窗体grid里面丢个按钮,按钮点击之后执行下列代码

private void RoseClick(object sender, RoutedEventArgs e)
        {
            NightingaleRose rdc = new NightingaleRose();
            this.GrdMain.Children.Clear();
            this.GrdMain.Children.Add(rdc);
            rdc.SetData(CrData());
        }
        private List<RadarObj> CrData()
        {
            List<RadarObj> list = new List<RadarObj>();
            list.Add(new RadarObj() { Name="A", DataValue= rdm.Next(20,100) });
            list.Add(new RadarObj() { Name = "B", DataValue = rdm.Next(20, 100) });
            list.Add(new RadarObj() { Name = "C", DataValue = rdm.Next(20, 100) });
            list.Add(new RadarObj() { Name = "D", DataValue = rdm.Next(20, 100) });
            list.Add(new RadarObj() { Name = "E", DataValue = rdm.Next(20, 100) });
            list.Add(new RadarObj() { Name = "F", DataValue = rdm.Next(20, 100) });
            list.Add(new RadarObj() { Name = "F", DataValue = rdm.Next(20, 100) });
            return list;
        }

  

原文地址:https://www.cnblogs.com/Funk/p/11405434.html

时间: 2024-10-10 20:18:44

WPF 玫瑰图绘制的相关文章

WPF 雷达图

雷达图逻辑同玫瑰图差不多,不同的地方在于绘制雷达网络,也就是蜘蛛网这样的底图. 界面代码 <UserControl x:Class="Painter.RadarControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc=&qu

ggplot2——玫瑰图

更多内容请见:R.ggplot2.shiny 汇总 初始图样: library(ggplot2) dt = data.frame(A = c(2, 7, 4, 10, 1), B = c('B','A','C','D','E')) windowsFonts(myFont = windowsFont("楷体")) ## 绑定字体 p = ggplot(dt, aes(x = B, y = A, fill = B)) + geom_bar(stat = "identity&quo

WPF使用Canvas绘制可变矩形

1.问题以及解决办法 最近因为项目需要,需要实现一个位置校对的功能,大致的需求如下:有一个图片,有一些位置信息,但是位置信息可能和实际有些偏差,需要做简单调整,后面会对这张图片进行切割等,做些处理.(位置信息连接起来是一个个小矩形.) 解决以上问题的大致思路如下:使用canvas进行绘制,把图片作为canvas的背景,在canvas上绘制矩形,类似于qq截图一样,矩形框可以使用鼠标拖动调整大小.然后在记下修改后的位置,提供给后面切割图片使用.目前的关键问题就是实现类似qq截图那样可以拖动的矩形.

UML类图绘制

*UML类图绘制* ----------------- 1. **UML结构图与功能关系描述** 2. **UML行为图与功能关系描述** ************************ *UML结构图与功能关系描述* ======== 1. 继承关系 2. 聚集和组成 3. 依赖关系

地球与地图思维导图绘制

地理这门科目可以让我们更好更快的认识祖国的大好河山,以及各个国家的形状以及排列位置,那对于地理我们想要深入了解一下要怎样操作办呢?下面是分享的地球与地图总结的思维导图模板,以及绘制该思维导图的操作方法介绍,希望对大家有所帮助. **绘制工具:迅捷画图 绘制方法:** 1.选择在线网站进行编辑使用,搜索迅捷画图进入到该网站,在绘制之前可以对该网站进行熟悉,之后点击首页面中的立即体验就可以开始编辑使用. 2.这里讲述的是思维导图所以在跳转到新建文件页面之后选择思维导图进行新建使用就可以. 3.新建的

帆软报表(finereport)入门 3-图表——扇形图/等弧度的玫瑰图

扇形图/等弧度的玫瑰图,展示的是展示数据所占的比例,需要所有数据的和加起来为1. 下面利用一个实例说明玫瑰图的用法和设置起始角度和终止角度,操作如下: 1.配置一个内置数据集 新增一个等弧度的玫瑰图模板,修改图形的标题: 选择数据集为扇形图(内置数据集的标题),分类选择无,系列名和值分别使用名称和百分比 保存预览: 返回设计器,切换到样式下的系列,设置起始角度和终结角度: 再次保存文件,浏览器上查看图形效果,发现图形不是封闭的圆弧了 扇形图与等弧度的玫瑰图操作一致 原文地址:https://ww

绘制思维导图简单的方法是什么?思维导图绘制的作用又是什么

目前,思维导图在我们的日常生活中运用的十分广泛,它是一种能帮助我们学习和记忆的工具,市面上也出现了很多的思维工具和学习思维导图的公开课,可是更多的人只知道怎样将思维导图画出来,但当你问他如何将思维导图变成自己的思维习惯的时候很多人却不知道,这也就导致了很多人说思维导图是没有用处的原因之一,事实是这样的吗?下面我们一起来看看吧! 思维导图巧妙用处: 一.梳理思绪 在我们平时的工作中,思维导图有助于我们梳理思维,思维导图有助于我们梳理思维,慢慢的会建立起良好的逻辑思维和系统思维. 二.便于记忆 在我

MATLAB之心形图绘制

一.静态心形图绘制 (1)效果展示 (2)静态心形原始代码 1 clc; 2 clear all; 3 const=0; 4 % 均布三位坐标 5 x=-5:0.05:5; 6 y=-5:0.05:5; 7 z=-5:0.05:5; 8 [x,y,z]=meshgrid(x,y,z); % 绘制三位坐标点 9 % 心形函数 10 f=(x.^2 + (9/4)*y.^2 + z.^2 - 1).^3 - x.^2.*z.^3 - (9/80)*y.^2.*z.^3-const; 11 p=pat

使用ggplot2绘制风向风速玫瑰图

install.packages("ggplot2") library(ggplot2) # WindRose.R  http://stackoverflow.com/questions/17266780/wind-rose-with-ggplot-rrequire(ggplot2)require(RColorBrewer) plot.windrose <- function(data, spd, dir, spdres = 10, dirres = 30, spdmin = 0