Nov 01, 2005

How to make a data bound bar graph

A very simple bar graph can be created by combining Avalon’s styling and templating features with a data bound ItemsControl. An ItemsControl is simply a control that displays a list of items. Those items can be anything you want: people, numbers, controls, and so on. If you template each item of an ItemsControl to be a rectangle whose height is bound to numerical data, you have a data bound bar graph.

The data source for this sample is a class with a property called ValueCollection of type ObservableCollection. ObservableCollection implements INotifyCollectionChanged, which means that if items are added/removed/replaced from that collection, the binding engine will be notified of that and the UI will be updated.

This is the markup for the ItemsControl:

    <ItemsControl ItemsSource="{Binding Source={StaticResource source}, Path=ValueCollection}" ItemTemplate="{StaticResource template}" Height="130">
        <ItemsControl.ItemsPanel>
            <StackPanel Orientation="Horizontal" />
        </ItemsControl.ItemsPanel>
    </ItemsControl>

The default Panel for ItemsControl has vertical orientation, but we want the items to be displayed horizontally - each bar should be to the right of the previous one. To change the panel, we set the ItemsControl’s ItemsPanel property (see my previous blog post for more details on changing the Panel of an ItemsControl).

The template for each item has a rectangle with height bound to the corresponding integer in the data source and a second rectangle with no fill to add some blank space between the bars:

    <DataTemplate x:Key="template">
        <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom">
            <Rectangle Height="{Binding}" Width="20" Fill="Red" />
            <Rectangle Width="5" />
        </StackPanel>
    </DataTemplate>

This is a very simple bar graph but it will hopefully give you ideas and serve as the base for more elaborate representations of data. This is the result:

Here you can find the VS project with this sample code. This works with September CTP WPF bits.

13 Comments
  1. Anonymous

    how do I generate a bar for each series if my datasource is xml and it has 3 attributes corresponding to 3 series

    • Bea

      Can you please elaborate a little more? What type is the data in those elements? (int?) If you have series A, B and C, and have several elements with attribute A, how do you want column A to be represented? (add all ints?)

      • Anonymous

        yes the data is say int or double

        let say if I have xml , I want to have a chart with 3 series showing countries ‘a’,'b’..

        each country will have 3 bars one for male, one for female one for children etc.

        I am trying to do a custom control for charts

        so the number of series or the names will have to be evaluated from the properties of the control

        Thanks

        • Bea

          I made a sample for you that has 3 countries, each with 3 series (Children, Male, Female), and displays each of those series as a separate bar in the graph. Hopefully this is what you were looking for.

        • Anonymous

          hi,
          Thanks for your time, last few days I was playing with it somehow got it to work, not elegant as your sample. I am having difficult in figuring out how to display only the series that are in the xml

          in the sample xml we have male,female,children. lets say my xml also has seriescollection node which will have only the series I want to see, I cannot hardcode the values, how would I send what I have to you

        • Anonymous

          here is the code I am playing with
          <Window x:Class=”WindowsApplication1.Window5″
          xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
          xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
          Title=”WindowsApplication1″ Height=”600″ Width=”600″
          >
          <Window.Resources>
          <XmlDataProvider x:Key=”master” XPath=”/root” >
          <x:XData >
          <root xmlns=”">
          <series>
          <s field=”orders”></s>
          <s field=”quantity”></s>
          </series>
          <v customerid=”ALFKI” orders=”16″ quantity=”20″ />
          <v customerid=”ANATR” orders=”40″ quantity=”40″ />
          <v customerid=”ANTON” orders=”70″ quantity=”60″ />
          <v customerid=”AROUT” orders=”13″ quantity=”10″ />
          <v customerid=”BERGS” orders=”18″ quantity=”30″ />
          <v customerid=”BLAUS” orders=”17″ quantity=”90″ />
          </root>
          </x:XData>
          </XmlDataProvider>
          <DataTemplate x:Key=”dt1″>
          <Border Margin=”2″ BorderBrush=”Black” Height=”100″ VerticalAlignment=”Bottom” >
          <ListBox Style=”{StaticResource q2}” ItemsSource=”{Binding Source={StaticResource master}, XPath=series/s}” ItemTemplate=”{StaticResource dt2}”>
          </ListBox>

          </Border>

          </DataTemplate>
          <DataTemplate x:Key=”dt2″>
          <Rectangle Fill=”green” Width=”5″ x:Name=”rect”
          DataContext=”{Binding Path=Content,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ListBoxItem},AncestorLevel=2}}” >
          <Rectangle.Height>
          <Binding XPath=”@orders” >
          </Binding>
          </Rectangle.Height>
          </Rectangle>
          </DataTemplate>
          <Style x:Key=”q2″ TargetType=”{x:Type ListBox}”>
          <Setter Property=”Template”>
          <Setter.Value>
          <ControlTemplate TargetType=”{x:Type ListBox}”>
          <StackPanel VerticalAlignment=”Bottom” Orientation=”Horizontal” IsItemsHost=”True” Background=”Aqua”>

          </StackPanel>
          </ControlTemplate>
          </Setter.Value>
          </Setter>
          </Style>
          <Style x:Key=”q1″ TargetType=”{x:Type ListBox}”>
          <Setter Property=”Template”>
          <Setter.Value>
          <ControlTemplate TargetType=”{x:Type ListBox}”>
          <StackPanel VerticalAlignment=”Bottom” Orientation=”Horizontal” Width=”600″ IsItemsHost=”True” Height=”100″ Background=”Azure”>

          </StackPanel>
          </ControlTemplate>
          </Setter.Value>
          </Setter>
          </Style>
          </Window.Resources>
          <ListBox Style=”{StaticResource q1}” ItemsSource=”{Binding Source={StaticResource master}, XPath=v}” ItemTemplate=”{StaticResource dt1}”>
          </ListBox>

          </Window>

        • Bea

          I see the problem.

          Your xml data source implementation would work great if we could bind to the XPath property of binding: you would get the names of the attributes in the series section and bind the XPath of DataTemplate dt2 to that. Unfortunately you can not bind to any property of the Binding object - Binding is not even a DO, so its properties can not be DPs.

          I gave your sample a little twist to get it to work. I changed the schema of the XML data to be the following:

          <XmlDataProvider x:Key=”master” XPath=”/root” >
          <x:XData>
          <root xmlns=”">
          <v orders=”16″ quantity=”20>ALFKI</v>
          <v orders=”40″ quantity=”40″>ANATR</v>
          <v orders=”70″ quantity=”60″>ANTON</v>
          <v orders=”13″ quantity=”10″>AROUT</v>
          <v orders=”18″ quantity=”30″>BERGS</v>
          <v orders=”17″ quantity=”90″>BLAUS</v>
          </root>
          </x:XData>
          </XmlDataProvider>

          The idea here is that all attributes of “v” become a bar in the graph custom control. You can have any number of them, and call them whatever you want, and in fact you can have different attributes from one item to the next - the control would be able to generate the right columns for you. To make this work, I added the customer ID as content of “v”. I don’t know if it is a problem for your application, or if there are any other attributes that should be ignored when creating the columns. If so let me know and I’ll think of a solution for that.

          Anyway, assuming you are ok with the changes in the xml source, your DataTemplates would be really simple. The top level DataTemplate would enumerate through all attributes. The bottom level one simply needs to bind the Rectangle’s height to the InnerText of the XmlAttribute, which is really easy to do because the DataContext of this template is the XmlAttribute we’re interested in:

          <DataTemplate x:Key=”dt1″>
          <Border Margin=”2″ BorderBrush=”Black” Height=”100″ VerticalAlignment=”Bottom” >
          <ListBox Style=”{StaticResource q2}” ItemsSource=”{Binding Path=Attributes}” ItemContainerStyle=”{StaticResource lbiStyle}” ItemTemplate=”{StaticResource dt2}”>
          </ListBox>
          </Border>
          </DataTemplate>

          <DataTemplate x:Key=”dt2″>
          <Rectangle Fill=”green” Width=”5″ x:Name=”rect” VerticalAlignment=”Bottom” Height=”{Binding Path=InnerText}”/>
          </DataTemplate>

          Let me know if this would work for your scenario.

        • Anonymous

          Thanks a lot for your time and sample. let me play around with this and see if I can somehow extend this to show only the series I want

          Thanks

        • Anonymous

          Hi,
          I am still working with this. I am trying to adjust the height of the bars based on the height set for the control, and the max value of the data.
          havind hard time trying to pass this calculated value in codebehind as converter parameter.
          I even declared the calculated value as property
          <Rectangle.Height>
          <Binding XPath=”@orders” Converter=”{StaticResource heightConverter}” ConverterParameter=”{???}” >
          </Binding>

          </Rectangle.Height>

          Thanks

        • Bea

          Hi,

          Having that calculated value in a public property would be helpful if you could bind the ValueConverter to that property (you would do this by giving your Window a Name and setting the ElementName of your binding to that Name). This however is not possible because you can not bind any of the properties of Binding. Binding is not a DependencyObject, which means none of its properties are DependencyProperties, and the target of a Binding has to be a DependencyProperty. You can’t use a DynamicResource either because it also requires a DP as the target.

          You also can’t set the ConverterParameter in code when you calculate your new value because we made a breaking change recently that makes bindings immutable, for perf reasons.

          One possible workaround for this would be to redo the binding in code, when you calculate the new value for the height of the bars, and set the ConverterParameter to that value, manually.

          I know this is an ugly workaround :( Allowing Bindings, DynamicResources or changing properties in the Binding would make the Binding object a lot more heavyweight than it is right now, and lead to perf problems. It was more important for us to make Bindings cheap and simple than to make them heavy and with more functionality. I hope our customers will give us feedback in that direction. And if we get feedback from people to add specific extra functionality, we can always do that in V2.

          Bea

        • Anonymous

          thanks

  2. Anonymous

    Excellent!

    Would it be possible for you to provide an article explaining/demonstrating how to create a Line Graph control?

    • Bea

      Hello,

      I wrote a three series blog article about binding Polygon’s Points to a data source. You can implement a line graph with a Polygon, and use the techniques I mentioned to bind the points to your source.

      Let me know if this helps.

      Bea

Comments are closed.