WPF 制作雷达扫描图
实现一个雷达扫描图。
源代码在TK_King/雷达 (gitee.com),自行下载就好了
制作思路
- 绘制圆形(或者称之轮)
- 绘制分割线
- 绘制扫描范围
- 添加扫描点
具体实现
首先我们使用自定义的控件。你可以使用vs自动添加,也可以手动创建类。注意手动创建时要创建Themes/Generic.xaml的文件路径哦。
控件继承自itemscontrol,取名叫做Radar。
我们第一步思考如何实现圆形或者轮,特别是等距的轮。
我们可以使用简单的itemscontrol的WPF控件,通过自定义ItemTemplate就可以简单的创建了。
因为要显示圆,所以使用Ellipse是最简单的事情。
又因为要在同一个区域内,显示同心圆,我们将面板改为Grid,利用叠加的特性去构造同心圆。
既然我们用了itemscontrol 来承载圈轮,直接让这个圈可自定义呢?
所以,我们构造一个集合依赖属性。关于集合依赖属性我们可以参加MSDN集合类型依赖属性 - WPF .NET | Microsoft Docs
/// <summary>
/// 每圈的大小
/// </summary>
public FreezableCollection<RadarSize> RadarCircle
{
get { return (FreezableCollection<RadarSize>)GetValue(RadarCircleProperty); }
set { SetValue(RadarCircleProperty, value); }
} /// <summary>
/// 每圈的大小
/// </summary>
public static readonly DependencyProperty RadarCircleProperty =
DependencyProperty.Register("RadarCircle", typeof(FreezableCollection<RadarSize>), typeof(Radar), new PropertyMetadata(new PropertyChangedCallback(OnRadarCircelValueChanged)));
对应泛型类可以参考源代码,基本元素就是绑定ellipse的参数
<ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic" ItemsSource="{TemplateBinding RadarCircle }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Width}" Height="{Binding Height}" Stroke="{Binding Color}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
哇啦,图像就出来了。

同理,我们创建分割线也是同样的过程。
对于分割线的切割算法,我们使用圆上点的坐标可以通过( rcos,rsin)=》(x,y) ,也就是极坐标。
关于此部分代码是放在布局块内ArrangeOverride,也可以放置在OnReader。
下面是局部代码,完整可以参考源代码
var angle = 180.0 / 6;
circlesize = size.Height > size.Width ? size.Width : size.Height;
RadarFillWidth = circlesize;
var midx = circlesize / 2.0;
var midy = circlesize / 2.0;
circlesize = circlesize / 2;
RadarRadius = circlesize;
//默认为6个
for (int i = 0; i < 6; i++)
{
var baseangel = angle * i;
var l1 = new Point(midx + circlesize * Math.Cos(Rad(baseangel)), midy - circlesize * Math.Sin(Rad(baseangel)));
var half = baseangel + 180;
var l2 = new Point(midx + circlesize * Math.Cos(Rad(half)), midy - circlesize * Math.Sin(Rad(half)));
RadarLineSize radarLine = new RadarLineSize();
radarLine.Start = l1;
radarLine.End = l2;
radarLine.Color = RadarLineColor;
RadarLine.Add(radarLine);
}
return size;
依赖属性
/// <summary>
/// 雷达图的分割线,目前固定为6,可以自行修改
/// </summary>
public FreezableCollection<RadarLineSize> RadarLine
{
get { return (FreezableCollection<RadarLineSize>)GetValue(RadarLineProperty); }
set { SetValue(RadarLineProperty, value); }
} /// <summary>
/// 雷达图的分割线,目前固定为6,可以自行修改
/// </summary>
public static readonly DependencyProperty RadarLineProperty =
DependencyProperty.Register("RadarLine", typeof(FreezableCollection<RadarLineSize>), typeof(Radar));
xaml代码
<ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic2" ItemsSource="{TemplateBinding RadarLine }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}" Stroke="{Binding Color}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

下一步就是扇形扫描了。
我们使用一个完整的圆,将其内部颜色填充为线性刷就可以得到一个效果不错的扫描了。
/// <summary>
/// 雷达扫描的颜色
/// </summary>
public Brush RadarColor
{
get { return (Brush)GetValue(RadarColorProperty); }
set { SetValue(RadarColorProperty, value); }
} /// <summary>
/// 雷达扫描的颜色
/// </summary>
public static readonly DependencyProperty RadarColorProperty =
DependencyProperty.Register("RadarColor", typeof(Brush), typeof(Radar));
为了更好的定义这个圆,我们将radar的template使用grid面板等距分成四个区域(其实没啥用,主要是为了扇形扫描时做圆心选择的line,也可以不分成四个)。
在考虑动画,只需要做圆形360的选择就可以了。为了更好应用,我们创一个paly的依赖属性来播放动画。
/// <summary>
/// 是否播放动画
/// </summary>
public bool Play
{
get { return (bool)GetValue(PlayProperty); }
set { SetValue(PlayProperty, value); }
} /// <summary>
/// 是否播放动画
/// </summary>
public static readonly DependencyProperty PlayProperty =
DependencyProperty.Register("Play", typeof(bool), typeof(Radar), new PropertyMetadata(false));
xaml代码( 部分)
<Style.Resources>
<LinearGradientBrush x:Key="radarcolor" StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Lime" />
<GradientStop Offset="0.5" Color="Transparent" />
</LinearGradientBrush>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Radar}">
<Grid x:Name="grid" >
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic" ItemsSource="{TemplateBinding RadarCircle }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Width}" Height="{Binding Height}" Stroke="{Binding Color}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl Grid.ColumnSpan="2" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="ic2" ItemsSource="{TemplateBinding RadarLine }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding Start.X}" Y1="{Binding Start.Y}" X2="{Binding End.X}" Y2="{Binding End.Y}" Stroke="{Binding Color}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Ellipse Fill="{TemplateBinding RadarColor}" Grid.ColumnSpan="2" Grid.RowSpan="2" x:Name="ep" RenderTransformOrigin="0.5,0.5" Width="{TemplateBinding RadarFillWidth}" Height="{TemplateBinding RadarFillWidth}">
<Ellipse.RenderTransform>
<RotateTransform x:Name="rtf" />
</Ellipse.RenderTransform>
</Ellipse>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Play" Value="True">
<Trigger.EnterActions>
<BeginStoryboard x:Name="bs" >
<Storyboard >
<DoubleAnimation Storyboard.TargetName="rtf" Storyboard.TargetProperty="Angle" From="0" To="360" Duration="0:0:2" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
<Trigger Property="Play" Value="False">
<Trigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="bs"/>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
效果

