Telerik blogs

When you install RadControls for Metro two files are placed on your desktop. One is the QSF for HTML/JavaScript and the other is for XAML/C#. Not only will these samples demonstrate how powerful the controls are, the code in the sample applications can serve as an excellent learning tool.

In this blog post, I will tease out of the QSF for XAML the fundamentals of creating a RadBulletGraph. A bullet graph is a very concise way to convey a primary measure (e.g., current revenue) compared to one or more other measures (e.g., a target revenue) in the context of qualitative ranges (e.g, poor, good, excellent), as illustrated in figure 1 taken from this article on bullet graphs in Wikipedia.

imageFigure 1

The BulletGraph we will create will compare Call Duration (at a call center) vs. percent of target – that is, how long are the representatives staying on a call vs. how long we have targeted for a call. The BulletGraph will be divided into three sections: 0-50%, 50 to 125% and 125 to 175% of target. The indicator will show the call duration and a TextBlock will show the actual value, as seen in figure 2,

imageFigure 2

If the call duration falls below a given threshold, we will change the bar indicator from black to red, as shown in figure 3,

imageFigure 3

To make this work, we’ll need two helper classes. The first is going to convert a number (the percentage of target) to a color (red or black). We’ll call that class ValueToColorConverter,

public class ValueToColorConverter : IValueConverter
{
    private SolidColorBrush RedBrush = 
new SolidColorBrush( Colors.Red ); private SolidColorBrush NormalBrush = new SolidColorBrush( Colors.Black ); public object Convert( object value, Type targetType,
object parameter, string language ) { double treshold = double.Parse( (string)parameter ); double actualValue = 0.0; if ( value.GetType() == typeof( double ) ) { actualValue = (double)value; } else { actualValue = (int)value; } return actualValue > treshold ? NormalBrush : RedBrush; } public object ConvertBack( object value, Type targetType,
object parameter, string language ) { throw new NotImplementedException(); } }

Notice that the threshold is passed in as is the value. If the value is greater than the threshold the NormalBrush is returned, otherwise the RedBrush is returned.

The second class will be responsible for providing a value to our graph. Because it makes the graph more interesting, we’ll have the value change over time, updating to a new (random) value every second.

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged( string propName )
    {
        PropertyChangedEventHandler eh = this.PropertyChanged;
        if ( eh != null )
        {
            eh( this, new PropertyChangedEventArgs( propName ) );
        }
    }
}


public class RandomViewModel : ViewModelBase
{
    private DispatcherTimer timer;
    private Random r;

    private int callDuration;

    public int CallDuration
    {
        get { return callDuration; }
        set
        {
            callDuration = value;
            this.OnPropertyChanged( "CallDuration" );
        }
    }


    public RandomViewModel()
    {
        r = new Random();
        timer = new DispatcherTimer() { 
Interval = TimeSpan.FromSeconds( 1 ) }; timer.Tick += timer_Tick; this.LoadData(); } public void StopTimer() { this.timer.Stop(); } public void StartTimer() { this.timer.Start(); } void timer_Tick( object sender, object e ) { this.UpdateIndicators(); } private void LoadData() { UpdateIndicators(); } private void UpdateIndicators() { this.CallDuration = r.Next( 0, 175 ); } }

The first class, ViewModelBase, acts as a base class for all ViewModels, and handles the INotifyPropertyChanged interface. The second class, RandomViewModel derives from ViewModelBase, and is where the action is; it has the CallDuration property that we’ll be data binding to, and it handles the timer tick to update the value every second.

With that under our belt, we’re ready to turn to the XAML file. First, we’ll create some Page resources to provide a key (usable later in the XAML) to the two classes we created,

<Page.Resources>
    <local:RandomViewModel
        x:Key="ViewModel"></local:RandomViewModel>
    <local:ValueToColorConverter
        x:Key="ColorConverter"></local:ValueToColorConverter>
</Page.Resources>

Next, we declare our Grid and set the DataContext for the Grid (and thus for all the controls in the Grid) to the ViewModel we just declared in the Page resources. Note how this works, the Grid sets the DataContext to the StaticResource ViewModel which is the key to the Page resource RandomViewModel, which is namespaced to be the class in this project named RandomViewModel.

<Grid
    DataContext="{StaticResource ViewModel}"
    Margin="0 20 0 0">

Next, we declare a set of resources in the Grid that we’ll use in our RadBulletGraph. First, we need some brushes,

<Grid.Resources>
    <SolidColorBrush
        x:Key="BadBrush"
        Color="#3FFFFFFF" />
    <SolidColorBrush
        x:Key="SatisfactoryBrush"
        Color="#7FFFFFFF" />
    <SolidColorBrush
        x:Key="GoodBrush"
        Color="#BFFFFFFF" />

Each brush serves as the background color for the qualitative areas, as shown in figure 4,

imageFigure 4

While we’re in the Resources, we’ll declare a few DataTemplates for use later. The first will be for the Label to be added to the BulletGraph through the LabelTemplate property

    <DataTemplate
        x:Key="LabelTemplate">
        <TextBlock
            Text="{Binding}"
            FontSize="16"
            Foreground=
"{StaticResource
ApplicationSecondaryForegroundThemeBrush}" />

Similarly, we’ll set the DataTemplate for the TickTemplate, the EmptyTemplate (my personal favorite) and the ComparativeMeasureTemplate.

<DataTemplate
    x:Key="TickTemplate">
    <Rectangle
        Width="2"
        Height="6"
        Fill="#999999" />
</DataTemplate>
<DataTemplate
    x:Key="EmptyTemplate">
</DataTemplate>
<DataTemplate
    x:Key="ComparativeMeasureTemplate">
    <Rectangle
        Width="2"
        Height="20">
        <Rectangle.Fill>
            <SolidColorBrush
                Color="Orange"
                Opacity="0.9"></SolidColorBrush>
        </Rectangle.Fill>
    </Rectangle>
