Telerik UI for Windows Phone by Progress

In this topic we are going to create a radial gauge from scratch. We will see how to create ranges, set their properties, fill them with indicators and even write some code to synchronize

the ranges and indicators which comprise the gauge.

The initial design looks like this:

gauge Design

If you would like to get the XAML immediately, it can be found here.

In programming terms this image can be broken down like this. First there is an outer scale from 0 to 40 and two indicators pointing at values on it. This scale will be represented by RadialGaugeRange, one SegmentedRadialGaugeIndicator which is placed over the ticks, one MarkerGaugeIndicator (the diamond shaped element), one ArrowGaugeIndicator, two circle markers at 26 and 40 and one label marker at 26 (the red 26 number). Also notice that after about 24, the ticks become transparent.

Inside the outer scale there is an inner blue scale from 0 to 120 which also has a SegmentedRadialGaugeIndicator placed just above the ticks. Notice that only 0, 100, and 120 are notable values. There are two ways to display values only at these points. We can either use a GaugeValueToBrushConverter and use it inside the LabelTemplate which will make the unwanted labels transparent, or we can simply avoid drawing the labels at all and put four markers at the desired points. We will choose the first approach and create a value to brush converter.

The 6.59 and %90 are simple TextBlocks that will be bound to the value of the arrow indicator.

Let's start by defining the XAML for the inner scale. We start with the inner scale because the outer scale will be placed on top of it, notice how the white arrow is over the blue scale.

We define a radial gauge range like this:

CopyXAML
<gauges:RadialGaugeRange MaxValue="120" 
                         MaxAngle="115" 
                         LabelStep="20" 
                         TickStep="20" 
                         TickRadiusScale="0.60" 
                         LabelRadiusScale="0.50" 
                         SweepDirection="Counterclockwise" 
                         Height="400"
                         Width="400" 
                         x:Name="range2">
</gauges:RadialGaugeRange>

Notice how the SweepDirection property is set to CounterClockwise, the labels are displayed from right to left.

Next we need to define the templates for the ticks and the labels:

CopyXAML
<gauges:RadialGaugeRange.TickTemplate>
    <DataTemplate>
        <Rectangle Width="10"
                   Height="2"
                   Fill="{Binding Converter={StaticResource converter3}}" />
    </DataTemplate>
</gauges:RadialGaugeRange.TickTemplate>

<gauges:RadialGaugeRange.LabelTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding}"
                   FontSize="12"
                   Foreground="{Binding Converter={StaticResource converter2}}"
                   FontWeight="Bold" />
    </DataTemplate>
</gauges:RadialGaugeRange.LabelTemplate>

The Fill and Foreground properties of the Rectangle and TextBlock in the templates are simply bound. This works because in every tick and label template, the data context is the value with which the tick or label is associated. It is a double precision floating point number. There are also two converters involved in the binding. Converter 3 makes the last tick transparent and the one before the last a darker blue color. Converter 2 on the other hand makes all labels transparent with appropriate colors only for the 0, 100 and 120 values.

Let us see what the XAML looks like:

CopyXAML
<gauges:GaugeValueToBrushConverter x:Key="converter1" DefaultColor="Gray">
    <gauges:GaugeColorValueRange MinValue="25" MaxValue="40" Color="Transparent" />
</gauges:GaugeValueToBrushConverter>

After the ticks and labels are ready we need to add the segmented indicator that is a part of the scale. We define a SegmentedRadialGaugeIndicator with two segments:

CopyXAML
<gauges:SegmentedRadialGaugeIndicator Value="120"
                                      gauges:RadialGaugeRange.MaxAngle="115.5"
                                      gauges:RadialGaugeRange.IndicatorRadiusScale="0.63">
    <gauges:BarIndicatorSegment Thickness="4"
                                Length="0.8"
                                Stroke="#FF005676"/>
    <gauges:BarIndicatorSegment Thickness="4"
                                Length="4"
                                Stroke="{StaticResource PhoneAccentBrush}" />
</gauges:SegmentedRadialGaugeIndicator>

Finally we need to add the marker indicator with the "%" sign. We do it by creating a MarkerGaugeIndicator:

CopyXAML
<gauges:MarkerGaugeIndicator Value="60" gauges:RadialGaugeRange.MaxAngle="115" gauges:RadialGaugeRange.SweepDirection="Counterclockwise" gauges:RadialGaugeRange.IndicatorRadiusScale="0.5" IsMarkerRotated="False">
    <gauges:MarkerGaugeIndicator.MarkerTemplate>
        <DataTemplate>
            <TextBlock Text="%" FontSize="17" Foreground="{StaticResource PhoneAccentBrush}" />
        </DataTemplate>
    </gauges:MarkerGaugeIndicator.MarkerTemplate>
</gauges:MarkerGaugeIndicator>

This is it, the blue inner scale is ready, if everything is correct it should look like this:

inner Radial Gauge

Now we have to define the outer scale. We start with the range:

CopyXAML
<gauges:RadialGaugeRange MaxAngle="180" 
                         MaxValue="40" 
                         TickStep="3.35" 
                         LabelStep="10" 
                         LabelRadiusScale="0.86" 
                         TickRadiusScale="0.78" 
                         Width="400" 
                         Height="400" 
                         x:Name="range1">
</gauges:RadialGaugeRange>

Next are the tick and label templates:

CopyXAML
<gauges:RadialGaugeRange.TickTemplate>
    <DataTemplate>
        <Rectangle Width="10" 
                   Height="2" 
                   Fill="{Binding Converter={StaticResource converter1}}" />
    </DataTemplate>
</gauges:RadialGaugeRange.TickTemplate>

<gauges:RadialGaugeRange.LabelTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding}" 
                   Foreground="{Binding Converter={StaticResource converter1}}" 
                   FontWeight="Bold" 
                   FontSize="17" />
    </DataTemplate>
</gauges:RadialGaugeRange.LabelTemplate>

Notice the binding with converter 1. This converter makes the ticks and labels transparent for the red region of the scale.

Here is its definition:

CopyXAML
<gauges:GaugeValueToBrushConverter x:Key="converter1" DefaultColor="Gray">
    <gauges:GaugeColorValueRange MinValue="25" MaxValue="40" Color="Transparent" />
</gauges:GaugeValueToBrushConverter>

Two things remain to be defined, the markers including the arrow and the segmented indicator over the ticks.

Let's define the segmented indicator first:

CopyXAML
<gauges:SegmentedRadialGaugeIndicator gauges:RadialGaugeRange.MaxAngle="180" gauges:RadialGaugeRange.IndicatorRadiusScale="0.78" Value="40">
    <gauges:BarIndicatorSegment Length="1.05" Thickness="2" Stroke="#FFCCCCCC" />
    <gauges:BarIndicatorSegment Length="1" Thickness="2" Stroke="Gray" />
    <gauges:BarIndicatorSegment Length="1.1" Thickness="2" Stroke="Red" />
</gauges:SegmentedRadialGaugeIndicator>

Now we need to add the markers. These are the diamond, the red circles and the 26 and 40 label markers.

First are the red circle markers:

CopyXAML
<gauges:MarkerGaugeIndicator gauges:RadialGaugeRange.MaxAngle="180" 
                             gauges:RadialGaugeRange.IndicatorRadiusScale="0.78" 
                             Value="26" 
                             MarkerTemplate="{StaticResource redCircleTemplate}"/>
<gauges:MarkerGaugeIndicator gauges:RadialGaugeRange.MaxAngle="180"
                             gauges:RadialGaugeRange.IndicatorRadiusScale="0.78"
                             Value="40" 
                             MarkerTemplate="{StaticResource redCircleTemplate}" />

Notice that the marker templates point to a static resource. Here is the definition of the red circle template:

CopyXAML
<DataTemplate x:Key="redCircleTemplate">
    <Ellipse Width="12" Height="12" Fill="{StaticResource PhoneBackgroundBrush}" Stroke="Red" />
</DataTemplate>

Now we define the 26 and 40 labels markers along with their template:

CopyXAML
<gauges:MarkerGaugeIndicator gauges:RadialGaugeRange.MaxAngle="180" 
                             gauges:RadialGaugeRange.IndicatorRadiusScale="0.90" 
                             Value="26" 
                             IsMarkerRotated="False" 
                             MarkerTemplate="{StaticResource redLabelTemplate}" />

