Разумное оптимизированное масштабирование графика - PullRequest
31 голосов
/ 04 марта 2009

Мне нужно сделать график с оптимизированным максимальным значением оси y .

Текущий метод построения диаграмм, который я использую, просто использует максимальное значение всех графиков, затем делит его на десять и использует его в качестве линий сетки. Я не писал это.

Примечание об обновлении: Эти графики были изменены. Как только я исправил код, мои динамические графики начали работать, что делало этот вопрос бессмысленным (потому что в примерах больше не было ошибок). Я обновил их со статическими изображениями, но некоторые ответы содержат разные значения. Запомни. Old Chart До сих пор в феврале было от 12003 до 14003 входящих звонков. Информативно, но некрасиво.

Я бы хотел избежать диаграмм, которые выглядят так, будто обезьяна придумала y -осные числа.

Использование API Google Charts немного помогает, но это все-таки не совсем то, что я хочу. Google API Chart Числа чистые, но вершина значения y всегда совпадает с максимальным значением на графике. Эта диаграмма масштабируется от 0 до 1357. Мне нужно рассчитать правильное значение 1400, , проблематично .


Я добавляю rbobby определение «хорошего» числа здесь, потому что это так хорошо объясняет.

  • "Хороший" номер - это номер, который имеет 3 или меньше ненулевых цифр (например, 1230000)
  • «Хорошее» число имеет те же или несколько ненулевых цифр, что и нулевое число (например, 1230 не хорошо, 1200 хорошо)
  • Самыми хорошими числами являются числа, кратные 3 нулям (например, "1000", "1 000 000")
  • Вторыми самыми хорошими числами являются числа с кратными 3 нулями плюс 2 нуля (например, "1 500 000", "1 200")

Решение

New Chart

Я нашел способ получить желаемые результаты, используя модифицированную версию идеи Марка Рэнсома.

Кулак, код Марка Рэнсома определяет оптимальное расстояние между тиками, если указано количество тиков. Иногда это число в два раза превышает максимальное значение на графике, в зависимости от того, сколько линий сетки вы хотите.

Что я делаю, так это запускаю код Марка с 5, 6, 7, 8, 9 и 10 линиями сетки (галочками), чтобы найти, какая из них самая низкая. При значении 23 высота диаграммы увеличивается до 25 с линией сетки 5, 10, 15, 20 и 25. При значении 26 высота диаграммы составляет 30 с линиями сетки 5, 10. , 15, 20, 25 и 30. Он имеет одинаковое расстояние между линиями сетки, но их больше.

Итак, вот несколько шагов, чтобы просто скопировать то, что делает Excel, чтобы сделать все диаграммы красивыми.

  1. Временно увеличьте самое высокое значение на графике примерно на 5% (так что между самой высокой точкой графика и вершиной области графика всегда будет некоторое пространство. Мы хотим, чтобы 99,9 округлилось до 120)
  2. Найдите оптимальное расположение линий сетки для сетки 5, 6, 7, 8, 9 и 10 линии.
  3. Выберите самое низкое из этих чисел. Запомните количество линий сетки, которое потребовалось, чтобы получить это значение.
  4. Теперь у вас есть оптимальная высота графика. Линии / полоса никогда не встанут на верхушку графика, и у вас будет оптимальное количество тиков.

PHP:

function roundUp($maxValue){
    $optiMax = $maxValue * 2;
    for ($i = 5; $i <= 10; $i++){
        $tmpMaxValue = bestTick($maxValue,$i);
        if (($optiMax > $tmpMaxValue) and ($tmpMaxValue > ($maxValue + $maxValue * 0.05))){
            $optiMax = $tmpMaxValue;
            $optiTicks = $i;
        }
    }
    return $optiMax;
}
function bestTick($maxValue, $mostTicks){
    $minimum = $maxValue / $mostTicks;
    $magnitude = pow(10,floor(log($minimum) / log(10)));
    $residual = $minimum / $magnitude;
    if ($residual > 5){
        $tick = 10 * $magnitude;
    } elseif ($residual > 2) {
        $tick = 5 * $magnitude;
    } elseif ($residual > 1){
        $tick = 2 * $magnitude;
    } else {
        $tick = $magnitude;
    }
    return ($tick * $mostTicks);
}

