The blog of dlaa.me

Posts tagged "Silverlight Toolkit"

A rose by any other name... [LayoutTransformControl on track to ship in the Silverlight Toolkit under the name LayoutTransformer!]

I'm a believer in the power of LayoutTransform - so much so that I wrote a control to graft this capability onto Silverlight 2 (which natively supports only the slightly less practical RenderTransform). I've posted extensively about what my LayoutTransformControl project is and how it works - you can read more via the following links: Motivation and introduction for Beta 1, Significant enhancements and update for Beta 2, Update for RTW, Fixes for two edge case bugs, Usefulness on WPF. I've been happy with how this project has gone and have gotten lots of good feedback from customers who are successfully making use of LayoutTransformControl in their own applications. So, yeah, I'm a fan... :)

LayoutTransformer Sample Application

That said, I haven't pushed to include this control in the Silverlight Toolkit - I figured things would sort themselves out if and when the need for LayoutTransform functionality became sufficiently compelling. As it happens, we saw this need arise for Silverlight Charting a few months ago, and the December 08 release of Charting includes a private (internal) copy of this control (we weren't prepared to officially expose it at the time). More recently, the issue came up again in the context of a different Toolkit control and in this case using a private implementation isn't practical.

Nobody really had time on the schedule to "productize" LayoutTransformControl, but it was becoming clear that our inability to take advantage of LayoutTransform functionality was limiting our ability to develop flexible controls. So I dedicated some time to making LayoutTransform an official part of the Toolkit and am happy to say that we're tentatively planning to include it in the next release of the Silverlight Toolkit under the name LayoutTransformer.

For a variety of reasons, I did this work in a private area of our source control tree. At some point I realized that by starting from the same Visual Studio solution/projects I'd already shared on my blog, I'd made it easy to give everyone a sneak peak of LayoutTransformer and benefit from some pre-release feedback. :) That said, if LayoutTransformer were simply a renamed LayoutTransformControl, I probably wouldn't bother - but there's an internal change of some significance that I would love to get early feedback on from folks who are already making use of LayoutTransformControl. To the extent that I've tested things, LayoutTransformer behaves the same as LayoutTransformControl - but if anyone finds otherwise, I'd definitely like to understand why!

LayoutTransformer Silverlight Test Cases

And as long as I'm publishing new bits, I thought it would be nice to include two new projects in the solution: a Silverlight-based testing project and a WPF-based one. These two test projects share the same source code, run cross-platform (just like LayoutTransformer), and get nearly complete code coverage from various flavors of 26 test cases. Even better, when the tests are run on WPF, they validate all scenarios against WPF's built-in LayoutTransform, too - thereby ensuring that LayoutTransformer matches the real LayoutTransform (for all the test scenarios, at least)!

I hope you like the new bits and would love to hear from anyone who tries LayoutTransformer out in their own project(s). Good or bad, your feedback is very much appreciated!

 

[Click here to download the LayoutTransformer source code, samples, and test projects.]

 

Notes:

  • The most obvious difference with the new bits is the name change from LayoutTransformControl to LayoutTransformer. Along with this renaming of the control itself, we've gone ahead and renamed a couple of other things for increased clarity. So migrating to LayoutTransformer involves a bit of manual effort, but it's important to emphasize that the changes are just to the names of things - the core behavior remains the same as it was before.
    Aside: I'll be the first to admit that "LayoutTransformer" isn't exactly my favorite name in the whole world - mostly because it always makes me think of other things. But all the other proposed names were rejected for valid reasons (ex: inconsistency with the platform, potential confusion, etc.). LayoutTransformer simply outlasted its competition and got the most votes when I put the issue to the team. Fortunately, it happens to be the shortest of the proposed names - and that helps me feel a little better! :)
  • The difference that concerns me just a bit is that LayoutTransformer derives from ContentControl whereas LayoutTransformControl has always derived from Control. We made this base class change because LayoutTransformer seemed to make more sense as a ContentControl given that it follows the same "put your content in me" model as the rest of the ContentControl subclasses (like Button). Furthermore, by being a ContentControl, it's now possible to put non-UI business objects inside a LayoutTransformer, set its ContentTemplate to a suitable DataTemplate, and have the transform automatically apply to the resulting UI elements. Additionally, it's nice that this switch simplifies the code ever so slightly. As I note above, my testing suggests there should be no negative impact - but if you run into any difficulty, please let me know!
  • Here's the relevant portion of my check-in notes for a list of the other changes we decided to make:
    Base class Control -> ContentControl
    Removed Child property and handlers; supplanted by Content property
    Transform property -> LayoutTransform
    TransformUpdated method -> ApplyLayoutTransform
    Create MatrixTransform in code instead of Template
    Added TemplatePartAttributes ("TransformRoot"/Grid, "Presenter"/ContentPresenter)
    Moved template to generic.xaml
    Sealed class
    Removed unnecessary #if for template definition
    
  • The WPF test project may fail to load if you don't have the unit testing feature of Visual Studio installed (which isn't supported by the Express editions as far as I know). If so, don't worry because that project's absence doesn't affect anything else. The good news is that the Silverlight test project is self-contained and should work for everyone!

 

DesignerProperties.GetIsInDesignMode_ForRealz [How to: Reliably detect Silverlight design mode in Blend and Visual Studio]

Catching up on Dave Campbell's fantastic WynApse feed earlier today, I came across this post by Bryant Likes about detecting design mode in Silverlight. One of the things mentioned in that post was the challenge of finding a design mode check that works correctly in both Expression Blend and Visual Studio's XAML designer. Unfortunately, the official mechanism for this, DesignerProperties.GetIsInDesignMode, doesn't always return the correct value under Visual Studio. :(

Well, I needed something reliable for the Charting assembly in the Silverlight Toolkit, so I checked with the experts (i.e., members of both teams) and came up with the following code that returns the correct value under both Blend and Visual studio (from DesignerProperties.cs in the Silverlight Toolkit source).

I present it here in the hope that others might also benefit:

