Click here to Skip to main content
15,885,957 members
Articles / Programming Languages / XML

Adding a Location Crosshair to Silverlight Charts

Rate me:
Please Sign up or sign in to vote.
4.80/5 (2 votes)
17 May 2009CPOL3 min read 22.5K   6  
This technical blog post details the differences in the latest release of the Silverlight charts and shows how to add a location crosshair.

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:

XML
<ControlTemplate TargetType="charting:Chart">
    <Grid Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}">
 
        <!-- ##### chart control adds column / row definitions and axes here #### -->
 
        <Grid Height="250" x:Name="PlotArea" Style="{TemplateBinding PlotAreaStyle}">
 
            <!-- the standard chart template components -->
            <Grid x:Name="GridLinesContainer" />
            <Grid x:Name="SeriesContainer"/>
            <Border BorderBrush="#FF919191" BorderThickness="1" />
 
            <!-- ##### I added cross hair and legend here #### -->
        </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:

visualtree-before

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:

XML
<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" />
 
            <!-- ##### I added cross hair and legend here #### -->
 
            <!-- ##### chart control adds column / row definitions and axes here #### -->
 
        </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:

visualtree-after

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:

XML
<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" />
 
            <!-- a location crosshair -->
            <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>
 
            <!-- a location 'legend' -->
            <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:

C#
/// <summary>
/// Transforms the supplied position on the plot area grid into a point within
/// the plot area coordinate system
/// </summary>
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Scott Logic
United Kingdom United Kingdom
I am CTO at ShinobiControls, a team of iOS developers who are carefully crafting iOS charts, grids and controls for making your applications awesome.

I am a Technical Architect for Visiblox which have developed the world's fastest WPF / Silverlight and WP7 charts.

I am also a Technical Evangelist at Scott Logic, a provider of bespoke financial software and consultancy for the retail and investment banking, stockbroking, asset management and hedge fund communities.

Visit my blog - Colin Eberhardt's Adventures in .NET.

Follow me on Twitter - @ColinEberhardt

-

Comments and Discussions

 
-- There are no messages in this forum --