Sep 20, 2009

How to port the WPF labeled pie chart to Silverlight

Update April 4 2010: The LabeledPieChart code in this post has been updated to the latest WPF and Silverlight toolkits. You can find more details in this blog post.


Two posts ago I showed a possible solution to add labels to a WPF pie chart. In my last post, I explained some implementation details of that solution. In this blog post, I will show the steps I took to port the labeled pie chart to Silverlight.

The Silverlight and WPF teams do their best to ensure that porting a Silverlight application to WPF is a smooth experience. This is expected, since Silverlight is (for the most part) a subset of WPF. Porting WPF applications to Silverlight, on the other hand, can be pretty tricky. The Silverlight and WPF teams are not specifically supporting this scenario, but the reality is that in the real world, many people need to do just that. I decided to port my WPF labeled pie chart to Silverlight to see how many issues I would come across and to document the workarounds.

MeasureOverride is sealed in Silverlight

The first issue I encountered was the fact that Canvas’ MeasureOverride is sealed in Silverlight, so I couldn’t override it. I was overriding it in the PieChartLabelArea, where I had to know whether any of the child labels are associated with small arcs to help with the Auto display mode.

My workaround was to derive from Panel instead, since Panel’s MeasureOverride is not sealed. Since Panel doesn’t do automatic arranging, I had to also implement ArrangeOverride:

    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach (UIElement child in this.Children)
        {
            child.Arrange(new Rect(new Point(0, 0), child.DesiredSize));
        }

        return finalSize;
    }

In this scenario it was OK to replace the Canvas with a Panel because the WPF implementation wasn’t using any of Canvas’ functionality other than the automatic arranging. The contents of the labels are positioned inside a different Canvas that’s part of the ControlTemplate for PieChartLabel. When PieChartLabels are added to the PieChartLabelArea, the canvases in their templates are all placed at the origin of the label area, and each label is positioned correctly within its own canvas.

OverrideMetadata doesn’t exist in Silverlight

In WPF I was using the following code in the LabeledPieChart’s static constructor to set its default style key:

    static LabeledPieChart()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(LabeledPieChart), new FrameworkPropertyMetadata(typeof(LabeledPieChart)));
    }

Since OverrideMetadata doesn’t exist in Silverlight, I used the instance constructor instead to set the DefaultStyleKey property:

    public LabeledPieChart()
    {
        this.DefaultStyleKey = typeof(LabeledPieChart);
    }

AddOwner doesn’t exist in Silverlight

In WPF, I was registering some DPs with the convenient AddOwner method. For example, the following line of code indicates that the Title property of Chart can be applied to LabeledPieChart as well:

    public static readonly DependencyProperty TitleProperty = Chart.TitleProperty.AddOwner(typeof(LabeledPieChart), null);

The workaround is to register a new DP with the same name in LabeledPieChart. The syntax is not much longer:

    public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(object), typeof(LabeledPieChart), new PropertyMetadata(String.Empty));

Vector doesn’t exist in Silverlight

The WPF version of this code makes use of the Vector class in several places. We rely on Vector in PieChartHelper to calculate the midpoint of a wedge’s arc, and in PieChartLabel to determine the three points needed in Connected mode.

As a workaround, I decided to bring a version of WPF’s Vector class into the Silverlight project. I simply copied this code from Reflector and tweaked it a bit.

FrameworkPropertyMetadataOptions.AffectsArrange doesn’t exist in Silverlight

In WPF, I specified that the DisplayMode property should invalidate arrange when set, which in turn was causing the label to be repositioned.

    public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register("DisplayMode", typeof(DisplayMode), typeof(PieChartLabel), new FrameworkPropertyMetadata(DisplayMode.ArcMidpoint, FrameworkPropertyMetadataOptions.AffectsArrange));

FrameworkPropertyMetadataOptions.AffectsArrange doesn’t exist in Silverlight, but we can work around it by invalidating arrange in the change handler for the DP. Here’s the corresponding code in Silverlight:

    public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register("DisplayMode", typeof(DisplayMode), typeof(PieChartLabel), new PropertyMetadata(DisplayMode.ArcMidpoint, DisplayModePropertyChanged));

    private static void DisplayModePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        PieChartLabel label = obj as PieChartLabel;
        if (label != null)
        {
            label.InvalidateArrange();
        }
    }

