Styling Silverlight Charts with automatic colors

The Silverlight Toolkit provides some pretty nice data visualization tools. There are a plethora of different chart types available, and for the most part, they’re pretty easy to use, and very customizable to your needs. One of the nice things it does to make your life easier, each series you add to the chart gets automatically color-coded with attractive gradients. Its a nice touch.

One of the frustrating things about the charting tools is that if you style your series, that automatic color disappears – everything is orange. You can manually set colors – but its extra effort, and you need to create a new style for every color. Irritating, to say the least, especially if you dynamically add your series. I had a series that I wanted to style – specifically, I wanted to add a custom ToolTip, like I describe here. And the number of series is dynamic, which didn’t help things.

The charting toolkit is open source, so I went poking around, trying to find how and where they set these automatic colors. I never did find it – but I did find a cache of styles, that looked to be exactly the automatic colors that get applied. See for yourself – the code is installed by default to C:\Program Files (x86)\Microsoft SDKs\Silverlight\v4.0\Toolkit\Apr10\Source\Source code.zip. Unzip that, then open up Controls.DataVisualization.Toolkit\Themes\generic.xaml. About line 270, you’ll see this….

<Style TargetType="charting:Chart">
    <Setter Property="BorderBrush" Value="Black" />
    <Setter Property="BorderThickness" Value="1" />
    <Setter Property="IsTabStop" Value="False" />
    <Setter Property="Padding" Value="10" />
    <Setter Property="Palette">
        <Setter.Value>
            <datavis:ResourceDictionaryCollection>
                <!-- Blue -->
                <ResourceDictionary>
                    <RadialGradientBrush x:Key="Background" GradientOrigin="-0.1,-0.1" Center="0.075,0.015" RadiusX="1.05" RadiusY="0.9">
                        <GradientStop Color="#FFB9D6F7" />
                        <GradientStop Color="#FF284B70" Offset="1" />
                    </RadialGradientBrush>
                    <Style x:Key="DataPointStyle" TargetType="Control">
                     .....

Jackpot! It seems what I need is under Chart.Palatte. Now its just a matter of applying…which, of course, was not as easy as it seems.

First, I tried making a style, adding the background color to it, and applying it to my series. Like this:

XAML:

<Style x:Name="MyStyle" TargetType="charting:LineDataPoint">
  <Setter Property="Template" Value="{StaticResource MyTemplate}" />
</Style>

Code:

//Create a new setter using the chart palatte and apply it to my existing style
Setter bgSetter = new Setter(LineDataPoint.BackgroundProperty, MyChart.Palatte[0]["Background"]);
Style myStyle = Resources["MyStyle"] as Style;
myStyle.Setters.Add(bgSetter);

//Create a new series using that style and add it to the chart
LineSeries newSeries = new LineSeries();
newSeries.DataPointStyle = myStyle;

MyChart.Series.Add(newSeries);

Looks good right? Compiles ok….and you get a CATASTROPHIC FAILURE COM Exception. Yikes! That’s not what you want to hear. Turns out, there’s a bug deep in the bowels of Silverlight that doesn’t like when you change a DependencyProperty after its already been set. So, you have to create the new style from scratch.

//Clear the previous series
MyChart.Series.Clear();
int palatteIndex = 0;

foreach (Object myData in myDataList)
{
  //Create a new BackgroundProperty setter, using the current index of the chart palatte
  Setter bgSetter = new Setter(LineDataPoint.BackgroundProperty, MyChart.Palatte[palatteIndex]["Background"]);
  //create other setters

  //Create the new style
  Style newStyle = new Style(typeof(LineDataPoint));
  newStyle.Setters.Add(bgSetter);
  //add other setters

  //Create your new series and apply the new style
  LineSeries newSeries = new LineSeries();
  newSeries.DataPointStyle = newStyle;
  //create bindings and add data to series

  MyChart.Series.Add(newSeries);

  //Increment the palatte
  //If we've reached the total number of presets, reset back to the beginning
  //If you'd prefer, you could use some sort of random number generator here instead.
  ++palatteIndex;
  if(palatteIndex > MyChart.Palatte.Count)
  {
    palatteIndex = 0;
  }
}

