用Silverlight做雷达图

很多游戏都用雷达图来表示角色的能力值,比如主角的体智敏贤。接下来介绍一下我做的Silverlight雷达图还包含了动画功能。虽然很简单,但不失为一次很好的Silverlight开发体验。

示例:

首先创建一个叫Star的UserControl,作为独立可重用的组件。不需要改动前端的XAML Code,所有的绘画动作都有后台代码完成。假设现在是一个五星图,绘制五个端点的逻辑其实就是从正上方的点开始,每隔360/5放置下个点。

Silverlight有一个多边形的类Polygon可以很好的完成任务。可是这里选用更加通用的Path类主要是为动画效果,由于Polygon的端点无法绑定到Storyboard,在下面会有解释。

在Silverlight中可以用RotateTransform做到基于圆心的点的旋转。

 
  
var rotate = new RotateTransform();
rotate.Angle
= ( 360 / 5 );
rotate.Transform(
new Point());

如此重复5次五个点就可以定位了。由于Path支持许多复杂的图形,能力越大功能越通用也就意味着结构上的复杂。往Path里添加线段需要几个步骤:

1. 设置Path的绘图数据Path.Data为一个几何数据类型PathGeometry

2. PathGeometry可以包含若干个图形,比如同一个Path可以包含一个圆形和一个多边形。由于这里只需要画一个五角形所以只需要一个PathFigure。

3. PathFigure需要很多线段组成,我们往PathFigure.Segments里添加LineSegment

整个流程大致这样

 
  
var path = new Path { Data = new PathGeometry() };
var geo
= new PathGeometry();
path.Data
= geo;
var fig
= new PathFigure();
geo.Figures.Add(fig);
fig.StartPoint
= new Point();
var seg
= new LineSegment();
seg.Point
= new Point();
fig.Segments.Add(seg);

有了Path勾勒出的五角形就可以绘制线段或者填充了。Path使用起来虽然麻烦但是对动画的支持很好,接下来就可以体现它的优势了。

制作动画的关键是动画效果Animation以及动画播放器Storyboard。这里用到了点的位移所以选用PointAnimation。为了绑定多边形的点,Silverlight提供了强大的路径选择器,整个的语法就像解构上面添加点的过程。

 
  
var anima = new PointAnimation();
anima.To
= new Point();
anima.Duration
= TimeSpan.FromMilliseconds(AnimaDuration);
Storyboard.SetTarget(anima,
new Path());
Storyboard.SetTargetProperty(anima,
new PropertyPath(
"
(Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[0].(LineSegment.Point) " ));

注意最后的字符串,对比之前构建五星图的过程,不难看出这就是一个层层访问属性的路径,数字代表集合元素的位置。整个动画设置了终止点以及持续时间。

好了所有关键技术点都介绍完毕,我们就可以搭建整个五星图类,当然可以很方便的推广到一般雷达图。下面是注释和代码

 
  
public partial class Star : UserControl
{
Path instance;
// 内脏 // 显示的数据
Point center; // 图的中心
double radius; // 半径
const double AnimaDuration = 800 ; // 动画时长
Brush stroke_color = new SolidColorBrush(Colors.Black); // 骨架色
Brush fill_color = new SolidColorBrush(Colors.Blue); // 填充色

public Star()
{
InitializeComponent();
Loaded
+= new RoutedEventHandler(Star_Loaded);
}

// 画骨架,初始化内脏
void Star_Loaded( object sender, RoutedEventArgs e)
{
radius
= 300 / 2 ;
if (Height < Width)
radius
= 300 / 2 ;
center
= new Point( 300 / 2 , 300 / 2 );

double step = radius / 5 ;
for ( int i = 0 ; i < 5 ; i ++ )
{
var star
= AddStar(center, radius - i * step);
star.Stroke
= stroke_color;
}
AddLines(radius, center);
InitInstance(center);
}

// 设置五个0-1的值,按半径比例显示五个点的位置
public void SetStarValues( double ratio1, double ratio2, double ratio3, double ratio4, double ratio5)
{
var newPoints
= CalcStarByRatio(center, radius, ratio1, ratio2, ratio3, ratio4, ratio5);

var storyboard
= new Storyboard();

// 起始点和线段点要分开处理
for ( int i = 0 ; i < 6 ; i ++ )
{
var anima
= new PointAnimation();
anima.To
= newPoints[i % 5 ];
anima.Duration
= TimeSpan.FromMilliseconds(AnimaDuration);
Storyboard.SetTarget(anima, instance);
if (i == 5 )
Storyboard.SetTargetProperty(anima,
new PropertyPath( " (Path.Data).(PathGeometry.Figures)[0].(Point.StartPoint) " ));
else
Storyboard.SetTargetProperty(anima,
new PropertyPath( " (Path.Data).(PathGeometry.Figures)[0].(PathFigure.Segments)[ " + i + " ].(LineSegment.Point) " ));
storyboard.Children.Add(anima);
}
storyboard.Begin();
}

// 初始内脏,五个值都为0
private void InitInstance(Point center)
{
instance
= AddStar(center, 0 );
instance.Fill
= fill_color;
}

// 画直线
private void AddLines( double radius, Point center)
{
var outsidePoints
= CalcFiveVertice(center, radius);
foreach (var p in outsidePoints)
{
var line
= new Line();
line.X1
= p.X;
line.Y1
= p.Y;
line.X2
= center.X;
line.Y2
= center.Y;
line.Stroke
= stroke_color;
LayoutRoot.Children.Add(line);
}
}

// 画五角形的Path,可以用来填充或者画线段
Path AddStar(Point center, double radius)
{
var points
= CalcFiveVertice(center, radius);
var star
= new Path();
var geo
= new PathGeometry();
star.Data
= geo;
var fig
= new PathFigure();
geo.Figures.Add(fig);
fig.StartPoint
= points[ 0 ];
// 最后一个点要回到起始点
for ( int i = 1 ; i < points.Length + 1 ; i ++ )
{
var p
= points[i % points.Length];
var seg
= new LineSegment();
seg.Point
= p;
fig.Segments.Add(seg);
}

LayoutRoot.Children.Add(star);
return star;
}

// 定位点
internal static Point[] CalcStarByRatio(Point center, double radius, params double [] r)
{
var points
= new List < Point > ();
for ( int i = 0 ; i < r.Length; i ++ )
{
var radians
= i * 2 * Math.PI / r.Length + Math.PI / 2 ;
points.Add(
new Point(r[i] * Math.Cos(radians) * radius + center.X, - r[i] * Math.Sin(radians) * radius + center.Y));
}

return points.ToArray();
}

// 同比的五个点,助手方法
internal static Point[] CalcFiveVertice(Point center, double radius)
{
return CalcStarByRatio(center, radius, 1 , 1 , 1 , 1 , 1 );
}
}

这里要注意区分处理Path的起始点和普通点。只要在外部调用类的CalcStarValues并填入对应值就可以工作了。

PS: 1. 本程序在SL4中编译调试通过

2. 如果换用Polygon可以简化很多图形处理步骤,可惜Polygon的端点不是DependencyProperty,所以没有办法是用动画绑定了。

转载于:https://www.cnblogs.com/dragonpig/archive/2011/01/28/1946577.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值