Home > Code, Graphics, Interactive, Tutorial > Remaking a Washington Post graph, part 5

Remaking a Washington Post graph, part 5

This is the fourth part of a tutorial for making a graph like this one on the Washington Post website, using RaphaëlJS.

Click here to go to the first part of the tutorial

Step 9: Adding y-axis and title info

Unlike the x-axis, the y-axis and title info change for each plot. The y-axis changes to reflect changes in scale, and the title changes to reflect the information shown in the graph. We’re going to add two objects to keep references to the y-axis and title info:

var yaxis = {ticks: [], labels: [], title: null};

and

var graphTitle = { title: null, subtitle: null };

For the y-axis, I chose tick and label locations just by looking at the graph and picking some that seemed about right. In a case like this, where we know the data, and the size of the graph is fixed, this is the easiest way. I put all of the tick locations in an object.

var tickLocations = {
	total: {numbers: [0, 1, 2, 3, 4, 5], scale: "trillion"},
	Asia: {numbers: [0, 1, 2, 3], scale: "trillion"},
	Europe: {numbers: [0, 0.25, 0.5, 0.75, 1], scale: "trillion"},
	"Middle East": {numbers: [0, 100, 200, 300], scale: "billion"},
	"North America": {numbers: [0, 100, 200, 300], scale: "billion"},
	Australia: {numbers: [0, 5, 10, 15, 20], scale: "billion"},
	"South America": {numbers: [0, 50, 100, 150, 200, 250], scale: "billion"},
	Other: {numbers: [0, 50, 100, 150, 200], scale: "billion"}
};

This object includes a label of either “billion” or “trillion”. This will be used in the y-axis title and in the graph subtitle.

The function for making and re-making the title is

function makeRemakeTitle(whichPlace) {
	var titleText, subtitleText;
	var heldBy = {
		Asia: "Asian countries",
		Europe: "European countries",
		"Middle East": "Middle Eastern coutries",
		"North America": "other North American countries",
		Australia: "Australia",
		"Other": "other countries"
	}
	titleText = (whichPlace === "total") ? "Total foreign debt" : "U.S. debt held by " + heldBy[whichPlace];
	subtitleText = "(in " + tickLocations[whichPlace]["scale"] + "s)";
	if (graphTitle.title !== null) {
		graphTitle.title.remove();
		graphTitle.subtitle.remove();
	}
	graphTitle.title = R.text(scales.xintercept, 30, titleText).attr({"font-size":26, "text-anchor":"start"});
	graphTitle.subtitle = R.text(scales.xintercept, 50, subtitleText).attr({"font-size":12, "text-anchor":"start"});
}

The includes a variable heldBy with wording that’s modified based on the place. For instance, instead of saying “U.S. debt held by North America” it says “U.S. debt held by other North American countries”. As mentioned, the subtitle gives the scale (billions or trillions), and the function checks to see if the graph title and subtitle already exist, and if they do, it erases them before making the new title and subtitle.

The function for making and remaking the y-axis is

function makeRemakeYaxis(whichGraph) {
	if (yaxis.ticks !== []) {
		for(var i = 0; i < yaxis.ticks.length; i++) {
			yaxis.ticks[i].remove();
			yaxis.labels[i].remove();
		}
		if (yaxis.title !== null) yaxis.title.remove();
	}
	var y0 = scales.yintercept;
	var y1 = scales[whichGraph];
	var x0 = scales.xintercept + graphInnerWidth;
	var ypos, title;
	var numbers = tickLocations[whichGraph]["numbers"];
	var adjust = (tickLocations[whichGraph]["scale"] === "trillion") ? 1 : 1/1000;
	for(var i = 0; i < numbers.length; i++) {
		number = numbers[i]
		ypos = y0 + y1*number*adjust;
		yaxis.ticks.push(R.path(["M",String(x0),String(ypos),"L",String(x0 + 8),String(ypos)].join(" ")));
		yaxis.labels.push(R.text(x0 + 12, ypos, String(number)).attr({"font-size":12, "text-anchor":"start"}));
	}
	ypos = (y0+30)/2;
	title = tickLocations[whichGraph]["scale"].charAt(0).toUpperCase() + tickLocations[whichGraph]["scale"].slice(1);
	yaxis.title = R.text(width - 15, ypos, "$ " + title).attr({"font-size":12});
	yaxis.title.rotate(-90);
}

