AngularJS in Action读书笔记5(实战篇)——在directive中引入D3饼状图显示

前言:

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

  这是前几天在查资料无意间看到的一位园友的签名,看完后又读了两遍,觉得很有味道。后来一寻根究底才知这是出资大诗人汪国真之口,出处《她》。且抛开上下文,单从这短短几句,正恰如其分的折射出有一群人,他们穿着不那么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本身就是负责直观显示的视觉类产品,所以首先需要跑出一个效果出来。于是找到了一段代码

<html>
<head>
	<meta charset="utf-8">
	<title>做一个简单的图表</title>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>

	var width = 300;	//画布的宽度
	var height = 300;	//画布的高度

	var svg = d3.select("body")				//选择文档中的body元素
				.append("svg")				//添加一个svg元素
				.attr("width", width)		//设定宽度
				.attr("height", height);	//设定高度

	var dataset = [ 250 , 210 , 170 , 130 , 90 ];

	var rectHeight = 25;	//每个矩形所占的像素高度(包括空白)

	svg.selectAll("rect")
		  .data(dataset)
		  .enter()
		  .append("rect")
		  .attr("x",20)
		  .attr("y",function(d,i){
				return i * rectHeight;
		  })
		  .attr("width",function(d){
		   		return d;
		  })
		  .attr("height",rectHeight-2)
		  .attr("fill","steelblue");

</script>  

</body>
</html>

  

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

