Algorithme pour les intervalles de grille" nice " sur un graphique
j'ai besoin d'un algorithme raisonnablement intelligent pour trouver des lignes de grille" nice " pour un graphique (graphique).
par exemple, supposons un diagramme à barres avec des valeurs de 10, 30, 72 et 60. Vous savez:
valeur minimale: 10 Valeur Max: 72 Portée: 62
la première question est: par quoi commencez-vous? Dans ce cas, 0 serait la valeur intuitive mais cela ne tiendra pas sur les autres ensembles de données donc je devine:
grille valeur min doit être 0 ou un "gentil", valeur inférieure à la valeur min des données dans la gamme. Alternativement, il peut être spécifié.
grille valeur max doit être une valeur" nice " au-dessus de la valeur max dans la gamme. Alternativement, il peut être spécifié (par exemple, vous pourriez vouloir 0 à 100 Si vous montrez des pourcentages, indépendamment des valeurs réelles).
le nombre de lignes de quadrillage (tiques) dans l'intervalle doit être spécifié ou un nombre dans un intervalle donné (par exemple 3-8) ce que les valeurs sont "sympa" (c'est à dire des nombres ronds) et permet d'optimiser l'utilisation de la zone de graphique. Dans notre exemple, 80 correspondrait à un max raisonnable, puisque cela utiliserait 90% de la hauteur de la carte (72/80), tandis que 100 créerait plus d'espace perdu.
Quelqu'un connaît un bon algorithme pour ça? Le langage est hors de propos car je vais le mettre en œuvre dans ce dont j'ai besoin.
14 réponses
CPAN fournit une mise en œuvre ici (voir lien source)
Voir aussi algorithme de Tickmark pour un axe de graphique
FYI, avec vos données d'échantillon:
- Maple: Min=8, Max= 74, Labels=10,20,..,60, 70, Ticks=10,12,14,..70,72
- MATLAB: Min=10, Max= 80, Labels=10,20,,..,60, 80
j'ai fait ça avec une sorte de méthode de la force brute. Tout d'abord, déterminez le nombre maximum de marques de graduation vous pouvez adapter dans l'espace. Divisez l'intervalle total des valeurs par le nombre de tiques; c'est le minimum espacement de la tique. Maintenant, calculez le plancher du logarithme base 10 pour obtenir la grandeur de la tique, et divisez par cette valeur. Vous devriez vous retrouver avec quelque chose dans la gamme de 1 à 10. Il suffit de choisir le nombre rond supérieur ou égal à la valeur et la multiplier par le logarithme calculé plus tôt. C'est votre dernier tick de l'espacement.
exemple en 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
Edit: vous êtes libre de modifier la sélection de "gentil" intervalles. Un commentateur semble insatisfait des sélections fournies, car le nombre réel de tiques peut être jusqu'à 2,5 fois inférieur au maximum. Voici une légère modification qui définit une table pour la belle intervalles. Dans l'exemple, j'ai Elargissement des sélections pour que le nombre de tiques ne soit pas inférieur à 3/5 du maximum.
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
il y a 2 pièces au problème:
- déterminer l'ordre de grandeur impliqué, et
- rond à quelque chose de pratique.
vous pouvez manipuler la première partie en utilisant des logarithmes:
range = max - min;
exponent = int(log(range)); // See comment below.
magnitude = pow(10, exponent);
ainsi, par exemple, si votre gamme est de 50 - 1200, l'exposant est 3 et la magnitude est 1000.
puis traiter la deuxième partie en décidant comment beaucoup de subdivisions que vous voulez dans votre grille:
value_per_division = magnitude / subdivisions;
c'est un calcul approximatif parce que l'exposant a été tronqué à un entier. Vous pouvez vouloir modifier le calcul de l'exposant pour mieux gérer les conditions limites, par exemple en arrondissant au lieu de prendre le int()
si vous finissez avec trop de subdivisions.
j'utilise l'algorithme suivant. C'est similaire à d'autres affichés ici, mais c'est le premier exemple en 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;
}
}
voici une autre implémentation en 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;
};
j'ai écrit une méthode objective-c pour retourner une échelle d'axe de nice et de bonnes tiques pour des valeurs min - et max données de votre ensemble de données:
- (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;
}
Vous pouvez appeler la méthode comme ceci:
NSArray *niceYAxisValues = [self niceAxis:-maxy :maxy];
et obtenir la configuration de l'axe:
double minYAxisValue = [[niceYAxisValues objectAtIndex:0]doubleValue];
double maxYAxisValue = [[niceYAxisValues objectAtIndex:1]doubleValue];
double ticksYAxis = [[niceYAxisValues objectAtIndex:2]doubleValue];
Juste au cas où vous souhaitez limiter le nombre d'axes tiques faire ceci:
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)));
la première partie du code est basée sur le calcul que j'ai trouvé ici pour calculer l'échelle de l'axe du graphique de nice et des tiques similaires aux graphiques excel. Il fonctionne excellent pour tous les types d'ensembles de données. Voici un exemple d'application iPhone:
tiré de la marque ci-dessus, une classe D'Util légèrement plus complète en c#. Cela calcule également une première et dernière tique appropriée.
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;
une autre idée est d'avoir la portée de l'axe soit la portée des valeurs, mais de mettre les marques de tique à la position appropriée.. i.e. pour 7 à 22 do:
[- - - | - - - - | - - - - | - - ] 10 15 20
quant à la sélection de l'espacement des tiques, je suggérerais n'importe quel nombre de la forme 10^x * i / n, Où i < n, et 0 < n < 10. Générez cette liste, et triez-les, et vous pouvez trouver le plus grand nombre plus petit que value_per_division (comme dans adam_liss) en utilisant une recherche binaire.
je suis l'auteur de " algorithme pour une mise à L'échelle optimale sur un axe de carte ". Il était hébergé sur trollop.org, mais j'ai récemment déménagé domaines/blogging moteurs.
s'il vous Plaît voir mon réponse à une question relative à la .
en utilisant beaucoup d'inspiration des réponses déjà disponibles ici, voici mon implémentation en C. notez qu'il y a une certaine extensibilité intégrée dans le tableau ndex
.
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;
}
dans R, utiliser
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)
}
où l'argument range est max-min de domaine.
Voici une fonction javascript que j'ai écrite pour arrondir les intervalles de grille (max-min)/gridLinesNumber
à de belles valeurs. Il fonctionne avec tous les numéros, voir le gist avec des commets détaillés pour savoir comment il fonctionne et comment l'appeler.
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
}
appelant ceilAbs(number, [0.5])
pour différents nombres arrondira les nombres comme cela:
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
consultez le violon pour expérimenter avec le code. Code dans la réponse, l'essentiel et le violon sont légèrement différents j'utilise celui donné dans la réponse.
si vous essayez d'obtenir les échelles à droite sur VB.NET graphiques, puis j'ai utilisé L'exemple D'Adam Liss, mais assurez-vous lorsque vous définissez les valeurs d'échelle min et max que vous passez à partir d'une variable de type décimal (pas de type simple ou double) sinon les valeurs de marque de tique finissent par être réglé à comme 8 décimales. Ainsi, par exemple, j'ai eu 1 graphique où j'ai mis la valeur de l'axe min Y à 0.0001 et la valeur de l'axe max Y à 0.002. Si je passe ces valeurs à l'objet graphique comme singles-je obtenir graduation des valeurs de 0.00048000001697801, 0.000860000036482233 .... Alors que si je passe ces valeurs à l'objet graphique en décimales, j'obtiens de belles valeurs de marque de 0.00048, 0.00086 ......
en python:
steps = [numpy.round(x) for x in np.linspace(min, max, num=num_of_steps)]