d3中的axis.ticks详解

砾滩超类
• 阅读 7703

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

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

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

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个刻度。

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

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.

当超过10之后怎么算?传入大于10之后,就以20,50和100来计算,当然,10还是算数的,传入15相当于传入10,但是传入16就相当于传入20.这个规则跟10以内其实差不多。

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

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

比如说,如果你的定义域为[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,会出现什么结果?

d3中的axis.ticks详解

结果没有在轴末端显示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 留言,我会联系你。

点赞
收藏
评论区
推荐文章
CuterCorley CuterCorley
4年前
Python matplotlab库使用方法及注意事项
1.Python使用Matplotlib模块时坐标轴标题中文及各种特殊符号显示方法pythonimportmatplotlib.pyplotasplttarange(4pi,4pi,0.01)ysin(t)/tplt.plot(t,y)plt.title('www.jb51.nettest')plt.xlabe
Wesley13 Wesley13
3年前
CSS3之2D与3D变换
!(https://oscimg.oschina.net/oscnet/ad817cc0a9554005b6198dc385a416e5cd0.gif)目录关于坐标轴2D变换translate(x,y)rotate(deg)s
Stella981 Stella981
3年前
Decision Surface
一、深入理解超平面很多讲解支持向量机的文章从一开头就开始讲超平面及其方程,这样对多数没有基础的人来说较难理解,下面将从一维直线讲起,带你逐步过渡理解超平面。例如,下面是一个只有x轴的一维坐标轴,该轴同样存在原点,x轴正方向和x轴反方向,且该轴上分布了很多点:!(https://oscimg.oschina.net/oscnet/2d9
Stella981 Stella981
3年前
CSS 层叠上下文(Stacking Context)
本文首发于政采云前端团队博客:CSS层叠上下文(StackingContext)https://www.zoo.team/article/cssstackingcontext在网页制作的过程中,元素与元素之间的位置关系,在坐标轴上一般可体现为X轴、Y轴和Z轴。对于X轴和Y轴的定位大多数开发都能比较直观的
Stella981 Stella981
3年前
SVG 绘制椭圆
本节我们来学习如何在SVG中绘制椭圆,椭圆和圆形有点像,但是又不一样。圆形只有一个半径,而椭圆x轴和y轴上的半径不同,所以椭圆就是一个不规则的圆。如何绘制一个椭圆在绘制椭圆时,可以通过cx和cy属性确定椭圆的圆心,rx设置椭圆的x轴的半径,ry设置y轴的半径。
Easter79 Easter79
3年前
SVG 绘制椭圆
本节我们来学习如何在SVG中绘制椭圆,椭圆和圆形有点像,但是又不一样。圆形只有一个半径,而椭圆x轴和y轴上的半径不同,所以椭圆就是一个不规则的圆。如何绘制一个椭圆在绘制椭圆时,可以通过cx和cy属性确定椭圆的圆心,rx设置椭圆的x轴的半径,ry设置y轴的半径。
Stella981 Stella981
3年前
D3(v5) in TypeScript 坐标轴之 scaleBand用法
在学习d3时候,发现在TS中实现D3的坐标轴中遇到一些错误,而这些错误却不会存在于js(因为ts的类型检查)写法中,因此做下笔记:importasd3from'd3';importasReactfrom'react';classTestGraphextendsReact.Component{publicc
Easter79 Easter79
3年前
SVG 如何绘制椭圆
本节我们来学习如何在SVG中绘制椭圆,椭圆和圆形有点像,但是又不一样。圆形只有一个半径,而椭圆x轴和y轴上的半径不同,所以椭圆就是一个不规则的圆。如何绘制一个椭圆在绘制椭圆时,可以通过cx和cy属性确定椭圆的圆心,rx设置椭圆的x轴的半径,ry设置y轴的半径。
Stella981 Stella981
3年前
SVG 如何绘制椭圆
本节我们来学习如何在SVG中绘制椭圆,椭圆和圆形有点像,但是又不一样。圆形只有一个半径,而椭圆x轴和y轴上的半径不同,所以椭圆就是一个不规则的圆。如何绘制一个椭圆在绘制椭圆时,可以通过cx和cy属性确定椭圆的圆心,rx设置椭圆的x轴的半径,ry设置y轴的半径。
Wesley13 Wesley13
3年前
D3画完整柱状图(带坐标轴、标签)
   昨天晚上本来打算花一点时间把之前学的柱状图改一下,用CSV文件来替换自定义数据。这一替换可不得了,一晚上就搭进去了,还好今早找到了问题的所在,原因在于我的数据引用出了问题。现在就来讲解一下如何画一个柱状图吧:  柱状图的画法和折线图其实很类似,只要掌握了比例尺的用法和坐标轴的画法,我们只要在此基础上添加“rect”元素添加矩形就可以了,但这其中
陈杨 陈杨
2星期前
鸿蒙5莓创图表组件折线类型的实际案例分享-yAxis
大家好,欢迎回来鸿蒙5莓创图表组件的专场,我们这一期来讲解McLineChart折线图中yAxis的实际应用场景和案例用法。yAxis基础介绍yAxis是折线图中Y轴的配置项,用于控制Y轴的显示样式、刻度、标签等。在莓创图表中,yAxis支持以下核心功能: