入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章。
GitHub:https://github.com/kwwwvagaa/NetWinformControl
码云:https://gitee.com/kwwwvagaa/net_winform_custom_control.git
如果觉得写的还行,请点个 star 支持一下吧
欢迎前来交流探讨: 企鹅群568015492
麻烦博客下方点个【推荐】,谢谢
Install-Package HZH_Controls
https://www.cnblogs.com/bfyx/p/11364884.html
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 }
完整代码
1 // ***********************************************************************
2 // Assembly : HZH_Controls
3 // Created : 2019-09-25
4 //
5 // ***********************************************************************
6 // <copyright file="UCRadarChart.cs">
7 // Copyright by Huang Zhenghui(黄正辉) All, QQ group:568015492 QQ:623128629 Email:623128629@qq.com
8 // </copyright>
9 //
10 // Blog: https://www.cnblogs.com/bfyx
11 // GitHub:https://github.com/kwwwvagaa/NetWinformControl
12 // gitee:https://gitee.com/kwwwvagaa/net_winform_custom_control.git
13 //
14 // If you use this code, please keep this note.
15 // ***********************************************************************
16 using System;
17 using System.Collections.Generic;
18 using System.Linq;
19 using System.Text;
20 using System.Windows.Forms;
21 using System.Drawing;
22 using System.Drawing.Drawing2D;
23 using System.ComponentModel;
24
25 namespace HZH_Controls.Controls
26 {
27 /// <summary>
28 /// Class UCRadarChart.
29 /// Implements the <see cref="System.Windows.Forms.UserControl" />
30 /// </summary>
31 /// <seealso cref="System.Windows.Forms.UserControl" />
32 public class UCRadarChart : UserControl
33 {
34 /// <summary>
35 /// The split count
36 /// </summary>
37 private int splitCount = 5;
38 /// <summary>
39 /// Gets or sets the split count.
40 /// </summary>
41 /// <value>The split count.</value>
42 [Browsable(true)]
43 [Category("自定义")]
44 [Description("获取或设置分隔份数")]
45 public int SplitCount
46 {
47 get { return splitCount; }
48 set
49 {
50 splitCount = value;
51 Invalidate();
52 }
53 }
54
55 /// <summary>
56 /// The split odd color
57 /// </summary>
58 private Color splitOddColor = Color.White;
59 /// <summary>
60 /// 分隔奇数栏背景色
61 /// </summary>
62 /// <value>The color of the split odd.</value>
63 [Browsable(true)]
64 [Category("自定义")]
65 [Description("获取或设置分隔奇数栏背景色")]
66 public Color SplitOddColor
67 {
68 get { return splitOddColor; }
69 set
70 {
71 splitOddColor = value;
72 Invalidate();
73 }
74 }
75 /// <summary>
76 /// The split even color
77 /// </summary>
78 private Color splitEvenColor = Color.FromArgb(232, 232, 232);
79 /// <summary>
80 /// 分隔偶数栏背景色
81 /// </summary>
82 /// <value>The color of the split even.</value>
83 [Browsable(true)]
84 [Category("自定义")]
85 [Description("获取或设置分隔偶数栏背景色")]
86 public Color SplitEvenColor
87 {
88 get { return splitEvenColor; }
89 set { splitEvenColor = value; }
90 }
91
92 /// <summary>
93 /// The line color
94 /// </summary>
95 private Color lineColor = Color.FromArgb(153, 153, 153);
96 /// <summary>
97 /// Gets or sets the color of the line.
98 /// </summary>
99 /// <value>The color of the line.</value>
100 [Browsable(true)]
101 [Category("自定义")]
102 [Description("获取或设置线条色")]
103 public Color LineColor
104 {
105 get { return lineColor; }
106 set
107 {
108 lineColor = value;
109 Invalidate();
110 }
111 }
112
113 /// <summary>
114 /// The radar positions
115 /// </summary>
116 private RadarPosition[] radarPositions;
117 /// <summary>
118 /// 节点列表,至少需要3个
119 /// </summary>
120 /// <value>The radar positions.</value>
121 [Browsable(true)]
122 [Category("自定义")]
123 [Description("获取或设置节点,至少需要3个")]
124 public RadarPosition[] RadarPositions
125 {
126 get { return radarPositions; }
127 set
128 {
129 radarPositions = value;
130 Invalidate();
131 }
132 }
133
134 /// <summary>
135 /// The title
136 /// </summary>
137 private string title;
138 /// <summary>
139 /// 标题
140 /// </summary>
141 /// <value>The title.</value>
142 [Browsable(true)]
143 [Category("自定义")]
144 [Description("获取或设置标题")]
145 public string Title
146 {
147 get { return title; }
148 set
149 {
150 title = value;
151 ResetTitleSize();
152 Invalidate();
153 }
154 }
155
156 /// <summary>
157 /// The title font
158 /// </summary>
159 private Font titleFont = new Font("微软雅黑", 12);
160 /// <summary>
161 /// Gets or sets the title font.
162 /// </summary>
163 /// <value>The title font.</value>
164 [Browsable(true)]
165 [Category("自定义")]
166 [Description("获取或设置标题字体")]
167 public Font TitleFont
168 {
169 get { return titleFont; }
170 set
171 {
172 titleFont = value;
173 ResetTitleSize();
174 Invalidate();
175 }
176 }
177
178 /// <summary>
179 /// The title color
180 /// </summary>
181 private Color titleColor = Color.Black;
182 /// <summary>
183 /// Gets or sets the color of the title.
184 /// </summary>
185 /// <value>The color of the title.</value>
186 [Browsable(true)]
187 [Category("自定义")]
188 [Description("获取或设置标题文本颜色")]
189 public Color TitleColor
190 {
191 get { return titleColor; }
192 set
193 {
194 titleColor = value;
195 Invalidate();
196 }
197 }
198
199 /// <summary>
200 /// The lines
201 /// </summary>
202 private RadarLine[] lines;
203 /// <summary>
204 /// Gets or sets the lines.
205 /// </summary>
206 /// <value>The lines.</value>
207 [Browsable(true)]
208 [Category("自定义")]
209 [Description("获取或设置值线条,Values长度必须与RadarPositions长度一致,否则无法显示")]
210 public RadarLine[] Lines
211 {
212 get { return lines; }
213 set
214 {
215 lines = value;
216 Invalidate();
217 }
218 }
219
220
221 /// <summary>
222 /// The title size
223 /// </summary>
224 SizeF titleSize = SizeF.Empty;
225 /// <summary>
226 /// The m rect working
227 /// </summary>
228 private RectangleF m_rectWorking = Rectangle.Empty;
229 /// <summary>
230 /// The line value type size
231 /// </summary>
232 SizeF lineValueTypeSize = SizeF.Empty;
233 /// <summary>
234 /// The int line value COM count
235 /// </summary>
236 int intLineValueComCount = 0;
237 /// <summary>
238 /// The int line value row count
239 /// </summary>
240 int intLineValueRowCount = 0;
241 /// <summary>
242 /// Initializes a new instance of the <see cref="UCRadarChart"/> class.
243 /// </summary>
244 public UCRadarChart()
245 {
246 this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
247 this.SetStyle(ControlStyles.DoubleBuffer, true);
248 this.SetStyle(ControlStyles.ResizeRedraw, true);
249 this.SetStyle(ControlStyles.Selectable, true);
250 this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
251 this.SetStyle(ControlStyles.UserPaint, true);
252 this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
253 this.SizeChanged += UCRadarChart_SizeChanged;
254 Size = new System.Drawing.Size(150, 150);
255 radarPositions = new RadarPosition[0];
256 if (ControlHelper.IsDesignMode())
257 {
258 radarPositions = new RadarPosition[6];
259 for (int i = 0; i < 6; i++)
260 {
261 radarPositions[i] = new RadarPosition
262 {
263 Text = "Item" + (i + 1),
264 MaxValue = 100
265 };
266 }
267 }
268
269 lines = new RadarLine[0];
270 if (ControlHelper.IsDesignMode())
271 {
272 Random r = new Random();
273 lines = new RadarLine[2];
274 for (int i = 0; i < 2; i++)
275 {
276 lines[i] = new RadarLine()
277 {
278 Name = "line" + i
279 };
280 lines[i].Values = new double[radarPositions.Length];
281 for (int j = 0; j < radarPositions.Length; j++)
282 {
283 lines[i].Values[j] = r.Next(20, (int)radarPositions[j].MaxValue);
284 }
285 }
286 }
287 }
288
289 /// <summary>
290 /// Handles the SizeChanged event of the UCRadarChart control.
291 /// </summary>
292 /// <param name="sender">The source of the event.</param>
293 /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
294 void UCRadarChart_SizeChanged(object sender, EventArgs e)
295 {
296 ResetWorkingRect();
297 }
298
299 /// <summary>
300 /// Resets the working rect.
301 /// </summary>
302 private void ResetWorkingRect()
303 {
304 if (lines != null && lines.Length > 0)
305 {
306 using (Graphics g = this.CreateGraphics())
307 {
308 foreach (var item in lines)
309 {
310 var s = g.MeasureString(item.Name, Font);
311 if (s.Width > lineValueTypeSize.Width)
312 lineValueTypeSize = s;
313 }
314 }
315 }
316 var lineTypePanelHeight = 0f;
317 if (lineValueTypeSize != SizeF.Empty)
318 {
319 intLineValueComCount = (int)(this.Width / (lineValueTypeSize.Width + 25));
320
321 intLineValueRowCount = lines.Length / intLineValueComCount;
322 if (lines.Length % intLineValueComCount != 0)
323 {
324 intLineValueRowCount++;
325 }
326 lineTypePanelHeight = (lineValueTypeSize.Height + 10) * intLineValueRowCount;
327 }
328 var min = Math.Min(this.Width, this.Height - titleSize.Height - lineTypePanelHeight);
329 var rectWorking = new RectangleF((this.Width - min) / 2 + 10, titleSize.Height + lineTypePanelHeight + 10, min - 10, min - 10);
330 //处理文字
331 float fltSplitAngle = 360F / radarPositions.Length;
332 float fltRadiusWidth = rectWorking.Width / 2;
333 float minX = rectWorking.Left;
334 float maxX = rectWorking.Right;
335 float minY = rectWorking.Top;
336 float maxY = rectWorking.Bottom;
337 using (Graphics g = this.CreateGraphics())
338 {
339 PointF centrePoint = new PointF(rectWorking.Left + rectWorking.Width / 2, rectWorking.Top + rectWorking.Height / 2);
340 for (int i = 0; i < radarPositions.Length; i++)
341 {
342 float fltAngle = 270 + fltSplitAngle * i;
343 fltAngle = fltAngle % 360;
344 PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth);
345 var _txtSize = g.MeasureString(radarPositions[i].Text, Font);
346 if (_point.X < centrePoint.X)//左
347 {
348 if (_point.X - _txtSize.Width < minX)
349 {
350 minX = rectWorking.Left + _txtSize.Width;
351 }
352 }
353 else//右
354 {
355 if (_point.X + _txtSize.Width > maxX)
356 {
357 maxX = rectWorking.Right - _txtSize.Width;
358 }
359 }
360 if (_point.Y < centrePoint.Y)//上
361 {
362 if (_point.Y - _txtSize.Height < minY)
363 {
364 minY = rectWorking.Top + _txtSize.Height;
365 }
366 }
367 else//下
368 {
369 if (_point.Y + _txtSize.Height > maxY)
370 {
371 maxY = rectWorking.Bottom - _txtSize.Height;
372 }
373 }
374 }
375 }
376
377 min = Math.Min(maxX - minX, maxY - minY);
378 m_rectWorking = new RectangleF(minX, minY, min, min);
379 }
380
381 /// <summary>
382 /// 引发 <see cref="E:System.Windows.Forms.Control.Paint" /> 事件。
383 /// </summary>
384 /// <param name="e">包含事件数据的 <see cref="T:System.Windows.Forms.PaintEventArgs" />。</param>
385 protected override void OnPaint(PaintEventArgs e)
386 {
387 base.OnPaint(e);
388 var g = e.Graphics;
389 g.SetGDIHigh();
390
391 if (!string.IsNullOrEmpty(title))
392 {
393 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));
394 }
395
396 if (radarPositions.Length <= 2)
397 {
398 g.DrawString("至少需要3个顶点", Font, new SolidBrush(Color.Black), m_rectWorking, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
399 return;
400 }
401
402 var y = m_rectWorking.Top - 20 - (intLineValueRowCount * (10 + lineValueTypeSize.Height));
403
404 for (int i = 0; i < intLineValueRowCount; i++)
405 {
406 var x = 0f;
407 int intCount = intLineValueComCount;
408 if (i == intLineValueRowCount - 1)
409 {
410 intCount = lines.Length % intLineValueComCount;
411
412 }
413 x = m_rectWorking.Left + (m_rectWorking.Width - intCount * (lineValueTypeSize.Width + 25)) / 2;
414
415 for (int j = 0; j < intCount; j++)
416 {
417 g.FillRectangle(new SolidBrush(lines[i * intLineValueComCount + j].LineColor.Value), new RectangleF(x + (lineValueTypeSize.Width + 25)*j, y + lineValueTypeSize.Height * i, 15, lineValueTypeSize.Height));
418 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));
419 }
420 }
421
422 float fltSplitAngle = 360F / radarPositions.Length;
423 float fltRadiusWidth = m_rectWorking.Width / 2;
424 float fltSplitRadiusWidth = fltRadiusWidth / splitCount;
425 PointF centrePoint = new PointF(m_rectWorking.Left + m_rectWorking.Width / 2, m_rectWorking.Top + m_rectWorking.Height / 2);
426
427 List<List<PointF>> lstRingPoints = new List<List<PointF>>(splitCount);
428 //分割点
429 for (int i = 0; i < radarPositions.Length; i++)
430 {
431 float fltAngle = 270 + fltSplitAngle * i;
432 fltAngle = fltAngle % 360;
433 for (int j = 0; j < splitCount; j++)
434 {
435 if (i == 0)
436 {
437 lstRingPoints.Add(new List<PointF>());
438 }
439 PointF _point = GetPointByAngle(centrePoint, fltAngle, fltSplitRadiusWidth * (splitCount - j));
440 lstRingPoints[j].Add(_point);
441 }
442 }
443
444 for (int i = 0; i < lstRingPoints.Count; i++)
445 {
446 var ring = lstRingPoints[i];
447 GraphicsPath path = new GraphicsPath();
448 path.AddLines(ring.ToArray());
449 if ((lstRingPoints.Count - i) % 2 == 0)
450 {
451 g.FillPath(new SolidBrush(splitEvenColor), path);
452 }
453 else
454 {
455 g.FillPath(new SolidBrush(splitOddColor), path);
456 }
457 }
458
459 //画环
460 foreach (var ring in lstRingPoints)
461 {
462 ring.Add(ring[0]);
463 g.DrawLines(new Pen(new SolidBrush(lineColor)), ring.ToArray());
464 }
465 //分割线
466 foreach (var item in lstRingPoints[0])
467 {
468 g.DrawLine(new Pen(new SolidBrush(lineColor)), centrePoint, item);
469 }
470
471 //值
472 for (int i = 0; i < lines.Length; i++)
473 {
474 var line = lines[i];
475 if (line.Values.Length != radarPositions.Length)//如果数据长度和节点长度不一致则不绘制
476 continue;
477 if (line.LineColor == null || line.LineColor == Color.Empty || line.LineColor == Color.Transparent)
478 line.LineColor = ControlHelper.Colors[i + 13];
479 List<PointF> ps = new List<PointF>();
480 for (int j = 0; j < radarPositions.Length; j++)
481 {
482 float fltAngle = 270 + fltSplitAngle * j;
483 fltAngle = fltAngle % 360;
484 PointF _point = GetPointByAngle(centrePoint, fltAngle, fltRadiusWidth * (float)(line.Values[j] / radarPositions[i].MaxValue));
485 ps.Add(_point);
486 }
487 ps.Add(ps[0]);
488 if (line.FillColor != null && line.FillColor != Color.Empty && line.FillColor != Color.Transparent)
489 {
490 GraphicsPath path = new GraphicsPath();
491 path.AddLines(ps.ToArray());
492 g.FillPath(new SolidBrush(line.FillColor.Value), path);
493 }
494 g.DrawLines(new Pen(new SolidBrush(line.LineColor.Value), 2), ps.ToArray());
495
496 for (int j = 0; j < radarPositions.Length; j++)
497 {
498 var item = ps[j];
499 g.FillEllipse(new SolidBrush(Color.White), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
500 g.DrawEllipse(new Pen(new SolidBrush(line.LineColor.Value)), new RectangleF(item.X - 3, item.Y - 3, 6, 6));
501 if (line.ShowValueText)
502 {
503 var valueSize = g.MeasureString(line.Values[j].ToString("0.##"), Font);
504 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));
505 }
506 }
507 }
508
509 //文本
510
511 for (int i = 0; i < radarPositions.Length; i++)
512 {
513 PointF point = lstRingPoints[0][i];
514 var txtSize = g.MeasureString(radarPositions[i].Text, Font);
515
516 if (point.X == centrePoint.X)
517 {
518 if (point.Y > centrePoint.Y)
519 {
520 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y + 10));
521 }
522 else
523 {
524 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - txtSize.Width / 2, point.Y - 10 - txtSize.Height));
525 }
526 }
527 else if (point.Y == centrePoint.Y)
528 {
529 if (point.X < centrePoint.X)
530 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - txtSize.Height / 2));
531 else
532 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - txtSize.Height / 2));
533 }
534 else if (point.X < centrePoint.X)//左
535 {
536 if (point.Y < centrePoint.Y)//左上
537 {
538 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y - 10 + txtSize.Height / 2));
539 }
540 else//左下
541 {
542 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X - 10 - txtSize.Width, point.Y + 10 - txtSize.Height / 2));
543 }
544 }
545 else
546 {
547 if (point.Y < centrePoint.Y)//右上
548 {
549 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y - 10 + txtSize.Height / 2));
550 }
551 else//右下
552 {
553 g.DrawString(radarPositions[i].Text, Font, new SolidBrush(ForeColor), new PointF(point.X + 10, point.Y + 10 - txtSize.Height / 2));
554 }
555 }
556 }
557
558 }
559
560 #region 根据中心点、角度、半径计算圆边坐标点 English:Calculating the coordinate points of circular edge according to the center point, angle and radius
561 /// <summary>
562 /// 功能描述:根据中心点、角度、半径计算圆边坐标点 English:Calculating the coordinate points of circular edge according to the center point, angle and radius
563 /// 作 者:HZH
564 /// 创建日期:2019-09-25 09:46:32
565 /// 任务编号:POS
566 /// </summary>
567 /// <param name="centrePoint">centrePoint</param>
568 /// <param name="fltAngle">fltAngle</param>
569 /// <param name="fltRadiusWidth">fltRadiusWidth</param>
570 /// <returns>返回值</returns>
571 private PointF GetPointByAngle(PointF centrePoint, float fltAngle, float fltRadiusWidth)
572 {
573 PointF p = centrePoint;
574 if (fltAngle == 0)
575 {
576 p.X += fltRadiusWidth;
577 }
578 else if (fltAngle == 90)
579 {
580 p.Y += fltRadiusWidth;
581 }
582 else if (fltAngle == 180)
583 {
584 p.X -= fltRadiusWidth;
585 }
586 else if (fltAngle == 270)
587 {
588 p.Y -= fltRadiusWidth;
589 }
590 else if (fltAngle > 0 && fltAngle < 90)
591 {
592 p.Y += (float)Math.Sin(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
593 p.X += (float)Math.Cos(Math.PI * (fltAngle / 180.00F)) * fltRadiusWidth;
594 }
595 else if (fltAngle > 90 && fltAngle < 180)
596 {
597 p.Y += (float)Math.Sin(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
598 p.X -= (float)Math.Cos(Math.PI * ((180 - fltAngle) / 180.00F)) * fltRadiusWidth;
599 }
600 else if (fltAngle > 180 && fltAngle < 270)
601 {
602 p.Y -= (float)Math.Sin(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
603 p.X -= (float)Math.Cos(Math.PI * ((fltAngle - 180) / 180.00F)) * fltRadiusWidth;
604 }
605 else if (fltAngle > 270 && fltAngle < 360)
606 {
607 p.Y -= (float)Math.Sin(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
608 p.X += (float)Math.Cos(Math.PI * ((360 - fltAngle) / 180.00F)) * fltRadiusWidth;
609 }
610 return p;
611 }
612 #endregion
613
614 /// <summary>
615 /// Resets the size of the title.
616 /// </summary>
617 private void ResetTitleSize()
618 {
619 if (!string.IsNullOrEmpty(title))
620 {
621 using (Graphics g = this.CreateGraphics())
622 {
623 titleSize = g.MeasureString(title, titleFont);
624 }
625 }
626 else
627 {
628 titleSize = SizeF.Empty;
629 }
630 titleSize.Height += 20;
631 ResetWorkingRect();
632 }
633 }
634 }
如果你喜欢的话,请到 https://gitee.com/kwwwvagaa/net_winform_custom_control 点个星星吧