D3图表绘制

image

本节内容将描述饼状图、力导向图、弦图、集群图、树状图、打包图、分区图、圆形分区图、直方图、捆图、堆栈图、矩阵树图、地图的绘制过程,参考D3.js入门系列

温馨提示:对于有D3基础的人,本节内容能够帮助其快速掌握各图表的绘制。若没有掌握基础知识,不建议直接学习本节内容。

对图表绘制的重点内容进行了总结,下述图表绘制步骤相似,总结如下:

1.使用布局,转换数据格式
2.如需绘制复杂path,需要创建对应的路径生成器
3.依次绘制各个元素,如需绘制path可能需要调用路径生成器

布局内容总结:

D3总共提供了12个布局:饼状图(Pie)、力导向图(Force)、弦图(Chord)、树状图(Tree)、集群图(Cluster)、捆图(Bundle)、打包图(Pack)、直方图(Histogram)、分区图(Partition)、堆栈图(Stack)、矩阵树图(Treemap)、层级图(Hierarchy)。

12 个布局中,层级图(Hierarchy)不能直接使用。集群图、打包图、分区图、树状图、矩阵树图是由层级图扩展来的。如此一来,能够使用的布局是11个(有5个是由层级图扩展而来)。这些布局的作用都是将某种数据转换成另一种数据,而转换后的数据是利于可视化的。

饼状图

image

1.数据格式

var dataset = [ 30 , 10 , 43 , 55 , 13 ];

2.使用pie布局,转换数据

var pie = d3.layout.pie();
var piedata = pie(data);

转换后的数据:

data、startAngle、endAngle、padAngle、value

3.绘制

弧生成器计算路径(svg的path)

var arc = d3.svg.arc()  //弧生成器
    .innerRadius(innerRadius)   //设置内半径
    .outerRadius(outerRadius);  //设置外半径

绘制路径path,需要调用弧生成器

.attr("d", function(d){
    return arc(d);   //调用弧生成器,得到路径值
})

或者

.attr("d", arc)

绘制文本text,计算路径中心位置,放入文本值

.attr("transform",function(d){
    return "translate(" + arc.centroid(d) + ")";
})

力导向图

image

1.数据格式

var nodes = [ { name: "桂林" }, { name: "广州" },
    { name: "厦门" }, { name: "杭州" },
    { name: "上海" }, { name: "青岛" },
    { name: "天津" } ];

var edges = [ { source : 0 , target: 1 } , { source : 0 , target: 2 } ,
    { source : 0 , target: 3 } , { source : 1 , target: 4 } ,
    { source : 1 , target: 5 } , { source : 1 , target: 6 } ];

2.force布局,转换数据

var force = d3.layout.force()
      .nodes(nodes) //指定节点数组
      .links(edges) //指定连线数组
      .size([width,height]) //指定作用域范围
      .linkDistance(150) //指定连线长度
      .charge([-400]); //相互之间的作用

转换后的数据:

节点:index(节点的索引号)、px、py(节点上一时刻的坐标)、x、y(现在的坐标)、weight(节点权重)、name

连线:source、target

然后,使力学作用生效:

force.start();    //开始作用

3.绘制

  • line,连线
  • circle,节点
  • text,文字

绘制节点时,使节点拖动

.call(force.drag);  //使得节点能够拖动

力导向图布局force有一个事件tick,每进行到一个时刻,都要调用它,更新的内容就写在它的监听器里就好。

force.on("tick", function(){ //对于每一个时间间隔
    //更新连线坐标
    svg_edges.attr("x1",function(d){ return d.source.x; })
        .attr("y1",function(d){ return d.source.y; })
        .attr("x2",function(d){ return d.target.x; })
        .attr("y2",function(d){ return d.target.y; });

    //更新节点坐标
    svg_nodes.attr("cx",function(d){ return d.x; })
        .attr("cy",function(d){ return d.y; });

    //更新文字坐标
    svg_texts.attr("x", function(d){ return d.x; })
       .attr("y", function(d){ return d.y; });
 });

弦图

image

1.数据格式

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 ]
];

2.chord布局

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

数据转换

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

转换后的数据:

节点:angle、startAngle、endAngle、index、name、value

连线: source、target

3.绘制

首先绘制外部圆环,使用arc弧生成器

var outer_arc = d3.svg.arc()
    .innerRadius(innerRadius)
    .outerRadius(outerRadius);

绘制path,使用数据为groups

.attr("d", outer_arc)   //调用arc路径生成器

绘制text,使用数据为groups。首先旋转适当角度,然后向上移动外半径个长度,135-225度范围的文字倒置

.each( function(d,i) { 
   d.angle = (d.startAngle + d.endAngle) / 2; 
   d.name = city_name[i];
})
.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)" : "");
})

然后,绘制弦,使用chord生成器生成路径

var inner_chord = d3.svg.chord()
    .radius(innerRadius);

绘制path,使用数据为chords

.attr("d", inner_chord) //调用chord路径生成器

