(七十二)c#Winform自定义控件-雷达图
程序员文章站
2023-10-16 22:54:41
前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 码云:https://gitee.com/kwwwvagaa/net_winform_custom_contr ......
前提
入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章。
github:https://github.com/kwwwvagaa/netwinformcontrol
码云:
如果觉得写的还行,请点个 star 支持一下吧
欢迎前来交流探讨: 企鹅群568015492
麻烦博客下方点个【推荐】,谢谢
nuget
install-package hzh_controls
目录
用处及效果
准备工作
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 }
最后的话
如果你喜欢的话,请到 点个星星吧