前言:

  "宁肯像种子一样等待 
  也不愿像疲惫的陀螺 
  旋转得那样勉强"

  这是前几天在查资料无意间看到的一位园友的签名,看完后又读了两遍,觉得很有味道。后来一寻根究底才知这是出资大诗人汪国真之口,出处《她》。且抛开上下文,单从这短短几句,正恰如其分的折射出有一群人,他们穿着不那么fashion,言辞不那么犀利,但是内心某一块地方像是躁动的火山,拥有无尽的动力和激情,矢志不渝种子般投身到技术研究和心得分享当中。

  或许每一次的生长都是那么悄无声息,但是无数次的坚持只是为了破土那日让别人看到坚持的自己。

正文:

上篇我们主要做了如下工作:

  创建了两个文件:

  statistic.html:提供Statistic这个功能的界面

  StatisticController.js:作为statistic.html的御用控制器,负责为statistic.html提供相应的功能和数据

  更新了两个文件:

  Angello.js:为页面跳转添加接口

  boot.js:注册新建的js文件,以便新建的js文件投入使用

  同时遇到了一些坑比如:

  controllerAs参数的使用,以及它的利与弊

  Statistic的功能分为两块:

  第一是数据统计,通过上篇的StatisticController控制器就能实现传值并配合data.html显示,如上篇中看到效果图页面的上半部分;

  第二是数据展示,这就是今天以及后面所要做的工作。今天会讲到如何使用指令,为什么要用指令以及在编码过程中遇到的一些各色问题。

  项目的代码我已经托管在Github上:angelloExtend

一、使用D3.js

  以前做可视化的时候,研究过GephiPrefuse,但是D3.js的大名如雷贯耳。当时只知道D3都是js的代码,与项目使用的场景不合,现在来看,正好派上用场。

  D3本身就是负责直观显示的视觉类产品,所以首先需要跑出一个效果出来。于是找到了一段代码

  1. <html>
  2. <head>
  3. <meta charset="utf-8">
  4. <title>做一个简单的图表</title>
  5. </head>
  6. <body>
  7. <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
  8. <script>
  9.  
  10. var width = 300; //画布的宽度
  11. var height = 300; //画布的高度
  12.  
  13. var svg = d3.select("body") //选择文档中的body元素
  14. .append("svg") //添加一个svg元素
  15. .attr("width", width) //设定宽度
  16. .attr("height", height); //设定高度
  17.  
  18. var dataset = [ 250 , 210 , 170 , 130 , 90 ];
  19.  
  20. var rectHeight = 25; //每个矩形所占的像素高度(包括空白)
  21.  
  22. svg.selectAll("rect")
  23. .data(dataset)
  24. .enter()
  25. .append("rect")
  26. .attr("x",20)
  27. .attr("y",function(d,i){
  28. return i * rectHeight;
  29. })
  30. .attr("width",function(d){
  31. return d;
  32. })
  33. .attr("height",rectHeight-2)
  34. .attr("fill","steelblue");
  35.  
  36. </script>
  37.  
  38. </body>
  39. </html>

  

  将其直接替换statistic.html页面,刷新页面发现并没有出现预期的图形,而是在控制台界面中报错说d3没定义。根据这个线索开始怀疑d3的js文件并没有引入成功,有怀疑过是否被墙,但是后来证实不是这个原因。最终发现还是应了上篇的那个小坑,js文件在适用前都需要注册,于是在boot.js中加入代码行:

  1. { file: 'http://d3js.org/d3.v3.min.js'},

  刷新界面后显示正常。