/// <summary>
/// Provides a custom implementation of DesignerProperties.GetIsInDesignMode
/// to work around an issue.
/// </summary>
internal static class DesignerProperties
{
    /// <summary>
    /// Returns whether the control is in design mode (running under Blend
    /// or Visual Studio).
    /// </summary>
    /// <param name="element">The element from which the property value is
    /// read.</param>
    /// <returns>True if in design mode.</returns>
    [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "element", Justification =
        "Matching declaration of System.ComponentModel.DesignerProperties.GetIsInDesignMode (which has a bug and is not reliable).")]
    public static bool GetIsInDesignMode(DependencyObject element)
    {
        if (!_isInDesignMode.HasValue)
        {
            _isInDesignMode =
                (null == Application.Current) ||
                Application.Current.GetType() == typeof(Application);
        }
        return _isInDesignMode.Value;
    }

    /// <summary>
    /// Stores the computed InDesignMode value.
    /// </summary>
    private static bool? _isInDesignMode;
}

My new home page, expanded [Updated collection of great Silverlight Charting resources!]

It's been a couple of months since I shared my semi-comprehensive page of Charting resources on the web. During that time, the Silverlight Toolkit's December release came out with some great new Charting features (Woot!) and there have been a number of fantastic Charting posts that I'd like people to be aware of. So I've updated my previous list of links (FYI: old links are grayed-out) with all the new content that caught my eye:

Overviews (100 level)

Scenarios (200 level)

Internals (300 level)

My own Charting posts (Ego level)

Many, many thanks to everyone who has spent time helping others learn how to use Silverlight Charting!

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

Columns of a different color [Customizing the appearance of Silverlight charts with re-templating and MVVM]

When we created Silverlight Charting (background reading here and here), we tried to make things as designer-friendly as possible. So friendly, in fact, that it would be possible for someone to take the default look-and-feel of what we'd released and significantly enhance it without changing the Charting framework at all. :) That said, it's worth noting that Charting controls are a little different than typical WPF/Silverlight controls: while it might make sense to completely change how a ListBox looks, there are certain aspects of a chart that can't be changed without rendering the visualization meaningless. And so there are certain assumptions behind our Charting implementation around things we didn't expect users to want to change. But that's the great thing about users: they want to change these things anyway! :)

One of the fundamentals of column/bar charts is that the columns/bars of a single series are all drawn the same; that's what ties them together and makes it clear they represent a single series. If you create a column chart in Excel, the default color for the columns is blue. It's easy to change that color to orange or green or plaid, but by default all of the columns of the series change together because they're all part of the same series. (Incidentally, it is possible to change the colors of an individual column in Excel, but it's not entirely obvious how to do so and it's clearly not a mainline scenario.) With that in mind, it's no surprise that our charts behave similarly: you can provide whatever look you want for the columns and bars (via the ColumnSeries.DataPointStyle property, perhaps), but the columns and bars of a particular series always look the same.

But what if your scenario is such that you want to do things a little differently and you want more control over the colors of individual columns and bars? Well, you take advantage of re-templating and Model-View-ViewModel (MVVM), that's what! :) You're reading this blog, so I'll assume you already know what re-templating is - if not, here's a good place to start. Model-View-ViewModel (MVVM) is probably less well known to date - it's an approach to application development commonly used with WPF and Silverlight where simple wrapper classes are used to expose aspects of the underlying data types in a manner that's easy for the UI layer to deal with. You can read lots more about MVVM on John Gossman's blog or this recent MSDN article by Josh Smith. But I'm not here to teach you what re-templating or MVVM are - I'm here to show you how to use them with Charting to implement the multi-colored column scenario!

 

[Click here to download the complete Silverlight 2 source code for the sample application shown/discussed below.]

 

Imagine that you're a teacher and you want to chart the grades of your students. You've already got a basic Student class that exposes some basic properties and you can create instances from a database or a file or something. The Student class probably looks like this:

// Standard data object representing a Student
public class Student : INotifyPropertyChanged
{
    // Student's name
    public string Name { get; private set; }

    // Student's favorite color
    public Brush FavoriteColor { get; private set; }

    // Student's grade
    public double Grade
    {
        get { return _grade; }
        set
        {
            _grade = value;
            Helpers.InvokePropertyChanged(PropertyChanged, this, "Grade");
        }
    }
    private double _grade;

    // Student constructor
    public Student(string name, Brush favoriteColor)
    {
        Name = name;
        FavoriteColor = favoriteColor;
    }

    // INotifyPropertyChanged event
    public event PropertyChangedEventHandler PropertyChanged;
}

The class above exposes a name and a favorite color (which I've implemented here as a Brush for convenience). There's also a grade, but we'll come back to that shortly... The goal is for each column representing a student to be drawn using that student's favorite color. To accomplish this, all we need to do is re-template. Using a designer tool like Blend or something simple like my SilverlightDefaultStyleBrowser, we can copy the default Style for ColumnDataPoint and paste it into our project's resources. By removing stuff that's not relevant to the demonstration and making a single change (highlighted below), we arrive at something like the following:

<Style
    x:Key="ColorByPreferenceColumn"
    TargetType="charting:ColumnDataPoint">
    <Setter Property="Background" Value="DarkGray"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate
                TargetType="charting:ColumnDataPoint">
                <Border
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid Background="{Binding FavoriteColor}">
                        <Rectangle>
                            <Rectangle.Fill>
                                <LinearGradientBrush>
                                    <GradientStop Color="#77ffffff" Offset="0"/>
                                    <GradientStop Color="#00ffffff" Offset="1"/>
                                </LinearGradientBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                        <Border BorderBrush="#ccffffff" BorderThickness="1">
                            <Border BorderBrush="#77ffffff" BorderThickness="1"/>
                        </Border>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

This is just a tweak of the default template so that each column pulls its Background Brush from the FavoriteColor property of the underlying data object. Hook that up to a Chart/ColumnSeries in XAML, and that's all there is to it:

Color from Student

By the way, here's the XAML for that Chart:

<charting:Chart
    x:Name="FavoriteColorColumnChart"
    Title="Grades - By Favorite Color"
    Grid.Column="0">
    <charting:ColumnSeries
        DependentValueBinding="{Binding Grade}"
        IndependentValueBinding="{Binding Name}"
        DataPointStyle="{StaticResource ColorByPreferenceColumn}">
        <charting:ColumnSeries.DependentRangeAxis>
            <charting:LinearAxis
                Minimum="0"
                Maximum="100"
                Title="Grade"
                ShowGridLines="True"/>
        </charting:ColumnSeries.DependentRangeAxis>
    </charting:ColumnSeries>
</charting:Chart>
Aside: This process is even easier on WPF! (Assuming I had access to something like daily builds of Charting for WPF, I might have even mocked this up quickly to prove it to myself...) Unfortunately, the necessary "Binding in a Setter" capability is not supported by Silverlight 2 in XAML or code:
<Style
    x:Key="ColorByPreferenceColumn"
    TargetType="charting:ColumnDataPoint">
    <Setter Property="Background" Value="{Binding FavoriteColor}"/>
</Style>

So that's how easy it is to get custom column and bar colors if your data objects already expose the information you need!

But what if you want to base the custom colors on something that's not directly available on the data objects and you also don't have the freedom to change the data objects themselves? In other words - continuing the example above - let's say we decided to change things so the columns are colored according to each student's current grade: great grades get green columns, satisfactory grades get yellow columns, and unsatisfactory grades get red columns.

The first thing to consider when faced with a problem like this is whether an IValueConverter will work. I've written about the usefulness of IValueConverter before, so I won't spend more time on that here. IValueConverter is great if you want to take a single property and mutate it as part of a Binding. But what if you want to do something more complicated than that? Well, on WPF there's IMultiValueConverter which might do the trick, but that's not available on Silverlight and it's not always the answer anyway. So let's take advantage of MVVM to wrap our existing Student data objects with an object that's more view-friendly: StudentViewModel. Here's a trivial StudentViewModel class that exposes a Student and a Brush that's colored according to the Student's Grade property. Because Student implements INotifyPropertyChanged (like a well behaved class should), StudentViewModel can listen for changes to the Grade property and update its Brush automatically. StudentViewModel also implements INotifyPropertyChanged - so that anything referencing it will be notified about changes to the GradeColor property it exposes. Here's how it looks in code:

// Custom data object to wrap a Student object for the view model
public class StudentViewModel : INotifyPropertyChanged
{
    // Student object
    public Student Student { get; private set; }

    // Color representing Student's Grade
    public Brush GradeColor { get; private set; }

    // StudentViewModel constructor
    public StudentViewModel(Student student)
    {
        Student = student;
        student.PropertyChanged += new PropertyChangedEventHandler(HandleStudentPropertyChanged);
    }

    // Detect changes to the Student's grade and update GradeColor
    void HandleStudentPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if ("Grade" == e.PropertyName)
        {
            if (Student.Grade < 50)
            {
                GradeColor = new SolidColorBrush { Color = Colors.Red };
            }
            else if (Student.Grade < 80)
            {
                GradeColor = new SolidColorBrush { Color = Colors.Yellow };
            }
            else
            {
                GradeColor = new SolidColorBrush { Color = Colors.Green };
            }
            Helpers.InvokePropertyChanged(PropertyChanged, this, "GradeColor");
        }
    }

    // INotifyPropertyChanged event
    public event PropertyChangedEventHandler PropertyChanged;
}
Aside: I've typically seen view model classes implemented by re-exposing each of the interesting data object properties - so for each property Foo on the data object, there will be a property Foo' on the view model object (which is either identical to the original property or some derivative of it). While I can see the value of this approach in some cases, the duplication of properties always bothers me and so I've instead exposed the entire Student object from the StudentViewModel object as a property (along with the new GradeColor property). This saves me from duplicating any existing properties, exposes the entire Student object to users of the StudentViewModel object, and is completely future-proof because any updates to the Student implementation will automatically show up for users of StudentViewModel.

Now that we've got a view model class that exposes a view-friendly property that is exactly what we need, our job is easy: change the chart to use StudentViewModels and change the custom template to reference the GradeColor property. Here's the new template (with the same kind of change as before):

<Style
    x:Key="ColorByGradeColumn"
    TargetType="charting:ColumnDataPoint">
    <Setter Property="Background" Value="DarkGray"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate
                TargetType="charting:ColumnDataPoint">
                <Border
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid Background="{Binding GradeColor}">
                        <Rectangle>
                            <Rectangle.Fill>
                                <LinearGradientBrush>
                                    <GradientStop Color="#77ffffff" Offset="0"/>
                                    <GradientStop Color="#00ffffff" Offset="1"/>
                                </LinearGradientBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                        <Border BorderBrush="#ccffffff" BorderThickness="1">
                            <Border BorderBrush="#77ffffff" BorderThickness="1"/>
                        </Border>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The XAML for this chart is nearly identical and the end result looks just how we wanted it to:

Color from Grade

Having shown off how re-templating and MVVM enable more advanced Charting customization scenarios, I've accomplished what I set out to do and could have stopped here... But there was still one customer scenario I wanted to address: synchronizing the colors of pie slices in a pie chart with the colors of columns in a column chart. Given what we've just discussed, the solution is easy: just repeat the re-templating process for a second chart with a PieSeries and PieDataPoints. Because the column/slice colors come from the data objects and because both charts are sharing the same data objects, the color for every data object (student) will naturally be the same across both charts. The re-templated XAML is the same as before and the final result is exactly what we want:

Color from Student as Pie

Well, actually, that's not entirely true; Getting the pie slices right was trivial - but there was a bit of additional effort required to synchronize the colors of the pie chart's legend items with the pie slices...

The way things work is that the Series creates whatever LegendItems it needs. As part of that creation, it also creates a "fake" DataPoint that's styled just like the "real" ones displayed in the chart. This fake data point exists so that the LegendItem's default Template can create Bindings for things like the Background and BorderBrush properties. (Recall that users can completely change the look of a DataPoint, so the only way we have to know how something will look is to create it and see.) This approach works out pretty well, but there was an oversight that caused problems for me when I tried to provide my own PieSeries.LegendItemStyle: the DataContext of the fake PieDataPoint wasn't set to the corresponding slice's data object. Normally, that's no big deal because it's unused - however in this case it's a problem because the custom Template we created above gets its color from the data object. Without a bound data object to provide context, the legend items weren't using the right colors. :(

I thought about a few ways to work around this, but eventually decided the fix (the setting of the DataContext property for the fake PieDataPoint) belonged in the Charting code itself. Fortunately, Charting is open source, so it's easy for anybody to make such changes if/when the need arises! I've included a copy of the relevant source file with the one-line change I made (Changes\PieSeries.cs, line 317) and changed the sample project to use a custom build of Charting's Microsoft.Windows.Controls.DataVisualization.dll assembly that includes this change.

And because I'm a nice guy, I also made the same change to the actual charting source code that's under development, got it reviewed, and submitted it (along with an associated unit test) for inclusion in the next official release of the Silverlight Toolkit! After all, if I needed this to work for my sample, chances are good that someone else might need it to work for their application as well. :)

