D3.js 进阶教程系列
原文链接 https://ronghuaxueleng.github.io/2016/09/24/d3-D3-js-%E8%BF%9B%E9%98%B6%E6%95%99%E7%A8%8B%E7%B3%BB%E5%88%97/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
事件
在d3入门系列文章中我们介绍过d3选择器,其中有提到选择器为交互添加或移除事件监听器
的方法selection.on
,除了选择器事件外,D3还提供了很多种事件机制,本节我们详细介绍下d3的事件机制
选择器事件
添加事件监听
跟jQuery类似的方法监听事件,传递参数为当前数据d
和索引i
;
如果所选择的元素相同类型的一个事件监听已经注册了,新的监听替换老的;
selection.on('click',function(d,i){
….
});
为相同事件类型注册多个监听器,该类型可以跟一个可选的命名空间,如“click.foo”和“click.bar”。
selection.on('click.foo',function(d,i){
….
});
移除事件监听
传递null删除监听器
selection.on('click', null);
d3.event
jQuery的事件一般是这样的
$('body').on('click', function(ev){
console.log(ev);
});
而在D3中,会有一点小小的不同,事件参数不再是默认的第一个参数,而是使用d3.event
获取
d3.select('body').on('click', function(){
var ev = d3.event;
console.log(ev);
});
d3.mouse(container)
以数组形式返回当前d3.event
相对于指定容器的x和y坐标([11,22]),该容器可以是一个HTML或SVG*容器元素*
需要注意一下容器元素,HTML的容器元素包括div、span、p之类标签,而SVG的容器元素主要是指g
标签
d3.select(‘#demo1').on('click', function(){
console.log(d3.mouse(this)); // ==>返回相对于点击容器自身的坐标,返回值示例:[11,22]
console.log(d3.mouse(d3.select('#demo2'))); // ==>返回相对于ID=demo2容器的坐标,返回值示例:[11,22]
});
d3.touch(container[, touches], identifier)
以数组形式返回当前d3.event
指定触摸标识符相对于指定容器的x和y坐标([11,22]),该容器可以是一个HTML或SVG*容器元素*,如果未指定touches时,touches默认为当前事件的changedTouches。
d3.select(‘#demo1').on(’touchstart', function(){
var ev = d3.event;
console.log(d3.touch(this, ev.changedTouches[0].identifier)); // ==>返回当前容器第1个触摸点的坐标,返回值示例:[11,22]
});
d3.touches(container[, touches])
以双重数组形式返回当前d3.event
相对于指定容器的所有x和y坐标([[11,22],[33,44]]),该容器可以是一个HTML或SVG*容器元素*,如果未指定touches时,touches默认为当前事件的touches。
d3.select(‘#demo1').on(’touchstart', function(){
var ev = d3.event;
console.log(d3.touches(this)); // ==>返回当前容器所有触摸点的坐标,返回值示例:[[11,22],[33,44]]
});
Ajax事件
D3中用d3.xhr来发送ajax请求,对指定的类型添加或者移除事件监听器,支持4种类型事件:
- beforesend – 在请求发送之前,允许自定义标题等来进行设定
- progress – 用来监听请求的过程
- load – 当请求成功的完成之后
- error – 当请求不成功之后,此类型包含4xx和5xx返回值
如果一个相同的类型监听器已经被注册,已存在的监听器就会在新监听器添加之前被移除。为了给同一个事件类型注册多个监听器,那么类型将遵循可选的命名空间,例如load.foo 和 load.bar。为了移除监听器,传递null值为监听器。
监听请求成功数据
d3.xhr('./data.json').on('load', function(d){
console.log(d)
}).send('get');
自定义事件
通过d3.dispatch创建自定义事件,如果事件监听注册了同一个事件类型,已经存在的监听将被删除,然后注册新的监听。为了注册多个事件监听到同一个事件类型,可以为这个typy提供命名空间,如: "click.foo" 和 "click.bar"。同样的,可以通过dispatch.on("click",null) 来移除某个命名空间内注册的所有监听
var dispatch = d3.dispatch('test','mock');
dispatch.on('test', function(text){
alert(text)
})
dispatch.test('this is test event');
拖曳选择事件
通过点击和拖曳来选择一个1维或2维区域,支持3种类型事件:
- brushstart - 鼠标按下时,即mousedown;
- brush - 鼠标移动时,如果范围在改变,即mousemove;
brushend – 鼠标弹起/松开时,即mouseup;
var body = d3.select('body'); var svg = body.append('svg') .attr('width',200) .attr('height',200) .style('background','#eee'); var linear = d3.scale.linear() .domain([0,100]) .range([0,200]); var brush = d3.svg.brush() .x(linear) .y(linear) .on('brush',function(){ console.log(brush.extent()); }); var g = svg.append('g').call(brush);
拖动事件
构造一个新的拖拽行为,使节点可以被移动,支持3种类型事件:
- dragstart - 拖动开始时
- drag - 拖动移动时
dragend - 拖动结束时(放下时)
var drag = d3.behavior.drag() .on("drag", function() { console.log(d3.event.x); console.log(d3.event.y); });
svg.selectAll('circle').call(drag)
缩放事件
构造一个新的缩放行为,使节点可以被缩放,支持3种类型事件:
- zoomstart - 缩放开始时
- zoom - 缩放行为发生时
- zoomend - 缩放行为结束时
事件发生时,d3.event中会包含以下属性:
- scale - 一个数值,即当前的比例;
translate - 一个含有两个数值元素的数组,代表平移向量。
var zoom = d3.behavior.zoom() .on("zoom", function() { console.log(d3.event.scale); });
svg.selectAll('circle').call(zoom)
力导向图布局事件
为力布局注册指定的事件监听器,支持3种类型事件:
- start - 事件开始时
- tick - 事件发生时
end - 事件结束时
var link = vis.selectAll("line") .data(links) enter() .append("line");
var node = vis.selectAll("circle") .data(nodes) .enter() .append("circle") .attr("r", 5);
var force = d3.layout.force() .nodes(nodes) .links(links) .on("tick", function() { link.attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }); });
深入了解transition动画
除了实现简单的属性、样式过渡动画,D3的transition通过tween(自定义补间)、interpolate(插值器)还可以实现非常强大的动画效果,而且更灵活,本节我们来了解一下更多动画细节。
transition.tween(name,factory)
注册一个自定义补间函数,每次执行过渡动画时都会执行这个函数所返回的自定义插值函数,自定义插值函数将传递一个[0,1]区间的参数,参数值随着动画过程逐步从0增加到1,而我们需要做的就是在这个自定义插值函数
中给动画元素赋值,可以是元素属性,也可以是元素样式,这完全由我们决定,通过设置name值,可以在需要时给此name赋值null来停止动画,上一段代码来更好的理解,简单实现一个矩形宽从0到500的动画,:
var body = d3.select('body');
var div = body.append('div').style({width: 0, height:'20px', background: '#eee'});
div
.transition()
.duration(1000)
.tween('width',function(){
return function(t){
var w = Math.floor(t*500)+'px';
d3.select(this).text(w);
d3.select(this).style('width',w);
};
});
setTimeout(function(){div.transition().tween('width',null);},500);
在上例中我们取到新的宽度值后,需要手动调用选择器的style的方法来赋值,感觉有点LOW是不。
其实在D3中有提供styleTween和attrTween方法分别为选择器的样式和属性快速赋值,所以上面的动画可以用styleTween来实现
var body = d3.select('body');
var div = body.append('div').style({width: 0, height:'20px', background: '#eee'});
div
.transition()
.duration(1000)
.styleTween('width',function(){
var that = this;
return function(t){
var w = Math.floor(t*500)+'px';
d3.select(that).text(w);
return w;
};
});
setTimeout(function(){div.transition().tween('width',null);},500);
d3.interpolate(a,b)
上面我们用的是自定义插值函数
来实现过渡动画,D3中为我们提供更强大的内置插值器方法d3.interpolate,通过插值器,我们可以轻松的实现数字、颜色或任意值的过渡,如我们要实现上例中宽度变化的同时,背景颜色由浅变深,字体颜色由深变浅:
var body = d3.select('body');
var div = body.append('div').style({width: 0, height:'20px', background: '#eee'});
div
.transition()
.duration(1000)
.styleTween('width',function(){
var that = this;
return function(t){
var w = Math.floor(t*500)+'px';
d3.select(that).text(w);
return w;
};
})
.styleTween('background',function(){
return d3.interpolate('#eee','#000');
})
.styleTween('color',function(){
return d3.interpolate('#000','#fff');
});
d3.interpolate在执行时会根据参数的类型自动判断以什么样的方式实现过渡,以下是参数类型与具体内置插值器的对应关系
类型 | 内置插值器 |
---|---|
数组 | d3.interpolateArray |
HCL颜色值 | d3.interpolateHcl |
HSL颜色值 | d3.interpolateHsl |
Lab颜色值 | d3.interpolateLab |
RGB颜色值 | d3.interpolateRgb |
数字 | d3.interpolateNumber |
对象 | d3.interpolateObject |
整数 | d3.interpolateRound |
字符串 | d3.interpolateString |
2D矩阵变换 | d3.interpolateTransform |
d3.interpolators
如果内置插值器都不符合需要,那我们可以用d3.interpolators来自定义一个插值器,以一个百分比过渡动画为例:
d3.interpolators.push(function(a, b) {
var re = /^(\d\d\.\d\d)%$/, ma, mb, f = d3.format('05.2f’);
//判断过渡参数是否符合此自定义插值器的要求
if ((ma = re.exec(a)) && (mb = re.exec(b))) {
a = parseFloat(ma[1]);
b = parseFloat(mb[1]) - a;
return function(t) {
return f(a + b * t) + '%';
};
}
});
d3.select('body')
.transition()
.duration(10000)
.tween('text',function(){
return function(t){
d3.select(this).text(d3.interpolate("00.00%", "30.00%")(t));
};
});
用实例细述d3.format的用法
d3.format函数将一个字符串作为输入,并返回一个数字转换为字符串的函数。它使用一种类似"正则表达式”的语法使数字显示为正确的格式。
在D3的官方API中,对d3.format的用法说明非常的简单,甚至于看了几遍都不知道如何使用,本节中我们以循序渐进的方式通过实例与API的结合来细述神奇的d3.format用法,语法中高亮部分
为语法格式需要用到的部分。
空
允许语法为空,可以是空字符串或无参数,直接返回数字字符串,相当于执行.toString();
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
var formatter = d3.format("");
formatter(2) // "2"
var formatter = d3.format();
formatter(2) // "2"
简单类型
在格式语法的结尾处,你可以放置一个数字类型。
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
指数 e
通过在数字对象调用.toExponential
方法将数字以指数方式显示。
d3.format("e")(2000); // "2e+3"
d3.format("e")(2000.0); // "2e+3"
d3.format("e")(20003010); // "2.000301e+7"
d3.format("e")(0.000001); // "1e-6"
整数 d
将数字以整数方式显示,忽略非整数值。
d3.format("d")(20); // "20"
d3.format("d")(1000.0); // "1000"
d3.format("d")(1000.000001); // ""
d3.format("d")(-1.12); // ""
百分比 %
将数字x100并以Math.round方法取整,并在后面添加%显示。
d3.format("%")(1); // "100%"
d3.format("%")(0.999); // "100%"
d3.format("%")(+0.12); // "+12%"
d3.format("%")(-0.12); // "-12%"
科学计数法 s
将数字转换为国际单位制显示。
d3.format("s")(1000); // "1k"
d3.format("s")(0.000001); // "1µ"
d3.format("s")(1000); // "1k"
不常用类型
以下这些类型是我认为在数据可视化中不常用的,了解即可
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
二进制 b
将数字转换为二进制显示。
d3.format("b")(1); // "1"
d3.format("b")(8); // "1000"
d3.format("b")(16)’ // "10000"
d3.format("b")(2012); // "11111011100"
八进制 o
将数字转换为八进制显示。
d3.format("o")(1); // "1"
d3.format("o")(8); // "10"
d3.format("o")(16)’ // "20"
d3.format("o")(2012); // "3734"
十六进制 x
将数字转换为十六进制显示。
d3.format("x")(1); // "1"
d3.format(“x")(8); // "8"
d3.format(“x")(16)’ // "10"
d3.format(“x")(2012); // "7dc"
Unicode编码 c
将数字作为Unicode编码,显示其所代码的字符。
d3.format("c")(1000); // "Ϩ"
与精度(precision)相关的类型
以下3种类型非常类似,一般是跟精度(precision)参数一起使用。
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
普通类型 g
将数字转换普通的数字形式。
d3.format("g")(2000); // "2000"
d3.format("g")(2000.0); // "2000"
d3.format("g")(2000.93010); // "2000.9301"
d3.format("g")(0.910120); // "0.91012"
d3.format("g")(+0.910120); // "0.91012"
d3.format("g")(-0.910120); // "-0.91012"
通过调用数字对象的toPrecision
方法把数字转换为相应精度的值
d3.format(".4g")(1010101); // "1.010e+6"
d3.format(".7g")(1010101); // "1010101"
d3.format(".8g")(1010101); // "1010101.0"
Fixed类型 f
将数字做整数的四舍五入处理。
d3.format("f")(2000); // "2000"
d3.format("f")(2000.0); // "2000"
d3.format("f")(2000.93010); // "2001"
d3.format("f")(0.910120); // "1"
d3.format("f")(+0.910120); // "1"
d3.format("f")(-0.910120); // "-1"
通过调用数字对象的toFixed
方法把数字小数点转换为相应精度的值
d3.format(".4f")(1010101); // "1010101.0000"
d3.format(".7f")(1010101); // "1010101.0000000"
d3.format(".8f")(1010101); // "1010101.00000000"
Rounded类型 r
基本上跟普通类型 g一样;
d3.format("r")(2000); // "2000"
d3.format("r")(2000.0); // "2000"
d3.format("r")(2000.93010); // "2000.93010"
d3.format("r")(0.910120); // "0.91012"
d3.format("r")(+0.910120); // "0.91012"
d3.format("r")(-0.910120); // "-0.91012"
通过调用数字对象的toFixed
方法把数字转换为相应精度的值,超出精度值范围用0代替
d3.format(".4r")(1010101); // "1010000"
d3.format(".7r")(1010101); // "1010101"
d3.format(".8r")(1010101); // "1010101.0"
千位分隔符
在跟货币打交道时,千位分隔符是非常有用的。
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
d3.format(",")(1000000); // "1,000,000"
d3.format(",")(1010101); // "1,010,101"
d3.format(",.4g")(1010101); // "1.010e+6"
d3.format(",.4e")(1010101); // "1.0101e+6"
d3.format(",.4f")(1010101); // "1,010,101.0000"
d3.format(",.2f")(10101); // "10,101.00"
宽度
指定输出时字符串的长度,如果指定值小于数字长度,则以数字长度为准
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
d3.format("8")(1); // " 1"
d3.format("8,.2f")(1); // " 1.00"
d3.format("8g")(1e6); // " 1000000"
补0
在指定输出字符串长度大于数字长度时,不足的部分用0代替
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
d3.format("8")(1234); // " 1234"
d3.format("08")(1234); // "00001234"
d3.format("08.2f")(123.456); // "00123.46"
d3.format("08.3f")(123.456); // "0123.456"
注意:在使用千位分隔符时小心使用
d3.format("09,")(123456); // "0,123,456"
符号
根据语言环境判断显示货币符号,二进制,八进制符号。
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
d3.format("$,")(1250); // "$1,250"
d3.format("$,.2f")(1250); // "$1,250.00"
d3.format("#0b")(125); // "0b1111101"
d3.format("#0o")(125); // "0o175"
d3.format("#0x")(125); // "0x7d"
标记
如何显示正负号标记。
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
- " - " - 只有负数时显示标记。(默认)
- " + " - 正负数都显示标记。
“ " - 负数显示标记,正数显示空格。
d3.format("+")(125); //"+125" d3.format("+")(-125); //"-125" d3.format("-")(125); //"125" d3.format("-")(-125); //"-125" d3.format(" ")(125); //" 125" d3.format(" ")(-125); //"-125"
对齐和填充
在不设置补0时,可以在输出的字符串占位上填充一个任意字符,并可以设置这些字符的位置
语法:[[fill]align][sign][symbol][0][width][,][.precision][type]
d3.format("4>8")(1); //"44444441"
d3.format("4^8")(1); //"44441444"
d3.format("4<8")(1); //"14444444"