<gauges:MarkerGaugeIndicator gauges:RadialGaugeRange.MaxAngle="180" 
                             gauges:RadialGaugeRange.IndicatorRadiusScale="0.90" 
                             Value="40" 
                             IsMarkerRotated="False" 
                             MarkerTemplate="{StaticResource redLabelTemplate}" />
CopyXAML
<DataTemplate x:Key="redLabelTemplate">
    <TextBlock Text="{Binding}" Foreground="Red" FontWeight="Bold" FontSize="17" />
</DataTemplate>

<DataTemplate x:Key="rhombTemplate">
    <Rectangle Width="11" Height="11" Fill="Gray" RenderTransformOrigin="0.5, 0.5">
        <Rectangle.RenderTransform>
            <TransformGroup>
                <SkewTransform AngleX="20" AngleY="20" />
                <RotateTransform Angle="-40" />
            </TransformGroup>
        </Rectangle.RenderTransform>
    </Rectangle>
</DataTemplate>

Finally we define the diamond and arrow:

CopyXAML
<gauges:MarkerGaugeIndicator gauges:RadialGaugeRange.MaxAngle="180"
                             gauges:RadialGaugeRange.IndicatorRadiusScale="0.78"
                             Value="34"
                             x:Name="indicator1"
                             IsAnimated="True"
                             MarkerTemplate="{StaticResource rhombTemplate}" />
<gauges:ArrowGaugeIndicator x:Name="indicator2"
                            ArrowBrush="{StaticResource PhoneForegroundBrush}"
                            gauges:RadialGaugeRange.IndicatorRadiusScale="0.73"
                            gauges:RadialGaugeRange.MaxAngle="180"
                            ArrowThickness="2"
                            ArrowTailRadius="1.2"
                            Value="23.66"
                            IsAnimated="True" />

Our gauge is almost complete, the last thing to do is to add the labels below the gauge and write some custom logic so that their text updates accordingly:

CopyXAML
<StackPanel HorizontalAlignment="Center" 
            VerticalAlignment="Center">
    <TextBlock Text="{Binding Path=ActualValue, ElementName=indicator2}"
               FontWeight="Bold"
               FontSize="18"
               Width="50"/>
    <TextBlock Text="%8.3"
               HorizontalAlignment="Center"
               Foreground="{StaticResource PhoneAccentBrush}"
               FontWeight="Bold"
               FontSize="15"
               Margin="0, -5, 0, 0"
               x:Name="percentText"/>

    <StackPanel.RenderTransform>
        <TranslateTransform Y="20" />
    </StackPanel.RenderTransform>
</StackPanel>

The code behind maps a value from the [0, 40] range to a value in the [0, 120] range. This is necessary so that the bottom label indicates a percentage between 0 and 26. If the value

of the arrow indicator is greater than 26, the bottom label displays zero. Here is the code:

CopyC#
public partial class MainPage : PhoneApplicationPage
{
    const double specialValue = 26;

    double expenseDiff;
    double profitDiff;
    Random random = new Random();

    public MainPage()
    {
        InitializeComponent(); this.expenseDiff = specialValue - this.range1.MinValue;
        this.profitDiff = this.range2.MaxValue - this.range2.MinValue;
    }

    public void UpdateIndicator(GaugeIndicator indicator)
    {
        if (indicator == null)
        {
            return;
        }
        double max = indicator.Owner.MaxValue - indicator.Owner.MinValue;
        indicator.Value = indicator.Owner.MinValue + random.NextDouble() * max;
    }

    private void OnButtonClick(object sender, EventArgs args)
    {
        UpdateIndicator(this.indicator1);
        UpdateIndicator(this.indicator2);
        double percent = 0;
        if (this.indicator2.Value < specialValue)
        {
            percent = this.range2.MaxValue - GaugeRange.MapLogicalToPhysicalValue(this.indicator2.Value, profitDiff, expenseDiff);
        }
        this.percentText.Text = "%" + Math.Round(percent).ToString();
    }
}

Now our gauge should be complete and the page of the application should look like this:

gauge Design Complete

The complete XAML can be found here for copy paste purposes.