Preface
d3是基于HTML和SVG的数据可视化JS库. 它将数据(data)和元素(DOM)相互绑定在一起, 并且在数据实时改变情况下DOM元素也会实时改变.
所以, D3是Data-Driven Documents, 即数据驱动元素, 而D3的命名则来源于W3C Document Object Model.
html模板文件为:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<script src="d3.js"></script>
<script>
</script>
我们需要在当前html模板文件下启动一个简单的服务器, 这样才能读取数据文件:
leicj@lishunan:~$ python -m SimpleHTTPServer 8888 &
[1] 16960
leicj@lishunan:~$ Serving HTTP on 0.0.0.0 port 8888 ...
127.0.0.1 - - [23/Oct/2016 13:02:13] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [23/Oct/2016 13:02:13] code 404, message File not found
127.0.0.1 - - [23/Oct/2016 13:02:13] "GET /favicon.ico HTTP/1.1" 404 -
127.0.0.1 - - [23/Oct/2016 13:02:37] "GET /test/ HTTP/1.1" 200 -
127.0.0.1 - - [23/Oct/2016 13:02:37] "GET /test/d3.js HTTP/1.1" 200 -
Selections
d3的选择器和jQuery很像, 以下是经常要用到的选择器:
#foo // <any id="foo">
foo // <foo>
.foo // <any class="foo">
[foo=bar] // <any foo="bar">
foo bar // <foo><bar></foo>
foo.bar // <foo class="bar">
foo#bar // <foo id="bar">
d3使用selectAll来筛选, 所以:
d3.selectAll("pre,code")
等价于jQuery中:
$("pre,code")
选择器本身返回的是一个数组.
一个实际的例子:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
margin-top: 20px;
margin-left: 20px;
}
</style>
<svg height="960" width="960">
<circle></circle>
</svg>
<script src="d3.js"></script>
<script>
var circle = d3.selectAll("circle");
circle.attr('cx', 50);
circle.attr('cy', 52);
circle.attr('r', 24);
circle.style("fill", "red");
</script>
绘制一个circle:
而d3支持链式编写:
d3.selectAll("circle")
.attr('cx', 50)
.attr('cy', 52)
.attr('r', 24)
.style("fill", "red");
selection.append用于创建一个新的元素, 选择其元素, 并在其元素后append数据.
d3.select("body").append("h1")
.text("Hello!");
或者创建多个小圆圈:
<svg height="960" width="960">
</svg>
<script src="d3.js"></script>
<script>
var data = [5, 10, 15, 20, 25, 20, 15, 10, 5];
var g = d3.select('svg').append('g'),
circle = g.selectAll('circle').data(data);
circle.enter()
.append("circle")
.attr('cx', function(d, i) { return (i + 1) * 32; })
.attr('cy', function(d, i) { return d * 20; })
.attr('r', function(d, i) { return 10; })
.attr('fill', 'red');
</script>
Data
我们考虑下如下的图形如何绘制:
这里由五部分组成: 五个rect图形, x轴的索引值, y轴的索引值, x轴, y轴. 开些编写前, 先普及一些基本的svg函数:
1. rect: 用来绘制矩形, 基本属性如下:
x: 矩形左上角的x位置
y: 矩形左下角的y位置
width: 矩形的宽度
height: 矩形的高度
rx: 圆角的x方位的半径
ry: 圆角的y方位的半径
2. translate: 使元素进行移动
首先, 我们绘制五个rect:
var rect_arr = [
{"height": 40, "width": 100},
{"height": 40, "width": 200},
{"height": 40, "width": 300},
{"height": 40, "width": 400},
{"height": 40, "width": 500}
];
var g = d3.select("svg").append("g").attr("transform", "translate(40,60)"),
rect = g.selectAll("rect").data(rect_arr);
rect.enter()
.append("rect")
.attr("class", "bar")
.attr("height", function(d, i) { return d.height; })
.attr("width", function(d, i) { return d.width; })
.attr("y", function(d, i) { return 80 + i * 70; })
.attr("fill", "steelblue");
效果图如下:
其次, 我们绘制x轴:
var x_arr = [0, 100, 200, 300, 400, 500, 600],
gx = d3.select("svg").append("g").attr("class", "x axis").attr("transform", "translate(40, 460)"),
xaxis = gx.selectAll("g").data(x_arr);
xaxis.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(" + d + ",0)"})
.attr("style", "opacity:1")
.append("text")
.attr("y", 14)
.attr("x", 0)
.attr("dy", ".71em")
.attr("text-anchor", "middle")
.text(function(d, i) { return i; });
和x轴的线:
var xaxis_path = d3.select("svg g.x").append("path")
.attr("stroke", "#000")
.attr("d", "M0 6H600");
y轴:
var y_arr = [
{"y": 20, "text": "A"},
{"y": 90, "text": "B"},
{"y": 160, "text": "C"},
{"y": 230, "text": "D"},
{"y": 300, "text": "E"}
],
gy = d3.select("svg").append("g").attr("class", "y axis").attr("transform", "translate(40, 100)"),
yaxis = gy.selectAll("g").data(y_arr);
yaxis.enter()
.append("g")
.attr("transform", function(d, i) { return "translate(0," + (40 + d.y) + ")"})
.attr("style", "opacity:1")
.append("text")
.attr("x", "-8")
.attr("y", 0)
.attr("dy", ".32em")
.attr("text-anchor", "end")
.text(function(d, i) { return d.text; });
和y轴的线:
var yaxis_path = d3.select("svg g.y").append("path")
.attr("stroke", "#000")
.attr("d", "M0 0V370");
我们来看以下作者给出的一个实例, 涉及到d3.js中的很多函数. 如果是初学d3.js, 需要查看文档才能看懂以下代码.
效果图:
具体代码:
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<style>
circle.dot {
fill: steelblue;
}
.axis text {
font: 10px sans-serif;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
<body>
<!--<script src="d3.js"></script>-->
<script src="http://d3js.org/d3.v2.min.js" charset="utf-8"></script>
<script>
var data = [
{x: 10.0, y: 9.14},
{x: 8.0, y: 8.14},
{x: 13.0, y: 8.74},
{x: 9.0, y: 8.77},
{x: 11.0, y: 9.26},
{x: 14.0, y: 8.10},
{x: 6.0, y: 6.13},
{x: 4.0, y: 3.10},
{x: 12.0, y: 9.13},
{x: 7.0, y: 7.26},
{x: 5.0, y: 4.74}
];
var margin = {top: 40, right: 40, bottom: 40, left: 40},
width = 960,
height = 500;
var x = pad(d3.scale.linear()
.domain(d3.extent(data, function(d) { return d.x; }))
.range([0, width - margin.left - margin.right]), 40);
var y = pad(d3.scale.linear()
.domain(d3.extent(data, function(d) { return d.y; }))
.range([height - margin.top - margin.bottom, 0]), 40);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickPadding(8);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickPadding(8);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "dot chart")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", 12);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + y.range()[0] + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
function pad(scale, k) {
var range = scale.range();
if (range[0] > range[1]) k *= -1;
return scale.domain([range[0] - k, range[1] + k].map(scale.invert)).nice();
}
</script>
</body>
</html>
通过查询API文档, 解释一些基本的函数:
domain和range相伴相生, domain用来设置比例尺, 而range用来设置实际的长度. 例如在一个SVG内部(宽度为960), 我们绘制X轴, 则range的范围可以是[0,900], 而domain代表实际的数据(例如5个点, x轴分别为0, 1, 2, 3, 4), 则domain的范围就是[0,4]. 所以第一个点绘制在0处, 中间2绘制在450处, 最后一个点4绘制在900处.
d3.extent: 用来找出数组中的最大值和最小值.
axis().scale: 用来设置比例尺
备注: 此代码无法理解, 需要学习到后面, 多联系, 才可以自己完整写出来.
以上两个例子有难度. 现在来看看具体如何操作数据:
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", x)
.attr("cy", y)
.attr("r", 2.5);
这里我们可能有疑惑, 为什么circle还为存在情况下, 我们却selectAll("circle")呢?
这里是语法糖, 主要代码块应该这样看: selectAll("circle").data(data); 它的作用是将data和DOM元素关联起来, 使用enter()代表进入要递归的每个元素, 然后append()生成此元素. 所以代码实际上应该分开成:
var circle = svg.selectAll("circle")
.data(data);
circle.enter()
.append("circle")
.attr("cx", x)
.attr("cy", y)
.attr("r", 2.5);
Scales&Axes
Data-->Attributes: 属性(attribute/style)是用来控制元素的位置和展现.
Domain-->Range: 用来控制data-space和visual-space.
var x = d3.scale.linear()
.domain([12, 24])
.range([0, 720]);
console.log(x(16)); // 240 = (16 - 12) * ((720 - 0) / (24 - 12)) + 0
我们可以使用d3.max/d3.min获取数组的最大最小值, 也可以使用d3.extent获取数组的最小最大值.
而extent甚至还可以接收一个对象和函数:
var objects = [
{"num": 1},
{"num": 3},
{"num": 2},
{"num": 4}
];
function value(d) { return d.num; }
// [1,4]
console.log(d3.extent(objects, value));
对于颜色, 我们也可以编写:
var x = d3.scale.linear()
.domain([12, 24])
.range(["steelblue", "brown"]);
// #ʚ586
console.log(x(16));
甚至对像素, 我们也可以编写:
var x = d3.scale.linear()
.domain([12, 24])
.range(["0px", "720px"]);
// 240px
console.log(x(16));
我们可以使用interpolate来更改所要显示的格式:
var x = d3.scale.linear()
.domain([12, 24])
.range(["steelblue", "brown"])
.interpolate(d3.interpolateHsl);
// #5f3cb0
console.log(x(16));
实际上, 对于domain/range, 不仅仅只支持两个参数, 允许传递多个参数:
var x = d3.scale.linear()
.domain([-10, 0, 100])
.range(["red", "white", "green"]);
console.log(x(-5)); // #ff8080
console.log(x(50)); // #80c080
对于ordinal来说, 其domain和range是显式一一对应的:
var x = d3.scale.ordinal()
.domain(["A", "B", "C", "D"])
.range([0, 10, 20, 30, 40]);
console.log(x("B")); // 10
console.log(x("f")); // 40
而ordinal经常用于categorical colors:
var x = d3.scale.category20()
.domain(["A", "B", "C", "D"])
console.log(x("B")); // #aec7e8
d3.js提供的颜色:
而对于ordinal来说, 其range会依据domain进行等间距划分:
var x = d3.scale.ordinal()
.domain(["A", "B", "C", "D"])
.rangePoints([0, 720]);
console.log(x("B")); // 240
我们可以通过给定的scale, 创建一个axis:
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
然后将y轴绑定到指定的绘图区间:
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
而通过以上代码生成的html, 实际上大概如下:
由line进行线条的绘制, 而text显示坐标值. 所以我们通常需要为line或path进行style的设置:
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
我们一般使用transforms进行元素的移动:
后记:
1. HTML学习资料:
https://developers.whatwg.org/
https://developer.mozilla.org/zh-CN/
2. SVG学习资料
https://developer.mozilla.org/en-US/docs/Web/SVG
https://github.com/d3/d3/wiki/SVG-Shapes
3. CSS学习资料
https://www.w3.org/TR/selectors/
4. JavaScript学习资料
https://developer.mozilla.org/en-US/docs/Web/JavaScript
http://javascript.crockford.com/
5. D3学习资料
https://github.com/d3/d3/wiki/API-Reference
https://groups.google.com/forum/#!forum/d3-js
http://stackoverflow.com/questions/tagged/d3.js
6. 本学习资料来自:
https://bost.ocks.org/mike/d3/workshop/#0
来源:oschina
链接:https://my.oschina.net/u/1017135/blog/769809