多平台雷达图
为各种 .NET 平台实现的雷达图
引言
市面上有许多商业和免费的图表实现。雷达图(有时也称为网络图)是一种不太常见的图表类型。这种图表可以用于展示数据,同时也非常适合作为用户控件来输入各种类型的变量(例如:https://multi-sense.renault.pl/megane/pl_PL/)。
本文介绍的雷达图是开源库 Manufaktura.Controls
(http://musicengravingcontrols.com/)的一部分。该库尤其以其音乐记谱组件(本文中有介绍:https://codeproject.org.cn/Articles/1252423/Music-Notation-in-NET)而闻名,但也包含在其他领域有用的工具和控件。
与库中的所有内容一样,雷达图采用跨平台方式编写。其实现方式类似于乐谱渲染器(参见此处:https://codeproject.org.cn/Articles/1252423/Music-Notation-in-NET)——基类处理大部分计算,以正确地定位图表元素,如坐标轴、样本点等。然后,特定平台的类负责在先前计算好的坐标上进行绘制。
本示例包含一个功能完整的 WPF 控件和一个用于 ASP.NET MVC(作为 Razor 扩展)的简单概念验证实现。
架构
基类和 WPF 实现
基类 RadarChartRenderer
包含一个公共方法 RedrawChart
,用于绘制给定样本集的图表。它还包含以下抽象方法:
ClearCanvas
– 清除画布DrawAxisLabel
- 在给定坐标处绘制坐标轴标签DrawAxisLine
– 在给定坐标处绘制坐标轴线DrawPolygon
– 在给定坐标处绘制多边形DrawSample
– 在给定坐标处绘制样本点(通常表示为圆)DrawTick
– 仅当轴的数量小于三个时调用。绘制刻度线DrawWebLine
– 仅当轴的数量等于或大于三个时调用——绘制刻度之间的线
RadarChartRenderer
负责在画布表面上正确放置元素所需的所有计算。然后,它会调用具有特定平台实现的抽象方法。它还需要两个泛型参数:
TControl
– 代表图表控件或包含图表设置的对象TCanvas
– 将执行渲染的画布对象。它可以是任何对象,如控件、XML 元素、StringBuilder
等。
让我们来看一下 WPFRadarChart
渲染器。它在 RadarChart
控件的 Canvas
元素上进行绘制。它还包含了 RadarChartRenderer
抽象类的实现。这是一个绘制坐标轴线的示例:
protected override void DrawAxisLine(Primitives.Point start, Primitives.Point end)
{
var line = new Line();
line.SetBinding(Shape.StrokeProperty,
new Binding(nameof(RadialChart.AxisStroke)) { Source = Control });
line.SetBinding(Shape.StrokeThicknessProperty,
new Binding(nameof(RadialChart.AxisStrokeThickness)) { Source = Control });
line.X1 = start.X;
line.Y1 = start.Y;
line.X2 = end.X;
line.Y2 = end.Y;
Canvas.Children.Add(line);
}
正如我们所见,创建了一个 Line
对象并将其添加到画布中。此示例使用数据绑定机制将线条参数(如粗细)绑定到控件属性。
由于基于数据绑定的实现,该图表具有完全的交互性。用户可以在图表上拖动样本点,值的变化会立即反映在模型中,该模型是绑定到 RadarChart
控件的 Samples
属性的 RadarChartSamples
集合。
RadarChartSample
类具有以下属性:
AxisDisplayName
,AxisShortName
- 轴的短名称和全称Value
- 样本值ValidationMinValue
,ValidationMaxValue
- 定义有效值范围的边界,在上面的示例中显示为绿色多边形ValidationCompartments
- 功能类似于ValidationMinValue
和ValidationMaxValue
,但允许程序员在同一轴上指定多个有效值范围(例如 3-10、15-20 等)。Scale
- 如果您想创建一个在不同轴上展示不同数量级值的图表,这个属性会很有用。例如:图表的MaxValue
属性值为 100,在一个轴上,您有一个变量取值范围是0
到100
。另一个轴展示一个变量,其值范围是 0 到 10。您可能希望为另一个轴设置Scale
为10
,以获得更好的用户体验(用户将有完整的轴用于拖动样本点)。
Razor 实现
该项目的架构允许程序员使用单一代码库快速地在其他平台上实现雷达图。我创建了一个 HtmlSvgRadarChartRenderer
类作为该架构的概念验证。此类将图表渲染为 HTML SVG 元素。
HtmlSvgRadarChartRenderer : RadarChartRenderer<HtmlRadarChartRendererSettings, XElement>
我只实现了最重要的几个方法,例如 DrawAxisLine
。
protected override void DrawAxisLine(Point start, Point end)
{
var element = new XElement("line",
new XAttribute("x1", start.X.ToStringInvariant()),
new XAttribute("y1", start.Y.ToStringInvariant()),
new XAttribute("x2", end.X.ToStringInvariant()),
new XAttribute("y2", end.Y.ToStringInvariant()),
new XAttribute("style", Control.AxisLinePen.ToCss()));
Canvas.Add(element);
}
我们还需要编写 Razor 扩展才能在视图中使用该图表。
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures",
Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString RadialChartFor<TModel>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, RadarChartSample[]>> expression, HtmlRadarChartRendererSettings settings)
{
if (expression == null) throw new ArgumentNullException(nameof(expression));
if (settings == null) throw new ArgumentNullException(nameof(settings));
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
RadarChartSample[] samples = metadata.Model == null ? null : metadata.Model as RadarChartSample[];
return RadialChartHelper(htmlHelper, samples, settings);
}
private static MvcHtmlString RadialChartHelper
(HtmlHelper helper, RadarChartSample[] samples, HtmlRadarChartRendererSettings settings)
{
var xElement = new XElement("svg");
xElement.Add(new XAttribute("style",
$"width:{settings.Width.ToStringInvariant()}px; height:{settings.Height.ToStringInvariant()}px;"));
new HtmlSvgRadarChartRenderer(settings, xElement).RedrawChart(samples);
return MvcHtmlString.Create(xElement.ToString());
}
现在我们可以将控件添加到视图中了。
<div>
@Html.RadialChartFor(x => x.Samples, Model.RadialChartSettings)
</div>
在没有过多样式的情况下,效果如下:
现在,程序员只需正确设置控件样式,并根据需要处理用户交互逻辑。