冰锥图可视化
矩形冰锥图
d3.partition()
官方文档
用来生成邻接图:一个节点链接树图的空间填充变体。与使用连线链接节点与父节点不同,在这个布局中节点会被绘制为一个区域(可以是弧也可以是矩形),并且其位置反应了其在层次结构中的相对位置。节点的尺寸被编码为一个可度量的维度,这个在节点-链接图中很难表示。
d3.partition().size()
返回一个函数,会把传入函数的数据划分成一个个区域,一般情况下使用方法为:
d3.json('./data/games.json').then( data => {
root = d3.partition().size([height,width])(
d3.hierarchy(data).sum(d => d.popularity)
.sort((a,b) => {return b.popularity - a.popularity})
);
});
代码理解
- sum:每个节点的高度都由它的子节点的高度求和决定的。那么如何求和是由我们来告诉d3.js的。如:给非叶子结点的树形赋值为子节点
结果
做data join的时候,直接用 红框内的数据即可,绘制矩形和文本的时候都需要
- x0,y0 : 矩形左上角的点
- x1,y1 : 矩形右下角的点
绘制区域代码
//绘制矩形区域代码
g.selectAll('datarect').data(data.descendants()).join('rect')
.attr('class', 'datarect' )
.attr('fill', fill )
.attr('x',d => d.y0 )
.attr('y', d => d.x0)
.attr('height', d => d.x1 - d.x0)
.attr('width', d => d.y1 -d.y0);
//绘制文本代码
g.selectAll('.datatext').data(data.descendants()).join('text')
.attr('class', 'datatext')
.attr('x', d => (d.y0 + d.y1)/2)
.attr('y', d => (d.x0 + d.x1)/2)
.attr('text-anchor', 'middle')
.text(d => d.data.name);
结果
问题
这种类型的冰锥图需要滑动网页查看,非常不方便,因此考虑将冰锥图掰成一圈,更加方便查看
Sunburst(光晕图)
数据预处理
- 和冰锥图一样,依然是使用
partition()
这个接口,但是size应该要改变一下。 - 画path时,设置d属性使用
d3.arc()
由于要画出环状的冰锥图,他用的是极坐标,需要注意的是,d3中默认的极坐标系0度是竖直向上的 - Size放入的数组中包含两个元素,一个是角度 θ,一个是长度ρ
d3.json('./data/games.json').then( data => {
root = d3.partition().size([2 * Math.PI, height / 1.6])
(d3.hierarchy(data).sum(d => d.popularity)
.sort((a, b) => b.popularity - a.popularity));
size为布局指定大小
- 若是直角坐标系就传入width和height;
- 若是极坐标系就传入θ和ρ的最大值;(注意,这里的θ用的是弧长,不是角度)
绘制path(扇形区域)
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.innerRadius(d => d.y0)
.outerRadius(d => d.y1)
// .padAngle()
//startAngle:起始角度
//endAngle: 终止角度
//innerRadius:起始半径
//outerRadius:终止半径
color = d3.scaleOrdinal(d3.schemeCategory10)
g.selectAll('.datapath').data(data.descendants().filter(d => d.depth>0)).join('path')
.attr('calss', 'datapath')
.attr('d', arc )
.attr('fill',fill )
绘制文本
g.selectAll('.datatext').data(data.descendants().filter(d => d.depth>0))
.join('text')
.attr('class', 'datatext')
.attr('transform', d =>{
let x = (d.x0 + d.x1) / 2 * 180 /Math.PI;
let y = (d.y0 + d.y1) / 2 ;
//data中使用的是弧度制(x,y表示弧度),但是rotate()使用的是角度制,因此这里需要人为进行一步转换(*180/Math.PI)
return `rotate(${x-90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`
})
.attr('text-anchor', 'middle')
.text(d => d.data.name)
- 注意:translate里面的函数执行时绝对有序的,rotate旋转的时候会连带着坐标轴一起旋转
- text默认的0度是水平向右的,而d3中默认的0度是水平向上的,因此在rotate里面还是要减去90
- 左半边的文本需要再转一下,让文字变正,因此在translate里面还要加上一个rotate,让左半边的文字旋转180度变正
矩形冰锥图icicle 可视化代码
<!DOCTYPE html>
<html>
<head>
<title>Icicle</title>
<script src="./js/d3.min.js"></script>
<meta charset="utf-8">
</head>
<body>
<svg width="1000" height="3000" id="mainsvg" class="svgs"
style="display: block; margin: auto;"></svg>
<script>
const svg = d3.select('#mainsvg');
const width = +svg.attr('width');
const height = +svg.attr('height');
const g = svg.append('g')
//.attr('transform', `translate(${width/2}, ${height/2})`);
let root;
const fill = d => {
if( d.depth == 0 ){
//根节点
return color(d.data.name);
}
while (d.depth >1 ){
//不是第二层
d = d.parent;
}
return color(d.data.name)
};
const render = function(data){
color = d3.scaleOrdinal(d3.schemeCategory10);
g.selectAll('datarect').data(data.descendants()).join('rect')
.attr('class', 'datarect' )
.attr('fill', fill )
.attr('x',d => d.y0 )
.attr('y', d => d.x0)
.attr('height', d => d.x1 - d.x0)
.attr('width', d => d.y1 -d.y0);
g.selectAll('.datatext').data(data.descendants()).join('text')
.attr('class', 'datatext')
.attr('x', d => (d.y0 + d.y1)/2)
.attr('y', d => (d.x0 + d.x1)/2)
.attr('text-anchor', 'middle')
.text(d => d.data.name);
}
d3.json('./data/games.json').then( data => {
root = d3.partition().size([height,width])(
d3.hierarchy(data).sum(d => d.popularity)
.sort((a,b) => {return b.popularity - a.popularity})
);
render(root)
});
</script>
</body>
</html>
可视化效果
光晕图Sunburst可视化代码(用到了极坐标轴)
<!DOCTYPE html>
<html>
<head>
<title>SunBurst</title>
<script src="./js/d3.min.js"></script>
</head>
<body>
<svg width="1600" height="940" id="mainsvg" class="svgs"
style="display: block; margin: auto;"></svg>
<script>
const svg = d3.select('#mainsvg');
const width = +svg.attr('width');
const height = +svg.attr('height');
svg.attr("viewBox", [0, 0, width, height]);
const g = svg.append('g')
.attr('transform', `translate(${width/2}, ${height/2})`);
let root;
const arc = d3.arc()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.innerRadius(d => d.y0)
.outerRadius(d => d.y1)
// .padAngle()
const fill = d => {
while (d.depth > 1)
d = d.parent;
return color(d.data.name);
};
const render = function(data){
color = d3.scaleOrdinal(d3.schemeCategory10)
g.selectAll('.datapath').data(data.descendants().filter(d => d.depth>0)).join('path')
.attr('calss', 'datapath')
.attr('d', arc )
.attr('fill',fill )
g.selectAll('.datatext').data(data.descendants().filter(d => d.depth>0))
.join('text')
.attr('class', 'datatext')
.attr('transform', d =>{
let x = (d.x0 + d.x1) / 2 * 180 /Math.PI;
let y = (d.y0 + d.y1) / 2 ;
//data中使用的是弧度制(x,y表示弧度),但是rotate()使用的是角度制,因此这里需要人为进行一步转换(*180/Math.PI)
return `rotate(${x-90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`
})
.attr('text-anchor', 'middle')
.text(d => d.data.name)
//注意:translate里面的函数执行时绝对有序的,rotate旋转的时候会连带着坐标轴一起旋转
//text默认的0度是水平向右的,而d3中默认的0度是水平向上的,因此在rotate里面还是要减去90
//左半边的文本需要再转一下,让文字变正,因此在translate里面还要加上一个rotate,让左半边的文字旋转180度变正
}
d3.json('./data/games.json').then( data => {
root = d3.partition().size([2 * Math.PI, height / 1.6])
(d3.hierarchy(data).sum(d => d.popularity)
.sort((a, b) => b.popularity - a.popularity));
console.log(root);
render(root);
});
</script>
</body>
</html>