And there you have it! You can style your series to your heart’s content, but still maintain the nice, attractive automatic colors for the series! Note that while I used LineSeries, this will work for any of them – ColumnSeries, BarSeries, PieSeries, you name it.

Advertisements

Using a custom ToolTip in Silverlight charting

This has been done plenty of places before – in itself, it isn’t too complicated. However, it provides the set up for my next blog post, so I figured I would write this out separately.

I’m using the charting tools from the Silverlight toolkit. In general, they are solid – easy to use and attractive. However, its not always so simple if you need to customize things beyond a simple binding expression. In this post, I will explain how to create a custom ToolTip for data in a LineSeries.

By default, Silverlight displays the numeric value of the bound data as the ToolTip. Like this:

Silverlight Charting - Default ToolTip

This is nice – but not what we want. We want to expand this to display more detailed information. Within Silverlight, setting a ToolTip is usually pretty easy:

XAML:

<TextBlock Name="MyTextBlock" Text="SomeText">
  <ToolTipService.SetToolTip>
    <ToolTip>Hello From My ToolTip!</ToolTip>
  </ToolTipService.SetToolTip>
</TextBlock>

Code:

TextBlock text = new TextBlock()
{
  Name = "MyTextBlock",
  Text = "SomeText",
};
ToolTipService.SetToolTip(text, "Hello From My ToolTip!");

Unfortunately, it isn’t quite as easy for charting. The ToolTip is buried in the ControlTemplate of the DataPointStyle of the LineSeries. So, you need to create a new style with that template, and update the ToolTip there. You can get the full default style from the toolkit source code or from Expression Blend. Below is the default style for the ControlTemplate, with the altered ToolTip:

<ControlTemplate x:Key="ModifiedToolTipTemplate" TargetType="charting:LineDataPoint">
    <Grid x:Name="Root" Opacity="0">
        <ToolTipService.ToolTip>
            <ContentControl Content="{Binding Converter={StaticResource MyDataConverter}}"/>
        </ToolTipService.ToolTip>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
                <VisualStateGroup.Transitions>
                    <VisualTransition GeneratedDuration="0:0:0.1"/>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="Normal"/>
                <VisualState x:Name="MouseOver">
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="MouseOverHighlight">
                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.24"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="SelectionStates">
                <VisualStateGroup.Transitions>
                    <VisualTransition GeneratedDuration="0:0:0.1"/>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="Unselected"/>
                <VisualState x:Name="Selected">
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="SelectionHighlight">
                            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.18"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="RevealStates">
                <VisualStateGroup.Transitions>
                    <VisualTransition GeneratedDuration="0:0:0.5"/>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="Shown">
                    <Storyboard>
                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Hidden">
                    <Storyboard>
                        <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Ellipse Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}"/>
        <Ellipse RenderTransformOrigin="0.661,0.321">
            <Ellipse.Fill>
                <RadialGradientBrush GradientOrigin="0.681,0.308">
                    <GradientStop Color="#00FFFFFF"/>
                    <GradientStop Color="#FF3D3A3A" Offset="1"/>
                </RadialGradientBrush>
            </Ellipse.Fill>
        </Ellipse>
        <Ellipse x:Name="SelectionHighlight" Fill="Red" Opacity="0"/>
        <Ellipse x:Name="MouseOverHighlight" Fill="White" Opacity="0"/>
    </Grid>
</ControlTemplate>

Then, apply this template to a LineDataPoint style:

<Style x:Key="ModifiedDataPointStyle" TargetType="charting:LineDataPoint">
    <Setter Property="Background" Value="Blue" />
    <Setter Property="Width" Value="8" />
    <Setter Property="Height" Value="8" />
    <Setter Property="Template" Value="{StaticResource ModifiedToolTipTemplate}" />
</Style>

Finally, apply that to your LineSeries:

<charting:LineSeries Name="MySeries" DataPointStyle="{StaticResource ModifiedDataPointStyle}" />

…and that’s it! You can put whatever you want in the custom ToolTip – I use a Silverlight data converter to take the bound value and create a custom TextBlock. The end result looks like this:

Custom ToolTip