Разумное оптимизированное масштабирование диаграммы

StackOverflow https://stackoverflow.com/questions/611878

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

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


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

  • «Красивое» число — это число, состоящее из 3 или менее отличных от нуля цифр (например,1230000)
  • «Красивое» число содержит столько же или несколько ненулевых цифр, чем нулевые цифры (например, 1230 — нехорошо, 1200 — хорошо).
  • Самые красивые числа — это числа, кратные трем нулям (например,«1000», «1000000»)
  • Вторые по красоте числа — это числа, кратные 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);
}

Питон:

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)
Это было полезно?

Решение

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

Алгоритм создания «красивых» интервалов сетки на графике

Я сделал это с помощью своего рода метода грубой силы.Во -первых, выясните максимальное количество отметок, которые вы можете вписать в пространство.Разделите общий диапазон значений на количество клещей;это минимумрасстояние между галочками.Теперь вычислите пол базы логарифма 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

Другие советы

Раньше я делал это методом грубой силы.Вот кусок кода 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 — хорошо).
  • Самые красивые числа — это числа, кратные трем нулям (например,«1000», «1000000»)
  • Вторые по красоте числа — это числа, кратные 3 нулям и 2 нулям (например.«1 500 000», «1 200»)

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

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

// 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).

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

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);


    }

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

alt text

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

Допустим, вы хотите, чтобы ваша диаграмма округлялась с шагом 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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top