Scegliere una scala lineare attraente per l'asse Y di un grafico


84

Sto scrivendo un po 'di codice per visualizzare un grafico a barre (o linee) nel nostro software. Va tutto bene. La cosa che mi ha lasciato perplesso è l'etichettatura dell'asse Y.

Il chiamante può dirmi con che precisione vuole che la scala Y sia etichettata, ma mi sembra di essere bloccato su cosa etichettarli in un modo "attraente". Non posso descrivere "attraente", e probabilmente nemmeno tu puoi, ma lo sappiamo quando lo vediamo, giusto?

Quindi, se i punti dati sono:

   15, 234, 140, 65, 90

E l'utente chiede 10 etichette sull'asse Y, un po 'di rifinitura con carta e matita arriva con:

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

Quindi c'è 10 (escluso 0), l'ultimo si estende appena oltre il valore più alto (234 <250), ed è un "bel" incremento di 25 ciascuno. Se avessero chiesto 8 etichette, un incremento di 30 sarebbe stato carino:

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

Nove sarebbe stato complicato. Forse hai usato solo 8 o 10 e chiamarlo abbastanza vicino andrebbe bene. E cosa fare quando alcuni punti sono negativi?

Vedo che Excel affronta questo problema bene.

Qualcuno conosce un algoritmo generico (anche un po 'di forza bruta va bene) per risolvere questo problema? Non devo farlo velocemente, ma dovrebbe essere carino.


1
Ci sono alcune informazioni su come Excel sceglie i valori massimo e minimo per il proprio asse Y qui: support.microsoft.com/kb/214075
Christopher Orr

Risposte:


103

Molto tempo fa ho scritto un modulo grafico che lo copriva bene. Scavando nella massa grigia si ottiene quanto segue:

  • Determina il limite inferiore e superiore dei dati. (Attenzione al caso speciale in cui limite inferiore = limite superiore!
  • Dividi l'intervallo nella quantità richiesta di tick.
  • Arrotondare l'intervallo di tick in quantità adeguate.
  • Regola il limite inferiore e superiore di conseguenza.

Prendiamo il tuo esempio:

15, 234, 140, 65, 90 with 10 ticks
  1. limite inferiore = 15
  2. limite superiore = 234
  3. intervallo = 234-15 = 219
  4. intervallo tick = 21,9. Dovrebbe essere 25,0
  5. nuovo limite inferiore = 25 * round (15/25) = 0
  6. nuovo limite superiore = 25 * round (1 + 235/25) = 250

Quindi l'intervallo = 0,25,50, ..., 225,250

Puoi ottenere il buon intervallo di tick con i seguenti passaggi:

  1. dividere per 10 ^ x in modo che il risultato sia compreso tra 0,1 e 1,0 (incluso 0,1 escluso 1).
  2. traduci di conseguenza:
    • 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. moltiplicare per 10 ^ x.

In questo caso, 21,9 viene diviso per 10 ^ 2 per ottenere 0,219. Questo è <= 0,25 quindi ora abbiamo 0,25. Moltiplicato per 10 ^ 2 questo dà 25.

Diamo un'occhiata allo stesso esempio con 8 tick:

15, 234, 140, 65, 90 with 8 ticks
  1. limite inferiore = 15
  2. limite superiore = 234
  3. intervallo = 234-15 = 219
  4. intervallo tick = 27,375
    1. Dividi per 10 ^ 2 per 0,27375, si traduce in 0,3, che dà (moltiplicato per 10 ^ 2) 30.
  5. nuovo limite inferiore = 30 * round (15/30) = 0
  6. nuovo limite superiore = 30 * round (1 + 235/30) = 240

Che danno il risultato che hai richiesto ;-).

------ Aggiunto da KD ------

Ecco il codice che raggiunge questo algoritmo senza utilizzare tabelle di ricerca, ecc ...:

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;

In generale, il numero di tick include il tick inferiore, quindi i segmenti effettivi dell'asse y sono uno in meno del numero di tick.


1
Era giusto così. Passaggio 3, ho dovuto ridurre X di 1. Per ottenere un intervallo compreso tra 219 e 0,1-> 1, devo dividere per 10 ^ 3 (1000) e non per 10 ^ 2 (100). Altrimenti, esatto.
Clinton Pierce

2
Fai riferimento alla divisione per 10 ^ x e alla moltiplicazione per 10 ^ x. Va notato che x può essere trovato in questo modo: 'double x = Math.Ceiling (Math.Log10 (tickRange));'
Bryan

1
Molto utile. Anche se non ho capito: "nuovo limite inferiore = 30 * round (15/30) = 0" (verrà 30 penso) e come hai ottenuto 235 nel "nuovo limite superiore = 30 * round (1 + 235/30) = 240 '235 non è menzionato da nessuna parte, dovrebbe essere 234.
Mutant

4
Questa è un'ottima risposta. Davvero apprezzato.
Joel Anair

4
@JoelAnair Grazie, hai appena reso un po 'più luminosa una giornata triste.
Toon Krijthe

22

Ecco un esempio PHP che sto usando. Questa funzione restituisce un array di valori dell'asse Y graziosi che comprendono i valori Y minimo e massimo passati. Ovviamente, questa routine potrebbe essere utilizzata anche per i valori dell'asse X.

Ti consente di "suggerire" quante zecche potresti volere, ma la routine restituirà ciò che sembra buono. Ho aggiunto alcuni dati di esempio e mostrato i risultati per questi.

#!/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);
?>

Output dei risultati dai dati di esempio

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

il mio capo sarà felice di questo - voto positivo anche da parte mia n GRAZIE !!
Stephen Hazel

Bella risposta! Converto a Swift 4 stackoverflow.com/a/55151115/2670547
Petr Syrov

@ Scott Guthrie: Questo è ottimo a meno che gli input non siano numeri interi e siano numeri piccoli, ad esempio, se yMin = 0,03 e yMax = 0,11.
Greg

9

Prova questo codice. L'ho usato in alcuni scenari grafici e funziona bene. È anche abbastanza veloce.

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

Sembra che il chiamante non ti dica gli intervalli che desidera.

Quindi sei libero di modificare i punti finali fino a quando non lo ottieni ben divisibile per il conteggio dell'etichetta.

Definiamolo "carino". Definirei carino se le etichette sono disattivate da:

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, ...

Trova il massimo e il minimo delle tue serie di dati. Chiamiamo questi punti:

min_point and max_point.

Ora tutto ciò che devi fare è trovare 3 valori:

- 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"

che corrispondono all'equazione:

(end_label - start_label)/label_offset == label_count

Probabilmente ci sono molte soluzioni, quindi scegline una. Il più delle volte scommetto che puoi impostare

start_label to 0

quindi prova solo un numero intero diverso

end_label

fino a quando l'offset è "carino"


3

Sto ancora combattendo con questo :)

