D3.js系列——布局:弦图和集群图/树状图

一、弦图

1、弦图是什么

  弦图(Chord),主要用于表示两个节点之间的联系的图表。两点之间的连线,表示谁和谁具有联系。

2、数据

  初始数据为:

var city_name = [ "北京" , "上海" , "广州" , "深圳" , "香港"  ];
var population = [
          [ 1000,  3045  , 4567 , 1234 , 3714 ],
          [ 3214,  2000  , 2060 , 124  , 3234 ],
          [ 8761,  6545  , 3000 , 8045 , 647  ],
          [ 3211,  1067  , 3214 , 4000  , 1006 ],
          [ 2146,  1034  , 6745 , 4764  , 5000 ]
        ];

  数据是一些城市名和一些数字,这些数字表示城市人口的来源。其意思如下:

  北京 上海
北京 1000 3045
上海 3214 2000

  左边第一列是被统计人口的城市,上边第一行是被统计的来源城市,即:

  北京市的人口有 1000 个人来自本地,有 3045 人是来自上海的移民,总人口为 1000 + 3045。

  上海市的人口有 2000 个人来自本地,有 3214 人是来自北京的移民,总人口为 3214 + 2000。

  好了!!!对于这样一组数据,怎么进行可视化。

3、布局(数据转换)

  弦图的布局如下:

 var chord_layout = d3.layout.chord()
      .padding(0.03) //节点之间的间隔
      .sortSubgroups(d3.descending) //排序
      .matrix(population); //输入矩阵

  然后,应用此布局转换数据。

var groups = chord_layout.groups();
var chords = chord_layout.chords();
 
console.log( groups );
console.log( chords );

  population 经过转换后,实际上分成了两部分:groups 和 chords。前者是节点,后者是连线,也就是。chords 就是上图中的连线。chords 里面又分为 source 和 target ,也就是连线的两端。

  在控制台输出一下节点和连线,看看得到了怎样的数据。

  节点:

  连线(弦):

4、绘制节点

   先定义相关变量,很熟悉了

 var width = 600;
 var height = 600;
 var innerRadius = width/2 * 0.7;
 var outerRadius = innerRadius * 1.1;
 
 var color20 = d3.scale.category20();
 
 var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(" + width/2 + "," + height/2 + ")")

  绘制节点(即分组,有多少个城市画多少个弧形),及绘制城市名称

var outer_arc = d3.svg.arc()   //获取圆弧生成器的路径值
    .innerRadius(innerRadius)
    .outerRadius(outerRadius);
 
var g_outer = svg.append("g"); //添加分组
 
g_outer.selectAll("path")
    .data(groups)
    .enter()
    .append("path")
    .style("fill", function(d) { return color20(d.index); })
    .style("stroke", function(d) { return color20(d.index); })
    .attr("d", outer_arc );  //绑定路径的属性值
 
g_outer.selectAll("text")
    .data(groups)
    .enter()
    .append("text")
    .each( function(d,i) { 
       d.angle = (d.startAngle + d.endAngle) / 2; 
       d.name = city_name[i];
    })
    .attr("dy",".35em")
    .attr("transform", function(d){
       return "rotate(" + ( d.angle * 180 / Math.PI ) + ")" +
       "translate(0,"+ -1.0*(outerRadius+10) +")" +
       ( ( d.angle > Math.PI*3/4 && d.angle < Math.PI*5/4 ) ? "rotate(180)" : "");
     })
    .text(function(d){
       return d.name;
    });

  节点位于弦图的外部。节点数组 groups 的每一项,都有起始角度和终止角度,因此节点其实是用弧形来表示的,这与饼状图类似。

  然后就是节点的文字(即城市名称),有两个地方要特别注意。

  each():表示对任何一个绑定数据的元素,都执行后面的无名函数 function(d,i) ,函数体里做两件事:

  • 计算起始角度和终止角度的平均值,赋值给 d.angle 。
  • 将 city_name[i] 城市名称赋值给 d.name 。

  transform 的参数:用 translate 进行坐标变换时,要注意顺序: rotate -> translate(先旋转再平移)。 此外,

( ( d.angle > Math.PI*3/4 && d.angle < Math.PI*5/4 ) ? "rotate(180)" : "")

  意思是,当角度在 135° 到 225° 之间时,旋转 180°。不这么做的话,下方的文字是倒的。

5、绘制连线(弦)

  绘制连线(即所有城市人口的来源,即有 5 * 5 = 25 条弧)

var inner_chord = d3.svg.chord()
    .radius(innerRadius);
 
