Вибір привабливого лінійного масштабу для осі Y графіка


84

Я пишу трохи коду для відображення стовпчастого (або рядкового) графіку в нашому програмному забезпеченні. Все йде нормально. Те, що мене запнуло, це маркування осі Y.

Той, хто телефонує, може сказати мені, наскільки тонко вони хочуть позначити шкалу Y, але я, здається, застряг у тому, що саме потрібно позначати їх "привабливим" видом. Я не можу описати "привабливий", і, мабуть, не можете і ви, але ми це знаємо, коли бачимо, так?

Отже, якщо точки даних:

   15, 234, 140, 65, 90

І користувач просить 10 ярликів на осі Y, трохи доробляючи папером та олівцем, виходить:

  0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250

Отже, там 10 (не враховуючи 0), останнє виходить за межі найвищого значення (234 <250), і це "приємний" приріст у 25 кожний. Якби вони попросили 8 етикеток, приріст у 30 виглядав би непогано:

  0, 30, 60, 90, 120, 150, 180, 210, 240

Дев’ять було б хитро. Можливо, просто використали 8 або 10 і назвати це досить близько було б нормально. А що робити, коли деякі пункти негативні?

Я бачу, як Excel прекрасно вирішує цю проблему.

Хтось знає загальний алгоритм (навіть деяка груба сила це нормально) для вирішення цього? Мені не потрібно робити це швидко, але це повинно виглядати красиво.


1
Тут є деяка інформація про те, як Excel вибирає максимальне та мінімальне значення для своєї осі Y тут: support.microsoft.com/kb/214075
Крістофер Орр,

Хороша реалізація: stackoverflow.com/a/16363437/829571
assylias

Відповіді:


103

Давно я написав графічний модуль, який це чудово висвітлив. Викопуючи сіру масу, виходить наступне:

  • Визначте нижню та верхню межу даних. (Остерігайтеся особливого випадку, коли нижня межа = верхня межа!
  • Діліть діапазон на необхідну кількість кліщів.
  • Округліть діапазон кліщів до приємних сум.
  • Відрегулюйте нижню та верхню межі відповідно.

Візьмемо ваш приклад:

15, 234, 140, 65, 90 with 10 ticks
  1. нижня межа = 15
  2. верхня межа = 234
  3. діапазон = 234-15 = 219
  4. діапазон галочок = 21,9. Це має бути 25,0
  5. нова нижня межа = 25 * кругла (15/25) = 0
  6. нова верхня межа = 25 * кругла (1 + 235/25) = 250

Отже, діапазон = 0,25,50, ..., 225,250

Ви можете отримати приємний діапазон галочок, виконавши такі дії:

  1. розділити на 10 ^ x так, щоб результат лежав між 0,1 і 1,0 (включаючи 0,1, за винятком 1).
  2. перекласти відповідно:
    • 0,1 -> 0,1
    • <= 0,2 -> 0,2
    • <= 0,25 -> 0,25
    • <= 0,3 -> 0,3
    • <= 0,4 -> 0,4
    • <= 0,5 -> 0,5
    • <= 0,6 -> 0,6
    • <= 0,7 -> 0,7
    • <= 0,75 -> 0,75
    • <= 0,8 -> 0,8
    • <= 0,9 -> 0,9
    • <= 1,0 -> 1,0
  3. помножте на 10 ^ х.

У цьому випадку 21,9 ділиться на 10 ^ 2, щоб отримати 0,219. Це <= 0,25, тож ми маємо 0,25. Помножене на 10 ^ 2, це дає 25.

Давайте подивимось на той самий приклад із 8 кліщами:

15, 234, 140, 65, 90 with 8 ticks
  1. нижня межа = 15
  2. верхня межа = 234
  3. діапазон = 234-15 = 219
  4. діапазон галочок = 27.375
    1. Ділимо на 10 ^ 2 на 0,27375, перекладаємо на 0,3, що дає (помножено на 10 ^ 2) 30.
  5. нова нижня межа = 30 * кругла (15/30) = 0
  6. нова верхня межа = 30 * раунд (1 + 235/30) = 240

Які дають результат, про який ви просили ;-).