FrameworkPropertyMetadataOptions.AffectsParentMeasure doesn’t exist in Silverlight

In WPF, I specified that changing the Geometry property of PieChartLabel should affect the parent’s measure when I registered the DP:

    public static readonly DependencyProperty GeometryProperty = PieDataPoint.GeometryProperty.AddOwner(typeof(PieChartLabel), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsParentMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, GeometryPropertyChanged));

The equivalent behavior in Silverlight can be done by adding a change handler for this DP that invalidates measure on the element’s parent:

    public static readonly DependencyProperty GeometryProperty = DependencyProperty.Register("Geometry", typeof(Geometry), typeof(PieChartLabel), new PropertyMetadata(null, GeometryPropertyChanged));

    private static void GeometryPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        PieChartLabel label = obj as PieChartLabel;
        if (label != null)
        {
            …
            FrameworkElement fe = label.Parent as FrameworkElement;
            if (fe != null)
            {
                fe.InvalidateMeasure();
            }
        }
    }

Template.FindName doesn’t exist in Silverlight

WPF has a useful method that allows us to find an element with a specified name within a template:

    PieChartLabelArea labelArea = chart.Template.FindName("LabelArea_PART", chart) as PieChartLabelArea;

Silverlight doesn’t include the FindName method. As a workaround, I wrote a helper method that searches the visual tree until it finds an element with the specified name. The following line of code returns the label area in Silverlight:

    PieChartLabelArea labelArea = TreeHelper.FindDescendent(chart, "LabelArea_PART") as PieChartLabelArea;

FrameworkElement.Unloaded doesn’t exist in Silverlight

In the WPF version of this project, I ensure that the labels are removed when the PieDataPoints are unloaded with the following code:

    pieDataPoint.Unloaded += delegate
    {
        labelArea.Children.Remove(label);
    };

However, in Silverlight, FrameworkElement does not have an Unloaded event. As a workaround, I check whether the PieDataPoint is still in the tree every time layout is updated. When I discover that the data point is no longer in the tree, I remove the corresponding label from the tree.

    pieDataPoint.LayoutUpdated += delegate
    {
        if (!pieDataPoint.IsInTree())
        {
            labelArea.Children.Remove(label);
        }
    };

And here’s how I implemented the IsInTree helper method:

    public static bool IsInTree(this FrameworkElement element)
    {
        var rootElement = Application.Current.RootVisual as FrameworkElement;
        while (element != null)
        {
            if (element == rootElement)
            {
                return true;
            }
            element = VisualTreeHelper.GetParent(element) as FrameworkElement;
        }
        return false;
    }

SnapsToDevicePixels doesn’t exist in Silverlight

I set the inherited property SnapsToDevicePixels to true on the label area in WPF to make sure all the labels have crisp borders – I don’t want antialiasing to blur any edges that fall between pixel boundaries. There’s no SnapsToDevicePixels property in Silverlight, but the workaround is easy: just leave it out! Silverlight introduces a new inherited property, UseLayoutRounding, which is true by default. The difference between the two properties is subtle (UseLayoutRounding affects layout sizes, while SnapsToDevicePixels doesn’t), but the effect is the same: both keep single-pixel borders sharp. Note that UseLayoutRounding was recently added to version 4 of WPF, but it’s false by default to maintain backward compatibility.

{x:Type …} syntax is not supported in Silverlight

The following syntax is supported only in WPF:

    <Style TargetType="{x:Type local:PieChartLabel}">

The syntax below is equivalent and is supported both in WPF and Silverlight:

    <Style TargetType="local:PieChartLabel">

Binding to AncestorType is not supported in Silverlight