svg.append("g")
    .attr("class", "chord")
    .selectAll("path")
    .data(chords)
    .enter()
    .append("path")
    .attr("d", inner_chord )  //path值
    .style("fill", function(d) { return color20(d.source.index); })
    .style("opacity", 1)
    .on("mouseover",function(d,i){
        d3.select(this)
          .style("fill","yellow");
    })
    .on("mouseout",function(d,i) { 
        d3.select(this)
          .transition()
          .duration(1000)
          .style("fill",color20(d.source.index));
    });

  SVG 中没有现成的弦元素(例如圆有 <circle>,但是弦却没有 <chord>),需要用路径元素 <path> 来制作。至于路径值是什么呢?我们不需要手动计算,D3 提供了 d3.svg.chord() ,只需要将弦的对象传递给它,即可得到路径值

  上面还有几句关于交互式操作的代码: mouseover 和 mouseout 。

二、集群图

  集群图,是一种用于表示包含与被包含关系的图表。

1、数据

  初始数据先写在一个 JSON 文件中,再用 D3 来读取。现有数据如下:

{
"name":"中国",
"children":
[
    { 
      "name":"浙江" , 
      "children":
      [
            {"name":"杭州" },
            {"name":"宁波" },
            {"name":"温州" },
            {"name":"绍兴" }
      ] 
    },
    
    { 
        "name":"广西" , 
        "children":
        [
            {"name":"桂林"},
            {"name":"南宁"},
            {"name":"柳州"},
            {"name":"防城港"}
        ] 
    },
    
    { 
        "name":"黑龙江",
        "children":
        [
            {"name":"哈尔滨"},
            {"name":"齐齐哈尔"},
            {"name":"牡丹江"},
            {"name":"大庆"}
        ] 
    },
    
    { 
        "name":"新疆" , 
        "children":
        [
            {"name":"乌鲁木齐"},
            {"name":"克拉玛依"},
            {"name":"吐鲁番"},
            {"name":"哈密"}
        ]
    }
]
}

  这段数据表示:“中国 – 省份名 – 城市名”的包含于被包含关系。

2、布局(数据转换)

  定义一个集群图布局:

var cluster = d3.layout.cluster()
                 .size([width, height - 200]);

  布局保存在变量 cluster 中,变量 cluster 可用于转换数据。

  size() 设定尺寸,即转换后的各节点的坐标在哪一个范围内。

  接下来,转换数据:

d3.json("city.json", function(error, root) {
  var nodes = cluster.nodes(root);
  var links = cluster.links(nodes);
  
  console.log(nodes);
  console.log(links);
}

  d3.json() 是用来读取 JSON 文件的。要注意,d3.json() 不能读取本地文件。例如,将 html 文件与 json 文件放到本地同一目录,打开 html 文件是不能顺利读取的。需要搭建一个网络服务器来使用它,可用 Apache 搭建一个简单的服务器。否则,浏览器(Chrome)的控制台中,会出现以下错误:XMLHttpRequest cannot load file:///D:/*******/city.json. Cross origin requests are only supported for HTTP.

  经过测试,Firefox 可以直接读取本地文件,无需搭服务器,其他大多数浏览器不行。建议搭建服务器进行测试,这是正确的做法。

  d3.json() 函数后面跟一个无名函数 function(error) ,参数 root 是读入的数据。后两行代码调用 cluster 转换数据,保存到变量 nodes 和 links 中。然后输出转换后的数据,结果如下图所示:

  转换后的顶点数据(nodes):

  转换后的连接线数据(links):

  nodes 中有各个节点的子节点(children)、深度(depth)、名称(name)、位置(x,y)信息,其中名称(name)是 json 文件中就有的属性。

  links 中有连线两端( source , target )的节点信息。

3、绘制

  D3 已经基本上为我们准备好了绘制的函数:d3.svg.diagonal() 。这是一个对角线生成器,只需要输入两个顶点坐标,即可生成一条贝塞尔曲线

  创建一个对角线生成器:

var diagonal = d3.svg.diagonal()
    .projection(function(d) { return [d.y, d.x]; });

  projection() 是一个点变换器,默认是 [ d.x , d.y ],即保持原坐标不变,如果写成 [ d.y , d.x ] ,即是说对任意输入的顶点,都交换 x 和 y 坐标

  绘制连线时,使用方法如下:

var link = svg.selectAll(".link")
      .data(links)
      .enter()
      .append("path")
      .attr("class", "link")
      .attr("d", diagonal);   //使用对角线生成器path值

  绘制节点时,还是用 <svg> 中的 <circle> 来绘制。

  //获取所有节点元素
  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; });

三、树状图

  树状图( Tree )用于表示层级、上下级、包含与被包含关系,其布局的用法与集群图几乎完全相同。

原文地址:https://www.cnblogs.com/goloving/p/8612061.html