1.1.1 摘要
在大多数情况下,我们的Web程序不仅仅需要给用户提供具体数据,在一些情况下,我们还需要给高级的用户或管理者提供数据汇总和分析图表之类的功能。
如果我们不想显示一大堆烦心的数据,希望通过饼图或条形图来直观地显示数据,这是我们可以考虑使用图表控件显示。
大家在访问我的博客时,在左边都可以看到一个统计每天的访问人数的工具,这就是一个简单的数据仪表程序,我们可以通过它直观地知道当日的访问数和时间。
在接下来的博文中,我们将向大家介绍数据仪表板程序的实现。
目录
1.1.2 正文
前一阵子有一篇博文关于StackOverflow 上的编程趋势,它通过条形和区域图,向我们展示了Stackoverflow上的热门的问题标签。
图1 Stackoverflow的热门标签
通过上图,我们可以直观地了解Stackoverflow上的热门标签的变化趋势,现在,我们通过仪表程序实现同样的功能。
在仪表程序界面中,我们会通过饼状图、区域图和条形图显示数据,这里我们使用Google Charts控件来显示饼状图、区域图和条形图数据图。
Google Charts通过Javascript实现动态图片的绘制,它的使用非常简便,我们只需给相应的绘图函数传递相应的数据,就可以生成相应的数据图表了。
UI设计
图2 Dashboard界面
现在,我们要在主界面(Dashboard)中,显示数据的饼状图、区域图和条形图,那么我们使用Google Charts控件动态地把三种图形加载到Index.cshtml页面中,下面是Index.cshtml页面代码:
<!-- Dashboard UI START --> <body> <div> @{ Html.RenderAction("Dashboard_Pie", "DashBoard"); } </div> <div> @{ Html.RenderAction("Dashboard_AreaChart", "DashBoard"); } </div> <div> @{ Html.RenderAction("Dashboard_ColumnChart", "DashBoard"); } </div> </body> <!-- Dashboard UI END -->
上面,我们定义了三个div元素,Index.cshtml页面动态地加载Dashboard_Pie、Dashboard_AreaChart以及Dashboard_ColumnChart的内容。
接下来,我们要定义Dashboard_Pie(饼状图)、Dashboard_AreaChart(区域图)和Dashboard_ColumnChart(条形图)页面,在定义数据图界面之前,首先让我们介绍Google Charts的使用。
Javascript
前面我们提到Google Charts的使用十分方便,首先我们需要引用jsapi库,在页面代码中添加如下代码:
<!-- Adds Google js api reference.--> <script type="text/javascript" src="https://www.google.com/jsapi"></script>
Google的JSAPI,不仅可以加载Google自身提供的AJAX API(如:Google Map API、Google Search API和Google Earth API),它还可以加载各种常用的JS库(如:jQuery、jQuery UI、Prototype、MooTools和Dojo等)。
现在,我们在页面中添加如下Javascript代码,引用Google的visualization库:
<script type="text/javascript"> google.load("visualization", "1", { packages: ["corechart"] }); google.setOnLoadCallback(drawPieChart); </script>
上面,我们使用google的load()方法加载了visualization库,并且定义了加载成功后的回调函数为drawPieChart()。
也许有人会问:“为什么不直接用Google CDN中提供Javascript库呢?”有两个原因,首先我们在Google CDN中没有找到和visualization库相关的引用地址(如有请告诉一下),其次,google的load()方法会加载一系列相关的资源(如:Javascript和CSS),这样我们就无需一个个引用了。
前面,我们定义了回调函数drawPieChart(),但还没有实现该方法,接下来,我们需要实现回调函数drawPieChart(),它负责获绘制数据图,具体实现如下:
/** * Draws the pie chart. **/ function drawPieChart() { // Gets data from GetLanguageRank(). $.ajax({ type: 'GET', dataType: 'json', url: '<%= Url.Content("") %>', data: {}, success: function(data) { var dt = new google.visualization.DataTable(); dt.addColumn('string', 'Language'); dt.addColumn('number', 'Question'); // Adds data. for (var i = 0; i < data.length; i++) { dt.addRow([data[i].Name, data[i].Question]); } var options = { title: "Top 25 programming language" }; // Draws pie implemention. var chart = new google.visualization.PieChart(document.getElementById('pieChart')); chart.draw(dt, options); }, error: function(xhr, textStatus, e) { console.log('Status: ' + textStatus + ' Error: ' + e.toString()); }, complete: function() { } }); }
上面,我们实现了回调函数drawPieChart(),它通过调用$.ajax()方法从后端中获取数据,如果数据获取成功,就把数据传递给draw()方法绘制数据图表。
接着,我们实现Dashboard_Pie数据图界面,具体代码如下:
<!-- Pie chart page --> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head id="Head1" runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> </head> <body> <form id="form2" runat="server"> <div id="pieChart"> </div> </form> </body> </html>
上面,我们在form元素中添加了一个div元素,由于我们在回调函数drawPieChart()中,指定了饼状图的加载位置,所以我们需要在页面中添加饼状图的div元素。
前面,我们提到回调函数drawPieChart(),通过$.ajax()方法从后端中获取数据,现在,我们需要提供API方法,让客户端通过调用API获取相应的数据。
这里,我们使用Stackoverflow Jan/01/2010到July/01/2013的热门标签数据(从这里下载)。
由于数据是CSV格式的,所以我们可以使用Excel查看数据。
图3 热门标签数据
通过上图中的数据,我们定义Language类,它包含四个字段分别是Id、Name、Question和CreateOn,具体定义如下:
图4 Language类
/// <summary> /// The language model. /// </summary> public class QuestionTag { public int Id { get; set; } public string Name { get; set; } public int Question { get; set; } public DateTime CreateOn { get; set; } }
上面,我们定义了QuestionTag类,接下来,我们需要定义控制器类,它负责返回后端数据,所以我们在Controllers文件中创建DashboardController类,并且我们添加GetLanguageRank()方法,具体实现如下:
图5 DashboardController类
/// <summary> /// Gets language rank data. /// </summary> /// <returns>JSON arrary.</returns> public JsonResult GetLanguageRank() { // Gets data from database. }
导入数据
上面,我们定义了DashboardController类,它包含GetLanguageRank()方法,接下来我们把CSV数据保存到数据库中。
首先,我们在数据库中创建数据表,具体SQL代码如下:
-- ============================================= -- Author: JKhuang -- Create date: 07/25/2013 -- Description: Table for storing question tag data -- ============================================= SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO SET ANSI_PADDING ON GO CREATE TABLE [dbo].[QuestionTags]( [Name] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL, [Question] [int] NOT NULL, [CreateOn] [datetime] NOT NULL ) ON [PRIMARY] GO SET ANSI_PADDING OFF
接着,我们CSV数据导入到SQL Server中,具体实现如下:
-- ============================================= -- Author: JKhuang -- Create date: 07/25/2013 -- Description: Imports csv data into database. -- ============================================= BULK INSERT QuestionTags FROM 'C:UsersAdministratorDesktopStackoverflow Tags Data.csv' WITH ( FIRSTROW = 2, -- Start row excludes header. FIELDTERMINATOR = ',', --CSV field delimiter ROWTERMINATOR = ' ', --Use to shift the control to next row ERRORFILE = 'C:UsersAdministratorDesktopErrorLog.csv',? TABLOCK )
上面,我们直接使用SQL语句把CSV数据导入到数据库中,其中,我们定义了导入数据的源文件和数据格式,并且定义了ErrorLog文件记录导入失败的数据,最后,我们在表QuestionTags中添加自增型的Id主键。
图6 导入CSV数据
ASP.NET控制器
现在,我们已经把数据储存到数据库中了,接下来我们将使用EF获取数据库中的数据,接触过EF的应该都知道EF的编程模型有3种:
Database First:数据库先行
Model First:模型先行
Code First:代码先行
由于,前面我们已经把数据表定义好了,所以我们将使用数据库先行(Database First)模型对数据库进行访问。
接下来,让我们实现GetLanguageRank()方法,具体代码如下:
/// <summary> /// Gets language rank data. /// </summary> /// <param name="index">Specifies the range of data, /// for instance, when index is 0, then get the data range from Jan/1/2010 till Feb/2/2010. /// </param> /// <returns>JSON Array</returns> public JsonResult GetLanguageRank(int index = 0) { using (var db = new DashboardDbContext()) { var result = (from tags in db.QuestionTags orderby tags.CreateOn ascending select new { tags.Id, tags.Name, tags.Question, tags.CreateOn }).Skip((index % 42) * 25).Take(25).ToList(); return Json(result, JsonRequestBehavior.AllowGet); } }
我们实现了GetLanguageRank()方法,它根据index值获取指定时间的数据,然后通过JSON数据格式返回给客户端。
现在,我们已经实现了饼状图(Dashboard_Pie)了,接下来,让我们运行Index.cshtml页面查看运行的效果吧!
图7 饼状图
我们注意到图1是一个动态图,它直观的展示了Stackoverflow热门标签的变化趋势,如果我们也要实现动态生成数据图该如何实现呢?
其实,问题转化为实时获取数据,然后生成数据图就OK了,如果要实现实时获取时间,我们想到的方法有:
1.Timer()
2.方法二数据库实时方法数据(SqlDependency)
3.Other(请大家分享好方法)
接下来,我们将使用Javascript中Timer()函数来定时访问GetLanguageRank()方法,所以我们需要修改Javascript代码,通过Timer()函数定时调用drawColumnChart()方法,具体实现如下:
<script type="text/javascript"> google.load("visualization", "1", { packages: ["corechart"] }); google.setOnLoadCallback(timerStart); var cnt = 0, t; function timerStart() { t = window.setInterval(drawColumnChart, 1000); } function timerStop() { clearTimeout(t); } function drawColumnChart() { $.ajax({ type: 'GET', dataType: 'json', url: '<%= Url.Content("~/Dashboard/GetLanguageRank") %>', data: { index: cnt }, success: function(data) { var dt = new google.visualization.DataTable(); dt.addColumn('string', 'Language'); dt.addColumn('number', 'Question'); for (var i = 0; i < data.length; i++) { dt.addRow([data[i].Name, data[i].Question]); } var dateTime = new Date(parseInt(data[0].CreateOn.substr(6))); var options = { title: "Top 25 programming language on " + (dateTime.getMonth() + 1) + '/' + dateTime.getDate() + '/' + dateTime.getFullYear(), // 600, height: 500 }; var chart = new google.visualization.ColumnChart(document.getElementById('columnChart')); chart.draw(dt, options); }, error: function(xhr, textStatus, e) { timerStop(); console.log('Status: ' + textStatus + ' Error: ' + e.toString()); }, complete: function() { cnt = cnt + 1; } }); } </script>
当Google的visualization库加载完毕后,访问回调函数timerStart(),然后使用setInterval()方法每隔1s就调用drawColumnChart()绘制新的柱状图。
图8 柱状图
现在,我们通过Timer()函数实时的访问API接口,数据通过柱状图动态地显示出来。
页面样式
现在,我们已经完成了饼状图和柱状图,接下来,我们需要给仪表程序添加一些简单的CSS效果,具体代码如下:
/*Dashboard APP CSS*/ .pageHeader { height: 20px; background-color: #2C2C2C; padding: 10px 10px; margin-bottom: 10px; color: White; position: relative; } .pageHeader h1 { font: normal 1.2em Arial; color: White; margin: 0; padding: 0; } .pageHeader .platformular { position: absolute; top: 10px; right: 10px; } .pageBody { margin: 0 10px; } .pageFooter { clear: both; padding-top: 10px; width: 100%; text-align: center; font-size: 0.8em; color: #686868; margin: 25px 0 0 0; border-top: solid 1px #e7e7e7; }
现在,我们重新运行程序查看页面效果。
图10仪表程序
1.1.3 总结
在本博文中,我们通过使用ASP.NET MVC和EF的Database First实现了简单的仪表程序,使用Google Charts控件来显示数据图,这只是一个简单的程序,我们还有很大的改善空间,提供一个内容丰富和功能强大的程序是每个程序员的目标。
参考
更新:08/02/2013