做项目遇到一个需求,将具有层级关系的词语用树状图的形式展示它们之间的关系,像这样:
或者是这样:
上面的图片只是样例,跟我下面的代码里面用的数据不同
网上有很多这种数据可视化展示的js控件,我这里选择了D3.js。
首先在html页面需要包含D3的js文件,其次我们需要将数据构造成json格式,然后存入到一个d3.json文件
{ "name":"如何学习D3", "children": [ { "name":"预备知识" , "children": [ {"name":"HTML & CSS" }, {"name":"JavaScript" }, {"name":"DOM" }, {"name":"SVG" } ] }, { "name":"安装" , "children": [ { "name":"记事本软件", "children": [ {"name":"Notepad++"}, {"name":"EditPlus"}, {"name":"Sublime Text"} ] }, { "name":"服务器软件", "children": [ {"name":"Apache Http Server"}, {"name":"Tomcat"} ] }, {"name":"下载D3.js"} ] }, { "name":"入门", "children": [ { "name":"选择集", "children": [ {"name":"select"}, {"name":"selectAll"} ] }, { "name":"绑定数据", "children": [ {"name":"datum"}, {"name":"data"} ] }, {"name":"添加删除元素"}, { "name":"简单图形", "children": [ {"name":"柱形图"}, {"name":"折线图"}, {"name":"散点图"} ] }, {"name":"比例尺"}, {"name":"生成器"}, {"name":"过渡"} ] }, { "name":"进阶" , "children": [ { "name":"布局的应用", "children": [ {"name":"饼状图"}, {"name":"树状图"}, {"name":"矩阵树图"} ] }, {"name":"地图"} ] } ] }
然后开始编写JavaScript代码:
横向树状图:
var width = 700, height = 700; var cluster = d3.layout.cluster() .size([width, height - 200]); var diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x]; }); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height) .append("g") .attr("transform", "translate(40,0)"); d3.json("d3.json", function(error, root) { var nodes = cluster.nodes(root); var links = cluster.links(nodes); console.log(nodes); console.log(links); var link = svg.selectAll(".link") .data(links) .enter() .append("path") .attr("class", "link") .attr("d", diagonal); var node = svg.selectAll(".node") .data(nodes) .enter() .append("g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) node.append("circle") .attr("r", 4.5); node.append("text") .attr("dx", function(d) { return d.children ? -8 : 8; }) .attr("dy", 3) .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) .text(function(d) { return d.name; }); });
圆形树状图(这个需要用到投影,稍微麻烦一点,不详细解释了):
//图像区域大小 var R = 600; //定义一个Tree对象,定义旋转角度和最大半径 var tree = d3.layout.tree() .size([360,R/2-120]) .separation(function(a,b) { return a.parent == b.parent ? 1 : 2;}); //定义布局方向 var diagonal = d3.svg.diagonal() .projection(function(d) { var r = d.y, a = (d.x-90) / 180 * Math.PI; return [r * Math.cos(a), r * Math.sin(a)]; }); //新建画布,移动到圆心位置 var svg = d3.select("#showTree").append("svg") .attr("width", R) .attr("height", R) .append("g") .attr("transform", function(d){ return "translate("+R/2+"," + R/2 + ")";}); //根据JSON数据生成树 d3.json("d3.json", function(error, data) { //根据数据生成nodes集合 var nodes = tree.nodes(data); //获取node集合的关系集合 var links = tree.links(nodes); //为关系集合设置贝塞尔曲线连接 var link=svg.selectAll(".link") .data(links) .enter() .append("path") .attr("class", "link") .attr("d",diagonal); //根据node集合生成节点 var node = svg.selectAll(".node") .data(nodes) .enter() .append("g") .attr("class", "node") .attr("transform",function(d){return "rotate(" + (d.x-90) + ")translate(" + d.y + ")"; }); //为节点添加圆形标记,如果有子节点为红色,否则绿色 node.append("circle") .attr("fill",function(d){return d.children==null?"#0F0":"#F00";}) .attr("r", 5); //为节点添加说明文字 node.append("text") .attr("dy", ".4em") .text(function(d){return d.name;}) .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; }); });
D3的d3.json(url,callback)方法可以读取json文件,然后构造树状图。
如果不想把数据写入json文件,直接在后台构造好json数据结构然后传到前台要怎么弄?
其实d3.json(url,callback)方法的url是发起一个get请求,返回一个json字符串,然后进入callback进行处理,本质上跟ajax差不多
所以当我们不想读取文件时,修改一下d3.json(url,callback)的url参数,发起一个get请求到控制器,然后你在控制器里面构造相应的json数据结构,然后返回过来就可以了
但是这里的url不能直接带中文参数,比如:
url = "json.html?word=关键词";
这样的请求发到后台,后台接收的是这样的一段乱码字符串:
å ³é®è¯
那有人说先将这个中文做一次编码,然后在传到后台不就行了?
确实,我们一般在js里面如果要传中文参数到后台都会先进行编码然后再传的,所以我当时也是这么想的,心想这下应该不会出问题了吧,然并卵,后台接收然后解码得到的依然是那串乱码,然后没办法,我只能进d3.js的文件去查看源码,奈何才疏学浅,压根看不懂,也没找到它哪里对这个url做了什么编码或解码操作。后来想到以前见到过在前台进行多次编码然后再传到后台的,我也尝试了一下在js里面进行了两次编码,然后再传到后台,到后台接收然后解码一次,发现能够得到正确的中文参数
第一行是后台接收的参数,第二行是对参数解码一次得到的结果。
问题是解决了,但是我还是不知道这是为什么,只注意到d3.json这个方法里面发送的是get请求,后来查资料还有问其他人才知道在浏览器地址栏里,浏览器认为%是个转义字符,浏览器会把%与%之间的编码,两位两位取出后进行解码,然后再传递给处理页面,然后由处理页面进行再次解码,而get请求的中文参数就是显示在浏览器地址栏,所以在js里面参数只进行一次编码的话,参数经过浏览器的解码,传到后台的就是没编码的中文,这个当然就会变成乱码,所以在js里面对中文做两次编码然后通过get请求传到后台,后台只需做一次解码就能得到正确的中文参数