集群图

image

1.数据格式

{
    "name":"中国",
    "children":
    [
        { 
          "name":"浙江" , 
          "children":
          [
                {"name":"杭州" },
                {"name":"宁波" },
                {"name":"温州" },
                {"name":"绍兴" }
          ] 
        },
        { 
            "name":"广西" , 
            "children":
            [
                {"name":"桂林"},
                {"name":"南宁"},
                {"name":"柳州"},
                {"name":"防城港"}
            ] 
        },
        ......
    ]
}

2.定义集群图布局,转换数据

var cluster = d3.layout.cluster()
    .size([width, height - 200]);   //设定尺寸

{% endhighlight %}

使用数据

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

转换后的数据:

节点:children(Array)、depth、name、x、y

连线:source、target

3.绘制

首先,创建对角线生成器

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

绘制连线,使用生成器

.attr("d", diagonal)   //使用对角线生成器

然后绘制节点circle。

树状图

和集群图写法基本相同,使用布局不同。

var tree = d3.layout.tree()
    .size([width, height-200])
    .separation(function(a, b) { return (a.parent == b.parent ? 1 : 2); });

转换后的数据与集群图相同。

打包图

image

数据格式与树状图相同,布局如下:

var pack = d3.layout.pack()
    .size([ width, height ])
    .radius(20);

数据转换写法也类似。

转换后的数据:

节点:children、depth、name、r(半径)、value、x、y(圆心位置)

连线:source、target

然后分别绘制circle和text。

分区图

image

数据类型与集群图、树状图、打包图相同。用于表示包含与被包含关系的。布局如下:

var partition = d3.layout.partition()
    .sort(null) //sort设定内部的顶点的排序函数,null表示不排序
    .size([width,height])   //size设定转换后图形的范围
    .value(function(d) { return 1; });  //value设定表示分区大小的值

value设定表示分区大小的值。这里的意思是:如果数据文件中用size值表示结点大小,那么这里可写成return d.size。

数据转换写法也类似。(nodes、links)

转换后的数据:

节点:children、depth、dx(宽度)、dy(高度)、name、value、x、y(坐标)

连线:source、target

然后绘制rect和text。rect的width、height属性分别对应数据属性dx、dy。

圆形分区图

image

1.数据格式

与前面相同。

2.布局

var partition = d3.layout.partition()
    .sort(null)
    .size([2 * Math.PI, radius * radius])  
    .value(function(d) { return 1; });

数据转换写法也类似。(nodes、links)

转换后的数据:

节点:children、depth、dx(角度)、dy(向外宽度)、name、value、x(绕圆心方向的起始位置)、y(由圆心向外方向的起始位置)

连线:source、target

3.绘制

分别绘制path和text。

首先创建弧生成器。

var arc = d3.svg.arc()
    .startAngle(function(d) { return d.x; })
    .endAngle(function(d) { return d.x + d.dx; })
    .innerRadius(function(d) { return Math.sqrt(d.y); })
    .outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });

绘制path使调用弧生成器。

.attr("d", arc)

绘制text时要进行一下转换:

.attr("transform",function(d,i){
    //第一个元素(最中间的),只平移不旋转
    if( i == 0 )
        return "translate(" + arc.centroid(d) + ")";
    
    //其他的元素,既平移也旋转
    var r = 0;
    if( (d.x+d.dx/2)/Math.PI*180 < 180 )  // 0 - 180 度以内的
        r = 180 * ((d.x + d.dx / 2 - Math.PI / 2) / Math.PI);
    else  // 180 - 360 度以内的
        r = 180 * ((d.x + d.dx / 2 + Math.PI / 2) / Math.PI);
        
    //既平移也旋转
    return  "translate(" + arc.centroid(d) + ")" +
            "rotate(" + r + ")";
}) 

直方图

image

1.数据格式:数组

2.布局

var histogram = d3.layout.histogram()
    .range([-50, 50])   //区间的范围
    .bins(bins_num) //矩形数量
    .frequency(true);   //若值为true,则统计的是个数;若值为false,则统计的是概率

数据转换

var data = histogram(dataset);

转换后的数据:

x(区间的起始位置)、dx(区间的宽度)、y(
如果frequency为true,则是落到此区间的数值的数量;如果frequency为false,则是落到此区间的概率)

3.绘制

矩形rect、坐标轴line、刻度line、文字text。

捆图

image

1.数据格式

var cities = {
     name: "",
     children:[
        {name: "北京"},{name: "上海"},{name: "杭州"},
        {name: "广州"},{name: "桂林"},{name: "昆明"},
        {name: "成都"},{name: "西安"},{name: "太原"}
     ]
 };
 var railway = [
    {source: "北京", target: "上海"},
    {source: "北京", target: "广州"},
    {source: "北京", target: "杭州"},
    {source: "北京", target: "西安"},
    {source: "北京", target: "成都"},
    {source: "北京", target: "太原"},
    {source: "北京", target: "桂林"},
    {source: "北京", target: "昆明"},
    {source: "北京", target: "成都"},
    {source: "上海", target: "杭州"},
    {source: "昆明", target: "成都"},
    {source: "西安", target: "太原"}
]; 