二、开辟显示Statistic结果的地盘

  前面我们一直借statistic.html这个页面来实现测试,但事实是statistic.html是用于显示用户列表,当点击某个用户下面的statistic功能的时候,一切的statistic结果需要显示在另外一张页面。

  这个逻辑可以借用Angello项目中的User模块。所以我们需要新建一个存放statistic数据的data.html页面并在Angello.js中添加data.html页面跳转的路由:

  Angello.js

  1. .when('/statistic/:userId', {
  2. templateUrl: 'src/angello/statistic/tmpl/data.html',
  3. controller: 'DataCtrl',
  4. controllerAs: 'myUser',
  5. requiresLogin: true,
  6. resolve: {
  7. user: function ($route, $routeParams, UsersModel) {
  8. var userId = $route.current.params['userId']
  9. ? $route.current.params['userId']
  10. : $routeParams['userId'];
  11. return UsersModel.fetch(userId);
  12. },
  13. stories: function ($rootScope, StoriesModel) {
  14. return StoriesModel.all();
  15. }
  16. }
  17. })

  

  data.html

  就是上面放到statistic.html的中代码

  与此同时我们需要一个支持这个data.html的controller——DataController.js

  1. angular.module('Angello.Statistic')
  2. .controller('DataCtrl',
  3. function ($routeParams, user, stories, $log) {
  4. var myUser = this;
  5.  
  6. myUser.userId = $routeParams['userId'];
  7. myUser.user = user.data;
  8.  
  9. myUser.getAssignedStories = function (userId, stories) {
  10. var assignedStories = {};
  11.  
  12. Object.keys(stories, function(key, value) {
  13. if (value.assignee == userId) assignedStories[key] = stories[key];
  14. });
  15.  
  16. return assignedStories;
  17. };
  18.  
  19. myUser.stories = myUser.getAssignedStories(myUser.userId, stories);
  20. var len=0,toDo=0,inProgress=0,codeReview=0,qaReview=0,verified=0;
  21. function getJsonObjLength(jsonObj) {
  22. for (var item in jsonObj) {
  23. switch (jsonObj[item].status){
  24. case "To Do":
  25. toDo++;
  26. break;
  27. case "In Progress":
  28. inProgress++;
  29. break;
  30. case "Code Review":
  31. codeReview++;
  32. break;
  33. case "QA Review":
  34. qaReview++;
  35. break;
  36. case "Verified":
  37. verified++;
  38. break;
  39. }
  40. len++;
  41. }
  42. return len;
  43. }
  44.  
  45. myUser.num = getJsonObjLength(myUser.stories);
  46. myUser.toDo = toDo;
  47. myUser.inProgress = inProgress;
  48. myUser.codeReview = codeReview;
  49. myUser.qaReview = qaReview;
  50. myUser.verified = verified;
  51. myUser.statusArr = [["To Do", toDo], ["In Progress", inProgress], ["Code Review", codeReview], ["QA Review", qaReview], ["Verified", verified]];
  52. });

三、引入指令

  到目前为止已经有了路由和页面,能够正常显示,但是这里有一个问题:

  当前的data.html包含了javascript代码和要显示的页面元素,这不符合MVC分离架构。我们需要将负责显示d3的业务逻辑放到它该存在的地方。

  当时我想到了指令。在页面中通过Attribute、Element、Class等任意一种形式定义一个指令,然后在指令完成需要的代码逻辑。

  首先是在data.html中声明一个指令,我们命名为d3chart:

  1. <div style="margin-top:100px">
  2. <div style="color:white" align="center">
  3. <h2>Stories Assigned to {{myUser.user.name}}</h2>
  4. <h2>Total Stories Num {{myUser.num}}</h2>
  5. <h2>To Do:{{myUser.toDo}}</h2>
  6. <h2>In Progress:{{myUser.inProgress}}</h2>
  7. <h2>Code Review:{{myUser.codeReview}}</h2>
  8. <h2>QA Review:{{myUser.qaReview}}</h2>
  9. <h2>Verified:{{myUser.verified}}</h2>
  10. </div>
  11.  
  12. <div style="color:white" align="center" status-arr="myUser.statusArr" stories="myUser.stories" d3chart></div>
  13. </div>

  

  其中status-arr="myUser.statusArr"是将保存在DataController.js中的数据传到这里的status-arr变量上,然后在D3Chart.js中注入这个变量以便directive能够使用这个传过来的变量值。

  备注:这里有一个命名坑

  如果你在这里的data.html页面中想通过一个属性名为statusArr的来接收这个myUser.statusArr,你会发现在D3Chart.js中根本接收不到这个statusArr。

  原因很简单,html不区分大小写,所以在这里你可以使用status-arr来代替你想要的statusArr,这也是一种规范,希望大家不要在这个小问题上栽跟头。

  下面我们就来实现这个d3chart指令,其中业务很简单,只是将原来放在data.hmtl中的javascript代码移到这里的指令里面

  D3Chart.js

  1. angular.module("Angello.Statistic")
  2. .directive("d3chart",function(){
  3. return{
  4. restrict:'EA',
  5.  
  6. link: function($scope, $element, $attrs){//link or controller
  7.  
  8. var width = 400;
  9. var height = 400;
  10. var dataset = $scope.myUser.statusArr;
  11. var svg = d3.select("body")
  12. .attr("align","center")// align svg to center
  13. .append("svg")
  14. .attr("width", width)
  15. .attr("height", height);
  16. var pie = d3.layout.pie();
  17. var piedata = pie(dataset);
  18. var outerRadius = 150; //外半径
  19. var innerRadius = 0; //内半径,为0则中间没有空白
  20. var arc = d3.svg.arc() //弧生成器
  21. .innerRadius(innerRadius) //设置内半径
  22. .outerRadius(outerRadius); //设置外半径
  23. var color = d3.scale.category10();
  24. var arcs = svg.selectAll("g")
  25. .data(piedata)
  26. .enter()
  27. .append("g")
  28. .attr("transform","translate("+ (width/2) +","+ (width/2) +")");
  29. arcs.append("path")
  30. .attr("fill",function(d,i){
  31. return color(i);
  32. })
  33. .attr("d",function(d){
  34. return arc(d);
  35. });
  36. arcs.append("text")
  37. .attr("transform",function(d){
  38. return "translate(" + arc.centroid(d) + ")";
  39. })
  40. .attr("text-anchor","middle")
  41. .text(function(d){
  42. return d.data;
  43. });
  44. console.log(dataset);
  45. console.log(piedata);
  46. }
  47. }
  48.  
  49. });

  

  OK,到此为止,我们就能够看到一个显示了statistic结果的D3饼状图了

  文章还没有结束,下面补充多讲一点,有关controller和directive之间的scope问题和通信问题。

