Kevin Pluck

@kevpluck

Change your scales

This is part 7 of an N part series detailing how I make my animations.

Prev Next

Now we have reached the limits of what we can display in our 500x500 animation so we will need to change the scale to fit the plot for the full 58 years of data.

So once x is greater than 500 we can start reducing the x scale along with the y. Let’s do that:

    float xScale = 1.0;
float yScale = 40.0;

if(frameCount > width)
{
xScale = float(width) / float(frameCount);
}

float x = frameCount * xScale;
float y = (co2 - 313.04) * yScale;

ellipse(x, height - y, 1, 1);

What’s with the float()? Because width and framcCount are integers when you divide them the result is an integer. Unfortunately 500/501 as an integer results in 0, not 0.998 so we have to convert the integer values into floats. You’ll also note that when I am using a constant value, like 40.0 I include the decimal point, this causes the computer to do floating point arithmetic and not integer arithmetic. For example the result of 1/10 would equal 0 while the result of 1.0/10.0 would be 0.1 as expected. Be aware that 1.0/10 would also result in 0!

Oh, well that’s not what we want! The image doesn’t scale and the last few dots are all squashed up against the right side of the image.

Our code is plotting one dot per frame and keeping all the other dots previously drawn visible. This means that when we change the scale we are only changing the scale for the next dot drawn, not for all the dots previously drawn.

So to overcome this we have to redraw all previous dots and the next one each time. This is done with a for() loop of a different format to the one in loadData()

for(int dataIndex = 1; dataIndex <= frameCount; dataIndex++)
{}

This loop defines a variable called dataIndex which starts at 1 and increases by 1 until it reaches the current frameCount. The dataIndex++ notation is a shorthand way of saying dataIndex = dataIndex + 1

Here it is in context:

void draw()
{
float xScale = 1.0;
float yScale = 40.0;

if(frameCount > width)
{
xScale = float(width) / float(frameCount);
}
  for(int dataIndex = 1; dataIndex <= frameCount; dataIndex++)
{
int daysFromStart = (dataIndex - 1) * 7;
LocalDate frameDate = _startDate.plusDays(daysFromStart);

if(_data.hasKey(frameDate.toString()))
{
float co2 = _data.get(frameDate.toString());

float x = dataIndex * xScale;
float y = (co2 - 313.04) * yScale;

ellipse(x, height - y, 1, 1);
}
}
}

So this basically draws all previous points for every frame and then increases that count by one each time. Once the count gets above 500 then we start adjusting the xScale to

And here we have it:

Huh? Oh, I forgot to clear the frame for each draw!

Simply add background(0); to the first line of draw(). Now we get:

Lovely. Now let’s do the same to the y axis.

This’ll be a tad different as the y value doesn’t increase linearly so we’ll keep a running maximum value and scale according to that. As we already multiply our y-axis by 40 already it naturally leads to us setting:

float yScale = 40.0;

Place that line just before the void draw() definition so that yScale is remembered between draw() executions.

Now each time we calculate a new y value we need to check if this exceeds the previous height value, if so then calculate the new yScale:

if(y * yScale > height) 
{
yScale = float(height)/y;
}

Resulting in:

Here’s the entire code:

import java.time.*;
FloatDict _data = new FloatDict();
LocalDate _startDate = LocalDate.of(1958, 3, 29);
void setup()
{
loadData();
size(500,500);
background(0);
stroke(255,255,0);
}
float yScale = 40.0;
void draw()
{
background(0);
float xScale = 1.0;

if(frameCount > width)
{
xScale = float(width) / float(frameCount);
}
for(int dataIndex = 1; dataIndex <= frameCount; dataIndex++)
{
int daysFromStart = (dataIndex - 1) * 7;
LocalDate frameDate = _startDate.plusDays(daysFromStart);

if(_data.hasKey(frameDate.toString()))
{
float co2 = _data.get(frameDate.toString());

float x = dataIndex;
float y = (co2 - 313.04) ;

if(y * yScale > height)
{
yScale = float(height)/y;
}

ellipse(x * xScale, height - (y * yScale), 1, 1);
}
}
}
void loadData()
{
String[] lines = loadStrings("weekly_in_situ_co2_mlo.csv");

for (String line : lines)
{
if( line.startsWith("\"") ) continue;

String[] values = split(line, ',');
String date = values[0];
float co2 = parseFloat(values[1]);
_data.set(date, co2);
}
}

Join me next week when we play with speed and add some text and axis and stuff…

Edit: When I first published this I had a more complicated way of calculating yScale, this is now simpler.

More by Kevin Pluck

Topics of interest

More Related Stories