------ Додано KD ------

Ось код, який досягає цього алгоритму без використання таблиць пошуку тощо ...:

double range = ...;
int tickCount = ...;
double unroundedTickSize = range/(tickCount-1);
double x = Math.ceil(Math.log10(unroundedTickSize)-1);
double pow10x = Math.pow(10, x);
double roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
return roundedTickRange;

Взагалі кажучи, кількість кліщів включає нижню галочку, тому фактичні сегменти осі y на один менше, ніж кількість кліщів.


1
Це було майже правильно. Крок 3, мені довелося зменшити X на 1. Щоб отримати діапазон від 219 до .1-> 1, я повинен розділити на 10 ^ 3 (1000), а не на 10 ^ 2 (100). В іншому випадку помітьте.
Клінтон Пірс

2
Ви посилаєтесь на ділення на 10 ^ x і множення на 10 ^ x. Слід зазначити, що x можна знайти таким чином: 'double x = Math.Ceiling (Math.Log10 (tickRange));'
Брайан

1
Дуже корисний. Хоча не зрозумів - 'нова нижня межа = 30 * раунд (15/30) = 0' (прийде 30, я думаю) і як ви отримали 235 в 'нова верхня межа = 30 * раунд (1 + 235/30) = 240 '235 ніде не згадується, це повинно бути 234.
Мутант

4
Це чудова відповідь. Дуже ціную.
Joel Anair

4
@JoelAnair Дякую, що ти щойно зробив сумний день трохи яскравішим.
Toon Krijthe

22

Ось приклад PHP, який я використовую. Ця функція повертає масив гарних значень осі Y, які охоплюють передані мінімальне та максимальне значення Y. Звичайно, цю процедуру можна також використовувати для значень осі X.

Це дозволяє вам "підказати", скільки кліщів ви можете захотіти, але процедура поверне те, що виглядає добре. Я додав деякі зразки даних і показав результати для них.

#!/usr/bin/php -q
<?php

function makeYaxis($yMin, $yMax, $ticks = 10)
{
  // This routine creates the Y axis values for a graph.
  //
  // Calculate Min amd Max graphical labels and graph
  // increments.  The number of ticks defaults to
  // 10 which is the SUGGESTED value.  Any tick value
  // entered is used as a suggested value which is
  // adjusted to be a 'pretty' value.
  //
  // Output will be an array of the Y axis values that
  // encompass the Y values.
  $result = array();
  // If yMin and yMax are identical, then
  // adjust the yMin and yMax values to actually
  // make a graph. Also avoids division by zero errors.
  if($yMin == $yMax)
  {
    $yMin = $yMin - 10;   // some small value
    $yMax = $yMax + 10;   // some small value
  }
  // Determine Range
  $range = $yMax - $yMin;
  // Adjust ticks if needed
  if($ticks < 2)
    $ticks = 2;
  else if($ticks > 2)
    $ticks -= 2;
  // Get raw step value
  $tempStep = $range/$ticks;
  // Calculate pretty step value
  $mag = floor(log10($tempStep));
  $magPow = pow(10,$mag);
  $magMsd = (int)($tempStep/$magPow + 0.5);
  $stepSize = $magMsd*$magPow;

  // build Y label array.
  // Lower and upper bounds calculations
  $lb = $stepSize * floor($yMin/$stepSize);
  $ub = $stepSize * ceil(($yMax/$stepSize));
  // Build array
  $val = $lb;
  while(1)
  {
    $result[] = $val;
    $val += $stepSize;
    if($val > $ub)
      break;
  }
  return $result;
}

// Create some sample data for demonstration purposes
$yMin = 60;
$yMax = 330;
$scale =  makeYaxis($yMin, $yMax);
print_r($scale);