La risposta originale di Gamecat sembra funzionare la maggior parte del tempo, ma prova a collegare, ad esempio, "3 tick" come numero di tick richiesti (per gli stessi valori di dati 15, 234, 140, 65, 90) .... sembra dare un intervallo di tick di 73, che dopo aver diviso per 10 ^ 2 restituisce 0,73, che mappa a 0,75, che dà un intervallo di tick "carino" di 75.

Quindi calcolare il limite superiore: 75 * round (1 + 234/75) = 300

e il limite inferiore: 75 * round (15/75) = 0

Ma chiaramente se inizi da 0 e procedi a passi da 75 fino al limite superiore di 300, finisci con 0,75,150,225,300 .... che è senza dubbio utile, ma sono 4 tick (escluso 0) non il Sono necessari 3 tick.

È solo frustrante che non funzioni il 100% delle volte ... il che potrebbe essere dovuto a un mio errore da qualche parte ovviamente!


Inizialmente pensavo che il problema potesse avere qualcosa a che fare con il metodo suggerito da Bryan per derivare x, ma questo è ovviamente perfettamente accurato.
StillPondering

3

La risposta di Toon Krijthe funziona la maggior parte del tempo. Ma a volte produrrà un numero eccessivo di zecche. Non funzionerà anche con numeri negativi. L'approccio generale al problema va bene, ma c'è un modo migliore per gestirlo. L'algoritmo che vuoi usare dipenderà da ciò che vuoi veramente ottenere. Di seguito vi presento il mio codice che ho usato nella mia libreria di Ploting JS. L'ho provato e funziona sempre (si spera;)). Ecco i passaggi principali:

  • ottieni gli estremi globali xMin e xMax (inlucde tutti i grafici che vuoi stampare nell'algoritmo)
  • calcolare l'intervallo tra xMin e xMax
  • calcola l'ordine di grandezza del tuo intervallo
  • calcolare la dimensione del tick dividendo l'intervallo per il numero di tick meno uno
  • questo è opzionale. Se si desidera stampare sempre zero tick, utilizzare la dimensione del tick per calcolare il numero di tick positivi e negativi. Il numero totale di tick sarà la loro somma + 1 (lo zero tick)
  • questo non è necessario se hai sempre stampato zero tick. Calcola il limite inferiore e superiore ma ricorda di centrare la trama

Iniziamo. Prima i calcoli di base

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

Arrotondo i valori minimo e massimo per essere sicuro al 100% che il mio grafico coprirà tutti i dati. È anche molto importante abbassare il log10 dell'intervallo se è negativo o meno e sottrarre 1 in seguito. Altrimenti il ​​tuo algoritmo non funzionerà per i numeri inferiori a uno.

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

Uso "tick belli" per evitare tick come 7, 13, 17 ecc. Il metodo che utilizzo qui è piuttosto semplice. È anche bello avere zeroTick quando necessario. La trama sembra molto più professionale in questo modo. Troverai tutti i metodi alla fine di questa risposta.

Ora devi calcolare i limiti superiore e inferiore. Questo è molto facile con zero tick, ma richiede un po 'più di sforzo in altri casi. Perché? Perché vogliamo centrare bene la trama all'interno del limite superiore e inferiore. Dai un'occhiata al mio codice. Alcune delle variabili sono definite al di fuori di questo ambito e alcune di esse sono proprietà di un oggetto in cui viene mantenuto l'intero codice presentato.

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

E qui ci sono metodi che ho menzionato prima che puoi scrivere da solo ma puoi anche usare il mio

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

C'è solo un'altra cosa che non è inclusa qui. Questo è il "confine bello da vedere". Questi sono limiti inferiori che sono numeri simili ai numeri in "tick belli". Ad esempio, è meglio avere il limite inferiore che inizia da 5 con dimensione tick 5 piuttosto che avere un grafico che inizia da 6 con la stessa dimensione tick. Ma questo mio licenziamento lo lascio a te.

Spero che sia d'aiuto. Saluti!


2

Questa risposta è stata convertita in 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
    }

}

