The blog of dlaa.me

Posts from July 2009

Simple column labels you can create at home! [Re-Templating the Silverlight/WPF Data Visualization ColumnDataPoint to add annotations]

A customer contacted me over the weekend asking how to add labels (also known as annotations) to a ColumnSeries. My reply was that we don't support annotations in Silverlight/WPF Charting yet, but it's possible to create some pretty simple ones for limited scenarios just by editing the default ColumnDataPoint Template. And because it's so quick, I thought I'd write up a brief example on the bus!

Aside: For some more examples of basic DataPoint Template changes, please have a look at my earlier post on customizing ToolTips.

 

In this case, the customer wanted to add a label showing the column's value at the bottom of the column, just above its axis label. (This is a nice place to put labels because it makes it easy for viewers to associate the category with its value no matter how high each column is.) The obvious approach is to add a TextBlock to the body of the default Template, but the problem with that is that the text can get clipped or even disappear for small columns... So the trick is to add a negative Margin to pull the text "outside" the normal clipping region. Fortune must have been smiling upon me, because when I tried this on Silverlight, it worked just like I wanted! :)

Aside: My other idea was to use a Canvas because it doesn't clip by default; maybe someone else will need to use that approach for their scenario.

 

Here's how the resulting chart looks:

Simple column annotations (on bottom)

The XAML's nothing special - aside from the negative Margin, it's all standard stuff:

<charting:Chart
    Title="Simple Column Annotations - Bottom">
    <charting:ColumnSeries
        DependentValuePath="Value"
        IndependentValuePath="Key"
        ItemsSource="{Binding}">
        <charting:ColumnSeries.DataPointStyle>
            <Style TargetType="charting:ColumnDataPoint">
                <Setter Property="Background" Value="Yellow"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="charting:ColumnDataPoint">
                            <Grid>
                                <Rectangle
                                    Fill="{TemplateBinding Background}"
                                    Stroke="Black"/>
                                <Grid
                                    Background="#aaffffff"
                                    Margin="0 -20 0 0"
                                    HorizontalAlignment="Center"
                                    VerticalAlignment="Bottom">
                                    <TextBlock
                                        Text="{TemplateBinding FormattedDependentValue}"
                                        FontWeight="Bold"
                                        Margin="2"/>
                                </Grid>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </charting:ColumnSeries.DataPointStyle>
    </charting:ColumnSeries>
</charting:Chart>

 

Just for fun, I thought I'd try the same trick to put the annotations on top of the columns, too. This is the more traditional location - and that also works pretty nicely:

Simple column annotations (on top)

The only change from the previous XAML is switching the VerticalAlignment to Top:

<Grid
    Background="#aaffffff"
    Margin="0 -20 0 0"
    HorizontalAlignment="Center"
    VerticalAlignment="Top">
    <TextBlock
        Text="{TemplateBinding FormattedDependentValue}"
        FontWeight="Bold"
        Margin="2"/>
</Grid>

 

And there you have it - a simple technique for simple column annotations!

Aside: Of course, these aren't "real" annotations - they'll eventually break in more complicated scenarios. But hey, if you've got simple needs, here's a simple solution for you. :)

 

PS - For people playing along at home, here's how I created the data for the samples:

public MainPage()
{
    InitializeComponent();
    var items = new List<KeyValuePair<string, double>>();
    items.Add(new KeyValuePair<string,double>("Apples", 0));
    items.Add(new KeyValuePair<string,double>("Oranges", 0.1));
    items.Add(new KeyValuePair<string,double>("Pears", 1));
    DataContext = items;
}

My new home page, enhanced [Updated collection of great Silverlight/WPF Data Visualization resources!]

In the time since posting my last collection of Silverlight/WPF Charting links, there's been some great activity! Of particular significance, the June 2009 WPF Toolkit includes the same Data Visualization goodness that was introduced for Silverlight and the July 2009 release of the Silverlight Toolkit added a TreeMap control. Without further ado, here are the latest links (FYI: previously published links are gray):

