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! :)
Also here is a full list of the features that are currently supported out of the box:
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.
Cheers