四、controller和directive纠缠不清?

  只有一个controller存在的时候,我们思路很清晰,需要传的值,需要实现的逻辑,统统在这里。可是有了directive之后,从初学者来说,真的不是1+1=2这样看的见的难度。

  我们需要明确这两个家伙怎么联系,联系的方式有几种,又各有什么不同。

  主要的理论情景其实我早在《Angularjs入门新的1——directive和controller如何通信》就有介绍:

    1. 共享 scope :directive 中不设置 scope 属性

    2. 独立 scope :directive 中设置 scope : true

    3. 隔离 scope :directive 中设置 scope : { }

  之所以会牵扯到这个问题,是在注入statusArr时联想到的。

  当在directive中不添加scope声明的时候,默认是directive和controller共用scope,这会降低指令的重用性,也有可能会"弄脏"scope。此时这个statusArr是不需要注入的,只要在使用前加上$scope即可,代码如下:

  1. angular.module("Angello.Statistic")
  2. .directive("d3chart",function(){
  3. return{
  4. restrict:'EA',
  5. link: function($scope, $element, $attrs){//link or controller
  6. var width = 400;
  7. var height = 400;
  8. var dataset = $scope.myUser.statusArr;
  9. var svg = d3.select("body")
  10. .attr("align","center")// align svg to center
  11. .append("svg")
  12. .attr("width", width)
  13. .attr("height", height);
  14. var pie = d3.layout.pie();
  15. var piedata = pie(dataset);
  16. var outerRadius = 150; //外半径
  17. var innerRadius = 0; //内半径,为0则中间没有空白
  18. var arc = d3.svg.arc() //弧生成器
  19. .innerRadius(innerRadius) //设置内半径
  20. .outerRadius(outerRadius); //设置外半径
  21. var color = d3.scale.category10();
  22. var arcs = svg.selectAll("g")
  23. .data(piedata)
  24. .enter()
  25. .append("g")
  26. .attr("transform","translate("+ (width/2) +","+ (width/2) +")");
  27. arcs.append("path")
  28. .attr("fill",function(d,i){
  29. return color(i);
  30. })
  31. .attr("d",function(d){
  32. return arc(d);
  33. });
  34. arcs.append("text")
  35. .attr("transform",function(d){
  36. return "translate(" + arc.centroid(d) + ")";
  37. })
  38. .attr("text-anchor","middle")
  39. .text(function(d){
  40. return d.data;
  41. });
  42. console.log(dataset);
  43. console.log(piedata);
  44. }
  45. }
  46. });

  其中确实没有声明scope属性,所以这里在使用statusArr时只需要$scope.myUser.statusArr即可。

  另外一种是创建属于directive自己的scope,这时就没有了共享controller中scope的福利,但是也提高了自己的独立性,低耦合。代码如下:

  1. angular.module("Angello.Statistic")
  2. .directive("d3chart",function(){
  3. return{
  4. restrict:'EA',
  5. scope: {
  6. statusArr: "="
  7. },
  8. link: function($scope, $element, $attrs){//link or controller
  9. var width = 400;
  10. var height = 400;
  11. var dataset = $scope.statusArr;;
  12. var svg = d3.select("body")
  13. .attr("align","center")// align svg to center
  14. .append("svg")
  15. .attr("width", width)
  16. .attr("height", height);
  17. var pie = d3.layout.pie();
  18. var piedata = pie(dataset);
  19. var outerRadius = 150; //外半径
  20. var innerRadius = 0; //内半径,为0则中间没有空白
  21. var arc = d3.svg.arc() //弧生成器
  22. .innerRadius(innerRadius) //设置内半径
  23. .outerRadius(outerRadius); //设置外半径
  24. var color = d3.scale.category10();
  25. var arcs = svg.selectAll("g")
  26. .data(piedata)
  27. .enter()
  28. .append("g")
  29. .attr("transform","translate("+ (width/2) +","+ (width/2) +")");
  30. arcs.append("path")
  31. .attr("fill",function(d,i){
  32. return color(i);
  33. })
  34. .attr("d",function(d){
  35. return arc(d);
  36. });
  37. arcs.append("text")
  38. .attr("transform",function(d){
  39. return "translate(" + arc.centroid(d) + ")";
  40. })
  41. .attr("text-anchor","middle")
  42. .text(function(d){
  43. return d.data;
  44. });
  45. console.log(dataset);
  46. console.log(piedata);
  47. }
  48. }
  49. });

  这时候在scope将statusArr双向绑定到页面中的statusArr,所以这里要使用可以直接写成$scope.statusArr即可。

