Silverlight is moving fast. Really fast. The recent MIX09 conference saw the release of Silverlight 3 (Beta) and also a new release of the Silverlight Toolkit. All this change is making it hard for us bloggers to keep up!
Just over a month ago, I posted an article on this blog about how to add a location crosshair to the Silverlight Toolkit Charts. This blog post briefly discusses how the charts have changed with the release last week, and updates the code accordingly.
If you want to know how the general approach works, I would recommend reading the previous blog post. This post serves as an update and discussion on how the chart template has changed.
There have been a number of minor changes to the charts that affect my previous implementation, including a change of namespace and orientation type on the chart axes. However, the biggest change is in the template of the chart control itself. Previously, the chart template was structured via a Grid
as shown below:
<ControlTemplate TargetType="charting:Chart">
<Grid Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
<Grid Height="250" x:Name="PlotArea" Style="{TemplateBinding PlotAreaStyle}">
<Grid x:Name="GridLinesContainer" />
<Grid x:Name="SeriesContainer"/>
<Border BorderBrush="#FF919191" BorderThickness="1" />
</Grid>
</Grid>
</ControlTemplate>
After loading the template, the chart adds the axes and the appropriate column / row definitions. This leaves us free to add new visual content within the ‘PlotArea
’ grid. Inspecting the visual tree, with Silverlight Spy, it looks like the following:
The content I added in order to construct a crosshair and legend are highlighted in red.
The March release of the Silverlight toolkit modifies the way in which axes are added to the chart by the introduction of a new layout panel, the EdgePanel
(think DockPanel
!). This panel allows you to locate (or dock) elements at one of the four edges of a panel or the panel centre. The modified control template is shown below:
<ControlTemplate TargetType="charting:Chart" x:Key="ChartTemplate">
<Grid x:Name="ChartRoot" Style="{TemplateBinding PlotAreaStyle}">
<chartingprimitives:EdgePanel x:Name="ChartArea"
Style="{TemplateBinding ChartAreaStyle}">
<Grid Canvas.ZIndex="1" Style="{TemplateBinding PlotAreaStyle}" />
<Border Canvas.ZIndex="1" BorderBrush="#FF919191" BorderThickness="1" />
</chartingprimitives:EdgePanel>
</Grid>
</ControlTemplate>
I have indicated the location where I add my own visual content and also the location where the chart control adds the axes. The resulting visual tree is shown below:
The significant difference here is that all the components of our chart are contained within the same EdgePanel
, with their location dictated by their EdgePanel.Edge
property. One problem this does introduce for us is that of Z-ordering, previously our additional content was top of the stack, now it sits somewhere in between. Fortunately EdgePanel
honours the Canvas.ZIndex
attached property allowing us to push our content to the top. The modified template is given below in full:
<ControlTemplate TargetType="charting:Chart" x:Key="ChartTemplate">
<Grid x:Name="ChartRoot" Style="{TemplateBinding PlotAreaStyle}">
<chartingprimitives:EdgePanel x:Name="ChartArea"
Style="{TemplateBinding ChartAreaStyle}">
<Grid Canvas.ZIndex="-1" Style="{TemplateBinding PlotAreaStyle}" />
<Border Canvas.ZIndex="3" BorderBrush="#FF919191" BorderThickness="1" />
<Grid Name="CrosshairContainer" Canvas.ZIndex="1" Background="Transparent"
MouseMove="CrosshairContainer_MouseMove"
MouseEnter="CrosshairContainer_MouseEnter"
MouseLeave="CrosshairContainer_MouseLeave" >
<Grid Name="Crosshair">
<Line Name="Vertical" X1="{Binding Path=X}" Y1="0"
X2="{Binding Path=X}" Y2="400" Stroke="Black"/>
<Line Name="Horizontal" X1="0" Y1="{Binding Path=Y}"
X2="400" Y2="{Binding Path=Y}" Stroke="Black"/>
</Grid>
</Grid>
<Border Canvas.ZIndex="2" Name="LocationIndicator"
Visibility="Collapsed" Style="{StaticResource LocationLegendStyle}">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Text="Location: "/>
<TextBlock Text="{Binding Path=Key,
Converter={StaticResource FormattingConverter},
ConverterParameter=hh:mm:ss}"/>
<TextBlock Text=", "/>
<TextBlock Text="{Binding Path=Value,
Converter={StaticResource FormattingConverter},
ConverterParameter=0.00}"/>
</StackPanel>
</Border>
</chartingprimitives:EdgePanel>
</Grid>
</ControlTemplate>
One more subtle point, the event handlers for MouseMove
, MouseLeave
, etc. when associated with a Grid
only works if the background is not null
, hence the need for a transparent background. I have no idea why, believe me … it just works!
One final important change is the method used to translate points from screen coordinates into a position within the chart coordinate system. Previously, I used a method called GetPlotAreaCoordinateValueRange
on the ‘hidden IRangeAxis
interface. This has now been renamed to GetValueAtPosition
and returns and takes a UnitValue
type as its input (presumably for API consistency with the pie charts). Here is the code:
private KeyValuePair<DateTime, double> GetPlotAreaCoordinates(Point position)
{
IComparable yAxisHit = ((IRangeAxis)YAxis).GetValueAtPosition(
new UnitValue(PlotArea.ActualHeight - position.Y, Unit.Pixels));
IComparable xAxisHit = ((IRangeAxis)XAxis).GetValueAtPosition(
new UnitValue(position.X, Unit.Pixels));
return new KeyValuePair<DateTime, double>((DateTime)xAxisHit, (double)yAxisHit);
}
With these changes in place, our crosshair is fully functional once more:
… until the next release of the toolkit.
You can download the project source code here.
Regards,
Colin E.