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 留言,我會聯繫你。

相關文章
相關標籤/搜索