WPF实现雷达图

程序员有二十年 2024-05-18 19:33:30

WPF 实现雷达图

控件名:ChartRadar

作 者:WPFDevelopersOrg - 驚鏵

原文链接[1]:https://github.com/WPFDevelopersOrg/WPFDevelopers

码云链接[2]:https://gitee.com/WPFDevelopersOrg/WPFDevelopers

框架支持.NET4 至 .NET8;Visual Studio 2022;接着上一篇雷达图是一种显示数据点及其之间变化的方式。

1)修改 ChartRadar 代码如下:

ChartRadar 类继承自上一篇的 ChartBase 只是为了使用 Datas 与一些属性和重写了 OnRender 方法,用于在控件上绘制雷达图。

OnRender 方法首先检查 Datas 是否存在,如果没有数据则直接返回。然后设置一些绘图相关的属性,并根据数据绘制雷达图的各个点和连接线。

DrawPoints 方法用于绘制雷达图的多边形轮廓。它接受圆的半径和绘图上下文作为参数。通过调用 GetPolygonPoint 方法获取多边形的顶点,并使用 StreamGeometry 对象绘制多边形。

GetPolygonPoint 方法用于计算多边形的顶点坐标。接受中心点、半径和绘图上下文(可选)作为参数。通过遍历数据项计算每个顶点的坐标,并根据需要在顶点处绘制文本。最后返回顶点坐标集合。

OnRender 方法:

计算数据项在雷达图中对应的点坐标,并将其添加到 points 集合中。根据计算出的点坐标创建一个矩形 rect,并将其添加到 rects 集合中。创建一个稍微扩大的矩形 nRect,并将其与对应的数据项信息添加到 dicts 字典中。

创建一个 StreamGeometry 对象,用于定义要绘制的几何图形路径。

在使用 StreamGeometry 之前,通过 streamGeometry.Open() 方法获取一个 StreamGeometryContext,以便开始定义图形路径。

遍历数据集 Datas,对每个数据项执行以下操作:

使用 geometryContext.BeginFigure 和 geometryContext.PolyLineTo 方法来绘制多边形,连接 points 中的点。

冻结 streamGeometry,以便提高性能和安全性。

创建一个填充颜色为主题色的矩形 rectBrush,并设置透明度为0.5。

使用 DrawingContext.DrawGeometry 方法将雷达图的几何路径填充为指定的颜色,使用 myPen 对象定义边界线条。

创建一个画笔 drawingPen,用于绘制每个数据点的边界线条,生成笔画的粗细值为2,笔刷为预定义的 NormalBrush。

创建一个背景色刷子 backgroupBrush,颜色为预定义的背景色。

遍历 rects 集合中的每个矩形,并使用 DrawingContext.DrawGeometry 方法绘制每个点位的圆。