2.布局

需要使用集群图布局和捆图布局。

var cluster = d3.layout.cluster()
    .size([360, width/2-50]);   //首先使用集群图布局,360表示角度,width/2-50 表示捆图圆半径

    var bundle = d3.layout.bundle();    //捆图布局

    var nodes = cluster.nodes(cities);  //数据转换
    console.log(nodes);

    function map(nodes, links) {
        var hash = [];
        for(var i=0; i<nodes.length; i++) {
            hash[nodes[i].name] = nodes[i];
        }
        var resultLinks = [];
        for(var i=0; i<links.length; i++) {
            resultLinks.push({source:hash[links[i].source], target:hash[links[i].target]});
        }
        return resultLinks;
    }

    var oLinks = map(nodes, railway);
    console.log(oLinks);

    var links = bundle(oLinks); //数据转换
    console.log(links);

转换后的数据:

nodes-节点:name、depth、parent、x、y

oLinks-节点连线:source、target

links-捆图线条数据:Array(Object(name、depth、parent、children、x、y))

3.绘制

创建放射式的线段生成器:

var line = d3.svg.line.radial()
    .interpolate("bundle")  //在line.interpolate()所预定义的插值模式中,有一种就叫做bundle,正是为捆图准备的。
    .tension(.85)   //拉伸度0.85。变小线的弯曲度会变小,会使线与线之间的距离增大
    .radius(function(d) {return d.y;})
    .angle(function(d) {return d.x/180*Math.PI;})

首先绘制path,调用线段生成器:

.attr("d", line);   //调用线段生成器

接着创建g,在g中绘制circle和text

绘制g需要旋转、平移:

.attr("transform", function(d) {
    return "rotate("+(d.x-90)+")"+
        "translate("+(0, d.y)+")";
});

堆栈图

image

1.数据格式

var dataset = [
    { name: "PC" , 
      sales: [  { year:2005, profit: 3000 },
                { year:2006, profit: 1300 },
                { year:2007, profit: 3700 },
                { year:2008, profit: 4900 },
                { year:2009, profit: 700 }] },
    { name: "SmartPhone" , 
      sales: [  { year:2005, profit: 2000 },
                { year:2006, profit: 4000 },
                { year:2007, profit: 1810 },
                { year:2008, profit: 6540 },
                { year:2009, profit: 2820 }] },
    { name: "Software" , 
      sales: [  { year:2005, profit: 1100 },
                { year:2006, profit: 1700 },
                { year:2007, profit: 1680 },
                { year:2008, profit: 4000 },
                { year:2009, profit: 4900 }]}
    ];

2.布局

创建堆栈图布局

var stack = d3.layout.stack()
    .values(function(d) {return d.sales;})
    .x(function(d) {return d.year;})
    .y(function(d) {return d.profit});

数据转换

var data = stack(dataset);

转换后的数据:

name、Array(y0(纵坐标)、y(高度)、(year、profit)(属性名由dataset属性名决定))

3.绘制

分别绘制矩形rect、圆形circle、文字text、坐标轴axis,绘制过程与柱形图相似。

矩阵树图

image

1.数据格式

var dataSet = {
    "name": "中国",
    "children":
    [
        {
            "name": "浙江",
            "children": 
            [
                {"name":"杭州", "gdp":8343},
                {"name":"宁波", "gdp":7128},
                {"name":"温州", "gdp":4003},
                {"name":"绍兴", "gdp":3620},
                {"name":"湖州", "gdp":1803},
                {"name":"嘉兴", "gdp":3147},
                {"name":"金华", "gdp":2958},
                {"name":"衢州", "gdp":1056},
                {"name":"舟山", "gdp":1021},
                {"name":"台州", "gdp":3153},
                {"name":"丽水", "gdp":983}
            ]
        },
        ......
    ]
}

2.布局

创建矩阵树图布局

var treemap = d3.layout.treemap()
    .size([width, height])
    .value(function(d) {return d.gdp;})

数据转换

var nodes = treemap.nodes(dataSet);
var links = treemap.links(nodes);

转换后的数据:

节点:parent、children、depth、value、x、y、dx、dy

连线:source、target

3.绘制

分别绘制矩形rect、文字text。

地图

image

1.投影,转换经纬度数据,使能够在二维平面上显示

var projection = d3.geo.mercator()  //投影函数
    .center([107, 31])  //地图中心位置,经纬度
    .scale(850) //缩放比例
    .translate([width/2, height/2]);

2.创建地图路径生成器

var path = d3.geo.path()
    .projection(projection);

3.绘制

.data(root.features)    //数据绑定
...
.attr("d", path)    //调用路径生成器

更多内容:Github个人博客
备注:本文发表于 https://cnyangkui.github.io/2017/11/15/d3-graphlist/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容