webentwicklung-frage-antwort-db.com.de

Algorithmus für "Nice" Rasterlinienintervalle in einer Grafik

Ich brauche einen ziemlich intelligenten Algorithmus, um "Nice" -Gitterlinien für ein Diagramm (Diagramm) zu erhalten.

Nehmen Sie beispielsweise ein Balkendiagramm mit den Werten 10, 30, 72 und 60 an. Sie wissen:

Minimaler Wert: 10 ... Max. Wert: 72 .... Bereich: 62

Die erste Frage lautet: Womit fangen Sie an? In diesem Fall wäre 0 der intuitive Wert, aber dies hält andere Datensätze nicht aus. Ich vermute also:

Der minimale Rasterwert sollte entweder 0 oder ein "Nice" -Wert sein, der niedriger ist als der minimale Wert der Daten im Bereich. Alternativ kann es angegeben werden.

Der Maximalwert des Gitters sollte ein "Nice" -Wert sein, der über dem Maximalwert im Bereich liegt. Alternativ kann es angegeben werden (z. B. möchten Sie möglicherweise 0 bis 100 angeben, wenn Sie Prozentsätze anzeigen, unabhängig von den tatsächlichen Werten).

Die Anzahl der Gitterlinien (Ticks) im Bereich sollte entweder angegeben werden oder eine Anzahl innerhalb eines bestimmten Bereichs (z. B. 3-8), so dass die Werte "Nizza" (dh runde Zahlen) sind und Sie den Diagrammbereich maximal nutzen. In unserem Beispiel wäre 80 ein vernünftiges Maximum, da dies 90% der Diagrammhöhe (72/80) erfordern würde, während 100 mehr Platz verschwenden würde.

Kennt jemand einen guten Algorithmus dafür? Die Sprache ist irrelevant, da ich sie in das umsetze, was ich brauche.

54
cletus

CPAN bietet eine Implementierung hier (siehe Quelllink)

Siehe auch Tickmark-Algorithmus für eine Diagrammachse

Zu Ihrer Information, mit Ihren Beispieldaten:

  • Ahorn: Min = 8, Max = 74, Etiketten = 10,20, .., 60,70, Ticks = 10,12,14, .. 70,72
  • MATLAB: Min = 10, Max = 80, Labels = 10,20, .., 60,80
9

Ich habe das mit einer Art Brute-Force-Methode gemacht. Ermitteln Sie zunächst die maximale Anzahl von Markierungen, die Sie in das Feld einfügen können. Dividieren Sie den gesamten Wertebereich durch die Anzahl der Ticks. Dies ist das minimum Abstand der Zecke. Berechnen Sie nun den Boden der Logarithmusbasis 10, um die Größe des Ticks zu erhalten, und dividieren Sie diesen Wert. Sie sollten mit etwas im Bereich von 1 bis 10 enden. Wählen Sie einfach die Rundenummer, die größer oder gleich dem Wert ist, und multiplizieren Sie sie mit dem zuvor berechneten Logarithmus. Dies ist Ihr endgültiger Tickabstand.

Beispiel in Python:

import math

