D3.js

D3.js - 初体验

D3.js 是一个用动态视觉化显示资料的 js library,透过使用 HTML DOM、SVG 在网页上显示资料,在接触 SVG 的时候不断的接触到 D3.js 的资讯,原本想说要先来玩玩 snap.svg 的,但由于大家都在谈 D3.js,于是就也踏上这条不归路,因为 D3 主要的应用会以 SVG 来表现,趁着之前 SVG 31 天教学的文章刚写完,对 SVG 印象犹新之际,打铁趁热学学 D3.js。

既然下定决心要学了,就要先来了解一下 D3 的故事起源:

D3 是一个缩写,全名叫作 Data-Driven Documents( 资料驱动的文件 ),在 2005 年的时候,Jeffrey Heer、Stuart Card 和 James Landay 推出 prefuse,透过 JAVA 在浏览器中视觉化呈现资料,当时算是一个相当突破的应用,也开启了在 web 里头视觉化操作数据的时代。

两年后 Jeff Heer 又推出了 Flare,也是透过浏览器来呈现视觉化资料,到了 2009 年,Jeff Heer 说服一位刚毕业的学生Mike Bostock,共同开发 Protovis,Protovis 是一个只依赖原生的浏览器,利用 JavaScript 的视觉化检视包,这时候已经慢慢看出 D3 的样子,直到 2011 年,Mike Bostock、Vadim Ogievetsky 和 Jeff Heer 正式推出 D3,作为下一代 Web 视觉化检视,与 Protovis 不同的是,D3 可以直接操作网页文档,展示视觉效果的可能性也更多。

一直到今天,Mike Bostocks 都致力于 D3 的开发和维护, 他的网站 ( http://bost.ocks.org/ ) 以及 github( HTTPs://github.com/mbostock/d3 )也都是 D3 开发者必须要朝圣的地方。

SVG D3 - D3.js 初体验

故事讲完了,来谈点正经的吧!要使用 D3,第一步就是要先上 D3 的官网:http://d3js.org/,官网的 banner 就是一大堆六角形的图片,别怀疑,这些全都是 D3 可以做出来的范例。

SVG D3 - D3.js 初体验

点选上方的 example ( https://github.com/mbostock/d3/wiki/Gallery ),就可以连到 D3 的 github,同时也可以看到满满的范例等着使用。

SVG D3 - D3.js 初体验

随便打开一个范例,感觉都是要写很久的 code 才写得出来的...( 例如:http://www.brightpointinc.com/interactive/political_influence/index.html?source=d3js )

SVG D3 - D3.js 初体验

实际上用了几天,我自己的感觉就像是 jQuery 一样的好用,不过某种程度又更方便了些,不过毕竟是是初体验,没有办法有太多深入的用法介绍,待我来慢慢使用纪录一下吧!

D3.js - 起手式

虽然说对 D3.js 的印象是从图形的表现开始,但 D3.js 真正的强项却是在它的资料处理 ( data ),也因为能够用很简单的方式处理资料,将资料喂给 HTML 或 SVG,才能够轻鬆的长出漂漂亮亮的图形,也才更能将心力放在设计一个完美个图表上,而 D3.js 的 data 也不见得只适用在 SVG 的图形产生,只要是 HTML 的 DOM 也都可以藉由这个方式作成,例如以往要将一百个一千个资料放到 DOM 裡或产出 DOM,必须用上 for 迴圈或是 each 的方式,这部分 D3 也帮我们都解决掉了,真是相当的方便。

在介绍资料处理之前,一定得先来一段 D3.js 的起手式,第一步,挂载 D3.js:

<script src="http://d3js.org/d3.v3.min.js"></script>

挂载完 D3.js 之后,接下来就是要使用 D3.js,与 jquery 的「$」符号很像的地方,要使用 D3,也是要有 d3 开头,以下面的范例来说,就是选取了所有的 p 标籤,跟 jquery 里头的选择器很像 ( $('p') ),而 D3 也可以和 jquery 溷着使用,因为 D3 都会是 d3 开头,基本上并不会有甚么冲突的情形发生。

d3.selectAll('p')

要一层层选择下去的话,也可以这样写:

HTML:

<div>red
    <div>blue</div>
</div>

JS:

d3.select('div').style({
  'color': 'red'
});
d3.select('div div').style({
  'color': 'blue'
});

结果就像这样:

SVG D3 - 起手式

其实说穿了 D3 和 jquery 大同小异,如果已经熟悉 jquery 的写法,D3 用起来应该也很容易理解 ( 当然还是有不太一样的地方要习惯 ),而且 D3 也採用了 「串鍊」式的方法 ( Chaining Methods ),就是用点的一点一点串下去,例如:

d3.select('div').style({
    'color': 'red'
}).select('div').style({
    'color': 'blue'
});

SVG D3 - 起手式

跟上面也会是同样的结果,一直串一直串就对了!然而除了选取器 ( select、selectAll ) 之外,跟 jquery 同样,也可以使用 append 的指令在最后面放入物件或元素,不过要放在前面就要用 insert 而不是 prepend,而且要改变样式必须用 style 而不是 css,要特别注意,下面就用个实际的例子来瞧瞧:

d3.select('body')
  .append('div')
  .html('I am div')
  .style({
  'border':'1px solid #000',
  'width':'120px'
  });

SVG D3 - 起手式

除了塞 HTML 的 DOM 之外,更重要的是可以塞 SVG 的形状,还记得在我之前的文章:SVG 研究之路 (14) - 控制 SVG 的注意事项,因为 SVG 本身不属于 HTML 的标准元素,所以都要用额外的方式才能动态添加内容,但自从有了 D3,就都没有这问题了,直接添加即可,下列程式码会产生一个圆形 ( 如果对 SVG 形状不了解的可以参考:SVG 研究之路 (3) - 基本形状 ):

  d3.select('body')
    .append('svg')
    .attr({
      'width':200,
      'height':200
    });

  d3.select('svg')
    .append('circle')
    .attr({
    'cx':50,
    'cy':50,
    'r':30,
    'fill':'#f90',
    'stroke':'#c00',
    'stroke-width':'5px'
    });

SVG D3 - 起手式

以上就是一些 D3 起手式的介绍,虽然 D3 可以做到和 jquery 差不多的事情,但整体而言还是 jquery 比较方便,因为毕竟 D3 诞生的目的原本就和 jquery 不同,D3 是为了让数据可以用视觉图像化的方式而诞生的,所以上述基本的用法,其实也会跟随着 data 来自动产生,会在之后用一些篇幅仔细分享。

浅谈 D3.js 的资料处理

D3.js 毕竟是把数据做视觉化呈现的 library,所以最强的就在于它的资料处理能力,有别于一般我们要塞资料进 HTML,或要利用资料自动长出元素,不外乎都要用到 for 迴圈或是 each 的方式来进行,但对于 D3 来说,这些基本的功能已经全部包办,甚至连同乱数和排序都内建在里头,因此,再进行利用 D3 画图之前,一定要先搞懂处理数据的方式,所以这篇就来尝试一下 D3 资料处理的基本功能,也作为学习 D3 的一个纪录。

先来看看这个例子,有一个 data 的阵列,要把这个阵列的值分别放到五个 div 里头,过去应该会写个 for 迴圈一一把值丢进去长出来,但在 D3 里头可以这样写 ( 阿对了,忘记说一点,D3 的 script 放在 header,D3 的执行 script 放在 body 里头, 放错位置可就跑不出来了 ) :

var data = [1,2,3,4,5];

d3.select('body').selectAll('div')
  .data(data)
  .enter()
  .append('div')
  .text('ok');

执行的结果应该会在画面上长出五个 ok 分别放在五个 div 里头,就像范例 ( svg-d3-01-data-demo1.html ) 这样,这时候一定会有疑问,设定的 data 是五个数字,怎没有显示出来呢?所以我们要把开发者工具打开来看看,在开发者工具的 console 里头输入: console.log(d3.selectAll('div'));,看看会是什么结果,结果就像下图这样,其实在 selectAll('div').data(data).enter()这行里头,D3 已经悄悄的把资料放到背景,而且也预先塞入 div 里头。

SVG D3 - 浅谈 D3.js 的资料处理

这时侯只需要将程式改成这样,就可以把 data 载入:

var data = [1,2,3,4,5];

d3.select('body').selectAll('div')
  .data(data)
  .enter()
  .append('div')
  .text(function(d){
     return d;
  });

可以点选范例 ( svg-d3-01-data-demo2.html ) 参考,为什么会这样呢?D3 让我们在使用各个方法里头,都可以利用一个匿名函数的 function 来获取存在背景的资料,而这些资料会依序放在 function 的第一个变数里头,因此 return d就可以依序返回该有的数值,也就会按照 1,2,3,4,5 来呈现。

SVG D3 - 浅谈 D3.js 的资料处理

因为有了这个神奇的「d」,就可以做出许多有趣的变化,举例来说,现在有一个全班的成绩,要让不及格的数字是红色,就可以这样写 ( 范例:svg-d3-01-data-demo3.html ):

var data = [38,69,72,42,58,87];

d3.select('body').selectAll('div')
    .data(data)
    .enter()
    .append('div')
    .text(function(d){
        return d;
    }).style({
        'color':function(d){
            if(d<60){
                return 'red'
            }
        }
    });

SVG D3 - 浅谈 D3.js 的资料处理

从上图可以看到,分数不到六十的,都已经被加上了 color:red的 CSS style,如果在以前,感觉好像要写不少行程式,但在 D3 里头,却用没几个字就完成了,但光是会用数字还不稀奇,今天再来试着套用到 div 的宽度上看看会如何。( 范例:svg-d3-01-data-demo4.html )

var data = [38,69,72,42,58,87];

d3.select('body').selectAll('div')
    .data(data)
    .enter()
    .append('div')
    .text(function(d){
        return d;
    }).style({
        'color':function(d){
            if(d<60){
                return 'red'
            }
        },
        'width':function(d){
            return d+'px'
        },
        'margin':'2px 0',
        'background':'#aaa',
    });

SVG D3 - 浅谈 D3.js 的资料处理

由上面几个范例可以看出,D3 对于资料处理的能力,真是有不可言喻的奥妙,但其实 D3 针对数据,其实提供了不少的 API ( https://github.com/mbostock/d3/wiki/API-Reference ),以下来就来稍微看一下 D3 基本的数据处理 API。( 范例:svg-d3-01-data-demo5.html )

var data = [38,69,72,42,58,87];

console.log('min: '+ d3.min(data));         //最小值
console.log('max: '+ d3.max(data));         //最大值
console.log('sum: '+ d3.sum(data));         //总和
console.log('extent: '+ d3.extent(data));   //最小值与最大值
console.log('mean: '+ d3.mean(data));       //平均数
console.log('shuffle: '+ d3.shuffle(data)); //乱数排列

SVG D3 - 浅谈 D3.js 的资料处理

其实 D3 在数据的处理上还有很多方法无法在这篇阐述完成 ( 敝人功力太弱 >_< ),相信熟悉之后一定会更容易处理数据,理解之后也会再一一分享的啦!

D3.js - 绘製线段

在之前的 SVG 研究之路 (4) - Path 基础篇 裡头,详细的列出了 SVG path 的绘製方式,现在我们要来利用 D3.js 的 line() API ,来完成线段 ( line ) 的绘製,且有别于单纯的 SVG 产生 path 不容易放入数据,D3.js 可以根据我们的数据,自动产生对应的线条。

先来了解一下如何利用 D3.js 的 line() 画线,一开始一定要有一些 data,而且这些 data 必须要有 x 座标和 y 座标 ( 因为有「点」才有「线」,点是由 x 与 y 构成 ),因此我们的 data 阵列的值,都会是个「具有 x 与 y 属性的物件」 ( 大括号为物件,内容是属性 ),第一个点的座标就是 data[0].xdata[0].y,依此类推。

var data = [
  {x:10,y:10},
  {x:50,y:100},
  {x:60,y:50},
  {x:100,y:30}
];

有了 data 之后,再来就是要把 data 喂给 SVG 的线条上的每个点,让它们按照 data 长出来,首先先放一个 svg 到 body 里头

  var svg = d3.select('body')
    .append('svg')
    .attr({
      'width': 800,
      'height': 800
    });

然后使用 line().x() 以及 line().y(),让座标由 data 长出来

  var line = d3.svg.line()
    .x(function(d) {
      return d.x;
    })
    .y(function(d) {
      return d.y;
    });

最后就是利用 append 的方式在 svg 里头放入一个 path,d 是用 line(data)将 data 喂给刚刚的 line,如此各个点的座标就会依序长出

  svg.append('path')
    .attr({
      'd': line(data),
      'y': 0,
      'stroke': '#000',
      'stroke-width': '5px',
      'fill': 'none'
    });

结果就会像下图这样:

SVG D3 - 绘製线段

打开开发者工具就可以看到每个点的座标都照 data 画出来了 ( 范例:svg-d3-02-line-demo1.html )

SVG D3 - 绘製线段

从上面的例子,虽然还看不出有多强大,但如果有一百个点要画,就可以靠 D3 帮我们省下不少写座标的功夫,但 line() 特别的地方还不在此,有个很神奇的 .interpolate(),可以帮我们算出各种线段的样式,再来就看一下这个神奇的 .interpolate().interpolate()总共有 13 个模式可以设定,这是十三个模式分别是:

很好,看完上面的十三个模式应该已经晕头转向,完全搞不懂字面上的意思是啥,我也是一模一样的情形,所以这时候就要直接实作看看,应该就能明白箇中道理,因为 linear 刚刚是预设值,也就是刚刚的范例所以就不介绍,直接从 linear-closed 开始 ( data 还是刚刚那一组喔~ ):

SVG D3 - 绘製线段

var line2 = d3.svg.line()
              .x(function(d){
                return d.x;
              })
              .y(function(d){
                return d.y;
              })
              .interpolate('linear-closed');

SVG D3 - 绘製线段

SVG D3 - 绘製线段

SVG D3 - 绘製线段

SVG D3 - 绘製线段

SVG D3 - 绘製线段

SVG D3 - 绘製线段

SVG D3 - 绘製线段

SVG D3 - 绘製线段

var line2 = d3.svg.line()
              .x(function(d){
                return d.x;
              })
              .y(function(d){
                return d.y;
              })
              .interpolate('linear-closed')
              .tension(2);

SVG D3 - 绘製线段

SVG D3 - 绘製线段

SVG D3 - 绘製线段

SVG D3 - 绘製线段

从上面各种模式所产生的线段就可以发现,D3.js 其实已经帮我们解决了很多想像不到的情形,特别是如果今天画出来的线段希望它 smooth 一些,都可以藉由 D3.js 轻鬆实现,不需要再自己去计算 C 或是 Q ( 参考 SVG 研究之路 (4) - Path 基础篇 ),最后实现的范例:svg-d3-02-line-demo2.html

不过 D3.js 的 line() 其实还有不少其他的用法,之后研究再来慢慢分享。^_^

D3.js - 定义比例 ( scale.linear() )

这篇原本要延续上一篇 SVG D3 - 绘製线段,然后立马做一个折线图出来,不过在製作过程中发现有两个重点必须要先阐述,第一个就是比例 Scale,第二个就是座标 Axis,深入研究之后,发现这两个方法在 D3.js 里头学问还颇大的,所以就先独立出来分别描述,完成后再来继续折线图的製作。

什么是比例 Scale 呢?最常见到比例尺的地方应该就是在地图上,例如台湾面积大约 36,188 平方公里,要把台湾塞进一张 A4 大小的地图根本就是天方夜谭,这时候就必须要用到比例了。

SVG D3 - 定义比例 ( scale.linear() )

除了地图,应用比例的原理,在日常生活中也是很常见,例如最近发现全世界最大的恐龙,大约是非洲大象的 14 倍大,或是今天走在路上看到一个「九头身」的美女,亦或是拍照的时候旁边放个十元硬币,都是很简单的比例应用。

SVG D3 - 定义比例 ( scale.linear() )

有点离题了,因此,同样的当我们面对一个折线图或长条图的长宽,超过电脑视窗的画面大小,就必须用比例的方式让图表等比例缩小来适合宽度,在 D3.js 里头,就要使用 scale 这个方法来达成 ( 舖梗舖好长~ XD ),首先来看一下 scale 要怎么来使用,Scale 有分成两大类,第一类是「Quantitative」,主要以数字或日期为比例缩放的依据,第二类是「Ordinal」,则是以自订的名称或标籤为缩放依据,这篇会先介绍第一类「Quantitative」,接着再搭配座标轴「Axis」介绍第二类「Ordinal」。

Quantitative Scale 又分为 linear、pow、log、quantize、threshold、quantile 和 identity,其中又以 linear 最常使用,这篇也将主要介绍 scale.linear() 的用法;scale.linear() 具有以下几个 API 可以使用:

  • linear.domain([numbers])
  • linear.range([values])
  • linear.invert(y)
  • linear.clamp([boolean])
  • linear.rangeRound(values)
  • linear.nice([count])
  • linear.interpolate([factory])
  • linear.ticks([count])
  • linear.tickFormat(count, [format])
  • linear.copy()

来解释一下吧!

一开始要使用 scale.linear ,必须要有一个 domain([number]),里头是一个数字阵列,最少要有两个数字以上才可以,这代表了「原始的资料范围」,在 domain 之后通常都会紧跟着一个 range([values]),内容也是一个「值」的阵列,代表原本 domain 内容阵列的范围,dmmain 具有的阵列长度多少,range 内容阵列的长度也要多少,看解释其实很模煳,所以直接来看一下图片说明,下图的 domain 是原本是 30-50 ( domain([30,50]) ),经过 range 变成了 0-100 ( range([0,100]) ),这时候原本的 40 就会变成 50。

SVG D3 - 定义比例 ( scale.linear() )

上面看起来就像是小叮噹的放大灯,如果将 domain 设为 0-500 ( domain([0,500]) ),经过 range 变成了 0-100 ( range([0,100]) ),这时候就像是小叮噹的缩小灯一样,这时候原本的 250 就会变成 50。

SVG D3 - 定义比例 ( scale.linear() )

用前一篇的 line 来画个折线图试试看,当折线图超过 SVG 的范围就会被裁切,就可以利用 scale 将折线图固定在一定的大小内,如此一来也不用担心数值的变动,因为一定都会以我们自订的比例在范围内显示。原本的折线图,看起来实在有够小。 ( 范例:svg-d3-03-scale-linear-demo1.html )

  var data = [
  {x:0, y:1.89}, 
  {x:1, y:2.77}, 
  {x:2, y:0.86}, 
  {x:3, y:3.45}, 
  {x:4, y:4.13}, 
  {x:5, y:3.59}, 
  {x:6, y:2.33}, 
  {x:7, y:3.79}, 
  {x:8, y:2.61}, 
  {x:9, y:2.15}
  ];

  var width = 240,
    height = 120;

  var s = d3.select('#s');

  s.attr({
      'width': width,
      'height': height,
    }).style({
      'border':'1px solid #000'
    });

  var line = d3.svg.line()
    .x(function(d) {
      return d.x;
    }).y(function(d) {
      return d.y;
    });

  s.append('path')
    .attr({
        'd':line(data),
        'stroke':'#09c',
        'fill':'none'
    });
  </script>

SVG D3 - 定义比例 ( scale.linear() )

套用 scale 的折线图,瞬间放大到适合的大小,套用 scale 的方法很简单,最主要就是要先宣告水平和垂直的 scale 方法,然后把需要套用这个 scale 方法的数值加上 scaleX(d.x)scaleY(d.y),数值就会根据 scale 的定义进行缩放。( 范例:svg-d3-03-scale-linear-demo3.html )

  var scaleX = d3.scale.linear()
                 .range([0,width])
                 .domain([0,9]);

  var scaleY = d3.scale.linear()
                 .range([0,height])
                 .domain([0,5]);

  var line = d3.svg.line()
    .x(function(d) {
      return scaleX(d.x);
    }).y(function(d) {
      return scaleY(d.y);
    });

SVG D3 - 定义比例 ( scale.linear() )

如果换成超过范围的折线图。 ( 范例:svg-d3-03-scale-linear-demo2.html )

  var data = [
  {x:0, y:100}, 
  {x:10, y:154}, 
  {x:20, y:288}, 
  {x:30, y:187}, 
  {x:40, y:235}, 
  {x:50, y:198}, 
  {x:60, y:172}, 
  {x:70, y:134}, 
  {x:80, y:94}, 
  {x:90, y:88}
  ];

SVG D3 - 定义比例 ( scale.linear() )

套用 scale 的折线图,瞬间放大到适合的大小。 ( 范例:svg-d3-03-scale-linear-demo4.html )

  var scaleX = d3.scale.linear()
                 .range([0,width])
                 .domain([0,90]);

  var scaleY = d3.scale.linear()
                 .range([0,height])
                 .domain([0,300]);

  var line = d3.svg.line()
    .x(function(d) {
      return scaleX(d.x);
    }).y(function(d) {
      return scaleY(d.y);
    });

SVG D3 - 定义比例 ( scale.linear() )

SVG D3 - 定义比例 ( scale.linear() )

clamp 的内容是放 true 或 false 的布林值,这只是一个开关的设定,预设是 flase,为什么说是开关呢?因为当我们没有设定或设为 flase 的时候,超过 domain 最大值的数字将仍然按照 range 的比例进行缩放,但若设定为 true,超过最大值的数字一律以最大值呈现。

当数值超过了范围,其实还是受到 range 的影响。( 范例:svg-d3-03-scale-linear-demo6.html )

SVG D3 - 定义比例 ( scale.linear() )

如果将 clamp 设为 true,超过的部分一律以最大值呈现。( 范例:svg-d3-03-scale-linear-demo7.html )

  var scaleX = d3.scale.linear()
    .range([0, width])
    .domain([0, 9])
    .clamp(true);

SVG D3 - 定义比例 ( scale.linear() )

nice 会根据整体 range 的状况,改变函数的 domain,使 domain 内的范围值返回最接近的数,例如 0.986743 就返回 1.0,0.444 就返回 0.45,范例使用一个有套用 nice 一个没有,就可以很明显的看出差异,如果把 console 打开来看,就可以发现套用 nice 的数值明显变了许多。( 范例:svg-d3-03-scale-linear-demo8.html )

  //对照组,没有使用 .nice()
  var scaleX1 = d3.scale.linear()
    .range([0, width])
    .domain([0.123, 9.189]);

  var scaleY1 = d3.scale.linear()
    .range([height, 0])
    .domain([0.123, 5.567]);

  var line1 = d3.svg.line()
    .x(function(d) {
      return scaleX1(d.x);
    }).y(function(d) {
      return scaleY1(d.y);
    });

  //实验组,使用 .nice()
  var scaleX2 = d3.scale.linear()
    .range([0, width])
    .domain([0.123, 9.189]).nice();

  var scaleY2 = d3.scale.linear()
    .range([height, 0])
    .domain([0.123, 5.567]).nice();

  var line2 = d3.svg.line()
    .x(function(d) {
      return scaleX2(d.x);
    }).y(function(d) {
      return scaleY2(d.y);
    });

  s.append('path')
    .attr({
      'd': line1(data),
      'stroke': '#f66',
      'fill': 'none'
    });

  s.append('path')
    .attr({
      'd': line2(data),
      'stroke': '#09c',
      'fill': 'none'
    });

SVG D3 - 定义比例 ( scale.linear() )

在 Math 里头 round 是返回最接近的整数,因此若我们将 range 换成 rangeRound,那么返回的数值就不会是小数而是整数,可以从 console 看出原本的小数单位,都已经变成了整数。( 范例:svg-d3-03-scale-linear-demo9.html )

  //对照组,range()
  var scaleX1 = d3.scale.linear()
    .range([0, width])
    .domain([0, 9]);

  var scaleY1 = d3.scale.linear()
    .range([120.967, 0.678])
    .domain([0, 5]);

  var line1 = d3.svg.line()
    .x(function(d) {
      return scaleX1(d.x);
    }).y(function(d) {
      return scaleY1(d.y);
    });

  //实验组,rangeRound()
  var scaleX2 = d3.scale.linear()
    .rangeRound([0, width])
    .domain([0, 9]);

  var scaleY2 = d3.scale.linear()
    .rangeRound([120.967, 0.678])
    .domain([0, 5]);

  var line2 = d3.svg.line()
    .x(function(d) {
      return scaleX2(d.x);
    }).y(function(d) {
      return scaleY2(d.y);
    });

SVG D3 - 定义比例 ( scale.linear() )

了解了 domain 和 range 之后,再来谈谈 linear.invert(y),顾名思义,这就是让套用之后的数字反转回原本的数字,看到以下的例子,一开始的 a1 经过 range 之后,变成了,然后再经过 invert 就变回原本的数字。( 范例:svg-d3-03-scale-linear-demo10.html )

console.log(scaleX(2.5));             //得到 66.66666666666667 
console.log(scaleX.invert(66.6667));  //得到 2.50000125

SVG D3 - 定义比例 ( scale.linear() )

ticks 和 tickFormat 通常都会一起看,ticks 会根据内容数值的范围,按照 count 的数量来做切割,取出最适当的数值区间,预设值为 10,不过若按照数字切割出来的范围不适当,有时不一定会按照我们所设的数字切割范围,而 tickFormat 就是可以设定数值的格式,这两者在 Axis 座标里头比较有用,这裡可以使用 console 看出差异。( 范例:svg-d3-03-scale-linear-demo11.html )

console.log(
  scaleX.ticks(10)
  );
console.log(
  scaleX.ticks(10).map(scaleX.tickFormat(1,"%"))
  );
//格式可以参考 https://github.com/mbostock/d3/wiki/Formatting#d3_format

SVG D3 - 定义比例 ( scale.linear() )

copy 很容易理解,就单纯是複製一份 range ,而不会影响到原本的 range。( 范例:svg-d3-03-scale-linear-demo12.html )

   var scaleX2 = scaleX1.copy();

   var scaleY2 = d3.scale.linear()
    .range([height, 0])
    .domain([0, 10]);

   var line2 = d3.svg.line()
    .x(function(d) {
      return scaleX2(d.x);
    }).y(function(d) {
      return scaleY2(d.y);
    });

SVG D3 - 定义比例 ( scale.linear() )

还记得在 SVG D3 - 绘製线段 有提过 .interpolate() ,在 linear.interpolate 其实和之前提过的不太一样,主要是内容的 factory 是使用以下的 API 进行 ( 参考 D3 API Reference ),详细的用法之后用到了再进行介绍 。

* d3.interpolateNumber
* d3.interpolateRound
* d3.interpolateString
* d3.interpolateRgb
* d3.interpolateHsl
* d3.interpolateLab
* d3.interpolateHcl
* d3.interpolateArray
* d3.interpolateObject
* d3.interpolateTransform
* d3.interpolateZoom
* d3.interpolators

以上就是 D3.js 的 scale.linear() 介绍,scale.linear() 也是 Quantitative Scale 最常使用的,最后也列出 Quantitative Scale 裡其他的 API,该如何使用这边就不多做说明,之后有机会用到就会慢慢介绍囉。

D3.js - 座标轴 ( Axis )

藉由上一篇了解了最常用的 scale,再来就要谈谈座标轴 Axis,在 D3.js 里头,座标轴与 scale 几乎是如影随形地出现,毕竟既然是要用来显示座标,就一定会出现在画面裡,也因此必须要用 scale 才能够让座标轴按照比例大小摆放。至于什么是座标轴 Axis 呢?很简单就是水平 x 轴和垂直 y 轴,上面会有刻度,前使用 illustrator 慢慢刻画刻度,常常必须刻到天花地老,如果会用 Excel,就可以让 Excel 产生大家都会的一百零一种刻度,今天利用 D3.js,我们就可以自己做出自己的图表刻度。

首先来看看 Axis 有哪些 API 可以使用:

  • axis.scale
  • axis.orient
  • axis.ticks
  • axis.tickValues
  • axis.tickSize
  • axis.innerTickSize
  • axis.outerTickSize
  • axis.tickPadding
  • axis.tickFormat

整体而言,Axis 比 scale 容易理解得多,相关的 API 基本上也很方便使用,以下就来一一解释说明:

第一个 API ,是告诉我们,要让 axis 按照 scale 的比例缩放,不过光是知道这点还是不够,要如何才能将 asix 加入图表呢?下面的范例,我们拿上一篇的折线图来当作示范,可以看到裡面多了两个名为 axisX 和 axisY 的方法, 最下方让 SVG append 一个 g,然后 call axisX 方法与 axisY 方法,就可以产生座标轴,而 ticks 的用法和 scale 差不多,就是会按照上面设定的数字进行对应的区隔,不过同样的,若区隔不适当则不一定会按照我们的数字进行。 ( 范例:svg-d3-04-axis-demo0.html )

  var data = [
  {x: 0,y: 1.89}, 
  {x: 1,y: 2.77}, 
  {x: 2,y: 0.86}, 
  {x: 3,y: 3.45}, 
  {x: 4,y: 4.13}, 
  {x: 5,y: 3.59}, 
  {x: 6,y: 2.33}, 
  {x: 7,y: 3.79}, 
  {x: 8,y: 2.61}, 
  {x: 9,y: 2.15}
  ];

  var width = 240,
    height = 120;

  var s = d3.select('#s');

  s.attr({
    'width': '300',
    'height': '180'
  }).style({
    'border': '1px dotted #aaa'
  });

  var scaleX = d3.scale.linear()
    .range([0, width])
    .domain([0, 9]);

  var scaleY = d3.scale.linear()
    .range([height, 0])
    .domain([0, 5]);

  var line = d3.svg.line()
    .x(function(d) {
      return scaleX(d.x);
    }).y(function(d) {
      return scaleY(d.y);
    });

  var axisX = d3.svg.axis()
    .scale(scaleX)
    .ticks(10);

  var axisY = d3.svg.axis()
    .scale(scaleY)
    .ticks(10);

  s.append('path')
    .attr({
      'd': line(data),
      'stroke': '#09c',
      'fill': 'none'
    });

  s.append('g')
   .call(axisX);  //call axisX

  s.append('g')
   .call(axisY);  //call axisY

SVG D3 - 座标轴 ( Axis )

如果只是单纯的设定 axis.scale 和 axis.ticks,出来的长相一定跟上面那张图一样莫名其妙,文字挤在一起!?座标轴在最上方!?这时候就必须要用 axis.orient 来定义座标标籤的上下左右位置,只要填入 top、right、bottom 或 left 就可以,上下通常针对 x 轴座标标籤,左右则是针对 y 轴座标标籤。( 范例:svg-d3-04-axis-demo1.html )

  var axisX = d3.svg.axis()
    .scale(scaleX)
    .orient("bottom")
    .ticks(10);

  var axisY = d3.svg.axis()
    .scale(scaleY)
    .orient("left")
    .ticks(10);

SVG D3 - 座标轴 ( Axis )

SVG D3 - 座标轴 ( Axis )

不过从上图可以看到,文字好像都变成空心的,主要因为把整个 g 内部的形状都套用了 'fill':'none'以及 'stroke':'#000'的属性,因此文字就变成只有外框,没有填满的外框字,为了改善,我们必须要再单独针对 text 的属性作设定,同时调整它的 style,看起来就会漂亮许多。( 范例:svg-d3-04-axis-demo3.html )

  s.append('g')
   .call(axisX)
   .attr({
    'fill':'none',
    'stroke':'#000',
    'transform':'translate(35,'+(height+20)+')' 
   }).selectAll('text')
   .attr({
    'fill':'#000',
    'stroke':'none',
   }).style({
    'font-size':'11px'
   });

  s.append('g')
   .call(axisY)
   .attr({
    'fill':'none',
    'stroke':'#000',
    'transform':'translate(35,20)'
   }).selectAll('text')
   .attr({
    'fill':'#000',
    'stroke':'none',
   }).style({
    'font-size':'10px'
   });

SVG D3 - 座标轴 ( Axis )

如果把 axisX 的 orient 修改为 top ,座标标籤就会跑到上方,同理修改 axisY 的 orient 为 right 就会跑到右方。

SVG D3 - 座标轴 ( Axis )

上一篇 scale 里头提过 tickFormat,在 Axis 里头也有 tickFormat,不过用法不太相同,scale 的是套用 D3.js 的格式化,输入 % 或 p 就会自动转换成 100% 起跳的数值,而在 Axis 里头却是真的在原本的数值后方加上格式,下面的范例在 tickFormat 裡头家上了一个 function(d){return d+'n';},因为套用了 scale,所以 d 就会自动返回 d.x 或 d.y,可想而知,在后方加上 n 或 % ,就会产生有单位的座标轴,不过如果 tickFormat(""),就会清空座标轴数值,要小心。( 范例:svg-d3-04-axis-demo4.html )

  var axisX = d3.svg.axis()
    .scale(scaleX)
    .orient("bottom")
    .ticks(10)
    .tickFormat(function(d){return d+'n';});

  var axisY = d3.svg.axis()
    .scale(scaleY)
    .orient("left")
    .ticks(5)
    .tickFormat(function(d){return d+'%';});

SVG D3 - 座标轴 ( Axis )

如果要换成 scale 的 tickFormat 思考的话,就要改成下方的范例这样,% 就会变成百位数的百分比:

  var axisX = d3.svg.axis()
    .scale(scaleX)
    .orient("bottom")
    .ticks(10)
    .tickFormat(d3.format(",%"));

SVG D3 - 座标轴 ( Axis )

tickSize 表达的是座标轴上刻度线条的尺寸,包含了内部线段和和最外边的线段,共两个数值,而这两个数值也各自独立出来,所以其实直接用 tickSize 就可以都满足,座标轴上预设的刻度长度是 6,数字负值往座标内缩,正值往座标外长出去,利用 tickSize,我们可以轻鬆地做出座标的内格线 ( Grid ) ,做法就是新增另外两组 g 来放座标格线的 axis,然后让刻度尺寸长度和图表宽高一样就可以,而且这裡头使用刚刚讲的 tickFormat(""),就不会有额外的座标标籤产生。( 范例:svg-d3-04-axis-demo5.html )

  var axisXGrid = d3.svg.axis()
    .scale(scaleX)
    .orient("bottom")
    .ticks(10)
    .tickFormat("")
    .tickSize(-height,0);

  var axisYGrid = d3.svg.axis()
    .scale(scaleY)
    .orient("left")
    .ticks(10)
    .tickFormat("")
    .tickSize(-width,0);

  s.append('g')
   .call(axisXGrid)
   .attr({
    'fill':'none',
    'stroke':'rgba(0,0,0,.1)',
    'transform':'translate(35,'+(height+20)+')' 
   });

  s.append('g')
   .call(axisYGrid)
   .attr({
    'fill':'none',
    'stroke':'rgba(0,0,0,.1)',
    'transform':'translate(35,20)'
   });

SVG D3 - 座标轴 ( Axis )

刚刚我们的座标轴标籤都是由 D3.js 自动产生,虽然可以使用 ticks 作一些区隔,但也常常不受控制,因此如果要自订我们自己的间格区间,就可以使用 tickValues 来完成,tickValues 的内容是一个阵列,放上阵列之后就会按照我们设定的阵列进行。( 范例:svg-d3-04-axis-demo6.html )

  var axisX = d3.svg.axis()
    .scale(scaleX)
    .orient("bottom")
    .tickValues([1,3,5,7,9])
    .tickFormat(function(d){return d+'n';});

SVG D3 - 座标轴 ( Axis )

改变区间就会看到不同的效果。( 范例:svg-d3-04-axis-demo7.html )

 var axisX = d3.svg.axis()
    .scale(scaleX)
    .orient("bottom")
    .tickValues([1,2,6,7,9])
    .tickFormat(function(d){return d+'n';});

SVG D3 - 座标轴 ( Axis )

padding 很简单不太需要解释,就是座标标籤和座标轴之间的间距,直接用范例玩玩看囉!( 范例:svg-d3-04-axis-demo8.html )

  var axisX = d3.svg.axis()
    .scale(scaleX)
    .orient("bottom")
    .tickValues([1,3,5,7,9])
    .tickFormat(function(d){return d+'n';})
    .tickPadding(-20);

  var axisY = d3.svg.axis()
    .scale(scaleY)
    .orient("left")
    .ticks(5)
    .tickFormat(function(d){return d+'%';})
    .tickPadding(15);

SVG D3 - 座标轴 ( Axis )

以上就是 Axis 的基本用法,会使用座标轴之后,就可以做出一大堆实用的图表囉!^_^

D3.js - 区域 ( area )

理解了 line、scale 和座标轴之后,再来要介绍与 area 这个方法,area 就像字面翻译一样,可以绘製一个区域,同时也可以像 line 一样的设定 interpolate ,因此搭配 line 一起使用,就可以画出具有区域颜色的折线图,相当的有意思。

一如往常,先来看看 area 有哪些 API 可以使用:

  • area.x
  • area.x0
  • area.x1
  • area.y
  • area.y0
  • area.y1
  • area.interpolate
  • area.tension
  • area.defined

一开始的 x、x0、x1、y、y0、y1 这六个 API 是要一併处理的,这几个 API 是专门处理座标的 API,通常x、y0、y1 互相搭配,y、x0、x1 互相搭配,举例来说,若我们把 x 和 y1 带入一串数据 ( 不是阵列,要用 data 的方式带入 ),y0 固定,那么就会画出一个区域的图形。( svg-d3-05-area-demo1.html )

  var data = [
  {x:0, y:18}, 
  {x:20, y:27}, 
  {x:40, y:56}, 
  {x:60, y:34}, 
  {x:80, y:41}, 
  {x:100, y:35}, 
  {x:120, y:100}, 
  {x:140, y:37}, 
  {x:160, y:26}, 
  {x:180, y:21}
  ];

  var width = 240,
    height = 120;

  var s = d3.select('#s');

  s.attr({
      'width': width,
      'height': height,
    });

  var area = d3.svg.area()
      .x(function(d) { return d.x; })
      .y0(0)
      .y1(function(d) { return d.y; });

    s.append('path')
      .attr({
        '   d':area(data),
            'stroke':'#c00',
            'fill':'rgba(255,0,0,.3)',
            'transform':'translate(2,2)'
      });

SVG D3 - 区域 ( area )

如果我们将阵列换成垂直的,再改一下 area 的 API,方向就会变成垂直的。( svg-d3-05-area-demo2.html)

  var area = d3.svg.area()
      .y(function(d) { return d.y; })
      .x0(0)
      .x1(function(d) { return d.x; });

SVG D3 - 区域 ( area )

如果要比较简单来思考的话,y0 和 y1 可以分别看成是上方 y 座标与下方 y 坐标,x0、x1 可以看成左方的 x 座标与右方的 x 座标,基本上一定要有 x 一组数据搭配 y0、y1 或 y 一组数据搭配 x0、x1,因此最重要的其实就是必须要有一组座标来产生 area,前面使用了固定 x0 或固定 y0 的方式,就可以做出折线图的区域效果,当然,如果我们数据,就会产生非常有趣的形状。( svg-d3-05-area-demo3.html )

  var data = [
  {x:0, y0:0, y1:20},
  {x:20, y0:10, y1:30},
  {x:40, y0:30, y1:80},
  {x:60, y0:60, y1:70},
  {x:80, y0:40, y1:40},
  {x:100, y0:40, y1:20},
  {x:120, y0:50, y1:30},
  {x:140, y0:50, y1:40},
  {x:160, y0:40, y1:20},
  ];

  var area = d3.svg.area()
      .x(function(d) { return d.x; })
      .y0(function(d) { return d.y0; })
      .y1(function(d) { return d.y1; });

SVG D3 - 区域 ( area )

这两个 API 和 line() 的用法其实一模一样,可以参考 SVG D3 - 绘製线段,这两者的目的在于根据我们的数据,描绘出有 B-spline 或 Cardinal spline 或 cubic interpolation 的边缘线段,这裡我们拿刚刚范例 1 来做比较,实验组套用了 .interpolate('bundle')。( svg-d3-05-area-demo4.html )

 //对照组
  var area1 = d3.svg.area()
      .x(function(d) { return d.x; })
      .y0(0)
      .y1(function(d) { return d.y; });

  //实验组
  var area2 = d3.svg.area()
      .x(function(d) { return d.x; })
      .y0(0)
      .y1(function(d) { return d.y; })
      .interpolate('bundle');

    s.append('path')
      .attr({
        'd':area1(data),
        'stroke':'#00c',
        'fill':'rgba(0,0,255,.1)',
        'transform':'translate(2,2)'
      });
    s.append('path')
      .attr({
        'd':area2(data),
        'stroke':'#c00',
        'fill':'rgba(255,0,0,.3)',
        'transform':'translate(2,2)'
      });

SVG D3 - 区域 ( area )

defined 就式定义那些资料该出现或不该出现,举例来说可以定义资料大于多少才出现,或资料是偶数才能出现...等之类,下面的范例让区域只显示 x 不是三的倍数的资料。 ( svg-d3-05-area-demo5.html )

  //对照组
  var area1 = d3.svg.area()
      .x(function(d) { return d.x; })
      .y0(0)
      .y1(function(d) { return d.y; });

  //实验组
  var area2 = d3.svg.area()
      .x(function(d) { return d.x; })
      .y0(0)
      .y1(function(d) { return d.y; })
      .defined(function(d) { return d.x%3 != 0; });

SVG D3 - 区域 ( area )

理解了 area 的用法之后,现在要来把之前的 line()、scale()、Axis() 和 area() 全部结合在一起,做一个票漂亮亮的图表,第一步,直接参考上一篇的 范例 拿来用,然后我们直接多新增 area 进去,就可以做出具有区域的折线图表,程式码有点多,不过都是之前介绍过的,所以直接点开范例看看吧。( svg-d3-05-area-demo6.html )

SVG D3 - 区域 ( area )

只有一个折线图不稀奇,可以放超过一个折线图,看起来整个图表的样貌就更为完善囉!( svg-d3-05-area-demo7.html )

资料长这样:

  var data = [
  {x:0, y:38, z:28}, 
  {x:20, y:27, z:15}, 
  {x:40, y:56, z:39}, 
  {x:60, y:34, z:45}, 
  {x:80, y:41, z:88}, 
  {x:100, y:35, z:74}, 
  {x:120, y:100, z:55}, 
  {x:140, y:57, z:75}, 
  {x:160, y:36, z:44}, 
  {x:180, y:41, z:30}
  ];

两条线段两个区域:

  //line
  var line1 = d3.svg.line()
    .x(function(d) {
      return scaleX(d.x);
    }).y(function(d) {
      return scaleY(d.y);
    });

  var line2 = d3.svg.line()
    .x(function(d) {
      return scaleX(d.x);
    }).y(function(d) {
      return scaleY(d.z);
    });

  //area
  var area1 = d3.svg.area()
  .x(function(d) { return scaleX(d.x); })
  .y0(height)
  .y1(function(d) { return scaleY(d.y); });

  var area2 = d3.svg.area()
  .x(function(d) { return scaleX(d.x); })
  .y0(height)
  .y1(function(d) { return scaleY(d.z); });

  s.append('path')
    .attr({
      'd':line1(data),
      'stroke':'#06c',
      'fill':'none',
      'transform':'translate(35,20)' 
    });

  s.append('path')
  .attr({
    'd':area1(data),
    'fill':'rgba(0,150,255,.1)',
    'transform':'translate(35,20)' 
  });

  s.append('path')
    .attr({
      'd':line2(data),
      'stroke':'#c00',
      'fill':'none',
      'transform':'translate(35,20)' 
    });

  s.append('path')
  .attr({
    'd':area2(data),
    'fill':'rgba(255,0,0,.1)',
    'transform':'translate(35,20)' 
  });

SVG D3 - 区域 ( area )

以上就是 D3.js area 的基本介绍,相信理解之后,就可以做出更多有趣的图表囉!最后,这篇文章的起始图片,取自三星的 S3 广告,总共有两部,都超讚的,我很喜欢,分享给大家。^_^

SVG D3 - 区域 ( area )
http://youtu.be/zyMfpJh3h4A

SVG D3 - 区域 ( area )
http://youtu.be/kF0yk9q2hp0

D3.js - 巢狀數據結構 ( d3.nest )

之前在 SVG D3.js - 淺談 D3.js 的資料處理 提過基本的數據處理方法,這陣子又研究了一下 d3.js 的 nest,這篇就來介紹一下這個很有意思的數據處理方法:d3.nest。

nest 就是「巢」的意思 ( 不是潮喔~ 雖然 d3.js 好像有點潮 ),透過 d3.nest 的轉換,可以將原本的數據變成以 key 和 value 為主的巢狀結構,並且可以針對巢狀結構的每個節點進行排序,而所得到的巢狀結構數據,可以在程式裡重複使用而不會影響到原本的數據。

光是看文字其實是搞不清楚的,直接用個簡單的範例來說明一下,下面是一個 json 的檔案格式,是四個班級學生的得分,分別有 name、score 和 type。

var data = [
  {name:'Tom'   , score:98, type:'class1' , sex:'man'},
  {name:'Jack'  , score:72, type:'class2' , sex:'man'},
  {name:'Owen'  , score:93, type:'class3' , sex:'man'},
  {name:'Mark'  , score:41, type:'class4' , sex:'man'},
  {name:'Marry' , score:67, type:'class4' , sex:'woman'},
  {name:'Rock'  , score:59, type:'class2' , sex:'man'},
  {name:'Jason' , score:83, type:'class1' , sex:'man'},
  {name:'Peter' , score:47, type:'class3' , sex:'man'},
  {name:'Cherry', score:53, type:'class1' , sex:'woman'},
  {name:'Jean'  , score:68, type:'class4' , sex:'woman'}
];

這時候利用 d3.nest,把 type 設為 key,這時候 value 就會以 type 為樹狀結構的節點往下長,這裡我們先使用了 d3.nest 的 key 和 entries 這兩個 API,key 主要讓我們作為節點使用,把要做為節點的值以 key 的方法長出來,最後用 entries 把 data 丟進去。( 範例:svg-d3-06-data-nest-demo1.html,打開開發者工具看 console 喔~ )

var a = d3.nest()
          .key(function(d){return d.type;})
          .entries(data);

console.log(a);

SVG D3.js - 巢狀數據結構 ( d3.nest )

同樣的道理,如果再把 sex 設為第二層 key,就會再依據 sex 長出一層。( 範例:svg-d3-06-data-nest-demo2.html )

var a = d3.nest()
        .key(function(d){return d.type;})
        .key(function(d){return d.sex;})
            .entries(data);

console.log(a);

SVG D3.js - 巢狀數據結構 ( d3.nest )

其實這就是 d3.nest 最基本的用法,用這個方法,就可以很輕鬆的把一堆資料分類,篩選出所需要的東西,但 d3.nest 還有其他的 API,這裡先列出 d3.nest 具有的 API,然後再來一個個看看它們功用:

  • nest.sortKeys(comparator)
  • nest.sortValues(comparator)
  • nest.rollup(function)
  • nest.map(array[, mapType])

這是進行排序的 API,在 sortKeys 內是放入排序的比較函式,不過這裡可以先使用d3.descending降冪或d3.aescending升冪來進行簡單的判斷。( 範例:svg-d3-06-data-nest-demo3.html )

var a = d3.nest()
        .key(function(d){return d.score;})
        .sortKeys(d3.descending)  //降冪
            .entries(data);

var b = d3.nest()
        .key(function(d){return d.score;})
        .sortKeys(d3.ascending)  //升冪
            .entries(data);

SVG D3.js - 巢狀數據結構 ( d3.nest )

而 sortKeys 是針對所連接的值進行排序作業,以下面的例子來說,並不會把 type 排序,而是會把 type 內的 score 排序。( 範例:svg-d3-06-data-nest-demo4.html )

var a = d3.nest()
        .key(function(d){return d.type;})
        .key(function(d){return d.score;})
        .sortKeys(d3.descending)
            .entries(data);

var b = d3.nest()
        .key(function(d){return d.type;})
        .key(function(d){return d.score;})
        .sortKeys(d3.ascending)
            .entries(data);

SVG D3.js - 巢狀數據結構 ( d3.nest )

當然如果不使用 d3 本身的升冪與降冪,也可以使用自定義的方式進行排序,例如下面的範例,就會把分數大於六十的排在上面或排在下面。( 範例:svg-d3-06-data-nest-demo5.html )

var a = d3.nest()
        .key(function(d){return d.score;})
        .sortKeys(function(a){return a<60;})
            .entries(data);

var b = d3.nest()
        .key(function(d){return d.score;})
        .sortKeys(function(a){return a>60;})
            .entries(data);

SVG D3.js - 巢狀數據結構 ( d3.nest )

SVG D3.js - 巢狀數據結構 ( d3.nest )

rollup 翻譯起來是「彙總」的意思,不過這裡翻譯成彙總容易與「sum」有所混淆,rollup 其實是將 value 內的所有東西,根據我們所下的條件,彙總成一個 value,而不是像 sum 一樣,可以把內容的所有數字相加起來得到一個總和,必須要有所區隔,舉例來說,如果我們寫成下面的範例這樣,彙總的 value 就會得到陣列的長度。( 範例:svg-d3-06-data-nest-demo7.html )

var a = d3.nest()
        .key(function(d){return d.type;})
        .rollup(function(d){return d.length;})
        .entries(data);

SVG D3.js - 巢狀數據結構 ( d3.nest )

換個寫法,也可以得到內容所有分數的總和 ( 範例:svg-d3-06-data-nest-demo8.html ):

var a = d3.nest()
        .key(function(d){return d.type;})
        .rollup(function(d){return d3.sum(d,function(dd){return dd.score;});})
        .entries(data);

SVG D3.js - 巢狀數據結構 ( d3.nest )

map 與 entries 其實有點類似,就是把 data 的值丟進去 nest 裏頭處理,不過 map 丟進去的出來會變成物件,entries 丟進去的出來會變成陣列,這其實從 console 裏頭可以很明顯的看出來,至於要用 map 還是 entries ,就看這份 data 的使用情境來決定囉。 ( 範例:svg-d3-06-data-nest-demo9.html )

var a = d3.nest()
        .key(function(d){return d.sex;})
        .key(function(d){return d.type;})
            .entries(data);

var b = d3.nest()
        .key(function(d){return d.sex;})
        .key(function(d){return d.type;})
            .map(data);

SVG D3.js - 巢狀數據結構 ( d3.nest )

以上就是 d3.nest 的用法介紹,基本上因為 d3.js 已經有相當豐富的圖表讓大家使用,因此如何利用巢狀結構,做出適合圖表的數據,就變成使用者要學習的了。

D3.js - 阵列数据地图 ( d3.map )

d3.map 这个其实我找不到合适的翻译,只好暂时用我的破英文翻译成「阵列数据地图」,虽然有地图两个字,但跟实际的地图却是没啥关联性,主要是利用 d3.map 的 API,可以帮助我们把一连串的数据做转换,与之前提过的巢状数据处理 ( SVG D3.js - 巢状数据结构 ( d3.nest ) ) 有异曲同工之妙,d3.map 会根据我们所下的条件,把特定的值取出成为 key 而成为一个新的阵列,也可藉由 get、has 或 set 的 API 对数据做判断或重新设定,在许多的 D3.js 图形与数据的处理中,d3.map 都扮演了相当中要的脚色。

要处理数据,就一定要先有数据,以下是这篇文章会用到的范例数据:

var data = [
  {name:'Tom'   , type:['a','b','c']},
  {name:'Jack'  , type:['b,c']},
  {name:'Owen'  , type:['c','d','e']},
  {name:'Mark'  , type:['a','c']},
  {name:'Marry' , type:['a','b','c','d']},
  {name:'Rock'  , type:['a','c']},
  {name:'Jason' , type:['b','c']},
  {name:'Peter' , type:['a','b','c']},
  {name:'Cherry', type:['c','d']},
  {name:'Jean'  , type:['a','c','d']}
];

而这篇将会介绍 d3.map 的 API 如下:

  • d3.map([object][, key])
  • map.has(key)
  • map.get(key)
  • map.set(key, value)
  • map.remove(key)
  • map.keys()
  • map.values()
  • map.size()
  • map.empty()
  • map.forEach(function)

这应该是 d3.map 里头最常用到的 API,跟 d3.nest 类似,将数据分为 keys 和 values 两种,从 API 带入的值可以看到,将物件丢进去之后,设定 key 是什么,最后就会产生一个以这个 key 为基础的阵列,以下面的例子来说,可以很明显的看出来设定的 key 不同,出来的阵列也不相同,但很神奇的,使用 d3.map 所创造出的物件阵列,都已经帮我们排序排好了。( 范例:svg-d3-07-data-map-demo1.html )

var i = d3.map(data,function(d){return d.name;});
var j = d3.map(data,function(d){return d.type;});

console.log(i);
console.log(j);

SVG D3.js - 阵列数据地图 ( d3.map )

这个 API 回传的是 true 或 false,主要是找看看列出来的 key 当中有没有我们想要的值,如果有,回传 true,没有则回传 false。( 范例:svg-d3-07-data-map-demo2.html )

var i = d3.map(data,function(d){return d.name;});

console.log(i.has('Tom'));   // true
console.log(i.has('Rock'));  // true
console.log(i.has('rock'));  // false
console.log(i.has('oxxo'));  // false

get 顾名思义就是将这个 key 提取出变成独立的物件。( 范例:svg-d3-07-data-map-demo3.html )

var i = d3.map(data,function(d){return d.name;});

console.log(i.get('Tom'));  
console.log(i.get('Owen')); 
console.log(i.get('oxxo'));

SVG D3.js - 阵列数据地图 ( d3.map )

设定某个 key 的值,但置换后并不会影响到原本的值。( 范例:svg-d3-07-data-map-demo4.html )

var i = d3.map(data,function(d){return d.name;});

console.log(i.set('Tom','hello world?!')); 
console.log(i);  
console.log(data);

SVG D3.js - 阵列数据地图 ( d3.map )

SVG D3.js - 阵列数据地图 ( d3.map )

列出所有的 key 成为一个阵列。( 范例:svg-d3-07-data-map-demo6.html )

var i = d3.map(data,function(d){return d.name;});
var j = i.keys();

console.log(j);

SVG D3.js - 阵列数据地图 ( d3.map )

列出所有的 value 成为一个阵列。( 范例:svg-d3-07-data-map-demo7.html )

var i = d3.map(data,function(d){return d.name;});
var j = i.values();

console.log(j);

SVG D3.js - 阵列数据地图 ( d3.map )

跟我们在写 length 相同,回传 map 有多少 key。( 范例:svg-d3-07-data-map-demo7.html )

var i = d3.map(data,function(d){return d.name;});

console.log(i.size());  //10

如果 map 中具有 key 则回传 false, map 的 key 为空则回传 true。( 范例:svg-d3-07-data-map-demo9.html )

var emptyData = [];

var i = d3.map(data,function(d){return d.name;});
var j = d3.map(emptyData,function(d){return d.name;});

console.log(i.empty());  //false
console.log(j.empty());  //true

针对 map 产生的值,一一进行 function,最后回传 undefined。( 范例:svg-d3-07-data-map-demo10.html )

var i = d3.map(data,function(d){return d.name;});
var j = i.forEach(function(n){console.log(n);});

console.log(j);

SVG D3.js - 阵列数据地图 ( d3.map )

以上就是 d3.map 的相关介绍,当然我们也可以直接对我们的数据下 map,例如以下的范例,就可以直接回传 key 成为一个阵列,之后撰写与 d3.js 有关的范例文章,也将会大量地使用 d3.map,这也是学习 d3.js 一定要会的部分喔。( 范例:svg-d3-07-data-map-demo11.html )

var i = data.map(function(d){return d.name;});

console.log(i);

SVG D3.js - 阵列数据地图 ( d3.map )

D3.js - 序数比例尺 ( ordinal )

之前我在 SVG D3.js - 定义比例 ( scale.linear() ) 有介绍过比例尺的用法,当中有提到 ordinal,ordinal 与 linear 最大的差别,可以不一定要使用数字,可以使用非定量的值作为比例尺 ( 序数:非定量的值依序排列 ),因为可以使用非数字的值,画出来的座标,也就更能朝理想的座标长相迈进了。

其实 ordinal 的用法和 linear 的用法几乎一模一样,不过仍然先来看一下 ordinal 有哪些 API ,然后再介绍一下吧!( 忘记的请複习 SVG D3.js - 定义比例 ( scale.linear() ) )

  • ordinal.domain
  • ordinal.range
  • ordinal.rangePoints
  • ordinal.rangeRoundPoints
  • ordinal.rangeBands
  • ordinal.rangeRoundBands
  • ordinal.rangeBand
  • ordinal.rangeExtent
  • ordinal.copy

开始之前,同样用一组数据做为范例使用:

var data=[
  {x:'a',y:135},
  {x:'b',y:138},
  {x:'c',y:132},
  {x:'d',y:121},
  {x:'e',y:126},
  {x:'f',y:140},
  {x:'g',y:123},
  {x:'h',y:89},
  {x:'i',y:79},
  {x:'j',y:83}
];

然后也先画个 svg 的外框出来,就可以开始了。

var s = d3.select('svg');

s.attr({
     'width': width,
     'height': 200,
  }).style({
     'border':'1px solid #000'
  });

ordinal 的 domain 内所带入的值,与 linear 有所不同,linear 带入的是一个数字范围,而 ordinal.domain 内所带入的是一串阵列值,阵列的第一个值会对应到第一个元素的的输出范围,第二个则会对应到第二个范围,依此类推。

在 ordinal.range 里头同样是带入阵列,换句话说,domain 有几个,范围也要有几个,这裡与 linear 是不太相同的,如果照着 linear 的设定方式,出来的结果可能就会出乎意料,范例里头蓝色的线就是按照 linear 的 range 设定,整个就是非常的诡异呀!范例的 domain 使用之前介绍过的 d3.map,很容易的产生一个阵列放进去。( 范例:svg-d3-08-ordinal-demo1.html )

var height = 120,
    width = 200;

var scaleX1 = d3.scale 
               .ordinal() 
               .domain(data.map(function(d){return(d.x);}))
               .range([0, width]);

var scaleX2 = d3.scale 
               .ordinal() 
               .domain(data.map(function(d){return(d.x);}))
               .range([0,20,40,60,80,100,120,140,160,180]);

var scaleY = d3.scale
               .linear()
               .domain([0,150])
               .range([height, 0]);

var line1 = d3.svg.line()
                       .x(function(d) {
                          return scaleX1(d.x);
                       }).y(function(d) {
                          return scaleY(d.y);
                       });

var line2 = d3.svg.line()
                       .x(function(d) {
                          return scaleX2(d.x);
                       }).y(function(d) {
                          return scaleY(d.y);
                       });

s.append('path')
   .attr({
        'd':line1(data),
        'stroke':'#09c',
        'fill':'none'
   });

s.append('path')
   .attr({
        'd':line2(data),
        'stroke':'#f66',
        'fill':'none',
        'transform': 'translate(0,80)'
   });

SVG D3.js - 序数比例尺 ( ordinal )

由于使用 range,要写了一大堆的点在里头,因此,通常如果我们使用 ordinal,会使用 rangePoints 代替 range,rangePoints 可以指定一段范围,这段范围会根据 domain 有多少个值,自动区分为多少个间隔单位。( 范例:svg-d3-08-ordinal-demo2.html )

var scaleX = d3.scale 
               .ordinal() 
               .domain(data.map(function(d){return(d.x);}))
               .rangePoints([0, width]);

SVG D3.js - 序数比例尺 ( ordinal )

这裡还有一个选填的数值 padding,表示范围前后的边距,而边距的长度等于 step 与 step 之间的距离,乘以 padding/2,如果 step 之间的距离是 10, padding 是 1,那么实际 padding 的长度就会是 5 ( 10*1/2 )。( 范例:svg-d3-08-ordinal-demo3.html )

var scaleX = d3.scale 
               .ordinal() 
               .domain(data.map(function(d){return(d.x);}))
               .rangePoints([0, width],1);

SVG D3.js - 序数比例尺 ( ordinal )

SVG D3.js - 序数比例尺 ( ordinal )

rangeBands 是 ordinal 满特别的一个 API,要了解这个 API 就要先看一下 官方的说明 ,最主要的也就是下面这张图片,rangeBands 可以看做是总长度的分段,有几个数值,就会分成「数值-1」段,而当中又包含了 padding 与 outpadding 的设定,定义了每段 rangeBands 后方接续的 padding 与两侧的 outpadding 数值。

SVG D3.js - 序数比例尺 ( ordinal )

不过光是看这张图或许还不太明白,当我们把上面写的范例改为 rangeBands,就会发现后面空了一块,因为预设的 padding 与 outpadding 都是 0,分成九段,最后面当然就会空出一段出来。( 范例:svg-d3-08-ordinal-demo5.html )

SVG D3.js - 序数比例尺 ( ordinal )

padding 的值,根据 d3.js 的定义是 0 到 1 之间的数字,而 padding 的实际大小是该 step 点经过 range 之后的数值乘以这个 padding 值,恩,没错,讲得非常模煳,举例来说,是我把十个点,放在宽度 100px 的范围内,把 padding 设为 1,会出现 11.111111 的数值,因为 rangeBands 自的真谛是让每段 rangeBand 长度相等,而且之间有相等的空隙,当我们把 padding 设为 1,换个角度思考,其实就是利用 padding 补满一段 bands 的长度,因此 padding 的值就是 10/9=1.11111111。( 范例:svg-d3-08-ordinal-demo6.html)

SVG D3.js - 序数比例尺 ( ordinal )

或许这样说还是不太明白,可以看看下面这张我画的示意图,换个角度思考,padding=1 的意思,也等同于全部 padding 总和,等于一段 rangBand,因此会补满原本的宽度,这也是为什么 d3.js 要我们设定 0 到 1 的原因,如果设定大于 1,例如 padding=2,就表示总 padding 的长度的两段 rangeBand,而在范围内的 rangeBand + padding 必须要等于范围,所以就变成有一段 rangeBand 不见了 ( 凸到外面去 )

SVG D3.js - 序数比例尺 ( ordinal )

依此类推,如果范围是 100,有十个点,就可以用计算机算出 padding 的值会是多少,1 就是 0.11111 ( 1/9 ),2 就是 0.25 ( 2/8 ) ,3 就是 0.4285714... ( 3/7 ),4 就是 0.66666 ( 4/6 ),其他就自己算下去吧,为了证明,我们写一个 padding=4 的来看看是不是这样,结果当然是这样囉!而且还可以很明显地看到有三个点跑到 100 外面去了。( 范例:svg-d3-08-ordinal-demo7.html )

var scaleX = d3.scale 
               .ordinal() 
               .domain(data.map(function(d){return(d.x);}))
               .rangeBands([0, width],4,0);

SVG D3.js - 序数比例尺 ( ordinal )

理解了 padding 之后,outpadding 就是两侧的边距,预设也是 0,如果我们写 rangeBands([0, width],1),就表示 padding 与 outpadding 都是 1。( 范例:svg-d3-08-ordinal-demo8.html )

var scaleX = d3.scale 
               .ordinal() 
               .domain(data.map(function(d){return(d.x);}))
               .rangeBands([0, width],1);

SVG D3.js - 序数比例尺 ( ordinal )

SVG D3.js - 序数比例尺 ( ordinal )

返回与边界的边距,如果 padding 为 1,则 rangeBand 出来的结果是 0,padding 设为 2,则 rangeBand 出来的结果是 -2.5。( 范例:svg-d3-08-ordinal-demo10.html )

console.log(scaleX.rangeBand());

返回范围宽度,如果以这篇的范例来说,就是返回 100 或 200 ( 因为一开始用 200,后来的都用 100 )( 范例:svg-d3-08-ordinal-demo11.html )

console.log(scaleX.rangeExtent());

複製一份出来,而不会影响到原本的。

以上大概就是 ordinal 的 API 介绍,最后,当然要结合 Axis 坐标轴,做一个与 ordinal 有关的座标来瞧瞧,如果忘记座标轴怎么做的,请参考 SVG D3.js - 座标轴 ( Axis ) 还有 SVG D3.js - 区域 ( area ):( 范例:svg-d3-08-ordinal-demo12.html )

SVG D3.js - 序数比例尺 ( ordinal )

D3.js - 定义色彩 - 基本篇 ( colors )

在网页的世界裡,色彩是非常重要的,通常我们在写网页的色彩,不外乎就是用十六进制 ( #f00、#fff )、颜色名称 ( red、yellow ) 或 rgba ( rgba(0,0,0,0) ),而在 d3.js 裡,同样也有操作颜色的 API,有别于一般我们在写 CSS 的颜色方式,d3.js 是使用 RGB ( 红绿蓝 )、HSL (色调、饱和度,亮度) 、HCL 与 LAB ( 亮度,a 维度,b 维度 ) 的这些色彩空间,对于这些色彩空间不了解的,可以参考 维基百科色彩空间

和 CSS 最大的差异,d3.js 可以运用其 API 让颜色亮一点或暗一点,如果在 CSS 里头,要调整某个颜色亮一点暗一点,就非得要自己从十六进制色码下去着手,而当我们运用 d3.js,就可以直接交给 API 去处理即可,以下就来看看 d3.js 的 colors,具有那些 API 可以使用,这裡列出了以 rgb 开头的 API,而 HSL、HCL 与 LAB 的用法也都一样,大家举一反三就好:

  • d3.rgb
  • rgb.brighter
  • rgb.darker
  • rgb.hsl
  • rgb.toString

这是可以把一个变数宣告为 d3 的色彩,也就是经过这个方式宣告之后,该变数不仅可作为色彩使用,也可利用 d3.js 其他的色彩 API 来控制和调整,下面的例子便是利用这种方式,将四个 div 的背景色设定为红绿黄蓝。( 范例:svg-d3-09-colors-demo1.html )

HTML:

<div id="red"></div>
<div id="green"></div>
<div id="yellow"></div>
<div id="blue"></div>

JS:

var d = d3.selectAll('div'),
        r = d3.select('#red'),
        g = d3.select('#green'),
        y = d3.select('#yellow'),
        b = d3.select('#blue');

d.style({
  'width':'80px',
  'height':'80px',
  'display':'inline-block',
  'margin':'3px'
});

var red = d3.rgb(255,0,0);
var green = d3.rgb(0,255,0);
var yellow = d3.rgb(255,255,0);
var blue = d3.rgb(0,0,255);

r.style({
    'background':red
  });
g.style({
    'background':green
  });
y.style({
    'background':yellow
  });
b.style({
    'background':blue
  });

SVG D3.js - 定义色彩 - 基本篇 ( colors )

除了用十进位撰写颜色之外,其实 d3.js 也可以直接让我们填写颜色的名称或十六进位色码,不过要注意的是,记得要用引号包起来,才识字串的形式。( 范例:svg-d3-09-colors-1-demo2.html )

var red = d3.rgb('red');
var green = d3.rgb('green');
var yellow = d3.rgb('yellow');
var blue = d3.rgb('blue');

SVG D3.js - 定义色彩 - 基本篇 ( colors )

var red = d3.rgb('#f00');
var green = d3.rgb('#0f0');
var yellow = d3.rgb('#ff0');
var blue = d3.rgb('#00f');

SVG D3.js - 定义色彩 - 基本篇 ( colors )

加亮颜色的 API,数值从 0 开始往上加,最主要是在原本的颜色数字乘上 0.7^-k ,数字越大越亮,不过最终的数值范围是 30-255,超过范围的就会以最大值或最小值表现,范例有改了一下上面的 HTML,可以点开看完整原始档。( 范例:svg-d3-09-colors-1-demo3.html )

var red = d3.rgb(50,0,0);

r.style({
    'background':red.brighter(0)
  });
r1.style({
    'background':red.brighter(1)
  });
r2.style({
    'background':red.brighter(2)
  });
r3.style({
    'background':red.brighter(3)
  });
r4.style({
    'background':red.brighter(4)
  });

SVG D3.js - 定义色彩 - 基本篇 ( colors )

对于想要了解背后真正运算式的,可以利用 console 看出背后的运作原理,但实际上我们也不太需要真正了解,因为 我们使用 d3.js ,就是为了帮我们解决这些运算呀!

brighter: function (n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new dt(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new dt(u,u,u)}

和刚刚的 rgb.brighter 相反,rgb.darker 是变暗,是在原本的颜色数字乘上 0.7^k,注意不是 -k 而是 k。( 范例:svg-d3-09-colors-1-demo4.html )

var red = d3.rgb(255,0,0);

r.style({
    'background':red.darker(0)
  });
r1.style({
    'background':red.darker(1)
  });
r2.style({
    'background':red.darker(2)
  });
r3.style({
    'background':red.darker(3)
  });
r4.style({
    'background':red.darker(4)
  });

SVG D3.js - 定义色彩 - 基本篇 ( colors )

返回 HSL 的色彩,可以直接从 console 看出来。( 范例:svg-d3-09-colors-1-demo5.html )

var red = d3.rgb(255,0,0);  
console.log(red.hsl());

SVG D3.js - 定义色彩 - 基本篇 ( colors )

输出色彩为十六进位字串。( 范例:svg-d3-09-colors-1-demo6.html )

var red = d3.rgb(255,0,0);
console.log(red.toString());

以上就是 d3.js 对于色彩的基本用法,最主要也是练习色彩的相关 API,下一篇将会继续介绍色彩的其他用法。

D3.js - 定义色彩 - 应用篇 ( colors )

我们探讨了 d3.js 里头色彩的定义与 API 操作方式,其实对于色彩,d3.js 还有不少应用的方法,也因此在官网所介绍的范例,才会如此的色彩丰富生动多姿,因此,这篇将继续介绍色彩的应用,主要是利用之前介绍过的 scale 来变化色彩,很神奇吧!scale 要怎么定义色彩?就让我们继续看下去吧~

介绍之前,来回顾一下 scale 的用法,之前介绍过两种用法,分别是 linearordinal ( 当然还有其他的,不过最常用的就这两种 ),而 d3.js 的色彩同样也可以使用这两种 API 来进行定义,首先来看看 linear 该如何使用。

一开始先在 HTML 放入十个 div

<div id="div1">1</div>
<div id="div2">2</div>
<div id="div3">3</div>
<div id="div4">4</div>
<div id="div5">5</div>
<div id="div6">6</div>
<div id="div7">7</div>
<div id="div8">8</div>
<div id="div9">9</div>
<div id="div10">10</div>

接着按照 scale.linear() 的方式写 js,比较特别的是,range 裡放入的是颜色的色码 ( 或颜色名称 ),如此一来,就会在 domain 里头,让颜色进行如渐层般的运算,接着只要填入对应的数字,就可以得到渐层内的颜色,范例的程式码比较冗长,因为完全没有使用 CSS,连同 CSS 都一併由 d3.js 解决,这种方法其实不好,最好还是 CSS 与 js 各司其职,但这只是为了范例可以更清楚的表现才使用喔!( 范例:svg-d3-10-colors-2-demo1.html )

var d = d3.selectAll('div'),
    div1 = d3.select('#div1'),
    div2 = d3.select('#div2'),
    div3 = d3.select('#div3'),
    div4 = d3.select('#div4'),
    div5 = d3.select('#div5'),
    div6 = d3.select('#div6'),
    div7 = d3.select('#div7'),
    div8 = d3.select('#div8'),
    div9 = d3.select('#div9'),
    div10 = d3.select('#div10');

d.style({
  'width':'50px',
  'height':'50px',
  'display':'inline-block',
  'line-height':'50px',
  'color':'#fff',
  'text-align':'center',
  'margin':'2px'
});

var color = d3.scale
              .linear()
              .domain([1,10])
              .range(['#f00', '#00f']);

div1.style({background:color(1)});  //注意这裡的写法
div2.style({background:color(2)});
div3.style({background:color(3)});
div4.style({background:color(4)});
div5.style({background:color(5)});
div6.style({background:color(6)});
div7.style({background:color(7)});
div8.style({background:color(8)});
div9.style({background:color(9)});
div10.style({background:color(10)});

SVG D3.js - 定义色彩 - 应用篇 ( colors )

用这种方法,我们就可以定义出一个范围内的颜色变化,也更能将死板板的数据,对应到各自代表的颜色,例如污染越高的地方越黑、音量越大越红、温度越冷越蓝、雨量越多越紫...等之类的图表,然而除了 scale.linear 这种方法之外,也可以使用之前介绍过的 ordinal,定义出类似阵列概念的颜色阵列,而且其实我们不用自己定义,d3.js 已经帮我们定义好了几种,分别是以下几种:

  • d3.scale.category10()
  • d3.scale.category20()
  • d3.scale.category20b()
  • d3.scale.category20c()

当中所包含的色彩序列如下:

1f77b4
ff7f0e
2ca02c
d62728
9467bd
8c564b
e377c2
7f7f7f
bcbd22
17becf

1f77b4
aec7e8
ff7f0e
ffbb78
2ca02c
98df8a
d62728
ff9896
9467bd
c5b0d5
1f77b4
aec7e8
ff7f0e
ffbb78
2ca02c
98df8a
d62728
ff9896
9467bd
c5b0d5

393b79
5254a3
6b6ecf
9c9ede
637939
8ca252
b5cf6b
cedb9c
8c6d31
bd9e39
e7ba52
e7cb94
843c39
ad494a
d6616b
e7969c
7b4173
a55194
ce6dbd
de9ed6

3182bd
6baed6
9ecae1
c6dbef
e6550d
fd8d3c
fdae6b
fdd0a2
31a354
74c476
a1d99b
c7e9c0
756bb1
9e9ac8
bcbddc
dadaeb
636363
969696
bdbdbd
d9d9d9

当你滑鼠滚轮滚到这边,一定满肚子怨念,干嘛列出这么多颜色,坦白说我写完上面这一大串也是满肚子大便,但为了忠实呈现 d3.js 官网的东西,还是必须要列出这些颜色,这些颜色就是 d3.js 帮我们定义的颜色序列,我们可以像下面范例这样使用,先定义 color,然后内容的数字分别就是对应到色彩序列的颜色编号。( 范例:svg-d3-09-colors-2-demo2.html )

var d = d3.selectAll('div'),
    div1 = d3.select('#div1'),
    div2 = d3.select('#div2'),
    div3 = d3.select('#div3'),
    div4 = d3.select('#div4'),
    div5 = d3.select('#div5');

d.style({
  'width':'50px',
  'height':'50px',
  'display':'inline-block',
  'line-height':'50px',
  'color':'#fff',
  'text-align':'center',
  'margin':'2px'
});

var color = d3.scale.category20b();

div1.style({
  background:color(1)
});
div2.style({
  background:color(2)
});
div3.style({
  background:color(3)
});
div4.style({
  background:color(4)
});
div5.style({
  background:color(5)
});

SVG D3.js - 定义色彩 - 应用篇 ( colors )

var color = d3.scale.category20c();

div1.style({
  background:color(1)
});
div2.style({
  background:color(2)
});
div3.style({
  background:color(3)
});
div4.style({
  background:color(4)
});
div5.style({
  background:color(5)
});

SVG D3.js - 定义色彩 - 应用篇 ( colors )

以上,就是在 d3.js 里头色彩的另外一种应用作法,相信理解了之后,看待一大串的数字,可能都会不由自主地把这些数据给上颜色囉!如果对于色码与颜色名称有兴趣的,也可以参考:Recognized color keyword names

D3.js - 时间格式 ( Time Formatting )

我们都知道在 javascript 里头可以抓取电脑的当下的时间 (new date()),或是在里头设定某个时间,但如果要把产生的时间进行格式化,常常要耗费不少字串处理的时间,在 d3.js 里头,有提供不少的时间格式的 API ,让我们可以轻鬆的产生我们所需要的格式。

来看一下关于定义时间格式,有哪些 API 可以使用:

  • d3.time.format(specifier)
  • format(date)
  • format.parse(string)
  • d3.time.format.multi(formats)
  • d3.time.format.utc(specifier)
  • d3.time.format.iso

这两个 API 要一起看,虽然都是 format ,不过意思却不相同,time.format 主要是定义时间的格式是什么,而 format,则是将我们要格式化的时间套入,下面是 d3.js 所有 time.format 的格式:

* %a - 星期几的缩写
* %A - 星期几
* %b - 月份的缩写
* %B - 月份
* %c - 日期时间的组合 ( 星期缩写 + 月份缩写 + 日期 +  +  +  +  )
* %d - 一个月的哪一天 ( 十进位,01  31,个位数开头有 0 )
* %e - 一个月的哪一天 ( 十进位,1  31 )
* %H - 24 小时制 ( 00  23 ).
* %I - 12 小时制 ( 01  12 ).
* %j - 一年的哪一天 ( 十进位,001  366 )
* %m - 一年的哪一个月 ( 十进位,01  12 )
* %M - 分钟 ( 十进位,00  59 )
* %L - 毫秒 ( 十进位,000  999 )
* %p - AM  PM.
* %S - 第几秒 ( 十进位,00  61 )
* %U - 一年内的第几周 ( 十进位,00  53,星期一开头 )
* %w - 一周内的第几天 ( 十进位,0  60 是星期天 )
* %W - 一年内的第几周 ( 十进位,00  53,星期天开头 )
* %x - 日期组合 ( 月份缩写 + 日期 +  )
* %X - 时间组合 ( 小时 +  +  )
* %y - 西元年后两位数的年份 ( 十进位,00  99 )
* %Y - 西元年
* %Z - 时区偏移量

至于要如何使用呢?可以参考下面的范例,先把宣告一个 d3.js 的 time 物件,制定好格式,再将时间放入,就会变成相关格式的时间囉!( 范例:svg-d3-11-time-demo1.html )

var d = new Date('2014,12,01,12:16:05');

var _a = d3.time.format("%a");
var _A = d3.time.format("%A");
var _b = d3.time.format("%b");
var _B = d3.time.format("%B");
var _c = d3.time.format("%c");
var _d = d3.time.format("%d");
var _e = d3.time.format("%e");
var _H = d3.time.format("%H");
var _I = d3.time.format("%I");
var _j = d3.time.format("%j");
var _m = d3.time.format("%m");
var _M = d3.time.format("%M");
var _L = d3.time.format("%L");
var _p = d3.time.format("%p");
var _S = d3.time.format("%S");
var _U = d3.time.format("%U"); 
var _w = d3.time.format("%w");
var _W = d3.time.format("%W");
var _x = d3.time.format("%x");
var _X = d3.time.format("%X");
var _y = d3.time.format("%y");
var _Y = d3.time.format("%Y");
var _Z = d3.time.format("%Z");

console.log(d); //Mon Dec 01 2014 12:16:05 GMT+0800 (台北标准时间)

console.log(_a(d)); //Mon
console.log(_A(d)); //Monday
console.log(_b(d)); //Dec
console.log(_B(d)); //December
console.log(_c(d)); //Mon Dec  1 12:16:05 2014
console.log(_d(d)); //01
console.log(_e(d)); // 1
console.log(_H(d)); //12
console.log(_I(d)); //12
console.log(_j(d)); //335
console.log(_m(d)); //12
console.log(_M(d)); //16
console.log(_L(d)); //000
console.log(_p(d)); //PM
console.log(_S(d)); //05
console.log(_U(d)); //48
console.log(_w(d)); //1
console.log(_W(d)); //48
console.log(_x(d)); //12/01/2014
console.log(_X(d)); //12:16:05
console.log(_y(d)); //14
console.log(_Y(d)); //2014
console.log(_Z(d)); //+0800

了解运作原理之后,接着就可以利用组合的方式,做出我们想要的格式。( 范例:svg-d3-11-time-demo2.html )

var Ymd = d3.time.format("%Y-%m-%d");
var pIM = d3.time.format("(%p)%I:%M");

console.log(Ymd(d)); //2014-12-16
console.log(pIM(d)); //(PM)12:16

multi 有多选或複合的意思,这个 API 主要针对时间的尺度而设计,可以让时间根据不同的尺度,自动从多个格式中挑出最适当的格式 ( 返回 true 即套用该格式 ),因此当从年分逐渐缩减到小时或分钟,都可以在同一个画面中呈现,在下方的范例中,time.scale 的用法就像之前提过的 scale 一样,把一段时间放到一个范围内显示,而 customTimeFormat 就是负责要显示那些时间出来,因为这个范例是一年份,所以左右就变成了年份,中间就是月份。 ( 可以参考 d3 原作者范例,或 svg-d3-11-time-demo4.html )

var customTimeFormat = d3.time.format.multi([
  [".%L", function(d) { return d.getMilliseconds(); }],
  [":%S", function(d) { return d.getSeconds(); }],
  ["%I:%M", function(d) { return d.getMinutes(); }],
  ["%I %p", function(d) { return d.getHours(); }],
  ["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }],
  ["%b %d", function(d) { return d.getDate() != 1; }],
  ["%B", function(d) { return d.getMonth(); }],
  ["%Y", function() { return true; }]
]);

var margin = {top: 250, right: 40, bottom: 250, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var x = d3.time.scale()
    .domain([new Date(2012, 0, 1), new Date(2013, 0, 1)])
    .range([0, width]);

var xAxis = d3.svg.axis()
    .scale(x)
    .tickFormat(customTimeFormat);

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 + ")");

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

SVG D3.js - 时间格式 ( Time Formatting )

当我们把时间间隔缩减,就会发现时间轴的格式自动变化了。( 范例:svg-d3-11-time-demo5.html )

var x = d3.time.scale()
.domain([new Date(2012, 0, 1), new Date(2012, 3, 1)])
.range([0, width]);

SVG D3.js - 时间格式 ( Time Formatting )

utc 是标准定位时间格式 ( UTC time ),以台湾来说,就是 UTC+8:00,utc 对于跨时区或涉及多个国家地区的时候,设定为 utc 的时间会比较方便,UTC 传回参数日期的 GMT ( 由 1970年1月1日零时零分计起,以微秒为单位 )

一个完整的 ISO 8601 UTC 时间格式为 %Y-%m-%dT%H:%M:%S.%LZ。( 范例:svg-d3-11-time-demo6.html )

var d = new Date();

var f = d3.time.format("%c");
var u = d3.time.format.utc(f(d));
var i = d3.time.format.iso(d);

console.log(d);    //Mon Dec 29 2014 23:15:05 GMT+0800 (台北标准时间)
console.log(f(d)); //Mon Dec 29 23:15:05 2014
console.log(u);    //Mon Dec 29 23:15:05 2014
console.log(i);    //2014-12-29T15:15:05.665Z

其实定义时间格式没有太大难度,因为定义格式,目的其实也是为了在显示图表的时候,可以更为清楚,所以在这篇了解时间格式之后,会用其他篇幅来继续介绍如何运用这些时间格式,创造漂亮的图表。

D3.js - 数字格式 ( Formatting )

SVG D3.js - 时间格式 ( Time Formatting ) 这篇文章裡看完了时间的格式,接着就要来看看 d3.js 对于数字格式是如何处理的,因为 d3.js 主要就是针对数据去进行视觉化,所以大幅简化了过去我们往往要写很多判断式来让数字格式化的方法,只需要运用简单的 API,就能够轻鬆地让数据格式化。其实讲到数据的格式化,让我想到一个强迫症艺术家 Ursus Wehrli 的一系列作品 ( Tidying Up Art ),从一堆杂乱无章的数据当中把这些数据分门别类并且格式化,意境上非常的雷同呀喔哈!( 所以这篇文章的图片就用这系列艺术作品来表现吧! )

d3.js 格式化数字的方法,就像下面的范例这样,先宣告一个格式化的方法,然后把数字放进去,就会得到格式化的「字串」。( 注意,得到的结果是字串 )

var formatting = d3.format("");
formatting(123); // '123'

知道用法之后,就来看看 d3.js 格式化数据的用法,下面这串是 d3.js 所提供的格式,每个项目都是可以单独填写或溷合使用,这时候看不明白不打紧,后面会继续介绍相关用法。

[[fill]align][sign][symbol][0][width][,][.precision][type]

首先我们先从 type看起,因为 d3.js 的官方文件也是最着墨在这边,type有以下几种,不同的type就会把数字产生成不同格式的字串显示。

  • e:数字的指数 ( 使用 Number.toExponential )
  • g:指定有几个位数 ( 使用 Number.toPrecision )
  • f:指定小数点后有几位数 ( 使用 Number.toFixed )
  • d:返回这个数字的字串格式,忽略任何非整数值 ( 使用 Number.toString )
  • r:四捨五入指定的小数精度位数,必要时会採用 f 的方式
  • %:以 f 为基础,返回乘以 100 后加上 %
  • p:以 r 为基础,返回乘以 100 后加上 %
  • b:二进位
  • o:八进位
  • x:十六进位 ( 小写英文字母 )
  • X:十六进位 ( 大写英文字母 )
  • c:将整数转换为对应的 unicode
  • s:以 r 为基础,但带有一个单位码 ( 例如 9.5M、或 1.00µ )

了解有这些 type 之后,直接用一个范例来看看数据产生出来的样子为何。 ( 范例都要开 console 出来看喔 )

e 范例:svg-d3-12-formatting-demo1.html

var _e = d3.format("e");

console.log(_e(100));  //1e+2
console.log(_e(10));   //1e+1
console.log(_e(1));    //1e+0
console.log(_e(0.1));  //1e-1
console.log(_e(0.01)); //1e-2
console.log(_e(-10));  //-1e+1
console.log(_e(-0.01));//-1e-2

g 范例:svg-d3-12-formatting-demo2.html

var _g1 = d3.format(".9g");

console.log(_g1(10000));  //10000.0000
console.log(_g1(10));     //10.0000000
console.log(_g1(1));      //1.00000000
console.log(_g1(0.01));   //0.0100000000
console.log(_g1(0.0001)); //0.000100000000
console.log(_g1(-10));    //-10.0000000
console.log(_g1(-0.01));  //-0.0100000000

var _g2 = d3.format("5g");

console.log(_g2(10000));  //10000
console.log(_g2(10));     //   10 <-- 前面有空白
console.log(_g2(1));      //    1
console.log(_g2(0.01));   // 0.01
console.log(_g2(0.0001)); //0.0001
console.log(_g2(-10));    //  -10
console.log(_g2(-0.01));  //-0.01

f 范例:svg-d3-12-formatting-demo3.html

var _f1 = d3.format(".6f");

console.log(_f1(10000));  //10000.000000
console.log(_f1(10));     //10.000000
console.log(_f1(1));      //1.000000
console.log(_f1(0.01));   //0.010000
console.log(_f1(0.0001)); //0.000100
console.log(_f1(-10));    //-10.000000
console.log(_f1(-0.01));  //-0.010000

var _f2 = d3.format("6f");

console.log(_f2(10000));  // 10000 <-- 前面有空白
console.log(_f2(10));     //    10
console.log(_f2(1));      //     1
console.log(_f2(0.01));   //     0
console.log(_f2(0.0001)); //     0
console.log(_f2(-10));    //   -10
console.log(_f2(-0.01));  //    -0 <-- -0

d 范例:svg-d3-12-formatting-demo4.html

var _d1 = d3.format("d");

console.log(_d1(10000));  //10000
console.log(_d1(10));     //10
console.log(_d1(1));      //1
console.log(_d1(0.01));   //
console.log(_d1(0.0001)); //
console.log(_d1(-10));    //-10
console.log(_d1(-0.01));  //

var _d2 = d3.format("6d");

console.log(_d2(10000));  // 10000 <-- 前面有空白
console.log(_d2(10));     //    10
console.log(_d2(1));      //     1
console.log(_d2(0.01));   //
console.log(_d2(0.0001)); //
console.log(_d2(-10));    //   -10
console.log(_d2(-0.01));  //

r 范例:svg-d3-12-formatting-demo5.html

var _r1 = d3.format("5r");

console.log(_r1(100));      //  100 <-- 前面有空白
console.log(_r1(10));       //   10
console.log(_r1(1));        //    1
console.log(_r1(0.0123));   //0.0123
console.log(_r1(0.012345)); //0.012345
console.log(_r1(-10));      //  -10
console.log(_r1(-0.123));   //-0.123

var _r2 = d3.format(".5r");

console.log(_r2(100));      //100.00
console.log(_r2(10));       //10.000
console.log(_r2(1));        //1.0000
console.log(_r2(0.0123));   //0.012300
console.log(_r2(0.012345)); //0.012345
console.log(_r2(-10));      //-10.000
console.log(_r2(-0.123));   //-0.12300

% 范例:svg-d3-12-formatting-demo6.html

var _pc1 = d3.format("%");

console.log(_pc1(100));      //10000%
console.log(_pc1(10));       //1000%
console.log(_pc1(1));        //100%
console.log(_pc1(0.0123));   //1%
console.log(_pc1(0.012345)); //1%
console.log(_pc1(-10));      //-1000%
console.log(_pc1(-0.123));   //-12%

var _pc2 = d3.format("3%");

console.log(_pc2(100));      //10000%
console.log(_pc2(10));       //1000%
console.log(_pc2(1));        //100%
console.log(_pc2(0.0123));   //1%      <-- 只剩整数部分
console.log(_pc2(0.012345)); //1%
console.log(_pc2(-10));      //-1000%
console.log(_pc2(-0.123));   //-12%

var _pc3 = d3.format(".3%");

console.log(_pc3(100));      //10000.000%
console.log(_pc3(10));       //1000.000%
console.log(_pc3(1));        //100.000%
console.log(_pc3(0.0123));   //1.230%
console.log(_pc3(0.012345)); //1.234%   <-- 后面删除了
console.log(_pc3(-10));      //-1000.000%
console.log(_pc3(-0.123));   //-12.300%

p 范例:svg-d3-12-formatting-demo7.html

var _p1 = d3.format("p");

console.log(_p1(100));      //10000%
console.log(_p1(10));       //1000%
console.log(_p1(1));        //100%
console.log(_p1(0.0123));   //1.23%
console.log(_p1(0.012345)); //1.2345%
console.log(_p1(-10));      //-1000%
console.log(_p1(-0.123));   //-12.3%

var _p2 = d3.format("3p");

console.log(_p2(100));      //10000%
console.log(_p2(10));       //1000%
console.log(_p2(1));        //100%
console.log(_p2(0.0123));   //1.23%
console.log(_p2(0.012345)); //1.2345%
console.log(_p2(-10));      //-1000%
console.log(_p2(-0.123));   //-12.3%

var _p3 = d3.format(".3p");

console.log(_p3(100));      //10000.000%
console.log(_p3(10));       //1000.000%
console.log(_p3(1));        //100.000%
console.log(_p3(0.0123));   //1.23% 
console.log(_p3(0.012345)); //1.23%  <-- 四捨五入了
console.log(_p3(-10));      //-1000.000%
console.log(_p3(-0.123));   //-12.3%

p 范例:svg-d3-12-formatting-demo8.html

var _b1 = d3.format("b");

console.log(_b1(100));      //1100100
console.log(_b1(10));       //1010
console.log(_b1(1));        //1
console.log(_b1(0.0123));   //
console.log(_b1(0.012345)); //
console.log(_b1(-10));      //-1010
console.log(_b1(-0.123));   //

o 范例:svg-d3-12-formatting-demo9.html

var _o1 = d3.format("o");

console.log(_o1(100));      //144
console.log(_o1(10));       //12
console.log(_o1(1));        //1
console.log(_o1(0.0123));   //
console.log(_o1(0.012345)); //
console.log(_o1(-10));      //-12
console.log(_o1(-0.123));   //

x 范例:svg-d3-12-formatting-demo10.html

var _x1 = d3.format("x");

console.log(_x1(1000));     //3e8
console.log(_x1(10));       //a
console.log(_x1(1));        //1
console.log(_x1(0.0123));   //
console.log(_x1(0.012345)); //
console.log(_x1(-10));      //-a
console.log(_x1(-0.123));   //

X 范例:svg-d3-12-formatting-demo11.html

var _X1 = d3.format("X");

console.log(_X1(1000));     //3E8
console.log(_X1(10));       //A
console.log(_X1(1));        //1
console.log(_X1(0.0123));   //
console.log(_X1(0.012345)); //
console.log(_X1(-10));      //-A
console.log(_X1(-0.123));   //

c 范例:svg-d3-12-formatting-demo12.html

var _c1 = d3.format("c");

console.log(_c1(13256));     //㏈
console.log(_c1(1256));      //Ө
console.log(_c1(13899));     //㙋

s 范例:svg-d3-12-formatting-demo13.html

var _s1 = d3.format("s");

console.log(_s1(10000));   //10k
console.log(_s1(100));     //100
console.log(_s1(1));       //1
console.log(_s1(0.01));    //10m
console.log(_s1(0.0001));  //100µ
console.log(_s1(-100));    //-100
console.log(_s1(-0.01));   //-10m

var _s2 = d3.format("10s");

console.log(_s2(10000));   //        10k  <-- 前面有空格
console.log(_s2(100));     //       100
console.log(_s2(1));       //         1
console.log(_s2(0.01));    //        10m
console.log(_s2(0.0001));  //       100µ
console.log(_s2(-100));    //      -100
console.log(_s2(-0.01));   //       -10m

var _s3 = d3.format(".5s");

console.log(_s3(10000));   //10.000k
console.log(_s3(100));     //100.00
console.log(_s3(1));       //1.0000
console.log(_s3(0.01));    //10.000m
console.log(_s3(0.0001));  //100.00µ
console.log(_s3(-100));    //-100.00
console.log(_s3(-0.01));   //-10.000m

以上的范例应该就可以看出 type对于格式化数据的影响力,上述的范例除了是介绍 type,其实无形当中也介绍了一些额外的格式化方法,例如我们在格式化type的前面加上一个整数,就是 d3.js Formatting 里头的[width],也就是格式化数据的宽度,换句话说就是有几个整数的位数,这也是为什么在上面的范例,会看到有些转换出来的数据格式「前面有空格」了;至于加上小数点,就是[.precision],意思是「加上小数点之后总共会有几位数」,也因此套用后就会发现整数后面多了一些 0 的小数在后面。

除了加上整数和小数,当我们加上逗号,数据就会自动被逗号来区隔:(范例:svg-d3-12-formatting-demo14.html)

var _comma = d3.format(",");

console.log(_comma(1000000));   //1,000,000
console.log(_comma(10000));     //10,000
console.log(_comma(1));         //1
console.log(_comma(-10000));    //-10,000

我们也可以 用组合的方式来组合,但组合的顺序记得要按照这个规则进行:(范例:svg-d3-12-formatting-demo15.html)

[[fill]align][sign][symbol][0][width][,][.precision][type]

console.log(d3.format(",.5e")(10000)); //1.00000e+4 console.log(d3.format(",.5g")(10000)); //10,000 console.log(d3.format(",.5f")(10000)); //10,000.00000 console.log(d3.format(",.5d")(10000)); //10,000 console.log(d3.format(",.5r")(10000)); //10,000.0 console.log(d3.format(",.5%")(10000)); //1,000,000.00000% console.log(d3.format(",.5p")(10000)); //1,000,000% console.log(d3.format(",.5b")(10000)); //10,011,100,010,000 console.log(d3.format(",.5o")(10000)); //23,420 console.log(d3.format(",.5x")(10000)); //2,710 console.log(d3.format(",.5s")(10000)); //10.000k

console.log(d3.format("10,.5e")(10000));   //1.00000e+4
console.log(d3.format("10,.5g")(10000));   //    10,000  <-- 前面有空格
console.log(d3.format("10,.5f")(10000));   //10,000.00000
console.log(d3.format("10,.5d")(10000));   //    10,000
console.log(d3.format("10,.5r")(10000));   //  10,000.0
console.log(d3.format("10,.5%")(10000));   //1,000,000.00000%
console.log(d3.format("10,.5p")(10000));   // 1,000,000%
console.log(d3.format("10,.5b")(10000));   //10,011,100,010,000
console.log(d3.format("10,.5o")(10000));   //    23,420
console.log(d3.format("10,.5x")(10000));   //     2,710
console.log(d3.format("10,.5s")(10000));   //    10.000k

了解组合之后,接着看到 [0],意思是我们可以在刚刚留白的地方补 0,看看下面的范例就会清楚了:(范例:svg-d3-12-formatting-demo16.html)

console.log(d3.format("08,.5e")(1000));   //1.00000e+3
console.log(d3.format("08,.5g")(1000));   //01,000.0
console.log(d3.format("08,.5f")(1000));   //1,000.00000
console.log(d3.format("08,.5d")(1000));   //0,001,000
console.log(d3.format("08,.5r")(1000));   //01,000.0
console.log(d3.format("08,.5%")(1000));   //100,000.00000%
console.log(d3.format("08,.5p")(1000));   //0,100,000%
console.log(d3.format("08,.5b")(1000));   //1,111,101,000
console.log(d3.format("08,.5o")(1000));   //0,001,750
console.log(d3.format("08,.5x")(1000));   //0,000,3e8
console.log(d3.format("08,.5s")(1000));   //001.0000k

再来就是 [sign][symbol],这两个没有太大的区隔,主要就是一些标记符号例如 $ 这个钱币符号,而正负号也是可以添加的,不过负号只有在负数的情形才会出现。(范例:svg-d3-12-formatting-demo17.html)

console.log(d3.format("$08,.5e")(1000));   //$1.00000e+4
console.log(d3.format("$08,.5g")(1000));   //$10,000
console.log(d3.format("$08,.5f")(1000));   //$10,000.00000
console.log(d3.format("$08,.5d")(1000));   //$10,000
console.log(d3.format("$08,.5r")(1000));   //$10,000.0
console.log(d3.format("+08,.5%")(1000));   //+1,000,000.00000%
console.log(d3.format("+08,.5p")(1000));   //+1,000,000%
console.log(d3.format("+08,.5b")(1000));   //+10,011,100,010,000
console.log(d3.format("+08,.5o")(1000));   //+0,001,750
console.log(d3.format("-08,.5x")(1000));   //0,000,3e8  
console.log(d3.format("-08,.5s")(1000));   //001.0000k
console.log(d3.format("-08,.5x")(-1000));  //-0,000,3e8
console.log(d3.format("-08,.5s")(-1000));  //-001.0000k

最后就是 [[fill]align]了,这可以在空白填充字串,然后把我们原本的数字对齐,对齐的方式有三种:

  • ^ 置中
  • < 靠左
  • 靠右

直接看看范例就会比较清楚了:(范例:svg-d3-12-formatting-demo18.html)

console.log(d3.format("哈^8")(1));   //哈哈哈哈1哈哈哈
console.log(d3.format("哈<8")(1));   //1哈哈哈哈哈哈哈
console.log(d3.format("哈>8")(1));   //哈哈哈哈哈哈哈1

以上就是 d3.js 数字格式 ( Formatting ) 的用法,相信熟练之后,在数据视觉化的图表裡,就可以把显示的数字做一个统一的格式处理囉!

D3.js - CSV 数据处理

在数据的类型裡,CSV 是一种相当普及且方便的数据文件格式,从我们每个人手边的 excel 就可以轻鬆地将数据转换成 CSV 的数据格式,而对于 d3.js 而言,除了可以很轻鬆的处理 JSON 数据之外,也可以很便利的处理 CSV 的数据,搭配前几篇所介绍的数据处理方法,就可以做出多样的变化。

首先来看一下什么是 CSV 数据,CSV 就是「用逗号分隔值」( Comma-Separated Value ),举例来说,在 EXCEL 里头的数据长得像下图这样:

CSV 数据处理

转换为 CSV 之后就变成这样:

sex,name,score
male,Jack,88
female,Marry,65
female,Cherry,74
male,Owen,95
male,Jason,87
male,Bill,61
female,Amy,69
female,Mia,74
male,Peter,86

这个 CSV 格式的资料,也就是我们要载入的,载入 CSV 格式资料的方法,和 jquery 里头的 ajax 类似,而且同样会遇到「跨域」 ( cross domain ) 的问题,什么是跨域呢?就是当我们使用网页浏览器浏览网页时,浏览器为了保障资料传输的安全性,具备一个重要的浏览器端语言的安全协定,这个协定仅允许 script 在同个网域之间互相传送资料,但是禁止不同网域之间互相取用方法与属性。( 参考 Same-origin policy )

因此,为了方便在网页端进行开发,建议大家可以使用 Google Chrome 进行浏览,并安装跨域小套件:「Allow-Control-Allow-Origin: *」,安装之后再 Chrome 右上角会出现一个红色按钮,点选会出现一个开关的选项:

CSV 数据处理

打开开关就会变成绿色的,这时候就是把浏览器的安全性关闭,可以跨域了。

CSV 数据处理

可以跨域之后,我们就可以拿这个 CSV 格式的档桉 ( svg-d3-13-csv.csv ) 来练习,首先我们要先把这个 CSV 载入,然后用 console 就看看载入的数据,会发现载入的数据经过 d3.js 的处理,已经变成了 JSON 的格式了。( 范例:svg-d3-13-csv-demo1.html,如果 console 出现No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.就是没有跨域,跨域成功就会出现以下数据 )

d3.csv("http://www.oxxostudio.tw/articles/201501/svg-d3-13-csv.csv", function(data) {
    console.log(data);
});

CSV 数据处理

然而 d3.js 的 CSV 也具有像 ajax 一样的 callback 机制,里头也具有 error 和 success 的方法,要测试 error 的话可以把跨域关起来,就会看到 console 出现 oh no 的字样,范例里头将 CSV 的资料指定给另外一个阵列变成另外一个 JSON 资料,同时将分数的部分由字串转换为数字。( 范例:svg-d3-13-csv-demo2.html)

d3.csv("http://www.oxxostudio.tw/articles/201501/svg-d3-13-csv.csv", function(d) {
  return {
    '性别': d.sex,
    '姓名': d.name,
    '分数': +d.score
    };
}, function(error, rows) {
        if(error){
            console.log('oh no');
        }
        else{
        console.log(rows);
        }
});

CSV 数据处理

上述的方式,是针对已经符合 RFC4180 标准 的字串,同样也适用于d3.csv.parse(string[, accessor])这个 API,不过使用d3.csv.parse必须是要喂字串才型喔!但是对于不符合 RFC4180 标准的字串,又该怎么处理呢?例如下面这个 CSV:( svg-d3-13-csv.csv )

name,Bill,score,80,sex,male
name,Jean,score,78,sex,female
name,Owen,score,92,sex,male

针对这类型的数据,d3.js 提供了我们d3.csv.parseRows(string[, accessor])的方法来处理,下面的范例是利用 d3.text 将载入的 CSV 变成了字串的形式,接着利用d3.csv.parseRows把每一列变成物件,接着再利用d3.map把数据变成我们理想中的格式。( 范例:svg-d3-13-csv-demo3.html)

d3.text("http://www.oxxostudio.tw/articles/201501/svg-d3-13-csv2.csv", function(data) {
  console.log(data);
  var parsedCSV = d3.csv.parseRows(data);
  console.log(parsedCSV);
  var a = parsedCSV.map(function(d){
    return{
        name:d[1],
        score:d[3],
        sex:d[5]
    }
  });
  console.log(a);
});

CSV 数据处理

把字串转化为我们要的各式化数据还不稀奇,d3.js 里头的d3.csv.format(rows)d3.csv.formatRows(rows),可以将格式化后的数据,转化回原本的 CSV 样子。( 范例:svg-d3-13-csv-demo4.html)

console.log(d3.csv.format([
    {name:"Bill",score:'80',sex:'male'},
    {name:"Jean",score:'78',sex:'female'},
    {name:"Owen",score:'92',sex:'male'}
]));

//name,score,sex
//Bill,80,male
//Jean,78,female
//Owen,92,male

console.log(d3.csv.formatRows([
    ["Bill",'80','male'],
    ["Jean",'78','female'],
    ["Owen",'92','male']
]));

//Bill,80,male
//Jean,78,female
//Owen,92,male

知道用法之后,如果刚刚 d3.text 不明白的,就可以用 d3.csv.format 来转换,结果会是一样的。( 范例:svg-d3-13-csv-demo5.html)

d3.csv("http://www.oxxostudio.tw/articles/201501/svg-d3-13-csv2.csv", function(data) {
  var f = d3.csv.format(data);
  console.log(f);
  var parsedCSV = d3.csv.parseRows(f);
  console.log(parsedCSV);
  var a = parsedCSV.map(function(d){
    return{
        name:d[1],
        score:d[3],
        sex:d[5]
    }
  });
  console.log(a);
});

CSV 数据处理

以上就是 d3.js 里头 CSV 数据处理的基本方式,记得读取数据的时候要记得跨域,不然就是要在同一个 domain name 底下喔!

D3.js - transition 基本篇

在 CSS 裡面有非常好用的 transition 属性,同样的在 d3.js 裡头也有,而且比 CSS 的 transition 更加强大,除了会自动去计算补间动画 ( 这个词在 flash 裡头很常听到 ),更可以自己设定自己的补间效果 ( tween ),自己去设定相关的插值 ( interpolate ),许多 d3.js 图表的动态效果,都是藉由这个 transition 来完成的!这篇将先会先介绍基本的 transition 的用法,后续会用其他篇幅来说明比较进阶的应用。

d3.js 基本的 API 有以下几个:

  • d3.transition([selection], [name])
  • transition.delay([delay])
  • transition.duration([duration])
  • transition.ease([value[, arguments]])
  • transition.attr(name, value)
  • transition.style(name, value[, priority])
  • transition.each([type, ]listener)
  • transition.call(function[, arguments…])

要看 API 就一定要实作,先看到下面这个范例,三个正方形在页面载入之后就会往下移动,但因为设定的不同,所以会在不同的时间点抵达下方,红色的正方形,单纯只写了.transition,因此会带入预设的.duration(250),也就是 0.25 秒,绿色的正方形多写了.duration(1000),表示会在一秒内抵达下方,蓝色的正方形则是又多了.delay(500),表示会延迟五秒钟才开始进行动作,其实在在 CSS 也几乎是同样的设定方式啦。( 范例:svg-d3-14-transition-1-demo1.html )

d3.selectAll('div')
  .style({
        'top':'0'
  });
d3.select('.box1')
    .transition()
    .style({
        'top':'100px'
    });
d3.select('.box2')
    .transition()
    .duration(1000)
    .style({
        'top':'100px'
    });
d3.select('.box3')
    .transition()
    .duration(1000)
    .delay(500)
    .style({
        'top':'100px'
    });

SVG D3.js - transition 基本篇

在上面的范例,其实还用到了.style这个方法,这个方法就是单纯的针对具有样式的样式属性去做变换,如果换成像 SVG 裡头使用 attr 控制的元素,就必须要改用.attr的方式来控制,例如下面这个范例。( 范例:svg-d3-14-transition-1-demo2.html )

d3.selectAll('rect')
  .attr({
        'y':'0'
  });
d3.select('#r1')
    .transition()
    .attr({
        'y':'100'
    });
d3.select('#r2')
    .transition()
    .duration(1000)
    .attr({
        'y':'100'
    });
d3.select('#r3')
    .transition()
    .duration(1000)
    .delay(500)
    .attr({
        'y':'100'
    });

SVG D3.js - transition 基本篇

看完上面两个范例,我想应该已经大致上了解 transition 的运作原理,接着再来就看看.ease这个方法,在刚刚的两个范例里头,并没有设定.ease,呈现出来就是死板板的等速运动,换句话说,就是用.ease(‘linear’)的这个预设的动作类型,为了让动画看起来更漂亮,我们就会使用加速、减速或反弹...等动作类型,这些类型,在 d3.js 里头其实已经预设了不少囉!以下就是.ease具备的动作类型:

* linear:线性,预设值,为一个常数
* poly(k)Math.Pow,指定一个幂次 k  线性的常数
* quad:等同于 poly(2)
* cubic:等同于 poly(3).
* sin:三角函数的 sin 曲线
* exp:基于线性常数的指数函数
* circle:四分之一的圆周
* elastic(a, p):像是橡皮筋一样的有弹性,会超出 0  1 的范围
* back(s):先往后再往前
* bounce:会像球一样的反弹

这个这个范例 svg-d3-14-transition-1-demo3.html 里头,将.ease具备的模式一一展现,为了更清楚表现程式码,就一段一段忠实呈现,在这裡就不一一把程式码列出,可以点选范例看原始码即可。

d3.select('.box1')
    .transition()
    .duration(1000)
    .ease('linear') //换裡面的字串即可
    .style({
        'top': '200px'
    });
d3.select('.box2')
    .transition()
    .duration(1000)
    .ease('poly','2') //比较需要注意的是,有带参数的要这样写
    .style({
        'top': '200px'
    });

SVG D3.js - transition 基本篇

除此之外,还有另外四种类型。( 范例:svg-d3-14-transition-1-demo4.html )

* cubic-in:加速
* cubic-out:减速
* cubic-in-out:先减速再加速
* cubic-out-in:先加速再减速

d3.select('.box1') .transition() .duration(1000) .ease('cubic-in') //cubic-out,cubic-out-in,cubic-in-out .style({ 'top': '200px' });

SVG D3.js - transition 基本篇

看完.ease,接下来就是.each这个方法,按照官网的说明,这是一个在补间动画运作时的「监听器」 ( listener ),具有三种类型,分别是「start」、「end」与「interrupt」,「start」代表在补间动画开始的时候,同时执行,但根据官方说法,会有 17ms 的延迟,所以也不是完全同时,「end」表示在补间动画结束后,要执行甚么,而「interrupt」则表示当补间动画中断时,会执行些甚么,下面的范例,第一个正方形在开始的时候就会有边框,第二个则是在结束的时候会有边框,第三个按钮则是在我们按下 interrupt 按钮的时候会出现边框。( 范例:svg-d3-14-transition-1-demo5.html )

d3.select('.box1')
    .transition()
    .duration(2000)
    .style({
        'top':'100px'
    })
    .each('start',function(){
        d3.select(this)
            .style({
                'border':'10px solid #000'
            });
    });
d3.select('.box2')
    .transition()
    .duration(2000)
    .style({
        'top':'100px'
    })
    .each('end',function(){
        d3.select(this)
            .style({
                'border':'10px solid #000'
            });
    });
d3.select('.box3')
    .transition()
    .duration(2000)
    .style({
        'top':'100px'
    })
    .each('interrupt',function(){
        d3.select(this)
            .style({
                'border':'10px solid #000'
            });
    });

SVG D3.js - transition 基本篇

最后就是.call这个方法,其实这就类似我们在写 JS 的时候,会把多个地方用到的函式独立出来一样,在下面的范例,由于三个正方形都会移动到同样的位置,所以我们就将位置独立出来,再使用.call来呼叫即可。( 范例:svg-d3-14-transition-1-demo6.html )

function d3Transition(){
    d3.selectAll('div')
      .style({
            'top':'30px'
      });
    d3.select('.box1')
        .transition()
        .call(foo);
    d3.select('.box2')
        .transition()
        .duration(1000)
        .call(foo);
    d3.select('.box3')
        .transition()
        .duration(1000)
        .delay(500)
        .call(foo);
}
function foo(t){
    t.style({
            'top':'100px'
        });
}

SVG D3.js - transition 基本篇

补充一点,我们也可以将 transition 组合起来成为一连串的动画效果。( 范例:svg-d3-14-transition-1-demo7.html )

d3.select('.box1')
    .transition()
    .duration(1000)
    .style('top','100px')
    .transition()
    .duration(1000)
    .style('left','100px')
    .transition()
    .duration(1000)
    .style('top','30px')
    .transition()
    .duration(1000)
    .style('left','10px');

SVG D3.js - transition 基本篇

以上就是 d3.js 里头 transition 的基本用法,不过 transition 真是满博大精深的,之后将会再用其他篇幅,介绍比较深入的用法囉!

( 第一张图片来源:http://iallenkelhet.no/2013/04/02/animation-a-little-goes-a-long-way/ )