$scale = makeYaxis($yMin, $yMax,5);
print_r($scale);

$yMin = 60847326;
$yMax = 73425330;
$scale =  makeYaxis($yMin, $yMax);
print_r($scale);
?>

Результат результату зразкових даних

# ./test1.php
Array
(
    [0] => 60
    [1] => 90
    [2] => 120
    [3] => 150
    [4] => 180
    [5] => 210
    [6] => 240
    [7] => 270
    [8] => 300
    [9] => 330
)

Array
(
    [0] => 0
    [1] => 90
    [2] => 180
    [3] => 270
    [4] => 360
)

Array
(
    [0] => 60000000
    [1] => 62000000
    [2] => 64000000
    [3] => 66000000
    [4] => 68000000
    [5] => 70000000
    [6] => 72000000
    [7] => 74000000
)

мій бос буде задоволений цим - голосуйте і від мене n ДЯКУЄМО !!
Стівен Хейзел,

Чудова відповідь! Я перетворюю його на Swift 4 stackoverflow.com/a/55151115/2670547
Петро

@Scott Guthrie: Це чудово, якщо вхідні дані не є цілими числами і мають невеликі числа, наприклад, якщо yMin = 0,03 та yMax = 0,11.
Грег

9

Спробуйте цей код. Я використовував його в декількох сценаріях складання графіків, і він працює добре. Це теж досить швидко.

public static class AxisUtil
{
    public static float CalculateStepSize(float range, float targetSteps)
    {
        // calculate an initial guess at step size
        float tempStep = range/targetSteps;

        // get the magnitude of the step size
        float mag = (float)Math.Floor(Math.Log10(tempStep));
        float magPow = (float)Math.Pow(10, mag);

        // calculate most significant digit of the new step size
        float magMsd = (int)(tempStep/magPow + 0.5);

        // promote the MSD to either 1, 2, or 5
        if (magMsd > 5.0)
            magMsd = 10.0f;
        else if (magMsd > 2.0)
            magMsd = 5.0f;
        else if (magMsd > 1.0)
            magMsd = 2.0f;

        return magMsd*magPow;
    }
}

6

Схоже, абонент не повідомляє вам потрібні діапазони.

Таким чином, ви можете вільно змінювати кінцеві точки, доки ви не отримаєте, щоб він мірно ділився на кількість міток.

Давайте визначимо "приємно". Я б назвав приємним, якщо ярлики вимкнено:

1. 2^n, for some integer n. eg. ..., .25, .5, 1, 2, 4, 8, 16, ...
2. 10^n, for some integer n. eg. ..., .01, .1, 1, 10, 100
3. n/5 == 0, for some positive integer n, eg, 5, 10, 15, 20, 25, ...
4. n/2 == 0, for some positive integer n, eg, 2, 4, 6, 8, 10, 12, 14, ...

Знайдіть максимум і мінімум ваших рядів даних. Давайте назвемо ці моменти:

min_point and max_point.

Тепер вам потрібно лише знайти 3 значення:

- start_label, where start_label < min_point and start_label is an integer
- end_label, where end_label > max_point and end_label is an integer
- label_offset, where label_offset is "nice"

що відповідають рівнянню:

(end_label - start_label)/label_offset == label_count

Рішень існує, мабуть, багато, тому просто виберіть одне. Більшу частину часу я впевнений, що ви можете встановити

start_label to 0

так що просто спробуйте різні цілі числа

end_label

поки зсув не стане "приємним"


3

Я все ще борюся з цим :)

Оригінальна відповідь Gamecat, здається, працює більшу частину часу, але спробуйте підключити, скажімо, "3 галочки" як необхідну кількість галочок (для тих самих значень даних 15, 234, 140, 65, 90) .... це здається, дають діапазон тиків 73, який після ділення на 10 ^ 2 дає 0,73, що відповідає 0,75, що дає "приємний" діапазон тиків 75.

