Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Multiplatform Radar Chart

5.00/5 (6 votes)
24 Jul 2018MIT4 min read 18.6K   685  
Implementation of radar chart for various .NET platforms

Introduction

There are many commercial and free chart implementations. One of the less frequently encountered chart types is a radar chart, sometimes also called a web chart. This type of chart can be used for presenting data but it also works great as user control for entering various types of variables (example: https://multi-sense.renault.pl/megane/pl_PL/).

This article describes a radar chart which is part of the open source Manufaktura.Controls library (http://musicengravingcontrols.com/). This library is known especially for its music notation components (described in this article: https://www.codeproject.com/Articles/1252423/Music-Notation-in-NET) but also contains tools and controls useful in other areas.

Like everything in the library, the radar chart is written in a cross-platform fashion. The implementation is similar to score renderers (described here: https://www.codeproject.com/Articles/1252423/Music-Notation-in-NET) – base classes handle most of the calculations to properly position chart elements like axes, sample points, etc. Then classes for specific platforms handle the drawing at previously calculated coordinates.

This example contains a fully usable WPF control and a simple proof-of-concept implementation for ASP.NET MVC (as Razor extension).

Architecture

Base Classes and WPF Implementation

The base class, RadarChartRenderer, contains a single public method RedrawChart which is used to draw a chart for a given set of samples. It also contains the following abstract methods:

  • ClearCanvas – clears a canvas
  • DrawAxisLabel  - draws axis label at given coordinates
  • DrawAxisLine – draws axis line at given coordinates
  • DrawPolygon – draws a polygon at given coordinates
  • DrawSample – draws a sample point (usually represented by a circle) at given coordinates
  • DrawTick – called only when number of axes is less than three. Draws tick lines
  • DrawWebLine – called only when number of axes is three or more – draws lines between ticks

RadarChartRenderer handles all the calculations needed to property position elements on canvas surface. It then calls abstract methods which have implementations for specific platforms. It also requires two generic arguments:

  • TControl – a type that represents a chart control or an object containing chart settings
  • TCanvas – a canvas object on which rendering will be performed. It can be any object, like control, XML element, StringBuilder, etc.

Let’s look at WPFRadarChart renderer. It draws chart on a Canvas element which is a part of RadarChart control. It also contains implementations of RadarChartRenderer’s abstract classes. This is an example of drawing axis lines:

C#
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);
}

As we can see, a Line object is created and added to canvas. This example uses the mechanism of databinding to bind line parameters (such as thickness) to control properties.

Thanks to the implementation based on data bindings, the chart is fully interactive. The user can drag samples on the chart and the change of value is immediately reflected in the model which is a collection of RadarChartSamples, bound to Samples property of RadarChart control.

RadarChartSample class has the following properties:

  • AxisDisplayName, AxisShortName - short and full name of axis
  • Value - sample value
  • ValidationMinValue, ValidationMaxValue - boundaries that define a range of valid values which is displayed on the above example as a green polygon
  • ValidationCompartments - work like ValidationMinValue and ValidationMaxValue but allows a programmer to specify many ranges of valid values on the same axis (i.e., 3-10, 15-20, etc.),
  • Scale - useful property if you want to create a chart that presents values with different orders of magnitude on different axes. Example: MaxValue property of the chart has a value of 100 and on one axis you have a variable which takes values from 0 to 100. The other axis presents a variable that has values from 0 to 10. You might want to set a Scale of 10 for the other axis to create a better user experience (the user will have the entire axis available for dragging sample points).

Razor Implementation

The architecture of this project allows the programmer to quickly implement radar chart on the other platforms using a single codebase. I created an HtmlSvgRadarChartRenderer class as a proof of concept for this architecture. This class renders charts as HTML SVG element:

C#
HtmlSvgRadarChartRenderer : RadarChartRenderer<HtmlRadarChartRendererSettings, XElement>

I implemented only most important methods, like DrawAxisLine:

C#
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);
}

We also have to write Razor extesions to use the chart in views:

C#
[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());
}

Now we can add the control to the view:

HTML
<div>
    @Html.RadialChartFor(x => x.Samples, Model.RadialChartSettings)
</div>

Without excessive styling, the effect looks like this:

The programmer now only has to properly style the control and optionally handle the user interaction logic.

License

This article, along with any associated source code and files, is licensed under The MIT License