Telerik blogs
We introduced RadBulletGraph beta in the Q2 2011 SP1 release with the idea that the sooner we release a stable product the more time we will have to polish it and even implement user requirements before the final version. With this blog post I will explain how RadBulletGraph is structured internally and how this internal structure enables the features that it currently supports. With this in mind I kindly ask you, dear reader, to give your opinion on RadBulletGraph, to tell us what else you need or what can be done in a better way so that we can provide the best possible product for Q3 2011. Now on to the architecture.

Considering that the bullet graph has a very simplistic visual representation, that is, it is made mostly by rectangular components (bars) on a number scale, we decided that we should build it on top of our gauge components. This allowed us to just take the some of the existing gauge parts and build the bullet graph on top of them. As a result, the development time for RadBulletGraph was very short and its performance is superb since the gauges were designed from the ground up to perform well on a Windows Phone device. At least 15 gauge indicators of any kind can be placed on a single page and updated by the cpu many times per second with the whole thing running at ~20 fps.

RadBulletGraph has the following visual structure:

<ControlTemplate TargetType="local:RadBulletGraph">
                    <local:LinearGaugeRange x:Name="PART_BulletGraphScale"
                                            MinValue="{TemplateBinding StartValue}"
                                            MaxValue="{TemplateBinding EndValue}"
                                            TickStep="{TemplateBinding TickStep}"
                                            LabelStep="{TemplateBinding LabelStep}"
                                            LabelOffset="{TemplateBinding LabelOffset}"
                                            Orientation="{TemplateBinding Orientation}"
                                            TickTemplate="{TemplateBinding TickTemplate}"
                                            LabelTemplate="{TemplateBinding LabelTemplate}">
                         
                        <local:SegmentedLinearGaugeIndicator x:Name="PART_QualitativeBar"
                                                             Value="{TemplateBinding EndValue}"
                                                             local:LinearGaugeRange.IndicatorOffset="-20"
                                                             Canvas.ZIndex="0"/>
                        <local:LinearBarGaugeIndicator x:Name="PART_FeaturedMeasure"
                                                       Value="{TemplateBinding FeaturedMeasure}"
                                                       BarBrush="{TemplateBinding FeaturedMeasureBrush}"
                                                       BarThickness="{TemplateBinding FeaturedMeasureThickness}"
                                                       local:LinearGaugeRange.IndicatorOffset="-20"
                                                       StartValue="{TemplateBinding FeaturedMeasureStartValue}"
                                                       Visibility="{Binding ElementName=PART_BulletGraphScale, Path=MinValue, Converter={StaticResource ZeroToVisibleConverter}}"
                                                       Canvas.ZIndex="3"/>
                        <local:LinearBarGaugeIndicator x:Name="PART_ProjectedMeasure"
                                                       Value="{TemplateBinding ProjectedMeasure}"
                                                       BarBrush="{TemplateBinding ProjectedMeasureBrush}"
                                                       StartValue="{TemplateBinding FeaturedMeasureStartValue}"
                                                       BarThickness="{Binding BarThickness, ElementName=PART_FeaturedMeasure}"
                                                       local:LinearGaugeRange.IndicatorOffset="-20"
                                                       Canvas.ZIndex="1"/>
                        <local:MarkerGaugeIndicator x:Name="PART_AlternativeFeaturedMeasure"
                                                    Value="{TemplateBinding FeaturedMeasure}"
                                                    local:LinearGaugeRange.IndicatorOffset="-20"
                                                    Visibility="{Binding ElementName=PART_BulletGraphScale, Path=MinValue, Converter={StaticResource ZeroToCollapsedConverter}}"
                                                    MarkerTemplate="{TemplateBinding FeaturedMeasureAlternativeTemplate}"
                                                    IsMarkerRotated="True"
                                                    Canvas.ZIndex="3"/>
                        <local:MarkerGaugeIndicator x:Name="PART_ComparativeMeasure"
                                                    IsMarkerRotated="True"
                                                    local:LinearGaugeRange.IndicatorOffset="-20"
                                                    Value="{TemplateBinding ComparativeMeasure}"
                                                    Canvas.ZIndex="2"
                                                    MarkerTemplate="{TemplateBinding ComparativeMeasureTemplate}"/>
                    </local:LinearGaugeRange>
                </ControlTemplate>

Notice that there are only gauge components under the hood. Nothing too special, at least until you have a closer look. The interesting thing is a new property that is set on the gauge indicators – the StartValue property. This is the only thing that had to be implemented inside the gauges so that the bullet graph could support every feature described in the official specification. StartValue allows the user to anchor the beginning of the bar indicators to some value. For example if we have a bar indicator that is on a scale from 0 to 100 and we anchor the bar to 50 by setting StartValue = 50, its Value property can be set to values less than 50 and greater than 50 with the result being that the bar can now stretch in both directions – left or right (up or down in vertical orientation).

RadBulletGraph's featured measure extending to the left:



RadBulletGraph's featured measure extending to the right:


So with the StartValue property the bars can be bi-directional. The horizontal/vertical orientation, the scale and comparative measures and all the visual customization (label and tick templates, indicator colors and sizes) capabilities are available out for the box because of the gauges (the comparative measures are simple MarkerIndicator objects internally and can be restyled with an arbitrary DataTemplate) and are simply exposed as public properties. 

Currently one of the visual customization restrictions is that the featured measure (the main indicator of the bullet graph) is locked down to always be a bar indicator in order to prevent big deviations from the official visual specification. The additional comparative measures on the other hand are not restricted visually, they have a publicly available Template property for maximum flexibility which, however, does not mean that you should go crazy and put pink cats and bunnies inside. The comparative measures should be simple figures that are less visually dominant than the featured measure, so please behave! :)


We have a very good example of all the available configurations that the bullet graph can be used with. Here is a screenshot from our examples app:


Also here is a full list of the features that are currently supported out of the box:

  • Great performance.
  • Ability to anchor the featured measure to an arbitrary value on the value scale.
  • Ability to display a projected measure.
  • Ability to display an arbitrary number of comparative measures.
  • Ability to display an arbitrary number of scale value ranges.
  • Ability to display a customizable alternative template for the featured measure.
  • Ability to display a decreasing number scale.

They cover all the cases described in Stephen Few’s specification. If you require additional features that you think would be a good addition to the core functionality, and that will make your development easier, please let us know and we will do our best to incorporate them into the final version of the control.

Now that I have talked the talk, I will walk the walk. Feel free to have a look at the sample app below and to judge the bullet graph performance objectively. Any modification and reuse is not only allowed, but encouraged.

BulletGraphFeatures.zip

Cheers


About the Author

Viktor Skarlatov

Software Developer,
Windows Phone Team

Comments

Comments are disabled in preview mode.