(七十二)c#Winform自定义控件-雷达图

前提

入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章。

官网:https://www.hzhcontrols.cn

GitHub:https://github.com/kwwwvagaa/NetWinformControl

码云:HZHControls控件库: HZHControls控件库,c#的winform自定义控件,对触屏具有更好的操作支持,项目是基于framework4.0,完全原生控件开发,没有使用任何第三方控件,你可以放心的用在你的项目中(winfromcontrol/winformcontrol/.net)。还有更丰富的工业控件持续增加中~~~

如果觉得写的还行,请点个 star 支持一下吧

欢迎前来交流探讨: 企鹅群568015492 企鹅群568015492

麻烦博客下方点个【推荐】,谢谢

NuGet

Install-Package HZH_Controls

目录

c#Winform自定义控件-目录_c#winform自定义控件-有图标的按钮-CSDN博客

用处及效果

准备工作

GDI+画的,不会的可以先百度了解下

开始

添加一个类UCRadarChart ,继承 UserControl

添加一些控制属性

复制代码

  1  /// <summary>
  2         /// The split count
  3         /// </summary>
  4         private int splitCount = 5;
  5         /// <summary>
  6         /// Gets or sets the split count.
  7         /// </summary>
  8         /// <value>The split count.</value>
  9         [Browsable(true)]
 10         [Category("自定义")]
 11         [Description("获取或设置分隔份数")]
 12         public int SplitCount
 13         {
 14             get { return splitCount; }
 15             set
 16             {
 17                 splitCount = value;
 18                 Invalidate();
 19             }
 20         }
 21 
 22         /// <summary>
 23         /// The split odd color
 24         /// </summary>
 25         private Color splitOddColor = Color.White;
 26         /// <summary>
 27         /// 分隔奇数栏背景色
 28         /// </summary>
 29         /// <value>The color of the split odd.</value>
 30         [Browsable(true)]
 31         [Category("自定义")]
 32         [Description("获取或设置分隔奇数栏背景色")]
 33         public Color SplitOddColor
 34         {
 35             get { return splitOddColor; }
 36             set
 37             {
 38                 splitOddColor = value;
 39                 Invalidate();
 40             }
 41         }
 42         /// <summary>
 43         /// The split even color
 44         /// </summary>
 45         private Color splitEvenColor = Color.FromArgb(232, 232, 232);
 46         /// <summary>
 47         /// 分隔偶数栏背景色
 48         /// </summary>
 49         /// <value>The color of the split even.</value>
 50         [Browsable(true)]
 51         [Category("自定义")]
 52         [Description("获取或设置分隔偶数栏背景色")]
 53         public Color SplitEvenColor
 54         {
 55             get { return splitEvenColor; }
 56             set { splitEvenColor = value; }
 57         }
 58 
 59         /// <summary>
 60         /// The line color
 61         /// </summary>
 62         private Color lineColor = Color.FromArgb(153, 153, 153);
 63         /// <summary>
 64         /// Gets or sets the color of the line.
 65         /// </summary>
 66         /// <value>The color of the line.</value>
 67         [Browsable(true)]
 68         [Category("自定义")]
 69         [Description("获取或设置线条色")]
 70         public Color LineColor
 71         {
 72             get { return lineColor; }
 73             set
 74             {
 75                 lineColor = value;
 76                 Invalidate();
 77             }
 78         }
 79 
 80         /// <summary>
 81         /// The radar positions
 82         /// </summary>
 83         private RadarPosition[] radarPositions;
 84         /// <summary>
 85         /// 节点列表,至少需要3个
 86         /// </summary>
 87         /// <value>The radar positions.</value>
 88         [Browsable(true)]
 89         [Category("自定义")]
 90         [Description("获取或设置节点,至少需要3个")]
 91         public RadarPosition[] RadarPositions
 92         {
 93             get { return radarPositions; }
 94             set
 95             {
 96                 radarPositions = value;
 97                 Invalidate();
 98             }
 99         }
