Order of magnitude detection logic - any ideas?

Hi guys,

I’m working on a barChart plugin (that I will be sharing here) and I’m having a brain fart moment, so looking for input.

How would I best go about “order of magnitude” detection for the scale and division of the vertical axis and subdivision?

For instance:

If the highest value in my barChart would be 86.000 then it’s kind of logical to have my y-Axis go to 100.000 and have division lines for 20.000, 40.000, 60.000 and 80.000.

If the highest value in my barChart would be 17 then it’s probably logical to have my y-Axis go to 20 and have division lines for 5, 10 and 15.

If the highest value in my barChart would be 320 then it’s probably logical to have my y-Axis go to 400 and have division lines for 100, 200 and 300.

When you look at the values as a human it’s easy, but how would I code the logic to have Lua do this?

All input is welcome! Thanks!

The title asks for order of magnitude detection, but your post actually asks for something slightly more complex (nearest rounded-up number on the same order of magnitude).

Getting the order of magnitude is simple enough, just get the base10 logarithm of a number, then use math.ceil to round up the result. Then raise 10 to that power.

local values = {1, 2, 5, 10, 11, 50, 95, 100, 101, 500, 1000, 10000, 10001} for i = 1, #values do --add a catch for 0, because math.log10( 0 ) will return inf if values[i] \> 0 then local magnitude = math.ceil(math.log10( values[i] )) print("if value is "..values[i], "highest y-axis value would be "..math.pow(10,magnitude)) end end

You need to add a catch for 0 otherwise you’ll get an infinite number error.

The problem with this is that 100,001 would give the next order of magnitude as 1,000,000 while you want to round up to 110k/150k/something like that. I’m not sure of an easy way to do that without putting some more thought in, but hopefully the magnitude code I’ve shown will give you a starting point.

As for the number of divisions, I presume you already know how to do this part but I’ll put it out there for anyone else who might need it. Once you have a final value that you are happy to use as the max y-axis value just divide that by the number of divisions you want to have to get the values to display for each divider along the axis.

local maxValue = 100000 local numberOfDivisions = 4 local divisionValues = {} for i = 1, numberOfDivisions do divisionValues[i] = (maxValue / numberOfDivisions) \* i end

Hi Alan,

You are right in that my title isn’t that clear - orders of magnitude being about powers of 10 and all that, while I’m looking for something that would better be called “intuitive step size” or something like that :slight_smile:

Thank for the input though!

As it turns out, I’ve been doing some math and looking at how PowerPoint handles this, and I am arriving at something interesting. I’ll keep you posted when I’ve implemented the code, somewhere this weekend. Unfortunately it involves a “heavy” branch to test wether a certain ration lies between the following values - the ratio being the original number divided by the rounding up to the nearest power of 10 of this number.

0

0.1

0.12

0.15

0.2

0.4

0.5

0.8

1

This is a bit crude, but comes closer to what you originally asked for than my previous post:

local values = {1, 2, 5, 10, 11, 48, 50, 51, 78, 100, 101, 284, 312, 500, 860, 1000, 3333, 10000, 10001} for i = 1, #values do --add a catch for 0, because math.log10( 0 ) will return inf if values[i] \> 0 then local magnitude = math.ceil(math.log10( values[i] )) local scaledDown = values[i] / math.pow(10,magnitude-1) local roundedUp = math.ceil( scaledDown ) local maxAxisValue = roundedUp \* math.pow(10,magnitude-1) print("if max value in data is "..values[i], "max y-axis value is ", maxAxisValue) end end

It should round up a number less than 10 to the next nearest 1, less than 100 to nearest 10, less than 1000 to nearest 100.

I’ve only given it a quick test though so you might want to check it out first.

Hi Alan,

Cool - interesting stuff, and moving in the good direction. It’s interesting to see how some numbers feel more intuitive than other though. For example, I would prefer a number like 1257 to be rounded to 1400 (in 7 steps of 200) rather than 1300 (ehm, 13 steps of 100?) while I would like a number like 1327 to be round to 1500 (in 3 steps of 500 or 5 steps of 300), rather than 1400.

And then there is the issue of the height of the barChart - where depending on the height, you would want more or less “steps” in the y-Axis values. Tricky stuff, making a good barChart!

I think I’m going to have to leave that to you  :slight_smile:   

It sounds a lot more complex to pick a max value based on preference. The main problem is, where do you stop? You could round 1257 to 1500, and then you’d be able to have just 3 steps of 500. Or up to 2000 with 2 steps of 1000. 

A compromise might be to allow the user of the plugin to specify the increments they want on the chart, and a min/max y value. If they don’t provide these, only then do you work out the values using some of the methods we’ve discussed. 

For instance I might prefer that the highest data point got as close to the top as possible (e.g. my max data value is 87 and so I want the max y axis value to be 87 with 3 divisions of 29). Might seem weird but you never know what the user will want to display.

Likewise, the max value might be 21 but it’s a graph showing percentages of people who like X, so you may want the max y axis to always be 100.