Потім обчислюється верхня межа: 75 * раунд (1 + 234/75) = 300

а нижня межа: 75 * кругла (15/75) = 0

Але зрозуміло, якщо ви починаєте з 0 і продовжуєте кроками від 75 до верхньої межі 300, ви отримуєте 0,75,150,225,300 ...., що, без сумніву, корисно, але це 4 галочки (не враховуючи 0), не Потрібно 3 кліща.

Просто розчаровує те, що це не працює у 100% випадків .... що, звичайно, може бути десь моєю помилкою!


Спочатку думка, що проблема може бути пов’язана із запропонованим Брайаном способом виведення х, але це, звичайно, цілком точно.
StillPointing

3

Відповідь Тун Крейте працює більшу частину часу. Але іноді це призводить до надмірної кількості кліщів. Це не спрацює і з від’ємними числами. Загальний підхід до проблеми є нормальним, але є кращий спосіб вирішити цю проблему. Алгоритм, який ви хочете використовувати, буде залежати від того, що ви насправді хочете отримати. Нижче я представляю вам мій код, який я використав у своїй бібліотеці JS Ploting. Я перевірив це, і воно завжди працює (сподіваюся;)). Ось основні кроки:

  • отримати глобальні екстремуми xMin та xMax (включити всі графіки, які ви хочете надрукувати в алгоритмі)
  • обчислити діапазон між xMin та xMax
  • обчисліть порядок величини вашого діапазону
  • обчисліть розмір кліща, діливши діапазон на кількість кліщів мінус одиниця
  • цей необов’язковий. Якщо ви хочете, щоб завжди було надруковано нульове позначення, ви використовуєте розмір галочки для обчислення кількості позитивних і негативних галочок. Загальна кількість кліків буде їх сумою + 1 (нульовий клік)
  • цей не потрібен, якщо у вас постійно надруковано нульове позначення. Обчисліть нижню та верхню межі, але пам’ятайте про те, щоб відцентрувати графік

Давайте розпочнемо. Спочатку основні розрахунки

    var range = Math.abs(xMax - xMin); //both can be negative
    var rangeOrder = Math.floor(Math.log10(range)) - 1; 
    var power10 = Math.pow(10, rangeOrder);
    var maxRound = (xMax > 0) ? Math.ceil(xMax / power10) : Math.floor(xMax / power10);
    var minRound = (xMin < 0) ? Math.floor(xMin / power10) : Math.ceil(xMin / power10);

Я округляю мінімальні та максимальні значення, щоб бути на 100% впевненим, що мій графік охопить усі дані. Також дуже важливо підставити log10 діапазону приводу або ні, він є від’ємним і пізніше відняти 1. В іншому випадку ваш алгоритм не працюватиме для чисел, менших за одиницю.

    var fullRange = Math.abs(maxRound - minRound);
    var tickSize = Math.ceil(fullRange / (this.XTickCount - 1));

    //You can set nice looking ticks if you want
    //You can find exemplary method below 
    tickSize = this.NiceLookingTick(tickSize);

    //Here you can write a method to determine if you need zero tick
    //You can find exemplary method below
    var isZeroNeeded = this.HasZeroTick(maxRound, minRound, tickSize);

Я використовую "приємні на вигляд кліщі", щоб уникати таких кліщів, як 7, 13, 17 тощо. Метод, який я використовую тут, досить простий. Також приємно мати zeroTick, коли це потрібно. Сюжет виглядає набагато професійніше. Усі методи ви знайдете в кінці цієї відповіді.