Overviews (100 level)

Scenarios (200 level)

Internals (300 level)

Team Member posts (Partner level)

My posts (Ego level)

Many, many thanks to everyone who's spent time helping others learn how to use Silverlight/WPF Data Visualization!

PS - If I've missed any good resources, please leave a comment with a link - I'm always happy to find more good stuff! :)

Bringing the Silverlight Toolkit's TreeMap to WPF [Silverlight/WPF Data Visualization Development Release 0]

Now that Data Visualization is part of the Silverlight Toolkit and the WPF Toolkit, it has an official release vehicle for both platforms of interest. This is great because it means that any customers who need to be running signed bits from an official release have a place to get what they need. Of course, because the two Toolkits are not on the same ship schedule, there will probably always be a delta between them: some features or fixes that are present for one platform but not the other. Fortunately, the release cadence for the Toolkits is pretty short (on the order of months), so it won't take long for features to make their way into both releases.

But for folks who always want to be running the latest-and-greatest stuff, that wait can feel like an eternity... :) The good news is that both Toolkits are Ms-Pl open source, so anyone can migrate code between the two. (And, in fact, I made some small tweaks recently to make that even easier!) But why should everyone have to reinvent the wheel? Wouldn't it be better if one person simplified the process and shared the results with everybody? Yeah, I thought so too...

 

Announcing Silverlight/WPF Data Visualization Development Release 0!

The Silverlight/WPF Data Visualization Development Release is a side project of mine to make it easier for Silverlight and WPF customers to get their hands on the latest Data Visualization code for both platforms. From time to time, I plan to release a single, comprehensive ZIP file with the complete Data Visualization source code, projects for all supported platforms, a unifying solution, and pre-compiled release-mode binaries for all supported platforms (unsigned). These development releases will contain the latest internal source code changes with all the fixes and features that are in progress. Interested parties can evaluate the newest stuff and provide early feedback on what works and what's broken - feedback that can help ensure the things you care about get some love and attention.

The catch - and there's always a catch - is that I'm not committing to any kind of schedule for these releases - and they won't be as thoroughly tested as what's in the official Toolkit releases. But if that doesn't bother you, I'd love to have your feedback!

 

[Please click here to download the complete SilverlightWpfDataVisualization solution.]

 

Release Notes

  • The source code for Development Release 0 is identical to what's included with the recent Silverlight Toolkit July 09 release, so there are no new features for the Silverlight assembly. However, the WPF assembly includes the new TreeMap control and a better performing BubbleSeries! (Please see my release announcement and notes for more information about what TreeMap is and how to use it.)
  • Please remember that whenever you add a project reference to the Data Visualization assembly on WPF, you also need to add a reference to WPFToolkit.dll or you'll get weird runtime errors because the Visual State Manager (VSM) isn't available.
  • The file structure of the archive looks like this (highlighted items are files; everything else is directories):
    SilverlightWpfDataVisualization
    |   SilverlightWpfDataVisualization.sln
    +---Binaries
    |   +---Silverlight3
    |   |       System.Windows.Controls.DataVisualization.Toolkit.dll
    |   |       
    |   \---WPF35
    |           System.Windows.Controls.DataVisualization.Toolkit.dll
    |           WPFToolkit.dll
    +---Platforms
    |   +---Silverlight3
    |   |   |   Core.Silverlight3.csproj
    |   |   +---Properties
    |   |   \---Themes
    |   \---WPF35
    |       |   Core.WPF35.csproj
    |       +---Properties
    |       \---Themes
    \---SourceCode
        \---Core
            +---Charting
            |   +---Axis
            |   +---Chart
            |   +---DataPoint
            |   +---Primitives
            |   \---Series
            +---Collections
            +---Legend
            +---Properties
            +---Title
            \---TreeMap
                +---Interpolators
                \---Layout
    The pre-compiled release-mode binaries are located under the Binaries directory. All the common source code is under the SourceCode directory. Platform-specific project and source files are under the Platforms directory. The unified Visual Studio solution is in the root.
  • Design-time assemblies are not part of this release because I don't expect the target audience to make changes to them and also because they add a lot of complexity. However, if there's strong interest in having the design-time assemblies included with future development releases, I'm open to doing so.
  • I haven't updated my DataVisualizationDemos application for this unofficial release, but it's fairly easy to file-link the included TreeMap sample page into the WPF project to get those TreeMap samples running on WPF. (And that's exactly what I did to make sure TreeMap still works well on WPF!)

 

This whole idea of doing development releases is a bit of an experiment for me and I'm interested to see how it works out... If folks really love the idea, I'll try to do development releases more frequently. Or if it's simply too much churn for everyone to keep up with, people can always just wait for the next official Silverlight/WPF Toolkit release to come around. But however people get at it, I hope the Silverlight/WPF Data Visualization project helps customers continue to do great things with Silverlight and WPF!!

An "extreme" update for the Silverlight 3 release [HeadTraxExtreme sample application updated]

A few weeks ago I wrote about an "app building" exercise we did on the Silverlight Toolkit team to help test our controls and identify potential issues with Silverlight 3 before it was released. My contribution to that effort was HeadTraxExtreme, an organizational hierarchy viewer loosely based on an internal tool. That blog post has been fairly popular, and I wanted to update the sample code for the recent release of Silverlight 3 so people can continue to learn from the sample and so I can walk through a fairly typical application upgrade scenario to show what's involved.

 

HeadTraxExtreme sample application

[Click here (or on the image above) to run HeadTraxExtreme in your browser.]

[If you want to look at the complete source code or build HeadTraxExtreme yourself, click here to download it.]

 

Notes:

  • I've made no functional changes here; just done the upgrade from Silverlight 3 Beta to Silverlight 3 RTW bits.
  • The employee images are loaded from the web site of origin, so if you download and build the sample yourself, please be sure to run HeadTraxExtreme from the included web project (HeadTraxExtreme.Web) in order to see them.

Changes:

  • Updated Default.html to include the minRuntimeVersion value 3.0.40624.0 (Silverlight 3 RTW's version number), the new links for installing Silverlight 3, the new IFRAME declaration, and some other minor changes to the default web page body.
  • Updated all project references to point to the official, signed versions of the SDK and Silverlight Toolkit assemblies.
  • Converted the out-of-browser configuration entries in AppManifest.xml to use the RTW names (for example, ApplicationIdentity is now called OutOfBrowserSettings).
  • Updated XAML references to DockPanel and DataForm to reflect their move from the Beta SDK to the Silverlight Toolkit.
  • Switched AutoCompleteBox declaration from SearchMode/ValueMemberBinding to the new FilterMode/ValueMemberPath properties. The former represents a rename post-Beta and the latter is a new, simplified Binding syntax based on what we did for Charting's *Binding properties in the last release.
  • Updated DataForm XAML to reflect some notable improvements made after the Beta. Specifically, DisplayTemplate was renamed to ReadOnlyTemplate and there is new support for DisplayAttribute that enables the use of the DataField element within that template. (The Employee class was already fully decorated with DisplayAttribute, so it didn't need to change.) The new XAML syntax is just as expressive as the old one (more expressive, actually!), it's less fragile, and it's more concise. Yay! :)
  • DataGrid's DataGridTextColumns now also honor DisplayAttribute, so it's no longer necessary to manually specify a header for each column.
  • Significantly simplified the code for downloading employee images. (This has nothing to do with Silverlight 3: for some reason I thought I needed a separate thread the first time around - but I don't.)
  • Deleted Silverlight.js which is not used by the default Silverlight 3 web page.

 

HeadTraxExtreme was a fun project and I'm glad people have found it useful! I hope this quick update keeps it relevant and exciting and helps to outline the typical upgrade process for an application that targets the Silverlight 3 Beta.

 

Please enjoy!

Silverlight Charting gets an update - and a TreeMap! [Silverlight Toolkit July 2009 release now available!]

We've just published the July 2009 release of the Silverlight Toolkit to help celebrate today's release of Silverlight 3! Silverlight 3 includes a wide variety of new features that significantly enhance the platform and make developing powerful applications easier than ever! The Silverlight Toolkit helps extends the Silverlight platform by offering a compelling set of additional controls to enable even more advanced scenarios. I encourage everyone to have a look at the live samples for Silverlight 2 or Silverlight 3, download the Toolkit installer, and enjoy!

Note: The Silverlight Toolkit includes support for both Silverlight 2 and Silverlight 3, so please have a look even if you're not upgrading to 3 today. :)

 

That said, you're probably here because what you really care about is Silverlight/WPF Charting! You may remember my last post announcing the June 2009 release of the WPF Toolkit with the first official release of WPF Charting. I'm pleased to announce that this release of Silverlight Charting is almost exactly in sync with its WPF Charting sibling! There were just two notable change that happened too late to be included with WPF Charting - they're in italics below (along with a third, very minor tweak):

Notable Changes

Included new TreeMap control! Please refer to the second half of this post for lots more about what a TreeMap is, what it's good for, and how to use it!

Improved performance of internal data structures for many common scenarios. Charting now makes use of left-leaning red-black trees to maintain properly balanced data structures. For more detail on this change, please refer to my post about the LeftLeaningRedBlackTree implementation.

Numerous bug fixes for animation inconsistencies between Silverlight and WPF. Storyboards and Animations sometimes behave a little differently on Silverlight/WPF, and a good bit of effort was spent trying to ensure that Charting will behave the same way on both platforms. In most cases, this was a matter of finding an implementation both platforms agreed on - in some it meant resorting to small, localized #if blocks.

Fixed handling of data objects with non-unique hash codes. When each data object had a unique hash code, things already worked fine. But data sets containing items sharing the same hash code could exhibit incorrect behavior in previous releases. Most typical data sets would not have encountered this problem because hash codes are nearly always unique - but there are certain classes that report quite UNunique hash codes and could trigger the problem fairly easily. This is no longer an issue.

Corrected behavior of charts at very small sizes and during animations. Some third party controls offer so-called "fluid" layout in which size changes are all animated and elements can easily shrink to a size of 0x0. This kind of environment could previously trigger layout bugs that would result in an unhandled exception from the Chart control. These issues have been fixed and dynamic layout changes are now handled seamlessly.

Improved the performance of BubbleSeries. Scenarios with many bubbles animating simultaneously could have previously exhibited some sluggishness. In this release for Silverlight we've implemented an optimization that saves a significant amount of time in such cases. Animations of BubbleSeries data changes are now considerably smoother.

Breaking Changes

IRequireGlobalSeriesIndex's GlobalSeriesIndexChanged method takes a nullable int parameter. This should affect only people who have written custom Series implementations - and the code change is a trivial.

Other Changes

Many other fixes and improvements.

  • Better handling of non-double data by shared Series
  • Addition of StrokeMiterLimit to the Polyline used by LineSeries
  • Fixes for edge case scenarios when removing a Series
  • Ability to set Series.Title with a Binding (on Silverlight 3 and WPF)
  • Automatic inheritance of the Foreground property by the Title control
  • Visual improvements to the LegendItem DataPoint marker
  • Improvement to AreaSeries default Style StrokeThickness behavior

 

In my earlier post about WPF Charting, I announced a new DataVisualizationDemos sample that consolidates all the Charting samples I've posted to my blog into one handy place and compiles/runs on both Silverlight and WPF. I've just updated DataVisualizationDemos for this release so it includes everything through the "Letter Frequency" sample I introduced last time, the original easing functions sample from the March 2009 release notes, my recent "Jelly Charting" demonstration, and a completely new sample:

TreeMap Introduction

Click here to download the complete source code for the DataVisualizationDemos sample application.

 

The new sample shows off the TreeMap control that's now part of the System.Windows.Controls.DataVisualization namespace. The Wikipedia entry explains TreeMaps in detail; the executive summary is that TreeMaps are used to visualize the values of a single property (ex: team scores) across an entire data set. They do this by adjusting the size (i.e., area) of each element so items with larger values are bigger, items with smaller values are littler, and the relative proportions are correct. There are some different algorithms to do this layout; our TreeMap implements the common "squarified" algorithm.

But enough background - let's use a TreeMap to visualize some data!

 

To begin, imagine that we have a data set listing all the blog posts I've made and the following details for each: post date, length, tags, and relative popularity. (Aside: The popularity measure is not accurate because it's based on an incomplete set of statistics - but it's sufficient for our purposes.) Let's begin by displaying all the posts ranked by popularity - that looks like this:

Simple TreeMap

The XAML is a tad verbose, but it's quite easy to understand:

<datavis:TreeMap
    x:Name="AllPosts"
    ItemsSource="{Binding}">
    <datavis:TreeMap.ItemDefinition>
        <datavis:TreeMapItemDefinition
            ValueBinding="{Binding Popularity}">
            <DataTemplate>
                <Grid>
                    <Border
                        BorderBrush="Black"
                        BorderThickness="1"
                        Background="#ff7fc3ff"
                        Margin="0 0 1 1">
                        <Grid Background="{StaticResource GradientOverlay}">
                            <controls:Viewbox Margin="3 0 3 0">
                                <TextBlock Text="{Binding FormattedDate}"/>
                            </controls:Viewbox>
                        </Grid>
                        <ToolTipService.ToolTip>
                            <StackPanel>
                                <TextBlock Text="{Binding Title}"/>
                                <TextBlock Text="{Binding FormattedTags}" FontStyle="Italic"/>
                                <TextBlock Text="{Binding FormattedDate}"/>
                            </StackPanel>
                        </ToolTipService.ToolTip>
                    </Border>
                </Grid>
            </DataTemplate>
        </datavis:TreeMapItemDefinition>
    </datavis:TreeMap.ItemDefinition>
</datavis:TreeMap>

We start out by creating a TreeMap and pointing it at our data set using the standard ItemsControl pattern of setting the ItemsSource property. Then we specify a TreeMapItemDefinition to describe what the items should look like and provide a Binding to identify the value we're visualizing.

At this point, you may be wondering whether there's a corresponding TreeMapItem control for each element (think ListBoxItem). Well, there is not. The reason being that TreeMaps are often used to display very large data sets with hundreds of elements - and it turns out that the overhead of having a wrapper control for each of those elements adds up quickly. So TreeMap takes the slightly different approach of allowing you to define a DataTemplate for its item (as with ItemsControl.ItemTemplate). This DataTemplate can be as simple or as complex as you'd like - and can even use a custom TreeMapItem-like control - so there's no loss of flexibility. However, there's a big win in performance: this approach is roughly four times faster!

The contents of the DataTemplate used here are pretty typical: a border around some text that's bound to the underlying data object and a helpful ToolTip to display more detail when the user mouses over the element. It's as easy as that!

 

Okay, let's get a little more advanced and display two values at the same time! The conventional way of showing a second value in a TreeMap is by varying the color of the items, so we'll do that. Size corresponds to popularity as before, but now we're using color saturation to visualize post length (where darker=longer):

TreeMap with Interpolator

The basic TreeMap definition is basically the same as last time - the interesting part is the contents of the Interpolators collection:

...
<datavis:TreeMap.Interpolators>
    <datavis:SolidColorBrushInterpolator
        TargetName="Border"
        TargetProperty="Background"
        DataRangeBinding="{Binding Length}"
        From="#ffeeeeff"
        To="#ff8080ff"/>
</datavis:TreeMap.Interpolators>
...

What's an Interpolator? It's a new concept we've introduced for TreeMap to automatically map a particular value ("Length" in this case) to a range of something. In this case, the something is colors, so we're using SolidColorBrushInterpolator which works great for backgrounds and foregrounds. There's also DoubleInterpolator which is handy for changing opacity, font size, and things like that. And if you want to do something different, it's simple to create your own mapping by deriving from Interpolator (or RangeInterpolator<T>)!

We've purposely made SolidColorBrushInterpolator easy to use - note how it cleanly incorporates the familiar notions of Storyboard's TargetName and TargetProperty properties with ColorAnimation's From and To. The remaining property, DataRangeBinding, provides a Binding that's used to identify the relevant variable on the data objects. Once everything's in place, the Interpolator automatically interpolates each value according to where it falls in the overall range of values!

It's a simple, yet powerful concept that makes it easy to build powerful visualizations without writing any code at all. And please note that the TreeMap.Interpolators property is a collection, so you can add as many of these as you'd like in order to visualize multiple values or control multiple properties!

 

This time, instead of visualizing posts, let's visualize tags. Specifically, let's see how many posts have been tagged with each tag name (remembering that some posts have multiple tags):

TreeMap via Linq

The aggregation of per-post data into per-tag data is made easy by a simple bit of Linq that first creates a unique tag/post object for each tag/post pairing, then groups these all by tag, and finally selects them into a new collection of objects:

var blogPostsByTag = blogPosts
    .SelectMany(p => p.Tags.Select(t => new { Tag = t, Post = p }))
    .GroupBy(p => p.Tag)
    .Select(g => new BlogTag { Tag = g.Key, Posts = g.Select(p => p.Post).ToArray() });

After that, creating the TreeMap is easy - it's just a minor variation of what we've already seen.

 

For our final trick, let's enhance the previous sample so we can see posts sorted by popularity within each tag grouping:

Nested TreeMaps

This is pretty easy to do as well - it's the previous TreeMap with copies of the first TreeMap nested inside each tag node! Nested TreeMaps? Sure! Like I said, the DataTemplate model is totally flexible. The outer TreeMap creates the bounding boxes, and the inner TreeMaps sub-divide them according to post popularity. No sweat... :)

 

As I hope you'll agree, the new TreeMap is a pretty cool addition to the Data Visualization assembly! And we wouldn't have it if not for the tireless efforts of the folks on the GPD-E (Global Product Development Europe) team who we partnered with to deliver this new control. In particular, all the real work was done by Cristian Costache, Marek Latuskiewicz, and Gareth Bradshaw - I mainly helped coordinate things and do a bit of testing. I'd like to extend my sincere thanks to these guys for their passion and dedication these past few weeks! Please check out their blogs for more information about TreeMap in the coming weeks! :)

 

PS - If you have any questions or feedback, the right places to start are the Silverlight Discussion Forum or the WPF Discussion List. Bugs and feature requests can be logged with the Silverlight Issue Tracker or the WPF Issue Tracker. Please raise issues that are clearly unique to one platform or the other in the obvious place. But for general questions and things that are common to both platforms, the Silverlight forum/list is probably a better place because there's more context and history there.

PPS - TreeMap is currently available only as part of the Silverlight Toolkit. However, it should run perfectly well on WPF; in fact, I did that as part of the testing process! Interested parties can recompile the Silverlight source code for WPF in order to start playing with TreeMaps on WPF today; the process should be pretty easy because both the Silverlight and WPF Toolkits are open source and share the same source code files/layout/etc.. However, I'll be posting more about this in the next few days and hope to have explicit directions and a script to make the entire process easy for everyone! :)