def BestTick(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum, 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

Bearbeiten: Sie können die Auswahl der "Nizza" -Intervalle ändern. Ein Kommentator scheint mit der Auswahl unzufrieden zu sein, da die tatsächliche Anzahl der Ticks bis zu 2,5-mal unter dem Maximum liegen kann. Hier ist eine kleine Modifikation, die eine Tabelle für die Nice-Intervalle definiert. Im Beispiel habe ich die Auswahl so erweitert, dass die Anzahl der Ticks nicht weniger als 3/5 des Maximums beträgt.

import bisect

def BestTick2(largest, mostticks):
    minimum = largest / mostticks
    magnitude = 10 ** math.floor(math.log(minimum, 10))
    residual = minimum / magnitude
    # this table must begin with 1 and end with 10
    table = [1, 1.5, 2, 3, 5, 7, 10]
    tick = table[bisect.bisect_right(table, residual)] if residual < 10 else 10
    return tick * magnitude
31
Mark Ransom

Das Problem besteht aus zwei Teilen:

  1. Bestimmen Sie die Größenordnung und
  2. Auf etwas Bequemes.

Sie können den ersten Teil mit Logarithmen behandeln:

range = max - min;  
exponent = int(log(range));       // See comment below.
magnitude = pow(10, exponent);

Wenn Ihr Bereich beispielsweise zwischen 50 und 1200 liegt, ist der Exponent 3 und die Stärke 1000.

Dann beschäftigen Sie sich mit dem zweiten Teil, indem Sie entscheiden, wie viele Unterteilungen Sie in Ihrem Raster wünschen:

value_per_division = magnitude / subdivisions;

Dies ist eine grobe Berechnung, da der Exponent auf eine ganze Zahl gekürzt wurde. Möglicherweise möchten Sie die Exponentenberechnung optimieren, um Randbedingungen besser zu verarbeiten, z. durch Runden anstelle von int(), wenn Sie zu viele Unterteilungen erhalten.

27
Adam Liss

Ich verwende den folgenden Algorithmus. Es ist ähnlich wie bei anderen hier, aber es ist das erste Beispiel in C #.

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

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

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

        // promote the MSD to either 1, 2, or 5
        if (magMsd > 5)
            magMsd = 10;
        else if (magMsd > 2)
            magMsd = 5;
        else if (magMsd > 1)
            magMsd = 2;

        return magMsd*magPow;
    }
}
13
Drew Noakes

Hier ist eine weitere Implementierung in JavaScript:

var ln10 = Math.log(10);
var calcStepSize = function(range, targetSteps)
{
  // calculate an initial guess at step size
  var tempStep = range / targetSteps;

  // get the magnitude of the step size
  var mag = Math.floor(Math.log(tempStep) / ln10);
  var magPow = Math.pow(10, mag);

  // calculate most significant digit of the new step size
  var magMsd = Math.round(tempStep / magPow + 0.5);

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

  return magMsd * magPow;
};
5
Drew Noakes

Von Mark oben genommen, eine etwas vollständigere Util-Klasse in c #. Das berechnet auch einen passenden ersten und letzten Tick.

public  class AxisAssists
{
    public double Tick { get; private set; }

    public AxisAssists(double aTick)
    {
        Tick = aTick;
    }
    public AxisAssists(double range, int mostticks)
    {
        var minimum = range / mostticks;
        var magnitude = Math.Pow(10.0, (Math.Floor(Math.Log(minimum) / Math.Log(10))));
        var residual = minimum / magnitude;
        if (residual > 5)
        {
            Tick = 10 * magnitude;
        }
        else if (residual > 2)
        {
            Tick = 5 * magnitude;
        }
        else if (residual > 1)
        {
            Tick = 2 * magnitude;
        }
        else
        {
            Tick = magnitude;
        }
    }

    public double GetClosestTickBelow(double v)
    {
        return Tick* Math.Floor(v / Tick);
    }
    public double GetClosestTickAbove(double v)
    {
        return Tick * Math.Ceiling(v / Tick);
    }

}
With ability to create an instance ,but if you just want calculate and throw it away:   
double tickX = new AxisAssists(aMaxX - aMinX, 8).Tick;
2
Gregor Schmitz

Ich habe eine Objective-C-Methode geschrieben, um eine Nice-Achsenskala und Nice-Ticks für gegebene min- und max-Werte Ihres Datensatzes zurückzugeben:

- (NSArray*)niceAxis:(double)minValue :(double)maxValue
{
    double min_ = 0, max_ = 0, min = minValue, max = maxValue, power = 0, factor = 0, tickWidth, minAxisValue = 0, maxAxisValue = 0;
    NSArray *factorArray = [NSArray arrayWithObjects:@"0.0f",@"1.2f",@"2.5f",@"5.0f",@"10.0f",nil];
    NSArray *scalarArray = [NSArray arrayWithObjects:@"0.2f",@"0.2f",@"0.5f",@"1.0f",@"2.0f",nil];

    // calculate x-axis Nice scale and ticks
    // 1. min_
    if (min == 0) {
        min_ = 0;
    }
    else if (min > 0) {
        min_ = MAX(0, min-(max-min)/100);
    }
    else {
        min_ = min-(max-min)/100;
    }

    // 2. max_
    if (max == 0) {
        if (min == 0) {
            max_ = 1;
        }
        else {
            max_ = 0;
        }
    }
    else if (max < 0) {
        max_ = MIN(0, max+(max-min)/100);
    }
    else {
        max_ = max+(max-min)/100;
    }

    // 3. power
    power = log(max_ - min_) / log(10);

    // 4. factor
    factor = pow(10, power - floor(power));

    // 5. Nice ticks
    for (NSInteger i = 0; factor > [[factorArray objectAtIndex:i]doubleValue] ; i++) {
        tickWidth = [[scalarArray objectAtIndex:i]doubleValue] * pow(10, floor(power));
    }

    // 6. min-axisValues
    minAxisValue = tickWidth * floor(min_/tickWidth);

    // 7. min-axisValues
    maxAxisValue = tickWidth * floor((max_/tickWidth)+1);

    // 8. create NSArray to return
    NSArray *niceAxisValues = [NSArray arrayWithObjects:[NSNumber numberWithDouble:minAxisValue], [NSNumber numberWithDouble:maxAxisValue],[NSNumber numberWithDouble:tickWidth], nil];

    return niceAxisValues;
}

Sie können die Methode folgendermaßen aufrufen:

NSArray *niceYAxisValues = [self niceAxis:-maxy :maxy];

und erhalten Sie die Achseneinstellung:

double minYAxisValue = [[niceYAxisValues objectAtIndex:0]doubleValue];
double maxYAxisValue = [[niceYAxisValues objectAtIndex:1]doubleValue];
double ticksYAxis = [[niceYAxisValues objectAtIndex:2]doubleValue];

Nur für den Fall, dass Sie die Anzahl der Achsentexte begrenzen möchten, tun Sie dies:

NSInteger maxNumberOfTicks = 9;
NSInteger numberOfTicks = valueXRange / ticksXAxis;
NSInteger newNumberOfTicks = floor(numberOfTicks / (1 + floor(numberOfTicks/(maxNumberOfTicks+0.5))));
double newTicksXAxis = ticksXAxis * (1 + floor(numberOfTicks/(maxNumberOfTicks+0.5)));

Der erste Teil des Codes basiert auf der Berechnung, die ich gefunden habe: here , um die Achsenskala von Nice-Diagramm und die Ticks ähnlich wie in Excel-Diagrammen zu berechnen. Es funktioniert hervorragend für alle Arten von Datensätzen. Hier ist ein Beispiel für eine iPhone-Implementierung:

enter image description here

2
JFS

Ich bin der Autor des " Algorithmus für die optimale Skalierung auf einer Diagrammachse ". Es wurde früher auf trollop.org gehostet, aber ich habe kürzlich Domains/Blogging-Engines verschoben.

Bitte sehen Sie meine Antwort auf eine verwandte Frage .

1
Incongruous

Eine andere Idee ist, dass der Bereich der Achse der Bereich der Werte ist, die Strichmarkierungen jedoch an der entsprechenden Position gesetzt werden. Das heißt, für 7 bis 22 do:

 [- - - | - - - - | - - - - | - -] 
 10 15 20 

Für die Auswahl des Tick-Abstands würde ich eine beliebige Zahl der Form 10 ^ x * i/n vorschlagen, wobei i <n und 0 <n <10 ist. Erstellen Sie diese Liste und sortieren Sie sie, und Sie können die größte Zahl finden kleiner als value_per_division (wie in adam_liss) bei einer binären Suche.

1
FryGuy

In Python: steps = [numpy.round(x) for x in np.linspace(min, max, num=num_of_steps)]

0
MichaelLo

