Conmajia

Stop stealing sheep!

导航

< 20253 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

📡 GDI+ 绘制极坐标图雷达图

一个简单的GDI+例子

简单绘制极坐标系按类似的思路可以画直角坐标系对数直角系外太空银河系……

图中曲线是一个天线方向图非常适合在极坐标下描绘

文中是直接在窗体上绘制你完全可以自行封装到控件里这样用起来更加方便

 

写在前面的话

做事情一切以目标为出发点倒着找过去看有哪些方法技术资源具体的方法技术手段都是次要的只要能达到目的

我不会多线程如果你觉得这个直接在UI线程画效率低方法笨还请自己用多线程改造一遍似乎还真有这样ocd的人吧哈哈

欢迎把改造后的代码回传给我我会贴在这里小广告

 

目标设定例子

下面是例子不针对任何人物事件团体星球

boss接到了一单生意是帮某山寨厂做一个山寨手机天线的信号测试系统其中我分到的部分是做天线方向图的显示界面模块其实我懂个p的天线方向图之类的啊于是boss告诉我并强调我不管你怎么做总之要看起来像这样

ok不管会不会山寨是本行拿着原版开始分析

 

分析坐标系

说实话数学那套玩意老早就还给老师了现在要让我玩坐标系这样高深的东西得亏哥们还有点印象这样圆不拉叽的图一般用极坐标来画是比较方便的连上Wikipedia复习一下极坐标是一个二维坐标系统该坐标系统中的点由一个夹和一段相对中心——极点相当于我们较为熟知的直角坐标系中的原点的距离来表示

很好乱七八糟的看不太懂把这东西先放一遍还是用山寨的方法解决boss给的那张图拿来分析下其实就是很多同心圆和过圆心的辐条借用自行车术语虽然不知道正确的名字就这么叫了吧

那么我只需要画出同心圆再画辐条ok了吧画同心圆怎么画呢我可以这样从外面的大圆开始DrawEllipse()画一个圆然后收缩下半径再画一个如此这般……好了有想法就行动管他是nb方法还是sb方法一直坐那zb最后被炒了那才sb

画出同心圆的方法

复制代码
绘制同心圆点击+号展开
 1     // 画圆
 2     private void drawCircles(Graphics g, Rectangle rect)
 3     {
 4         // 圆的直径等于绘图区域最短边
 5         float diameter = Math.Min(rect.Width, rect.Height);
 6         // 半径
 7         float radius = diameter / 2;
 8         // 圆心
 9         PointF center = new PointF(
10             rect.X + rect.Width / 2,
11             rect.Y + rect.Height / 2
12             );
13 
14         // 画几个圆,先试试5
15         int count = 5;
16         float diameterStep = diameter / count;
17         float radiusStep = radius / count;
18 
19         // 生成圆的范围
20         RectangleF cirleRect = new RectangleF();
21         cirleRect.X = center.X - radius;
22         cirleRect.Y = center.Y - radius;
23         cirleRect.Width = cirleRect.Height = diameter;
24 
25         // 画同心圆
26         for (int i = 0; i < count; i++)
27         {
28             g.DrawEllipse(Pens.Gray, cirleRect);
29 
30             cirleRect.X += radiusStep;
31             cirleRect.Y += radiusStep;
32             cirleRect.Width -= diameterStep;
33             cirleRect.Height -= diameterStep;
34         }
35     }
复制代码

把这段代码添加到Paint事件里看看效果如何

Good效果还凑合好像有点锯齿哦那我就把抗锯齿打开顺手把文字抗锯齿也打开

1     e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;    // 图形抗锯齿
2     e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; // 文字抗锯齿

接下来就要画辐条了那个线可不能就像图里一个十字叉就完事了的肯定要能自己设n想当初就是曾经思维简单了没有考虑到这种变数被客户和boss烦得天昏地暗再也不会上当了

辐条怎么画呢思考下在草稿纸上画画先

以下都是中学数学本人上了大学以后数学从没及格过

从少到多看看辐条的规律原来是这样啊我不一定非要把辐条看成穿过圆心的我可以看成从圆心发出的n个射线把圆切成了n个扇面每个角度就是360°÷n这样那就好办了刚才我画圆的时候已经算出来圆心位置了只要再算出射线终点的坐标就可以用DrawLine()画线了但是射线终点又要怎么算呢我可是要画到GDI+里哦

