问题
假设有一个可见宽度固定的可缩放的坐标轴,轴上需要每隔一定的距离就要标注一次时间,这个距离不能太大,也不能太小,在缩放时如何确定可视窗口中的左坐标和右坐标及取多大的间隔进行分割呢?如下图:
- 初始坐标轴:0-1000ns
- 缩放后:250-550ns
- 进一步缩放:338-348ns
如图所示,当坐标范围为0-1000时,每隔100分割一次坐标轴并且输出坐标, 当坐标范围为250-550时,每隔50分割一次坐标轴并且输出坐标,当坐标范围为338-348时,每隔2分割一次坐标轴并且输出坐标,进一步的,再次缩放后会每隔1进行一次分割并且输出坐标。这个坐标轴保证了在缩放的过程中保持最多输出十个坐标值,而且每次分割的值也是很合理的整数值。
思考
要完成这个工作,需要解决的问题主要有:
- 确定当前窗口中显示几个坐标值
- 坐标值间的间隔如何确定
- 如果缩放后的坐标轴两端不是合理的整数(即
小数
,初值不是间隔值的倍数
,终值不是间隔值的倍数
)该如何确定。 - 间隔值的过渡如何平缓?假如初始区间长度为10000000000,如何保证在缩放到0-1的过程中间隔值不会跳跃或者反复横跳
尝试建立数学模型
可以想到首先要获取缩放后的区间长度,假如区间为[a, b)
,是个左闭右开区间,因为最后一个值可以不考虑标注坐标值。因此,
确定区间个数
- 取 d = b − a d=b-a d=b−a作为区间长度;
- 确定区间长度的数量级: m a g = lg d mag=\lg d mag=lgd, 然后就近取整,得到 m a g ‾ \overline{mag} mag
- 此时,通过 n = [ d 1 0 m a g ‾ ] + 1 n = [\frac{d}{10^{\overline{mag}}}]+1 n=[10magd]+1得到在2中得到的数量级下,区间可以被多少个点进行分割(得到的区间个数为 n − 1 n-1 n−1)。这就保证了分割的区间数量始终在10以下。
- 经过3处理后,区间数量被限制在了1-10,但是如果只是这样分割,那么假如区间是
[100, 200]
,就会被只分割为1段,也不合理。 - 因此在这里对区间数量再次进行处理,如果3得到了两个端点(一个区间),那么表示这个区间占满了可视窗口,那么区间端点数量应该改为10(9段),确保这个区间会被分割,而且不至于很宽,如果3得到的区间段端点数为6-10(5-9段),那么就按照得到的数量进行分割,而端点数若为3-5(2-4段),应该将其乘二。这样一来,区间段端点数就被限制在了6-10之间(5-9段),比3得到的结果更优化,即: n ′ = { 10 f o r n = 2 2 ∗ n f o r 2 < n ≤ 5 l f o r n > 5 n'=\left\{\begin{array}{rcl}10 & for & n=2 \\ 2*n & for & 2 < n \leq 5 \\ l & for & n>5 \end{array} \right. n′=⎩⎨⎧102∗nlforforforn=22<n≤5n>5
确定区间长度
现在已经得到了根据数量级去分割的端点数 n n n,和经过优化的 n ′ n' n′,就需要计算出每个优化后区间的长度。
- 先求出优化前的区间总长度 l = n ∗ 1 0 m a g ‾ l=n*10^{\overline{mag}} l=n∗10mag,这时换算得到的是一个基于 m a g mag mag量级的整数区间;
- 然后再求优化后的各区间长度 l ′ = l n ′ l'=\frac{l}{n'} l′=n′l
确定起始坐标
前面提到过,缩放后的坐标轴最左端的坐标不一定满足 l ′ l' l′的倍数,换言之,一般情况下不会满足这种情况,那么就需要像文章一开始的第二幅图那样,最左端不一定要有标注的坐标值,因此需要计算出第一个坐标值应该写在何处。
- 先对区间左端点取整,即 a ‾ = [ a ] \overline{a} = [a] a=[a]
- 确定对应于优化后区间长度的初值,先求出左端点所在的区间的左端点 α = [ a ‾ l ′ ] ∗ l ′ \alpha=[\frac{\overline{a}}{l'}]*l' α=[l′a]∗l′,即左端点除以区间长度后取整,再乘以区间长度;
- 如果 α \alpha α小于左端点,那么要加上一个区间长度, α = α + l ′ , α < a \alpha=\alpha+l',\alpha<a α=α+l′,α<a
计算剩余的区间段端点坐标
至此,已经得到了初始点坐标 α \alpha α,单位区间长度 l ′ l' l′,以及区间数量 n ′ n' n′,马上得出最终的各坐标值: x = α + i ∗ l ′ , i ∈ [ 0 , n ′ ] x=\alpha+i*l',i\in[0, n'] x=α+i∗l′,i∈[0,n′]
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;
}
结语
这种分割模式可以满足当通过一个定宽窗口观察变长坐标轴时,坐标轴上多个参考坐标值的变化,但是仅限于不是很长的观察窗口。