This is part 10 of an N part series detailing how I make . my animations Prev Next Last week I managed to show you how to render the x-axis which took a bit more effort than I was expecting so hopefully the y-axis won’t be so challenging especially seeing as it is a simple scalar value. The y-axis is the concentration of in PPM (parts per million) with a range of about 310–400 PPM. CO2 measured As an aside; to comprehend those figures here’s an image of what 400 PPM looks like: First thing I’ve noticed about working out the y-axis is that unlike the x-axis where we know the maximum x value ahead of rendering as it’s simply a time variable we don’t know the maximum y value until it’s been extracted from the data and scaled. Not a problem, we simply draw the y-axis after the values have been plotted. First of all we need a variable to record the maximum CO2 value: float co2Max = 0.0; Pop that just before the loop in . for drawGraph() Just after where the CO2 value is extracted in the loop add: for if(co2 > co2Max) co2Max = co2; After the loop add: for drawYAxis(co2Max, yScale); That’ll get red squigglies underneath it as we haven’t created yet. So let’s do that just after : drawYAxis() drawGraph() void drawYAxis(float co2Max, float yScale){ } I think a tick mark starting at 320PPM showing every 10PPM should suffice so let’s set the start value: float co2Tick = 320.0; Now while is less than we want to draw a line for the tick mark and label it with the value, then increase it by 10: co2Tick co2Max while(co2Tick <= co2Max){float yAxisTickPos = height - MARGIN - (co2Tick - 313.04) * yScale; line(MARGIN, yAxisTickPos, MARGIN - 5.0, yAxisTickPos);text(int(co2Tick), MARGIN - 15, yAxisTickPos+3); co2Tick += 10.0;} Remember as the y axis is flipped for computers we need to subtract values from the . Hmm, is the minimum CO2 level from the data, perhaps should make that obvious by creating a global constant: height 313.04 final float CO2MIN = 313.04; That’s better: float yAxisTickPos = height - MARGIN - (co2Tick - CO2MIN) * yScale; The line is a shorthand way of incrementing a variable by 10. Equivalent to . co2Tick += 10.0 co2Tick = co2Tick + 10.0 Let’s give that a whirl: That’s pretty good, but, it looks like we need to set the initial max CO2 so we get some tick marks to start with. The initial max CO2 will have to be the CO2 value that would be at the top of the graph. Pop this code just before we call : drawYAxis() if((co2Max - CO2MIN) * yScale < graphHeight)co2Max = graphHeight / yScale + CO2MIN; Resulting in: Much better. Now we need to label the axes. The x axis is straight forward: text("Year", MARGIN + graphWidth / 2, height - 20); But the y axis needs to be rotated. Brace yourself as this involves some Don’t worry, we aren’t going to be multiplying matrices or doing dot products (although that would be fun). We are simply going to use these two functions: and . matrix manipulation. translate() rotate() If you imagine a grid overlaying our drawing space with the origin sitting in the top left corner then calling shifts the origin along with the whole grid to your new location while rotates the whole grid about the origin. After calling these matrix manipulation functions all following drawing functions will plot their points on the modified grid. translate(x,y) rotate(radian) This means that when we draw some rotated text we have to rotate the grid back again. Ugh. Thankfully Processing has a simple solution to that; it’s the matrix stack. (Not sure what a is? .) It has two commands; and . Basically remembers the current orientation of the grid so you can manipulate it how you want and then restores it. Because it’s a stack you can call those methods repeatedly as long as a call is paired with a preceding . stack Here we go pushMatrix() popMatrix() pushMatrix() popMatrix() popMatrix() pushMatrix() So just before add: drawYAxis() pushMatrix();translate(0, graphWidth / 2);rotate(-PI / 2.0);text("CO₂ concentration (PPM)", 0, 20);popMatrix(); When working with radians (the best unit of angles of course) I like to work with fractions of as it’s easier to comprehend as all you need to remember is that 180° is , 90° is , 45° is etc. PI PI PI / 2 PI / 4 After titling the main graph we get: And the now rather long and complete code: import java.time.*;import de.looksgood.ani.*;Ani _ani; FloatDict _data = new FloatDict();LocalDate _startDate = LocalDate.of(1958, 3, 29); void setup(){loadData();size(500,500);background(0);stroke(255,255,0);textAlign(CENTER); Ani.init(this);Ani.setDefaultTimeMode(Ani.FRAMES);_ani = new Ani(this, 530, "_change", 6, Ani.EXPO_IN);} float yScale = 20.0; boolean _coda = false;int _codaCount = 0; float _change = 1.0;final int MARGIN = 60;final float CO2MIN = 313.04; void draw(){// The following pauses the animation before exitingif(_change >= 6.0){if(!_coda){_coda = true;_codaCount = frameCount;}if(frameCount - _codaCount > 120){exit();}return;} background(0);strokeWeight(1); line(MARGIN, 0, MARGIN, height);line(0, height - MARGIN, width, height - MARGIN); drawGraph(MARGIN, height - MARGIN, width - MARGIN, height - MARGIN);} void drawGraph(int xPos, int yPos, int graphWidth, int graphHeight){text("Atmospheric CO₂ concentration 1958 - 2017\nR. F. Keeling, S. J. Walker, S. C. Piper and A. F. Bollenbacher\nMauna Loa, Observatory, Hawaii", MARGIN + graphWidth / 2.0, MARGIN);int deltaX = int(frameCount * _change); float xScale = 1.0; int xAxisMaximum = graphWidth; if(deltaX > graphWidth){xAxisMaximum = deltaX;xScale = float(graphWidth) / float(deltaX);} text("Year", MARGIN + graphWidth / 2, height - 20);drawXAxis(xAxisMaximum, xScale); strokeWeight(3); float co2Max = 0.0; for(int dataIndex = 1; dataIndex <= deltaX; dataIndex++){ int daysFromStart = (dataIndex - 1) \* 7; LocalDate frameDate = \_startDate.plusDays(daysFromStart); if(\_data.hasKey(frameDate.toString())) { float co2 = \_data.get(frameDate.toString()); if(co2 > co2Max) co2Max = co2; float x = dataIndex; float y = (co2 - CO2MIN); if(y \* yScale > graphHeight) { yScale = float(graphHeight)/y; } point(xPos + x \* xScale, yPos - (y \* yScale)); } } if((co2Max - CO2MIN) * yScale < graphHeight)co2Max = graphHeight / yScale + CO2MIN; pushMatrix();translate(0, graphWidth / 2);rotate(-PI / 2.0);text("CO₂ concentration (PPM)", 0, 20);popMatrix(); drawYAxis(co2Max, yScale);} void drawYAxis(float co2Max, float yScale){float co2Tick = 320.0;strokeWeight(1); while(co2Tick <= co2Max){float yAxisTickPos = height - MARGIN - (co2Tick - CO2MIN) * yScale; line(MARGIN, yAxisTickPos, MARGIN - 5.0, yAxisTickPos); text(int(co2Tick), MARGIN - 15, yAxisTickPos+3); co2Tick += 10.0; }} void drawXAxis(int xAxisMaximum, float xScale){int yPos = height - MARGIN;float xAxisTick = 279.0 / 7.0;int axisYear = 1959; while(xAxisTick <= xAxisMaximum){int tickLength = 5;int xPos = MARGIN + int(xAxisTick * xScale); if(axisYear % 5 == 0) { text(axisYear, xPos, yPos + 22); tickLength = 10; } line(xPos, yPos, xPos, yPos + tickLength); xAxisTick += 365.25 / 7.0; axisYear++; }} 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); }} Next week I’ll show you how to record videos and generate gifs along with hosting them on the internet.