{ 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

.when(‘/statistic/:userId‘, {
            templateUrl: ‘src/angello/statistic/tmpl/data.html‘,
            controller: ‘DataCtrl‘,
            controllerAs: ‘myUser‘,
            requiresLogin: true,
            resolve: {
                user: function ($route, $routeParams, UsersModel) {
                    var userId = $route.current.params[‘userId‘]
                               ? $route.current.params[‘userId‘]
                               : $routeParams[‘userId‘];
                    return UsersModel.fetch(userId);
                },
                stories: function ($rootScope, StoriesModel) {
                    return StoriesModel.all();
                }
            }
        })

  

  data.html

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

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

angular.module(‘Angello.Statistic‘)
    .controller(‘DataCtrl‘,
        function ($routeParams, user, stories, $log) {
            var myUser = this;

            myUser.userId = $routeParams[‘userId‘];
            myUser.user = user.data;

            myUser.getAssignedStories = function (userId, stories) {
                var assignedStories = {};

                Object.keys(stories, function(key, value) {
                    if (value.assignee == userId) assignedStories[key] = stories[key];
                });

                return assignedStories;
            };

            myUser.stories = myUser.getAssignedStories(myUser.userId, stories);
            var len=0,toDo=0,inProgress=0,codeReview=0,qaReview=0,verified=0;
            function getJsonObjLength(jsonObj) {
                for (var item in jsonObj) {
                	switch (jsonObj[item].status){
	                	case "To Do":
	                		toDo++;
	                		break;
	                	case "In Progress":
	                		inProgress++;
	                		break;
	                	case "Code Review":
	                		codeReview++;
	                		break;
	                	case "QA Review":
	                		qaReview++;
	                		break;
	                	case "Verified":
	                		verified++;
	                		break;
                	}
                	len++;
                }
                return len;
            }

			myUser.num = getJsonObjLength(myUser.stories);
            myUser.toDo = toDo;
            myUser.inProgress = inProgress;
            myUser.codeReview = codeReview;
            myUser.qaReview = qaReview;
            myUser.verified = verified;
            myUser.statusArr = [["To Do", toDo], ["In Progress", inProgress], ["Code Review", codeReview], ["QA Review", qaReview], ["Verified", verified]];
        });

三、引入指令

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

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

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

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

<div style="margin-top:100px">
<div style="color:white" align="center">
    <h2>Stories Assigned to {{myUser.user.name}}</h2>
    <h2>Total Stories Num {{myUser.num}}</h2>
    <h2>To Do:{{myUser.toDo}}</h2>
    <h2>In Progress:{{myUser.inProgress}}</h2>
    <h2>Code Review:{{myUser.codeReview}}</h2>
    <h2>QA Review:{{myUser.qaReview}}</h2>
    <h2>Verified:{{myUser.verified}}</h2>
</div>

<div style="color:white" align="center" status-arr="myUser.statusArr" stories="myUser.stories" d3chart></div>
</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

angular.module("Angello.Statistic")
.directive("d3chart",function(){
                return{
                 restrict:‘EA‘,

                link: function($scope, $element, $attrs){//link or controller

                	var width = 400;
    		 		var height = 400;
    		 		var dataset = $scope.myUser.statusArr;
    		 		var svg = d3.select("body")
    		 					.attr("align","center")// align svg to center
    		 					.append("svg")
    		 					.attr("width", width)
    		 					.attr("height", height);
    		 		var pie = d3.layout.pie();
    		 		var piedata = pie(dataset);
    		 		var outerRadius = 150;	//外半径
    		 		var innerRadius = 0;	//内半径,为0则中间没有空白
    		 		var arc = d3.svg.arc()	//弧生成器
    		 					.innerRadius(innerRadius)	//设置内半径
    		 					.outerRadius(outerRadius);	//设置外半径
    		 		var color = d3.scale.category10();
    		 		var arcs = svg.selectAll("g")
    		 					  .data(piedata)
    		 					  .enter()
    		 					  .append("g")
    		 					  .attr("transform","translate("+ (width/2) +","+ (width/2) +")");
    		 		arcs.append("path")
    		 			.attr("fill",function(d,i){
    		 				return color(i);
    		 			})
    		 			.attr("d",function(d){
    		 				return arc(d);
    		 			});
    		 		arcs.append("text")
    		 			.attr("transform",function(d){
    		 				return "translate(" + arc.centroid(d) + ")";
    		 			})
    		 			.attr("text-anchor","middle")
    		 			.text(function(d){
    		 				return d.data;
    		 			});
    		 		console.log(dataset);
    		 		console.log(piedata);
                    }
            	}

            });

  

  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即可,代码如下:

angular.module("Angello.Statistic")
.directive("d3chart",function(){
                return{
                 restrict:‘EA‘,
                link: function($scope, $element, $attrs){//link or controller
                	var width = 400;
    		 		var height = 400;
    		 		var dataset = $scope.myUser.statusArr;
    		 		var svg = d3.select("body")
    		 					.attr("align","center")// align svg to center
    		 					.append("svg")
    		 					.attr("width", width)
    		 					.attr("height", height);
    		 		var pie = d3.layout.pie();
    		 		var piedata = pie(dataset);
    		 		var outerRadius = 150;	//外半径
    		 		var innerRadius = 0;	//内半径,为0则中间没有空白
    		 		var arc = d3.svg.arc()	//弧生成器
    		 					.innerRadius(innerRadius)	//设置内半径
    		 					.outerRadius(outerRadius);	//设置外半径
    		 		var color = d3.scale.category10();
    		 		var arcs = svg.selectAll("g")
    		 					  .data(piedata)
    		 					  .enter()
    		 					  .append("g")
    		 					  .attr("transform","translate("+ (width/2) +","+ (width/2) +")");
    		 		arcs.append("path")
    		 			.attr("fill",function(d,i){
    		 				return color(i);
    		 			})
    		 			.attr("d",function(d){
    		 				return arc(d);
    		 			});
    		 		arcs.append("text")
    		 			.attr("transform",function(d){
    		 				return "translate(" + arc.centroid(d) + ")";
    		 			})
    		 			.attr("text-anchor","middle")
    		 			.text(function(d){
    		 				return d.data;
    		 			});
    		 		console.log(dataset);
    		 		console.log(piedata);
                    }
            	}
            });

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

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

angular.module("Angello.Statistic")
.directive("d3chart",function(){
                return{
                 restrict:‘EA‘,
				 scope: {
                	statusArr: "="
            	},
                link: function($scope, $element, $attrs){//link or controller
                	var width = 400;
    		 		var height = 400;
    		 		var dataset = $scope.statusArr;;
    		 		var svg = d3.select("body")
    		 					.attr("align","center")// align svg to center
    		 					.append("svg")
    		 					.attr("width", width)
    		 					.attr("height", height);
    		 		var pie = d3.layout.pie();
    		 		var piedata = pie(dataset);
    		 		var outerRadius = 150;	//外半径
    		 		var innerRadius = 0;	//内半径,为0则中间没有空白
    		 		var arc = d3.svg.arc()	//弧生成器
    		 					.innerRadius(innerRadius)	//设置内半径
    		 					.outerRadius(outerRadius);	//设置外半径
    		 		var color = d3.scale.category10();
    		 		var arcs = svg.selectAll("g")
    		 					  .data(piedata)
    		 					  .enter()
    		 					  .append("g")
    		 					  .attr("transform","translate("+ (width/2) +","+ (width/2) +")");
    		 		arcs.append("path")
    		 			.attr("fill",function(d,i){
    		 				return color(i);
    		 			})
    		 			.attr("d",function(d){
    		 				return arc(d);
    		 			});
    		 		arcs.append("text")
    		 			.attr("transform",function(d){
    		 				return "translate(" + arc.centroid(d) + ")";
    		 			})
    		 			.attr("text-anchor","middle")
    		 			.text(function(d){
    		 				return d.data;
    		 			});
    		 		console.log(dataset);
    		 		console.log(piedata);
                    }
            	}
            });

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

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

  今天主要介绍的内容有:

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

  下篇预告:bug hunting篇

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

  
时间: 2024-10-24 13:30:53

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

《人月神话》读书笔记 第3篇

<人月神话>读书笔记 第3篇 第15章:另一面 第16章:没有银弹 第17章:再论“没有银弹” 第18章:<人月神话>:是与非? 阅读<人月神话>马上就要接近尾声了,发现后面讲的内容越来越专业,但是对于我们正在进行的团队合作启发很大.前几天老师在课堂上给他们看了他统计整理的个人与每个团队第一个冲刺阶段的进度表格,看到有些严格按照要求每天有进度,有些则相反,也可能是完成了但是没有及时汇报进程.那“项目怎么会被延迟了?……延迟的时间是一天一天积累下来的.”目前我们做的都是一

《大道至简》读书笔记 第1篇

<大道至简>读书笔记 第1篇 第1章:编程的精义 第2章:是懒人造就了方法 第3章:团队缺乏的不只是管理 第4章:流于形式的沟通 终于开始了第三本书的阅读,因为时间关系选择一本稍微薄的<大道至简>.书中阐述的道理是通过与我们联系紧密的例子,和寓言故事,还有漫画……形式很新颖,更加的明白展示作者要表达的观点. 开篇通过分析愚公移山的经典故事,作者抛出了第一个重要的观点:“编程的第一要务就是先把事情分析清楚,把先后的逻辑关系和依赖关系搞清楚,然后再去写代码实现.”所以在我们刚学C/C+

《梦断代码》读书笔记 第2篇

<梦断代码>读书笔记 第2篇 第7章:细节视图 第8章:白板上的即时贴 第9章:方法 第10章:工程师和艺术家 第11章:通往狗食版之路 “读书时我喜欢上数学课——这类东西之所以能吸引我,是因为我知道自己做对了.”在书里面我找到了自己刚接触计算机时不喜欢编程可能的原因:我不知道自己是否做对了,也没有很顺利就做对的时候,慢慢地导致排斥编程,特别是一点思路也没有,或者知道这个程序将会花费我很多时间时情绪很糟糕,所以它不能吸引我.到现在,每次找到程序中不对的地方再修改正确,即使消耗很多时间,也会有一

《大道至简》读书笔记 第3篇

<大道至简>读书笔记 第3篇 第9章:现实中的软件工程 第10章:具体工程 第11章:是思考还是思想 “语言只是工具,成天讨论一门语言好坏的人,是可悲的.”然而,能够得出这样一个结论之前的人,恰恰之前正在经历这样一个阶段,好比说自己.在前几天向学弟学妹们软件拉票时,我们说:“C#比别的语言好学,容易掌握……”书读到这里,感觉自己确实幼稚了.因为任何语言都是可以学习的,他们只是工具,或者说是知识,真正转化为生产力的,还是需要用编程语言来实现系统.完成系统需求,让客户满意,并且每种语言都有自己的强

《大道至简》读书笔记 第2篇

<大道至简>读书笔记 第2篇 第5章:失败的过程也是过程 第6章:谁是解结的人 第7章:从编程到工程 第8章:你看得到工具的本质吗 <汉书>中说“言人三为众”,这里的“众”字是要理解成为:一个群体,又或者说是一个团队.很巧,我们三人行团队就是由三个人组成,莫名的对自己的团队有了很大的信心.书读到了一大半,软件工程经历两个冲刺阶段,我们一起合作的项目也进行了一大段了,期间确实学到了不少东西,这都是在一个过程中的积累.所以说,“过程伴随工程而出现.”过程解决的问题是工程中角色之间的关系

《人月神话》读书笔记 第1篇

<人月神话>读书笔记 第1篇 第1章:焦油坑 第2章:人月神话 第3章:外科手术队伍 第4章:贵族专制.民主政治和系统设计 第5章:画蛇添足 第6章:贯彻执行 第7章:为什么巴比伦塔会失败 第8章:胸有成竹 继<梦断代码>之后,我又选了一本老师推荐的关于软件工程的书——<人月神话>,这本书读起来相对<梦断代码>就轻松多了,可能是翻译得较为通俗,并且每章前都有个寓言或者名句作为引子.并且举了相似的例子来说明,同样也列出了对立的情况来证实一些道理. 开篇书中提到

《梦断代码》读书笔记 第0篇

<梦断代码>读书笔记 第0篇 第0章:软件时间 第1章:死定了 第2章:Agenda之魂 从老师布置这个作业之后,我便按照自己的计划开始阅读识字以来的第一本关于软件工程的小说——<梦断代码>,周一至周五每天睡前读几页. 首先,第一遍从第0章至第1章看完,我愣是不知道书上到底在说些什么,感觉这小说跟教科书一样好催眠,说实话,每次还没看多少行就困得不行了.所以,我看了第二遍(而且还寻思着再看不懂也不看第三遍了),果然,我还是没有与作者产生共鸣.不过,在再次阅读的过程中我勾画了一些给自己

Hadoop读书笔记(十四)MapReduce中TopK算法(Top100算法)

Hadoop读书笔记系列文章:http://blog.csdn.net/caicongyang/article/category/2166855 (系列文章会逐步修整完成,添加数据文件格式预计相关注释) 1.说明: 从给定的文件中的找到最大的100个值,给定的数据文件格式如下: 533 16565 17800 2929 11374 9826 6852 20679 18224 21222 8227 5336 912 29525 3382 2100 10673 12284 31634 27405 1

Entity Framework 4 in Action读书笔记——第一章:数据访问重载:Entity Fram

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 写在之前的话 在深入研究实体框架的细节之前,我们先讨论从传统的DataSet方法转换到基于对象的方法实现数据访问所带来的便利,以及这两种方法不同的工作方式是怎样导致采用像Entity Framework这样的O/RM工具. 使用Dataset和