using System;using System.Collections.Generic;using System.Linq;using System.Windows;using System.Windows.Media;using WPFDevelopers.Helpers;namespace WPFDevelopers.Controls{public ChartRadar : ChartBase {private PointCollection _points;private double _h, _w;static ChartRadar() { DefaultStyleKeyProperty.OverrideMetadata(typeof(ChartRadar),new FrameworkPropertyMetadata(typeof(ChartRadar))); }protected override void OnRender(DrawingContext drawingContext) {if (Datas == || Datas.Count() == 0)return; SnapsToDevicePixels = true; UseLayoutRounding = true;var dicts = new Dictionary<Rect, string>();var rects = new List<Rect>();var max = Convert.ToInt32(Datas.Max(kvp => kvp.Value)) + 50;double v = StartX;for (var i = 0; i < Rows; i++) { DrawPoints(v, drawingContext, i == Rows - 1); v += StartX; }var myPen = new Pen { Thickness = 3, Brush = NormalBrush }; myPen.Freeze();var streamGeometry = new StreamGeometry();using (var geometryContext = streamGeometry.Open()) {var points = new PointCollection();short index = 0;foreach (var item in Datas) {if (index < _points.Count) {var startPoint = _points[index];var point = new Point((startPoint.X - _w) / max * item.Value + _w, (startPoint.Y - _h) / max * item.Value + _h); points.Add(point);var ellipsePoint = new Point(point.X - EllipseSize / 2, point.Y - EllipseSize / 2);var rect = new Rect(ellipsePoint, new Size(EllipseSize, EllipseSize)); rects.Add(rect);var nRect = new Rect(rect.Left - EllipsePadding, rect.Top - EllipsePadding, rect.Width + EllipsePadding, rect.Height + EllipsePadding); dicts.Add(nRect, $"{item.Key} : {item.Value}"); } index++; } geometryContext.BeginFigure(points[points.Count - 1], true, true); geometryContext.PolyLineTo(points, true, true); } PointCache = dicts; streamGeometry.Freeze();var color = (Color)Application.Current.TryFindResource("WD.PrimaryNormalColor");var rectBrush = new SolidColorBrush(color); rectBrush.Opacity = 0.5; rectBrush.Freeze(); drawingContext.DrawGeometry(rectBrush, myPen, streamGeometry);var drawingPen = new Pen { Thickness = 2, Brush = NormalBrush }; drawingPen.Freeze();var backgroupBrush = new SolidColorBrush() { Color = (Color)Application.Current.TryFindResource("WD.BackgroundColor") }; backgroupBrush.Freeze();foreach (var item in rects) {var ellipseGeom = new EllipseGeometry(item); drawingContext.DrawGeometry(backgroupBrush, drawingPen, ellipseGeom); } }private void DrawPoints(double circleRadius, DrawingContext drawingContext, bool isDrawText = false) {var myPen = new Pen { Thickness = 1, Brush = Application.Current.TryFindResource("WD.ChartXAxisSolidColorBrush") as Brush }; myPen.Freeze();var streamGeometry = new StreamGeometry();using (var geometryContext = streamGeometry.Open()) { _h = ActualHeight / 2; _w = ActualWidth / 2;if (isDrawText) _points = GetPolygonPoint(new Point(_w, _h), circleRadius, drawingContext);else _points = GetPolygonPoint(new Point(_w, _h), circleRadius); geometryContext.BeginFigure(_points[_points.Count - 1], true, true); geometryContext.PolyLineTo(_points, true, true); } streamGeometry.Freeze(); drawingContext.DrawGeometry(, myPen, streamGeometry); }private PointCollection GetPolygonPoint(Point center, double r, DrawingContext drawingContext = ) {double g = 18;double perangle = 360 / Datas.Count();var pi = Math.PI;var values = new List<Point>();foreach (var item in Datas) {var p2 = new Point(r * Math.Cos(g * pi / 180) + center.X, r * Math.Sin(g * pi / 180) + center.Y);if (drawingContext != ) {var formattedText = DrawingContextHelper.GetFormattedText(item.Key, ControlsHelper.PrimaryNormalBrush, flowDirection: FlowDirection.LeftToRight, textSize: 20.001D);if (p2.Y > center.Y && p2.X < center.X) drawingContext.DrawText(formattedText,new Point(p2.X - formattedText.Width - 5, p2.Y - formattedText.Height / 2));else if (p2.Y < center.Y && p2.X > center.X) drawingContext.DrawText(formattedText, new Point(p2.X, p2.Y - formattedText.Height));else if (p2.Y < center.Y && p2.X < center.X) drawingContext.DrawText(formattedText,new Point(p2.X - formattedText.Width - 5, p2.Y - formattedText.Height));else if (p2.Y < center.Y && p2.X == center.X) drawingContext.DrawText(formattedText,new Point(p2.X - formattedText.Width, p2.Y - formattedText.Height));else drawingContext.DrawText(formattedText, new Point(p2.X, p2.Y)); } values.Add(p2); g += perangle; }var pcollect = new PointCollection(values);return pcollect; } }}

2)示例 ChartRadarExample.xaml 代码如下:

<BorderWidth="700"Height="500"Background="{DynamicResource WD.BackgroundSolidColorBrush}"><Grid Margin="20,10"><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition Width="40" /></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="40" /><RowDefinition /><RowDefinition Height="auto" /></Grid.RowDefinitions><WrapPanel><RectangleWidth="6"Height="26"Fill="Black" /><TextBlockPadding="10,0"FontSize="24"FontWeight="Black"Text="能力图" /><TextBlockVerticalAlignment="Center"FontSize="18"FontWeight="Black"Foreground="#2B579A"Text="{Binding NowPlayerName, RelativeSource={RelativeSource AncestorType=local:ChartRadarExample}}" /></WrapPanel><wd:ChartRadarGrid.Row="1"Grid.Column="0"Datas="{Binding Datas, RelativeSource={RelativeSource AncestorType=local:ChartRadarExample}}" /><ButtonGrid.Row="2"Width="200"VerticalAlignment="Bottom"Click="Button_Click"Content="刷新"Style="{StaticResource WD.PrimaryButton}" /></Grid></Border>

3)示例 ChartRadarExample.xaml.cs 代码如下:

定义一个名为 Datas 的公共属性,类型为 IEnumerable<KeyValuePair<string, double>>,用于设置雷达图表中的数据。这个属性使用了依赖属性,可以在 XAML 中进行绑定。

NowPlayerName 属性用于显示当前选定的玩家的名称。

Players 列表存储了不同的玩家对象,每个玩家都有姓名和各项属性值,如击杀、助攻等。

在构造函数中,初始化了几个玩家对象,并将它们添加到 Players 列表中。然后,通过反射获取了玩家对象的属性信息,并将属性名称和属性值转换为 KeyValuePair<string, double>,并存储在 collectionList 中。

在构造函数末尾,将 Datas 设置为 collectionList 的第一个元素(即第一个玩家的属性数据),并将 NowPlayerName 设置为第一个玩家的姓名。

Button_Click 方法用于处理按钮点击事件,切换到下一个玩家的数据,并更新界面上的显示。

public partial ChartRadarExample : UserControl {public IEnumerable<KeyValuePair<string, double>> Datas {get { return (IEnumerable<KeyValuePair<string, double>>)GetValue(DatasProperty); }set { SetValue(DatasProperty, value); } }public static readonly DependencyProperty DatasProperty = DependencyProperty.Register("Datas", typeof(IEnumerable<KeyValuePair<string, double>>), typeof(ChartRadarExample), new PropertyMetadata()); List<Player> Players = new List<Player>();private int NowPlayerIndex = 0;public st
0 阅读:8

程序员有二十年

简介:感谢大家的关注