D3.js 进阶教程系列

2016-09-24 曹强 更多博文 » 博客 » GitHub »

d3

原文链接 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);

点击查看DEMO

在上例中我们取到新的宽度值后,需要手动调用选择器的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);

点击查看DEMO

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');
    });

点击查看DEMO

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));
  };
});

点击查看DEMO

用实例细述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"

未完待续