Тепер вам потрібно обчислити верхню і нижню межі. Це дуже просто з нульовою галочкою, але в іншому випадку потрібно трохи більше зусиль. Чому? Тому що ми хочемо красиво відцентрувати ділянку в межах верхньої та нижньої меж. Погляньте на мій код. Деякі змінні визначені поза цим обсягом, а деякі з них є властивостями об'єкта, в якому зберігається весь представлений код.

    if (isZeroNeeded) {

        var positiveTicksCount = 0;
        var negativeTickCount = 0;

        if (maxRound != 0) {

            positiveTicksCount = Math.ceil(maxRound / tickSize);
            XUpperBound = tickSize * positiveTicksCount * power10;
        }

        if (minRound != 0) {
            negativeTickCount = Math.floor(minRound / tickSize);
            XLowerBound = tickSize * negativeTickCount * power10;
        }

        XTickRange = tickSize * power10;
        this.XTickCount = positiveTicksCount - negativeTickCount + 1;
    }
    else {
        var delta = (tickSize * (this.XTickCount - 1) - fullRange) / 2.0;

        if (delta % 1 == 0) {
            XUpperBound = maxRound + delta;
            XLowerBound = minRound - delta;
        }
        else {
            XUpperBound =  maxRound + Math.ceil(delta);
            XLowerBound =  minRound - Math.floor(delta);
        }

        XTickRange = tickSize * power10;
        XUpperBound = XUpperBound * power10;
        XLowerBound = XLowerBound * power10;
    }

І ось методи, про які я згадав раніше, ви можете писати самостійно, але ви також можете використовувати мої

this.NiceLookingTick = function (tickSize) {

    var NiceArray = [1, 2, 2.5, 3, 4, 5, 10];

    var tickOrder = Math.floor(Math.log10(tickSize));
    var power10 = Math.pow(10, tickOrder);
    tickSize = tickSize / power10;

    var niceTick;
    var minDistance = 10;
    var index = 0;

    for (var i = 0; i < NiceArray.length; i++) {
        var dist = Math.abs(NiceArray[i] - tickSize);
        if (dist < minDistance) {
            minDistance = dist;
            index = i;
        }
    }

    return NiceArray[index] * power10;
}

this.HasZeroTick = function (maxRound, minRound, tickSize) {

    if (maxRound * minRound < 0)
    {
        return true;
    }
    else if (Math.abs(maxRound) < tickSize || Math.round(minRound) < tickSize) {

        return true;
    }
    else {

        return false;
    }
}

Є ще одна річ, яка сюди не включена. Це "приємні на вигляд межі". Це нижчі межі, які є цифрами, подібними до цифр у "гарних на вигляд кліщах". Наприклад, краще мати нижню межу, починаючи з 5 з розміром галочки 5, ніж мати графік, який починається з 6 з однаковим розміром галочки. Але цю мою звільнену я залишаю вам.

Сподіваюся, це допоможе. На здоров’я!


2

Перетворив цю відповідь як Swift 4

extension Int {

    static func makeYaxis(yMin: Int, yMax: Int, ticks: Int = 10) -> [Int] {
        var yMin = yMin
        var yMax = yMax
        var ticks = ticks
        // This routine creates the Y axis values for a graph.
        //
        // Calculate Min amd Max graphical labels and graph
        // increments.  The number of ticks defaults to
        // 10 which is the SUGGESTED value.  Any tick value
        // entered is used as a suggested value which is
        // adjusted to be a 'pretty' value.
        //
        // Output will be an array of the Y axis values that
        // encompass the Y values.
        var result = [Int]()
        // If yMin and yMax are identical, then
        // adjust the yMin and yMax values to actually
        // make a graph. Also avoids division by zero errors.
        if yMin == yMax {
            yMin -= ticks   // some small value
            yMax += ticks   // some small value
        }
        // Determine Range
        let range = yMax - yMin
        // Adjust ticks if needed
        if ticks < 2 { ticks = 2 }
        else if ticks > 2 { ticks -= 2 }

        // Get raw step value
        let tempStep: CGFloat = CGFloat(range) / CGFloat(ticks)
        // Calculate pretty step value
        let mag = floor(log10(tempStep))
        let magPow = pow(10,mag)
        let magMsd = Int(tempStep / magPow + 0.5)
        let stepSize = magMsd * Int(magPow)

        // build Y label array.
        // Lower and upper bounds calculations
        let lb = stepSize * Int(yMin/stepSize)
        let ub = stepSize * Int(ceil(CGFloat(yMax)/CGFloat(stepSize)))
        // Build array
        var val = lb
        while true {
            result.append(val)
            val += stepSize
            if val > ub { break }
        }
        return result
    }

}

Це чудово, якщо вхідні дані не є цілими числами і є малими числами, наприклад, якщо yMin = 0,03 та yMax = 0,11.
Грег

1

це працює як шарм, якщо ви хочете 10 кроків + нуль

//get proper scale for y
$maximoyi_temp= max($institucion); //get max value from data array
 for ($i=10; $i< $maximoyi_temp; $i=($i*10)) {   
    if (($divisor = ($maximoyi_temp / $i)) < 2) break; //get which divisor will give a number between 1-2    
 } 
 $factor_d = $maximoyi_temp / $i;
 $factor_d = ceil($factor_d); //round up number to 2
 $maximoyi = $factor_d * $i; //get new max value for y
 if ( ($maximoyi/ $maximoyi_temp) > 2) $maximoyi = $maximoyi /2; //check if max value is too big, then split by 2

1

Для тих, хто потребує цього в ES5 Javascript, трохи займався боротьбою, але ось він:

var min=52;
var max=173;
var actualHeight=500; // 500 pixels high graph

var tickCount =Math.round(actualHeight/100); 
// we want lines about every 100 pixels.

if(tickCount <3) tickCount =3; 
var range=Math.abs(max-min);
var unroundedTickSize = range/(tickCount-1);
var x = Math.ceil(Math.log10(unroundedTickSize)-1);
var pow10x = Math.pow(10, x);
var roundedTickRange = Math.ceil(unroundedTickSize / pow10x) * pow10x;
var min_rounded=roundedTickRange * Math.floor(min/roundedTickRange);
var max_rounded= roundedTickRange * Math.ceil(max/roundedTickRange);
var nr=tickCount;
var str="";
for(var x=min_rounded;x<=max_rounded;x+=roundedTickRange)
{
    str+=x+", ";
}
console.log("nice Y axis "+str);    

На основі чудової відповіді Тун Крейтье.


1

Це рішення базується на прикладі Java, який я знайшов.

const niceScale = ( minPoint, maxPoint, maxTicks) => {
    const niceNum = ( localRange,  round) => {
        var exponent,fraction,niceFraction;
        exponent = Math.floor(Math.log10(localRange));
        fraction = localRange / Math.pow(10, exponent);
        if (round) {
            if (fraction < 1.5) niceFraction = 1;
            else if (fraction < 3) niceFraction = 2;
            else if (fraction < 7) niceFraction = 5;
            else niceFraction = 10;
        } else {
            if (fraction <= 1) niceFraction = 1;
            else if (fraction <= 2) niceFraction = 2;
            else if (fraction <= 5) niceFraction = 5;
            else niceFraction = 10;
        }
        return niceFraction * Math.pow(10, exponent);
    }
    const result = [];
    const range = niceNum(maxPoint - minPoint, false);
    const stepSize = niceNum(range / (maxTicks - 1), true);
    const lBound = Math.floor(minPoint / stepSize) * stepSize;
    const uBound = Math.ceil(maxPoint / stepSize) * stepSize;
    for(let i=lBound;i<=uBound;i+=stepSize) result.push(i);
    return result;
};
console.log(niceScale(15,234,6));
// > [0, 100, 200, 300]


0

Дякую за питання та відповідь, дуже корисно. Gamecat, мені цікаво, як ви визначаєте, до чого слід округляти діапазон кліщів.

діапазон галочок = 21,9. Це має бути 25,0