那么剩下就是扫描点的操作。
因为我们的控件是继承ItemsControl,我们到现在还没有利用ItemsSource这个属性。
所以我们要制作一个子控件来呈现扫描点。
由于子控件较为简单,只不过是一个圆而已。我们就让子控件继承Control就好了。
一切从简,我们不弄布局这一套了,直接在父控件中使用Canvas面板,子控件增加属性Left,Top这两个依赖属性。
重点说一下,子控件中存在一个linscar的方法,是为了将点如果在雷达外侧时,按照同角度缩放到最外层的方法。就是通过半径重新计算一边极坐标。
/// <summary>
/// 线性缩放
/// </summary>
/// <param name="size">半径</param>
internal void LineScar(double size)
{
var midpoint = new Vector(size, size);
var vp = new Vector(Left, Top);
var sub = vp - midpoint;
var angle = Vector.AngleBetween(sub, new Vector(size, 1));
angle = angle > 0 ? angle : angle + 360;
//距离大于半径,根据半径重新绘制
if (sub.Length >= size)
{
Top = size - size * Math.Sin(Rad(angle)) - Width / 2;
Left = size + size * Math.Cos(Rad(angle)) - Width / 2;
}
}
那么在父项中如何摆放呢?
我们刚才说父项使用canvas绘图,所以我们在radar中修改itempanel的面板属性,下面代码存在于父项xaml
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
子项代码如下,比较少就贴了
xaml代码
<Style TargetType="local:RadarItem">
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Padding" Value="0" />
<Setter Property="Margin" Value="0" />
<Setter Property="Canvas.Top" Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=Top}" />
<Setter Property="Canvas.Left" Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=Left}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:RadarItem">
<Border >
<Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Color}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
radarItem
/// <summary>
/// 雷达子项
/// </summary>
public class RadarItem : Control
{ static RadarItem()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(RadarItem), new FrameworkPropertyMetadata(typeof(RadarItem)));
}
public RadarItem()
{ } /// <summary>
/// 转弧度
/// </summary>
/// <param name="val">角度</param>
/// <returns>弧度制</returns>
double Rad(double val)
{
return val * Math.PI / 180;
}
/// <summary>
/// 线性缩放
/// </summary>
/// <param name="size">半径</param>
internal void LineScar(double size)
{
var midpoint = new Vector(size, size);
var vp = new Vector(Left, Top);
var sub = vp - midpoint;
var angle = Vector.AngleBetween(sub, new Vector(size, 1));
angle = angle > 0 ? angle : angle + 360;
//距离大于半径,根据半径重新绘制
if (sub.Length >= size)
{
Top = size - size * Math.Sin(Rad(angle)) - Width / 2;
Left = size + size * Math.Cos(Rad(angle)) - Width / 2;
}
} /// <summary>
/// 顶部距离,用canvas.top绘制
/// </summary>
public double Top
{
get { return (double)GetValue(TopProperty); }
set { SetValue(TopProperty, value); }
} /// <summary>
/// 顶部距离,用canvas.top绘制
/// </summary>
public static readonly DependencyProperty TopProperty =
DependencyProperty.Register("Top", typeof(double), typeof(RadarItem), new PropertyMetadata(0.0)); /// <summary>
/// 左侧距离,用于canvas.left绘制
/// </summary>
public double Left
{
get { return (double)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
} /// <summary>
/// 左侧距离,用于canvas.left绘制
/// </summary>
public static readonly DependencyProperty LeftProperty =
DependencyProperty.Register("Left", typeof(double), typeof(RadarItem), new PropertyMetadata(0.0)); /// <summary>
/// 填充颜色
/// </summary>
public Brush Color
{
get { return (Brush)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
} /// <summary>
/// 填充颜色
/// </summary>
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register("Color", typeof(Brush), typeof(RadarItem), new PropertyMetadata(new SolidColorBrush(Colors.Red)));
}
于是乎我们就得到了一个雷达扫描图

WPF 制作雷达扫描图的更多相关文章
- Qt自定义控件之仪表盘3--雷达扫描图
1.设计思想 雷达扫描图,在影视作品中见到较多,比如飞机雷达.舰艇雷达,有一个扫描线转圈代表雷达一周旋转或一个批次的收发,发现目标就在表盘上标记位置.和汽车仪表盘类似,汽车仪表盘有底盘背景图.同圆.刻 ...
- 在Excel中制作雷达图
雷达图的作用 雷达图是专门用来进行多指标体系比较分析的专业图表.从雷达图中可以看出指标的实际值与参照值的偏离程度,从而为分析者提供有益的信息.雷达图一般用于成绩展示.效果对比量化.多维数据对比等等,只 ...
- python批量制作雷达图
老板要画雷达图,但是数据好多组怎么办?不能一个一个点excel去画吧,那么可以利用python进行批量制作,得到样式如下: 首先制作一个演示的excel,评分为excel随机数生成: 1 =INT(( ...
- WPF制作的小型笔记本
WPF制作的小型笔记本-仿有道云笔记 楼主所在的公司不允许下载外部资源, 不允许私自安装应用程序, 平时记录东西都是用记事本,时间久了很难找到以前记的东西. 平时在家都用有道笔记, 因此就模仿着做了一 ...
- WPF制作表示透明区域的马赛克画刷
最近在用WPF制作一款软件,需要像ps一样表示透明区域,于是制作了一个马赛克背景的style.实现比较简单,那么过程和思路就不表了,直接上代码 <DrawingBrush TileMode=&q ...
- WPF模拟雷达界面效果图
原文:WPF模拟雷达界面效果图 iPad塔防的防守兵的效果很炫,2个小时用WPF模拟了一个. 效果图: 关键代码: <Grid> <Grid.Background> <I ...
- WPF制作的小时钟
原文:WPF制作的小时钟 周末无事, 看到WEB QQ上的小时钟挺可爱的, 于是寻思着用WPF模仿着做一个. 先看下WEB QQ的图: 打开VS, 开始动工. 建立好项目后, 面对一个空荡荡的页面, ...
- WPF制作Logo,很爽,今后在应用程序中加入Logo轻松,省事!
原文:WPF制作Logo,很爽,今后在应用程序中加入Logo轻松,省事! 这是效果: XAML代码:<Viewbox Width="723.955078" Height=&q ...
- WPF制作的党旗
原文:WPF制作的党旗 --------------------------------------------------------------------------------引用或转载时请保 ...
随机推荐
- [Errno 14] curl#6 - "Could not resolve host: mirrors.cloud.aliyuncs.com; Name or service not known"
修改/etc/resolv.conf文件 [root@lihui ~]# vi /etc/resolv.conf nameserver 8.8.8.8 nameserver 114.114.114.1 ...
- 控制算法的划分(自适应控制、预测控制、模糊控制等,PID等;蚁群算法、神经网络,还有机器学习、人工智能中的很多方法)
一般来说,控制器的设计,分为控制框架的选取,跟参数的优化.自适应控制.预测控制.模糊控制等,跟PID一样,是控制算法(我习惯称为控制框架). 而粒子群.遗传算法(类似的还有蚁群算法.神经网络,还有机器 ...
- (1/2)Canvas的交互&存为图片-基本篇
前言 公司的产品同学看到朋友圈疯传的这张图后.一拍脑袋,决定做个H5版本的来推广一波. 需求如下: 文字变成可以点击的,而且还要能够变色(闪瞎有木有) 中间的姓名换成用户的微信头像 点击button后 ...
- 编写大型项目web页面 从写web登陆页面开始
web页面搭建需要准备什么工具 首先我们会和设计师沟通 我们需要一些检验设计的工具 ps 自动裁图 自动测量工具 (我这里安利一下一个工具 我用的cutterman) sketch 可以使用阿里的工具 ...
- 小程序的初次遇见,使用mpvue搭建模板
由于公司业务需求的需要,在这一周需要开发小程序,加急看了下小程序的文档,发现用其原生来编写程序不是很顺手,公司前端用的技术栈是vue, 询问了谷哥和度娘发现大部分推荐了 wepy和 mpvue,对比了 ...
- vue2实现搜索结果中的搜索关键字高亮
// 筛选变色 brightenKeyword(val, keyword) { val = val + ''; if (val.indexOf(keyword) !== -1 && k ...
- Java/C++实现观察者模式--股票价格
当股票的价格上涨或下降5%时,会通知持有该股票的股民,当股民听到价格上涨的消息时会买股票,当价格下降时会大哭一场. 类图: Java代码: public class Investor implemen ...
- git总是需要输入用户名密码问题解决
解决办法: git bash进入你的项目目录,输入: git config --global credential.helper store 然后你会在你本地生成一个文本,上边记录你的账号和密码.当然 ...
- 在 MarkDown 中添加表格(例如:在 CSDN 中添加表格)
内容 一.使用 Markdown 创建表格(例如:在 CSDN 中创建表格) 1. 表格格式 对齐方式 -: 设置内容和标题栏居右对齐: :- 设置内容和标题栏居左对齐: :-: 设置内容和标题栏居中 ...
- Mybatis实现批量添加操作
Mybatis实现批量添加操作 学习内容: 1. 使用 2. 代码实现 2.1 UserMapper.java 接口 2.2 UserMapper.xml 总结: 学习内容: 1. 使用 这里通过动态 ...