In this post, we will demonstrate how to develop an Enterprise Dashboard Application using Silverlight.
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.
All these information will be available on single page and user should be easily able to navigate to various tube lines.
We will use following open source libraries:
These libraries are available in following attachment.
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,
All data files are available in the following attachment.
The development of Silverlight London Underground Dashboard application consists of four steps:
As mention above, we have to display following information
We propose following UI for displaying all this information on a single page.
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>
Let us discuss various data files used in the dashboard.
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>
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.
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
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; } }
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".
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 "
<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.