I created my own d3-time interval and added it to the switching time intervals example (ported to this jsfiddle).
But I'm unhappy with the result. The gaps between two bars are not shown.
I'd like the bars looking with a gap as all the other intervals. I suspect my implementation of the custom interval is wrong, but I cannot figure out what the problem is.
I used this implementation in another project and there the opposite happened: The gaps are maximal large and the bars very small.
var threeMonthsInterval = d3.timeInterval(
function (date) {
date.setDate(1);
date.setHours(0, 0, 0, 0);
var currentMounth = date.getMonth()
if (currentMounth <= 2) {
// 'Q1';
date.setMonth(0)
} else if (currentMounth > 2 && currentMounth <= 5) {
// 'Q2';
date.setMonth(3)
} else if (currentMounth > 5 && currentMounth <= 8) {
// 'Q3';
date.setMonth(6)
} else {
// 'Q4';
date.setMonth(9)
}
},
function (date, step) {
date.setMonth(date.getMonth() * 3 + step);
},
function (start, end) {
return (end.getMonth() - start.getMonth()) / 3 + (end.getFullYear() - start.getFullYear()) * 3;
},
function (date) {
return date.getMonth() * 3;
}
);
The one that counts the number between two dates
function (start, end) {
return (end.getMonth() - start.getMonth()) / 3 + (end.getFullYear() - start.getFullYear()) * 3;
},
is the one that tells dc.js how many bars to plan for, and therefore how wide they should be.
I haven't tested your code, so there could be other problems, but there are four quarters in a year, so that last number should be 4.
Related
Given the structure:
structure box_dimensions:
int? left
int? right
int? top
int? bottom
point? top_left
point? top_right
point? bottom_left
point? bottom_right
point? top_center
point? bottom_center
point? center_left
point? center_right
point? center
int? width
int? height
rectangle? bounds
where each field can be defined or not.
How would you implement the function check_and_complete(box_dimensions) ?
That function should return an error if there is not enough fields defined to describe a box, or too many.
If input is consistent, it should compute the undefined fields.
You can describe a box by its center, width and height, or top_left and bottom_right corners, etc
The only solution I can think of contains way to many if-elses. I'm sure there's a smart way to do it.
EDIT
If you wonder how I end up with a structure like that, here is why :
I'm toying with the idea of a "layout by constraints" system:
User define a bunch of boxes, and for each box define a set of constraints like "box_a.top_left = box_b.bottom_right", "box_a.width = box_b.width / 2".
The real structure fields are actually expression AST, not values.
So I need to check if a box is "underconstrained" or "overconstrained", and if it's ok, create the missing expression AST from the given ones.
Yes, certainly there will be too many if-elses.
Here's my attempt to keep them reasonably organized:
howManyLefts = 0
if (left is set) { realLeft = left; howManyLefts++; }
if (top_left is set) { realLeft = top_left.left; howManyLefts++; }
if (bottom_left is set) { realLeft = bottom_left.left; howManyLefts++; }
if (center_left is set) { realLeft = center_left.left; howManyLefts++; }
if (bounds is set) { realLeft = bounds.left; howManyLefts++; }
if (howManyLefts > 1) return error;
Now, repeat that code block for center, right and width.
Now you end up with howManyLefts, howManyCenters, howManyRights and howManyWidths, all of them being either zero or one, depending on whether the value was provided or not. You need exactly two values set and two unset, so:
if (howManyLefts + howManyRights + howManyCenters + howManyWidths != 2) return error
if (howManyWidths == 0)
{
// howManyWidths is 0, so we look for the remaining 0 and assume the rest is 1s
if (howManyCenters == 0)
{ realWidth = realRight - realLeft; realCenter = (realRight + realLeft) / 2; }
else if (howManyLefts == 0)
{ realWidth = 2 * (realRight - realCenter); realLeft = realRight - realWidth; }
else
{ realWidth = 2 * (realCenter - realLeft); realRight = realLeft + realWidth; }
}
else
{
// howManyWidths is 1, so we look for the remaining 1 and assume the rest is 0s
if (howManyCenters == 1)
{ realLeft = realCenter - realWidth / 2; realRight = realCenter + realWidth / 2; }
else if (howManyLefts == 1)
{ realRight = realLeft + realWidth; realCenter = (realRight + realLeft) / 2; }
else
{ realLeft = realRight - realWidth; realCenter = (realRight + realLeft) / 2; }
}
Now, repeat everything for the vertical axis (i.e. replacing { left, center, right, width } with { top, center, bottom, height }).
I am trying to slightly extend the problem described in this question:
dc.js and crossfilter reduce average counts per day of week
I would like to chart average counts per hour of the day. I have followed the solution above, counting the values by day in the custom reduce with the only change being to dimension by hour of day. This seems to work well and can be seen in the following fiddle:
http://jsfiddle.net/dolomite/6eeahs6z/73/
The top bar chart shows the average counts by hour, the lower chart the total counts by hour. So hour 22 has a total count of 47 and average count of 4.2727... There are 11 days in the data so this is correct.
However, when I click on the weekday row chart and filter for Sunday I get a total count for hour 22 of 4 and an average of 0.3636... The denominator in calculating the average values is still including all weekdays in the data, irrespective of the weekday I filter by. So while the total count has filtered to just show 4 for Sunday it is being divided by the total number of days in the data, whereas the requirement is just to divide by the number of whichever day/s have been selected in the filter.
I know the solution lies in modifying the custom reduce but I am stuck! Any pointers on where I am going wrong would be gratefully received.
hourAvgGroup = hourDim.group().reduce(
function (p, v) { // add
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) + 1 : 1);
p.avg = average_map(p.map);
return p;
},
function (p, v) { // remove
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) - 1 : 0);
p.avg = average_map(p.map);
return p;
},
function () { // init
return { map: d3.map(), avg: 0 };
}
)
function average_map(m) {
var sum = 0;
m.forEach(function(k, v) {
sum += v;
});
return m.size() ? sum / m.size() : 0;
}
m.size() counts up the number of keys in the map. The problem is that even if a day has 0 records assigned to it, the key is still there, so m.size() counts it in the denominator. The solution is to remove the key when the count gets to 0. There are probably more efficient ways to do this, but the simplest solution is to add one line to your remove function in the custom reducer so that the function looks like this:
function (p, v) { // remove
var day = d3.time.day(v.EventDate).getTime();
p.map.set(day, p.map.has(day) ? p.map.get(day) - 1 : 0);
// If the day has 0 records, remove the key
if(p.map.has(day) && p.map.get(day) == 0) p.map.remove(day);
p.avg = average_map(p.map);
return p;
},
By the way, I would also recommend not including the actual average and average calculation in your group. Calculate it in the dc.js chart valueAccessor instead. The reducer is run once for every record added or removed. The valueAccessor is only run once per filter operation.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I have been given the time in the format: hh:mm:ss
I have to find if for a given time, the second hand lies in the larger or smaller area formed by the hour and minute hands?
I know that the hour hand moves at the rate of 0.5 degrees per minute,
the minute hand moves at the rate of of 6 degrees per minute and the second hand completes 360 degrees per minute.
But I am unable to find out in which area the second hand lies. So how can I do this?
Second hand within angle between hour and minute hands:
10:15:00
04:40:30
Second hand in reflex angle:
12:01:30
The problem intrigued me so I went ahead and wrote a test project in C#. As far as I can tell it works, you will have to test it to make sure though.
This is the code:
string strTime = "10:15:00";
DateTime dt = DateTime.ParseExact(strTime, "HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
int nHourDegrees = (360 / 12) * dt.Hour;
int nMinuteDegrees = (360 / 60) * dt.Minute;
int nSecondDegrees = (360 / 60) * dt.Second;
if (nHourDegrees > nMinuteDegrees)
{
int nArea1 = nHourDegrees - nMinuteDegrees;
int nArea2 = 360 - nArea1;
bool bArea1IsBigger = (nArea1 >= nArea2);
if (nSecondDegrees <= nHourDegrees && nSecondDegrees >= nMinuteDegrees)
{
//Second hand lies in area1
if (bArea1IsBigger)
{
Console.WriteLine("Second hand is in the larger area");
}
else
{
Console.WriteLine("Second hand is in the smaller area");
}
}
else
{
if (bArea1IsBigger)
{
Console.WriteLine("Second hand is in the smaller area");
}
else
{
Console.WriteLine("Second hand is in the larger area");
}
}
}
else if (nMinuteDegrees > nHourDegrees)
{
int nArea1 = nMinuteDegrees - nHourDegrees;
int nArea2 = 360 - nArea1;
bool bArea1IsBigger = (nArea1 >= nArea2);
if (nSecondDegrees <= nMinuteDegrees && nSecondDegrees >= nHourDegrees)
{
//Second hand lies in area1
if (bArea1IsBigger)
{
Console.WriteLine("Second hand is in the larger area");
}
else
{
Console.WriteLine("Second hand is in the smaller area");
}
}
else
{
if (bArea1IsBigger)
{
Console.WriteLine("Second hand is in the smaller area");
}
else
{
Console.WriteLine("Second hand is in the larger area");
}
}
}
else
{
if (nSecondDegrees == nHourDegrees)
{
Console.WriteLine("Second hand is on both of the other hands");
}
else
{
Console.WriteLine("Second hand is in the ONLY area");
}
}
The idea is that we find the areas between the Hour and Minute hands. Then check to see if the second hand is inside this area. We also compare this area to the other one and then we can easily deduce if the second hand is in the smaller or larger of the two.
Note: Some improvements can be made to the code:
It can be significantly shorter - I haven't done this because it was mainly a test/proof of how this can be done
If the hours overrun (i.e. 24 hour clock not 12) an alteration will have to be made. i.e. minus 12
If the times go to 12/60/60 and not back to 0, this will have to be done manually
Constants can be added in to remove the need for magic numbers
Similar to the above but common calculations like 360 / 12 can be moved to constants
It can be broken out into separate methods to improve readability
Can be moved into a helper method i.e. bool IsInLargerArea(string timeString)
Need to add the case for when the areas are the same size, at the moment I just assume Area1 to be bigger if they are equal i.e. >= (greater than or equal to)
Add checks to make sure there are only 3 parts to the straTimes array
And possibly some more things that I have not thought of
I would go with method which look like this. You have to identify, if the smaller area goes through 0 degree or not and then based on that you can say the solution.
int minDegree;
int maxDegree;
bool over360;
if (Math.abs(hourHandDegree - minuteHandDegree) < 180){
minDegree = Math.min(hourHandDegree, minuteHandDegree);
maxDegree = Math.max(hourHandDegree, minuteHandDegree);
over360 = false;
} else {
minDegree = Math.min(hourHandDegree, minuteHandDegree);
maxDegree = Math.max(hourHandDegree, minuteHandDegree);
over360 = true;
}
if (over360){
if ((secondHandDegree < minDegree) && (secondHandDegree > maxDegree)){
return true;
} else {
return false;
}
} else {
if ((secondHandDegree > minDegree) && (secondHandDegree < maxDegree)){
return true;
} else {
return false;
}
}
This solution is concise, easy to understand, and allows 12-hour or 24-hour input.
It is easier to visualize when you use radians.
The following is in R, but hopefully easy enough to read. Note %% is modulo operator.
which_region <- function(s){
# number of seconds elapsed in day (i.e., since midnight)
sec <- as.numeric(as.POSIXct(s, tz = "GMT", format = "%H:%M:%S")) %% (12*60*60)
# angle of each hand, clockwise from vertical, in radians
hour_ang <- 2*pi * (sec / (12*60*60)) # hour makes a circuit every 12*60*60 sec
min_ang <- 2*pi * ((sec / 60^2) %% 1) # min makes a circuit every 60*60 sec
sec_ang <- 2*pi * ((sec / 60) %% 1) # sec makes a circuit every 60 sec
hour_to_min_ang <- (2*pi + min_ang - hour_ang) %% (2*pi)
min_to_hr_ang <- (2*pi + hour_ang - min_ang) %% (2*pi)
if(hour_to_min_ang < min_to_hr_ang){
return(ifelse(sec_ang > hour_ang & sec_ang < min_ang,
"Smaller Area","Larger Area") )
} else if(min_to_hr_ang < hour_to_min_ang){
return(ifelse(sec_ang > min_ang & sec_ang < hour_ang,
"Smaller Area","Larger Area") )
} else return("Equal")
}
which_region("06:00:00") # Equal
which_region("01:10:00") # Larger Area
which_region("01:20:15") # Smaller Area
which_region("05:10:20") # Smaller Area
which_region("12:00:00") # Equal
which_region("21:55:50") # Smaller Area
which_region("10:55:15 PM") # Larger Area
I am working on a visulaization http://bost.ocks.org/mike/nations/:
I have a query as to the interpolation function. Initially the mousemove is called which calls the displayYear, where the interapolate data is called.
function mousemove() {
displayYear(yearScale.invert(d3.mouse(this)[0]));
}
}
// Tweens the entire chart by first tweening the year, and then the data.
// For the interpolated data, the dots and label are redrawn.
function tweenYear() {
var year = d3.interpolateNumber(thisyear, 2009);
return function(t) { displayYear(year(t)); };
}
// Updates the display to show the specified year.
function displayYear(year) {
//alert(year);
thisyear=year;
dot.data(interpolateData(year), key).call(position).sort(order);
label.text(Math.round(year));
}
// Interpolates the dataset for the given (fractional) year.
function interpolateData(year) {
return nations.map(function(d) {
return {
name: d.name,
region: d.region,
checkins: interpolateValues(d.checkins, year),
teamsize: interpolateValues(d.teamsize, year),
Checkintimes: interpolateValues(d.Checkintimes, year)
};
// Finds (and possibly interpolates) the value for the specified year.
function interpolateValues(values, year) {
var i = bisect.left(values, year, 0, values.length - 1),
a = values[i];
if (i > 0) {
var b = values[i - 1],
t = (year - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
}
what does this function do? i tried debugging and found that it compares 2 years and does a calculation and then returns the values. can anyone elaborate this?
The json file contains
[
{
"name":"India",
"region":"South Asia",
"income":[[2000,225],[2001,225],[2002,226],[2003,227],[2004,229],[2005,230],[2006,231],[2007,244],[2008,254],[2009,253]],
"population":[[2000,41542812],[2001,41623424],[2002,41866106],[2003,42186202],[2004,41521432],[2005,41827315],[2006,42127071],[2007,42420476],[2008,42707546],[2009,42707546]],
"lifeExpectancy":[[2000,43.56],[2001,43.86],[2002,44.22],[2003,64.61],[2004,56.05],[2005,56.52],[2006,66.02],[2007,68.54],[2008,67.06],[2009,73.58]]
}
]
What this code is meant for is the case where a year (or more) of data is missing, which doesn't seem to be the case in the example data that you've posted. First, it finds the closest data point to the given year using bisect.left and then computes an extrapolation between the previous data point and the current data point for the requested year.
Note in particular that year - a[0] in your case will always be 0, as all the years are present (this computes the difference between the requested and found year). Hence, the whole term will be 0 and the return value corresponds to a[1].
If the year that's requested is not present, t denotes the factor that describes the change between the previous year b and the current year a. This change factor is used to extrapolate past a to get the value for the requested year.
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.
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.
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
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.
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)
Find the optimum grid line placement
for 5, 6, 7, 8, 9, and 10 grid
lines.
Pick out the lowest of those numbers. Remember the number of grid lines it took to get that value.
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)
This is from a previous similar question:
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
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).
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).
A slight refinement and tested... (works for fractions of units and not just integers)
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);
}
If you want 1400 at the top, how about adjusting the last two parameters to 1400 instead of 1357:
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;