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

前提

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

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

码云:https://gitee.com/kwwwvagaa/net_winform_custom_control.git

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

欢迎前来交流探讨: 企鹅群568015492 (七十二)c#Winform自定义控件-雷达图

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

NuGet

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 title103 /// </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 Title113  {114 get { return title; }115 set116  {117 title = value;118  ResetTitleSize();119  Invalidate();120  }121  }122 123 /// <summary>124 /// The title font125 /// </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 TitleFont135  {136 get { return titleFont; }137 set138  {139 titleFont = value;140  ResetTitleSize();141  Invalidate();142  }143  }144 145 /// <summary>146 /// The title color147 /// </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 TitleColor157  {158 get { return titleColor; }159 set160  {161 titleColor = value;162  Invalidate();163  }164  }165 166 /// <summary>167 /// The lines168 /// </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[] Lines178  {179 get { return lines; }180 set181  {182 lines = value;183  Invalidate();184  }185  }186 187 188 /// <summary>189 /// The title size190 /// </summary>191 SizeF titleSize = SizeF.Empty;192 /// <summary>193 /// The m rect working194 /// </summary>195 private RectangleF m_rectWorking = Rectangle.Empty;196 /// <summary>197 /// The line value type size198 /// </summary>199 SizeF lineValueTypeSize = SizeF.Empty;200 /// <summary>201 /// The int line value COM count202 /// </summary>203 int intLineValueComCount = 0;204 /// <summary>205 /// The int line value row count206 /// </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 else139  {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 else148 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 else162  {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 #endregion54 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 else68  {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 LineColor104  {105 get { return lineColor; }106 set107  {108 lineColor = value;109  Invalidate();110  }111  }112 113 /// <summary>114 /// The radar positions115 /// </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[] RadarPositions125  {126 get { return radarPositions; }127 set128  {129 radarPositions = value;130  Invalidate();131  }132  }133 134 /// <summary>135 /// The title136 /// </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 Title146  {147 get { return title; }148 set149  {150 title = value;151  ResetTitleSize();152  Invalidate();153  }154  }155 156 /// <summary>157 /// The title font158 /// </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 TitleFont168  {169 get { return titleFont; }170 set171  {172 titleFont = value;173  ResetTitleSize();174  Invalidate();175  }176  }177 178 /// <summary>179 /// The title color180 /// </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 TitleColor190  {191 get { return titleColor; }192 set193  {194 titleColor = value;195  Invalidate();196  }197  }198 199 /// <summary>200 /// The lines201 /// </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[] Lines211  {212 get { return lines; }213 set214  {215 lines = value;216  Invalidate();217  }218  }219 220 221 /// <summary>222 /// The title size223 /// </summary>224 SizeF titleSize = SizeF.Empty;225 /// <summary>226 /// The m rect working227 /// </summary>228 private RectangleF m_rectWorking = Rectangle.Empty;229 /// <summary>230 /// The line value type size231 /// </summary>232 SizeF lineValueTypeSize = SizeF.Empty;233 /// <summary>234 /// The int line value COM count235 /// </summary>236 int intLineValueComCount = 0;237 /// <summary>238 /// The int line value row count239 /// </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 RadarPosition262  {263 Text = "Item" + (i + 1),264 MaxValue = 100265  };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" + i279  };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 else454  {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 else523  {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 else532 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 else546  {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 radius561 /// <summary>562 /// 功能描述:根据中心点、角度、半径计算圆边坐标点 English:Calculating the coordinate points of circular edge according to the center point, angle and radius563 /// 作  者:HZH564 /// 创建日期:2019-09-25 09:46:32565 /// 任务编号:POS566 /// </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 #endregion613 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 else627  {628 titleSize = SizeF.Empty;629  }630 titleSize.Height += 20;631  ResetWorkingRect();632  }633  }634 }

View Code

 

最后的话

如果你喜欢的话,请到 https://gitee.com/kwwwvagaa/net_winform_custom_control 点个星星吧

相关文章