用黑色的笔画出圆红色的画出GDI+坐标系那么就可以算出来终点在GDI+下的坐标圆心(x0,y0)r半径刚才我已经算出来了θ就是360/n现在所有参数都确定了只要把圆心半径这几个我需要使用的变量从画圆的方法里拿出来大家用我就可以开始写画辐条的方法了

复制代码
绘制辐条点击+展开
 1     // 提出来公用
 2     float diameter, radius;
 3     PointF center;
 4     // 画圆
 5     private void drawCircles(Graphics g, Rectangle rect)
 6     {
 7         // (略)
 8     }
 9 
10     // 辐射线
11     private void drawSpokes(Graphics g)
12     {
13         int count = 8;
14         if (count > 0)
15         {
16             // 计算角度
17             float angle = 0;
18             float angleStep = 360 / count;
19             PointF endPoint = new PointF();
20 
21             for (int i = 0; i < count; i++)
22             {
23                 // 得到终点
24                 endPoint = getPoint(angle);
25                 g.DrawLine(Pens.Gray, center, endPoint);
26 
27                 angle += angleStep;
28                 angle %= 360;
29             }
30         }
31     }
32 
33     // 计算终点
34     private PointF getPoint(double angle)
35     {
36         PointF pt = new PointF();
37 
38         pt.X = (float)(radius * Math.Cos(angle * Math.PI / 180) + center.X);
39         pt.Y = (float)(radius * Math.Sin(angle * Math.PI / 180) + center.Y);
40 
41         return pt;
42     }
复制代码

把代码加到Paint事件画圆的后面看看效果

Yeah, baby你太听话了

 

永不满足的客户·永不结束的工作

没过半天客户就找到boss要求在辐射线边上加上角度数字于是义不容辞的开始了新一轮改造

说起加上数字先前我已经得到了每个射线终点的坐标那我直接在那坐标上DrawString()出角度数字就行了吧drawSpokes()里面先加上这句试试

1     // 画角度值
2     g.DrawString(angle.ToString("0") + "°", this.Font, Brushes.Gray, endPoint);

卖糕的问题多多哦最下面的字跑出画面了上面的和左边的字跑到圆圈里面右边的字也有点往里靠改改试试看先把画圆的区域缩小一点以便下面的标签能显示出来

1     //drawDiagramCircles(e.Graphics, this.ClientRectangle);
2     // 缩小点画圆的区域
3     Rectangle rect = this.ClientRectangle;
4     rect.Inflate(0, -20);
5     drawDiagramCircles(e.Graphics, rect);

ok解决下一个问题先思考下什么情况下字会跑到圆里去θ∈(90°, 270°)这个区间那我就在这个区间画文字的时候把文字往左平移出去就行了270°时我把文字往上移动试试看drawSpokes()画文字的地方

复制代码
 1     // 把要画的字符串提出来便于操作
 2     string angleString = angle.ToString("0") + "°";
 3 
 4     // 画角度值,如果文字在90-270度区间内,
 5     PointF textPoint = endPoint;
 6 
 7     if (angle == 270)
 8         textPoint.Y -= TextRenderer.MeasureText(angleString, this.Font).Height; //TextRenderer测量字符串大小
 9     else if (angle < 270 && angle > 90)
10         textPoint.X -= TextRenderer.MeasureText(angleString, this.Font).Width;
11     else
12         textPoint.X += 8; // 随便来点漂移
13 
14     g.DrawString(angleString, this.Font, Brushes.Gray, textPoint);
复制代码

看看效果

嗯哼很好其实我觉得最好的办法是分象限比如第一象限就增加x增加y第二象限就增加x减少y第三象限减少x减少y第四象限减少x增加y’

 

加入数据点

光画一副坐标系那肯定是什么都干不了的所以还有最重要的添加数据所谓一个数据就是包含了角度数值的这样一组数比如天线对着某个方向角度的接收信号强度数值角度很好理解就是0360°然后转圈数值就要费点功夫了用户添加的数据肯定是他们采集到的真实数据这个数据要映射到我这里做的坐标图里面使其同样大小数值具有同样的映射点最小数值映射在圆心最大数值映射在射线终点这样所有的数据就都可以用这张图来记录了下面使用最简单的线性映射来设计所谓线性映射其实就是这样