Wenn Sie versuchen, die Skalen in VB.NET-Diagrammen richtig darzustellen, habe ich das Beispiel von Adam Liss verwendet. Stellen Sie jedoch sicher, dass Sie die Min- und Max-Skalenwerte festlegen, die Sie aus einer Variablen vom Typ decimal übergeben (nicht vom Typ "single" oder "double") Andernfalls werden die Markierungswerte auf 8 Dezimalstellen gesetzt. So hatte ich beispielsweise 1 Diagramm, in dem ich den Wert für die minimale Y-Achse auf 0,0001 und die maximale Y-Achse eingestellt habe Wert auf 0,002 .. Wenn ich diese Werte als Einzelobjekte an das Diagrammobjekt übergeben, bekomme ich Häkchenwerte von 0.00048000001697801, 0.000860000036482233 .... Wenn ich diese Werte jedoch als Dezimalzahlen an das Diagrammobjekt weitergebe, bekomme ich Nice tick Markierungswerte von 0.00048, 0.00086 ......

0
Kristian

Hier einige Anregungen aus den hier verfügbaren Antworten. Hier ist meine Implementierung in C. Beachten Sie, dass das Array ndex eine gewisse Erweiterbarkeit enthält.

float findNiceDelta(float maxvalue, int count)
{
    float step = maxvalue/count,
         order = powf(10, floorf(log10(step))),
         delta = (int)(step/order + 0.5);

    static float ndex[] = {1, 1.5, 2, 2.5, 5, 10};
    static int ndexLenght = sizeof(ndex)/sizeof(float);
    for(int i = ndexLenght - 2; i > 0; --i)
        if(delta > ndex[i]) return ndex[i + 1] * order;
    return delta*order;
}
0
Gleno

In R verwenden

tickSize <- function(range,minCount){
    logMaxTick <- log10(range/minCount)
    exponent <- floor(logMaxTick)
    mantissa <- 10^(logMaxTick-exponent)
    af <- c(1,2,5) # allowed factors
    mantissa <- af[findInterval(mantissa,af)]
    return(mantissa*10^exponent)
}

das Bereichsargument ist max-min der Domäne.

0
Museful

Hier ist eine Javascript-Funktion, die ich geschrieben habe, um die Rasterintervalle (max-min)/gridLinesNumber auf schöne Werte zu runden. Es funktioniert mit beliebigen Zahlen, siehe Gist mit detaillierten Kommets, um herauszufinden, wie es funktioniert und wie man es anruft.

var ceilAbs = function(num, to, bias) {
  if (to == undefined) to = [-2, -5, -10]
  if (bias == undefined) bias = 0
  var numAbs = Math.abs(num) - bias
  var exp = Math.floor( Math.log10(numAbs) )

    if (typeof to == 'number') {
        return Math.sign(num) * to * Math.ceil(numAbs/to) + bias
    }

  var mults = to.filter(function(value) {return value > 0})
  to = to.filter(function(value) {return value < 0}).map(Math.abs)
  var m = Math.abs(numAbs) * Math.pow(10, -exp)
  var mRounded = Infinity

  for (var i=0; i<mults.length; i++) {
    var candidate = mults[i] * Math.ceil(m / mults[i])
    if (candidate < mRounded)
      mRounded = candidate
  }
  for (var i=0; i<to.length; i++) {
    if (to[i] >= m && to[i] < mRounded)
      mRounded = to[i]
  }
  return Math.sign(num) * mRounded * Math.pow(10, exp) + bias
}

Durch Aufruf von ceilAbs(number, [0.5]) für verschiedene Nummern werden Zahlen wie folgt gerundet:

301573431.1193228 -> 350000000
14127.786597236991 -> 15000
-63105746.17236853 -> -65000000
-718854.2201183736 -> -750000
-700660.340487957 -> -750000
0.055717507097870114 -> 0.06
0.0008068701205775142 -> 0.00085
-8.66660070605576 -> -9
-400.09256079792976 -> -450
0.0011740548815578223 -> 0.0015
-5.3003294346854085e-8 -> -6e-8
-0.00005815960629843176 -> -0.00006
-742465964.5184875 -> -750000000
-81289225.90985894 -> -85000000
0.000901771713513881 -> 0.00095
-652726598.5496342 -> -700000000
-0.6498901364393532 -> -0.65
0.9978325804695487 -> 1
5409.4078950583935 -> 5500
26906671.095639467 -> 30000000

Schauen Sie sich die Geige an, um mit dem Code zu experimentieren. Code in der Antwort, der Gist und die Geige unterscheiden sich geringfügig. Ich verwende den in der Antwort angegebenen.

0
grabantot