实现一个雷达扫描图。

源代码在TK_King/雷达 (gitee.com),自行下载就好了

制作思路

  1. 绘制圆形(或者称之轮)
  2. 绘制分割线
  3. 绘制扫描范围
  4. 添加扫描点

具体实现

首先我们使用自定义的控件。你可以使用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 制作雷达扫描图的更多相关文章

  1. Qt自定义控件之仪表盘3--雷达扫描图

    1.设计思想 雷达扫描图,在影视作品中见到较多,比如飞机雷达.舰艇雷达,有一个扫描线转圈代表雷达一周旋转或一个批次的收发,发现目标就在表盘上标记位置.和汽车仪表盘类似,汽车仪表盘有底盘背景图.同圆.刻 ...

  2. 在Excel中制作雷达图

    雷达图的作用 雷达图是专门用来进行多指标体系比较分析的专业图表.从雷达图中可以看出指标的实际值与参照值的偏离程度,从而为分析者提供有益的信息.雷达图一般用于成绩展示.效果对比量化.多维数据对比等等,只 ...

  3. python批量制作雷达图

    老板要画雷达图,但是数据好多组怎么办?不能一个一个点excel去画吧,那么可以利用python进行批量制作,得到样式如下: 首先制作一个演示的excel,评分为excel随机数生成: 1 =INT(( ...

  4. WPF制作的小型笔记本

    WPF制作的小型笔记本-仿有道云笔记 楼主所在的公司不允许下载外部资源, 不允许私自安装应用程序, 平时记录东西都是用记事本,时间久了很难找到以前记的东西. 平时在家都用有道笔记, 因此就模仿着做了一 ...

  5. WPF制作表示透明区域的马赛克画刷

    最近在用WPF制作一款软件,需要像ps一样表示透明区域,于是制作了一个马赛克背景的style.实现比较简单,那么过程和思路就不表了,直接上代码 <DrawingBrush TileMode=&q ...

  6. WPF模拟雷达界面效果图

    原文:WPF模拟雷达界面效果图 iPad塔防的防守兵的效果很炫,2个小时用WPF模拟了一个. 效果图: 关键代码: <Grid> <Grid.Background> <I ...

  7. WPF制作的小时钟

    原文:WPF制作的小时钟 周末无事, 看到WEB QQ上的小时钟挺可爱的, 于是寻思着用WPF模仿着做一个. 先看下WEB QQ的图: 打开VS, 开始动工. 建立好项目后, 面对一个空荡荡的页面, ...

  8. WPF制作Logo,很爽,今后在应用程序中加入Logo轻松,省事!

    原文:WPF制作Logo,很爽,今后在应用程序中加入Logo轻松,省事! 这是效果: XAML代码:<Viewbox Width="723.955078" Height=&q ...

  9. WPF制作的党旗

    原文:WPF制作的党旗 --------------------------------------------------------------------------------引用或转载时请保 ...

随机推荐

  1. Elasticsearch 是如何实现 Master 选举的?

    1.Elasticsearch 的选主是 ZenDiscovery 模块负责的,主要包含 Ping(节点之 间通过这个 RPC 来发现彼此)和 Unicast(单播模块包含一个主机列表以控制哪 些节点 ...

  2. 说说 Redis 哈希槽的概念?

    Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽, 集群的每个节点 ...

  3. 列举 spring 支持的事务管理类型?

    Spring 支持两种类型的事务管理: 1. 程序化事务管理:在此过程中,在编程的帮助下管理事务.它为您提供极大 的灵活性,但维护起来非常困难. 2. 声明式事务管理:在此,事务管理与业务代码分离.仅 ...

  4. vue中的全局组件和局部组件的应用

    1全局组件 vue框架: https://www.vue-js.com/?tab=all 组件树 solt 内置分发组件 应用见局部组件 2局部组件 2.1局部组件的使用:生子 - 挂子 - 用子 & ...

  5. Linux 安装jdk1.8

    Linux安装jdk1.8 总结一句话就是:下载jdk1.8 ==> 解压 ==> 配置环境变量. 一.  jdk的下载,这里有两种方法: 1.去Oracle官网下载. 2.jdk1.8的 ...

  6. Numpy计算逆矩阵求解线性方程组

    对于这样的线性方程组: x + y + z = 6 2y + 5z = -4 2x + 5y - z = 27 可以表示成矩阵的形式: 用公式可以表示为:Ax=b,其中A是矩阵,x和b都是列向量 逆矩 ...

  7. 切图崽的自我修养-[ES6] 迭代器Iterator浅析

    Iterator 这真是毅种循环 Iterator不是array,也不是set,不是map, 它不是一个实体,而是一种访问机制,是一个用来访问某个对象的接口规范,为各种不同的数据结构提供统一的访问机制 ...

  8. ES6-11学习笔记--Map

    Map:一种新的数据结构 常用方法 遍历 应用场景 WeakMap   常用方法: let map = new Map() let obj = { name: '张三' } map.set(obj, ...

  9. python-使用函数求余弦函数的近似值

    本题要求实现一个函数,用下列公式求cos(x)近似值,精确到最后一项的绝对值小于eps(绝对值小于eps的项不要加): cos(x)=0!x0​−2!x2​+4!x4​−6!x6​+... 函数接口定 ...

  10. 将百度地图Demo抽取出来安到自己的程序中

    今日所学: 使用百度地图ADK实现手机定位 [Android]使用百度.高德.腾讯地图SDK获取定位数据与屏幕截图分享到QQ_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili 代码获取SHA1 (2 ...