本文中,我们将处理一个常见的数据可视化任务,即创建一个销售仪表板(dashboard)。在商业演示中经常会使用销售仪表板来展现某个商业流程或商业目标的关键绩效指标,而完成这样一个演示的关键不仅在于对数据进行良好的可视化展示,还要有赏心悦目的外观。为了完成这一任务,我会使用相关的图表组件,它提供了全部的所需功能。这个示例中将使用 ShieldUI 中的图表组件,这一系列产品可以从网站的免费下载。
完成后的展示请见下图:
(点击图像放大)
本示例将使用ASP.NET 与MVC 两种方式讲解。
使用代码
ASP.NET 版本
我首先建立一个 Visual Studio 的 web 项目,这个 web 应用包含一个单独的.aspx 文件,其中包含了相关的控件。第二步则是将图表组件所在的.dll 文件加入当前项目:
将组件所在的.dll 文件加入项目之后,我们还需要在项目中引用它。可以直接在.aspx 页面中完成,请见下面的示例:
<%@ Register Assembly="Shield.Web.UI" Namespace="Shield.Web.UI" TagPrefix="shield" %>
因为这个控件其实是对一个客户端 JavaScript 组件的服务端封装,我们还需要引用基础的 JavaScript 图表文件,同样也在.aspx 文件中完成:
<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>
接下来就是要将需求转化为代码了,需求中表示我们需要两个或者更多的图表。在这个示例中,我们将使用一个图表显示所有的季度,另外用一个图表显示每个季度的相关数据,最后还有一个图表与第二个图表相关联。这样,我们就可以按照季度和产品线进行分类,这对于销售仪表板来说是一种常见的数据可视化场景。按照以上所概括的步骤,让我们首先将第一个图表加入.aspx 文件中。它的声明部分如下所示:
<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>
这里用一个 update panel 将控件包装起来,以提供更平滑的视觉上的更新。
为了将数据加载到图表控件中,我们利用了 TakeDataSource 这个事件处理器,以下是.aspx 文件的 code behind 中的部分代码:
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 } }; }
控件的 DataSource 属性告诉了图表需要传递给它以实现可视化的数据是什么。我们传入了一个简单的对象数组,它包含了每个季度的销售数据,这样图表就可以表现出每个季度的数据了。接下来将第二个相关的图表也加入.aspx 文件中,看起来是这样的:
<div id="container2" style="width: 490px; height: 340px; margin: auto; top: 5px; left: 5px; position: inherit;"> <asp:UpdatePanel ID="UpdatePanel1" runat="server" pdateMode="Conditional" ChildrenAsTriggers="false"> <ContentTemplate> <shield:ShieldChart ID="ShieldChart2" nTakeDataSource="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" ollectionAlias="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>
第一和第二个图表控件都应用了一个被选择的事件处理器,以允许对相关控件进行重建,因为他们各自带有一个子图表。这个项目允许最终用户在“SheidChart1”控件以可视化方式显示的 4 个季度选择一个季度。接下来,对所选中的季度,“ShieldChart”控件会以饼图的方式显示所有可用的数据。用户接下来可以从饼图中选择一个特定的分类,这又载入了第三个图表的数据。它的定义如下所示:
<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" nTakeDataSource= "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>
为了允许对子图表进行重建,还需要加入一个额外的步骤。这一步是当图表被选择的事件触发后在服务端完成的,这样就可以避免编写客户端代码了。具体的服务端代码如下所示:
protected void ShieldChart1_SelectionChanged(object sender, ChartSelectionChangedEventArgs e) { if (e.Selected) { SelectedQuarter = e.Name; DataPerformance = GetPerformanceData().Where(d => d.Quarter == electedQuarter). 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(); }
以上就完成了 ASP.NET 的版本,销售仪表板已经可以使用了。请随意下载该示例代码并作为今后的参考。
MVC 应用程序
这个示例也可以很方便地搭建在 MVC 上。首先建立一个新的 Visual Studio 2012 MV 应用程序,使用.NET Framework 4.0。为了使用图表控件的功能,我在项目中添加了对 Shield.Mvc.UI 这个 dll 文件的引用:
以下是该应用程序的文件夹结构:
接下来我会逐一地讲解每个文件夹中令人感兴趣的部分,并讲述一些代码之外的信息,以及每段代码的重要性。首先从 Views 文件夹开始,它包含了四个不同的视图,三个用来显示图表,另一个则包含主体页面结构。第一个视图是“Layout.cshtml”,它决定了页面的主体结构,并且加入了图表组件与它的封装所需的各种 css 文件与 js 文件的引用。
<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>
该视图中还包含了一个对“scripts.js”的引用,这个文件包含了客户端事件的处理器,但本文的末尾部分讨论了该文件的更多细节。
下一个视图“Index.cshtml”包含了“季度”图表的定义,这是唯一一个会在初始化页面时就显示的图片,选择这张图表上的某个季度就会加载相应的饼图。这个视图的声明部分包括了设置控件的各种必需属性,例如它的 X 轴与 Y 轴,还有 DataSeries 属性以及实际的数据。其中有趣的一点是“Events”属性,它定义了从条状图中选择某个点之后的事件处理器,这一点对于关联图表与子图表非常关键,当用户从第一张图表中选择了某个季度后,子图表就会显示相应的数据。该页面的代码如下所示:
@{ 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>
当用户从页面初始化时就显示的条状图中选择了某个季度后,第一个子图表就会加载相应的数据,并显示为一个饼图。它的定义包含在“_PerformanceChart.cshtml”视图中,代码如下所示:
@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")))
当用户选择了饼图里的某一项之后,就会加载数据并显示最后一张图表。它的代码包含在“_SalesDetailsChart.cshtml”文件中,具体定义如下:
@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)))
“Models”文件夹包含了数据的模型,它将用以加载三个不同的图表。举例来说,首个图表将对应类型“QuarterlySales”中的数据,它的代码如下:
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 }; } }
其它图表也将对应类似的数据类,我在这里就略过不提了。“Controllers”文件夹包含了一个单独的控制器,它将响应图表的选择所对应的各种行为。代码如下:
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", alesByProduct. GetDataByProductAndQuarter(product, quarter)); } }
有一段重要的代码需要引起注意,那就是“Content”文件夹下的“scripts.js”文件。从以上的代码声明可以看出,第一与第二个图表包括了选择事件的处理器。这些处理器将处理客户端的事件,这是由用户在条状图或饼图中选择了一段数据所触发的,而“scripts.js”文件则包含了这些事件的客户端处理器,它的代码如下:
(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);
实质上,这段代码会发起一个 Ajax 请求,它将处理被选择的数据,而这是由方法的参数中传递的。该请求将触发“Performance”或者“Details”的行为处理器,它们都由“HomeController”中的控制器所定义。
我们的代码中最主要的部分到这里差不多就介绍完了,完整的代码和一个可运行的项目都可以在这里下载。
关于作者
David Johnson来自于英国伦敦,他已经是一位 37 岁的“老”程序员了。过去的 15 年来他主要工作于 web 技术的领域,而过去 10 年中他主要专注于 ASP.NET 和 MVC 平台。David 作为一名外包开发者参与了许多项目,最近的职位是在 ShieldUI 担任首席开发者。
查看英文原文: Creating a sales dashboard for ASP.NET and MVC with ShieldUI Chart
评论