With that fix in place, here is the Style that applies the proper color to the LegendItems:

<Style
    x:Key="ColorByPreferenceLegendItem"
    TargetType="charting:LegendItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="charting:LegendItem">
                <StackPanel Orientation="Horizontal">
                    <Rectangle
                        Width="8" Height="8"
                        Fill="{Binding DataContext.FavoriteColor}"
                        Stroke="{Binding BorderBrush}"
                        StrokeThickness="1" Margin="0,0,3,0"/>
                    <datavis:Title Content="{TemplateBinding Content}"/>
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

 

And there you have it: a few simple ways to take Charting and extend it to do exactly what you want! The examples here are fairly simple, but re-templating and MVVM are very powerful concepts which enable a high degree of customization for Silverlight and WPF applications that's pretty hard to come by in other platforms. If you're trying to do something unique and you're not having any luck the "normal" way, please take a few moments to consider the techniques discussed here - you may find that your problem has an easy solution after all!

Yummier pies! [A technique for more flexible gradient styling of Silverlight Toolkit pie charts]

One of the goals of Charting for the Silverlight Toolkit is to enable rich, flexible styling by designers. (Background and overviews for Charting are available here and here.) And there are already some great resources for chart design: Designer’s Guide to Styling Silverlight Toolkit Charting Controls, Styling the Charts in the Silverlight Toolkit.

However, pie charts differ from the other common chart types in some significant ways and that makes the task of styling them a bit challenging. In particular, because styling happens at the DataPoint level (in the case of PieSeries, that's PieDataPoint: the visual representation of one of the slices of the pie), it initially seemed difficult to create a unified style for an entire pie because the sizes and positions of the individual slices can vary so dramatically for different data...

Working on something unrelated one day, I came across the GradientBrush.MappingMode property and realized the BrushMappingMode.Absolute enumeration was ideal for the pie styling problem. I created a simple demonstration and shared it with some folks on the Silverlight Toolkit team. That demo got passed around a bit and eventually made its way to Pete Brown; my sample shows up in his post about pie styling as what he calls the "Rainbow Brite" example.

Using Absolute mode is obviously a big win, but there's still a significant limitation: the coordinates it uses are expressed in pixels and therefore are closely tied to the size of the pie. What looks good for a pie at one size looks silly for that same pie when it is made a little bigger or smaller. I called this limitation out with my initial demo and Pete mentions it in his post as well...

Knowing that Absolute mode works well to enable cohesive styling for fixed-size pies (just look at Pete's great second example!), it seemed to me that the sizing limitation could be overcome with some fairly simple code. So when I got a chance, I wrote that code and put together the following proof-of-concept demonstration using the existing examples from Pete's post (with only a few trivial tweaks to the XAML to adjust gradient offsets). What's great is that now both pies can both be resized dynamically and the styling looks good at any size!

PieDataPointMappingModeUpdater sample application

[The complete sample code/project is available in PieDataPointMappingModeUpdater.zip as an attachment to this post.]

 

The principle here is simple: just update the relevant pixel coordinates of each gradient based on the current size of the PieSeries. What's more, a wrapper function abstracts out most of the complexity, so all that's necessary for the user is to pass in a simple helper method that's tailored to whatever specific pie design has been used. The helper is given the rectangular bounds of the pie and performs whatever gradient adjustments are necessary. It's that easy!

Here's what it looks like in code; the following is from the sample application's Page.xaml.cs:

/// <summary>
/// Initializes an instance of the Page class.
/// </summary>
public Page()
{
    InitializeComponent();

    // Hook up to Example1's PieSeries
    var pieSeries1 = Example1.Series[0] as PieSeries;
    PieDataPointMappingModeUpdater.UpdatePieSeries(pieSeries1, PieSeries1Updater, true);

    // Hook up to Example2's PieSeries
    var pieSeries2 = Example2.Series[0] as PieSeries;
    PieDataPointMappingModeUpdater.UpdatePieSeries(pieSeries2, PieSeries2Updater, true);
}

/// <summary>
/// Updates the gradients for Example1's PieSeries.
/// </summary>
private void PieSeries1Updater(PieDataPoint pieDataPoint, Rect pieBounds)
{
    var brush = pieDataPoint.Background as LinearGradientBrush;
    if (null != brush)
    {
        brush.StartPoint = new Point(pieBounds.Left, pieBounds.Top);
        brush.EndPoint = new Point(pieBounds.Right, pieBounds.Bottom);
    }
}

/// <summary>
/// Updates the gradients for Example2's PieSeries.
/// </summary>
private void PieSeries2Updater(PieDataPoint pieDataPoint, Rect pieBounds)
{
    var brush = pieDataPoint.Background as RadialGradientBrush;
    if (null != brush)
    {
        var center = new Point(
            pieBounds.Left + ((pieBounds.Right - pieBounds.Left) / 2),
            pieBounds.Top + ((pieBounds.Bottom - pieBounds.Top) / 2));
        brush.Center = center;
        brush.GradientOrigin = center;
        var radius = (pieBounds.Right - pieBounds.Left) / 2;
        brush.RadiusX = radius;
        brush.RadiusY = radius;
    }
}

Note that the constructor simply hooks things up and that each helper method is specific to the pie design it will be updating. All each updater does is nudge the pixel coordinates of its gradient to match up with the pie's size. So it seems reasonable to assume that other - potentially more complex - pie designs can be updated just as easily.

Here's the proof-of-concept wrapper method that does the bulk of the work:

/// <summary>
/// Updates the PieDataPoints of a PieSeries by applying the specified action to each.
/// </summary>
/// <param name="pieSeries">PieSeries instance to update.</param>
/// <param name="updater">Action to run for each PieDataPoint.</param>
/// <param name="keepUpdated">true to attach to the SizeChanged event of the PieSeries's PlotArea.</param>
public static void UpdatePieSeries(PieSeries pieSeries, Action<PieDataPoint, Rect> updater, bool keepUpdated)
{
    // Apply template to ensure visual tree containing PlotArea is created
    pieSeries.ApplyTemplate();
    // Find PieSeries's PlotArea element
    var children = Traverse<FrameworkElement>(
        pieSeries,
        e => VisualTreeChildren(e).OfType<FrameworkElement>(),
        element => null == element as Chart);
    var plotArea = children.OfType<Panel>().Where(e => "PlotArea" == e.Name).FirstOrDefault();
    // If able to find the PlotArea...
    if (null != plotArea)
    {
        // Calculate the diameter of the pie (0.95 multiplier is from PieSeries implementation)
        var diameter = Math.Min(plotArea.ActualWidth, plotArea.ActualHeight) * 0.95;
        // Calculate the bounding rectangle of the pie
        var leftTop = new Point((plotArea.ActualWidth - diameter) / 2, (plotArea.ActualHeight - diameter) / 2);
        var rightBottom = new Point(leftTop.X + diameter, leftTop.Y + diameter);
        var pieBounds = new Rect(leftTop, rightBottom);
        // Call the provided updater action for each PieDataPoint
        foreach (var pieDataPoint in plotArea.Children.OfType<PieDataPoint>())
        {
            updater(pieDataPoint, pieBounds);
        }
        // If asked to keep the gradients updated, hook up to PlotArea.SizeChanged as well
        if (keepUpdated)
        {
            plotArea.SizeChanged += delegate
            {
                UpdatePieSeries(pieSeries, updater, false);
            };
        }
    }
}

Attentive readers may have noticed that I'm using the same Traverse<T> implementation that ImplicitStyleManager uses - though I've got a custom getChildNodes implementation that works with the visual tree:

/// <summary>
/// Implementation of getChildNodes parameter to Traverse based on the visual tree.
/// </summary>
/// <param name="reference">Object in the visual tree.</param>
/// <returns>Stream of visual children of the object.</returns>
private static IEnumerable<DependencyObject> VisualTreeChildren(DependencyObject reference)
{
    var childrenCount = VisualTreeHelper.GetChildrenCount(reference);
    for (var i = 0; i < childrenCount; i++)
    {
        yield return VisualTreeHelper.GetChild(reference, i);
    }
}

 

And that's all there is to it! Now, not only do you know how to create appealing, holistic designs for fixed-size pie charts, you've got an easy way to keep those designs looking sharp for dynamically-sized pie charts as well. So go forth with that knowledge - and make even tastier pies! :)

[PieDataPointMappingModeUpdater.zip]

Expanded access to Silverlight 2's generic.xaml resources [SilverlightDefaultStyleBrowser updated for better compatibility!]

Kind reader (and fellow Silverlight Charting blogger!) Pete Brown contacted me recently to report that my SilverlightDefaultStyleBrowser application (background reading available here, here, here, here, and here) didn't seem to be working for assemblies from the Silverlight Toolkit or Telerik's RadControls for Silverlight. Specifically, he found that SilverlightDefaultStyleBrowser would work successfully with the controls in the Silverlight runtime and in the Silverlight SDK - but when he used the "Add Assembly" button to add assemblies from the Toolkit or RadControls, no new controls appeared in the list. This was unexpected and I started investigating...

Silverlight Toolkit: This scenario was particularly troubling because I'm on the Toolkit team and I know it's not doing anything weird that should break SilverlightDefaultStyleBrowser. So I stepped through the code for parsing generic.xaml and discovered that the root ResourceDictionary wasn't getting loaded. A bit more debugging revealed that this was because the Silverlight Toolkit's generic.xaml uses a different XAML namespace than SilverlightDefaultStyleBrowser was looking for (and than nearly every other Silverlight assembly I've seen). But the Toolkit is not at fault here - it turns out that Silverlight supports two different XAML namespaces! So I added support for the second namespace to SilverlightDefaultStyleBrowser, and now it happily parses assemblies from the Silverlight Toolkit. :)

RadControls: I'd assumed the issue here was the same and expected the RadControls to "just work" now that I'd added support for the second XAML namespace - but I was wrong. :( So I stepped through SilverlightDefaultStyleBrowser's generic.xaml parsing code again (recall that generic.xaml is a public entry point for Silverlight assemblies) and discovered that SilverlightDefaultStyleBrowser was finding and parsing the RadControls generic.xaml just fine all along - except that there weren't any Style elements in it. So SilverlightDefaultStyleBrowser's behavior was "correct" in the first place! What's different here is that the Telerik assemblies expose a generic.xaml containing references to custom elements that work at run time (when generic.xaml is parsed and executed by Silverlight), but which do not work with the simple XML-level parsing that SilverlightDefaultStyleBrowser performs. I'm guessing it would be fairly straightforward to modify SilverlightDefaultStyleBrowser to successfully expose the Telerik Styles, but I'm not going to add that custom code until/unless I hear from someone at Telerik that this is something they're okay with. So (for now, at least), SilverlightDefaultStyleBrowser doesn't find any Styles in the RadControls assemblies; that behavior is correct and "by design".

Having investigated the problem report and fixed what I could, I updated the public SilverlightDefaultStyleBrowser application and the downloadable source code. And started writing this post! :)

 

The version number of SilverlightDefaultStyleBrowser appears in the window's title and the latest release number is 1.0.3268.34946. (Note: I haven't updated the screen shot below which shows the introductory version number.) If installed via ClickOnce, the application should automatically prompt you to upgrade when it detects the update (which typically happens after running the application once or twice). If you're using the standalone EXE, you'll need to update manually.

SilverlightDefaultStyleBrowser Application

Click here or on the image above to install SilverlightDefaultStyleBrowser via ClickOnce with automatic updating.

Click here to download the standalone SilverlightDefaultStyleBrowser executable and source code in a ZIP file.

 

SilverlightDefaultStyleBrowser was written to do one thing and to do it simply. As is often the case when trying to duplicate existing behavior, there tend to be a few surprises along the way where things turn out not to work quite as expected. This was one of those surprises and I'm glad for the opportunity to fix this and make SilverlightDefaultStyleBrowser just a little more useful for everyone!

Great Silverlight charts are still just a click away [ChartBuilder sample and source code updated for Charting's December 08 release]

In yesterday's announcement of the December 08 release of Charting for Silverlight, I outlined some of the new Charting features and mentioned a forthcoming update to my ChartBuilder tool to show off the new features. (Background reading for ChartBuilder: Introduction and "user's guide", Updates.)

The new ChartBuilder is now live on the web - and I've even updated my sample screenshot! :)

You can click this text or the image below to run the latest ChartBuilder in your browser.

ChartBuilder

[And click here to download the complete ChartBuilder source code.]

Release notes:

  • Added support for new series type BubbleSeries. To keep thing simple and avoid needing to specify another set of values, the bubble size is bound to the dependent value of the series.
  • Added support for the new Independent(Range|Category)Axis and Dependent(Range|Category)Axis properties of the Column/Bar/Line/Scatter/Bubble series types. Off by default, either axis can be enabled by checking the relevant box in the settings panel for a series - then customized as always.
  • Added a ToolTip for the "Axes" header label to display the current value of the Chart.ActualAxes property in pseudo-XAML. More of a diagnostic aid than anything, this information can help understand the interaction between axes and series - and what's really going on behind the scenes.
  • Increased the maximum number of stand-alone Axis instances from 2 to 4 now that multiple axes are supported.
  • Various other improvements.
  • Updated the version to 2008-12-07.

ChartBuilder continues to be tremendously useful to me as an interactive Charting test application. The plethora of knobs, dials, and switches that represent a user experience designer's worst nightmare [ :) ] enable a degree of scenario testing that has helped find and fix a wide variety of issues. Furthermore, the interactive nature of the tool - and the ease with which it enables mocking up samples for answers on the Silverlight Controls and Silverlight Toolkit forum - has really seemed to help people understand Charting more easily.

My hope is that ChartBuilder can be just as useful to you - so if you've got ideas after using it, please let me know!

Silverlight Charting gets a host of improvements [Silverlight Toolkit December 08 release now available!]

The December 08 release of the Silverlight Toolkit was published a short while ago. Just about every control in the Toolkit got some love and attention for this release and I encourage you to have a look, download it, and enjoy!

That said, readers of my blog know that I'm all about the Charting. :) So just like my original Charting announcement and overview post, here's an announcement of the December 08 Charting release and an overview of some of the high points. By now, I assume folks have had time to play around with Charting and are starting to get into some more advanced scenarios, so the samples are going to be a little more technical than before. If you want a refresher on basic Charting concepts, please have a look at my original overview - all of those concepts still apply.

Though the Charting team was down to just two of us for this release, Jafar and I have done our best to deliver some pretty compelling new features. It's important to note that there are a few breaking changes from the November 08 release, but I hope you'll agree that the new functionality is worth the minor inconvenience of updating existing Charting code. As it happens, if you don't explicitly use the Axis class, you probably won't need to change anything at all!

 

I wrote the following summary for the release notes, and it seems like a good way to begin:

Notable Changes

Support has been added for arbitrary numbers of Axes for a Chart. A key and significant consequence of this change is that it is now possible to mix previously incompatible Series in the same chart (example: Column and Bar). The former "automatically share axes when possible" behavior remains present and can be used to render two series with a shared independent axis and different dependent axes in order to display related-but-differently-scaled values (example: an engine's torque and RPM).

The Column/Bar/Line/Scatter Series classes expose two new (optional) properties which specify a particular Axis instance to be used by the Series (rather than letting the Series choose from the Chart's Axes collection as it does by default). These properties allow very specific customization of each Series' axes in situations where maximum control is desired.

A design-time assembly for Charting has been created which is automatically used by Blend and Visual Studio to enhance the design-time experience. Most class properties are now categorized into custom "Data Visualization" and "Data Visualization Styles" categories to make them easier to find - while particularly common properties are now found in the "Common" category. ToolTips identify the purpose of each control and property, and Toolbox icons are automatically associated with each class. (Note: Some of these features are only supported by one of the design tools.)

The design-time behavior of DataPoints has been enhanced: DataPoints are now visible by default when dropped onto the design surface in Blend, so styling them is easier. PieDataPoint now creates a default Geometry (that can be customized interactively with the ActualRatio and ActualOffsetRatio properties) which makes it possible to see and understand relevant styling changes. DataPoints in a Chart show up in Visual Studio without a refresh of the design surface.

The Chart class is now decorated with ContentPropertyAttribute("Series") which means that it is no longer necessary to explicitly wrap Series with <charting:Chart.Series> ... </charting:Chart.Series> in XAML. Additionally, this enables Blend to display a Chart's Series in the "Objects and Timeline" pane and makes accessing the properties of a Series considerably easier. Example of simplified XAML syntax:

    <charting:Chart>
        <charting:PieSeries ... />
    </charting:Chart>

The behavior of the Axis classes during animations that expand or shrink the range of displayed data has been changed so that the animation of the size change is smooth (vs. jumping between axis intervals). This significantly increases the ease with which dynamic data changes can be observed and understood by the viewer.

The DataPointSeries class (a subclass of Series) has been unsealed to make the task of writing a custom Series considerably easier. While this change doesn't expose the entire hierarchy on which the "in-box" Series are built, it is a significant benefit to developers because DataPointSeries implements many of the key Series notions: the ItemsSource property, DataPoint creation, dynamic data detection, change animation, show/hide transitions, DataPoint selection, and more.

A new Series type, BubbleSeries, has been added (along with its associated BubbleDataPoint class). BubbleSeries is similar in nature to ScatterSeries, but conveys additional information by using the size of the data points to display an additional dependent value for each of the data points.

In scenarios where multiple columns/bars (of ColumnSeries/BarSeries) share the same category, all such data points will now be automatically displayed overlapping so that smaller values will no longer be obscured from view by larger values.

Breaking Changes

The Axis.AxisType property has been replaced by a corresponding hierarchy of Axis classes. The concrete classes map directly to the AxisType values that were removed: LinearAxis, DateTimeAxis, CategoryAxis. In addition to improving overall API consistency, a consequence of this refactoring is that the LinearAxis.Minimum/Maximum properties are now strongly typed as double and the corresponding properties on DateTimeAxis are strongly typed as DateTime - both of which improve development-time and design-time usability.

The Axis.ShouldIncludeZero property has been removed; this property is has no meaning for some axis types and is typically not needed because the same thing can be accomplished by setting Minimum or Maximum accordingly. A consequence of this is that Column/Bar charts will usually not include the 0 value by default (and therefore the "beginnings" of the columns/bars can be truncated). This behavior is consistent with Excel (demonstrated by creating a column chart of the values 10, 11, and 12 in Excel), though truncation is more likely because Charting's heuristics are more aggressive with regard to excluding 0.

Other Changes

Various UI improvements and bug fixes.

Whew - there's a lot of good stuff in there! :)

 

So now that we have an idea what's new, let's start by looking at the support for multiple axes with a common scenario: charting two quantities that are related, but measured in different units and/or on different scales. For the purposes of this demonstration, let's chart the performance characteristics of an imaginary engine:

Chart with multiple axes

[Note: Complete source code for all of the sample charts here can be found in the ChartingIntroduction.zip file attached to this post.]

The XAML for this Chart is straightforward (and discussed below):

<charting:Chart Title="Engine Performance">
    <!-- Power curve -->
    <charting:LineSeries
        Title="Power"
        ItemsSource="{StaticResource EngineMeasurementCollection}"
        IndependentValueBinding="{Binding Speed}"
        DependentValueBinding="{Binding Power}"
        MarkerWidth="5"
        MarkerHeight="5">
        <!-- Vertical axis for power curve -->
        <charting:LineSeries.DependentRangeAxis>
            <charting:LinearAxis
                Orientation="Vertical"
                Title="Power (hp)"
                Minimum="0"
                Maximum="250"
                Interval="50"
                ShowGridLines="True"/>
        </charting:LineSeries.DependentRangeAxis>
    </charting:LineSeries>
    <!-- Torque curve -->
    <charting:LineSeries
        Title="Torque"
        ItemsSource="{StaticResource EngineMeasurementCollection}"
        IndependentValueBinding="{Binding Speed}"
        DependentValueBinding="{Binding Torque}"
        MarkerWidth="5"
        MarkerHeight="5">
        <!-- Vertical axis for torque curve -->
        <charting:LineSeries.DependentRangeAxis>
            <charting:LinearAxis
                Orientation="Vertical"
                Title="Torque (lb-ft)"
                Minimum="50"
                Maximum="300"
                Interval="50"/>
        </charting:LineSeries.DependentRangeAxis>
    </charting:LineSeries>
    <charting:Chart.Axes>
        <!-- Shared horizontal axis -->
        <charting:LinearAxis
            Orientation="Horizontal"
            Title="Speed (rpm)"
            Interval="1000"
            ShowGridLines="True"/>
    </charting:Chart.Axes>
</charting:Chart>

We start with the usual Chart object to contain the series - but now we can put the series directly inside the Chart tags instead of inside nested Chart.Series tags (though that syntax still works and is fully supported). There are two LineSeries here (one for torque and one for power) and each starts out as you'd expect by hooking up to the data and doing a bit of customization. But then there's something new: the LineSeries.DependentRangeAxis property is used to identify a specific LinearAxis. Every series type (other than PieSeries which doesn't have axes) now exposes a Dependent???Axis property and an Independent???Axis property (where "???" is "Range" or "Category" depending on which series it is).

Normally, a series will search the Chart.Axis collection to find an axis that it can use - but when these new properties are set, it always uses the specified axis. So in this example we're providing a specific LinearAxis for each LineSeries to use and we're customizing it slightly to get the chart looking just how we want. Having specified both LineSeries in this manner, a third LinearAxis is added to the Chart.Axis collection where it will be found - and used - by both LineSeries when they acquire an independent value axis.

 

Moving on, here's a chart using the new BubbleSeries in a financial scenario (for a fictional stock ticker symbol). It's the usual "stock price by date" chart we've all seen before - but this chart is also showing the trading volume each day by varying the size the data points:

Bubble chart

The XAML for that Chart is what you'd expect:

<charting:Chart Title="Stock Performance">
    <!-- Stock price and volume -->
    <charting:BubbleSeries
        Title="ABCD"
        ItemsSource="{StaticResource StockDataCollection}"
        IndependentValueBinding="{Binding Date}"
        DependentValueBinding="{Binding Price}"
        SizeValueBinding="{Binding Volume}"
        DataPointStyle="{StaticResource CustomBubbleDataPointStyle}"/>
    <charting:Chart.Axes>
        <!-- Axis for custom labels -->
        <charting:DateTimeAxis
            Orientation="Horizontal">
            <charting:DateTimeAxis.AxisLabelStyle>
                <Style TargetType="charting:DateTimeAxisLabel">
                    <Setter Property="StringFormat" Value="{}{0:MMM d}"/>
                </Style>
            </charting:DateTimeAxis.AxisLabelStyle>
        </charting:DateTimeAxis>
    </charting:Chart.Axes>
</charting:Chart>

BubbleSeries is used just like a ScatterSeries, but it exposes an additional property SizeValueBinding that works just like Independent/DependentValueBinding to identify the source of the size values. The DataPointStyle property specifies a custom style for BubbleDataPoint (not shown here) where we've added the day's volume to the ToolTip of each bubble. This custom style was created in the usual manner - by starting from the default style for BubbleDataPoint and making the desired changes. (I did so in Visual Studio's XAML editor, but it could just as easily be done in Blend.)

Tweaking things just a little more, the independent value axis is customized by setting its AxisLabelStyle property and providing a style for the new DateTimeAxisLabel class. DateTime/NumericAxis/AxisLabel are the classes used to display an axis's labels. In this case we've provided a custom StringFormat so that the dates are all displayed in the friendly Month+Day pattern seen above. DateTimeAxisLabel offers specific StringFormat properties for each supported IntervalType - but in this case we've simply used the StringFormat property which conveniently overrides the other, more specific types.

 

Next up is a fairly typical column chart showing made-up bowling scores for some of the people on the Toolkit team. But there's twist because there are two "Shawn"s on the team:

Chart with overlapping columns

By now, the XAML is probably kind of boring in its predictability:

<charting:Chart Title="Bowling Scores">
    <!-- Scores -->
    <charting:ColumnSeries
        Title="Score"
        ItemsSource="{StaticResource ScoreDataCollection}"
        IndependentValueBinding="{Binding Player}"
        DependentValueBinding="{Binding Score}">
        <charting:ColumnSeries.IndependentCategoryAxis>
            <!-- Axis for automatic sorting -->
            <charting:CategoryAxis
                Orientation="Horizontal"
                SortOrder="Ascending"/>
        </charting:ColumnSeries.IndependentCategoryAxis>
    </charting:ColumnSeries>
</charting:Chart>

This is a standard ColumnSeries, which is specifying a IndependentCategoryAxis so the new SortOrder property of CategoryAxis can be used to automatically sort the category names. This data set contains two items with the independent value "Shawn". Previously, both would have been displayed in the same category slot with the same width which means the column in front could have obscured the one in back and led viewers to believe that only four scores were being displayed. But now ColumnSeries and BarSeries automatically detect this situation and overlap columns/bars in the same category slot (after sorting them by size). This behavior makes it clear that five values are being shown and makes it easy to hover over either of the "Shawn"s to get a ToolTip with the associated score.

 

The previous chart is interesting for another reason, too: it's using the natural, simple way to display the data, but a purist might argue that it's also the "wrong" way to do so... [Jafar and I actually had this "argument" - I won't say who took which position. :) ] Categorizing by player name works well enough, but what if we also had an image for each player and we wanted to display that image instead of their name? Changing the ColumnSeries.IndependentValueBinding property might work, but it's become obvious that we're mixing display concerns and data concerns.

When maintaining developer/designer separation is critical, what's probably more appropriate is to categorize by the actual data object - and then display it however we want. Here's a chart that does just that:

Chart with unique categories

And here's the XAML - which is a bit more complicated than before, but still quite manageable:

<charting:Chart>
    <!-- Customized Title -->
    <charting:Chart.Title>
        <StackPanel>
            <TextBlock
                Text="Bowling Scores"
                HorizontalAlignment="Center"/>
            <TextBlock
                Text="(Alternate Approach)"
                FontSize="10"
                HorizontalAlignment="Center"/>
        </StackPanel>
    </charting:Chart.Title>
    <!-- Scores -->
    <charting:ColumnSeries
        Title="Score"
        ItemsSource="{StaticResource ScoreDataCollection}"
        IndependentValueBinding="{Binding}"
        DependentValueBinding="{Binding Score}">
        <charting:ColumnSeries.IndependentCategoryAxis>
            <!-- Axis for automatic sorting and custom labels -->
            <charting:CategoryAxis
                Orientation="Horizontal"
                SortOrder="Ascending">
                <charting:CategoryAxis.AxisLabelStyle>
                    <Style TargetType="charting:AxisLabel">
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="charting:AxisLabel">
                                    <TextBlock Text="{Binding Player}"/>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </charting:CategoryAxis.AxisLabelStyle>
            </charting:CategoryAxis>
        </charting:ColumnSeries.IndependentCategoryAxis>
    </charting:ColumnSeries>
</charting:Chart>

Now that we've set IndependentValueBinding to the data objects themselves, there are automatically five categories (one for each of the five objects in the data set). (Aside: CategoryAxis will still sort them for us because the data objects implement the IComparable interface.) Once the data is set up "properly", all that's left to do is customize how the objects are displayed - and this is done by providing a custom template for the AxisLabel class (something that's already been discussed).

One other thing to note above is how the Chart.Title property is set to a tree of UI elements instead of the usual string. Chart.Title (and other such properties in Charting) follows the ContentControl model because it makes simple things simple while also allowing more advanced scenarios. In this case, all we've done is provide a "subtitle" - but we could also have gone much further and created something very customized.

 

The last thing I wanted to highlight was some of the design-time enhancements for Charting in this release. Data points were previously a little tricky to customize because their default state is not visible (which allows the reveal/show animation to fade them in without flicker). Data points now check if they're being used in design-time and will automatically play their reveal/show animation so that they're visible on the design surface by default. PieDataPoint used to be extra tricky because its Geometry property is only set when it's actually being used by PieSeries. But now it knows about design-time and its ActualRatio/ActualOffsetRatio properties can be changed to interactively evaluate different styling approaches. Here's a stand-alone PieDataPoint in Blend showing off both of these improvements (along with the new "Data Visualization" property category):

PieDataPoint in Blend

 

I hope you're excited by some of the new functionality we've just looked at from the December 08 release of Charting! And naturally, we've made a some other improvements, too! :) You can browse the live Charting sample page to find out more - then download the December 08 Toolkit release and start playing around with the new stuff! As always, if you have questions about Charting, you can ask them in the Silverlight Controls forum. If you think you've found a bug, please report it with the Issue Tracker in the Toolkit's CodePlex site.

 

PS - If you're looking for an update to my ChartBuilder application (Background reading: Introduction, Update) to show off the new Charting functionality, please stay tuned because it will be available very soon! :)

PPS - I'm on vacation in December, so responses to blog comments and emails may be delayed. But please don't let that stop you from contacting me: I'll follow up on everything I get - I just might be a little slower than usual. :)

[ChartingIntroduction.zip]

My new home page [A collection of great Silverlight Charting resources!]

It's been nearly two weeks since the Silverlight Toolkit's November release. I've been trying to keep up with what's been written about Charting and want to summarize some of the most helpful posts I've seen so far.

Overviews (100 level)

Scenarios (200 level)

Internals (300 level)

My own Charting posts (Ego level)

Many, many thanks to everyone who has spent time helping others learn how to use Silverlight Charting!

PS - If I've missed any good resources, please leave a comment and to them - I'm always happy to find good Charting content! :)

Improving ChartBuilder's cultural sensitivity [ChartBuilder app/source updated!]

Kind readers Eugenio and Thimp independently reported that the initial version of ChartBuilder did not work well on machines with certain culture settings. Specifically, ChartBuilder was inadvertently using the CurrentCulture for formatting numbers in XAML and that's a problem because the XAML is parsed according to the InvariantCulture. For example, on German machines ChartBuilder would write a double value to XAML as "1,23" according to the German culture settings - and the XAML parser would complain that wasn't a valid value. I'm usually able to catch problems like this by running code analysis and fixing all the Microsoft.Globalization warnings like CA1305, but in this case the code was calling objectInstance.ToString() which doesn't have any IFormatProvider-based overloads and therefore generates no warnings.

My sincerest apologies to anyone who was affected by this problem. I have switched one of my machines to the German locale and will leave it there for a couple of days as punishment. :)

I made the culture fix on my bus ride this morning and had a bit of extra time, so I also made the following improvements:

  • Added support for the Axis.Interval property. Now it's possible to customize the frequency of the labels, tickmarks, and gridlines on an axis.
  • Added the "datavis" xmlns prefix so it's already present for people who might need to reference it. [Like me when I posted a sample to the forums the other day. :) ]
  • Updated the version to 2008/11/06.

Again, sorry for any trouble. I hope the updated ChartBuilder works well for everyone - and that you enjoy the minor improvements!

 

You can click this text or the image below to run the latest ChartBuilder in your browser.

ChartBuilder

[And click here to download the complete ChartBuilder source code.]