Notice the line that asigns a value to the adjust variable. This variable checks to see if the scale labels are in billions, and if they are, the true tick locations need to be adjusted since the totals are actually in trillions. Also notice that the function reuses the scale info that was used for calculating the shapes, so it’s handy that we held on to this information.

Both of these functions need to be called when the graph is first made, so we add function calls at the end of the JavaScript:

makeRemakeYaxis('total');
makeRemakeTitle('total');

They also need to be remade each time the graph is clicked on, so we add function calls in the transition function, which gets called when a shape is clicked on. That function should now look like

function transition(place) {
	if (place.node.fireEvent) place.node.fireEvent("onmouseout"); // for IE; otherwise it doesn't know it's left the node (or, really, the node has left it)
	if (place.level === 0) {
		for (attr in areas["level0"]) {
			if (areas["level0"].hasOwnProperty(attr)) {
				areas["level0"][attr].hide();
			}
		}
		for (subattr in areas["level1"][place.continent]) {
			if (areas["level1"][place.continent].hasOwnProperty(subattr)) {
				areas["level1"][place.continent][subattr].show();
			}
		}
		makeRemakeYaxis(place.continent);
		makeRemakeTitle(place.continent);
	} else if (place.level === 1) {
		for (subattr in areas["level1"][place.continent]) {
			if (areas["level1"][place.continent].hasOwnProperty(subattr)) {
				areas["level1"][place.continent][subattr].hide();
			}
		}
		for (attr in areas["level0"]) {
			if (areas["level0"].hasOwnProperty(attr)) {
				areas["level0"][attr].show();
			}
		}
		makeRemakeYaxis("total");
		makeRemakeTitle("total");
	}
}

Notice there are two sets of calls, becaus the title and y-axis info need to be remade when going to a continent graph, and remade when coming back.

You can see how the graph should look after adding the y-axis and title, here.

Step 10: A smoother transition

A nicer transition between graphs would have continent shapes morphing from one to the other. So, if you click on the Asia shape in the first graph, the pinkish-red shape morphs into the entire total shape you get in the second graph. We’ll have to make two changes to get this transition. First, we need the paths that mark the beginning and ending of each morph.

We’ll add an object to hold references to these paths, similar to the areas object holding references to the RaphaëlJS shapes

var areaPaths = {
	level0: {},
	level1: {}
};

Now we need to calculate these paths, but we already have a function that does that for us, the makeSingleArea function inside of the makeAreas function. The only problem is that it returns a RaphaëlJS object instead of the path. We’ll just modify the function so that it returns just the path when we want just the path. Change

function makeSingleArea(data, yscale, name, continent, level) {

to

function makeSingleArea(data, yscale, name, continent, level, justThePath) {

And in makeSingleArea, just after

prevTotal[level] = total;

add

if (justThePath)
	return path; // this will be used in transitions

Of course, we now need to change every call we make to the makeSingleArea function. Add the parameter “false” to each existing call (or just copy the finished code that will appear at the end of this post).

Now we need to add the function calls that will return the paths that we want. Add these lines to the very end of makeAreas

prevTotal[0] = zeroes;
for(var j = continents.length - 1; j >= 0; j--) {
	cont = continents[j];
	
	areaPaths["level0"][cont] = makeSingleArea(debt[cont]["total"], scales["total"], cont, cont, 0, true);
	areaPaths["level1"][cont] = {};
	areaPaths["level2"][cont] = {};
	
	prevTotal[1] = zeroes;
	for(attr in debt[cont]) {
		if (debt[cont].hasOwnProperty(attr) && attr === "total") {
			debtData = debt[cont][attr];
			yscale = scales[cont];
			areaPaths["level1"][cont][attr] = makeSingleArea(debtData, yscale, attr, cont, 1, true);
		}
	}
}

These lines work much like the lines for returning the shapes, but notice we don’t get paths for each shape and vice versa. When we transition from the first graph to a continent graph, we need a path for the continent total to morph into. However, once inside the continent graph, we don’t need the shape for the continent total, but we do need shapes for country totals.

While we’re in this part of the code, we’ll make one more change here. In the lines above what we just added there is the line

areas["level1"][continents[j]][attr].hide();

Actually, this line appears twice. In both cases add this line right before it

areas['level1'][continents[j]][attr].attr({opacity: 0});

With the new transition, we’ll want to “show” these shapes at the beginning of the transition. We have to show them, in order for them to be in the same sequence of animation events, but we don’t want them to actually be visible until later in the animation.

Finally, to make use of these new pieces, swap out the old transition function for this one

function transition(place) {
	if (place.node.fireEvent) place.node.fireEvent("onmouseout"); // for IE; otherwise it doesn't know it's left the node (or, really, the node has left it)
	if (place.level === 0) {
		for (attr in areas["level0"]) {
			if (areas["level0"].hasOwnProperty(attr) && attr !== place.name) {
				areas["level0"][attr].animate({"30%":{opacity: 0, callback: function() { this.hide(); } } }, 1500);
			}
		}
		place.animate({ 
			"30%":{opacity:1}, 
			"70%":{path: areaPaths["level1"][place.continent]["total"], callback: function() { this.toBack();} },
			"100%": {opacity: 0, callback: function() { this.hide(); } }
		}, 1500);
		for (subattr in areas["level1"][place.continent]) {
			if (areas["level1"][place.continent].hasOwnProperty(subattr)) {
				areas["level1"][place.continent][subattr].show();
				areas["level1"][place.continent][subattr].animate({
					"70%":{opacity: 0}, 
					"100%":{opacity: 1, callback: function() { this.toFront(); } }
				}, 1500);
			}
		}
		makeRemakeYaxis(place.continent);
		makeRemakeTitle(place.continent);
	} else if (place.level === 1) {
		for (subattr in areas["level1"][place.continent]) {
			if (areas["level1"][place.continent].hasOwnProperty(subattr)) {
				areas["level1"][place.continent][subattr].animate({
					"30%":{opacity: 0, callback: function() { this.hide(); } }
				}, 1500);
			}
		}
		areas["level0"][place.continent].show();
		areas["level0"][place.continent].animate({ 
			"30%":{opacity:1}, 
			"70%":{path: areaPaths["level0"][place.continent], callback: function() { this.toFront();} }
		}, 1500);
		for (attr in areas["level0"]) {
			if (areas["level0"].hasOwnProperty(attr) && attr !== place.continent) {
				areas["level0"][attr].show();
				areas["level0"][attr].animate({
					"70%":{opacity: 0}, 
					"100%":{opacity: 1, callback: function() { this.show(); } } 
				}, 1500);
			}
		}
		makeRemakeYaxis("total");
		makeRemakeTitle("total");
	}
}

This version is a lot more complicated, but I’m won’t go into all of details. It involves a lot of showing and hiding shapes at the right time and animating in-between (no sense in animating a shape that isn’t visible, and we don’t need to see the shape once it’s done morphing in the continent graph). Some of the animations are for changing one path to another. Some of the animations are for fading shapes in and out of visibility. For details about how animations work, see the RaphaëlJS documentation.

You can see this version of the graph here, and your final html should look something like

<html>
    <head>
        <title>Washington Post graph clone</title>
		<style type="text/css" media="screen">
			#info {
				visibility: hidden;
				text-align: center;
				background: #eeeeee;
				position: absolute;
				padding-top: 5px;
				padding-bottom: 5px;
				padding-left: 10px;
				padding-right: 10px;
			}
		</style>
        <script src="raphael-min.js" type="text/javascript" charset="utf-8"></script>
        <script src="wapo-data.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript" charset="utf-8">
			window.onload = function () {
				var width = 900;
				var height = 800;
				var xintercept = 40;  // left side of graph, or, the x-position of the first month
				var yintercept = 750; // bottom of the graph, or, the y-position of zero debt
				var graphInnerHeight = 700;
				var graphInnerWidth = 800;
				var numMonths = 134;
				var scales = {
					xintercept: xintercept,
					yintercept: yintercept,
					xslope: graphInnerWidth/(numMonths - 1)
				}
				var totalDebt = [];
				var continents = ["Asia","Europe","Middle East","North America","Australia","South America","Other"];
				var areas = {
					level0: {},
					level1: {}
				};
				var areaPaths = {
					level0: {},
					level1: {}
				};
				var colors = {
					"Asia": ["#e99d9d", "#efbaba"],
					"Europe": ["#95a0be", "#b4bcd1"],
					"Middle East": ["#ffe2a4", "#ccb483"],
					"North America": ["#b5b88a", "#cbcdad"],
					"Australia": ["#f0b780", "#f2c595"],
					"South America": ["#987bb7", "#b6a2cc"],
					"Other": ["#cccccc", "#a3a3a3"]
				};
				var xaxis = {ticks: [], labels: []};
				var yaxis = {ticks: [], labels: [], title: null};
				var tickLocations = {
					total: {numbers: [0, 1, 2, 3, 4, 5], scale: "trillion"},
					Asia: {numbers: [0, 1, 2, 3], scale: "trillion"},
					Europe: {numbers: [0, 0.25, 0.5, 0.75, 1], scale: "trillion"},
					"Middle East": {numbers: [0, 100, 200, 300], scale: "billion"},
					"North America": {numbers: [0, 100, 200, 300], scale: "billion"},
					Australia: {numbers: [0, 5, 10, 15, 20], scale: "billion"},
					"South America": {numbers: [0, 50, 100, 150, 200, 250], scale: "billion"},
					Other: {numbers: [0, 50, 100, 150, 200], scale: "billion"}
				};
				var graphTitle = { title: null, subtitle: null };
				var infoPopup = document.getElementById("info");
				R = Raphael("graph",width,height);

				function rescaleData() {
					for (attr in debt) {
						if (debt.hasOwnProperty(attr) 
						  && attr !== "Asia" 
						  && attr !== "Europe" 
						  && attr !== "Australia" 
						  && attr !== "Other") {
							for (subattr in debt[attr]) {
								if (debt[attr].hasOwnProperty(subattr) && subattr !== "total") {
									for(var i = 0; i < numMonths; i++) {
										debt[attr][subattr][i] = debt[attr][subattr][i]/1000;
									}
								}
							}
						}
					}
				}
				
				function makeTotal() {
					var cont;

					for(var i = 0; i < numMonths; i++) {
						totalDebt[i] = 0;
					}
					for(var j = 0; j < continents.length; j++) {
						cont = debt[continents[j]];
						for(var i = 0; i < numMonths; i++) {
							totalDebt[i] += cont["total"][i];
						}
					}
				}
				
				function makeScales() {
					var attrMax;
					var yscale;

					scales["total"] = -graphInnerHeight/Math.max.apply(null,totalDebt);
	
					for (attr in debt) {
						if (debt.hasOwnProperty(attr)) {
							attrMax = Math.max.apply(null,debt[attr]["total"]);
							yscale = (attr === "Australia" || attr === "Other") ? 
								(-graphInnerHeight/attrMax + scales["total"])/2 : -graphInnerHeight/attrMax;
							scales[attr] = yscale;
						}
					}
				}
				
				function makeXaxis() {
					var x0 = scales.xintercept;
					var x1 = scales.xslope;
					var y0 = scales.yintercept;
					
					var path = ["M",String(x0),String(y0),"L",String(x0),String(y0 + 8)].join(" ");
					xaxis.ticks.push(R.path(path));  // first tick, March 2000
					xaxis.labels.push(R.text(x0,y0 + 25, dates[0].replace("-","\n\'")).attr({"font-size":12}));
					
					for(var i = 10; i < numMonths; i+=6) { // every January and July
						if ((i-10)%12 === 0) {              // if it's January, make a label and a longer tick
							xaxis.labels.push(R.text(x0 + x1*i, y0 + 25, dates[i].replace("-","\n\'")).attr({"font-size":12}));
							path = ["M",String(x0 + x1*i),String(y0),"L",String(x0 + x1*i),String(y0 + 8)].join(" ");
							xaxis.ticks.push(R.path(path));
						} else {
							path = ["M",String(x0 + x1*i),String(y0),"L",String(x0 + x1*i),String(y0 + 4)].join(" ");
							xaxis.ticks.push(R.path(path));
						}
					}
				}
				
				function makeRemakeTitle(whichPlace) {
					var titleText, subtitleText;
					var heldBy = {
						Asia: "Asian countries",
						Europe: "European countries",
						"Middle East": "Middle Eastern coutries",
						"North America": "other North American countries",
						Australia: "Australia",
						"Other": "other countries"
					}
					titleText = (whichPlace === "total") ? "Total foreign debt" : "U.S. debt held by " + heldBy[whichPlace];
					subtitleText = "(in " + tickLocations[whichPlace]["scale"] + "s)";
					if (graphTitle.title !== null) {
						graphTitle.title.remove();
						graphTitle.subtitle.remove();
					}
					graphTitle.title = R.text(scales.xintercept, 30, titleText).attr({"font-size":26, "text-anchor":"start"});
					graphTitle.subtitle = R.text(scales.xintercept, 50, subtitleText).attr({"font-size":12, "text-anchor":"start"});
				}
				
				function makeRemakeYaxis(whichGraph) {
					if (yaxis.ticks !== []) {
						for(var i = 0; i < yaxis.ticks.length; i++) {
							yaxis.ticks[i].remove();
							yaxis.labels[i].remove();
						}
						if (yaxis.title !== null) yaxis.title.remove();
					}
					var y0 = scales.yintercept;
					var y1 = scales[whichGraph];
					var x0 = scales.xintercept + graphInnerWidth;
					var ypos, title;
					var numbers = tickLocations[whichGraph]["numbers"];
					var adjust = (tickLocations[whichGraph]["scale"] === "trillion") ? 1 : 1/1000;
					for(var i = 0; i < numbers.length; i++) {
						number = numbers[i]
						ypos = y0 + y1*number*adjust;
						yaxis.ticks.push(R.path(["M",String(x0),String(ypos),"L",String(x0 + 8),String(ypos)].join(" ")));
						yaxis.labels.push(R.text(x0 + 12, ypos, String(number)).attr({"font-size":12, "text-anchor":"start"}));
					}
					ypos = (y0+30)/2;
					title = tickLocations[whichGraph]["scale"].charAt(0).toUpperCase() + tickLocations[whichGraph]["scale"].slice(1);
					yaxis.title = R.text(width - 15, ypos, "$ " + title).attr({"font-size":12});
					yaxis.title.rotate(-90);
				}

				function makeAreas() {
					var placeData, placeScale, placeName, strokeColor;
					var prevTotal = [];
					var zeroes = [];
					for(var i = 0; i < numMonths; i++) {
						zeroes[i] = 0;
					}
					prevTotal[0] = zeroes;
					prevTotal[1] = zeroes;
					function makeSingleArea(data, yscale, name, continent, level, justThePath) {
						var total = [];
						var top = [];
						var bottom = [];
						var topX, topY, bottomX, bottomY, letter, path, newArea;
						
						for(var i = 0; i < numMonths; i++) {
							total[i] = prevTotal[level][i] + data[i];
							topX = scales["xintercept"] + scales["xslope"]*i;
							topY = scales["yintercept"] + yscale*total[i];
							
							bottomX = scales["xintercept"] + scales["xslope"]*(numMonths - 1 - i);
							bottomY = scales["yintercept"] + yscale*prevTotal[level][numMonths - 1 - i];
							
							letter = (i === 0) ? "M" : "L"
							top.push(letter);
							top.push(String(topX));
							top.push(String(topY));
							
							bottom.push("L");
							bottom.push(String(bottomX));
							bottom.push(String(bottomY));
						}
						
						path = top.join(" ") + bottom.join(" ") + " Z";
						
						prevTotal[level] = total;
						
						if (justThePath)
							return path; // this will be used in transitions
						
						strokeColor = (level === 0) ? colors[continent][0] : "#ffffff";
						newArea = R.path(path).attr({stroke: strokeColor, fill: colors[continent][0]});
						newArea.name = name;
						newArea.continent = continent;
						newArea.level = level;
						newArea.mouseover( function(e) { areaMouseover(this,e); });
						newArea.mouseout( function(e) { areaMouseout(this); });
						newArea.mousemove( function(e) { trackCursor(this,e); });
						newArea.click( function(e) { transition(this); });
						
						return newArea;
					}
					
					for(var j = continents.length - 1; j >= 0; j--) {		// make the shapes
						placeData = debt[continents[j]]["total"];
						placeScale = scales["total"];
						placeName = continents[j];
						areas["level0"][continents[j]] = makeSingleArea(placeData, placeScale, placeName, continents[j], 0, false);
						
						areas["level1"][continents[j]] = {};
						prevTotal[1] = zeroes;
						for(attr in debt[continents[j]]) {
							if (debt[continents[j]].hasOwnProperty(attr) && attr !== "total") {
								placeData = debt[continents[j]][attr];
								placeScale = scales[continents[j]];
								placeName = attr;
								areas["level1"][continents[j]][attr] = makeSingleArea(placeData, placeScale, placeName, continents[j], 1, false);
								areas['level1'][continents[j]][attr].attr({opacity: 0});
								areas["level1"][continents[j]][attr].hide();
							} else if (attr === "total" && (continents[j] === "Australia" || continents[j] === "Other")) {
								placeData = debt[continents[j]][attr];
								placeScale = scales[continents[j]];
								placeName = continents[j];
								areas["level1"][continents[j]][attr] = makeSingleArea(placeData, placeScale, placeName, continents[j], 1, false);
								areas['level1'][continents[j]][attr].attr({opacity: 0});
								areas["level1"][continents[j]][attr].hide();
							}
						}
					}
					
					prevTotal[0] = zeroes;
					for(var j = continents.length - 1; j >= 0; j--) {		// make the paths for transitions
						cont = continents[j];
						
						areaPaths["level0"][cont] = makeSingleArea(debt[cont]["total"], scales["total"], cont, cont, 0, true);
						areaPaths["level1"][cont] = {};
						
						prevTotal[1] = zeroes;
						for(attr in debt[cont]) {
							if (debt[cont].hasOwnProperty(attr) && attr === "total") {
								debtData = debt[cont][attr];
								yscale = scales[cont];
								areaPaths["level1"][cont][attr] = makeSingleArea(debtData, yscale, attr, cont, 1, true);
							}
						}
					}
				}
				
				function getCursorPosition(e) {  // from http://www.quirksmode.org/js/events_properties.html
					var posx = 0;  
					var posy = 0;
					if (!e) var e = window.event;
					if (e.pageX || e.pageY) 	{
						posx = e.pageX;
						posy = e.pageY;
					}
					else if (e.clientX || e.clientY) 	{
						posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
						posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
					}
					return [posx,posy];
				}
				
				function makePopup(place,e) {
					var dataValue, valueString, extra, placeText, holdText;
					var cursorPos = getCursorPosition(e);
					
					if (place.level === 0) {
						dataValue = debt[place.name]["total"][numMonths - 1];
						extra = "(Click for detailed view.)";
					} else {
						dataValue = (place.name === "Australia" || place.name === "Other") ? debt[place.continent]["total"][numMonths - 1] : debt[place.continent][place.name][numMonths - 1];
						extra = "(Click to go back.)";
					}
						
					if (dataValue > 1)
						valueString = "$" + String(Math.round(dataValue,1)) + " trillion";
					else
						valueString = "$" + String(Math.round(dataValue*1000)) + " billion";
					
					placeText = (place.name === "Other") ? "Other countries" : place.name;
					holdText = (place.name === "Other" || place.name === "Caribbean banks" || place.name === "Oil exporters") ? " hold " : " holds ";
					infoPopup.innerHTML = placeText + holdText + valueString + " <br>of US debt. <br><br> " + extra;
					infoPopup.style.visibility = "visible";
					infoPopup.style.left = String(cursorPos[0] + 30) + "px";
					infoPopup.style.top = String(cursorPos[1] + 30) + "px";
				}
				
				function erasePopup() {
					infoPopup.style.visibility = "hidden";
				}
				
				function areaMouseover(place,e) {
					if (place.attr("fill") !== colors[place.continent][1]) {  // for IE and Opera, to prevent re-firing this event
						place.attr({fill:colors[place.continent][1]});
						place.toFront();
						makePopup(place,e);
					}
				}
				
				function areaMouseout(place) {
					place.attr({fill:colors[place.continent][0]});
					erasePopup();
				}
				
				function trackCursor(place,e) {	
					var cursorPos = getCursorPosition(e);
					infoPopup.style.left = String(cursorPos[0] + 30) + "px";
					infoPopup.style.top = String(cursorPos[1] + 30) + "px";
				}
				
				function transition(place) {
					if (place.node.fireEvent) place.node.fireEvent("onmouseout"); // for IE; otherwise it doesn't know it's left the node (or, really, the node has left it)
					if (place.level === 0) {
						for (attr in areas["level0"]) {
							if (areas["level0"].hasOwnProperty(attr) && attr !== place.name) {
								areas["level0"][attr].animate({"30%":{opacity: 0, callback: function() { this.hide(); } } }, 1500);
							}
						}
						place.animate({ 
							"30%":{opacity:1}, 
							"70%":{path: areaPaths["level1"][place.continent]["total"], callback: function() { this.toBack();} },
							"100%": {opacity: 0, callback: function() { this.hide(); } }
						}, 1500);
						for (subattr in areas["level1"][place.continent]) {
							if (areas["level1"][place.continent].hasOwnProperty(subattr)) {
								areas["level1"][place.continent][subattr].show();
								areas["level1"][place.continent][subattr].animate({
									"70%":{opacity: 0}, 
									"100%":{opacity: 1, callback: function() { this.toFront(); } }
								}, 1500);
							}
						}
						makeRemakeYaxis(place.continent);
						makeRemakeTitle(place.continent);
					} else if (place.level === 1) {
						for (subattr in areas["level1"][place.continent]) {
							if (areas["level1"][place.continent].hasOwnProperty(subattr)) {
								areas["level1"][place.continent][subattr].animate({
									"30%":{opacity: 0, callback: function() { this.hide(); } }
								}, 1500);
							}
						}
						areas["level0"][place.continent].show();
						areas["level0"][place.continent].animate({ 
							"30%":{opacity:1}, 
							"70%":{path: areaPaths["level0"][place.continent], callback: function() { this.toFront();} }
						}, 1500);
						for (attr in areas["level0"]) {
							if (areas["level0"].hasOwnProperty(attr) && attr !== place.continent) {
								areas["level0"][attr].show();
								areas["level0"][attr].animate({
									"70%":{opacity: 0}, 
									"100%":{opacity: 1, callback: function() { this.show(); } } 
								}, 1500);
							}
						}
						makeRemakeYaxis("total");
						makeRemakeTitle("total");
					}
				}
				
				rescaleData(); // do the rescaling
				makeTotal();
				makeScales();
				makeAreas();
				makeXaxis();
				makeRemakeYaxis('total');
				makeRemakeTitle('total');
			};
        </script>
    </head>
    <body>
        <div id="graph"></div>
		<div id="info"></div>
    </body>
</html>

That’s the end of the tutorial.

Next Time?

I might add another post extending this graph so that you can drill down to see each country’s shape individually, and I might add a post making a Processing version of this code.

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: