BT

Creating a sales dashboard for ASP.NET and MVC with ShieldUI Chart

Posted by David Johnson on Oct 23, 2013 |

In this article, we tackle a common data visualization task – creating a sales dashboard. A sales dashboard is widely used in business presentations, to outline key performance indicators for a given business process or objective. Key to any such presentation is the good visualization of the data, as well as a polished appearance. To achieve the task, I am using related chart components, which offer all of the required functionality. The sample uses chart components from ShieldUI,which are freely available from their site.

The finished presentation looks like this:

(Click on the image to enlarge it)


The sample contains the same setup in both ASP.NET and MVC.

Using the code

ASP.NET Version

To start off, I create a new Visual Studio project for web. The web application needs to contain a single .aspx file, which will host the related controls.  The second step is to include the .dll files for the chart components to the project:

Once we have added the .dlls for the component we will use, we need to reference it in the project. This can be done directly in the .aspx page, containing the following dashboard sample:

<%@ Register Assembly="Shield.Web.UI"  
     Namespace="Shield.Web.UI" TagPrefix="shield" %>  

Then, since this control is a server-side wrapper of a client JavaScript component, we also need to include references to the base JavaScript chart. This is also done in the .aspx file:

<head> 
<link rel="stylesheet" type="text/css" 
href="shield-chart.1.2.2-Trial/shield-chart.1.2.2-Trial/css/shield-chart.min.css"/> 
   <script src="shield-chart.1.2.2-Trial/shield-chart.1.2.2-Trial/js/jquery-1.9.1.min.js" 
     type="text/javascript"></script> 
   <script src="shield-chart.1.2.2-Trial/shield-chart.1.2.2-Trial/js/shield-chart.all.min.js" 
     type="text/javascript"></script> 
</head> 

The next phase of the project is to transform the requirements into code. The requirements specify that we need to have two or more related charts. In our case, we have a control to host the quarters, as well as a second chart to display data related to each quarter and one more chart to relate to the second one. In this manner, we can have subdivision by quarter and product-line - a common data visualization scenario for a sales dashboard. Following the outline above, we add the first chart to the .aspx file. Its declaration looks like this:

<asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="false"> 
    <ContentTemplate> 
       <shield:ShieldChart ID="ShieldChart1" runat="server" AutoPostBack="true" OnSelectionChanged="ShieldChart1_SelectionChanged" Width="320px" Height="330px"
        OnTakeDataSource="ShieldChart1_TakeDataSource">
        <PrimaryHeader Text="Quarterly Sales"> 
        </PrimaryHeader> 
         <ExportOptions AllowExportToImage="false" AllowPrint="false" /> 
         <TooltipSettings CustomPointText="Sales Volume: <b>{point.y}</b>"> 
         </TooltipSettings> 
        <Axes> 
          <shield:ChartAxisX CategoricalValuesField="Quarter"> 
            </shield:ChartAxisX> 
                 <shield:ChartAxisY> 
                   <Title Text="Quarter verview"></Title> 
                 </shield:ChartAxisY> 
               </Axes> 
               <DataSeries> 
                 <shield:ChartBarSeries DataFieldY="Sales">
                   <Settings EnablePointSelection="true" EnableAnimation="true"> 
                     <DataPointText BorderWidth=""> 
                     </DataPointText> 
                   </Settings> 
                 </shield:ChartBarSeries>
               </DataSeries> 
               <Legend Align="Center" BorderWidth=""></Legend> 
             </shield:ShieldChart> 
           </ContentTemplate> 
   </asp:UpdatePanel>  

It is wrapped in an update panel, to provide smooth visual updates.
In order to populate the chart control with data, we use the TakeDataSource event handler, in the code-behind of the .aspx file:

  protected void ShieldChart1_TakeDataSource(object sender, Shield.Web.UI.ChartTakeDataSourceEventArgs e) 
     { 
       ShieldChart1.DataSource = new object[] 
       { 
         new {Quarter = "Q1", Sales = 312 },  
         new {Quarter = "Q2", Sales = 212 }, 
         new {Quarter = "Q3", Sales = 322 }, 
         new {Quarter = "Q4", Sales = 128 } 
       }; 
     }  

The DataSource property of the control tells the chart what data will be passed to it for visualization. Here we pass a simple object array with sales entry for each quarter. This will represent the chart with the quarterly data. The second, related, chart also added in the .aspx file, looks like this:

<div id="container2" style="width: 490px; height: 340px; margin: auto; top: 5px; left: 5px; position: inherit;"> 
   <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional"ChildrenAsTriggers="false"> 
     <ContentTemplate>         <shield:ShieldChart ID="ShieldChart2" OnTakeDataSource="ShieldChart2_TakeDataSource" AutoPostBack="true"           OnSelectionChanged="ShieldChart2_SelectionChanged" runat="server" Width="463px" Height="331px">           <ExportOptions AllowExportToImage="false" AllowPrint="false" />           <PrimaryHeader Text="Select a Quarter to show products sales">           </PrimaryHeader>           <Axes>             <shield:ChartAxisY>               <Title Text="Break-Down for selected quarter"></Title>             </shield:ChartAxisY>           </Axes>           <DataSeries>             <shield:ChartDonutSeries EnableValueXSorting="false" CollectionAlias="Q Data" DataFieldY="Data" DataFieldX="Product">               <Settings EnablePointSelection="true" EnableAnimation="true" AddToLegend="true">                 <DataPointText BorderWidth="">                 </DataPointText>               </Settings>             </shield:ChartDonutSeries>           </DataSeries>           <Legend Align="Center" BorderWidth=""></Legend>         </shield:ShieldChart>       </ContentTemplate>       <Triggers>         <asp:AsyncPostBackTrigger ControlID="ShieldChart1" EventName="SelectionChanged" />       </Triggers>     </asp:UpdatePanel>  </div>  

Both the first and the second charts have a selection event handler enabled, in order to allow the re-creation of the related control, since they both have a sub-chart attached to them. The flow of the project allows the end user to select a quarter from among the four quarters visualized in “ShieldChart1”. Then, for this quarter, all available data is displayed in a donut chart, which is hosted in "ShieldChart2". The user can then select a particular category from the donut, which in turn populates the third chart. Its declaration is listed below:

<div id="container3_box" style="width: 925px; height: 300px; border: 2px solid #40B3DF; margin: auto; top: 420px; left: 25px; position: absolute;"> 
   <div id="container3" style="width: 890px; height: 290px; margin: auto; top: 5px; left: 5px; position: inherit;"> 
     <asp:UpdatePanel ID="UpdatePanel3" runat="server" UpdateMode="Conditional"> 
       <ContentTemplate> 
         <shield:ShieldChart ID="ShieldChart3" runat="server" OnTakeDataSource="ShieldChart3_TakeDataSource" 
           Width="905px" Height="280px"> 
           <DataSeries> 
             <shield:ChartLineSeries DataFieldY="QuarterSales"> 
               <Settings AddToLegend="false"> 
                 <DataPointText BorderWidth=""> 
                 </DataPointText> 
               </Settings> 
             </shield:ChartLineSeries>             </DataSeries>             <PrimaryHeader Text="Select a product to show sales details...">             </PrimaryHeader>             <ExportOptions AllowExportToImage="false" AllowPrint="false" />             <Legend Align="Center" BorderWidth=""></Legend>           </shield:ShieldChart>         </ContentTemplate>         <Triggers>           <asp:AsyncPostBackTrigger ControlID="ShieldChart2" EventName="SelectionChanged" />         </Triggers>       </asp:UpdatePanel>     </div> 
</div> 

In order to allow the re-creation of the sub-charts, one additional step needs to be taken. This is done on the server when the selection of the charts is triggered, which eliminates the need to write any client side code. The server side code looks like this:

protected void ShieldChart1_SelectionChanged(object sender, ChartSelectionChangedEventArgs e) 
{ 
   if (e.Selected) 
   { 
     SelectedQuarter = e.Name;
     DataPerformance = GetPerformanceData().Where(d => d.Quarter == SelectedQuarter).ToList(); 
   } 
   else 
   { 
     DataPerformance = null; 
   } 
   ShieldChart2.DataBind(); 
 } 
 protected void ShieldChart2_SelectionChanged(object sender, ChartSelectionChangedEventArgs e) 
 { 
   if (e.Selected) 
   { 
     SalesData = GetSalesDataProducts().Where(s => s.Quarter == SelectedQuarter && s.Product == e.Item.ValueX.ToString()).ToList(); 
   } 
   else 
   { 
     SalesData = null; 
   } 
   ShieldChart3.DataBind(); 
 }  

This completes our ASP.NET setup, and the sales dashboard is ready for use. Feel free to download the code sample and explore the code for further reference.

MVC application

The same setup can easily be achieved in MVC. To start off, I create a new Visual Studio 2012 MVC application, with .NET Framework 4.0. To be able to take advantage of the charting component, I add a reference to the Shield.Mvc.UI dll:

The folder structure of the application looks like this:


In the next paragraphs I will go over the contents of each folder of interest and give some additional information on the code used, along with its importance. We start with the Views folder. It contains four different views – three for the charts and one for the main page structure. The first view is “Layout.cshtml”, which determines the main layout of the page and includes references to any required css files, along with the js files, needed by the charting component and its wrapper:

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Sales Dashboard - Shield Chart for ASP.NET MVC</title> 
    <link rel="stylesheet" type="text/css" href="content/shield-chart.1.2.3-Trial/css/shield-chart.min.css" />     <script src="content/shield-chart.1.2.3-Trial/js/jquery-1.9.1.min.js" type="text/javascript"></script>     <script src="content/shield-chart.1.2.3-Trial/js/shield-chart.all.min.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="content/css/site.css" />     <script src="content/js/scripts.js" type="text/javascript"></script>
</head> <body>     <div class="page">         <div class="header">         </div>         <div class="main">             @RenderBody()         </div>         <div class="clear">         </div>     </div>
    <div class="footer">     </div> </body> </html>

This view also contains a reference to “scripts.js” – a file which contains the handlers for the client side events of the charts, and is discussed in more detail at the end of the article.

The next view, “Index.cshtml” contains the declaration of the “quarterly” chart – the only one which is rendered initially, and the selection of which populates the related donut chart. The declaration includes setting all the required properties of the control, such as its X and Y axes, or the Data Series and their actual data. A point of interest is the “Events” property, which declares a handler for selecting a point from the bar chart. This is important in relating the chart with a sub-chart, which will render once the user makes a selection from the first chart. The code on the page looks like this:

@{
    ViewBag.Title = "Sales Dashboard with Shield Chart for ASP.NET MVC";
    Layout = "~/Views/Shared/Layout.cshtml";
}
@model IEnumerable<SalesDashboardMVC.Models.QuarterlySales>
<div class="dashboard">     <div class="header">         Sales DashBoard using <span class="highlight">Shield             UI MVC Chart</span>     </div>     <div class="topleft">         @(Html.ShieldChart(Model)             .Name("quarterlySales")             .HtmlAttribute("class", "chart")             .PrimaryHeader(header => header.Text("Quarterly Sales"))             .Export(false)             .Tooltip(tooltip => tooltip.CustomPointText("Sales Volume: <b>{point.y}</b>"))             .AxisX(axisX => axisX.CategoricalValues(model => model.Quarter))             .AxisY(axisY => axisY.Title(title => title.Text("Quarterly Overview")))             .DataSeries(dataSeries => dataSeries                 .Bar()                 .Data(model => model.Sales)                 .EnablePointSelection(true))             .ChartLegend(chartLegend => chartLegend                 .Align(Shield.Mvc.UI.Chart.Align.Center))             .Events(events => events.PointSelect("app.quarterSelected")))     </div>     <div class="topright">     </div>     <div class="bottom">       </div> </div>

The first sub-chart, which is populated with data once the user selects a quarter from the initially rendered bar chart, is a donut chart. Its declaration is contained in the “_PerformanceChart.cshtml” view. Its code is as follows: 

@model IEnumerable<SalesDashboardMVC.Models.PerformanceData>
@(Html.ShieldChart(Model)     .Name("productsByQuarter")     .HtmlAttribute("class", "chart")     .Export(false)     .PrimaryHeader(header => header.Text("Select a Quarter to show products sales"))     .AxisY(axisY => axisY.Title(title => title.Text("Break-Down for selected quarter")))     .DataSeries(dataSeries => dataSeries         .Donut()         .Name("Q Data")         .Data(model => new         {             collectionAlias = model.Product,             x = model.Product,             y = model.Data,         })         .EnablePointSelection(true)         .AddToLegend(true))     .ChartLegend(chartLegend => chartLegend.Align(Shield.Mvc.UI.Chart.Align.Center))     .Events(events => events.PointSelect("app.productSelected")))

The last chart, which is populated when an item from the donut is selected. It is located in the “_SalesDetailsChart.cshtml” file and has the following declaration:

@model IEnumerable<SalesDashboardMVC.Models.SalesByProduct>
@(Html.ShieldChart(Model)     .Name("salesDetails")     .HtmlAttribute("class", "chart")     .PrimaryHeader(header => header.Text("Select a product to show sales details"))     .Export(false)     .DataSeries(dataSeries => dataSeries         .Line()         .Data(model => model.QuarterSales)         .AddToLegend(false)))

The “Models” folder contains the models for the data, which will be used to populate the three different charts. For example, the first chart is bound to data of type “QuarterlySales” and looks like this:

namespace SalesDashboardMVC.Models
{
    public class QuarterlySales
    {
        public string Quarter { get; set; }
        public decimal Sales { get; set; }
        public static IEnumerable<QuarterlySales> GetData()         {          yield return new QuarterlySales() { Quarter = "Q1", Sales = 312 };          yield return new QuarterlySales() { Quarter = "Q2", Sales = 212 };          yield return new QuarterlySales() { Quarter = "Q3", Sales = 322 };          yield return new QuarterlySales() { Quarter = "Q4", Sales = 128 };         }
    } }

The other charts are bound to similar data classes, which I will omit for brevity. The “Controllers” folder contains a single controller, which governs the actions triggered by selections in the charts. It looks like this:

public class HomeController: Controller
    {
        //
        // GET: /Home/
        public ActionResult Index()
        {
            return View(QuarterlySales.GetData());
        }
        public ActionResult Performance(string quarter)
        {
            return View("_PerformanceChart",                   
PerformanceData.GetDataByQuarter(quarter));
        }
        public ActionResult Details(string product, string quarter)
        {
            return View("_SalesDetailsChart", SalesByProduct.GetDataByProductAndQuarter(product, quarter));
        }
    }

One important piece of code remains to be mentioned. This is the “scripts.js” file, located in the “Content” folder. As can be seen from the declarations above, the first and second charts have selection event handlers. These handle client side events, which are triggered when the user selects a bar or donut segment from one of the charts. The “scripts.js” file contains client side handlers for these events, which look like this:

(function (jQuery) {
    this.app = {
        quarter: "",
        quarterSelected: function (e) {             var quarter = app.quarter = e.point.name;             $(".topright").load("/performance/" + quarter);             $(".bottom").empty();         },
        productSelected: function (e) {             var product = e.point.x,                 quarter = app.quarter;             $(".bottom").load("/details/" + product + "/" + quarter);         }     }; }).call(this, jQuery);

Essentially, this code makes an Ajax request, taking into account the selection data, which is available through the arguments for the function. This, in turn, triggers either the “Performance” or the “Details” action handlers, which were listed in the “HomeController” controller.

This sums up the most important points in our setup. The compete code, along with a working project, is available for download here.

About the Author

David Johnson is a 37 years old developer from London, UK. For the past 15 years he worked mainly in the field of web technologies. During the last 10 years, I he focused primarily on ASP.NET and MVC. David worked on many projects as a contract developer. David’s most recent position is a Lead developer at ShieldUI.

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Great way to introduce Sales Dashboard by Eone James

I extremely thankful you to provided such a valuable information about Sales Dashboard which will be helpful to identify business growth in different way.

-.NET Developer
www.elantechnologies.com

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

1 Discuss

Educational Content

General Feedback
Bugs
Advertising
Editorial
InfoQ.com and all content copyright © 2006-2014 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT