Подтвердить что ты не робот

Алгоритм для хороших меток графа для оси времени/даты?

Я ищу алгоритм "хороших чисел" для определения меток на оси значений даты/времени. Я знаком с Пол Хекберт: алгоритм 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...

Символы ярких меток не должны соответствовать первой видимой точке, но близко к ней.

Кто-нибудь знаком с таким алгоритмом?

4b9b3361

Ответ 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 для "минуты" и т.д., Как вы сказали.

Ответ 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']