“Where do you stop?” is indeed a very good question. Still, PowerPoint, with all its flaws, has barCharts that behave really nicely and seem to pick values for axes and step that just seem right, intuitively.

Letting the user pick values is an option of course, but only works well when you can predict the range beforehand somewhat. Good idea to allow both methods: preset max range and steps, or automatic. Sheesh, I though a barChart module would be a quickie - I was wrong!

The title asks for order of magnitude detection, but your post actually asks for something slightly more complex (nearest rounded-up number on the same order of magnitude).

Getting the order of magnitude is simple enough, just get the base10 logarithm of a number, then use math.ceil to round up the result. Then raise 10 to that power.

local values = {1, 2, 5, 10, 11, 50, 95, 100, 101, 500, 1000, 10000, 10001} for i = 1, #values do --add a catch for 0, because math.log10( 0 ) will return inf if values[i] \> 0 then local magnitude = math.ceil(math.log10( values[i] )) print("if value is "..values[i], "highest y-axis value would be "..math.pow(10,magnitude)) end end

You need to add a catch for 0 otherwise you’ll get an infinite number error.

The problem with this is that 100,001 would give the next order of magnitude as 1,000,000 while you want to round up to 110k/150k/something like that. I’m not sure of an easy way to do that without putting some more thought in, but hopefully the magnitude code I’ve shown will give you a starting point.

As for the number of divisions, I presume you already know how to do this part but I’ll put it out there for anyone else who might need it. Once you have a final value that you are happy to use as the max y-axis value just divide that by the number of divisions you want to have to get the values to display for each divider along the axis.

local maxValue = 100000 local numberOfDivisions = 4 local divisionValues = {} for i = 1, numberOfDivisions do divisionValues[i] = (maxValue / numberOfDivisions) \* i end

Hi Alan,

You are right in that my title isn’t that clear - orders of magnitude being about powers of 10 and all that, while I’m looking for something that would better be called “intuitive step size” or something like that :slight_smile:

Thank for the input though!

As it turns out, I’ve been doing some math and looking at how PowerPoint handles this, and I am arriving at something interesting. I’ll keep you posted when I’ve implemented the code, somewhere this weekend. Unfortunately it involves a “heavy” branch to test wether a certain ration lies between the following values - the ratio being the original number divided by the rounding up to the nearest power of 10 of this number.

0

0.1

0.12

0.15

0.2

0.4

0.5

0.8

1

This is a bit crude, but comes closer to what you originally asked for than my previous post:

local values = {1, 2, 5, 10, 11, 48, 50, 51, 78, 100, 101, 284, 312, 500, 860, 1000, 3333, 10000, 10001} for i = 1, #values do --add a catch for 0, because math.log10( 0 ) will return inf if values[i] \> 0 then local magnitude = math.ceil(math.log10( values[i] )) local scaledDown = values[i] / math.pow(10,magnitude-1) local roundedUp = math.ceil( scaledDown ) local maxAxisValue = roundedUp \* math.pow(10,magnitude-1) print("if max value in data is "..values[i], "max y-axis value is ", maxAxisValue) end end

It should round up a number less than 10 to the next nearest 1, less than 100 to nearest 10, less than 1000 to nearest 100.

I’ve only given it a quick test though so you might want to check it out first.

Hi Alan,

Cool - interesting stuff, and moving in the good direction. It’s interesting to see how some numbers feel more intuitive than other though. For example, I would prefer a number like 1257 to be rounded to 1400 (in 7 steps of 200) rather than 1300 (ehm, 13 steps of 100?) while I would like a number like 1327 to be round to 1500 (in 3 steps of 500 or 5 steps of 300), rather than 1400.

And then there is the issue of the height of the barChart - where depending on the height, you would want more or less “steps” in the y-Axis values. Tricky stuff, making a good barChart!

I think I’m going to have to leave that to you  :slight_smile:   

It sounds a lot more complex to pick a max value based on preference. The main problem is, where do you stop? You could round 1257 to 1500, and then you’d be able to have just 3 steps of 500. Or up to 2000 with 2 steps of 1000. 

A compromise might be to allow the user of the plugin to specify the increments they want on the chart, and a min/max y value. If they don’t provide these, only then do you work out the values using some of the methods we’ve discussed. 

For instance I might prefer that the highest data point got as close to the top as possible (e.g. my max data value is 87 and so I want the max y axis value to be 87 with 3 divisions of 29). Might seem weird but you never know what the user will want to display.

Likewise, the max value might be 21 but it’s a graph showing percentages of people who like X, so you may want the max y axis to always be 100.

“Where do you stop?” is indeed a very good question. Still, PowerPoint, with all its flaws, has barCharts that behave really nicely and seem to pick values for axes and step that just seem right, intuitively.

Letting the user pick values is an option of course, but only works well when you can predict the range beforehand somewhat. Good idea to allow both methods: preset max range and steps, or automatic. Sheesh, I though a barChart module would be a quickie - I was wrong!