d3中的axis.ticks详解

d3中有一个和坐标轴相关的方法,即axis.ticks().可是它的用法让人琢磨不透,本文就试图经过一些案例来对其进行详细的解释。html

axis.ticks()用来设定分段数量

当经过比例尺建立一个y坐标轴的时候,可能你只想让你的y轴坐标刻度只显示两个,即只有收尾,怎么办?把ticks()参数设为1便可:git

let scale = d3.scale.linear()
  .domain([0, 100])
  .range([0, 300])

let axis = d3.svg.axis()
  .scale(scale)
  .orient('left')
  .ticks(1)

设为1表示整个y轴被分为1段,两头都会出现一个刻度,因此就会出现两个刻度。一样的道理,设置为2则会出现两段三个刻度,设置为5则会出现5段6个刻度。github

然而,当你设置为3的时候,奇怪的事情发生了。并无出现3段4个刻度的结果,而是出现了2段3个刻度。这是怎么回事呢?web

axis.ticks()的分段规则

之因此3不起做用了,是由于定义域domain中的[0, 100]没法计算得出对应的结果。在这个回答里,mbostock指出,不管你怎么设定,ticks的参数实际上都是在2,5和10这三个中选。算法

The default ticks for quantitative scales are multiples of 2, 5 and 10. You appear to want multiples of 6; though unusual, this could make sense depending on the nature of the underlying data. So, while you can use axis.ticks to increase or decrease the tick count, it will always return multiples of 2, 5 and 10 — not 6.数组

(实际上还支持0和1,当超过10以后下文再作详说),因此你传入3实际上至关于传入2,传入4至关于传入5,传入6至关于传入5,传入8至关于传入10.也就是说你传入的值,首先和上面这几个备选参数去比较,越接近哪个,就用哪一个做为值。因此当你传入接近10而距离另外一个值更远的值时,就会至关于传入10. 传入1.5时取1,传入1.6时取2.app

当超过10以后怎么算?传入大于10以后,就以20,50和100来计算,固然,10仍是算数的,传入15至关于传入10,可是传入16就至关于传入20.这个规则跟10之内其实差很少。dom

axis.ticks()还和定义域相关

上面的演示其实有一个隐含条件,就是咱们的定义域规定为[0, 100],也就是说2,5,10的规律是基于100来计算的,若是换成[0, 200], [0, 300]结果又不同,分段的时候,也不是简单的把轴刻度上的数值改成对应的定义域的值而已。svg

好比说,若是你的定义域为[0, 200],ticks传入4,那么能够获得分4段5个刻度的轴。这和[0, 100]的结果大不相同,若是按照这个逻辑,100/4=25,为何不分25为单位的刻度呢?函数

这里又有一个逻辑,就是分割出来的每一段的值,也必须在1,2,5,10(及其10倍值)中挑选,不能有3,6,8之类的个位值出现。因此分出来的结果中,超出10的,个位值只有0,不会有5,更别提其余值。超出100的,总体上又要遵循这个规律,如此类推下去。

可是正是因为这一规则,致使出现了一个界面上的bug,好比定义域为[0, 300],ticks为2,会出现什么结果?

图片描述

结果没有在轴末端显示300,而是在2/3处显示了200. 这种状况,对于开发者而言,也是应该注意和尽可能避免的。为何会出现这个状况呢?

当定义域上限为300的时候,分红2段,那么每一段按理应该是150. 可是因为这个值超过了100,因此须要在十位数上进行观察,发现十位数是5,这是不被容许的,单位值超过100的,必须以100及其倍数做为单位值,因此就会被从新计算。按照上文的规则,150按照上文1.5的那种方法去思考,应该使用100做为单位值。

看似去已经遵循规则了,可是并非这样,当你用100做为单位的时候,300会被分为3段4个刻度,因此ticks的值应该是3才对,而不是2. 咱们用代码来实际验证一下:

let scale = d3.scale.linear()
  .domain([0, 300])
  .range([0, 300])

let axis = d3.svg.axis()
  .scale(scale)
  .orient('left')
  .ticks(3)

你会发现,真的被分为了3段4个刻度。这彻底打翻了咱们上文提到的ticks值只能在2,5,10中挑选的设定。

当定义域为[0, 300]时,ticks值为1和3时,咱们均可以很是容易想象出结果,可是为2时d3也没法按照预想结果处理。也就出现了上图的尴尬局面。

axis.ticks()根本不是用来决定分段数量的

经过上面的一系列弯路,你或许已经隐隐察觉,ticks根本不是用来设定分段数量的。真正的分段,是靠每一段的单位来肯定的,好比说定义域为[0, 40]想分5段,那么每一段长度应该为8,可是8根本不在2,5,10这个考虑范围内,一样的道理,[0, 700]想分3段也分不出来。

真正的思考逻辑是,用1,2,5及其10倍数做为除数去计算。好比[0, 600],用1,2,5及其10倍数去除,而不要用3,6,9之类的数去除。600/50=12,因此ticks填写12是可行的,600/3=200,因此ticks填写200是不行的。

ticks的值填写什么,根本不是段数决定的,而是除数决定的,先在脑海中想一下除数是否为1,2,5及其10倍数,(不是的话,放心,得不到你想要的效果,)想一下除出来的结果,再填写这个结果。

这里的定义域所有都是0开头的,实际上不必定从0开始,好比[300, 600],这个时候被除数应该用300。

若是你一不当心,填写了未经脑海计算的值,会获得什么结果呢?d3会尽量处理到与你传入的值最接近的结果,可是有的时候就会变成上面那图的最后的效果,会让人摸不着头脑。

d3的代码是怎么处理axis.ticks的?

到底ticks是怎么来处理传入的参数的,还须要经过代码来研究。在d3中ticks的概念是指,从一个值到另外一个分割为特定数量的数组:

Returns an array of approximately count + 1 uniformly-spaced, nicely-rounded values between start and stop (inclusive). Each value is a power of ten multiplied by 1, 2 or 5.

译:返回一个start到stop之间(包含stop)的接近count+1个值的数组,这个数组具备均匀的间隔(uniformly-spaced),进行了优雅地(nicely)四舍五入(round)。每一个值都是1,2或5乘以10的幂。

而在axis.ticks上,也必然遵循这一算法。它是以axis的domain做为start和stop,以ticks的值做为count,获得一个数组,这个数组里面的值就一一对应每个刻度的值。固然它们也遵循10的次幂的规则。

所以,从算法上讲,它根本不涉及咱们上文提到的“分多少段”的问题。

ticks的算法源码在这里,核心算法以下:

var e10 = Math.sqrt(50),
    e5 = Math.sqrt(10),
    e2 = Math.sqrt(2);

export default function(start, stop, count) {
  var step = tickStep(start, stop, count);
  return range(
    Math.ceil(start / step) * step,
    Math.floor(stop / step) * step + step / 2, // inclusive
    step
  );
}

export function tickStep(start, stop, count) {
  var step0 = Math.abs(stop - start) / Math.max(0, count),
      step1 = Math.pow(10, Math.floor(Math.log(step0) / Math.LN10)),
      error = step0 / step1;
  if (error >= e10) step1 *= 10;
  else if (error >= e5) step1 *= 5;
  else if (error >= e2) step1 *= 2;
  return stop < start ? -step1 : step1;
}

tickSetp是用来计算step的,也就是咱们上文提到的“单位值”,因此可见d3代码内部仍是考虑了“分段”问题的。经过tickStep能够得到每一段的值,再用这个值做为step参与range计算。总之,最最核心的,就是tickStep函数,它决定了你传入给ticks的参数,最后实现的效果。


本文发布在个人博客,你能够到个人博客参与讨论
求个兼职,若是您有web开发方面的须要,能够联系我,生活不容易,且行且珍惜。
请在个人我的博客 www.tangshuang.net 留言,我会联系你。

相关文章
相关标签/搜索