views:

796

answers:

5
+1  A: 
schnaader
I don't know what the value of the chart will be before hand. I can't manually set all the chart's max values.
Grant
If you're able to insert the data into the Google Charts API then you have access to the max value going in.
Nerdling
The max value in this case is 1357. I know how to round it up to 1400 but a computer might round it up to 1360 or something.
Grant
Alright, I now understand your problem better.
schnaader
A: 

You could use div and mod. For example.

Let's say you want your chart to round up by increments of 20 (just to make it more a more arbitrary number than your typical "10" value).

So I would assume that 1, 11, 18 would all round up to 20. But 21, 33, 38 would round to 40.

To come up with the right value do the following:

Where divisor = your rounding increment.

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

graphMax = multiple * maxValue;

So now let's plugin real numbers:

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;
Macon Pegram
Wouldn't a number like 1479 round up to 1480 in this case? It's better than what I have currently, but I need it to round up to 1500 when the number is that high.
Grant
Then make the divisor 100Here's the math:multiple = 1479 / 100 = 14;if (1479 modulus 100 > 0) (it does... it equals 479) multiple ++; (15)graphMax = multiple (15) * divisor (100) = 1500
Macon Pegram
It the formula works for all cases.. you just have to decide what value to want to round to.. round by 20's? Round by 100's. It doesn't matter.
Macon Pegram
Problem is, I don't know how large the charts are going to be before hand. Some charts are in the 100's, some are in the 10,000's It would be silly to round 5 up to the nearest 1,000.
Grant
In order to answer this... I'd need to understand your own arbitrary algorithm for determining the rounding point? Let's forget code and formal algorithms for a moment... Given the following MAX values, what would you setup the divisor to?: 138, 597, 1311, 7623, 14921, 81431.
Macon Pegram
You could base an algorithm off a number of things... An acceptable range of "ticks" or a given amount of precision (say only the first 2 digits of the number) So how would you, if you were just drawing it yourself decide the tick increment?
Macon Pegram
If I were just to throw out what I think, and try to get the computer to do the same, I'd say, 200, 600, 1500, 8000, 15000, 90000. I'm just going on instinct here. If an algorithm returned 200, 700, 1400... that would be fine in my book.
Grant
There's probably a name for what I'm trying to accomplish, but I don't know what it is.
Grant
Even better, Excel automatically adjusts charts in *just* the way I like. So in simplest terms, I want to copy whatever excel does to calculate scale.
Grant
+3  A: 

In the past I've done this in a brute force-ish sort of way. Here's a chunk of C++ code that works well... but for a hardcoded lower and upper limits (0 and 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];
}

However something in what you've said tweaked me. To pick nice axis numbers a definition of "nice number" would help:

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

Not sure if the above definition is "right" or actually helpful (but with the definition in hand it then becomes a simpler task to devise an algorithm).

I like your definition of a 'nice' number. These rules give me something tangible to shoot for.
Grant
This is similar to the solution that evolved from one of my own questions. It seems that a mostly brute-force method is the way to go: http://stackoverflow.com/questions/302406/algorithm-to-determine-the-usual-cash-payment-amounts-for-a-given-price
e.James
+2  A: 

You could round up to two significant figures. The following pseudocode should work:

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

For example, if maxValue is 1357, then magnitude is 3 and base is 100. Dividing by 100, rounding up, and multiplying by 100 has the result of rounding up to the next multiple of 100, i.e. rounding up to two significant figures. In this case, the result if 1400 (1357 ⇒ 13.57 ⇒ 14 ⇒ 1400).

Adam Rosenfield
1357 is rounding to 1360, and 1401 is rounding to 1408. I'll keep checking to see if I've done something wrong here
Grant
I was doing something wrong. "^" isn't "to the power of" in PHP. This is the closest I've come yet.
Grant
^ is bitwise XOR in many languages (I'm not sure about PHP). Try using pow() instead: http://us.php.net/pow
Adam Rosenfield
Pow(10,x) works. I neglected to mention that I had it working. I meant that your entire answer was the closest I've come yet.
Grant
+2  A: 

This is from a previous similar question:

http://stackoverflow.com/questions/361681/algorithm-for-nice-grid-line-intervals-on-a-graph

I've done this with kind of a brute force method. First, figure out the maximum number of tick marks you can fit into the space. Divide the total range of values by the number of ticks; this is the minimum spacing of the tick. Now calculate the floor of the logarithm base 10 to get the magnitude of the tick, and divide by this value. You should end up with something in the range of 1 to 10. Simply choose the round number greater than or equal to the value and multiply it by the logarithm calculated earlier. This is your final tick spacing.

Example in 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
Mark Ransom
I read that question. I thought it was only dealing with the tickmarks between the maxvalue and minimum value?
Grant
Once you have the spacing between tickmarks, it is trivial to round your endpoints to a multiple of the tickmark.
Mark Ransom
If I don't have to optimized max value to get the tickmarks from, how is using tickmarks going to help me get the optimized max value? Am I misunderstanding what your tickmark thing does? I don't mean to sound unappreciative, but you're being awfully vague.
Grant
Yes, I do that sometimes ;) You don't need the optimized max to get the optimized tick size, use the actual max instead. Once that's calculated, the optimized max is ceil(max/tick)*tick.
Mark Ransom
Alright, I'll try it out.
Grant
Thanks for copying the answer from the other thread - I should have done that myself.
Mark Ransom