WPF自定义实现雷达图控件的示例详解

 更新时间:2023年08月28日 11:12:42   作者:流水若冰  
雷达图用于表示不同内容的占比关系,在项目中有广泛的应用,但是目前未曾有封装良好的雷达图控件,所以本文分享了如何封装一个通用的雷达图控件,希望对大家有所帮助

效果图

源码地址:https://gitee.com/LiuShuiRuoBing/code_blog

雷达图用于表示不同内容的占比关系,在项目中有广泛的应用,但是目前未曾有封装良好的雷达图控件,鉴于最近项目的使用,于是想要封装一个通用的雷达图控件,便于日后的扩展使用。

首先雷达图的绘制大概分为雷达图的图层、雷达图的射线、雷达图的有效值区域、雷达图有效值在射线上的点、标题文字等几个部分,封装的初衷就是想要每个部分足够灵活,像图层层数、颜色、射线条数、颜色、粗细文字颜色等属性都开发出来,允许使用者自行设置。这就需要给雷达图控件定义各种不同的依赖属性。

使用自定义控件来实现雷达图的封装,首先创建UserControl,并使用Grid来最为父级容器控件,这里之所以选用Grid而不是Canvas,是因为Canvas上的控件属于绝对定位,而在Grid中更多的是相对定位,查看很多原生空间的样式或者模板会看到更多的使用布局控件作为底层父级容器控件。

关于控件的自定义依赖属性的知识点,在此不再赘述,说几个需要注意的地方,

  • 当要创建可以Binding的集合时,需要定义IEnumerable的类型,注册时PropertyMetadata要使用FrameworkPropertyMetadata
  • 当需要修改属性值,UI会随之更新时,要在注册时,注入PropertyChangedCallback函数
public IEnumerable<double> LayersPercentList
        {
            get { return (IEnumerable<double>)GetValue(LayersPercentListProperty); }
            set { SetValue(LayersPercentListProperty, value); }
        }
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LayersPercentListProperty =
            DependencyProperty.Register("LayersPercentList", typeof(IEnumerable<double>), typeof(RadarMapUserControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnChangedToRefreshRadarMap)));

1.雷达图层的绘制

雷达图层即雷达图有几层,每一层会有不同的占比,每一层的颜色也不相同,因此分别定义了下面的依赖属性,来对雷达图层进行设置,详细解释请看代码

  • Layers 雷达图层数
  • LayersPercentList 每一层的占比
  • LayerStrokeThickness 每层边框的粗细
  • LayerStroke 每层的边框颜色
  • InnerColor 雷达图从内到外的渐变色,内部颜色
  • OutColor 雷达图从内到外的渐变色,外部颜色

2.雷达图射线

  • Radials 雷达图的射线数
  • Radius 雷达图半径
  • RadialBrush 射线颜色
  • RadialThickness 射线粗细

3.雷达图上的点

  • Values ,普通点的集合,可以绑定
  • ValueRadius 普通点的绘制半径
  • ValueBrush 普通点的填充色
  • HeightLightValues, 希望高亮点的集合
  • HeighLightRadius,高亮点的半径
  • HeighLightBrush,高亮点的

4.雷达图上的值区域

  • ValuesAreaFill 值区域的填充色
  • ValuesAreaStroke 值区域的边框色

5.雷达图的射线标题文字

  • ShowTitle 是否显示标题文字
  • TitleForground 文字的前景色
  • TitleFontSize 文字的字号
  • TitleFontWeight 字重
  • Titles ,集合, 每条射线要现实的文字

RadarMapUserControl.xaml 完整代码

<UserControl x:Class="MT.CustomUserControl.Views.UserControls.RadarMapUserControl"
             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:MT.CustomUserControl.Views.UserControls"
             mc:Ignorable="d" 
             d:Background="White"    
             SizeChanged="RadarMapUserControl_SizeChanged"
             >
    <!--雷达图-->
    <Grid x:Name="Grid_RadarMap" HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="300" MinHeight="300" Margin="200"  SizeChanged="RadarMapUserControl_SizeChanged" />
</UserControl>

