(七十二)c#Winform自定義控制元件-雷達圖
阿新 • • 發佈:2019-09-25
前提
入行已經7,8年了,一直想做一套漂亮點的自定義控制元件,於是就有了本系列文章。
GitHub:https://github.com/kwwwvagaa/NetWinformControl
碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_control.git
如果覺得寫的還行,請點個 star 支援一下吧
歡迎前來交流探討: 企鵝群568015492
麻煩部落格下方點個【推薦】,謝謝
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 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:[email protected] 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 }View Code
最後的話
如果你喜歡的話,請到 https://gitee.com/kwwwvagaa/net_winform_custom_control 點個星