我正在寻找一个“好数字”算法来确定日期/时间值轴上的标签。我熟悉Paul Heckbert's Nice Numbers algorithm。
我有一个在X轴上显示时间/日期的图表,用户可以放大并查看较小的时间范围。我正在寻找一种算法,可以选择好的日期显示在刻度线上。
例如:
漂亮的标签刻度不需要与第一个可见点相对应,但要靠近它。
有人熟悉这样的算法吗?
答案 0 :(得分:8)
您链接的“好数字”文章提到了
十进制中最好的数字是1,2,5和这些数字的所有10倍幂
所以我认为做类似于日期/时间的事情你需要从类似的分解组件开始。因此,请考虑每种类型间隔的好因素:
现在很明显,当你进入更大的数量时,这会开始崩溃。当然,你不想显示5周的分钟,即使以30分钟的“漂亮”间隔也是如此。另一方面,当您只有48小时的价值时,您不希望每隔1天显示一次。你已经指出的诀窍就是找到合适的过渡点。
只是预感,我会说合理的交叉点大约是下一个间隔的两倍。这将给你以下(之后显示的最小和最大间隔数)
不幸的是,我们不一致的时间间隔意味着你最终会遇到一些可能有超过100个间隔的病例,而其他病例最多只有8或9.所以你要选择你的间隔大小,而不是你不要t最多有10-15个间隔(或者少于5个间隔)。此外,如果您认为很容易跟踪,那么您可以从严格定义中断下一个最大间隔的2倍。例如,您可以使用最多3天(72小时)和数周(最多4个月)的工作时间。可能需要进行一些试验和错误。
所以要重新选择,根据范围的大小选择间隔类型,然后通过选择一个“漂亮”数字来选择间隔大小,这将使您留下5到15个刻度线。或者,如果您知道和/或可以控制刻度线之间的实际像素数,则可以在刻度线之间设置可接受的像素的上限和下限(如果它们间隔太远,则图形可能难以读取,但如果有太多的刻度线图形将会混乱,并且您的标签可能会重叠。)
答案 1 :(得分:1)
仍然没有回答这个问题......我会在那时抛出我的第一个想法!我假设你有可见轴的范围。
这可能就是我的意思。
粗伪:
// quantify range
rangeLength = endOfVisiblePart - startOfVisiblePart;
// qualify range resolution
if (range < "1.5 day") {
resolution = "day"; // it can be a number, e.g.: ..., 3 for day, 4 for week, ...
} else if (range < "9 days") {
resolution = "week";
} else if (range < "35 days") {
resolution = "month";
} // you can expand this in both ways to get from nanoseconds to geological eras if you wish
之后,它应该(取决于您可以轻松访问的内容)很容易确定每个漂亮标签的价值。根据“分辨率”,您可以采用不同的格式。例如:MM / DD为“周”,MM:SS为“分钟”等,就像你说的那样。
答案 2 :(得分:1)
看看
http://tools.netsa.cert.org/netsa-python/doc/index.html
它有一个nice.py(python / netsa / data / nice.py),我认为它是独立的,应该可以正常工作。
答案 3 :(得分:0)
我建议你把源代码抓到gnuplot或RRDTool(甚至是Flot),然后检查他们如何处理这个问题。一般情况可能是根据你的情节宽度应用了N个标签,这种情况会“捕捉”到最近的“漂亮”数字。
每次我写这样的算法(真的太多次),我都会使用一个'偏好'表...即:根据情节的时间范围,决定我是否使用周,Days,Hours,Minutes等作为主轴点。我通常包括一些首选格式,因为我很少想看到我在图表上绘制的每一分钟的日期。
我很高兴但很惊讶地发现有人使用公式(像Heckbert那样)找到'好',因为时间单位在分钟,小时,天和周之间的变化不是线性的。
答案 4 :(得分:0)
[编辑 - 我在http://www.acooke.org/cute/AutoScalin0.html]
进一步扩展了这一点“好数字”算法的天真扩展似乎适用于基数12和60,它提供了小时和分钟的良好间隔。这是我刚刚攻击的代码:
LIM10 = (10, [(1.5, 1), (3, 2), (7, 5)], [1, 2, 5])
LIM12 = (12, [(1.5, 1), (3, 2), (8, 6)], [1, 2, 6])
LIM60 = (60, [(1.5, 1), (20, 15), (40, 30)], [1, 15, 40])
def heckbert_d(lo, hi, ntick=5, limits=None):
'''
Heckbert's "nice numbers" algorithm for graph ranges, from "Graphics Gems".
'''
if limits is None:
limits = LIM10
(base, rfs, fs) = limits
def nicenum(x, round):
step = base ** floor(log(x)/log(base))
f = float(x) / step
nf = base
if round:
for (a, b) in rfs:
if f < a:
nf = b
break
else:
for a in fs:
if f <= a:
nf = a
break
return nf * step
delta = nicenum(hi-lo, False)
return nicenum(delta / (ntick-1), True)
def heckbert(lo, hi, ntick=5, limits=None):
'''
Heckbert's "nice numbers" algorithm for graph ranges, from "Graphics Gems".
'''
def _heckbert():
d = heckbert_d(lo, hi, ntick=ntick, limits=limits)
graphlo = floor(lo / d) * d
graphhi = ceil(hi / d) * d
fmt = '%' + '.%df' % max(-floor(log10(d)), 0)
value = graphlo
while value < graphhi + 0.5*d:
yield fmt % value
value += d
return list(_heckbert())
因此,例如,如果要显示0到60之间的秒数,
>>> heckbert(0, 60, limits=LIM60)
['0', '15', '30', '45', '60']
或0到5小时:
>>> heckbert(0, 5, limits=LIM12)
['0', '2', '4', '6']
答案 5 :(得分:0)
理论上你也可以改变你的观念。如果不是您的数据位于可视化的中心,而是位于中心,则表示您的数据。
当您知道数据日期的开始和结束时,您可以创建包含所有日期的比例,并按此比例分配数据。像一个固定的尺度。
您可以使用年,月,日,小时等类型的比例,并将比例缩放到这些比例,这意味着您删除了自由缩放的概念。
优点是可以轻松显示日期差距。但是如果你有很多差距,那也会变得毫无用处。