某天下班回来后,习惯性的打开博客园,看看首页有没有感兴趣的文章。在 不重复随机数列生成算法 这篇博文中,发现作者的思路不错。莫名其妙的突然想到如何获取随机颜色的算法以及以图形的方式显示出来,那时候刚好12点了,想睡又睡不着,连着猛写了2个小时代码,大概模型出来了。随后的几天,将近期想到的算法综合起来,因此就有了这么一篇文章。
这篇文章主要有如下3个简单的算法,本人将它们结合起来练习练习手感。
(1)不重复随机颜色数组算法
(2)坐标换行算法
(3)改进后的箭头算法
先看下用这3个算法实现的效果图(自动绘制会从1个一直绘制到174个圆,然后再从1个绘起........看起来就像动态的哦!!),如下(图一):
(图二)
哥只是实现功能而已,至于其他页面布局什么的,哥不太会,各位不要见怪,界面实在是简单得不能太简单了,^_^。以下内容将以上述3个算法来讲解。
(一)不重复随机颜色数组算法
这个是在eaglet写的那篇文章中想到了,效率挺高的。哥改动下,也拿来用用,eaglet兄弟不要见怪,代码如下。
2 {
3 /// <summary>
4 /// 获取颜色数组(每种颜色都不相同)
5 /// </summary>
6 /// <param name="count"> 个数 </param>
7 /// <returns></returns>
8 public static Color[] GetRandomColorSequence( byte count)
9 {
10 if (count == 0 )
11 {
12 return null ;
13 }
14
15 string [] colors = typeof (KnownColor).GetEnumNames();
16
17 byte length = ( byte )colors.Length;
18 // 如果大于174(枚举个数),则设置为174
19 if (count > length)
20 {
21 count = length;
22 }
23
24 Color[] sequence = new Color[length];
25 Color[] output = new Color[count];
26 KnownColor color;
27
28 // 获取所有的系统颜色
29 for ( byte index = 0 ; index < length; index ++ )
30 {
31 if ( ! Enum.TryParse < KnownColor > (colors[index], out color))
32 {
33 color = KnownColor.Red;
34 }
35 sequence[index] = Color.FromKnownColor(color);
36 }
37
38 // 移动数组,使颜色数组排列为随机数组,并且设置的颜色都不一样(单一性)
39 Random random = new Random();
40 int end = length - 1 ;
41
42 for ( byte index = 0 ; index < count; index ++ )
43 {
44 int number = random.Next( 0 , end + 1 );
45 output[index] = sequence[number];
46 sequence[number] = sequence[end];
47 end -- ;
48 }
49
50 return output;
51 }
15-36行为改进的代码。
1:先通过反射获取KnownColor的所有枚举字符串(typeof(KnownColor).GetEnumNames();)
2:通过Enum.TryParse<KnownColor>(string, out color))获取每一个枚举字符串相对应的KnownColor,这样所有的KnownColor值将会获取到。
3:通过Color.FromKnownColor(color);来获取对应的Color对象,并且传递到数组中。
39-47为数组移位操作,将随机到的Color(位置为Index)对象与数组中位置为end的Color对象互换。
调用这个方法,最多能获取174个颜色,哈哈,其他的改进留给你们吧(如果需要改进的话)。
(二)改进后的箭头算法
2 PointF endPoint, float length = 15 , float realtiveValue = 3 , float radius = 6 )
3 {
4 if (graphics == null )
5 {
6 return ;
7 }
8
9 float startX = startPoint.X;
10 float startY = startPoint.Y;
11 float endX = endPoint.X;
12 float endY = endPoint.Y;
13
14 // 计算2点之间的距离
15 double distance = Math.Abs(Math.Sqrt(
16 (startX - endX) * (startX - endX) +
17 (startY - endY) * (startY - endY)));
18
19 if (distance == 0 )
20 {
21 return ;
22 }
23
24 // 计算起始点至终点距离为radius的坐标点
25 startX = ( float )(radius / distance * (endX - startX)) + startX;
26 startY = ( float )(radius / distance * (endY - startY)) + startY;
27
28 // 计算终点至起始点距离为radius的坐标点
29 endX = ( float )(radius / distance * (startX - endX)) + endX;
30 endY = ( float )(radius / distance * (startY - endY)) + endY;
31
32 // 计算箭头顶点外的2个角的坐标点
33 double xa = endX + length * ((startX - endX)
34 + (startY - endY) / realtiveValue) / distance;
35 double ya = endY + length * ((startY - endY)
36 - (startX - endX) / realtiveValue) / distance;
37 double xb = endX + length * ((startX - endX)
38 - (startY - endY) / realtiveValue) / distance;
39 double yb = endY + length * ((startY - endY)
40 + (startX - endX) / realtiveValue) / distance;
41
42 // 绘制箭头图形
43 PointF[] polygonPoints = {
44 new PointF(endX , endY),
45 new PointF( ( float )xa , ( float )ya),
46 new PointF( ( float )xb , ( float )yb)};
47
48 graphics.DrawLine( new Pen( new SolidBrush(Color.Red), 2 ),
49 startX, startY, endX, endY);
50
51 graphics.DrawPolygon(Pens.Red, polygonPoints);
52 graphics.FillPolygon( new SolidBrush(Color.Red), polygonPoints);
53 }
以下为本人用系统自带的绘图工具绘制的,不堪入目,汗,还真不是搞图片的料子,仅仅是能够让读者更加清晰明了的知道怎么计算的坐标。
先通过数学的方法获取直线与起始圆周、结束圆周的切割点,分别将切割点设置为相应的起始点和结束点。
//计算起始点至终点距离为radius的坐标点
25 startX = (float)(radius / distance * (endX - startX)) + startX;
26 startY = (float)(radius / distance * (endY - startY)) + startY;
27
28 //计算终点至起始点距离为radius的坐标点
29 endX = (float)(radius / distance * (startX - endX)) + endX;
30 endY = (float)(radius / distance * (startY - endY)) + endY;
然后通过graphics.DrawLine(new Pen(new SolidBrush(Color.Red), 2), startX, startY, endX, endY);绘制重新设置后的起始点到结束点的直线。
可能,你会想:为什么不直接用原始的起始点和结束点直接绘制直线?这就会牵涉到直线覆盖圆的某些部分的现象,并且箭头的终点是指在圆周的中心点,而不是直线与圆周的切割点,如下所示:
因此,将起始点和终点重新设置为直线与圆周的相应的交点,这样,绘制的直线不会覆盖到圆周上,并且箭头也固定在直线与圆周相交的点上。如下为正常显示的图:
箭头算法这里就不再讲了,因为已经讲了一次..........
(三)坐标换行算法
这个是本人在近期项目中的测试路线算法的第二种需求中的算法,后来这种需求被推到、不用了,汗,哥写算法写得累啊。开始花了点时间想了出来,后来突然想要记起这个算法时,却怎么也想不起来了。不过,幸好哥保存了一个项目副本,哈哈,哥可是有预感啊,谢天谢地!.......
以下的为改进版的换行坐标算法(当前行的绘制点的个数等于指定的perRowPointNumber时候,自动换行绘制,绘制方向改变):
2 {
3 /// <summary>
4 /// 计算颜色集合点的所有二维坐标
5 /// </summary>
6 /// <param name="colorPoints"> 颜色点的集合 </param>
7 /// <param name="startPoint"> 起始点 </param>
8 /// <param name="horizontalSpan"> 水平间隔 </param>
9 /// <param name="verticalSpan"> 垂直间隔 </param>
10 /// <param name="perRowPointNumber"> 每行点的个数 </param>
11 /// <returns></returns>
12 public static List < ColorPoint > Calculate(List < ColorPoint > colorPoints,
13 Point startPoint, int horizontalSpan, int verticalSpan, int perRowPointNumber)
14 {
15 if (colorPoints == null || colorPoints.Count == 0 )
16 {
17 return new List < ColorPoint > ();
18 }
19
20 bool towardRight = true ;
21 byte currentRowPointCount = 0 ;
22 float left = startPoint.X, top = startPoint.Y;
23
24 int length = colorPoints.Count;
25
26 for ( int index = 0 ; index < length; index ++ )
27 {
28 if (index == 0 )
29 {
30 // 设置起始点的坐标
31 colorPoints[index].Point = new PointF(left, top);
32 currentRowPointCount ++ ;
33 continue ;
34 }
35 else if (currentRowPointCount < perRowPointNumber)
36 {
37 // 当状态朝右时,增加水平距离,否则减少水平距离
38 left += towardRight ? horizontalSpan : - horizontalSpan;
39 currentRowPointCount ++ ;
40 }
41 else
42 {
43 // 当前行的点个数为perRowPointNumber时,设置为1(个),方向改变,垂直间隔增加
44 currentRowPointCount = 1 ;
45 towardRight = ! towardRight;
46 top += verticalSpan;
47 }
48 colorPoints[index].Point = new PointF(left, top);
49 }
50
51 return colorPoints;
52 }
53 }
26-49为循环设置点的坐标,注释比较清晰,这里就不再讲解。
总结
这里主要是将3个算法整合到一起进行运用,以一种图形的效果更加清晰的展现相应算法的结果。首先,对箭头算法灵活的运用,并且考虑圆周的半径来重新计算起始点和结束点,避免不必要的bug。其次,是不重复随机颜色数组的算法实现以及运用。再次,是对坐标换行算法的实现和运用。本文中难免会有bug,肯定有本人考虑不周全的地方,希望读者见谅。如有什么疑问,请留言............
源代码下载:算法整合的图形显示源代码