RadarMapUserControl.cs 完整代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace MT.CustomUserControl.Views.UserControls
{
    /// <summary>
    /// QSCC_RadarMapUserControl.xaml 的交互逻辑
    /// </summary>
    public partial class RadarMapUserControl : UserControl
    {
        #region 私有属性,用于绘图 
        /// <summary>
        /// 每个扇区的角度
        /// </summary>
        private double Angle { set; get; }
        /// <summary>
        /// 用于绘制雷达图的层数的多边形
        /// </summary>
        private List<Polygon> RadarMapLayersPolygon = new List<Polygon>();
        /// <summary>
        /// 用于绘制雷达图的射线
        /// </summary>
        private List<Polyline> RadarMapRadialsPolyline = new List<Polyline>();
        /// <summary>
        /// 用于绘制雷达图射线上实际值的圆点,使用多边形绘制,以实际值为圆心扩展多变形
        /// </summary>
        private List<Polygon> RadarMapRadialsValuesPolygons = new List<Polygon>();
        /// <summary>
        /// 所有的雷达图的多变形
        /// </summary>
        private Polygon RadarMapRadialsValuesPolygon = new Polygon();
        #endregion
        #region 雷达图图层
        /// <summary>
        /// 雷达图的层数
        /// </summary>
        public int Layers
        {
            get { return (int)GetValue(LayersProperty); }
            set
            {
                if (value < 1)
                    value = 1;
                SetValue(LayersProperty, value);
            }
        }
        // Using a DependencyProperty as the backing store for Layers.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LayersProperty =
            DependencyProperty.Register("Layers", typeof(int), typeof(RadarMapUserControl), new PropertyMetadata(4, new PropertyChangedCallback(OnCurrentLayersChanged)));
        private static void OnCurrentLayersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            bool needRefresh = false;
            if (userControl.RadarMapLayersPolygon.Count > userControl.Layers)
            {
                int nCnt = userControl.RadarMapLayersPolygon.Count - userControl.Layers;
                userControl.RadarMapLayersPolygon.RemoveRange(userControl.RadarMapLayersPolygon.Count - 1 - nCnt, nCnt);
                needRefresh = true;
            }
            else if (userControl.RadarMapLayersPolygon.Count < userControl.Layers)
            {
                int nCnt = userControl.Layers - userControl.RadarMapLayersPolygon.Count;
                for (int i = 0; i < nCnt; i++)
                {
                    userControl.RadarMapLayersPolygon.Add(new Polygon() { Stroke = userControl.LayerStroke, StrokeThickness = 1 });
                }
                needRefresh = true;
            }
            if (needRefresh)
            {
                userControl.RefreshRadarMap();
            }
        }
        /// <summary>
        /// 雷达图分层的规则,这里使用0-1之间的数据标识,主要是用比例来表示
        /// 在使用者未指定的情况下,则根据Layers的层数来均分
        /// 设置举例:雷达图分4层,均分每层面积,则LayersPercentList设置为:
        /// LayersPercentList[0] = 0.25;
        /// LayersPercentList[1] = 0.5;
        /// LayersPercentList[2] = 0.75;
        /// LayersPercentList[3] = 1;
        /// </summary>
        public IEnumerable<double> LayersPercentList
        {
            get { return (IEnumerable<double>)GetValue(LayersPercentListProperty); }
            set { SetValue(LayersPercentListProperty, value); }
        }
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LayersPercentListProperty =
            DependencyProperty.Register("LayersPercentList", typeof(IEnumerable<double>), typeof(RadarMapUserControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnChangedToRefreshRadarMap)));
        /// <summary>
        /// 每层边框的粗细
        /// </summary>
        public double LayerStrokeThickness
        {
            get { return (double)GetValue(LayerStrokeThicknessProperty); }
            set { SetValue(LayerStrokeThicknessProperty, value); }
        }
        // Using a DependencyProperty as the backing store for LayerStrokeThickness.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LayerStrokeThicknessProperty =
            DependencyProperty.Register("LayerStrokeThickness", typeof(double), typeof(RadarMapUserControl), new PropertyMetadata(1.0, new PropertyChangedCallback(OnCurrentLayersFillBrushAndStockThicknessChanged)));
        /// <summary>
        /// 每层的边框颜色
        /// </summary>
        public SolidColorBrush LayerStroke
        {
            get { return (SolidColorBrush)GetValue(LayerStrokeProperty); }
            set { SetValue(LayerStrokeProperty, value); }
        }
        // Using a DependencyProperty as the backing store for LayerStroke.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LayerStrokeProperty =
            DependencyProperty.Register("LayerStroke", typeof(SolidColorBrush), typeof(RadarMapUserControl), new PropertyMetadata(Brushes.White, new PropertyChangedCallback(OnCurrentLayersFillBrushAndStockThicknessChanged)));
        /// <summary>
        /// 雷达图从内到外渐变色,内部颜色
        /// </summary>
        public Color InnerColor
        {
            get { return (Color)GetValue(InnerColorProperty); }
            set { SetValue(InnerColorProperty, value); }
        }
        // Using a DependencyProperty as the backing store for InnerColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InnerColorProperty =
            DependencyProperty.Register("InnerColor", typeof(Color), typeof(RadarMapUserControl), new PropertyMetadata(Colors.White, new PropertyChangedCallback(OnCurrentLayersFillBrushAndStockThicknessChanged)));
        /// <summary>
        /// 雷达图从内到外渐变色,外部颜色
        /// </summary>
        public Color OutColor
        {
            get { return (Color)GetValue(OutColorProperty); }
            set { SetValue(OutColorProperty, value); }
        }
        // Using a DependencyProperty as the backing store for OutColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty OutColorProperty =
            DependencyProperty.Register("OutColor", typeof(Color), typeof(RadarMapUserControl), new PropertyMetadata(Colors.Purple, new PropertyChangedCallback(OnCurrentLayersFillBrushAndStockThicknessChanged)));
        private static void OnCurrentLayersFillBrushAndStockThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            userControl.RefreshLayersFillBrushAndThickness();
        }
        #endregion
        #region 雷达图射线
        /// <summary>
        /// 雷达图的射线数
        /// </summary>
        public int Radials
        {
            get { return (int)GetValue(RadialsProperty); }
            set { SetValue(RadialsProperty, value); }
        }
        // Using a DependencyProperty as the backing store for Radials.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RadialsProperty =
            DependencyProperty.Register("Radials", typeof(int), typeof(RadarMapUserControl), new PropertyMetadata(9, new PropertyChangedCallback(OnCurrentRadialsChanged)));
        private static void OnCurrentRadialsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            bool needRefresh = false;
            if (userControl.RadarMapRadialsPolyline.Count > userControl.Radials)
            {
                int nCnt = userControl.RadarMapRadialsPolyline.Count - userControl.Radials;
                userControl.RadarMapRadialsPolyline.RemoveRange(userControl.RadarMapRadialsPolyline.Count - 1 - nCnt, nCnt);
                needRefresh = true;
            }
            else if (userControl.RadarMapRadialsPolyline.Count < userControl.Radials)
            {
                int nCnt = userControl.Radials - userControl.RadarMapRadialsPolyline.Count;
                for (int i = 0; i < nCnt; i++)
                {
                    userControl.RadarMapRadialsPolyline.Add(new Polyline() { Stroke = userControl.RadialBrush, StrokeThickness = 2 });
                }
                needRefresh = true;
            }
            if (userControl.RadarMapRadialsValuesPolygons.Count > userControl.Radials)
            {
                int nCnt = userControl.RadarMapRadialsValuesPolygons.Count - userControl.Radials;
                userControl.RadarMapRadialsValuesPolygons.RemoveRange(userControl.RadarMapRadialsValuesPolygons.Count - 1 - nCnt, nCnt);
                needRefresh = true;
            }
            else if (userControl.RadarMapRadialsValuesPolygons.Count < userControl.Radials)
            {
                int nCnt = userControl.Radials - userControl.RadarMapRadialsValuesPolygons.Count;
                for (int i = 0; i < nCnt; i++)
                {
                    userControl.RadarMapRadialsValuesPolygons.Add(new Polygon() { Stroke = userControl.LayerStroke, StrokeThickness = 1 });
                }
                needRefresh = true;
            }
            if (needRefresh)
                userControl.RefreshRadarMap();
        }
        /// <summary>
        /// 雷达图半径,决定雷达图的半径
        /// </summary>
        public double Radius
        {
            get { return (double)GetValue(RadiusProperty); }
            set { SetValue(RadiusProperty, value); }
        }
        // Using a DependencyProperty as the backing store for Radius.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RadiusProperty =
            DependencyProperty.Register("Radius", typeof(double), typeof(RadarMapUserControl), new PropertyMetadata(100.0, new PropertyChangedCallback(OnChangedToRefreshRadarMap)));
        /// <summary>
        /// 射线颜色
        /// </summary>
        public SolidColorBrush RadialBrush
        {
            get { return (SolidColorBrush)GetValue(RadialBrushProperty); }
            set { SetValue(RadialBrushProperty, value); }
        }
        // Using a DependencyProperty as the backing store for RadialBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RadialBrushProperty =
            DependencyProperty.Register("RadialBrush", typeof(SolidColorBrush), typeof(RadarMapUserControl), new PropertyMetadata(Brushes.White, new PropertyChangedCallback(OnCurrentRadialBrushAndThicknessChanged)));
        /// <summary>
        /// 射线粗细
        /// </summary>
        public double RadialThickness
        {
            get { return (double)GetValue(RadialThicknessProperty); }
            set { SetValue(RadialThicknessProperty, value); }
        }
        // Using a DependencyProperty as the backing store for RadialThickness.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RadialThicknessProperty =
            DependencyProperty.Register("RadialThickness", typeof(double), typeof(RadarMapUserControl), new PropertyMetadata(1.5, new PropertyChangedCallback(OnCurrentRadialBrushAndThicknessChanged)));
        private static void OnCurrentRadialBrushAndThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            userControl.RefreshRadialBrushAndThinkness();
        }
        #endregion
        #region 雷达图上点
        /// <summary>
        /// 射线上的所有值点
        /// 1. 注意在使用绑定时,要先将Binding对象设置为null,然后将数据整合好的ObservableCollection再赋值给绑定对象,否则不更新
        /// </summary>
        public IEnumerable<double> Values
        {
            get { return (IEnumerable<double>)GetValue(ValuesProperty); }
            set { SetValue(ValuesProperty, value); }
        }
        // Using a DependencyProperty as the backing store for Values.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValuesProperty =
            DependencyProperty.Register("Values", typeof(IEnumerable<double>), typeof(RadarMapUserControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnChangedToRefreshValues)));
        private static void OnChangedToRefreshValues(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            userControl.DrawRadarMapRadialsValues();
        }
        /// <summary>
        /// 普通值点的绘制半径
        /// </summary>
        public double ValueRadius
        {
            get { return (double)GetValue(ValueRadiusProperty); }
            set { SetValue(ValueRadiusProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ValueRadius.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueRadiusProperty =
            DependencyProperty.Register("ValueRadius", typeof(double), typeof(RadarMapUserControl), new PropertyMetadata(4.0, new PropertyChangedCallback(OnChangedToRefreshValueRadiusAndBrush)));
        /// <summary>
        /// 普通值点的颜色
        /// </summary>
        public SolidColorBrush ValueBrush
        {
            get { return (SolidColorBrush)GetValue(ValueBrushProperty); }
            set { SetValue(ValueBrushProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ValueBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueBrushProperty =
            DependencyProperty.Register("ValueBrush", typeof(SolidColorBrush), typeof(RadarMapUserControl), new PropertyMetadata(Brushes.Red, new PropertyChangedCallback(OnChangedToRefreshValueRadiusAndBrush)));
        /// <summary>
        /// 需要高亮点的索引
        /// 1. 注意在使用绑定时,要先将Binding对象设置为null,然后将数据整合好的ObservableCollection再赋值给绑定对象,否则不更新
        /// </summary>
        public IEnumerable<double> HeightLightValues
        {
            get { return (IEnumerable<double>)GetValue(HeightLightValuesProperty); }
            set { SetValue(HeightLightValuesProperty, value); }
        }
        // Using a DependencyProperty as the backing store for HeightLightPoints.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HeightLightValuesProperty =
            DependencyProperty.Register("HeightLightValues", typeof(IEnumerable<double>), typeof(RadarMapUserControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnChangedToRefreshValues)));
        /// <summary>
        /// 光亮点的半径
        /// </summary>
        public double HeighLightRadius
        {
            get { return (double)GetValue(HeighLightRadiusProperty); }
            set { SetValue(HeighLightRadiusProperty, value); }
        }
        // Using a DependencyProperty as the backing store for HeighLightValueRadius.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HeighLightRadiusProperty =
            DependencyProperty.Register("HeighLightRadius", typeof(double), typeof(RadarMapUserControl), new PropertyMetadata(6.0, new PropertyChangedCallback(OnChangedToRefreshValueRadiusAndBrush)));
        /// <summary>
        /// 高亮点的颜色
        /// </summary>
        public SolidColorBrush HeighLightBrush
        {
            get { return (SolidColorBrush)GetValue(HeighLightBrushProperty); }
            set { SetValue(HeighLightBrushProperty, value); }
        }
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HeighLightBrushProperty =
            DependencyProperty.Register("HeighLightBrush", typeof(SolidColorBrush), typeof(RadarMapUserControl), new PropertyMetadata(Brushes.Yellow, new PropertyChangedCallback(OnChangedToRefreshValueRadiusAndBrush)));
        private static void OnChangedToRefreshValueRadiusAndBrush(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            userControl.RefreshValuesRadiusAndBrush();
        }
        #endregion
        #region 雷达图值区域
        /// <summary>
        /// 雷达图值区域填充色
        /// </summary>
        public SolidColorBrush ValuesAreaFill
        {
            get { return (SolidColorBrush)GetValue(ValuesAreaFillProperty); }
            set { SetValue(ValuesAreaFillProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ValuesAreaFill.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValuesAreaFillProperty =
            DependencyProperty.Register("ValuesAreaFill", typeof(SolidColorBrush), typeof(RadarMapUserControl), new PropertyMetadata(Brushes.Red, new PropertyChangedCallback(OnChangedToRefreshValuesAreaFillAndStrokeBrush)));
        /// <summary>
        /// 雷达图值区域边框色
        /// </summary>
        public SolidColorBrush ValuesAreaStroke
        {
            get { return (SolidColorBrush)GetValue(ValuesAreaStrokeProperty); }
            set { SetValue(ValuesAreaStrokeProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ValuesAreaStroke.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValuesAreaStrokeProperty =
            DependencyProperty.Register("ValuesAreaStroke", typeof(SolidColorBrush), typeof(RadarMapUserControl), new PropertyMetadata(Brushes.Gray, new PropertyChangedCallback(OnChangedToRefreshValuesAreaFillAndStrokeBrush)));
        private static void OnChangedToRefreshValuesAreaFillAndStrokeBrush(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            userControl.RefreshValuesAreaBrushAndStroke();
        }
        #endregion
        #region 雷达图的射线标题文字
        /// <summary>
        /// 是否显示Title
        /// </summary>
        public bool ShowTitle
        {
            get { return (bool)GetValue(ShowTitleProperty); }
            set { SetValue(ShowTitleProperty, value); }
        }
        // Using a DependencyProperty as the backing store for ShowTitle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShowTitleProperty =
            DependencyProperty.Register("ShowTitle", typeof(bool), typeof(RadarMapUserControl), new PropertyMetadata(false, new PropertyChangedCallback(OnChangedToRefreshTitles)));
        /// <summary>
        /// 文字的前景色
        /// </summary>
        public SolidColorBrush TitleForground
        {
            get { return (SolidColorBrush)GetValue(TitleForgroundProperty); }
            set { SetValue(TitleForgroundProperty, value); }
        }
        // Using a DependencyProperty as the backing store for TitleForground.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TitleForgroundProperty =
            DependencyProperty.Register("TitleForground", typeof(SolidColorBrush), typeof(RadarMapUserControl), new PropertyMetadata(Brushes.Black, new PropertyChangedCallback(OnChangedToRefreshTitles)));
        /// <summary>
        /// 文字的字号
        /// </summary>
        public int TitleFontSize
        {
            get { return (int)GetValue(TitleFontSizeProperty); }
            set { SetValue(TitleFontSizeProperty, value); }
        }
        // Using a DependencyProperty as the backing store for TitleFontSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TitleFontSizeProperty =
            DependencyProperty.Register("TitleFontSize", typeof(int), typeof(RadarMapUserControl), new PropertyMetadata(14, new PropertyChangedCallback(OnChangedToRefreshTitles)));
        /// <summary>
        /// FontWeight
        /// </summary>
        public FontWeight TitleFontWeight 
        {
            get { return (FontWeight)GetValue(TitleFontWeightProperty); }
            set { SetValue(TitleFontWeightProperty, value); }
        }
        // Using a DependencyProperty as the backing store for TitleFontWeights.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TitleFontWeightProperty =
            DependencyProperty.Register("TitleFontWeights", typeof(FontWeight), typeof(RadarMapUserControl), new PropertyMetadata(FontWeights.Normal, new PropertyChangedCallback(OnChangedToRefreshTitles)));
        /// <summary>
        /// Title要显示的文字
        /// </summary>
        public IEnumerable<string> Titles   
        {
            get { return (IEnumerable<string>)GetValue(TitlesProperty); }
            set { SetValue(TitlesProperty, value); }
        }
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TitlesProperty =
            DependencyProperty.Register("Titles", typeof(IEnumerable<string>), typeof(RadarMapUserControl), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnChangedToRefreshTitles)));
        private static void OnChangedToRefreshTitles(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            userControl.RefreshRadarMap();
        }
        #endregion
        private static void OnChangedToRefreshRadarMap(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            RadarMapUserControl userControl = (RadarMapUserControl)d;
            userControl.RefreshRadarMap();
        }
        public RadarMapUserControl()
        {           
            SolidColorBrush polygonFill = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#7653a7"));
            //绘制图层
            List<Color> colors = GetSingleColorList(OutColor, InnerColor, Layers);
            for (int i = 0; i < Layers; i++)
            {
                RadarMapLayersPolygon.Add(new Polygon() {Fill= new SolidColorBrush(colors[i]),  Stroke = LayerStroke, StrokeThickness = LayerStrokeThickness });
            }
            //绘制射线以及线上值
            for (int i = 0; i < Radials; i++)
            {
                RadarMapRadialsPolyline.Add(new Polyline() { Stroke = RadialBrush, StrokeThickness = RadialThickness });
                RadarMapRadialsValuesPolygons.Add(new Polygon() { Fill = ValueBrush, StrokeThickness = 1 });
            }
            //雷达图值组成的区域
            RadarMapRadialsValuesPolygon = new Polygon() { Fill = ValuesAreaFill, Stroke = ValuesAreaStroke, Opacity = 0.2 };
            InitializeComponent();
        }
        private void RadarMapUserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            RefreshRadarMap();
        }
        /// <summary>
        /// 刷新雷达图的层的填充色和层线粗细
        /// </summary>
        private void RefreshLayersFillBrushAndThickness()
        {
            //绘制雷达图层的多边形
            List<Color> colors = GetSingleColorList(OutColor, InnerColor, Layers);
            for (int i = 0; i < Layers; i++)
            {
                RadarMapLayersPolygon[i].Fill = new SolidColorBrush(colors[i]);
                RadarMapLayersPolygon[i].Stroke = LayerStroke;
                RadarMapLayersPolygon[i].StrokeThickness = LayerStrokeThickness;
            }
        }
        /// <summary>
        /// 刷新射线的颜色和粗细
        /// </summary>
        private void RefreshRadialBrushAndThinkness()
        {
            foreach (var item in RadarMapRadialsPolyline)
            {
                item.Stroke = RadialBrush;
                item.StrokeThickness = RadialThickness;
            }
        }
        /// <summary>
        /// 刷新雷达图
        /// </summary>
        private void RefreshRadarMap()
        {
            Grid_RadarMap.Children.Clear();
            //首先清除一下polygon里存储的数据
            for (int i = 0; i < Layers; i++)
            {
                RadarMapLayersPolygon[i]?.Points?.Clear();
            }
            for (int i = 0; i < Radials; i++)
            {
                RadarMapRadialsPolyline[i]?.Points?.Clear();
            }
            //如果设置了LayersPercentList,并且LayersPercentList的元素个数与层数相同则按照LayersPercentList画每层的占比,否则均分每层占比
            List<double> layersPercents = new List<double>();
            if (LayersPercentList != null && LayersPercentList.Count() == Layers && LayersPercentList.Max() < 1)
            {
                foreach (var item in LayersPercentList)
                {
                    layersPercents.Add(item);
                }
            }
            else
            {
                double gap = 1.0 / Layers;
                for (int i = 0; i < Layers; i++)
                {
                    layersPercents.Add(gap * i + gap); //计算每层的默认占比
                }
            }
            //计算每个扇区的角度
            Angle = 360 / Radials;
            //计算并添加雷达图的区域线和射线上的点
            for (int i = 0; i < Radials; i++)
            {
                //射线上每层的点,从外到内
                List<Point> points = new List<Point>();
                for (int j = 0; j < Layers; j++)
                {
                    Point p = new Point(Radius + (Radius * layersPercents[Layers - j - 1]) * Math.Cos((Angle * i - 90) * Math.PI / 180),
                                        Radius + (Radius * layersPercents[Layers - j - 1]) * Math.Sin((Angle * i - 90) * Math.PI / 180));
                    points.Add(p);
                    //添加到区域线中
                    RadarMapLayersPolygon[j].Points.Add(p);
                }
                //添加到射线中
                foreach (var item in points)
                {
                    RadarMapRadialsPolyline[i].Points.Add(item);
                }
                //计算原点并添加到射线中
                Point p_origin = new Point(Radius + Radius * 0 * Math.Cos((Angle * i - 90) * Math.PI / 180),
                                           Radius + Radius * 0 * Math.Sin((Angle * i - 90) * Math.PI / 180));
                RadarMapRadialsPolyline[i].Points.Add(p_origin);
            }
            //绘制区域层
            foreach (var polygon in RadarMapLayersPolygon)
            {
                if (!Grid_RadarMap.Children.Contains(polygon))
                    Grid_RadarMap.Children.Add(polygon);
            }
            //绘制雷达图射线
            foreach (var polyline in RadarMapRadialsPolyline)
            {
                if (!Grid_RadarMap.Children.Contains(polyline))
                    Grid_RadarMap.Children.Add(polyline);
            }
            //绘制雷达图上的文字
            if (ShowTitle && Titles != null && Titles.Count() == Radials)
            {
                List<string> titleList = Titles.ToList();
                for (int i = 0; i < Radials; i++)
                {
                    Point point = RadarMapLayersPolygon[0].Points[i];
                    string title = titleList[i];
                    TextBlock textBlock = RefreshRadiusTitles(point, title);
                    if (!Grid_RadarMap.Children.Contains(textBlock))
                        Grid_RadarMap.Children.Add(textBlock);
                }
            }
            DrawRadarMapRadialsValues();
        }
        /// <summary>
        /// 刷新雷达图上值的点的半径和填充色,以及高亮点的半径和填充色
        /// </summary>
        private void RefreshValuesRadiusAndBrush()
        {
            if (Values == null)
                return;
            bool drawHeight = false;
            if (HeightLightValues != null && HeightLightValues.Count() > 0)
                drawHeight = true;
            List<double> values = Values.ToList();
            for (int i = 0; i < RadarMapRadialsValuesPolygon.Points.Count; i++)
            {
                RadarMapRadialsValuesPolygons[i].Points.Clear();
                RadarMapRadialsValuesPolygons[i].Fill = ValueBrush;
                double radius = ValueRadius;
                if (drawHeight)
                {
                    if (HeightLightValues.Contains(values[i]))
                    {
                        radius = HeighLightRadius;
                        RadarMapRadialsValuesPolygons[i].Fill = HeighLightBrush;
                        if (ShowTitle && Titles != null && Titles.Count() > i)
                        {
                            List<string> titleList = Titles.ToList();
                            string heightTitle = titleList[i];
                            foreach (var item in Grid_RadarMap.Children)
                            {
                                if (item is TextBlock)
                                {
                                    TextBlock textBlock = (TextBlock)item;
                                    if (textBlock.Text == heightTitle)
                                    {
                                        textBlock.Foreground = HeighLightBrush;
                                    }
                                }
                            }
                        }
                    }
                }
                Point valuePoint = RadarMapRadialsValuesPolygon.Points[i];
                Point[] calc_points = GetEllipsePoints(valuePoint, radius);
                foreach (var p in calc_points)
                {
                    RadarMapRadialsValuesPolygons[i].Points.Add(p);
                }
                if (!Grid_RadarMap.Children.Contains(RadarMapRadialsValuesPolygons[i]))
                    Grid_RadarMap.Children.Add(RadarMapRadialsValuesPolygons[i]);
            }
        }
        /// <summary>
        /// 刷新雷达图值区域的填充色和边框色
        /// </summary>
        private void RefreshValuesAreaBrushAndStroke()
        {
            RadarMapRadialsValuesPolygon.Fill = ValuesAreaFill;
            RadarMapRadialsValuesPolygon.Stroke = ValuesAreaStroke;
        }
        /// <summary>
        /// 刷新射线上的文字标题
        /// </summary>
        /// <param name="point">图层最外层的点</param>
        /// <returns></returns>
        private TextBlock RefreshRadiusTitles(Point point, string title)
        {
            TextBlock textBlock = new TextBlock();
            textBlock.FontSize = 20;
            textBlock.Text = title;
            textBlock.Foreground = TitleForground;
            textBlock.FontWeight = FontWeights.Normal;
            textBlock.FontSize = TitleFontSize;
            //计算文字的实际像素值
            Rect rect1 = new Rect();
            textBlock.Arrange(rect1);
            double textLength = textBlock.ActualWidth;
            Thickness thickness = new Thickness(point.X + 10, point.Y - 10, 0, 0);
            if (point.X == Radius && point.Y < Radius)
            {
                thickness = new Thickness(point.X - textLength / 2, point.Y - 30, 0, 0);
            }
            else if (point.X == Radius && point.Y >= Radius)
            {
                thickness = new Thickness(point.X - textLength / 2, point.Y + 10, 0, 0);
            }
            else if (point.X < Radius)
            {
                thickness = new Thickness(point.X - 20 - textLength, point.Y - 10, 0, 0);
            }
            else
            {
                thickness = new Thickness(point.X + 10, point.Y - 10, 0, 0);
            }
            textBlock.Margin = thickness;
            return textBlock;
        }
        /// <summary>
        /// 绘制雷达图上的点
        /// </summary>
        /// <param name="Values"></param>
        /// <param name="mainType"></param>
        /// <param name="secondType"></param>
        public void DrawRadarMapRadialsValues()
        {
            if (Values == null || Values.Count() != Radials)
                return;
            int fullScore = 100;
            RadarMapRadialsValuesPolygon.Points.Clear();
            for (int i = 0; i < Radials; i++)
            {
                double temp = Values.ToList()[i];
                if (temp <= 0)
                    continue;
                Point value = new Point(Radius + Radius * (temp * 1.0 / fullScore) * Math.Cos((Angle * i - 90) * Math.PI / 180),
                                        Radius + Radius * (temp * 1.0 / fullScore) * Math.Sin((Angle * i - 90) * Math.PI / 180));
                RadarMapRadialsValuesPolygon.Points.Add(value);
            }
            if (!Grid_RadarMap.Children.Contains(RadarMapRadialsValuesPolygon))
                Grid_RadarMap.Children.Add(RadarMapRadialsValuesPolygon);
            RefreshValuesRadiusAndBrush();
        }
        #region 工具类
        /// <summary>
        /// 根据圆心,扩展绘制圆
        /// </summary>
        /// <param name="origin"></param>
        /// <param name="radius"></param>
        /// <returns></returns>
        private Point[] GetEllipsePoints(Point origin, double radius)
        {
            int count = 10;
            Point[] points = new Point[count];
            double angle = 360 / count;
            for (int i = 0; i < count; i++)
            {
                Point p1 = new Point(origin.X + radius * Math.Cos((angle * i - 90) * Math.PI / 180),
                                     origin.Y + radius * Math.Sin((angle * i - 90) * Math.PI / 180));
                points[i] = p1;
            }
            return points;
        }
        /// <summary>
        /// 获得某一颜色区间的颜色集合
        /// </summary>
        /// <param name="sourceColor">起始颜色</param>
        /// <param name="destColor">终止颜色</param>
        /// <param name="count">分度数</param>
        /// <returns>返回颜色集合</returns>
        private List<Color> GetSingleColorList(Color srcColor, Color desColor, int count)
        {
            List<Color> colorFactorList = new List<Color>();
            int redSpan = desColor.R - srcColor.R;
            int greenSpan = desColor.G - srcColor.G;
            int blueSpan = desColor.B - srcColor.B;
            for (int i = 0; i < count; i++)
            {
                Color color = Color.FromRgb(
                    (byte)(srcColor.R + (int)((double)i / count * redSpan)),
                    (byte)(srcColor.G + (int)((double)i / count * greenSpan)),
                    (byte)(srcColor.B + (int)((double)i / count * blueSpan))
                );
                colorFactorList.Add(color);
            }
            return colorFactorList;
        }
        #endregion
    }
}

以上就是WPF自定义实现雷达图控件的示例详解的详细内容,更多关于WPF雷达图的资料请关注脚本之家其它相关文章!

相关文章

  • Unity 实现框选游戏战斗单位的思路详解

    Unity 实现框选游戏战斗单位的思路详解

    这篇文章主要介绍了Unity 如何实现框选游戏战斗单位,本文简单介绍如何实现即时战略游戏中框选战斗单位的功能,需要的朋友可以参考下
    2022-12-12
  • C#实现获取多维数组的行数与列数

    C#实现获取多维数组的行数与列数

    这篇文章主要为大家详细介绍了C#如何分别使用Array.GetUpperBound方法和Array.GetLength方法实现获取多维数组的行数与列数,需要的可以参考下
    2024-02-02
  • Visual Studio 未能加载各种Package包的解决方案

    Visual Studio 未能加载各种Package包的解决方案

    打开Visual Studio 的时候,总提示未能加载相应的Package包,有时候还无法打开项目,各种错误提示,怎么解决呢?下面小编给大家带来了Visual Studio 未能加载各种Package包的解决方案,一起看看吧
    2016-10-10
  • 基于WPF实现面包屑效果的示例代码

    基于WPF实现面包屑效果的示例代码

    这篇文章主要为大家详细介绍了如何基于WPF实现面包屑效果,文中的示例代码讲解详细,对我们学习或工作有一定帮助,感兴趣的小伙伴可以了解一下
    2023-04-04
  • C#使用HtmlAgilityPack抓取糗事百科内容实例

    C#使用HtmlAgilityPack抓取糗事百科内容实例

    这篇文章主要介绍了C#使用HtmlAgilityPack抓取糗事百科内容的方法,实例分析了C#中HtmlAgilityPack的相关使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • C#函数式编程中的递归调用之尾递归详解

    C#函数式编程中的递归调用之尾递归详解

    这篇文章主要介绍了C#函数式编程中的递归调用详解,本文讲解了什么是尾递归、尾递归的多种方式、尾递归的代码实例等内容,需要的朋友可以参考下
    2015-01-01
  • 使用mutex实现应用程序单实例运行代码分享

    使用mutex实现应用程序单实例运行代码分享

    本文主要介绍了使用Mutex实现应用程序单实例运行的方法,实现原理是在程序启动时,请求一个互斥体,如果能获取对指定互斥的访问权,就继续运行程序,否则就退出程序
    2014-01-01
  • C#9特性record 类型、模式匹配、init 属性详情

    C#9特性record 类型、模式匹配、init 属性详情

    这篇文章主要介绍了C#的record 类型、模式匹配(Pattern Matching)、属性的 init 访问器三大特性,感兴趣的小伙伴请参考下面文章内容
    2021-09-09
  • 详解WMI RPC 服务器不可用的解决方案

    详解WMI RPC 服务器不可用的解决方案

    这篇文章主要介绍了详解WMI RPC 服务器不可用的解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-01-01
  • C#编程自学之数据类型和变量一

    C#编程自学之数据类型和变量一

    本节课我们将学习C#编程语言的数据类型,数据类型可以分为值类型和引用类型,接着介绍变量的使用方法和作用域等内容,为了方便大家理解,我们还会举一些小例子作为说明。
    2015-10-10

最新评论