所以在全局变量里我加入了数据范围的上下限

1     float min = 0;
2     float max = 100;

为了便于后续操作我决定把角度 - 数值这样一组数据封装在一起然后用一个列表来存储管理

复制代码
 1         public class PolarValue
 2         {
 3             float ang = 0;
 4             float val = 0;
 5 
 6             // 角度
 7             public float Angle
 8             {
 9                 get { return ang; }
10                 set { ang = value; }
11             }
12 
13             // 数值
14             public float Value
15             {
16                 get { return val; }
17                 set { val = value; }
18             }
19 
20             public PolarValue(float angle, float value)
21             {
22                 this.ang = angle;
23                 this.val = value;
24             }
25         }
26 
27         // 数据列表
28         public List<PolarValue> values = new List<PolarValue>();
复制代码

现在我有了一组数据点我需要做的就是把数据点映射到坐标图上如此遍历每一点并连接之就画出了我所需要的方向图这就是映射的方法

复制代码
 1     private PointF getMappedPoint(PolarValue pv)
 2     {
 3         // 计算映射在坐标图中的半径
 4         float r = radius * (pv.Value - min) / (max - min);
 5         // 计算GDI+坐标
 6         PointF pt = new PointF();
 7         pt.X = (float)(r * Math.Cos(pv.Angle * Math.PI / 180) + center.X);
 8         pt.Y = (float)(r * Math.Sin(pv.Angle * Math.PI / 180) + center.Y);
 9         return pt;
10     }
复制代码

写到这里我不由得回头看了看刚才画辐条时为了计算辐条终点而写的getPoint()方法这两个方法实在是太像了唯一区别就是getMappedPoint()使用变化的数值getPoint()使用固定的数值辐条终点可以认为是r=Rvalue=max现在合并这两个方法并修改相应调用的地方

复制代码
 1     // 合并后的映射方法
 2     private PointF getMappedPoint(float angle, float value)
 3     {
 4         // 计算映射在坐标图中的半径
 5         float r = radius * (value - min) / (max - min);
 6         // 计算GDI+坐标
 7         PointF pt = new PointF();
 8         pt.X = (float)(r * Math.Cos(angle * Math.PI / 180) + center.X);
 9         pt.Y = (float)(r * Math.Sin(angle * Math.PI / 180) + center.Y);
10         return pt;
11     }
复制代码

修改调用的地方

1     //drawSpokes()2     // (略)
3     // 得到终点
4     endPoint = getMappedPoint(angle, max);

现在可以一口气把所有数据点画出来了

复制代码
 1     private void drawPoints(Graphics g, List<PolarValue> pointList)
 2     {
 3         // 计算下一点
 4         PointF nextPt;
 5         for (int i = 0; i < pointList.Count; i++)
 6         {
 7             if ((i + 1) < pointList.Count)
 8                 nextPt = getMappedPoint(pointList[i + 1].Angle, pointList[i + 1].Value);
 9             else
10                 nextPt = getMappedPoint(pointList[0].Angle, pointList[0].Value);
11 
12             // 连接当前点和下一点
13             g.DrawLine(
14                 Pens.Black,
15                 nextPt,
16                 getMappedPoint(pointList[i].Angle, pointList[i].Value));
17         }
18     }
复制代码

随便添加几个数据顺便设置下圆圈数和辐条数看看效果如何

 

圆圈=3辐条=4

圆圈=6辐条=8

圆圈=9辐条=16

 

一些变化

以下内容为搞笑
好了我们做完了这个项目送走了天线的客户现在又来了一个游戏的客户他要求我们要制作一个类似FIFA或者实况的运动游戏游戏里面要有一个运动员个人素质参数的查看界面
我们要怎么做重新做就着上一个客户的稍微那么改上一改就像这样

如果稍微改造下你甚至可以用它来画战斗力分析图搞笑的

嗯好了就写这么多山寨故事到此结束谢谢收看

全文完

posted on2012-05-21   Conmajia  阅读(9528)  评论(0编辑  收藏  举报

编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示