69

グラフ(グラフ)の「素敵な」グリッド線を作成するには、適度にスマートなアルゴリズムが必要です。

たとえば、値が10、30、72、60の棒グラフを想定します。

最小値:10最大値:72範囲:62

最初の質問は、何から始めますか?この場合、0は直感的な値になりますが、これは他のデータセットには当てはまらないので、推測します。

グリッドの最小値は、0か、範囲内のデータの最小値よりも小さい「適切な」値である必要があります。または、指定することもできます。

グリッドの最大値は、範囲内の最大値よりも「適切な」値である必要があります。または、指定することもできます(たとえば、実際の値に関係なく、パーセンテージを表示する場合は0から100が必要になる場合があります)。

範囲内のグリッド線(目盛り)の数は、値が「適切」(つまり、丸められた数値)になり、グラフ領域を最大限に活用できるように、指定するか、特定の範囲内の数値(3〜8など)にする必要があります。この例では、チャートの高さの90%(72/80)を使用するため、80が適切な最大値になりますが、100はより多くの無駄なスペースを作成します。

このための良いアルゴリズムを知っている人はいますか?言語は、必要なものに実装するため、関係ありません。

4

16 に答える 16

34

私は一種のブルートフォース方式でこれを行いました。まず、スペースに収めることができる目盛りの最大数を計算します。値の全範囲をティック数で割ります。これはティックの最小間隔です。次に、対数の底10を計算してティックの大きさを取得し、この値で除算します。最終的には1から10の範囲になります。値以上の丸め数を選択し、前に計算した対数を掛けるだけです。これが最終的な目盛りの間隔です。

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

編集:「素敵な」間隔の選択を自由に変更できます。実際のティック数は最大の2.5分の1になる可能性があるため、1人のコメント投稿者は提供された選択に不満を持っているようです。これは、適切な間隔のテーブルを定義するわずかな変更です。この例では、ティック数が最大値の3/5を下回らないように、選択範囲を拡張しました。

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
于 2008-12-12T02:02:13.560 に答える
30

問題には2つの部分があります。

  1. 関係する桁数を決定し、
  2. 便利なものに丸めます。

対数を使用して最初の部分を処理できます。

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

したがって、たとえば、範囲が50〜1200の場合、指数は3で、大きさは1000です。

次に、グリッドに必要なサブディビジョンの数を決定して、2番目の部分に対処します。

value_per_division = magnitude / subdivisions;

指数が整数に切り捨てられているため、これは大まかな計算です。指数計算を微調整して、境界条件をより適切に処理することをお勧めします。たとえばint()、細分化が多すぎる場合は、ifを使用する代わりに丸めることができます。

于 2008-12-12T02:09:48.630 に答える
17

次のアルゴリズムを使用します。ここに投稿された他のものと似ていますが、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;
    }
}
于 2009-02-13T13:41:02.147 に答える
11

CPAN はここで実装を提供します(ソース リンクを参照)

グラフ軸の目盛りアルゴリズムも参照してください。

参考までに、サンプルデータを使用して:

  • Maple: 最小 = 8、最大 = 74、ラベル = 10、20、..、60、70、ティック = 10、12、14、..70、72
  • MATLAB: 最小 = 10、最大 = 80、ラベル = 10、20、..、60、80
于 2008-12-12T02:19:55.147 に答える
6

JavaScript での別の実装を次に示します。

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) / Math.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;
};
于 2013-02-25T16:47:32.610 に答える
3

上記の Mark から引用した、C# のもう少し完全な Util クラス。また、適切な最初と最後のティックも計算します。

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

インスタンスを作成する機能がありますが、計算して破棄するだけの場合:

    double tickX = new AxisAssists(aMaxX - aMinX, 8).Tick;
于 2014-10-01T16:36:05.013 に答える
2

データセットの特定の最小値と最大値に対して適切な軸スケールと適切な目盛りを返す目的の c メソッドを作成しました。

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

次のようにメソッドを呼び出すことができます。

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

軸のセットアップを取得します。

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

軸の目盛りの数を制限したい場合に備えて、これを行います:

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

コードの最初の部分は、ここで見つけた計算に基づいており、優れたグラフ軸の目盛と目盛りを Excel グラフと同様に計算します。あらゆる種類のデータセットに最適です。iPhone の実装例を次に示します。

ここに画像の説明を入力

于 2014-04-03T20:33:27.917 に答える
1

別のアイデアは、軸の範囲を値の範囲にすることですが、目盛りを適切な位置に置きます。つまり、7 から 22 の場合は次のようにします。

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

目盛り間隔の選択に関しては、10^x * i / n の形式の任意の数をお勧めします。ここで、i < n、および 0 < n < 10 です。このリストを生成し、それらを並べ替えると、最大の数を見つけることができます。二分探索を使用して value_per_division (adam_liss のように) よりも小さい。

于 2008-12-12T02:22:42.473 に答える
0

(max-min)/gridLinesNumberこれは、グリッド間隔を美しい値に丸めるために私が書いた JavaScript 関数です。任意の番号で動作します。詳細なコメントが記載された要旨を参照して、その動作と呼び出し方を確認してください。

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
}

異なる番号を呼び出すceilAbs(number, [0.5])と、次のように数値が丸められます。

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

コードを試すためにフィドルをチェックしてください。回答のコード、要点とフィドルが少し異なります。回答で指定されたものを使用しています。

于 2016-02-07T13:22:26.743 に答える
0

Rでは、使用

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

ここで、範囲引数はドメインの最大最小です。

于 2013-10-04T12:57:10.290 に答える
0

VB.NET チャートで正しく表示されるスケールを取得しようとしている場合は、Adam Liss の例を使用しましたが、小数型の変数から渡す最小および最大スケール値を設定するときに確認してください。 (single または double のタイプではありません) そうしないと、目盛りの値が小数点以下 8 桁のように設定されてしまいます。例として、Y 軸の最小値を 0.0001 に、Y 軸の最大値を 0.002 に設定した 1 つのグラフを用意しました。これらの値をシングルとしてチャート オブジェクトに渡すと、0.00048000001697801、0.000860000036482233 の目盛り値が得られます。 ..

于 2016-04-08T12:44:37.270 に答える