D3是目前最流行的JavaScript可视化图表库之一,D3的图表类型非常丰富,并且支持SVG格式,因此应用十分广泛,也有很多图表插件基于D3开发,比如MetricsGraphics.js,在D3上构建的数据图表非常强大。
D3的特点
允许绑定任意数据到DOM,将数据驱动转换应用到Document中。
不仅可以创建精美的HTML表格,而且可以绘制折线图、柱形图和饼图等数据图表。
支持SVG,在Web页面上渲染毫无压力。
回到顶部
D3的使用方法
关于D3的具体用法,可以看D3图形库API参考这篇文章。本文主要对介绍一些经典图表的实现效果及代码。
index.html代码:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif
}
.y.axis path {
display: none
}
.y.axis line {
stroke: #fff
stroke-opacity: .2
shape-rendering: crispEdges
}
.y.axis .zero line {
stroke: #000
stroke-opacity: 1
}
.title {
font: 300 78px Helvetica Neue
fill: #666
}
.birthyear,
.age {
text-anchor: middle
}
.birthyear {
fill: #fff
}
rect {
fill-opacity: .6
fill: #e377c2
}
rect:first-child {
fill: #1f77b4
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 40, bottom: 30, left: 20},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
barWidth = Math.floor(width / 19) - 1
var x = d3.scale.linear()
.range([barWidth / 2, width - barWidth / 2])
var y = d3.scale.linear()
.range([height, 0])
var yAxis = d3.svg.axis()
.scale(y)
.orient("right")
.tickSize(-width)
.tickFormat(function(d) { return Math.round(d / 1e6) + "M" })
// An SVG element with a bottom-right origin.
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
// A sliding container to hold the bars by birthyear.
var birthyears = svg.append("g")
.attr("class", "birthyears")
// A label for the current year.
var title = svg.append("text")
.attr("class", "title")
.attr("dy", ".71em")
.text(2000)
d3.csv("population.csv", function(error, data) {
// Convert strings to numbers.
data.forEach(function(d) {
d.people = +d.people
d.year = +d.year
d.age = +d.age
})
// Compute the extent of the data set in age and years.
var age1 = d3.max(data, function(d) { return d.age }),
year0 = d3.min(data, function(d) { return d.year }),
year1 = d3.max(data, function(d) { return d.year }),
year = year1
// Update the scale domains.
x.domain([year1 - age1, year1])
y.domain([0, d3.max(data, function(d) { return d.people })])
// Produce a map from year and birthyear to [male, female].
data = d3.nest()
.key(function(d) { return d.year })
.key(function(d) { return d.year - d.age })
.rollup(function(v) { return v.map(function(d) { return d.people }) })
.map(data)
// Add an axis to show the population values.
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ",0)")
.call(yAxis)
.selectAll("g")
.filter(function(value) { return !value })
.classed("zero", true)
// Add labeled rects for each birthyear (so that no enter or exit is required).
var birthyear = birthyears.selectAll(".birthyear")
.data(d3.range(year0 - age1, year1 + 1, 5))
.enter().append("g")
.attr("class", "birthyear")
.attr("transform", function(birthyear) { return "translate(" + x(birthyear) + ",0)" })
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0] })
.enter().append("rect")
.attr("x", -barWidth / 2)
.attr("width", barWidth)
.attr("y", y)
.attr("height", function(value) { return height - y(value) })
// Add labels to show birthyear.
birthyear.append("text")
.attr("y", height - 4)
.text(function(birthyear) { return birthyear })
// Add labels to show age (separate not animated).
svg.selectAll(".age")
.data(d3.range(0, age1 + 1, 5))
.enter().append("text")
.attr("class", "age")
.attr("x", function(age) { return x(year - age) })
.attr("y", height + 4)
.attr("dy", ".71em")
.text(function(age) { return age })
// Allow the arrow keys to change the displayed year.
window.focus()
d3.select(window).on("keydown", function() {
switch (d3.event.keyCode) {
case 37: year = Math.max(year0, year - 10) break
case 39: year = Math.min(year1, year + 10) break
}
update()
})
function update() {
if (!(year in data)) return
title.text(year)
birthyears.transition()
.duration(750)
.attr("transform", "translate(" + (x(year1) - x(year)) + ",0)")
birthyear.selectAll("rect")
.data(function(birthyear) { return data[year][birthyear] || [0, 0] })
.transition()
.duration(750)
.attr("y", y)
.attr("height", function(value) { return height - y(value) })
}
})
1、模拟数据
// 模拟100条0-100的随机数,作为柱状图的高度
var data = Array.apply(0, Array(100)).map(function() {
return Math.random() * 100
})
2、创建SVG容器
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = document.body.clientWidth - margin.left - margin.right,
height = 500 - margin.top - margin.bottom
var chart = d3.select('body')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')')
chart就是最终建立的容器,下面就往容器里面放元素。
3、画柱状图
// 计算每根柱状物体的宽度
var barWidth = width / data.length
// 用g作每根柱状物体的容器,意义可类比div
// 前一篇文章已经介绍过selectAll的意义,即生成占位符,等待填充svg图形
var bar = chart.selectAll('g')
.data(data)
.enter()
.append('g')
// 接收一个数据填充一个g元素
// 同时为g设置位置
.attr('transform', function(d, i) {
return 'translate(' + i * barWidth + ', 0)'
})
bar.append('rect')
// 添加一个矩形
.attr('y', function(d) {
return height - d
})
.attr('height', function(d) {
return d
})
.attr('width', barWidth - 1)
前文提到svg的元素定位都是基于整个svg容器左上角作为原点,但并不能使用position: absolute等方法定位,此处的g元素通过位移来定位x坐标,即transform: translate(x, 0)。
这里的bar可类比jQuery对象,是一个类数组对象,bar调用的方法都会对bar里面每个对象进行调用。代码中每一次调用都插入一个矩形,同时设置y坐标、高度和宽度,x坐标跟父容器(g)保持一致即可。这里需要注意y坐标往下为正,为了让所有矩形的下边处于同一高度,这里设置每个矩形的y坐标为容器高度减去矩形高度。为了用一像素区分开每个矩形,这里设置矩形宽度为父容器的宽度减1。
通过以上js代码再稍微设置一点css
rect {
fill: #2177BB
}
即可看到一张最简单的柱状图了。
4、添加坐标轴
var y = d3.scale.linear()
.domain([0, d3.max(data)])
.range([height, 0])
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.ticks(1)
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
// 添加x坐标轴
chart.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
// 添加y坐标轴
chart.append('g')
.attr('class', 'y axis')
.call(yAxis)
完整的柱状图就是这样了
本节内容将描述饼状图、力导向图、弦图、集群图、树状图、打包图、分区图、圆形分区图、直方图、捆图、堆栈图、矩阵树图、地图的绘制过程,参考 D3.js入门系列
温馨提示:对于有D3基础的人,本节内容能够帮助其快速掌握各图表的绘制。若没有掌握基础知识,不建议直接学习本节内容。
对图表绘制的重点内容进行了总结,下述图表绘制步骤相似,总结如下:
布局内容总结:
转换后的数据:
弧生成器计算路径(svg的path)
绘制路径path,需要调用弧生成器
或者
绘制文本text,计算路径中心位置,放入文本值
转换后的数据:
然后,使力学作用生效:
绘制节点时,使节点拖动
力导向图布局force有一个事件tick,每进行到一个时刻,都要调用它,更新的内容就写在它的监听器里就好。
数据转换
转换后的数据:
首先绘制外部圆环,使用arc弧生成器
绘制path,使用数据为groups
绘制text,使用数据为groups。首先旋转适当角度,然后向上移动外半径个长度,135-225度范围的文字倒置
然后,绘制弦,使用chord生成器生成路径
绘制path,使用数据为chords
使用数据
转换后的数据:
首先,创建对角线生成器
绘制连线,使用生成器
然后绘制节点circle。
和集群图写法基本相同,使用布局不同。
转换后的数据与集群图相同。
数据格式与树状图相同,布局如下:
数据转换写法也类似。
转换后的数据:
然后分别绘制circle和text。
数据类型与集群图、树状图、打包图相同。用于表示包含与被包含关系的。布局如下:
value设定表示分区大小的值。这里的意思是:如果数据文件中用size值表示结点大小,那么这里可写成return d.size。
数据转换写法也类似。(nodes、links)
转换后的数据:
然后绘制rect和text。rect的width、height属性分别对应数据属性dx、dy。
与前面相同。
数据转换写法也类似。(nodes、links)
转换后的数据:
分别绘制path和text。
首先创建弧生成器。
绘制path使调用弧生成器。
绘制text时要进行一下转换:
数据转换
转换后的数据:
矩形rect、坐标轴line、刻度line、文字text。
需要使用集群图布局和捆图布局。
转换后的数据:
创建放射式的线段生成器:
首先绘制path,调用线段生成器:
接着创建g,在g中绘制circle和text
绘制g需要旋转、平移:
创建堆栈图布局
数据转换
转换后的数据:
分别绘制矩形rect、圆形circle、文字text、坐标轴axis,绘制过程与柱形图相似。
创建矩阵树图布局
数据转换
转换后的数据:
分别绘制矩形rect、文字text。
更多内容: Github个人博客
备注:本文发表于 https://cnyangkui.github.io/2017/11/15/d3-graphlist/