London Underground Dashboard

In this post, we will demonstrate how to develop an Enterprise Dashboard Application using Silverlight.

Scenario

We chose to develop a dashboard for London Underground System. This dashboard will be similar to several tools available at http://www.tfl.gov.uk/tfl/traveltools/default.aspx and will provide following information.

Goal

All these information will be available on single page and user should be easily able to navigate to various tube lines.

Requirement

We will use following open source libraries:

These libraries are available in following attachment.

Creating Project

First let us create a new Silverlight Web Project in eclipse and call it as "LondonTubeDashboard". If you are not familiar to creating Silverlight projects in eclipse, please refer to Hello World sample.

Now let us add references to third party libraries like Silverlight Toolkit and Zip library using following steps,

Adding all data files to Project

All data files are available in the following attachment.

The development of Silverlight London Underground Dashboard application consists of four steps:

Deciding UI Layout

As mention above, we have to display following information

We propose following UI for displaying all this information on a single page.

Designing UI in XAML

Start by adding following code in the Page.xaml file. This contains references to charting and data visualization controls like line-series and pie-series. A canvas is set with gradient background from DarkGray to LightGray.

<UserControl  
    xmlns:charting="clr-namespace:Microsoft.Windows.Controls.DataVisualization.
        Charting;assembly=Microsoft.Windows.Controls.DataVisualization"
    xmlns:datavis="clr-namespace:Microsoft.Windows.Controls.DataVisualization;
        assembly=Microsoft.Windows.Controls.DataVisualization" 
    x:Class="LondonTubeDashboard.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Loaded="Page_Loaded" Height="493" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d" d:DesignWidth="1024">  
    <Canvas x:Name="LayoutRoot" Width="1024" Height="500">
        <!-- Set gradient background from DarkGray to LightGray -->
        <Canvas.Background>
            <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                <GradientStop Color="DarkGray" Offset="0"/>
                <GradientStop Color="LightGray" Offset="1"/>
            </LinearGradientBrush>
        </Canvas.Background>
    </Canvas>
</UserControl>

Now we will add application title "London Underground Dashboard" inside canvas. Let us have a nice border with rounded corners and gradient yellow color background. This can be done by putting TextBlock control inside Border control. Define CornerRadius attribute to have rounded corners. Also define gradient background by defining Border.Background properties inside Border control. The xaml code for this as follows.

<!-- Application Title -->
<Border Height="60.5" Width="967" Canvas.Left="29.5" Canvas.Top="8" 
        CornerRadius="5" BorderBrush="Black">
    <Border.Background>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="Yellow"/>
            <GradientStop Color="Gray" Offset="1"/>
        </LinearGradientBrush>
    </Border.Background>
    <TextBlock Text="London Underground Dashboard" TextWrapping="Wrap" 
    Canvas.Top="12.182" Canvas.Left="432" Height="62" Width="605.652" 
    FontSize="36" Foreground="Black"/>
</Border>

Now let us define a list box to list all available tube lines. As listbox itself as border properties, do not put thus listbox inside separate border for having rounded corners. Look at <ListBox.Template> xaml code to know the details.

Now let us define layout of each item in the list. We need to display the tube line name in a rectangle with appropriate color background. Each tube line is recognized by specific color. We also need rounded corner for the rectangle. Then we need to display current status of the line. This all is achieved by defining <ListBox.ItemTemplate>. This template will have horizontal StackPanel containing two TextBlocks, one for line name and one for line status. Let us put rounded border around the name TextBlocks. Also set appropriate background color for this name TextBlock. Bind these two TextBlocks with appropriate properties called "Name", "Color" and "Status". These values will be set at run time. The entire xaml code for list box is as follows,

<!--  ListBox for displaying list of Tube Lines -->
<ListBox x:Name="TubeLinesListBox" Height="397.84" Width="280" 
        Canvas.Left="29.5" Canvas.Top="84.096"
        SelectionChanged="OnSelectionChanged" Background="Gray">
    <ListBox.Template>
        <ControlTemplate TargetType="ListBox">
            <Border BorderBrush="Black" BorderThickness="2" CornerRadius="5" 
                    Background="White">
                <ItemsPresenter/>
            </Border>
        </ControlTemplate>
    </ListBox.Template>

    <!-- Layout of each item in list -->
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Border Width="175" CornerRadius="5" BorderBrush="Black" 
                        Background="{Binding Path=Color}">
                    <TextBlock Text="{Binding Path=Name}" Margin="5" 
                        Width="175" Foreground="White"/>
                </Border>
                <TextBlock Text="{Binding Path=Status}" Margin="5"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Also add an empty event handler OnSelectionChanged() to the Page.xaml.cs file for listbox selection change event. We will implement this handler later.

// Event fired whenever a new Tube Line is selected in list box
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e){
    }

Now let us Image control for displaying the Map for the tube line. Again we need nice rounded border around the Map. Selected tube line name should be displayed above the map. All this is done by following xaml code. Add this inside the canvas, after listbox code.

<!--  Map Image -->
<Border Height="193.904" Width="358" Canvas.Left="320" Canvas.Top="84.032"
        CornerRadius="5" BorderBrush="Black" Background="Gray">
    <StackPanel Orientation="Vertical">
        <TextBlock x:Name="MapTitle" Text="XXX Line Map" 
            HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="14"/>
        <Image x:Name="TubeLineMapImage" Height="195" Width="358" 
            Canvas.Left="320" Canvas.Top="119.936"/>
    </StackPanel>
</Border>

Now let us put pie-chart control bellow the Image Map. We want to define following color codes,

Define these colors in <datavis:StylePalette> property. Bind IndependentValue to the Name property and DependentValue to the Value property of binding class. Put this pie-chart inside the rounded border. The entire xaml code for pie-chart is as follows,

<!-- Pie-Chart for Service History -->
<Border Height="191" Width="358" Canvas.Left="320" Canvas.Top="290.936"
        CornerRadius="5" BorderBrush="Black" Background="Gray" 
        RenderTransformOrigin="0.525,0.361">
    <charting:Chart x:Name="ServiceHistoryAsPieChart" Title="Service History"
            Height="181" Width="358" Canvas.Left="320" 
            Canvas.Top="295.936" BorderThickness="0">
        <charting:PieSeries 
                IndependentValueBinding="{Binding Path=Name}" 
                DependentValueBinding="{Binding Path=Value}">
            <charting:PieSeries.StylePalette>
                <datavis:StylePalette>
                    <Style TargetType="Control">
                        <Setter Property="Background" Value="Green"/>
                    </Style>
                    <Style TargetType="Control">
                        <Setter Property="Background" Value="Yellow"/>
                    </Style>
                    <Style TargetType="Control">
                        <Setter Property="Background" Value="Red"/>
                    </Style>
                </datavis:StylePalette>
            </charting:PieSeries.StylePalette>
        </charting:PieSeries>
    </charting:Chart>
</Border>

Now let us draw line series for displaying hourly traffic statistics. . We do not need to display legends. This is achieved by defining a template <charting:Chart.Template>. We can hide legend by setting Visibility="Collapsed" in datavis:Legend property. Please refer to the link http://silverlight.net/forums/p/56533/144605.aspx for details.

<!-- Line Series for hourly traffic statistics -->
<Border Height="397.904" Width="306" Canvas.Left="690.5" 
        Canvas.Top="84.032"
        CornerRadius="5" BorderBrush="Black" Background="Gray">
    <charting:Chart x:Name="TubeLineStatistics" Title="Hourly Traffic Statistics" 
            Margin="10" >
        <charting:Chart.Template>
            <ControlTemplate TargetType="charting:Chart">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>

                    <StackPanel>
                        <datavis:Title 
                            Content="{TemplateBinding Title}"
                            Style="{TemplateBinding TitleStyle}"/>
                    </StackPanel>

                    <Grid x:Name="ChartArea" Style="{TemplateBinding ChartAreaStyle}" 
                            Grid.Row="1">
                        <Grid x:Name="PlotArea" Style="{TemplateBinding PlotAreaStyle}">
                            <Grid x:Name="SeriesContainer" />
                        </Grid>
                    </Grid>

                    <StackPanel  Grid.Row="2" Visibility="Collapsed">
                        <datavis:Legend x:Name="Legend" Visibility="Collapsed"
                            Title="{TemplateBinding LegendTitle}"
                            Style="{TemplateBinding LegendStyle}"/>
                    </StackPanel>
                </Grid>
            </ControlTemplate>
        </charting:Chart.Template>
        
        <charting:LineSeries            
            Title="Hourly Traffic Statistics"
            DependentValueBinding="{Binding Path=Value}"             
            IndependentValueBinding="{Binding Path=Key}"/>
    </charting:Chart>
