33

I need to make a chart with an optimized y axis maximum value.

The current method I have of making charts simply uses the maximum value of all the graphs, then divides it by ten, and uses that as grid lines. I didn't write it.

Update Note: These graphs have been changed. As soon as I fixed the code, my dynamic graphs started working, making this question nonsensical (because the examples no longer had any errors in them). I've updated these with static images, but some of the answers refrence different values. Keep that in mind. Old Chart There were between 12003 and 14003 inbound calls so far in February. Informative, but ugly.

I'd like to avoid charts that look like a monkey came up with the y-axis numbers.

Using the Google charts API helps a little bit, but it's still not quite what I want. Google API Chart The numbers are clean, but the top of the y value is always the same as the maximum value on the chart. This chart scales from 0 to 1357. I need to have calculated the proper value of 1400, problematically.


I'm throwing in rbobby's defanition of a 'nice' number here because it explains it so well.

  • A "nice" number is one that has 3 or fewer non-zero digits (eg. 1230000)
  • A "nice" number has the same or few non-zero digits than zero digits (eg 1230 is not nice, 1200 is nice)
  • The nicest numbers are ones with multiples of 3 zeros (eg. "1,000", "1,000,000")
  • The second nicest numbers are onces with multples of 3 zeros plus 2 zeros (eg. "1,500,000", "1,200")

Solution

New Chart

I found the way to get the results that I want using a modified version of Mark Ransom's idea.

Fist, Mark Ransom's code determines the optimum spacing between ticks, when given the number of ticks. Sometimes this number ends up being more than twice what the highest value on the chart is, depending on how many grid lines you want.

What I'm doing is I'm running Mark's code with 5, 6, 7, 8, 9, and 10 grid lines (ticks) to find which of those is the lowest. With a value of 23, the height of the chart goes to 25, with a grid line at 5, 10, 15, 20, and 25. With a value of 26, the chart's height is 30, with grid lines at 5, 10, 15, 20, 25, and 30. It has the same spacing between grid lines, but there are more of them.

So here's the steps to just-about copy what Excel does to make charts all fancy.

  1. Temporarily bump up the chart's highest value by about 5% (so that there is always some space between the chart's highest point and the top of the chart area. We want 99.9 to round up to 120)
  2. Find the optimum grid line placement for 5, 6, 7, 8, 9, and 10 grid lines.
  3. Pick out the lowest of those numbers. Remember the number of grid lines it took to get that value.
  4. Now you have the optimum chart height. The lines/bar will never butt up against the top of the chart and you have the optimum number of ticks.

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

6 に答える 6

5

これは、以前の同様の質問からのものです。

グラフ上の「適切な」グリッド線間隔のアルゴリズム

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

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
于 2009-03-04T19:19:58.920 に答える
4

有効数字 2 桁まで切り上げることができます。次の疑似コードが機能するはずです。

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

たとえば、maxValueが 1357 の場合、大きさは 3 で基数は 100 です。100 で割り、切り上げ、100 を掛けると、次の 100 の倍数に切り上げられます。つまり、有効数字 2 桁に切り上げられます。この場合、1400 の場合の結果 (1357 ⇒ 13.57 ⇒ 14 ⇒ 1400)。

于 2009-03-04T19:13:57.530 に答える
3

わずかな改良とテスト...(整数だけでなく、単位の分数でも機能します)

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


    }
于 2011-06-04T09:51:42.633 に答える
3

過去に、私は力ずくのような方法でこれを行いました。これは、うまく機能する C++ コードのチャンクです...ただし、ハードコードされた下限と上限 (0 と 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];
}

しかし、あなたが言ったことの何かが私をひねりました。適切な軸番号を選択するには、「適切な番号」の定義が役立ちます。

  • 「適切な」数値とは、ゼロ以外の数字が 3 つ以下の数値です (例: 1230000)
  • 「適切な」数値には、ゼロ以外の数字がゼロの数字と同じか、または少数しかありません (たとえば、1230 は適切ではなく、1200 は適切です)。
  • 最も適切な数字は、3 つのゼロの倍数 (例: "1,000"、"1,000,000") です。
  • 2 番目に適切な数字は、3 つのゼロと 2 つのゼロの複数の 1 回です (例: "1,500,000"、"1,200")。

上記の定義が「正しい」か、または実際に役立つかどうかはわかりません (しかし、定義が手元にあれば、アルゴリズムを考案するのはより簡単な作業になります)。

于 2009-03-04T19:05:17.287 に答える
1

一番上に 1400 が必要な場合は、最後の 2 つのパラメーターを 1357 ではなく 1400 に調整します。

代替テキスト

于 2009-03-04T18:44:12.157 に答える
0

div と mod を使用できます。例えば。

グラフを 20 単位で切り上げたいとします (通常の "10" 値よりも恣意的な数値にするため)。

したがって、1、11、18 はすべて 20 に丸められると仮定します。しかし、21、33、38 は 40 に丸められます。

適切な値を見つけるには、次の手順を実行します。

Where divisor = your rounding increment.

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

graphMax = multiple * maxValue;

それでは、実数をプラグインしましょう。

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;
于 2009-03-04T18:58:05.027 に答える