Domanda

Devo creare un grafico con un valore massimo dell'asse y ottimizzato.

L'attuale metodo che ho per creare grafici utilizza semplicemente il valore massimo di tutti i grafici, quindi lo divide per dieci e lo utilizza come linee della griglia. Non l'ho scritto.

Nota di aggiornamento: questi grafici sono stati modificati. Non appena ho corretto il codice, i miei grafici dinamici hanno iniziato a funzionare, rendendo insensata questa domanda (perché gli esempi non contenevano più errori). Li ho aggiornati con immagini statiche, ma alcune delle risposte fanno riferimento a valori diversi. Tienilo a mente. Vecchia carta Fino a febbraio c'erano tra 12003 e 14003 chiamate in entrata. Informativo, ma brutto.

Vorrei evitare grafici che assomiglino a una scimmia con i numeri dell'asse y .

L'uso dell'API dei grafici di Google aiuta un po ', ma non è ancora quello che voglio. Grafico API di Google I numeri sono puliti, ma la parte superiore del valore y è sempre la stessa del valore massimo sul grafico. Questo grafico scala da 0 a 1357. Devo aver calcolato il valore corretto di 1400, in modo problematico .


Sto lanciando rbobby la defanizione di un 'simpatico 'numero qui perché lo spiega così bene.

  • A " bello " il numero è uno che ha 3 o meno cifre diverse da zero (es. 1230000)
  • A " bello " il numero ha le stesse cifre o meno di zero rispetto a zero (ad es. 1230 non è bello, 1200 è bello)
  • I numeri più belli sono quelli con multipli di 3 zeri (ad es. "1.000", "1.000.000")
  • I secondi numeri più belli sono quelli con multipli di 3 zeri più 2 zeri (ad es. "1.500.000", "1.200")

Soluzione

 Nuovo grafico

Ho trovato il modo di ottenere i risultati desiderati usando una versione modificata dell'idea di Mark Ransom.

Pugno, il codice di Mark Ransom determina la spaziatura ottimale tra le zecche, quando viene dato il numero di zecche. A volte questo numero finisce per essere più del doppio di quello che è il valore più alto sul grafico, a seconda di quante linee della griglia desiderate.

Quello che sto facendo è che sto eseguendo il codice di Mark con 5, 6, 7, 8, 9 e 10 linee della griglia (tick) per trovare quale di questi è il più basso. Con un valore di 23, l'altezza del grafico diventa 25, con una linea della griglia a 5, 10, 15, 20 e 25. Con un valore di 26, l'altezza della carta è 30, con le linee della griglia a 5, 10 , 15, 20, 25 e 30. Ha la stessa spaziatura tra le linee della griglia, ma ce ne sono altre.

Quindi ecco i passaggi per copiare praticamente ciò che Excel fa per rendere i grafici tutti fantasiosi.

  1. Aumenta temporaneamente il valore più alto del grafico di circa il 5% (in modo che ci sia sempre dello spazio tra il punto più alto del grafico e la parte superiore dell'area del grafico. Vogliamo 99,9 arrotondare a 120)
  2. Trova il posizionamento ottimale della linea della griglia per griglia 5, 6, 7, 8, 9 e 10 linee.
  3. Scegli il più basso di quei numeri. Ricorda il numero di linee della griglia necessarie per ottenere quel valore.
  4. Ora hai l'altezza ottimale della carta. Le linee / barre non si scontreranno mai con la parte superiore del grafico e avrai il numero ottimale di tick.

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)
È stato utile?

Soluzione

Questo proviene da una precedente domanda simile:

Algorithm for " nice " intervalli della linea della griglia su un grafico

  

L'ho fatto con una specie di bruto   metodo di forza. In primo luogo, capire il   numero massimo di segni di spunta che è possibile   adattarsi allo spazio. Dividi il totale   intervallo di valori per il numero di   zecche; questo è il minimo   spaziatura del segno di spunta. Ora calcola   il pavimento della base logaritmica da 10 a   ottenere la grandezza del segno di spunta, e   dividere per questo valore. Dovresti finire   con qualcosa nell'intervallo da 1 a   10. Basta scegliere il numero tondo maggiore o uguale al valore e   moltiplicalo per il logaritmo   calcolato in precedenza. Questo è tuo   spaziatura tick finale.

     

Esempio in 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

Altri suggerimenti

In passato l'ho fatto in un modo brutale e forzato. Ecco un pezzo di codice C ++ che funziona bene ... ma per un limite inferiore e superiore hardcoded (0 e 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];
}

Comunque qualcosa in quello che hai detto mi ha modificato. Per selezionare numeri di assi piacevoli, una definizione di "bel numero" aiuterebbe:

  • A " bello " il numero è uno che ha 3 o meno cifre diverse da zero (es. 1230000)
  • A " bello " il numero ha le stesse cifre o meno di zero rispetto a zero (ad es. 1230 non è bello, 1200 è bello)
  • I numeri più belli sono quelli con multipli di 3 zeri (ad es. "1.000", "1.000.000")
  • I secondi numeri più belli sono quelli con multipli di 3 zeri più 2 zeri (ad es. "1.500.000", "1.200")

Non sono sicuro che la definizione di cui sopra sia " giusto " o effettivamente utile (ma con la definizione in mano diventa quindi un compito più semplice escogitare un algoritmo).

Potresti arrotondare per eccesso a due cifre significative. Il seguente pseudocodice dovrebbe funzionare:

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

Ad esempio, se maxValue è 1357, la magnitudine è 3 e la base è 100. La divisione per 100, l'arrotondamento per eccesso e la moltiplicazione per 100 ha il risultato di arrotondare per eccesso al multiplo successivo di 100 , cioè arrotondando per eccesso a due cifre significative. In questo caso, il risultato è 1400 (1357 ? 13,57 ? 14 ? 1400).

Una leggera raffinatezza e testato ... (funziona per frazioni di unità e non solo per numeri interi)

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


    }

Se vuoi 1400 in alto, che ne dici di regolare gli ultimi due parametri su 1400 anziché 1357:

 alt text

Puoi usare div e mod. Ad esempio.

Supponiamo che tu voglia arrotondare il tuo grafico con incrementi di 20 (solo per renderlo un numero più arbitrario rispetto al tuo tipico valore "10").

Quindi suppongo che 1, 11, 18 arrotondino tutti a 20. Ma 21, 33, 38 arrotonderebbero a 40.

Per trovare il valore giusto, procedi come segue:

Where divisor = your rounding increment.

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

graphMax = multiple * maxValue;

Quindi ora plug-in numeri reali:

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;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top