</DataTemplate>

That concludes the Grid resources. Inside the Grid we place a StackPanel and in that StackPanel we’ll place two controls: the label (Call Duration (% of target)), and the grid which will hold the RadBulletGraph and its label (50%).

<StackPanel
    Margin="0 0 0 40">
    <TextBlock
        Text="CALL DURATION (% OF TARGET)"
        FontSize="14"></TextBlock>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition
                Width="*" />
            <ColumnDefinition
                Width="100" />
        </Grid.ColumnDefinitions>

We are now, finally, ready to declare our RadBulletGraph. Here is the declaration and that of the TextBlock that follows it (the label). We’ll go through the RadBulletGraph line by line, below.

<telerik:RadBulletGraph
    FeaturedMeasureStartValue="0"
    EndValue="175"
    Margin="0 6 15 0"
    TickStep="25"
    LabelStep="25"
    LabelOffset="15"
    FeaturedMeasureThickness="6"
    LabelTemplate="{StaticResource LabelTemplate}"
    TickTemplate="{StaticResource TickTemplate}"
    ComparativeMeasureTemplate=
"{StaticResource ComparativeMeasureTemplate}" FeaturedMeasureAlternativeTemplate="{StaticResource EmptyTemplate}" FeaturedMeasureBrush=
"{Binding CallDuration,
Converter={StaticResource ColorConverter},
ConverterParameter='50'}" FeaturedMeasure="{Binding CallDuration}" Height="50" ComparativeMeasure="100"> <telerik:RadBulletGraph.QualitativeRanges> <telerik:BarIndicatorSegment Stroke="{StaticResource BadBrush}" Thickness="20" Length="50" /> <telerik:BarIndicatorSegment Stroke="{StaticResource SatisfactoryBrush}" Thickness="20" Length="75" /> <telerik:BarIndicatorSegment Stroke="{StaticResource GoodBrush}" Thickness="20" Length="50" /> </telerik:RadBulletGraph.QualitativeRanges> </telerik:RadBulletGraph> <TextBlock Margin="10 0 0 0" Grid.Column="1" FontSize="28" FontWeight="Light"> <Run Text="{Binding CallDuration}"></Run> <Run Text="%"></Run> </TextBlock>

Let’s examine the RadBulletGraph line by line.

image

It begins by setting the FeaturedMeasureStartValue to 0 and the EndValue to 175. The TickStep indicates how frequently there will be a tick mark; this is set to 25. The label offset is set to 15 and the FeaturedMeasureThickness (the thickness of the line that shows the featured measure) is set to 6. Feel free to play with these values to see what impact changes have.

Those values were “hard coded” into this instance of the RadBulletGraph. Next the LabelTemplate property is set to the DataTemplate declared in the resources, as is the TickTemplate, and the ComparativeMeasureTemplate, respectively.

It is possible to have a FeaturedMeasureAlternative indicator, but this template is set to the EmptyTemplate, effectively making it invisible.

Next the FeaturedMeasureBrush is set, and this is done by binding the CallDuration and using the ColorConverter that we created earlier, passing in the parameter of 50 as the threshold

FeaturedMeasureBrush="{Binding CallDuration,
   Converter={StaticResource ColorConverter},ConverterParameter='50'}"

The FeaturedMeasure itself is bound to the CallDuration.

After the height is set to 50 the ComparativeMeasure is set to 100, causing the yellow line to appear at 100 to provide a natural boundary between under and over 100% of target, as shown in figure 5,

image
Figure 5

The next section creates the ranges. Earlier we mentioned that we’d have a range from 0-50, 50-125 and 125-175, here is where they are created using the Brush resources declared in the Grid Resources section,

<telerik:RadBulletGraph.QualitativeRanges>
    <telerik:BarIndicatorSegment
        Stroke="{StaticResource BadBrush}"
        Thickness="20"
        Length="50" />
    <telerik:BarIndicatorSegment
        Stroke="{StaticResource SatisfactoryBrush}"
        Thickness="20"
        Length="75" />
    <telerik:BarIndicatorSegment
        Stroke="{StaticResource GoodBrush}"
        Thickness="20"
        Length="50" />
</telerik:RadBulletGraph.QualitativeRanges>

Animating Your BulletGraph

All of the infrastructure is now in place to animate your RadBulletGraph, we just have to set it in motion. In MainPage.xaml.cs we’ll create event handlers for the Loaded and Unloaded events,

public MainPage()
 {
     this.InitializeComponent();
     Loaded += MainPage_Loaded;
     Unloaded += MainPage_Unloaded;
 }

The Loaded event will obtain the RandomViewModel and call StartTimer, setting the timer going and updating the call duration every second,

void MainPage_Loaded( object sender, RoutedEventArgs e )
{
    RandomViewModel model = 
this.Resources[ "ViewModel" ] as RandomViewModel; model.StartTimer(); }

When the page is unloaded we’ll stop the timer,

void MainPage_Unloaded( object sender, RoutedEventArgs e )
 {
     RandomViewModel model = 
         this.Resources[ "ViewModel" ] as RandomViewModel;
     model.StopTimer();
 }

That’s it, you now have a very complex, tight, compact and powerful bullet graph to simulate examining the actual duration of calls against target.

You can download the RadControlsForMetro here.

Download the complete source code for this posting here


jesseLiberty
About the Author

Jesse Liberty

 has three decades of experience writing and delivering software projects. He is the author of 2 dozen books and has been a Distinguished Software Engineer for AT&T and a VP for Information Services for Citibank and a Software Architect for PBS. You can read more on his personal blog or follow him on twitter

Comments

Comments are disabled in preview mode.