Алгоритм для хороших меток графа для оси времени/даты?
Я ищу алгоритм "хороших чисел" для определения меток на оси значений даты/времени. Я знаком с Пол Хекберт: алгоритм Nice Numbers.
У меня есть график, который отображает время/дату на оси X, и пользователь может увеличивать масштаб и смотреть на меньший временной интервал. Я ищу алгоритм, который выбирает хорошие даты для отображения на тиках.
Например:
- Глядя на день или около того: 1/1 12:00, 1/1 4:00, 1/1 8:00...
- Глядя на неделю: 1/1, 1/2, 1/3...
- Глядя на месяц: 1/09, 2/09, 3/09...
Символы ярких меток не должны соответствовать первой видимой точке, но близко к ней.
Кто-нибудь знаком с таким алгоритмом?
Ответы
Ответ 1
В статье "хорошие номера" вы упомянули, что
самые красивые числа в десятичной форме: 1, 2, 5 и все 10 кратных этих чисел
Поэтому я думаю, что для того, чтобы делать что-то подобное с датой/временем, вам нужно начать с аналогичного разрушения компонентов. Поэтому возьмите приятные факторы каждого типа интервала:
- Если вы показываете секунды или минуты, используйте 1, 2, 3, 5, 10, 15, 30
(Я пропустил 6, 12, 15, 20, потому что они не "чувствуют" право).
- Если вы показываете часы, используйте 1, 2, 3, 4, 6, 8, 12
- для использования дней 1, 2, 7
- в течение нескольких недель используйте 1, 2, 4 (13 и 26 подходят для модели, но кажутся мне слишком странными).
- в течение нескольких месяцев используйте 1, 2, 3, 4, 6
- в течение многих лет используют 1, 2, 5 и 10-кратные мощности.
Теперь, очевидно, это начинает разрушаться, когда вы попадаете в большие суммы. Конечно, вы не хотите показывать 5 недель в минутах, даже в "хороших" интервалах 30 минут или еще что-то. С другой стороны, когда у вас есть только 48 часов, вы не хотите показывать интервалы в 1 день. Фокус, как вы уже указали, - это найти достойные точки перехода.
Как бы то ни было, я бы сказал, что разумная точка кроссовера будет примерно в два раза больше, чем следующий интервал. Это даст вам следующее (мин и максимальное количество интервалов, показанных впоследствии)
- используйте секунды, если у вас меньше 2 минут (1-120).
- используйте минуты, если у вас меньше 2 часов (2-120).
- используйте часы, если у вас меньше 2 дней (2-48).
- используйте дни, если у вас меньше 2 недель (2-14).
- используйте недели, если у вас меньше 2 месяцев (2-8/9)
- используйте месяцы, если у вас меньше 2 лет (2-24)
- В противном случае используйте годы (хотя вы могли бы продолжать с десятилетиями, столетиями и т.д., если ваши диапазоны могут быть такими длинными)
К сожалению, наши непоследовательные временные интервалы означают, что вы закончите с некоторыми случаями, которые могут иметь более ста интервалов, в то время как другие имеют не более 8 или 9. Таким образом, вы захотите выбрать размер ваших интервалов, t имеет более 10-15 интервалов максимум (или менее 5 в этом отношении). Кроме того, вы можете отказаться от строгого определения в 2 раза следующего большого интервала, если считаете, что его легко отслеживать. Например, вы можете использовать часы до 3 дней (72 часа) и недели до 4 месяцев. Может потребоваться небольшая пробная версия и ошибка.
Итак, чтобы вернуться назад, выберите тип интервала, основанный на размере вашего диапазона, затем выберите размер интервала, выбрав один из "хороших" чисел, который оставит вас от 5 до 15 меток. Или, если вы знаете и/или можете контролировать фактическое количество пикселей между отметками меток, вы можете установить верхнюю и нижнюю границы того, сколько пикселей приемлемо между тиками (если они расположены слишком далеко друг от друга, график может быть трудно читаемым, но если слишком много тиков, граф будет загроможден, а ваши метки могут перекрываться).
Ответ 2
По-прежнему нет ответа на этот вопрос... Тогда я брошу свою первую идею! Я предполагаю, что у вас есть диапазон видимой оси.
Скорее всего, я бы это сделал.
Грубое псевдо:
// 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 для "минуты" и т.д., Как вы сказали.
Ответ 3
Посмотрите
http://tools.netsa.cert.org/netsa-python/doc/index.html
У этого есть nice.py(python/netsa/data/nice.py), который я считаю автономным и должен работать нормально.
Ответ 4
Я бы предложил вам захватить исходный код gnuplot или RRDTool (или даже Flot) и изучить, как они подходят к этой проблеме. В общем случае, скорее всего, будут использоваться N меток, основанных на ширине вашего сюжета, что своего рода "привязка" к ближайшему "хорошему" номеру.
Каждый раз, когда я писал такой алгоритм (слишком много раз на самом деле), я использовал таблицу "предпочтений"... т.е. на основе временного диапазона на сюжете, решить, использую ли я недели, Дни, часы, минуты и т.д. В качестве основной оси. Я обычно включал некоторое предпочтительное форматирование, так как я редко хочу видеть дату каждой минуты, которую я рисую на графике.
Я был бы счастлив, но был бы удивлен, если бы кто-то использовал формулу (например, Хекберт), чтобы найти "хороший", поскольку изменение в единицах времени между минутами, часами, днями и неделями не так линейно.
Ответ 5
[Edit - я расширил это немного больше на 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 "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 "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']