In this article we present a Radial Range Indicator control for use in XAML-based UWP apps. RadialRangeIndicator is a circular control for graphically displaying a Range of values (from a minimum to a maximum) inside a scale, e.g. in a Dashboard. Circular and square controls are excellent citizens of adaptive user interfaces, since they scale easily and are independent of the screen orientation. RadialRangeIndicator is derived from PercentageRing and RadialGauge.

Here’s how a RadialRangeIndicator looks like:

Its main constituents are

the scale, i.e. the background arc,

the range, i.e. the foreground arc, and

the text.

All properties are implemented as dependency properties so you can bind to these in every way you like. Every change is immediately reflected in the UI. The control does not crash on unexpected values: it survives assignments like a maximum angle over 360 degrees, or any minimum value that is greater than its corresponding maximum. [note to self: add exception handling to the string.Format call that generates the Text ]

Here’s the list of configurable properties:



Scale related ScaleMinimum double Gets or sets the minimum value of the scale. ScaleMaximum double Gets or sets the maximum value of the scale. ScaleWidth double Gets or sets the width of the scale, in percentage of the radius. ScaleMinimumAngle int Gets or sets the start angle of the scale, which corresponds with the ScaleMinimum value, in degrees. ScaleMaximumAngle int Gets or sets the end angle of the scale, which corresponds with the ScaleMaximum value, in degrees. ScaleBrush Brush Gets or sets the brush for the scale. ScaleStartCap PenLineCap Gets or sets the StrokeStartCap for the Scale. ScaleEndCap PenLineCap Gets or sets the StrokeEndCap for the Scale. Range related RangeMinimum double Gets or sets the minimum value for the range. RangeMaximum double Gets or sets the maximum value for the range. RangeStepSize double Gets or sets the rounding interval for the range values. If the StepSize is zero, no rounding is applied. RangeBrush Brush Gets or sets the brush for the range. RangeStartCap PenLineCap Gets or sets the StrokeStartCap for the Range. RangeEndCap PenLineCap Gets or sets the StrokeEndCap for the Range. Text related TextBrush Brush Gets or sets the brush for the displayed value range. TextStringFormat string Gets or sets the text string format. Use {0} and {1} to display range minimum and maximum.

The core of the control’s default style template is a ViewBox with two Path instances with configurable PenLineCaps:

<ControlTemplate TargetType="local:RadialRangeIndicator"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Viewbox> <Grid x:Name="PART_Container" Height="200" Width="200" Background="Transparent"> <!-- Scale --> <Path Name="PART_Scale" Stroke="{TemplateBinding ScaleBrush}" StrokeThickness="{TemplateBinding ScaleWidth}" StrokeStartLineCap="{TemplateBinding ScaleStartCap}" StrokeEndLineCap="{TemplateBinding ScaleEndCap}" /> <!-- Range --> <Path Name="PART_Range" Stroke="{TemplateBinding RangeBrush}" StrokeThickness="{TemplateBinding ScaleWidth}" StrokeStartLineCap="{TemplateBinding RangeStartCap}" StrokeEndLineCap="{TemplateBinding RangeStartCap}" /> <!-- Value --> <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"> <TextBlock Name="PART_Text" Foreground="{TemplateBinding TextBrush}" FontSize="20" FontWeight="SemiBold" TextAlignment="Center" /> </StackPanel> </Grid> </Viewbox> </Border> </ControlTemplate>

The code-behind populates each of these Paths with an ArcSegment in a PathGeometry or a full circle EllipseGeometry. Here’s the code for the Range:

if (radialRangeIndicator.RangeMaximumValueAngle - radialRangeIndicator.NormalizedMinAngle == 360) { // Draw full circle. var eg = new EllipseGeometry { Center = new Point(Radius, Radius), RadiusX = Radius - (radialRangeIndicator.ScaleWidth / 2) }; eg.RadiusY = eg.RadiusX; range.Data = eg; } else { range.StrokeStartLineCap = radialRangeIndicator.RangeStartCap; range.StrokeEndLineCap = radialRangeIndicator.RangeEndCap; // Draw arc. var pg = new PathGeometry(); var pf = new PathFigure { IsClosed = false, StartPoint = radialRangeIndicator.ScalePoint(radialRangeIndicator.RangeMinimumValueAngle, middleOfScale) }; var seg = new ArcSegment { SweepDirection = SweepDirection.Clockwise, IsLargeArc = radialRangeIndicator.RangeMaximumValueAngle > (180 + radialRangeIndicator.RangeMinimumValueAngle), Size = new Size(middleOfScale, middleOfScale), Point = radialRangeIndicator.ScalePoint( Math.Min(radialRangeIndicator.RangeMaximumValueAngle, radialRangeIndicator.NormalizedMaxAngle), middleOfScale) }; pf.Segments.Add(seg); pg.Figures.Add(pf); range.Data = pg; }

For more info on the algorithms and calculations, please read the article on the Percentage Ring. After all, this Radial Range Indicator is the very same control, but with a variable start point for the Range.

The Gallery page of the sample app shows some more advanced usages and styles of the RadialRangeIndicator:

On the left, you see that a series of Radial Gauge Indicators can be used perfectly to indicate ranges inside (or outside) the scale of a Radial Gauge.

In the middle you see how to define a custom string format for the Text:

<controls:RadialRangeIndicator ScaleMinimumAngle="-150" ScaleMaximumAngle="150" ScaleBrush="Silver" TextStringFormat="{}{0}-{1} Å" TextBrush="{StaticResource PageForegroundBrush}" />

The instance in the middle also demonstrates how a DropShadowPanel control can be used inside a control’s template. There’s a white one around the Scale to smoothen the entire control, and a yellow one to add a glow effect to the Range:

<!-- Scale --> <toolkit:DropShadowPanel Color="White"> <Path Name="PART_Scale" Stroke="{TemplateBinding ScaleBrush}" StrokeThickness="{TemplateBinding ScaleWidth}" StrokeStartLineCap="{TemplateBinding ScaleStartCap}" StrokeEndLineCap="{TemplateBinding ScaleEndCap}" /> </toolkit:DropShadowPanel> <!-- Range --> <toolkit:DropShadowPanel Color="Yellow" BlurRadius="20"> <Path Name="PART_Range" Stroke="{TemplateBinding RangeBrush}" StrokeThickness="{TemplateBinding ScaleWidth}" StrokeStartLineCap="{TemplateBinding RangeStartCap}" StrokeEndLineCap="{TemplateBinding RangeStartCap}" /> </toolkit:DropShadowPanel>

Here’s an example of Radial Range Indicators in a more realistic app. They have a DropShadowPanel around the Scale, and a BackDrop underneath the Text to blur the background:

I also added a page with Radial Range Indicators inside a Simple Perfect Square. This gives an overview of the control in many different sizes and configurations, and allows to assess the performance when having multiple instances of it on the same page. On top of that, it’s also colorful and fun to look at:

If you want to start using the control, it’s available on NuGet. If you want to dive in its source code, it’s on GitHub.

Enjoy!