通过这个问题的解决,更加深刻的理解了不同scope的使用场景。directive和controller之间scope的关系。

  今天主要介绍的内容有:

  •   添加一个新的页面用于存放statistic出来的数据信息和图形信息;
  •   如何引入D3引擎;
  •   为什么要使用指令;
  •   我的代码逻辑中如何使用指令;
  •   html的命名规范坑;
  •   directive和controller如何通信以及它们的scope之间的关系

  下篇预告:bug hunting篇

  如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

  1.   

友情赞助

如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。

    1. 支付宝                          2. 微信

                      

AngularJS in Action读书笔记5(实战篇)——在directive中引入D3饼状图显示的更多相关文章

  1. AngularJS in Action读书笔记6(实战篇)——bug hunting

    这一系列文章感觉写的不好,思维跨度很大,原本是由于与<Angularjs in action>有种相见恨晚而激发要写点读后感之类的文章,但是在翻译或是阐述的时候还是会心有余而力不足,零零总 ...

  2. AngularJS in Action读书笔记4(实战篇)——创建Statistic模块

    个人感觉<Angularjs in action>这本书写的很好,很流畅,循序渐进,深入浅出,关键是结合了一个托管于Github上的实例讲解的,有代码可查,对于初学者应该是个不错的途径.( ...

  3. AngularJS in Action读书笔记1——扫平一揽子专业术语

    前(fei)言(hua): 数月前,以一个盲人摸象的姿态看了一些关于AngularJS的视频书籍,留下了我个人的一点或许是指点迷津或许是误人子弟的读后感.自以为已经达到熟悉ng的程度,但是因为刚入公司 ...

  4. AngularJS in Action读书笔记2——view和controller的那些事儿

    今天我们来818<angularjs in action>的第三章controller和view. 1.Big Picture概览图 View是angularjs编译html后呈现出来的, ...

  5. AngularJS in Action读书笔记3——走近Services

    试着想想这些问题:如果一个controller只关心自己所控制的view页面,那么对于整个application来说,你如何调用想要的function:如果controller从来都不会和其他cont ...

  6. AngularJS高级程序设计读书笔记 -- 大纲篇

    零. 初衷 现在 AngularJS 4 已经发布了, 楼主还停留在 1.x 的阶段, 深感自卑. 学习 AngularJS 的初衷是因为, 去年楼主开始尝试使用 Flask 开发自动化程序, 需要用 ...

  7. AngularJS高级程序设计读书笔记 -- 指令篇 之 内置指令

    1. 内置指令(10-12 章) AngularJS 内置超过 50 个内置指令, 包括 数据绑定,表单验证,模板生成,时间处理 和 HTML 操作. 指令暴露了 AngularJS 的核心功能, 如 ...

  8. AngularJS高级程序设计读书笔记 -- 服务篇

    服务是提供在整个应用程序中所使用的任何功能的单例对象. 单例 : 只用一个对象实例会被 AngularJS 创建出来, 并被程序需要服务的各个不同部分所共享. 1. 内置服务 一些关键方法也被 Ang ...

  9. AngularJS高级程序设计读书笔记 -- 过滤器篇

    一. 过滤器基础 过滤器用于在视图中格式化展现给用户的数据. 一旦定义过滤器之后, 就可在整个模块中全面应用, 也就意味着可以用来保证跨多个控制器和视图之间的数据展示的一致性. 过滤器将数据在被指令处 ...

随机推荐

  1. FastCgi 与 PHP-FPM

    - 如果现在请求的是 /index.php,根据配置文件,nginx 知道这个不.是静态文件,需要去找 PHP 解析器来处理,那么他会把这个请求简单处理后交给 PHP 解析器.Nginx 会传哪些数据 ...

  2. C#中的IEnumable与IEnumator接口的简单理解

    IEnumerable接口中的方法是返回IEnumator的对象,集合继承了IEnumerator接口才能实现Foreach方法实现遍历.集合类都继承IEnumable和IEnumerator接口,或 ...

  3. Android Studio 1.0.2项目实战——从一个APP的开发过程认识Android Studio

    Android Studio 1.0.1刚刚发布不久,谷歌紧接着发布了Android Studio 1.0.2版本,和1.0.0一样,是一个Bug修复版本.在上一篇Android Studio 1.0 ...

  4. Ubuntu下安装 jdk6

    Ubuntu下安装 jdk6 罗朝辉 (http://www.cnblogs.com/kesalin/) 本文遵循“署名-非商业用途-保持一致”创作公用协议   1,下载最新的 jdk6 版本,目前最 ...

  5. [ZigBee] 4、ZigBee基础实验——中断

    前言 上一篇介绍了CC2530的IO的基础知识,并用LED的控制来展示如何配置并控制GPIO的输出,用KEY状态的读取实验来展示如何读取GPIO的状态.从上一节的KEY状态读取的代码看出是采用轮训方式 ...

  6. [ACM_几何] The Deadly Olympic Returns!!! (空间相对运动之最短距离)

    http://acm.hust.edu.cn/vjudge/contest/view.action?cid=28235#problem/B 题目大意: 有两个同时再空间中匀速运动的导弹,告诉一个时间以 ...

  7. 使用aggregate在MongoDB中查找重复的数据记录

    我们知道,MongoDB属于文档型数据库,其存储的文档类型都是JSON对象.正是由于这一特性,我们在Node.js中会经常使用MongoDB进行数据的存取.但由于Node.js是异步执行的,这就导致我 ...

  8. java程序 启动时参数

      iEMP34:/opt/version/lktest/b030/jre/jre_linux/bin # ./java -classpath . SysInfo Exception in threa ...

  9. Node.js入门:Node.js&NPM的安装与配置

    Node.js安装与配置      Node.js已经诞生两年有余,由于一直处于快速开发中,过去的一些安装配置介绍多数针对0.4.x版本而言的,并非适合最新的0.6.x的版本情况了,对此,我们将在0. ...

  10. Atitit 面向对象  封装的实现原理

    Atitit 面向对象  封装的实现原理 1.1. 动态对象的模拟使用map+函数接口可以实现1 1.2. 在用结构体 + 函数指针 模拟 对象 1 1.3. This指针..1 1.4. " ...