100 
101         /// <summary>
102         /// The title
103         /// </summary>
104         private string title;
105         /// <summary>
106         /// 标题
107         /// </summary>
108         /// <value>The title.</value>
109         [Browsable(true)]
110         [Category("自定义")]
111         [Description("获取或设置标题")]
112         public string Title
113         {
114             get { return title; }
115             set
116             {
117                 title = value;
118                 ResetTitleSize();
119                 Invalidate();
120             }
121         }
122 
123         /// <summary>
124         /// The title font
125         /// </summary>
126         private Font titleFont = new Font("微软雅黑", 12);
127         /// <summary>
128         /// Gets or sets the title font.
129         /// </summary>
130         /// <value>The title font.</value>
131         [Browsable(true)]
132         [Category("自定义")]
133         [Description("获取或设置标题字体")]
134         public Font TitleFont
135         {
136             get { return titleFont; }
137             set
138             {
139                 titleFont = value;
140                 ResetTitleSize();
141                 Invalidate();
142             }
143         }
144 
145         /// <summary>
146         /// The title color
147         /// </summary>
148         private Color titleColor = Color.Black;
149         /// <summary>
150         /// Gets or sets the color of the title.
151         /// </summary>
152         /// <value>The color of the title.</value>
153         [Browsable(true)]
154         [Category("自定义")]
155         [Description("获取或设置标题文本颜色")]
156         public Color TitleColor
157         {
158             get { return titleColor; }
159             set
160             {
161                 titleColor = value;
162                 Invalidate();
163             }
164         }
165 
166         /// <summary>
167         /// The lines
168         /// </summary>
169         private RadarLine[] lines;
170         /// <summary>
171         /// Gets or sets the lines.
172         /// </summary>
173         /// <value>The lines.</value>
174         [Browsable(true)]
175         [Category("自定义")]
176         [Description("获取或设置值线条,Values长度必须与RadarPositions长度一致,否则无法显示")]
177         public RadarLine[] Lines
178         {
179             get { return lines; }
180             set
181             {
182                 lines = value;
183                 Invalidate();
184             }
185         }
186 
187 
188         /// <summary>
189         /// The title size
190         /// </summary>
191         SizeF titleSize = SizeF.Empty;
192         /// <summary>
193         /// The m rect working
194         /// </summary>
195         private RectangleF m_rectWorking = Rectangle.Empty;
196         /// <summary>
197         /// The line value type size
198         /// </summary>
199         SizeF lineValueTypeSize = SizeF.Empty;
200         /// <summary>
201         /// The int line value COM count
202         /// </summary>
203         int intLineValueComCount = 0;
204         /// <summary>
205         /// The int line value row count
206         /// </summary>
207         int intLineValueRowCount = 0;

复制代码

属性改变时处理工作区域

复制代码

 1  /// <summary>
 2         /// Handles the SizeChanged event of the UCRadarChart control.
 3         /// </summary>
 4         /// <param name="sender">The source of the event.</param>
 5         /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
 6         void UCRadarChart_SizeChanged(object sender, EventArgs e)
 7         {
 8             ResetWorkingRect();
 9         }
10 
11         /// <summary>
12         /// Resets the working rect.
13         /// </summary>
14         private void ResetWorkingRect()
15         {
16             if (lines != null && lines.Length > 0)
17             {
18                 using (Graphics g = this.CreateGraphics())
19                 {
20                     foreach (var item in lines)
21                     {
22                         var s = g.MeasureString(item.Name, Font);
23                         if (s.Width > lineValueTypeSize.Width)
24                             lineValueTypeSize = s;
25                     }
26                 }
27             }
28             var lineTypePanelHeight = 0f;
29             if (lineValueTypeSize != SizeF.Empty)
30             {
31                 intLineValueComCount = (int)(this.Width / (lineValueTypeSize.Width + 25));
32 
33                 intLineValueRowCount = lines.Length / intLineValueComCount;
34                 if (lines.Length % intLineValueComCount != 0)
35                 {
36                     intLineValueRowCount++;
37                 }
38                 lineTypePanelHeight = (lineValueTypeSize.Height + 10) * intLineValueRowCount;
39             }
40             var min = Math.Min(this.Width, this.Height - titleSize.Height - lineTypePanelHeight);
41             var rectWorking = new RectangleF((this.Width - min) / 2 + 10, titleSize.Height + lineTypePanelHeight + 10, min - 10, min - 10);
42             //处理文字
43             float fltSplitAngle = 360F / radarPositions.Length;
44             float fltRadiusWidth = rectWorking.Width / 2;
45             float minX = rectWorking.Left;
46             float maxX = rectWorking.Right;
47             float minY = rectWorking.Top;
48             float maxY = rectWorking.Bottom;
49             using (Graphics g = this.CreateGraphics())
50             {
51                 PointF centrePoint = new PointF(rectWorking.Left + rectWorking.Width / 2, rectWorking.Top + rectWorking.Height / 2);
52                 for (int i = 0; i < radarPositions.Length; i++)
53                 {
54                     float fltAngle = 270 + fltSplitAngle * i;
55                     fltAngle = fltAngle % 360;
56                     PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth);
57                     var _txtSize = g.MeasureString(radarPositions[i].Text, Font);
58                     if (_point.X < centrePoint.X)//左
59                     {
60                         if (_point.X - _txtSize.Width < minX)
61                         {
62                             minX = rectWorking.Left + _txtSize.Width;
63                         }
64                     }
65                     else//右
66                     {
67                         if (_point.X + _txtSize.Width > maxX)
68                         {
69                             maxX = rectWorking.Right - _txtSize.Width;
70                         }
71                     }
72                     if (_point.Y < centrePoint.Y)//上
73                     {
74                         if (_point.Y - _txtSize.Height < minY)
75                         {
76                             minY = rectWorking.Top + _txtSize.Height;
77                         }
78                     }
79                     else//下
80                     {
81                         if (_point.Y + _txtSize.Height > maxY)
82                         {
83                             maxY = rectWorking.Bottom - _txtSize.Height;
84                         }
85                     }
86                 }
87             }
88 
89             min = Math.Min(maxX - minX, maxY - minY);
90             m_rectWorking = new RectangleF(minX, minY, min, min);
91         }

复制代码

重绘

复制代码

  1 protected override void OnPaint(PaintEventArgs e)
  2         {
  3             base.OnPaint(e);
  4             var g = e.Graphics;
  5             g.SetGDIHigh();
  6 
  7             if (!string.IsNullOrEmpty(title))
  8             {
  9                 g.DrawString(title, titleFont, new SolidBrush(titleColor), new RectangleF(m_rectWorking.Left + (m_rectWorking.Width - titleSize.Width) / 2, m_rectWorking.Top - titleSize.Height - 10 - (intLineValueRowCount * (10 + lineValueTypeSize.Height)), titleSize.Width, titleSize.Height));
 10             }
 11 
 12             if (radarPositions.Length <= 2)
 13             {
 14                 g.DrawString("至少需要3个顶点", Font, new SolidBrush(Color.Black), m_rectWorking, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
 15                 return;
 16             }
 17 
 18             var y = m_rectWorking.Top - 20 - (intLineValueRowCount * (10 + lineValueTypeSize.Height));
 19 
 20             for (int i = 0; i < intLineValueRowCount; i++)
 21             {
 22                 var x = 0f;
 23                 int intCount = intLineValueComCount;
 24                 if (i == intLineValueRowCount - 1)
 25                 {
 26                     intCount = lines.Length % intLineValueComCount;
 27 
 28                 }
 29                 x = m_rectWorking.Left + (m_rectWorking.Width - intCount * (lineValueTypeSize.Width + 25)) / 2;
 30 
 31                 for (int j = 0; j < intCount; j++)
 32                 {
 33                     g.FillRectangle(new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new RectangleF(x + (lineValueTypeSize.Width + 25)*j, y + lineValueTypeSize.Height * i, 15, lineValueTypeSize.Height));
 34                     g.DrawString(lines[i * intLineValueComCount + j].Name, Font, new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new PointF(x + (lineValueTypeSize.Width + 25) * j + 20, y + lineValueTypeSize.Height * i));
 35                 }
 36             }
 37 
 38             float fltSplitAngle = 360F / radarPositions.Length;
 39             float fltRadiusWidth = m_rectWorking.Width / 2;
 40             float fltSplitRadiusWidth = fltRadiusWidth / splitCount;
 41             PointF centrePoint = new PointF(m_rectWorking.Left + m_rectWorking.Width / 2, m_rectWorking.Top + m_rectWorking.Height / 2);
 42 
 43             List<List<PointF>> lstRingPoints = new List<List<PointF>>(splitCount);
 44             //分割点
 45             for (int i = 0; i < radarPositions.Length; i++)
 46             {
 47                 float fltAngle = 270 + fltSplitAngle * i;
 48                 fltAngle = fltAngle % 360;
 49                 for (int j = 0; j < splitCount; j++)
 50                 {
 51                     if (i == 0)
 52                     {
 53                         lstRingPoints.Add(new List<PointF>());
 54                     }
 55                     PointF _point = GetPointByAngle(centrePoint, fltAngle, fltSplitRadiusWidth * (splitCount - j));
 56                     lstRingPoints[j].Add(_point);
 57                 }
 58             }
 59 
 60             for (int i = 0; i < lstRingPoints.Count; i++)
 61             {
 62                 var ring = lstRingPoints[i];
 63                 GraphicsPath path = new GraphicsPath();
 64                 path.AddLines(ring.ToArray());
 65                 if ((lstRingPoints.Count - i) % 2 == 0)
 66                 {
 67                     g.FillPath(new SolidBrush(splitEvenColor), path);
 68                 }
 69                 else
 70                 {
 71                     g.FillPath(new SolidBrush(splitOddColor), path);
 72                 }
 73             }
 74 
 75             //画环
 76             foreach (var ring in lstRingPoints)
 77             {
 78                 ring.Add(ring[0]);
 79                 g.DrawLines(new Pen(new SolidBrush(lineColor)), ring.ToArray());
 80             }
 81             //分割线
 82             foreach (var item in lstRingPoints[0])
 83             {
 84                 g.DrawLine(new Pen(new SolidBrush(lineColor)), centrePoint, item);
 85             }
 86 
 87             //值
 88             for (int i = 0; i < lines.Length; i++)
 89             {
 90                 var line = lines[i];
 91                 if (line.Values.Length != radarPositions.Length)//如果数据长度和节点长度不一致则不绘制
 92                     continue;
 93                 if (line.LineColor == null || line.LineColor == Color.Empty || line.LineColor == Color.Transparent)
 94                     line.LineColor = ControlHelper.Colors[i + 13];
 95                 List<PointF> ps = new List<PointF>();
 96                 for (int j = 0; j < radarPositions.Length; j++)
 97                 {
 98                     float fltAngle = 270 + fltSplitAngle * j;
 99                     fltAngle = fltAngle % 360;
100                     PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth * (float)(line.Values[j] / radarPositions[i].MaxValue));
101                     ps.Add(_point);
102                 }
103                 ps.Add(ps[0]);
104                 if (line.FillColor != null && line.FillColor != Color.Empty && line.FillColor != Color.Transparent)
105                 {
106                     GraphicsPath path = new GraphicsPath();
107                     path.AddLines(ps.ToArray());
108                     g.FillPath(new SolidBrush(line.FillColor.Value), path);
109                 }
110                 g.DrawLines(new Pen(new SolidBrush(line.LineColor.Value), 2), ps.ToArray());
111 
112                 for (int j = 0; j < radarPositions.Length; j++)
113                 {
114                     var item = ps[j];
115                     g.FillEllipse(new SolidBrush(Color.White), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
116                     g.DrawEllipse(new Pen(new SolidBrush(line.LineColor.Value)), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
117                     if (line.ShowValueText)
118                     {
119                         var valueSize = g.MeasureString(line.Values[j].ToString("0.##"), Font);
120                         g.DrawString(line.Values[j].ToString("0.##"), Font, new SolidBrush(line.LineColor.Value), new PointF(item.X - valueSize.Width / 2, item.Y - valueSize.Height - 5));
121                     }
122                 }
123             }
124 
125             //文本
126 
127             for (int i = 0; i < radarPositions.Length; i++)
128             {
129                 PointF point = lstRingPoints[0][i];
130                 var txtSize = g.MeasureString(radarPositions[i].Text, Font);
131 
132                 if (point.X == centrePoint.X)
133                 {
134                     if (point.Y > centrePoint.Y)
135                     {
136                         g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y + 10));
137                     }
138                     else
139                     {
140                         g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y - 10 - txtSize.Height));
141                     }
142                 }
143                 else if (point.Y == centrePoint.Y)
144                 {
145                     if (point.X < centrePoint.X)
146                         g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - txtSize.Height / 2));
147                     else
148                         g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - txtSize.Height / 2));
149                 }
150                 else if (point.X < centrePoint.X)//左
151                 {
152                     if (point.Y < centrePoint.Y)//左上
153                     {
154                         g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - 10 + txtSize.Height / 2));
155                     }
156                     else//左下
157                     {
158                         g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y + 10 - txtSize.Height / 2));
159                     }
160                 }
161                 else
162                 {
163                     if (point.Y < centrePoint.Y)//右上
164                     {
165                         g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - 10 + txtSize.Height / 2));
166                     }
167                     else//右下
168                     {
169                         g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y + 10 - txtSize.Height / 2));
170                     }
171                 }
172             }
173 
174         }

复制代码

辅助函数

复制代码

 1  #region 根据中心点、角度、半径计算圆边坐标点    English:Calculating the coordinate points of circular edge according to the center point, angle and radius
 2         /// <summary>
 3         /// 功能描述:根据中心点、角度、半径计算圆边坐标点    English:Calculating the coordinate points of circular edge according to the center point, angle and radius
 4         /// 作  者:HZH
 5         /// 创建日期:2019-09-25 09:46:32
 6         /// 任务编号:POS
 7         /// </summary>
 8         /// <param name="centrePoint">centrePoint</param>
 9         /// <param name="fltAngle">fltAngle</param>
10         /// <param name="fltRadiusWidth">fltRadiusWidth</param>
11         /// <returns>返回值</returns>
12         private PointF GetPointByAngle(PointF centrePoint, float fltAngle, float fltRadiusWidth)
13         {
14             PointF p = centrePoint;
15             if (fltAngle == 0)
16             {
17                 p.X += fltRadiusWidth;
18             }
19             else if (fltAngle == 90)
20             {
21                 p.Y += fltRadiusWidth;
22             }
23             else if (fltAngle == 180)
24             {
25                 p.X -= fltRadiusWidth;
26             }
27             else if (fltAngle == 270)
28             {
29                 p.Y -= fltRadiusWidth;
30             }
31             else if (fltAngle > 0 && fltAngle < 90)
32             {
33                 p.Y += (float)Math.Sin(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
34                 p.X += (float)Math.Cos(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
35             }
36             else if (fltAngle > 90 && fltAngle < 180)
37             {
38                 p.Y += (float)Math.Sin(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
39                 p.X -= (float)Math.Cos(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
40             }
41             else if (fltAngle > 180 && fltAngle < 270)
42             {
43                 p.Y -= (float)Math.Sin(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
44                 p.X -= (float)Math.Cos(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
45             }
46             else if (fltAngle > 270 && fltAngle < 360)
47             {
48                 p.Y -= (float)Math.Sin(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
49                 p.X += (float)Math.Cos(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
50             }
51             return p;
52         }
53         #endregion
54 
55         /// <summary>
56         /// Resets the size of the title.
57         /// </summary>
58         private void ResetTitleSize()
59         {
60             if (!string.IsNullOrEmpty(title))
61             {
62                 using (Graphics g = this.CreateGraphics())
63                 {
64                     titleSize = g.MeasureString(title, titleFont);
65                 }
66             }
67             else
68             {
69                 titleSize = SizeF.Empty;
70             }
71             titleSize.Height += 20;
72             ResetWorkingRect();
73         }

复制代码

完整代码

// ***********************************************************************
// Assembly         : HZH_Controls
// Created          : 2019-09-25
//
// ***********************************************************************
// <copyright file="UCRadarChart.cs">
//     Copyright by Huang Zhenghui(黄正辉) All, QQ group:568015492 QQ:623128629 Email:623128629@qq.com
// </copyright>
//
// Blog: https://www.cnblogs.com/bfyx
// GitHub:https://github.com/kwwwvagaa/NetWinformControl
// gitee:https://gitee.com/kwwwvagaa/net_winform_custom_control.git
//
// If you use this code, please keep this note.
// ***********************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;

namespace HZH_Controls.Controls
{
    /// <summary>
    /// Class UCRadarChart.
    /// Implements the <see cref="System.Windows.Forms.UserControl" />
    /// </summary>
    /// <seealso cref="System.Windows.Forms.UserControl" />
    public class UCRadarChart : UserControl
    {
        /// <summary>
        /// The split count
        /// </summary>
        private int splitCount = 5;
        /// <summary>
        /// Gets or sets the split count.
        /// </summary>
        /// <value>The split count.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置分隔份数")]
        public int SplitCount
        {
            get { return splitCount; }
            set
            {
                splitCount = value;
                Invalidate();
            }
        }

        /// <summary>
        /// The split odd color
        /// </summary>
        private Color splitOddColor = Color.White;
        /// <summary>
        /// 分隔奇数栏背景色
        /// </summary>
        /// <value>The color of the split odd.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置分隔奇数栏背景色")]
        public Color SplitOddColor
        {
            get { return splitOddColor; }
            set
            {
                splitOddColor = value;
                Invalidate();
            }
        }
        /// <summary>
        /// The split even color
        /// </summary>
        private Color splitEvenColor = Color.FromArgb(232, 232, 232);
        /// <summary>
        /// 分隔偶数栏背景色
        /// </summary>
        /// <value>The color of the split even.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置分隔偶数栏背景色")]
        public Color SplitEvenColor
        {
            get { return splitEvenColor; }
            set { splitEvenColor = value; }
        }

        /// <summary>
        /// The line color
        /// </summary>
        private Color lineColor = Color.FromArgb(153, 153, 153);
        /// <summary>
        /// Gets or sets the color of the line.
        /// </summary>
        /// <value>The color of the line.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置线条色")]
        public Color LineColor
        {
            get { return lineColor; }
            set
            {
                lineColor = value;
                Invalidate();
            }
        }

        /// <summary>
        /// The radar positions
        /// </summary>
        private RadarPosition[] radarPositions;
        /// <summary>
        /// 节点列表,至少需要3个
        /// </summary>
        /// <value>The radar positions.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置节点,至少需要3个")]
        public RadarPosition[] RadarPositions
        {
            get { return radarPositions; }
            set
            {
                radarPositions = value;
                Invalidate();
            }
        }

        /// <summary>
        /// The title
        /// </summary>
        private string title;
        /// <summary>
        /// 标题
        /// </summary>
        /// <value>The title.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置标题")]
        public string Title
        {
            get { return title; }
            set
            {
                title = value;
                ResetTitleSize();
                Invalidate();
            }
        }

        /// <summary>
        /// The title font
        /// </summary>
        private Font titleFont = new Font("微软雅黑", 12);
        /// <summary>
        /// Gets or sets the title font.
        /// </summary>
        /// <value>The title font.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置标题字体")]
        public Font TitleFont
        {
            get { return titleFont; }
            set
            {
                titleFont = value;
                ResetTitleSize();
                Invalidate();
            }
        }

        /// <summary>
        /// The title color
        /// </summary>
        private Color titleColor = Color.Black;
        /// <summary>
        /// Gets or sets the color of the title.
        /// </summary>
        /// <value>The color of the title.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置标题文本颜色")]
        public Color TitleColor
        {
            get { return titleColor; }
            set
            {
                titleColor = value;
                Invalidate();
            }
        }

        /// <summary>
        /// The lines
        /// </summary>
        private RadarLine[] lines;
        /// <summary>
        /// Gets or sets the lines.
        /// </summary>
        /// <value>The lines.</value>
        [Browsable(true)]
        [Category("自定义")]
        [Description("获取或设置值线条,Values长度必须与RadarPositions长度一致,否则无法显示")]
        public RadarLine[] Lines
        {
            get { return lines; }
            set
            {
                lines = value;
                Invalidate();
            }
        }


        /// <summary>
        /// The title size
        /// </summary>
        SizeF titleSize = SizeF.Empty;
        /// <summary>
        /// The m rect working
        /// </summary>
        private RectangleF m_rectWorking = Rectangle.Empty;
        /// <summary>
        /// The line value type size
        /// </summary>
        SizeF lineValueTypeSize = SizeF.Empty;
        /// <summary>
        /// The int line value COM count
        /// </summary>
        int intLineValueComCount = 0;
        /// <summary>
        /// The int line value row count
        /// </summary>
        int intLineValueRowCount = 0;
        /// <summary>
        /// Initializes a new instance of the <see cref="UCRadarChart"/> class.
        /// </summary>
        public UCRadarChart()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.DoubleBuffer, true);
            this.SetStyle(ControlStyles.ResizeRedraw, true);
            this.SetStyle(ControlStyles.Selectable, true);
            this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            this.SetStyle(ControlStyles.UserPaint, true);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
            this.SizeChanged += UCRadarChart_SizeChanged;
            Size = new System.Drawing.Size(150, 150);
            radarPositions = new RadarPosition[0];
            if (ControlHelper.IsDesignMode())
            {
                radarPositions = new RadarPosition[6];
                for (int i = 0; i < 6; i++)
                {
                    radarPositions[i] = new RadarPosition
                    {
                        Text = "Item" + (i + 1),
                        MaxValue = 100
                    };
                }
            }

            lines = new RadarLine[0];
            if (ControlHelper.IsDesignMode())
            {
                Random r = new Random();
                lines = new RadarLine[2];
                for (int i = 0; i < 2; i++)
                {
                    lines[i] = new RadarLine()
                    {
                        Name = "line" + i
                    };
                    lines[i].Values = new double[radarPositions.Length];
                    for (int j = 0; j < radarPositions.Length; j++)
                    {
                        lines[i].Values[j] = r.Next(20, (int)radarPositions[j].MaxValue);
                    }
                }
            }
        }

        /// <summary>
        /// Handles the SizeChanged event of the UCRadarChart control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        void UCRadarChart_SizeChanged(object sender, EventArgs e)
        {
            ResetWorkingRect();
        }

        /// <summary>
        /// Resets the working rect.
        /// </summary>
        private void ResetWorkingRect()
        {
            if (lines != null && lines.Length > 0)
            {
                using (Graphics g = this.CreateGraphics())
                {
                    foreach (var item in lines)
                    {
                        var s = g.MeasureString(item.Name, Font);
                        if (s.Width > lineValueTypeSize.Width)
                            lineValueTypeSize = s;
                    }
                }
            }
            var lineTypePanelHeight = 0f;
            if (lineValueTypeSize != SizeF.Empty)
            {
                intLineValueComCount = (int)(this.Width / (lineValueTypeSize.Width + 25));

                intLineValueRowCount = lines.Length / intLineValueComCount;
                if (lines.Length % intLineValueComCount != 0)
                {
                    intLineValueRowCount++;
                }
                lineTypePanelHeight = (lineValueTypeSize.Height + 10) * intLineValueRowCount;
            }
            var min = Math.Min(this.Width, this.Height - titleSize.Height - lineTypePanelHeight);
            var rectWorking = new RectangleF((this.Width - min) / 2 + 10, titleSize.Height + lineTypePanelHeight + 10, min - 10, min - 10);
            //处理文字
            float fltSplitAngle = 360F / radarPositions.Length;
            float fltRadiusWidth = rectWorking.Width / 2;
            float minX = rectWorking.Left;
            float maxX = rectWorking.Right;
            float minY = rectWorking.Top;
            float maxY = rectWorking.Bottom;
            using (Graphics g = this.CreateGraphics())
            {
                PointF centrePoint = new PointF(rectWorking.Left + rectWorking.Width / 2, rectWorking.Top + rectWorking.Height / 2);
                for (int i = 0; i < radarPositions.Length; i++)
                {
                    float fltAngle = 270 + fltSplitAngle * i;
                    fltAngle = fltAngle % 360;
                    PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth);
                    var _txtSize = g.MeasureString(radarPositions[i].Text, Font);
                    if (_point.X < centrePoint.X)//左
                    {
                        if (_point.X - _txtSize.Width < minX)
                        {
                            minX = rectWorking.Left + _txtSize.Width;
                        }
                    }
                    else//右
                    {
                        if (_point.X + _txtSize.Width > maxX)
                        {
                            maxX = rectWorking.Right - _txtSize.Width;
                        }
                    }
                    if (_point.Y < centrePoint.Y)//上
                    {
                        if (_point.Y - _txtSize.Height < minY)
                        {
                            minY = rectWorking.Top + _txtSize.Height;
                        }
                    }
                    else//下
                    {
                        if (_point.Y + _txtSize.Height > maxY)
                        {
                            maxY = rectWorking.Bottom - _txtSize.Height;
                        }
                    }
                }
            }

            min = Math.Min(maxX - minX, maxY - minY);
            m_rectWorking = new RectangleF(minX, minY, min, min);
        }

        /// <summary>
        /// 引发 <see cref="E:System.Windows.Forms.Control.Paint" /> 事件。
        /// </summary>
        /// <param name="e">包含事件数据的 <see cref="T:System.Windows.Forms.PaintEventArgs" />。</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            var g = e.Graphics;
            g.SetGDIHigh();

            if (!string.IsNullOrEmpty(title))
            {
                g.DrawString(title, titleFont, new SolidBrush(titleColor), new RectangleF(m_rectWorking.Left + (m_rectWorking.Width - titleSize.Width) / 2, m_rectWorking.Top - titleSize.Height - 10 - (intLineValueRowCount * (10 + lineValueTypeSize.Height)), titleSize.Width, titleSize.Height));
            }

            if (radarPositions.Length <= 2)
            {
                g.DrawString("至少需要3个顶点", Font, new SolidBrush(Color.Black), m_rectWorking, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
                return;
            }

            var y = m_rectWorking.Top - 20 - (intLineValueRowCount * (10 + lineValueTypeSize.Height));

            for (int i = 0; i < intLineValueRowCount; i++)
            {
                var x = 0f;
                int intCount = intLineValueComCount;
                if (i == intLineValueRowCount - 1)
                {
                    intCount = lines.Length % intLineValueComCount;

                }
                x = m_rectWorking.Left + (m_rectWorking.Width - intCount * (lineValueTypeSize.Width + 25)) / 2;

                for (int j = 0; j < intCount; j++)
                {
                    g.FillRectangle(new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new RectangleF(x + (lineValueTypeSize.Width + 25)*j, y + lineValueTypeSize.Height * i, 15, lineValueTypeSize.Height));
                    g.DrawString(lines[i * intLineValueComCount + j].Name, Font, new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new PointF(x + (lineValueTypeSize.Width + 25) * j + 20, y + lineValueTypeSize.Height * i));
                }
            }

            float fltSplitAngle = 360F / radarPositions.Length;
            float fltRadiusWidth = m_rectWorking.Width / 2;
            float fltSplitRadiusWidth = fltRadiusWidth / splitCount;
            PointF centrePoint = new PointF(m_rectWorking.Left + m_rectWorking.Width / 2, m_rectWorking.Top + m_rectWorking.Height / 2);

            List<List<PointF>> lstRingPoints = new List<List<PointF>>(splitCount);
            //分割点
            for (int i = 0; i < radarPositions.Length; i++)
            {
                float fltAngle = 270 + fltSplitAngle * i;
                fltAngle = fltAngle % 360;
                for (int j = 0; j < splitCount; j++)
                {
                    if (i == 0)
                    {
                        lstRingPoints.Add(new List<PointF>());
                    }
                    PointF _point = GetPointByAngle(centrePoint, fltAngle, fltSplitRadiusWidth * (splitCount - j));
                    lstRingPoints[j].Add(_point);
                }
            }

            for (int i = 0; i < lstRingPoints.Count; i++)
            {
                var ring = lstRingPoints[i];
                GraphicsPath path = new GraphicsPath();
                path.AddLines(ring.ToArray());
                if ((lstRingPoints.Count - i) % 2 == 0)
                {
                    g.FillPath(new SolidBrush(splitEvenColor), path);
                }
                else
                {
                    g.FillPath(new SolidBrush(splitOddColor), path);
                }
            }

            //画环
            foreach (var ring in lstRingPoints)
            {
                ring.Add(ring[0]);
                g.DrawLines(new Pen(new SolidBrush(lineColor)), ring.ToArray());
            }
            //分割线
            foreach (var item in lstRingPoints[0])
            {
                g.DrawLine(new Pen(new SolidBrush(lineColor)), centrePoint, item);
            }

            //值
            for (int i = 0; i < lines.Length; i++)
            {
                var line = lines[i];
                if (line.Values.Length != radarPositions.Length)//如果数据长度和节点长度不一致则不绘制
                    continue;
                if (line.LineColor == null || line.LineColor == Color.Empty || line.LineColor == Color.Transparent)
                    line.LineColor = ControlHelper.Colors[i + 13];
                List<PointF> ps = new List<PointF>();
                for (int j = 0; j < radarPositions.Length; j++)
                {
                    float fltAngle = 270 + fltSplitAngle * j;
                    fltAngle = fltAngle % 360;
                    PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth * (float)(line.Values[j] / radarPositions[i].MaxValue));
                    ps.Add(_point);
                }
                ps.Add(ps[0]);
                if (line.FillColor != null && line.FillColor != Color.Empty && line.FillColor != Color.Transparent)
                {
                    GraphicsPath path = new GraphicsPath();
                    path.AddLines(ps.ToArray());
                    g.FillPath(new SolidBrush(line.FillColor.Value), path);
                }
                g.DrawLines(new Pen(new SolidBrush(line.LineColor.Value), 2), ps.ToArray());

                for (int j = 0; j < radarPositions.Length; j++)
                {
                    var item = ps[j];
                    g.FillEllipse(new SolidBrush(Color.White), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
                    g.DrawEllipse(new Pen(new SolidBrush(line.LineColor.Value)), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
                    if (line.ShowValueText)
                    {
                        var valueSize = g.MeasureString(line.Values[j].ToString("0.##"), Font);
                        g.DrawString(line.Values[j].ToString("0.##"), Font, new SolidBrush(line.LineColor.Value), new PointF(item.X - valueSize.Width / 2, item.Y - valueSize.Height - 5));
                    }
                }
            }

            //文本

            for (int i = 0; i < radarPositions.Length; i++)
            {
                PointF point = lstRingPoints[0][i];
                var txtSize = g.MeasureString(radarPositions[i].Text, Font);

                if (point.X == centrePoint.X)
                {
                    if (point.Y > centrePoint.Y)
                    {
                        g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y + 10));
                    }
                    else
                    {
                        g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y - 10 - txtSize.Height));
                    }
                }
                else if (point.Y == centrePoint.Y)
                {
                    if (point.X < centrePoint.X)
                        g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - txtSize.Height / 2));
                    else
                        g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - txtSize.Height / 2));
                }
                else if (point.X < centrePoint.X)//左
                {
                    if (point.Y < centrePoint.Y)//左上
                    {
                        g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - 10 + txtSize.Height / 2));
                    }
                    else//左下
                    {
                        g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y + 10 - txtSize.Height / 2));
                    }
                }
                else
                {
                    if (point.Y < centrePoint.Y)//右上
                    {
                        g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - 10 + txtSize.Height / 2));
                    }
                    else//右下
                    {
                        g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y + 10 - txtSize.Height / 2));
                    }
                }
            }

        }

        #region 根据中心点、角度、半径计算圆边坐标点    English:Calculating the coordinate points of circular edge according to the center point, angle and radius
        /// <summary>
        /// 功能描述:根据中心点、角度、半径计算圆边坐标点    English:Calculating the coordinate points of circular edge according to the center point, angle and radius
        /// 作  者:HZH
        /// 创建日期:2019-09-25 09:46:32
        /// 任务编号:POS
        /// </summary>
        /// <param name="centrePoint">centrePoint</param>
        /// <param name="fltAngle">fltAngle</param>
        /// <param name="fltRadiusWidth">fltRadiusWidth</param>
        /// <returns>返回值</returns>
        private PointF GetPointByAngle(PointF centrePoint, float fltAngle, float fltRadiusWidth)
        {
            PointF p = centrePoint;
            if (fltAngle == 0)
            {
                p.X += fltRadiusWidth;
            }
            else if (fltAngle == 90)
            {
                p.Y += fltRadiusWidth;
            }
            else if (fltAngle == 180)
            {
                p.X -= fltRadiusWidth;
            }
            else if (fltAngle == 270)
            {
                p.Y -= fltRadiusWidth;
            }
            else if (fltAngle > 0 && fltAngle < 90)
            {
                p.Y += (float)Math.Sin(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
                p.X += (float)Math.Cos(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
            }
            else if (fltAngle > 90 && fltAngle < 180)
            {
                p.Y += (float)Math.Sin(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
                p.X -= (float)Math.Cos(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
            }
            else if (fltAngle > 180 && fltAngle < 270)
            {
                p.Y -= (float)Math.Sin(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
                p.X -= (float)Math.Cos(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
            }
            else if (fltAngle > 270 && fltAngle < 360)
            {
                p.Y -= (float)Math.Sin(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
                p.X += (float)Math.Cos(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
            }
            return p;
        }
        #endregion

        /// <summary>
        /// Resets the size of the title.
        /// </summary>
        private void ResetTitleSize()
        {
            if (!string.IsNullOrEmpty(title))
            {
                using (Graphics g = this.CreateGraphics())
                {
                    titleSize = g.MeasureString(title, titleFont);
                }
            }
            else
            {
                titleSize = SizeF.Empty;
            }
            titleSize.Height += 20;
            ResetWorkingRect();
        }
    }
}

最后的话

如果你喜欢的话,请到 HZHControls控件库: HZHControls控件库,c#的winform自定义控件,对触屏具有更好的操作支持,项目是基于framework4.0,完全原生控件开发,没有使用任何第三方控件,你可以放心的用在你的项目中(winfromcontrol/winformcontrol/.net)。还有更丰富的工业控件持续增加中~~~ 点个星星吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值