Questo è ottimo a meno che gli input non siano numeri interi e piccoli numeri, ad esempio, se yMin = 0,03 e yMax = 0,11.
Greg

1

funziona come un incantesimo, se vuoi 10 passaggi + zero

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

Per chiunque abbia bisogno di questo in ES5 Javascript, ha lottato un po ', ma eccolo:

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

Basato sull'eccellente risposta di Toon Krijtje.


1

Questa soluzione si basa su un esempio Java che ho trovato.

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

Grazie per la domanda e la risposta, molto utile. Gamecat, mi chiedo come stai determinando a cosa dovrebbe essere arrotondato l'intervallo di tick.

intervallo tick = 21,9. Dovrebbe essere 25,0

Per fare questo algoritmicamente, si dovrebbe aggiungere la logica all'algoritmo di cui sopra per rendere questa scala piacevolmente per numeri più grandi? Ad esempio con 10 tick, se l'intervallo è 3346, l'intervallo di tick viene valutato a 334,6 e l'arrotondamento al 10 più vicino darebbe 340 quando 350 è probabilmente più bello.

Cosa pensi?


Nell'esempio di @ Gamecat, 334,6 => 0,3346, che dovrebbe andare a 0,4. Quindi l'intervallo di tick sarebbe effettivamente 400, che è un numero piuttosto carino.
Bryan

0

Sulla base dell'algoritmo di @ Gamecat, ho prodotto la seguente classe di supporto

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

Gli algoritmi di cui sopra non prendono in considerazione il caso in cui l'intervallo tra il valore minimo e massimo è troppo piccolo. E se questi valori fossero molto più alti di zero? Quindi, abbiamo la possibilità di avviare l'asse y con un valore maggiore di zero. Inoltre, per evitare che la nostra linea sia interamente sul lato superiore o inferiore del grafico, dobbiamo dargli un po 'di "aria per respirare".

Per coprire quei casi ho scritto (su PHP) il codice sopra:

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;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.