Below you can see the control and data templates for the PieChartLabel in WPF:

    <Style TargetType="{x:Type local:PieChartLabel}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:PieChartLabel}">
                    <Canvas Name="Canvas_PART">
                        <ContentPresenter Name="Content_PART"/>
                    </Canvas>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <StackPanel Background="LightGray">
                        <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:PieChartLabel}}, Path=FormattedRatio}" VerticalAlignment="Center" Margin="5,0,5,0" />
                    </StackPanel>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Within the DataTemplate, I use an AncestorType binding to display the FormattedRatio property defined on PieChartLabel. I couldn’t use a TemplateBinding because that would refer to properties of the ContentPresenter.

Silverlight doesn’t support AncestorType bindings. One possible solution to work around this issue could have been to use a binding to Self with a Converter, and within the converter walk up the visual tree until the PieChartLabel is found. However, the binding is evaluated before the PieChartLabel is added to the tree, so this approach doesn’t really help.

As a workaround, I merged the data template into the control template so that I could use a TemplateBinding to access the FormattedRatio.

    <Style TargetType="local:PieChartLabel">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:PieChartLabel">
                    <Canvas Name="Canvas_PART">
                        <Polyline Name="Polyline_PART" StrokeThickness="{TemplateBinding LineStrokeThickness}" Stroke="{TemplateBinding LineStroke}" StrokeLineJoin="Round" />
                        <StackPanel Background="LightGray" Name="Content_PART">
                            <TextBlock Text="{TemplateBinding FormattedRatio}" VerticalAlignment="Center" Margin="5,0,5,0" />
                        </StackPanel>
                    </Canvas>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

WPF’s Polyline rendering bug is not present in Silverlight

To end on a high note, I was able to remove the workaround for the Polyline rendering bug I struggled with in the WPF version of this code. As I explained in my previous post, if I placed a Polyline in the label’s template and modified its points whenever the label position changed, WPF would occasionally render the Polyline incorrectly. To work around the issue, I had to create a new Polyline every time the label was repositioned, which is not as efficient.

I was glad to see that this bug is not present in Silverlight. So I added the Polyline to the PieChartLabel’s template and simply changed its points in code. You can see the XAML containing the Polyline in the Silverlight Style in the previous section. The code that adds the points to the Polyline instead of creating a new Polyline can be found in the PositionConnected() method of PieChartLabel. This is a straightforward change, so I won’t show it here.

Click on the following image to see the Silverlight project running on a separate page.

Download the Silverlight project (built with Silverlight 3).