Щоб зробити це алгоритмічно, потрібно додати логіку до наведеного вище алгоритму, щоб зробити цей масштаб приємним для більших чисел? Наприклад, якщо діапазон дорівнює 3346, якщо діапазон дорівнює 3346, діапазон позначок дорівнює 334,6, а округлення до найближчих 10 дасть 340, коли 350, мабуть, буде приємнішим.

Що ти думаєш?


У прикладі @ Gamecat 334,6 => 0,3346, що має перейти до 0,4. Тож діапазон галочок насправді буде 400, що є досить приємним числом.
Bryan

0

На основі алгоритму @ Gamecat я створив такий допоміжний клас

public struct Interval
{
    public readonly double Min, Max, TickRange;

    public static Interval Find(double min, double max, int tickCount, double padding = 0.05)
    {
        double range = max - min;
        max += range*padding;
        min -= range*padding;

        var attempts = new List<Interval>();
        for (int i = tickCount; i > tickCount / 2; --i)
            attempts.Add(new Interval(min, max, i));

        return attempts.MinBy(a => a.Max - a.Min);
    }

    private Interval(double min, double max, int tickCount)
    {
        var candidates = (min <= 0 && max >= 0 && tickCount <= 8) ? new[] {2, 2.5, 3, 4, 5, 7.5, 10} : new[] {2, 2.5, 5, 10};

        double unroundedTickSize = (max - min) / (tickCount - 1);
        double x = Math.Ceiling(Math.Log10(unroundedTickSize) - 1);
        double pow10X = Math.Pow(10, x);
        TickRange = RoundUp(unroundedTickSize/pow10X, candidates) * pow10X;
        Min = TickRange * Math.Floor(min / TickRange);
        Max = TickRange * Math.Ceiling(max / TickRange);
    }

    // 1 < scaled <= 10
    private static double RoundUp(double scaled, IEnumerable<double> candidates)
    {
        return candidates.First(candidate => scaled <= candidate);
    }
}

0

Вищезазначені алгоритми не беруть до уваги випадок, коли діапазон між мінімальним та максимальним значенням занадто малий. А що, якщо ці значення набагато вищі за нуль? Тоді ми маємо можливість запустити вісь y зі значенням, більшим за нуль. Крім того, щоб уникнути того, щоб наша лінія знаходилася повністю у верхній або нижній частині графіка, ми повинні дати їй трохи «повітря для дихання».

Для висвітлення тих випадків я написав (на PHP) наведений вище код:

function calculateStartingPoint($min, $ticks, $times, $scale) {

    $starting_point = $min - floor((($ticks - $times) * $scale)/2);

    if ($starting_point < 0) {
        $starting_point = 0;
    } else {
        $starting_point = floor($starting_point / $scale) * $scale;
        $starting_point = ceil($starting_point / $scale) * $scale;
        $starting_point = round($starting_point / $scale) * $scale;
    }
    return $starting_point;
}

function calculateYaxis($min, $max, $ticks = 7)
{
    print "Min = " . $min . "\n";
    print "Max = " . $max . "\n";

    $range = $max - $min;
    $step = floor($range/$ticks);
    print "First step is " . $step . "\n";
    $available_steps = array(5, 10, 20, 25, 30, 40, 50, 100, 150, 200, 300, 400, 500);
    $distance = 1000;
    $scale = 0;

    foreach ($available_steps as $i) {
        if (($i - $step < $distance) && ($i - $step > 0)) {
            $distance = $i - $step;
            $scale = $i;
        }
    }

    print "Final scale step is " . $scale . "\n";

    $times = floor($range/$scale);
    print "range/scale = " . $times . "\n";

    print "floor(times/2) = " . floor($times/2) . "\n";

    $starting_point = calculateStartingPoint($min, $ticks, $times, $scale);

    if ($starting_point + ($ticks * $scale) < $max) {
        $ticks += 1;
    }

    print "starting_point = " . $starting_point . "\n";

    // result calculation
    $result = [];
    for ($x = 0; $x <= $ticks; $x++) {
        $result[] = $starting_point + ($x * $scale);
    }
    return $result;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.