Интеллектуальный расчет позиций тиков на графике - PullRequest
7 голосов
/ 09 февраля 2011

Независимо от того, что я использую matplotlib, Open-Flash-Charts или другие каркасы диаграмм, я всегда заканчиваю тем, что мне нужно найти способ установить пределы и интервалы масштабов x / y, поскольку встроенные функции недостаточно умны (или не совсем ... )

просто попробуйте это в pylab (ipyhton -pylab), чтобы понять, что я имею в виду:

In [1]: a, b, x = np.zeros(10), np.ones(10), np.arange(10)

In [2]: plot(x, a); plot(x, b)

вы увидите только пустую рамку, скрывающую 2 горизонтальные линии под ее верхней и нижней границами.

Интересно, есть ли какой-нибудь алгоритм (который я могу портировать на python), чтобы грамотно установить верхний и нижний пределы и шаги y, а также вычислить, сколько значений показывают толщину x.

Например, допустим, у меня 475 мер как (datetime, temperature) как (x, y) с

2011-01-15 10:45:00 < datetime < 2011-01-17 02:20:00

(один раз в 5 минут) и

26.5 < temperature < 28.3

Мое предложение для этого конкретного случая может быть следующим:

26.4 <= y_scale <= 28.4 с толстым каждые .2

и отметка x_scale каждые 12 пунктов (один раз в час).

Но как быть, если у меня будет всего 20 мер за 20 дней с -21.5 < temperature < 38.7 и так далее? Есть ли стандартизированный метод вокруг?

Ответы [ 4 ]

7 голосов
/ 09 февраля 2011

Вот то, что я использовал годами, это просто и работает достаточно хорошо.Извините за то, что это C, но перевод на Python не должен быть сложным.

Следующая функция необходима из тома Graphic Gems, том 1.

double NiceNumber (const double Value, const int Round) {
  int    Exponent;
  double Fraction;
  double NiceFraction;

  Exponent = (int) floor(log10(Value));
  Fraction = Value/pow(10, (double)Exponent);

  if (Round) {
    if (Fraction < 1.5) 
      NiceFraction = 1.0;
    else if (Fraction < 3.0)
      NiceFraction = 2.0;
    else if (Fraction < 7.0)
      NiceFraction = 5.0;
    else
      NiceFraction = 10.0;
   }
  else {
    if (Fraction <= 1.0)
      NiceFraction = 1.0;
    else if (Fraction <= 2.0)
      NiceFraction = 2.0;
    else if (Fraction <= 5.0)
      NiceFraction = 5.0;
    else
      NiceFraction = 10.0;
   }

  return NiceFraction*pow(10, (double)Exponent);
 }

Используйте ее следующим образомПример выбора «хорошего» начала / конца оси в зависимости от количества отображаемых основных тиков.Если вы не заботитесь о тиках, вы можете просто установить его на постоянное значение (например, 10).

      //Input parameters
  double AxisStart = 26.5;
  double AxisEnd   = 28.3;
  double NumTicks  = 10;

  double AxisWidth;
  double NewAxisStart;
  double NewAxisEnd;
  double NiceRange;
  double NiceTick;

    /* Check for special cases */
  AxisWidth = AxisEnd - AxisStart;
  if (AxisWidth == 0.0) return (0.0);

    /* Compute the new nice range and ticks */
  NiceRange = NiceNumber(AxisEnd - AxisStart, 0);
  NiceTick = NiceNumber(NiceRange/(NumTicks - 1), 1);

    /* Compute the new nice start and end values */
  NewAxisStart = floor(AxisStart/NiceTick)*NiceTick;
  NewAxisEnd = ceil(AxisEnd/NiceTick)*NiceTick;

  AxisStart = NewAxisStart; //26.4
  AxisEnd = NewAxisEnd;     //28.4
3 голосов
/ 10 февраля 2011

Я сообщаю здесь мою версию Python C-кода выше, если это может кому-то помочь:

import math

def nice_number(value, round_=False):
    '''nice_number(value, round_=False) -> float'''
    exponent = math.floor(math.log(value, 10))
    fraction = value / 10 ** exponent

    if round_:
        if fraction < 1.5: nice_fraction = 1.
        elif fraction < 3.: nice_fraction = 2.
        elif fraction < 7.: nice_fraction = 5.
        else: niceFraction = 10.
    else:
        if fraction <= 1: nice_fraction = 1.
        elif fraction <= 2: nice_fraction = 2.
        elif fraction <= 5: nice_fraction = 5.
        else: nice_fraction = 10.

    return nice_fraction * 10 ** exponent

def nice_bounds(axis_start, axis_end, num_ticks=10):
    '''
    nice_bounds(axis_start, axis_end, num_ticks=10) -> tuple
    @return: tuple as (nice_axis_start, nice_axis_end, nice_tick_width)
    '''
    axis_width = axis_end - axis_start
    if axis_width == 0:
        nice_tick = 0
    else:
        nice_range = nice_number(axis_width)
        nice_tick = nice_number(nice_range / (num_ticks -1), round_=True)
        axis_start = math.floor(axis_start / nice_tick) * nice_tick
        axis_end = math.ceil(axis_end / nice_tick) * nice_tick

    return axis_start, axis_end, nice_tick

использовать как:

>>> nice_bounds(26.5, 28.3)
(26.4, 28.4, 0.2)

Также добавьте портирование javascript:

function nice_number(value, round_){
    //default value for round_ is false
    round_ = round_ || false;
    // :latex: \log_y z = \frac{\log_x z}{\log_x y}
    var exponent = Math.floor(Math.log(value) / Math.log(10));
    var fraction = value / Math.pow(10, exponent);

    if (round_)
        if (fraction < 1.5)
            nice_fraction = 1.
        else if (fraction < 3.)
            nice_fraction = 2.
        else if (fraction < 7.)
            nice_fraction = 5.
        else
            nice_fraction = 10.
    else
        if (fraction <= 1)
            nice_fraction = 1.
        else if (fraction <= 2)
            nice_fraction = 2.
        else if (fraction <= 5)
            nice_fraction = 5.
        else
            nice_fraction = 10.

    return nice_fraction * Math.pow(10, exponent)
}

function nice_bounds(axis_start, axis_end, num_ticks){
    //default value is 10
    num_ticks = num_ticks || 10;
    var axis_width = axis_end - axis_start;

    if (axis_width == 0){
        axis_start -= .5
        axis_end += .5
        axis_width = axis_end - axis_start
    }

    var nice_range = nice_number(axis_width);
    var nice_tick = nice_number(nice_range / (num_ticks -1), true);
    var axis_start = Math.floor(axis_start / nice_tick) * nice_tick;
    var axis_end = Math.ceil(axis_end / nice_tick) * nice_tick;
    return {
        "min": axis_start,
        "max": axis_end,
        "steps": nice_tick
    }
}
0 голосов
/ 20 марта 2019

А вот версия @ uesp answer в TypeScript / JavaScript ES6:

function niceNumber(value: number, round = false) {

  const exponent = Math.floor(Math.log10(value));
  const fraction = value / Math.pow(10, exponent);

  let niceFraction: number;

  if (round) {
    if (fraction < 1.5) {
      niceFraction = 1.0;
    } else if (fraction < 3.0) {
      niceFraction = 2.0;
    } else if (fraction < 7.0) {
      niceFraction = 5.0;
    } else {
      niceFraction = 10.0;
    }
  } else {
    if (fraction <= 1.0) {
      niceFraction = 1.0;
    } else if (fraction <= 2.0) {
      niceFraction = 2.0;
    } else if (fraction <= 5.0) {
      niceFraction = 5.0;
    } else {
      niceFraction = 10.0;
    }
  }

  return niceFraction * Math.pow(10, exponent);

}

export function calcTicks(minValue: number, maxValue: number, ticksCount: number) {

  const range = niceNumber(maxValue - minValue);

  const tickValue = niceNumber(range / (ticksCount - 1), true);

  const ticks: number[] = [];
  for (let i = 0; i < ticksCount; i++) {
    ticks.push(minValue + tickValue * i);
  }

  return ticks;

}

Однако функция calcTicks() здесь возвращает массив тиковвместо начальных и конечных границ.

0 голосов
/ 14 июля 2017

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

Например:

auto_tick([-120, 580], max_tick=10, tf_inside=False)
Out[224]: array([-100.,   -0.,  100.,  200.,  300.,  400.,  500.])
auto_tick([-120, 580], max_tick=20, tf_inside=False)
Out[225]: array([-100.,  -50.,   -0.,   50.,  100.,  150.,  200.,  250.,  300., 350.,  400.,  450.,  500.,  550.])

Ниже приведен код Pythonфункция

def auto_tick(data_range, max_tick=10, tf_inside=False):
    """
    tool function that automatically calculate optimal ticks based on range and the max number of ticks
    :param data_range:   range of data, e.g. [-0.1, 0.5]
    :param max_tick:     max number of ticks, an interger, default to 10
    :param tf_inside:    True/False if only allow ticks to be inside
    :return:             list of ticks
    """
    data_span = data_range[1] - data_range[0]
    scale = 10.0**np.floor(np.log10(data_span))    # scale of data as the order of 10, e.g. 1, 10, 100, 0.1, 0.01, ...
    list_tick_size_nmlz = [5.0, 2.0, 1.0, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01]   # possible tick sizes for normalized data in range [1, 10]
    tick_size_nmlz = 1.0     # initial tick size for normalized data
    for i in range(len(list_tick_size_nmlz)):                 # every loop reduces tick size thus increases tick number
        num_tick = data_span/scale/list_tick_size_nmlz[i]     # number of ticks for the current tick size
        if num_tick > max_tick:                               # if too many ticks, break loop
            tick_size_nmlz = list_tick_size_nmlz[i-1]
            break
    tick_size = tick_size_nmlz * scale             # tick sizse for the original data
    ticks = np.unique(np.arange(data_range[0]/tick_size, data_range[1]/tick_size).round())*tick_size    # list of ticks

    if tf_inside:     # if only allow ticks within the given range
        ticks = ticks[ (ticks>=data_range[0]) * (ticks<=data_range[1])]

    return ticks
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...