16 Comments
  1. bp

    Awesome stuff, Bea – was just wrestling with the exact same problem myself. Why did the toolkit team make the chart labels so hard to style in the first place!?

    • Bea

      Hi bp,

      Hmm… not sure I understand… the labels are not in the toolkit – that is the point of this post. :)
      Any charting feedback that you (and others) leave on my blog, I simply send David email with the details. You can also leave feedback to David directly on his blog.

      Thanks for your comment!
      Bea

  2. Taoffi

    Hi Bea…
    Thank you for this great article. And for taking the time for such detailed ‘real world’ examples.
    I just note: there are so many things that DO NOT exist in Slverlight :)

    • Bea

      Hi Taoffi – I’m glad you liked the article.
      Yeah, there are many WPF features that don’t exist in Silverlight. For the most part, there are simple workarounds like the ones I show here, but at times you have to make bigger compromises (you may have to reimplement a bigger feature or simply live without it).

      Bea

  3. Aitor

    Hi Bea! Thank you for the control! I have used it and is very useful, but I have a problem with it. If you put the labelledpiechart in a tabitem and you select this tabitem, you can see the labels, but if you navigate to another tab and then return to previous, the labels dissapear! How can I solve this problem?

    Thank you so much!

    Hola Bea! Gracias por el control. Lo he usado y es muy útil, pero me ha surgido un problema. Si pones un labelledpiechart en un tabitem y seleccionas ese tabitem, se pueden ver las etiquetas, pero si cambias de tab y luego vuelves al que contiene el piechart, las etiquetas han desaparecido! Como se podría resolver ese problema?

    Muchas gracias!

    • Bea

      Hi Aitor,

      Thanks for reporting this bug. The problem is that the labels were added when the IsLabeled property is set only. IsLabeled is set when the PieChart loads the first time, but not when changing tabs. The solution is to add the labels also in the “Loaded” event, which is fired when changing tabs. To do that, you can add the following code at the end of the “AddLabel” method in PieLabelBehavior.cs:

      pieDataPoint.Loaded += delegate
      {
      if (label.Parent == null)
      {
      labelArea.Children.Add(label);
      }
      };

      Or you can download the project again (I updated the zip linked to at the end of the blog).

      This issue could be reproduced both in the WPF and Silverlight versions of the PieChartLabel. I fixed it in the WPF zip I link to also, of course.

      Thanks!
      Bea

      • Aitor

        Thank you so much! The control works pretty fine! It’s exactly what I need!

  4. Aitor

    Hello again! Today I say the new release of SL Toolkit, but when I upgraded this, the labelledpiechart stopped working. The error is in the following line:

    PieChartLabelStyle=”{StaticResource pieChartLabelStyle}” LabelDisplayMode=”Auto” ItemsSource=”{Binding}”/>

    In my project the error is in the property ItemsSource, but when I tried to regenerate your project, the error was in the property LabelDisplayMode.

    Can you review the project?
    Thanks!

    • Bea

      Hi Aitor,

      Yes, I do need to review the project. I am actually planning to make more changes than simply getting it to work again. As you can see in David’s blog, the Charting classes are now unsealed, so now I can derive from those classes (instead of using a UserControl and a behavior, which is quite cumbersome).

      In the meantime, if you need to get it working soon, you can go through the list of breaking changes listed in David’s blog. My guess is that the only one that will affect the labeled chart is the renaming of StylePalette to Palette.

      Bea

      • Aitor

        Certainly, the fact that the classes are now unsealed is a very good feature. I’ll be waiting your upgrade. For now I will remain at July Release.

        Thanks a lot!

        • Saju

          Hi Bea,

          Please let us know if you have the updated version of LabeledPieChart for Nov09 release of DataVisualization Toolkit.

          Regards,
          -Saju

          • Bea

            Hi Saju,

            Good timing for that question. I have the code written and will be showing it on my next blog post, in a few days. So stay tuned.

            Bea

  5. agaace

    Re: Template.FindName doesn’t exist in Silverlight

    I don’t know WPF, so I’m not sure if it’s exactly the same, but in SL there’s a GetTemplateChild method.

    // chart inherits from Control
    PieChartLabelArea labelArea = chart.GetTemplateChild(“LabelArea_PART”) as PieChartLabelArea;

    Thanks for a nice article about the differences and workarounds.

    I have another issue in SL that apparently doesn’t exist in WPF: I’m trying to use TemplateBinding with a gradient stop (so bind to just one particulat Color that’s part of the gradient stops collection, instead of entire gradient). It simply does nothing (no error, simply doesn’t work). I was wondering, maybe you’d have a workaround for this?

    • Bea

      Hi,

      Regarding Template.FindName: GetTemplateChild exists both in WPF and Silverlight, but it’s protected in both technologies, so you can only use it within the code for the derived control. The code I show here is not in the definition for the chart, it’s in some other class where chart is used. FindName in WPF searches for an element within a particular namescope. myControl.Template.FindName searches for an element within the template for the control specified.

      In Silverlight 3, it’s not possible to data bind non-FrameworkElements, and GradientStop is not a FrameworkElement. So that scenario is not supported in Silverlight, whether you use Binding or TemplateBinding. It’s unfortunate that no error message is displayed.
      In WPF, you can use Binding on non-FrameworkElements, but not TemplateBinding. You should get an error message if you try to use TemplateBinding in this scenario.

      Bea

  6. Anil

    Hi,

    There is problem in implementing the labeling suppose i have three values 95% , 2.5% and 2.5% when labele is generated, for last two value they are overlapped.So there is no clear visibility. I am using LabelDisplayMode=”Connected”

    Is there is any solution for this is yes please suggest.

    Thanks
    Anil Kumar

    • Bea

      Hi Anil,

      I am using a simple algorithm to place the labels – they simply get placed in the middle point of that arc. So, no, I don’t have a solution implemented that improves the scenario you describe.

      It wouldn’t be that hard to extend the code in this blog post to do that though. If you decide to do so, please post the link to it here so that others can benefit from your work.

      Thanks!
      Bea

Comments are closed.