D3学习系列(二) 弦图绘制

Chord

「什么时候用弦图」

Chord Diagram主要是用来表示多个节点之间的关系,假设我们要表示5个节点之间的关系,那么输入的矩阵是下面这个样子,且必须是方阵。节点A的长度是元素A所在行的总和,就是(A, A)、(A, B)、(A, C)、(A, D)、(A, E)的和。图中C与D之间的弦表示C和D相关,与C相接的弧长实际上是(C, D)的值。

「绘制弦图」

导入初始数据

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

布局(转换数据)

// 弦布局
var chord_layout = d3.layout.chord()
                        .padding(0.03)
                        .sortSubgroups(d3.descending)
                        .matrix(population);
// 布局转化数据
var groups = chord_layout.groups();
var chords = chord_layout.chords();

padding(0.03)表示弧与弧之间的间隔,population是之前输入的人口数据。经过布局之后会生成两块,一块是groups,表示节点;另一块是chords,表示弦(连线),chords里面还会分source与target,表示连线的两端。

绘制画布SVG

// svg画布
var width = 600;
var height = 600;
var svg = d3.select("body")
            .append("svg")
            .attr("width",width)
            .attr('height', height)
            .append("g")
            .attr('transform', 'translate(' + width/2 + "," + height/2 + ")");
var color20 = d3.scale.category20();

先创建一个SVG元素,里面添加一个g元素同时设置平移属性(用来确定弦图的中心)。然后再在g元素添加2个g元素,分别用来装节点与弦,结构如下所示:

<svg>
 --<g>
 ---- <g>``</g>
 ---- <g>``</g>
 --</g>
</svg>

绘制节点(弧)

// 弧生成器
var innerRadius = width/2 * 0.7;
var outerRadius = innerRadius * 1.1;
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) {
            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) {    // 平移属性
            var result =  "rotate(" + (d.angle*180/Math.PI) + ")";
            result += "translate(0," + -1 * (outerRadius + 10) + ")";
            if (d.angle > Math.PI * 3 / 4 && d.angle < Math.PI * 5 / 4 )
                result += "rotate(180)";
            return result;
        })
        .text(function(d) {
            return d.name;
        });

在标记文字的地方要注意:

  1. each():表示对任何一个绑定数据的元素,都执行后面的无名函数 function(d,i) ,计算文字的角度与内容
  2. transform():不仅需要考虑文字的旋转角度与平移距离,还要考虑如果文字在下方是会是倒写的情况。

生成如下图:


绘制连线(弦)

// 弦生成器
var inner_chord = d3.svg.chord()
                        .radius(innerRadius);

// 绘制内部弦
var g_inner = svg.append("g")
                .attr("class","chord");

g_inner.selectAll("path")
        .data(chords)
        .enter()
        .append("path")
        .attr("d",inner_chord)  // 调用弦的路径值
        .style("fill",function(d) {
            return color20(d.source.index);
        })
        .style("opacity",1)
        ;

这样就生成了首页的弦图,但是当数据多了之后,会看不清节点与节点之间的关系,我们可以添加一些交互式的操作解决。如当鼠标移到该节点,只会显示与该节点相接的弦,其他的会被隐藏。这里我们定义一个fade函数,并在节点(弧)上通过mouseovermouseout添加动作

function fade(opacity){
    return function(g,i){
        g_inner.selectAll("path")
                .filter(function(d) {
                    return d.source.index != i && d.target.index != i;
                })
                .transition()
                .style("opacity",opacity);
    }
}

g_outer.selectAll("path")
        .on("mouseover",fade(0.0))  // 0.0完全透明
        .on("mouseout",fade(1.0))   // 1.0完全不透明
        ;

效果如图:

「源代码」

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
        <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
        <style>
            .chord path{
                fill-opacity: 0.67;
                stroke: #000;
                stroke-width: 0.5px;
            }
        </style>
    </head>

    <body>
        <script>
        // 初始数据
        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 ]
                ];

        // 弦布局
        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);

        // svg画布
        var width = 600;
        var height = 600;
        var svg = d3.select("body")
                    .append("svg")
                    .attr("width",width)
                    .attr('height', height)
                    .append("g")
                    .attr('transform', 'translate(' + width/2 + "," + height/2 + ")");

        var color20 = d3.scale.category20();

        // 弧生成器
        var innerRadius = width/2 * 0.7;
        var outerRadius = innerRadius * 1.1;
        var outer_arc = d3.svg.arc()
                            .innerRadius(innerRadius)
                            .outerRadius(outerRadius);

        // 绘制节点
        function fade(opacity){
            return function(g,i){
                g_inner.selectAll("path")
                        .filter(function(d) {
                            return d.source.index != i && d.target.index != i;
                        })
                        .transition()
                        .style("opacity",opacity);
            }
        }

        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) {
                    color20(d.index);
                })
                .attr("d",outer_arc)   // 此处调用了弧生成器
                .on("mouseover",fade(0.0))  // 0.0完全透明
                .on("mouseout",fade(1.0))   // 1.0完全不透明
                ;

        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) {    // 平移属性
                    var result =  "rotate(" + (d.angle*180/Math.PI) + ")";
                    result += "translate(0," + -1 * (outerRadius + 10) + ")";
                    if (d.angle > Math.PI * 3 / 4 && d.angle < Math.PI * 5 / 4 )
                        result += "rotate(180)";
                    return result;
                })
                .text(function(d) {
                    return d.name;
                });

        // 弦生成器
        var inner_chord = d3.svg.chord()
                                .radius(innerRadius);

        // 绘制内部弦,一共有5*5=25条
        var g_inner = svg.append("g")
                        .attr("class","chord");

        g_inner.selectAll("path")
                .data(chords)
                .enter()
                .append("path")
                .attr("d",inner_chord)  // 调用弦的路径值
                .style("fill",function(d) {
                    return color20(d.source.index);
                })
                .style("opacity",1)
                ;
        </script>
    </body>
</html>

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

推荐阅读更多精彩内容

  • 1. 关于诊断X线机准直器的作用,错误的是()。 (6.0 分) A. 显示照射野 B. 显示中心线 C. 屏蔽多...
    我们村我最帅阅读 9,606评论 0 5
  • 记得学生时代,一句“我喜欢你”仿佛代表了你比全班、全校、全世界都重要,就算对抗世界也要保护你的所有,而在很久以后的...
    巷口北走路小南阅读 383评论 0 1
  • 刚刚一个不小心,睡着了,哈哈… 我觉得,我最大的一点,就是,我成长❗️因为曾经以前,我会抱怨,但是现在...
    付爱宝兔子阅读 141评论 0 0
  • 我筑起了城堡,你咧起了嘴角,牵着我的手陪我慢慢变老。 军营里的零点,没有人为你祝福,可能也没有蛋糕,像过去的170...
    念木东兮阅读 816评论 15 15
  • 今天是反思的第35天。今天晚上听了得到的直播是古典老师讲的,超级个体,收获非常的大,我来做一个小小的总结。 人的认...
    張嘉宾阅读 130评论 0 0