93 Stimmen

Auswählen einer attraktiven linearen Skala für die Y-Achse eines Diagramms

Ich schreibe ein bisschen Code, um ein Balken- (oder Linien-) Diagramm in unserer Software anzuzeigen. Alles läuft gut. Die Sache, bei der ich nicht weiterkomme, ist die Beschriftung der Y-Achse.

Der Anrufer kann mir sagen, wie fein er die Y-Skala beschriftet haben möchte, aber ich scheine nicht genau zu wissen, wie ich sie in einer "attraktiven" Art und Weise beschriften soll. Ich kann "attraktiv" nicht beschreiben, und Sie wahrscheinlich auch nicht, aber wir erkennen es, wenn wir es sehen, oder?

Wenn die Datenpunkte also sind:

   15, 234, 140, 65, 90

Und der Benutzer fragt nach 10 Beschriftungen auf der Y-Achse, was nach ein wenig Fummelei mit Papier und Bleistift herauskommt:

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

Es sind also 10 (0 nicht mitgerechnet), der letzte reicht gerade über den höchsten Wert hinaus (234 < 250), und es ist eine "nette" Steigerung von jeweils 25. Hätten sie um 8 Etiketten gebeten, hätte eine Schrittweite von 30 gut ausgesehen:

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

Neun wären schwierig gewesen. Vielleicht hätte es gereicht, entweder 8 oder 10 zu nehmen und es als nahe genug zu bezeichnen. Und was ist zu tun, wenn einige der Punkte negativ sind?

Ich kann sehen, dass Excel dieses Problem gut löst.

Kennt jemand einen allgemeinen Algorithmus (auch ein Brute-Force-Verfahren ist in Ordnung), um dieses Problem zu lösen? Ich muss es nicht schnell machen, aber es sollte gut aussehen.

111voto

Toon Krijthe Punkte 51819

Vor langer Zeit habe ich ein Diagramm-Modul geschrieben, das dies sehr gut abdeckt. Wenn man in der grauen Masse wühlt, erhält man das Folgende:

  • Bestimmen Sie die untere und obere Grenze der Daten. (Achten Sie auf den Sonderfall, dass untere Grenze = obere Grenze ist!
  • Teilen Sie den Bereich in die gewünschte Anzahl von Ticks.
  • Runden Sie den Tick-Bereich auf schöne Beträge auf.
  • Passen Sie die untere und obere Grenze entsprechend an.

Nehmen wir Ihr Beispiel:

15, 234, 140, 65, 90 with 10 ticks
  1. untere Grenze = 15
  2. Obergrenze = 234
  3. Reichweite = 234-15 = 219
  4. Tickbereich = 21,9. Dies sollte 25,0 sein
  5. neue Untergrenze = 25 * round(15/25) = 0
  6. neue Obergrenze = 25 * round(1+235/25) = 250

Also der Bereich = 0,25,50,...,225,250

Sie können den schönen Tick-Bereich mit den folgenden Schritten erhalten:

  1. durch 10^x so dividieren, dass das Ergebnis zwischen 0,1 und 1,0 liegt (einschließlich 0,1 ohne 1).
  2. entsprechend übersetzen:
    • 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. mit 10^x multiplizieren.

In diesem Fall wird 21,9 durch 10^2 geteilt und ergibt 0,219. Dies ist <= 0,25, also haben wir jetzt 0,25. Multipliziert mit 10^2 ergibt dies 25.

Schauen wir uns das gleiche Beispiel mit 8 Ticks an:

15, 234, 140, 65, 90 with 8 ticks
  1. untere Grenze = 15
  2. Obergrenze = 234
  3. Reichweite = 234-15 = 219
  4. Tick-Bereich = 27,375
    1. Teilt man durch 10^2, erhält man 0,27375, also 0,3, was (multipliziert mit 10^2) 30 ergibt.
  5. neue Untergrenze = 30 * round(15/30) = 0
  6. neue Obergrenze = 30 * rund(1+235/30) = 240

Die das von Ihnen gewünschte Ergebnis liefern ;-).

------ Hinzugefügt von KD ------

Hier ist Code, der diesen Algorithmus ohne Verwendung von Nachschlagetabellen usw. erreicht:

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;

Im Allgemeinen schließt die Anzahl der Ticks den untersten Tick ein, so dass die tatsächlichen Segmente der y-Achse um eins kleiner sind als die Anzahl der Ticks.

23voto

Scott Guthrie Punkte 221

Hier ist ein PHP-Beispiel, das ich verwende. Diese Funktion gibt ein Array mit hübschen Y-Achsenwerten zurück, die die übergebenen minimalen und maximalen Y-Werte umfassen. Natürlich könnte diese Routine auch für X-Achsenwerte verwendet werden.

Sie können "vorschlagen", wie viele Häkchen Sie haben möchten, aber die Routine wird das zurückgeben was gut aussieht. Ich habe einige Beispieldaten hinzugefügt und zeige die Ergebnisse für diese.

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

Ergebnisausgabe aus Beispieldaten

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

9voto

Drew Noakes Punkte 282438

Probieren Sie diesen Code aus. Ich habe ihn in einigen Diagrammszenarien verwendet und er funktioniert gut. Es ist auch ziemlich schnell.

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

6voto

Pyrolistical Punkte 26854

Es klingt, als ob der Anrufer Ihnen nicht die gewünschten Bereiche mitteilt.

Es steht Ihnen also frei, die Endpunkte so lange zu ändern, bis sie durch die Anzahl der Etiketten gut teilbar sind.

Lassen Sie uns "nett" definieren. Ich würde es nett nennen, wenn die Etiketten nicht stimmen:

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

Ermitteln Sie den Höchst- und Mindestwert Ihrer Datenreihe. Nennen wir diese Punkte:

min_point and max_point.

Jetzt brauchen Sie nur noch 3 Werte zu finden:

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

die die Gleichung erfüllen:

(end_label - start_label)/label_offset == label_count

Es gibt wahrscheinlich viele Lösungen, also wählen Sie einfach eine aus. Ich wette, dass Sie in den meisten Fällen Folgendes einstellen können

start_label to 0

Probieren Sie also einfach eine andere ganze Zahl aus.

end_label

bis der Versatz "schön" ist

3voto

StillPondering Punkte 31

Ich kämpfe immer noch damit :)

Die ursprüngliche Gamecat-Antwort scheint die meiste Zeit zu funktionieren, aber versuchen Sie, sagen wir, "3 Ticks" als die Anzahl der Ticks erforderlich (für die gleichen Datenwerte 15, 234, 140, 65, 90) ....it scheint einen Tick-Bereich von 73 zu geben, die nach der Division durch 10^2 ergibt 0,73, die 0,75, die eine "schöne" Tick-Bereich von 75 gibt zugeordnet.

Dann Berechnung der oberen Grenze: 75*rund(1+234/75) = 300

und die untere Grenze: 75 * round(15/75) = 0

Wenn man jedoch bei 0 beginnt und in 75er-Schritten bis zur oberen Grenze von 300 fortschreitet, erhält man die Werte 0,75,150,225,300 ...., was zweifellos nützlich ist, aber es sind 4 Ticks (ohne 0) und nicht die erforderlichen 3 Ticks.

Es ist einfach frustrierend, dass es nicht zu 100 % funktioniert...., was natürlich auch an meinem Fehler liegen kann!

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X