問題
假設有一個可見寬度固定的可縮放的座標軸,軸上需要每隔一定的距離就要標註一次時間,這個距離不能太大,也不能太小,在縮放時如何確定可視窗口中的左座標和右座標及取多大的間隔進行分割呢?如下圖:
- 初始座標軸:0-1000ns
- 縮放後:250-550ns
- 進一步縮放:338-348ns
如圖所示,當座標範圍爲0-1000時,每隔100分割一次座標軸並且輸出座標, 當座標範圍爲250-550時,每隔50分割一次座標軸並且輸出座標,當座標範圍爲338-348時,每隔2分割一次座標軸並且輸出座標,進一步的,再次縮放後會每隔1進行一次分割並且輸出座標。這個座標軸保證了在縮放的過程中保持最多輸出十個座標值,而且每次分割的值也是很合理的整數值。
思考
要完成這個工作,需要解決的問題主要有:
- 確定當前窗口中顯示幾個座標值
- 座標值間的間隔如何確定
- 如果縮放後的座標軸兩端不是合理的整數(即
小數
,初值不是間隔值的倍數
,終值不是間隔值的倍數
)該如何確定。 - 間隔值的過渡如何平緩?假如初始區間長度爲10000000000,如何保證在縮放到0-1的過程中間隔值不會跳躍或者反覆橫跳
嘗試建立數學模型
可以想到首先要獲取縮放後的區間長度,假如區間爲[a, b)
,是個左閉右開區間,因爲最後一個值可以不考慮標註座標值。因此,
確定區間個數
- 取作爲區間長度;
- 確定區間長度的數量級:, 然後就近取整,得到
- 此時,通過得到在2中得到的數量級下,區間可以被多少個點進行分割(得到的區間個數爲)。這就保證了分割的區間數量始終在10以下。
- 經過3處理後,區間數量被限制在了1-10,但是如果只是這樣分割,那麼假如區間是
[100, 200]
,就會被只分割爲1段,也不合理。 - 因此在這裏對區間數量再次進行處理,如果3得到了兩個端點(一個區間),那麼表示這個區間佔滿了可視窗口,那麼區間端點數量應該改爲10(9段),確保這個區間會被分割,而且不至於很寬,如果3得到的區間段端點數爲6-10(5-9段),那麼就按照得到的數量進行分割,而端點數若爲3-5(2-4段),應該將其乘二。這樣一來,區間段端點數就被限制在了6-10之間(5-9段),比3得到的結果更優化,即:
確定區間長度
現在已經得到了根據數量級去分割的端點數,和經過優化的,就需要計算出每個優化後區間的長度。
- 先求出優化前的區間總長度,這時換算得到的是一個基於量級的整數區間;
- 然後再求優化後的各區間長度
確定起始座標
前面提到過,縮放後的座標軸最左端的座標不一定滿足的倍數,換言之,一般情況下不會滿足這種情況,那麼就需要像文章一開始的第二幅圖那樣,最左端不一定要有標註的座標值,因此需要計算出第一個座標值應該寫在何處。
- 先對區間左端點取整,即
- 確定對應於優化後區間長度的初值,先求出左端點所在的區間的左端點,即左端點除以區間長度後取整,再乘以區間長度;
- 如果小於左端點,那麼要加上一個區間長度,
計算剩餘的區間段端點座標
至此,已經得到了初始點座標,單位區間長度,以及區間數量,馬上得出最終的各座標值:
Javascript代碼
function getOptInterval(intv){
if (intv > 5){
return intv;
} else if (intv>2){
return intv * 2;
} else {
return 10;
}
}
function generateAxis(start, end){
let dif = end - start;
let mag = Math.round(Math.log10(dif));
let n = parseInt(dif / (10**mag)) +1;
let n1 = getOptInterval(n);
let l = n * (10**mag) / n1;
let optStart = parseInt(parseInt(start)/l) * l;
if (optStart < start){
optStart += l;
}
let result = [];
for (int i = 0; i <= n1; i++){
result.push(optStart + i * l);
}
return result;
}
結語
這種分割模式可以滿足當通過一個定寬窗口觀察變長座標軸時,座標軸上多個參考座標值的變化,但是僅限於不是很長的觀察窗口。