Python:

import math

def BestTick(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum) / math.log(10))
    residual = minimum / magnitude
    if residual > 5:
        tick = 10 * magnitude
    elif residual > 2:
        tick = 5 * magnitude
    elif residual > 1:
        tick = 2 * magnitude
    else:
        tick = magnitude
    return tick

value = int(input(""))
optMax = value * 2
for i in range(5,11):
    maxValue = BestTick(value,i) * i
    print maxValue
    if (optMax > maxValue) and (maxValue > value  + (value*.05)):
        optMax = maxValue
        optTicks = i
print "\nTest Value: " + str(value + (value * .05)) + "\n\nChart Height: " + str(optMax) + " Ticks: " + str(optTicks)

Ответы [ 6 ]

5 голосов
/ 04 марта 2009

Это из предыдущего аналогичного вопроса:

Алгоритм "хороших" интервалов линий сетки на графике

Я сделал это с какой-то грубой силовой метод. Во-первых, выяснить максимальное количество отметок вы можете вписаться в пространство. Разделите общее диапазон значений по количеству тики; это минимум интервал между галочками. Сейчас посчитаем пол логарифма основания 10 до получить величину тика, и разделите на это значение. Вы должны закончить с чем-то в диапазоне от 1 до 10. Просто выберите круглое число, большее или равное значению, и умножить на логарифм рассчитано ранее. Это ваша окончательный интервал между тиками.

Пример на Python:

import math

def BestTick(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum) / math.log(10))
    residual = minimum / magnitude
    if residual > 5:
        tick = 10 * magnitude
    elif residual > 2:
        tick = 5 * magnitude
    elif residual > 1:
        tick = 2 * magnitude
    else:
        tick = magnitude
    return tick
3 голосов
/ 04 июня 2011

Небольшое уточнение и проверка ... (работает для дробных единиц, а не только целых)

public void testNumbers() {
        double test = 0.20000;

        double multiple = 1;
        int scale = 0;
        String[] prefix = new String[]{"", "m", "u", "n"};
        while (Math.log10(test) < 0) {
            multiple = multiple * 1000;
            test = test * 1000;
            scale++;
        }

        double tick;
        double minimum = test / 10;
        double magnitude = 100000000;
        while (minimum <= magnitude){
            magnitude = magnitude / 10;
        }

        double residual = test / (magnitude * 10);
        if (residual > 5) {
            tick = 10 * magnitude;
        } else if (residual > 2) {
            tick = 5 * magnitude;
        } else if (residual > 1) {
            tick = 2 * magnitude;
        } else {
            tick = magnitude;
        }

        double curAmt = 0;

        int ticks = (int) Math.ceil(test / tick);

        for (int ix = 0; ix < ticks; ix++) {
            curAmt += tick;
            BigDecimal bigDecimal = new BigDecimal(curAmt);
            bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP);
            System.out.println(bigDecimal.stripTrailingZeros().toPlainString() + prefix[scale] + "s");
        }

        System.out.println("Value = " + test + prefix[scale] + "s");
        System.out.println("Tick = " + tick + prefix[scale] + "s");
        System.out.println("Ticks = " + ticks);
        System.out.println("Scale = " +  multiple + " : " + scale);


    }
3 голосов
/ 04 марта 2009

Вы можете округлить до двух значащих цифр. Следующий псевдокод должен работать:

// maxValue is the largest value in your chart
magnitude = floor(log10(maxValue))
base = 10^(magnitude - 1)
chartHeight = ceiling(maxValue / base) * base

Например, если maxValue равно 1357, то величина равна 3, а основание равно 100. Деление на 100, округление и умножение на 100 приводит к округлению до следующего кратного 100, т.е. округлению до две значимые цифры. В этом случае результат, если 1400 (1357 ⇒ 13.57 ⇒ 14 ⇒ 1400).

3 голосов
/ 04 марта 2009

Раньше я делал это грубо насильственно. Вот фрагмент кода C ++, который работает хорошо ... но для жестко закодированных нижнего и верхнего пределов (0 и 5000):

int PickYUnits()
{
    int MinSize[8] = {20, 20, 20, 20, 20, 20, 20, 20};
    int ItemsPerUnit[8] = {5, 10, 20, 25, 50, 100, 250, 500};
    int ItemLimits[8] = {20, 50, 100, 250, 500, 1000, 2500, 5000};
    int MaxNumUnits = 8;
    double PixelsPerY;
    int PixelsPerAxis;
    int Units;

    //
    // Figure out the max from the dataset
    //  - Min is always 0 for a bar chart
    //
    m_MinY = 0;
    m_MaxY = -9999999;
    m_TotalY = 0;
    for (int j = 0; j < m_DataPoints.GetSize(); j++) {
        if (m_DataPoints[j].m_y > m_MaxY) {
            m_MaxY = m_DataPoints[j].m_y;
        }

        m_TotalY += m_DataPoints[j].m_y;
    }

    //
    // Give some space at the top
    //
    m_MaxY = m_MaxY + 1;


    //
    // Figure out the size of the range
    //
    double yRange = (m_MaxY - m_MinY);

    //
    // Pick the initial size
    //
    Units = MaxNumUnits;
    for (int k = 0; k < MaxNumUnits; k++)
    {
        if (yRange < ItemLimits[k])
        {
            Units = k;
            break;
        }
    }

    //
    // Adjust it upwards based on the space available
    //
    PixelsPerY = m_rcGraph.Height() / yRange;
    PixelsPerAxis = (int)(PixelsPerY * ItemsPerUnit[Units]);

    while (PixelsPerAxis < MinSize[Units]){
        Units += 1;
        PixelsPerAxis = (int)(PixelsPerY * ItemsPerUnit[Units]);
        if (Units == 5)
            break;
    }


    return ItemsPerUnit[Units];
}

Однако кое-что из того, что ты сказал, изменило меня. Чтобы выбрать хорошие номера осей, поможет определение «хорошего числа»:

  • "Хороший" номер - это номер, который имеет 3 или меньше ненулевых цифр (например, 1230000)
  • «Хорошее» число имеет те же или несколько ненулевых цифр, что и нулевое число (например, 1230 не хорошо, 1200 хорошо)
  • Самыми хорошими числами являются числа, кратные 3 нулям (например, "1000", "1 000 000")
  • Вторыми самыми хорошими числами являются числа с кратными 3 нулями плюс 2 нуля (например, «1 500 000», «1 200»)

Не уверен, является ли приведенное выше определение «правильным» или действительно полезным (но с определением в руках тогда становится проще разработать алгоритм).

1 голос
/ 04 марта 2009

Если вы хотите 1400 сверху, как насчет корректировки двух последних параметров на 1400 вместо 1357:

alt text

0 голосов
/ 04 марта 2009

Вы можете использовать div и мод. Например.

Допустим, вы хотите, чтобы ваш график округлялся с шагом 20 (просто для того, чтобы сделать его более произвольным числом, чем обычное значение "10").

Так что я бы предположил, что 1, 11, 18 округлились бы до 20. Но 21, 33, 38 округлились бы до 40.

Чтобы найти правильное значение, сделайте следующее:

Where divisor = your rounding increment.

divisor = 20
multiple = maxValue / divisor;  // Do an integer divide here. 
if (maxValue modulus divisor > 0)
   multiple++;

graphMax = multiple * maxValue;

Итак, теперь давайте добавим реальные цифры:

divisor = 20;
multiple = 33 / 20; (integer divide)
so multiple = 1
if (33 modulus 20 > 0)  (it is.. it equals 13) 
   multiple++;

so multiple = 2;
graphMax = multiple (2) * maxValue (20);
graphMax = 40;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...