记得上次看d3应该是1年前的事情了,当时还一边看一边写了d3(v5.7)的一个学习笔记:https://www.cnblogs.com/eco-just/tag/d3/

后来转战three.js就没继续研究了(其实也是感觉api层面的东西也没有深入研究的必要,何况后续项目也不会用到这些东西)。

期间也有同行通过博客问过弦图的问题,出于种种原因吧,当时并没有深入研究。

但是今天!我们就结合d3的3.5.16版本来深入解析一下d3的弦图吧。(demo是找的简书上这为同学的笔记:https://www.jianshu.com/p/4b44c708c2da

先上效果:

step1:根据数据初始化布局

code:

// 初始数据
var city_name = [ "北京" , "上海" , "广州" , "深圳" , "香港" ];
var population = [
[ , , , , ],
[ , , , , ],
[ , , , , ],
[ , , , , ],
[ , , , , ]
]; // 弦布局初始化
var chord_layout = d3.layout.chord()
.padding(0.03)
.sortSubgroups(d3.descending)
.matrix(population); // 获取弦布局初始化后的数据
var groups = chord_layout.groups();
var chords = chord_layout.chords();

解析:

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

先用d3.layout.chord()这个api传入数据,初始化布局所需要的数据groups、chords;

groups数据5条,5个城市,根据population所占权重分配圆弧的大小,在上述数据上的反应就是startAngle和endAngle;

chords数据15条,5个城市选两个(source,target),根据排列组合应该是5+4+3+2+1=15种(source,target可以相同);

step2:绘制画布和计算内外圆半径

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

解析:

.d3content是画布依赖的根元素dom,上述代码将会在600X600的画布上绘制接下来的弦图;

step3:绘制外圆和文字

    var outer_arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
//绘制外圆(5个城市)
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) / ;
d.name = city_name[i];
})
.attr("dy",".35em")
.attr('transform', function(d) { // 平移属性
var result = "rotate(" + (d.angle*/Math.PI) + ")";
result += "translate(0," + - * (outerRadius + ) + ")";
if (d.angle > Math.PI * / && d.angle < Math.PI * / )
result += "rotate(180)";
return result;
})
.text(function(d) {
return d.name;
});

效果图:

注意上述有一句代码:

.attr("d",outer_arc)   // 此处调用了弧生成器

对于每个path都会根据这个函数来绘制,而这个函数对于对应源码里的d3.svg.arc,并且这里有个隐藏的东西:

.attr("d",out_arc),第二个参数执行的时候(他是一个函数),会将数据作为实参传给他,于是到了源码里:

d3.svg.arc = function() {
var innerRadius = d3_svg_arcInnerRadius,
    outerRadius = d3_svg_arcOuterRadius,
     cornerRadius = d3_zero,
     padRadius = d3_svg_arcAuto,
     startAngle = d3_svg_arcStartAngle,
     endAngle = d3_svg_arcEndAngle,
     padAngle = d3_svg_arcPadAngle;
function arc() {
var r0 = Math.max(, +innerRadius.apply(this, arguments)),
      r1 = Math.max(, +outerRadius.apply(this, arguments)),
      a0 = startAngle.apply(this, arguments) - halfπ,
      a1 = endAngle.apply(this, arguments) - halfπ,
      da = Math.abs(a1 - a0),
      cw = a0 > a1 ? : ;
if (r1 < r0) rc = r1, r1 = r0, r0 = rc;
if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, - cw) : "") + "Z";
var rc, cr, rp, ap, p0 = , p1 = , x0, y0, x1, y1, x2, y2, x3, y3, path = [];
  .....
}

这个arguments就是如下这样的单条数据:

最终根据传入的数据,加上一系列的逻辑处理返回了一个path节点的d属性值,具体的判断逻辑,我截图一张供各位欣赏,如果你有兴趣可以逐个去找他的函数:

最后,由这个属性值绘制了一个path节点:

后面的绘制文字就不多说了,注意一点,回调函数形参里面的d是data(数据)的意思,i是index(索引)的意思。

step4:绘制内弦chord

    // 弦生成器
var inner_chord = d3.svg.chord()
.radius(innerRadius);
console.log(inner_chord) // 添加g元素,接下来在这个元素里面绘制chord
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,i) {
return color20(d.source.index);
})
.style("opacity",)
;

为了大家看得清晰,我把之前绘制的外圆注释了:

解析:

同理,这里通过d3.svg.chord来绘制弦,依据的数据是:

同样贴上源码的主要部分:

d3.svg.chord = function() {
var source = d3_source,
     target = d3_target,
     radius = d3_svg_chordRadius,
     startAngle = d3_svg_arcStartAngle,
     endAngle = d3_svg_arcEndAngle;
function chord(d, i) {
var s = subgroup(this, source, d, i),
      t = subgroup(this, target, d, i);
return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
}

equals(s,t)判断两个端点是否相同来决定绘制的方式;

我们看到这里绘制路径,主要用到了两个函数arc()和curve();

   function arc(r, p, a) {
return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
}
function curve(r0, p0, r1, p1) {
return "Q 0,0 " + p1;
}

关于svg的path绘制中各参数的含义,下面给一张图,这里就不多说了:

所以弦主要就是由svg内置的弧绘制api来绘制的(普通的弧线/贝塞尔曲线)!

深入解析d3弦图的更多相关文章

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

    一.弦图 1.弦图是什么 弦图(Chord),主要用于表示两个节点之间的联系的图表.两点之间的连线,表示谁和谁具有联系. 2.数据 初始数据为: var city_name = [ "北京& ...

  2. 【 D3.js 入门系列 --- 9.3 】 弦图生产

    我个人的博客: www.ourd3js.com csdn博客为: blog.csdn.net/lzhlzz 转载请注明出处,谢谢. 弦图( Chord ),主要用于表示两个节点之间的联系.例如以下图: ...

  3. D3.js 弦图的制作

    这是一种用于描述节点之间联系的图表. 1. 弦图是什么 弦图(Chord),主要用于表示两个节点之间的联系. 两点之间的连线,表示谁和谁具有联系: 线的粗细表示权重: 2. 数据 初始数据为: var ...

  4. ZOJ 1015 Fishing Net(弦图判定)

    In a highly modernized fishing village, inhabitants there make a living on fishery. Their major tool ...

  5. 【BZOJ1006】【HNOI2008】神奇的国度(弦图染色)

    1006: [HNOI2008]神奇的国度 Time Limit: 20 Sec  Memory Limit: 162 MBSubmit: 1467  Solved: 603[Submit][Stat ...

  6. 弦图的判定MCS算法(zoj1015)

    题意:裸的弦图的判定: 弦图定义:给出一个无向连通图,如果每个环中都存在至少一条弦(环中存在不相邻的两点直接相连)这样的图叫做弦图: 转载:http://blog.csdn.net/crux_d/ar ...

  7. ZOJ 1015 Fishing Net(判断弦图)

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=15 题意:给定一个图.判断是不是弦图? 思路:(1)神马是弦图?对于一 ...

  8. BZOJ 1006 神奇的国度(弦图的染色数)

    题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1006 题意:给定一个弦图,求最小染色数.就是用最小数目的颜色进行染色使得任意两个相邻的节 ...

  9. bzoj 1242: Zju1015 Fishing Net 弦图判定

    1242: Zju1015 Fishing Net弦图判定 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 214  Solved: 81[Submit ...

随机推荐

  1. 学习elasticsearch(一)linux环境搭建(2)——启动elasticsearch

    在启动访问es的过程中遇到了各种的奇葩问题. 1.网上各种版本的启动方式让人眼花缭乱不知如何启动.简单粗暴——到es的bin目录下直接 执行 ./elasticsearch //显示启动,ctrl+c ...

  2. hdfs冷热数据分层存储

    hdfs如何让某些数据查询快,某些数据查询慢? hdfs冷热数据分层存储 本质: 不同路径制定不同的存储策略. hdfs存储策略 hdfs的存储策略 依赖于底层的存储介质. hdfs支持的存储介质: ...

  3. java Timer和TimerTask(简单的使用)

    Timer 是一个定时工具 TimerTask 是一个实现了Runnable接口抽象类,代表可以被Timer执行的任务 (1)Timer.schedule(TimerTask task,Date ti ...

  4. docker 共享卷

    宿主机与容器共享存储 [root@docker02 ~]# docker run -it -v /var/webroot:/abc 192.168.1.21:5000/busybox # 宿主机文件 ...

  5. shell爬取斗图网

    #!/bin/bash read -p "请输入要爬取的页面数(默认为10):" page_num page_num=${page_num:-} echo $page_num re ...

  6. P5057 [CQOI2006]简单题 前缀异或差分/树状数组

    好思路,好思路... 思路:前缀异或差分 提交:1次 题解:区间修改,单点查询,树状数组,如思路$qwq$ #include<cstdio> #include<iostream> ...

  7. BOOTING ELOQUENT MODEL TRAITS

    BOOTING ELOQUENT MODEL TRAITS So I've learnt a little Laravel/Eloquent trick today that is very much ...

  8. 阿里云Ubuntu安装Composer和中国镜像

    引用: Composer是PHP用来管理依赖(dependency)关系的工具.你可以在自己的项目中声明所依赖的外部工具库(libraries),Composer 会帮你安装这些依赖的库文件. PHP ...

  9. php-m 与 phpinfo 不一致的解决办法

    1.查看PHP当前加载的php.ini文件地址,在控制台输入以下命令: 1 php -i | grep php.ini 2.控制台返回的信息 1 2 Configuration File (php.i ...

  10. 2017 ZSTU寒假排位赛 #6

    题目链接:https://vjudge.net/contest/149212#overview. A题,水题,略过. B题,水题,读清题意即可. C题,数学题,如果把x表示成x=nb+m,则k=n/m ...