数据的海洋汹涌澎湃,其中有着不可计数的线索和彩虹。若要在波涛中捕捉这些光影,高级的图形绘制技术便是必备的舟桨。D3提供了强大的工具,以灵巧的指尖操纵数据,用精细的图形展现出来。这里,将介绍D3高级图形的绘制方法,为阅读者揭开绘画数据图谱的另一幅画卷。
力导向图(Force-Directed Graph)
力导向图是表现复杂关系网的理想选择,每个节点彼此吸引或排斥,形成一个动态的交互系统。
javascript
var nodes_data = [
{id: 'node1'}, {id: 'node2'}, {id: 'node3'}
];
var links_data = [
{source: 'node1', target: 'node2'},
{source: 'node2', target: 'node3'}
];
var simulation = d3.forceSimulation(nodes_data)
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(150, 150))
.force('link', d3.forceLink(links_data).id(d => d.id));
simulation.on('tick', function() {
d3.select('svg')
.selectAll('line')
.data(links_data)
.enter()
.append('line')
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
});
此代码段使用D3力导向模拟器创建了节点间的吸引与排斥,绘制出了节点与连接线。
树图(Tree Graph)
树图能够体现层级数据的结构,让每一支数据枝节尽收眼底。
javascript
var treeData = {
name: "Root",
children: [
{name: "Branch1"},
{name: "Branch2"}
]
};
var treeLayout = d3.tree().size([400, 200]);
var root = d3.hierarchy(treeData);
treeLayout(root);
var nodes = root.descendants();
var links = root.links();
d3.select('svg')
.selectAll('.node')
.data(nodes)
.enter().append('circle')
.classed('node', true)
.attr('r', 5)
.attr('cx', d => d.x)
.attr('cy', d => d.y);
d3.select('svg')
.selectAll('.link')
.data(links)
.enter().append('line')
.classed('link', true)
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
上述代码构建了树图的布局,并绘制了节点及其连线。
地图绘制(Maps)
D3能够通过地理数据生成极其精确的地图,为乏味的数字赋予所在之地的生气。
javascript
var projection = d3.geoMercator().center([107, 31]).scale(400);
var pathGenerator = d3.geoPath().projection(projection);
d3.json("china.geojson", function(error, data) {
d3.select('svg')
.selectAll('path')
.data(data.features)
.enter()
.append('path')
.attr('d', pathGenerator)
.attr('fill', 'lightgrey')
.attr('stroke', 'white');
});
在该段代码中,引用的地图数据文件 china.geojson
提供了地理信息,D3利用这些信息为每个行政区绘制了边界。
饼图(Pie Chart)
将饼图的各个切片着色,能够迅速传递部分与整体的关系。
javascript
var pieData = [1, 2, 3, 4];
var pieGenerator = d3.pie();
var arc = d3.arc().innerRadius(0).outerRadius(100);
d3.select('svg')
.selectAll('path')
.data(pieGenerator(pieData))
.enter()
.append('path')
.attr('d', arc)
.attr('transform', 'translate(150,150)')
.attr('fill', (d, i) => d3.schemeCategory10[i]);
此代码绘制了一个四色饼图,每个切片的大小与数据成比例。
堆叠图(Stacked Chart)
堆叠图将不同组的数据堆积起来,直观展现各部分之间的量度关系。
javascript
var stackData = [
{month: 'Jan', apples: 10, oranges: 5},
{month: 'Feb', apples: 15, oranges: 10},
{month: 'Mar', apples: 12, oranges: 8}
];
var stack = d3.stack().keys(['apples', 'oranges']);
var series = stack(stackData);
var color = d3.scaleOrdinal(d3.schemeCategory10);
d3.select('svg')
.selectAll('g.series')
.data(series)
.enter().append('g')
.classed('series', true)
.style('fill', (d, i) => color(i))
.selectAll('rect')
.data(d => d)
.enter().append('rect')
.attr('x', d => xScale(d.data.month))
.attr('y', d => yScale(d[1]))
.attr('height', d => yScale(d[0]) - yScale(d[1]))
.attr('width', xScale.bandwidth());
在此例中,每个月的苹果和橙子销量数据被顺序堆叠在一起,构成堆叠图的每一层。
散点图(Scatter Plot)
散点图通过标点在坐标轴上的位置,展示变量之间的关系。
javascript
var scatterData = [{x: 30, y: 30}, {x: 70, y: 70}, {x: 110, y: 100}];
var xScale = d3.scaleLinear().domain([0, 130]).range([0, 400]);
var yScale = d3.scaleLinear().domain([0, 130]).range([400, 0]);
d3.select('svg')
.selectAll('circle')
.data(scatterData)
.enter().append('circle')
.attr('cx', d => xScale(d.x))
.attr('cy', d => yScale(d.y))
.attr('r', 5)
.attr('fill', 'tomato');
这些标点的位置揭示了x和y两变量之间的相互作用。
热力图(Heatmap)
热力图以颜色的深浅来显示数值大小,使得数据热点一目了然。
javascript
var heatmapData = [
{ day: 1, hour: 1, value: 10},
{ day: 2, hour: 1, value: 58},
{ day: 1, hour: 2, value: 30}
];
var colorScale = d3.scaleSequential(d3.interpolateInferno)
.domain(d3.extent(heatmapData, d => d.value));
d3.select('svg')
.selectAll('rect')
.data(heatmapData)
.enter().append('rect')
.attr('x', d => xScale(d.hour))
.attr('y', d => yScale(d.day))
.attr('width', xScale.bandwidth())
.attr('height', yScale.bandwidth())
.attr('fill', d => colorScale(d.value));
热力图使热度如同火焰一般跃然纸上,暗藏的规律随之浮出水面。
箱线图(Boxplot)
箱线图展示了数据的分布情况,将中位数、四分位数及极值一览无余。
javascript
var boxplotData = [10, 20, 30, 40, 50];
var chartWidth = 400, chartHeight = 400;
var y = d3.scaleLinear()
.domain([0, 60])
.range([chartHeight, 0]);
var boxplot = d3.box()
.whiskers(iqr(1.5))
.height(chartHeight)
.domain(y.domain());
d3.select('svg')
.data([boxplotData])
.append('g')
.call(boxplot);
此例中的箱线图利用四分位距规则界定了数据的主要离散范围。
矩阵布局(Treemap)
矩阵布局利用了空间,让数据的大小关系在几何形状的差异中表现出来。
javascript
var treemapData = {
"name": "Root",
"children": [
{"name": "Leaf A", "value": 30},
{"name": "Leaf B", "value": 20},
{"name": "Leaf C", "value": 50}
]
};
var treemapLayout = d3.treemap().size([400, 200]);
var root = d3.hierarchy(treemapData)
.sum(d => d.value);
treemapLayout(root);
d3.select('svg')
.selectAll('rect')
.data(root.leaves())
.enter().append('rect')
.attr('x', d => d.x0)
.attr('y', d => d.y0)
.attr('width', d => d.x1 - d.x0)
.attr('height', d => d.y1 - d.y0)
.attr('fill', 'steelblue');
通过矩阵布局,每个叶子节点的面积与其数值成正比。
当数据遇见技术,便有了色彩斑斓的缤纷世界;而编织这幅图画的绘师,在数据的海洋中翱翔,用技术的羽翼折射出数据的鲜活与与众不同。高级图形绘制在D3的笔触之下,不再是简单的线条与色彩探索,而是深入数据的内在意义,从而描绘出让人心悦的数据风景画。