</Border>

Sample data for the Dashboard

Let us discuss various data files used in the dashboard.

TubeLines.xml

This file contains information about each tube line. Information contains line name, image file name containing map, Open XML document containing hourly traffic statistics, color of line and history of service status.

<?xml version="1.0" encoding="utf-8" ?>
<Lines>
    <Lines>
        <Name>Bakerloo Line</Name>
        <MapImage>Bakerloo_Line.png</MapImage>
        <StatsOpenXmlFile>Bakerloo_Line.docx</StatsOpenXmlFile>
        <Color>Brown</Color>
        <Status>Good</Status>
        <History>
            <Status name="Good" value="60"></Status>
            <Status name="PartlyClosed" value="20"></Status>
            <Status name="Closed" value="20"></Status>
        </History>
    </Line>

    <!-- Other line info -->

</Line>

   

<LineName>.png

TubeLines.xml contains reference to tube line map image file name. There is one image file for each tube line. The sample Jubilee_Line.png image is as follows.

<LineName>.docx

TubeLines.xml contains reference to Open XML document (.docx). There is one docx file for each tube line. This Open XML document contains following statistical data in tabular format. First column indicates hour of the day and second column indicates number of commuters (in thousands) travelling at that hour. Sample Bakerloo_Line.docx is shown bellow

Access Data in the Dashboard

Now let us write C# code behind to populate various Silverlight controls used in the dashboard. First add following namespaces to the Page.xaml.cs file.

using System.IO;
using  System.Xml;
using  System.Xml.Linq;
using  System.Reflection;
using  Microsoft.Windows.Controls.DataVisualization.Charting;
using  ICSharpCode.SharpZipLib.Zip;
using  System.Windows.Media.Imaging;
using  System.Windows.Resources;
   

Add following classes at the end of Page.xaml.cs file. These classes are used to hold data for bindings with ListBox and Pie-Chart controls.

// Data binding for listbox
public class LineDetail
{
        public string Name { get; set; }
        public string Color { get; set; }
        public string Status { get; set; }
}

// Data binding for pie-chart
public class ServiceInfo
{
        public string Name { get; set; }
        public int Value { get; set; }
}
   

Page_Loaded() Handler

Add following code to the Page_Loaded handler.

private void Page_Loaded(object sender, RoutedEventArgs e)
{
     // Load tube line information from xml
     doc = XDocument.Load("TubeLines.xml");
     var lines = from line in doc.Descendants("Line")
          select new LineDetail
          {
               Name = line.Element("Name").Value,
               Color = line.Element("Color").Value,
               Status = line.Element("Status").Value
          };
     // Bind list box with collection of lines read from xml
     TubeLinesListBox.ItemsSource = lines;

     // Set first line as active line
     TubeLinesListBox.SelectedIndex = 0;
}
   

Here Linq to XML is used to extract necessary information from the TubeLines.xml file. A local collection "lines" is prepared by iterating through each tube line and adding LineDetail instance to the collection. This collection is then bound with the listbox control.

As we need to use TubeLines.xml frequently, store this document as private member variable "doc".

OnSelectionChanged() Handler

This handler is called whenever a new tube line is selected in the list box. We need to show corresponding information in the dashboard.

First set tube line name and MapTitle using following code.

string lineName = ((LineDetail) TubeLinesListBox.SelectedItem).Name;
MapTitle.Text = ((LineDetail) TubeLinesListBox.SelectedItem).Name + " Map";
   

Use following code to get the image name for the selected tube line.

// Get tube map image name from xml
var varImageNames = from mapImage in doc.Descendants("MapImage")
          where mapImage.Parent.Element("Name").Value.Equals(lineName)
          select mapImage.Value;
string imageName = varImageNames.ElementAt(0).ToString();
   

Map images are inside xap file and they can be retrieved and loaded into Image control using following code.

BitmapImage bi = new BitmapImage();
bi.SetSource(Application.GetResourceStream(
          new Uri(imageName, UriKind.Relative)).Stream);
TubeLineMapImage.Source = bi;
TubeLineMapImage.Visibility = Visibility.Visible;

Use following code to populate pie-chart data and bind to the pie-chart control.

// Populate pie-chart data
var serviceInfos = from serviceInfo in doc.Descendants("Line").Elements("History").Elements("Status")
        where serviceInfo.Parent.Parent.Element("Name").Value.Equals(lineName)
        select new ServiceInfo
        {
            Name = serviceInfo.Attribute("name").Value,
            Value = Int32.Parse(serviceInfo.Attribute("value").Value)
        };

// Bind pie-chart data
((PieSeries)ServiceHistoryAsPieChart.Series[0]).ItemsSource = serviceInfos;

Use Following Code to Get Open XML document name from xml

var varOpenXmlFileNames = from openXmlFile in 
        doc.Descendants("StatsOpenXmlFile")
        where openXmlFile.Parent.Element("Name").Value.Equals(lineName)
        select openXmlFile.Value;
string openXmlFileName = varOpenXmlFileNames.ElementAt(0).ToString();

   

Use following code to read Open XML document from Xap file into IO stream.

Stream stream = Application.GetResourceStream(
        new Uri(openXmlFileName, 
        UriKind.Relative)).Stream);
   

As Open XML files are zip files, load Open XML document using SharpZipLib class "ZipFile",

ZipFile zip = new ZipFile(stream);

We know that table inside Open XML file is actually present inside "word/document.xml" file within Zip file. Hence extract this file and load it into XDocument using following code,

Stream xmlStream = zip.GetInputStream(zip.GetEntry("word/document.xml"));
XDocument openXmlDoc = XDocument.Load(xmlStream);

Each row of table data inside document.xml is inside "<w:tr>" tag. Each cell of the row is inside "" tag. One row of such table is as shown bellow.

<w:tr w:rsidR="003A2A45" w:rsidTr="003A2A45">
    <w:tc>
        <w:tcPr>
            <w:tcW w:w="1998" w:type="dxa" />
        </w:tcPr>
        <w:p w:rsidR="003A2A45" w:rsidRDefault="003A2A45" w:rsidP="00830C50">
            <w:r>
                <w:t>1</w:t>
            </w:r>
        </w:p>
    </w:tc>
    <w:tc>
        <w:tcPr>
            <w:tcW w:w="3600" w:type="dxa" />
        </w:tcPr>
        <w:p w:rsidR="003A2A45" w:rsidRDefault="003A2A45" w:rsidP="00830C50">
            <w:r>
                <w:t>5</w:t>
            </w:r>
        </w:p>
    </w:tc>
</w:tr>

   

Here "w" corresponds to the following xml namespace http://schemas.openxmlformats.org/wordprocessingml/2006/main. Set this programmatically as follows in the code,

XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
   

Now get all rows from table in the Open XML document as follows.

var rows = openXmlDoc.Descendants(w + "tr"); // w is the namespace
   

Now allocate new collection for holding table data using following code.

List<KeyValuePair<int, int>> dataPoints = new List<KeyValuePair<int, int>>();
   

Populate above collection using following code. Extract cell of the row from "<w:t>"

foreach (var row in rows)
{
        var cells = row.Descendants(w + "t"); // w is the namespace
        dataPoints.Add(new KeyValuePair<int, int>(
                (int)cells.ElementAt(0), (int)cells.ElementAt(1)));
}

   

Now bind the populated table data with line series control using following code.

((LineSeries) TubeLineStatistics.Series[0]).ItemsSource = dataPoints;
   

This we have populated all parts of the London Underground Dashboard UI. Now build and run the application in browser.

If you want to play with data, modify various data files and build the application again. This is needed because files are added into Xap as content.