Building a heatmap with XAML Grids

Marina Stoyanova / Thursday, February 6, 2014

 The grid is one of the most useful controls for visualizing structured data. It gives you the opportunity to display and style your data according to your needs. There are numerous features that can help you customize it and provide best possible user experience for your end-users while they are manipulating and extracting the information they need. If you display that information with a heatmap the users will be able to find patterns and track the data even faster. In this blog we will see how to build a heatmap with Infragistics WPF xamGrid and xamDataGrid.

 

Basics

If you have downloaded and installed the Infragistics package you will find a list of the controls from the 13.2 release of WPF in the toolbox. Drag the xamGrid or the xamDataGrid whatever you are going to use to the Designer View and you will see that the needed references will be added automatically. 

xamGrid:

xamGrid required references

xamDataGrid:

xamDataGrid required references

In the code behind we are going to create a sample data , that we are going to use for our heatmap. This data represents how busy are the employees in one organization each day of the week and their business is represented by percentage – 0% means the employee has no work to do and 100% means he is occupied the whole day. For the color calculations you will need the minimum and maximum values of your data one way or another, for this demo we'll expose them from the data collection but you can provide those any way you see fit.

  1. public class Appointment
  2. {
  3.     public int ID { get; set; }
  4.     public string Name { get; set; }
  5.     public int Mon { get; set; }
  6.     public int Tue { get; set; }
  7.     public int Wed { get; set; }
  8.     public int Thu { get; set; }
  9.     public int Fri { get; set; }
  10.  
  11. }
  12. public class DataCollection : List<Appointment>
  13. {
  14.     public int Min { get; set; }
  15.     public int Max { get; set; }
  16.     public DataCollection()
  17.     {
  18.         this.Add(new Appointment { ID = 1, Name = "Yoshi", Mon = 10, Tue = 96, Wed = 100, Thu = 99, Fri = 86 });
  19.         this.Add(new Appointment { ID = 2, Name = "Adria", Mon = 67, Tue = 18, Wed = 46, Thu = 39, Fri = 70 });
  20.         this.Add(new Appointment { ID = 3, Name = "Lionel", Mon = 78, Tue = 77, Wed = 51, Thu = 2, Fri = 0 });
  21.         this.Add(new Appointment { ID = 4, Name = "Indira", Mon = 1, Tue = 58, Wed = 38, Thu = 48, Fri = 30 });
  22.         this.Add(new Appointment { ID = 5, Name = "Shaeleigh", Mon = 83, Tue = 20, Wed = 74, Thu = 19, Fri = 81 });
  23.         this.Add(new Appointment { ID = 6, Name = "Octavius", Mon = 2, Tue = 25, Wed = 48, Thu = 69, Fri = 31 });
  24.         this.Add(new Appointment { ID = 7, Name = "Zephania", Mon = 46, Tue = 30, Wed = 44, Thu = 85, Fri = 73 });
  25.         this.Add(new Appointment { ID = 8, Name = "Dorian", Mon = 100, Tue = 28, Wed = 25, Thu = 60, Fri = 25 });
  26.         this.Add(new Appointment { ID = 9, Name = "Lysandra", Mon = 20, Tue = 33, Wed = 65, Thu = 91, Fri = 28 });
  27.         this.Add(new Appointment { ID = 10, Name = "Venus", Mon = 64, Tue = 81, Wed = 37, Thu = 72, Fri = 92 });
  28.         this.Add(new Appointment { ID = 11, Name = "Cairo", Mon = 82, Tue = 92, Wed = 40, Thu = 7, Fri = 46 });
  29.         this.Add(new Appointment { ID = 12, Name = "Iliana", Mon = 52, Tue = 78, Wed = 90, Thu = 16, Fri = 92 });
  30.         this.Add(new Appointment { ID = 13, Name = "Sarah", Mon = 33, Tue = 52, Wed = 27, Thu = 28, Fri = 59 });
  31.         this.Add(new Appointment { ID = 14, Name = "Valentine", Mon = 30, Tue = 26, Wed = 1, Thu = 52, Fri = 4 });
  32.         var list = this.Select(c => new List<int>() { c.Mon, c.Tue, c.Wed, c.Thu, c.Fri }).SelectMany(f => f);
  33.         Max = list.Max();
  34.         Min = list.Min();
  35.     }
  36. }

Now lets connect that information to the grid. As we dragged and dropped the xamGrid or the xamDataGrid respectively into the designer view we will see the control’s tag containing  the  grid itself. Now we can add some properties such as width, height and etc. How do we bind the data to the grid? Well it’s pretty easy.  We should add the  data collection that we have created as a resource and then in the control’s tag we need to use the data source option of the xamDataGrid and item Source option of the xamGrid and bind it to that static resource.

XAML:

  1. <Grid.Resources>
  2.     <local:DataCollection x:Key="source" />
  3. Grid.Resources>

xamDataGrid:

  1. <igWPF:XamDataGrid Name="xamdataGrid" DataSource="{Binding Source={StaticResource source}}">

xamGrid:

  1. <ig:XamGrid Name="Grid" Width="500" Height="320" RowHover="None" ColumnWidth="*" ItemsSource="{Binding Source={StaticResource source}}" />

Defining the different columns in the xamGrid and xamDataGrid looks as follows:

xamGrid:

  1. <ig:XamGrid.Columns>
  2.     <ig:TextColumn Key="ID" />
  3.     <ig:TextColumn Key="Name" />
  4.     <ig:TextColumn Key="Mon" />
  5.     <ig:TextColumn Key="Tue" />
  6.     <ig:TextColumn Key="Wed" />
  7.     <ig:TextColumn Key="Thu" />
  8.     <ig:TextColumn Key="Fri" />
  9. ig:XamGrid.Columns>

xamDataGrid:

  1. <igWPF:XamDataGrid.FieldLayouts>
  2.     <igWPF:FieldLayout>
  3.         <igWPF:Field Name="Name" Label="Name" />
  4.         <igWPF:Field Name="Mon" Label="Mon" />
  5.         <igWPF:Field Name="Tue" Label="Tue" />
  6.         <igWPF:Field Name="Wed" Label="Wed" />
  7.         <igWPF:Field Name="Thu" Label="Thu" />
  8.         <igWPF:Field Name="Fri" Label="Fri" />
  9.     igWPF:FieldLayout>
  10. igWPF:XamDataGrid.FieldLayouts>

xamGrid example image:

xamGrid with custom data

Now we have basic grids that display our data. The next step is to take the value from the numeric cells and based on that value to generate an appropriate color for every cell.

xamDataGrid with heatmap

Heatmaps visualize a table of numbers with corresponding colors. In our samples the values will be represented with both values and colors. Respectively to the data a green color meets the 0% and red meets 100%. In the xamDataGrid for every column that we want to add to the heatmap we will use the field settings and we will define a style which will take the current value of the cell and it will call a color converter function that will generate the relevant color.

Color Converter:

  1. public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  2. {
  3.     var collection = parameter as DataCollection;
  4.     double max = collection.Max;
  5.     double min = collection.Min;
  6.  
  7.     int val = (Int32)(value);
  8.     double perRed = 0;
  9.     double perGreen = 0;
  10.     string color = "";
  11.     double ratio = 0;
  12.     
  13.     ratio = 1 / (max-min) ;
  14.     var finalValue = (val - min) * ratio;
  15.  
  16.     if (finalValue == 0.5)
  17.     {
  18.         perRed = 1;
  19.         perGreen = 1;
  20.     }
  21.     else if (finalValue > 0.5)
  22.     {
  23.         perRed = 1;
  24.         perGreen = (1 - finalValue) * 2;
  25.     }
  26.     else
  27.     {
  28.         perGreen = 1;
  29.         perRed = finalValue * 2;
  30.     }
  31.  
  32.     var red = (int)Math.Round(255.0 * perRed);
  33.     var green = (int)Math.Round(255.0 * perGreen);
  34.     var gString = green.ToString("X");
  35.     var rString = red.ToString("X");
  36.  
  37.     if (gString.Length == 1)
  38.     {
  39.         gString = '0' + gString;
  40.     }
  41.  
  42.     if (rString.Length == 1)
  43.     {
  44.         rString = rString + '0';
  45.     }
  46.  
  47.     color = "#" + rString + gString + "00";
  48.  
  49.     return color;
  50. }

XAML:

  1. <igWPF:XamDataGrid.FieldLayouts>
  2.     <igWPF:FieldLayout>
  3.         <igWPF:Field Name="Name" Label="Name" />
  4.         <igWPF:Field Name="Mon" Label="Mon" >
  5.             <igWPF:Field.Settings>
  6.                 <igWPF:FieldSettings CellWidth="50" LabelWidth="50">
  7.                     <igWPF:FieldSettings.CellValuePresenterStyle>
  8.                         <Style TargetType="{x:Type igWPF:CellValuePresenter}">
  9.                             <Setter Property="Background" Value="{Binding Path=Cells[Mon].Value, Converter={StaticResource ColorConv}, ConverterParameter={StaticResource source}}" />
  10.                         Style>
  11.                     igWPF:FieldSettings.CellValuePresenterStyle>
  12.                 igWPF:FieldSettings>
  13.             igWPF:Field.Settings>
  14.         igWPF:Field>
  15.         <igWPF:Field Name="Tue" Label="Tue" >
  16.             <igWPF:Field.Settings>
  17.                 <igWPF:FieldSettings CellWidth="50" LabelWidth="50">
  18.                     <igWPF:FieldSettings.CellValuePresenterStyle>
  19.                         <Style TargetType="{x:Type igWPF:CellValuePresenter}">
  20.                             <Setter Property="Background" Value="{Binding Path=Cells[Tue].Value, Converter={StaticResource ColorConv},ConverterParameter={StaticResource source}}" />
  21.                         Style>
  22.                     igWPF:FieldSettings.CellValuePresenterStyle>
  23.                 igWPF:FieldSettings>
  24.             igWPF:Field.Settings>
  25.         igWPF:Field>
  26.         <igWPF:Field Name="Wed" Label="Wed">
  27.             <igWPF:Field.Settings>
  28.                 <igWPF:FieldSettings CellWidth="50" LabelWidth="50">
  29.                     <igWPF:FieldSettings.CellValuePresenterStyle>
  30.                         <Style TargetType="{x:Type igWPF:CellValuePresenter}">
  31.                             <Setter Property="Background" Value="{Binding Path=Cells[Wed].Value, Converter={StaticResource ColorConv},ConverterParameter={StaticResource source}}" />
  32.                         Style>
  33.                     igWPF:FieldSettings.CellValuePresenterStyle>
  34.                 igWPF:FieldSettings>
  35.             igWPF:Field.Settings>
  36.         igWPF:Field>
  37.         <igWPF:Field Name="Thu" Label="Thu">
  38.             <igWPF:Field.Settings>
  39.                 <igWPF:FieldSettings CellWidth="50" LabelWidth="50">
  40.                     <igWPF:FieldSettings.CellValuePresenterStyle>
  41.                         <Style TargetType="{x:Type igWPF:CellValuePresenter}">
  42.                             <Setter Property="Background" Value="{Binding Path=Cells[Thu].Value, Converter={StaticResource ColorConv},ConverterParameter={StaticResource source}}" />
  43.                         Style>
  44.                     igWPF:FieldSettings.CellValuePresenterStyle>
  45.                 igWPF:FieldSettings>
  46.             igWPF:Field.Settings>
  47.         igWPF:Field>
  48.         <igWPF:Field Name="Fri" Label="Fri">
  49.             <igWPF:Field.Settings>
  50.                 <igWPF:FieldSettings CellWidth="50" LabelWidth="50">
  51.                     <igWPF:FieldSettings.CellValuePresenterStyle>
  52.                         <Style TargetType="{x:Type igWPF:CellValuePresenter}">
  53.                             <Setter Property="Background" Value="{Binding Path=Cells[Fri].Value, Converter={StaticResource ColorConv},ConverterParameter={StaticResource source}}" />
  54.                         Style>
  55.                     igWPF:FieldSettings.CellValuePresenterStyle>
  56.                 igWPF:FieldSettings>
  57.             igWPF:Field.Settings>
  58.         igWPF:Field>
  59.     igWPF:FieldLayout>
  60. igWPF:XamDataGrid.FieldLayouts>

To have the same color converter produce a color scale from blue to purple change the color variable at line 47 from the snippet and give it a maximum value for the blue(last) component:

  1. color = "#" + rString + gString + "ff";

 

xamGrid with Heatmap

We will use the same color converter as the one explained above, but we will show two ways of connecting it to that converter. One of the ways is setting a different style for every column. In this case we will send the current value of every column and receive back the relevant color. We will define the different styles in the static resources and then we will use the cellStyle property to bind the separate columns to those styles. It is good to disable the IsAlternateRowsEnabledateColumns property by assigning it a false value because otherwise it will override the coloring of the cells.

Resources:

  1. <Grid.Resources>
  2.     <local:DataCollection x:Key="source"/>
  3.     <local:ColorConverter x:Key="ColorConv"/>
  4.     <Style x:Key="MonStyle" TargetType="ig:CellControl">
  5.         <Setter Property="Background" Value="{Binding Path=Mon, Converter={StaticResource ColorConv}}">Setter>
  6.     Style>
  7.     <Style x:Key="TueStyle" TargetType="ig:CellControl">
  8.         <Setter Property="Background" Value="{Binding Path=Tue, Converter={StaticResource ColorConv}}">Setter>
  9.     Style>
  10.     <Style x:Key="WedStyle" TargetType="ig:CellControl">
  11.         <Setter Property="Background" Value="{Binding Path=Wed, Converter={StaticResource ColorConv}}">Setter>
  12.     Style>
  13.     <Style x:Key="ThuStyle" TargetType="ig:CellControl">
  14.         <Setter Property="Background" Value="{Binding Path=Thu, Converter={StaticResource ColorConv}}">Setter>
  15.     Style>
  16.     <Style x:Key="FriStyle" TargetType="ig:CellControl">
  17.         <Setter Property="Background" Value="{Binding Path=Fri, Converter={StaticResource ColorConv}}">Setter>
  18.     Style>
  19. Grid.Resources>

Styling the columns:

  1. <ig:XamGrid.Columns>
  2.     <ig:TextColumn Key="ID" />
  3.     <ig:TextColumn Key="Name" />
  4.     <ig:TextColumn Key="Mon" CellStyle="{StaticResource ResourceKey=MonStyle}" />
  5.     <ig:TextColumn Key="Tue" CellStyle="{StaticResource ResourceKey=TueStyle}"/>
  6.     <ig:TextColumn Key="Wed" CellStyle="{StaticResource ResourceKey=WedStyle}"/>
  7.     <ig:TextColumn Key="Thu" CellStyle="{StaticResource ResourceKey=ThuStyle}"/>
  8.                      <ig:TextColumn Key="Fri" CellStyle="{StaticResource ResourceKey=FriStyle}"/>
  9. ig:XamGrid.Columns>

The second way of achieving the same heatmap effect is by using the cellControlAttached event. In this case instead of of creating individual style for every column and connecting it to the color converter we will have only one style and in the event we will handle the coloring of the different cells.

XAML:

  1. <Style x:Key="Style" TargetType="ig:CellControl">
  2.     <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource ColorConv}}">Setter>
  3. Style>

Event:

  1. private void Grid_CellControlAttached(object sender, Infragistics.Controls.Grids.CellControlAttachedEventArgs e)
  2. {
  3.     XamGrid grid = sender as XamGrid;
  4.     int columnIndex = grid.Columns.DataColumns.IndexOf(e.Cell.Column);
  5.     if (grid != null && columnIndex > 1 )
  6.     {
  7.         e.Cell.Style = this.root.Resources["Style"] as Style;
  8.     }            
  9. }

 

If you follow those steps you can create your own heatmap grid in no time.

Summary

Using a heatmap is a useful way of finding patterns thought the data. It helps the end user to find faster highs and lows and track the information he needs. Although usually in heatmap the numbers are substituted by corresponding colors with the xamDataGrid and xamGrid we demonstrate how to keep them both and thus enrich the user experience.

 

You can follow us on Twitter @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!