Instantly share code, notes, and snippets.

Last active August 29, 2015 13:57
Exponential water tank

This example aims to demonstrate our inability to fully grasp exponential functions. As Albert Bartlett once said, "The greatest shortcoming of the human race is our inability to understand the exponential function." This little D3 animation is based on a paper by Dr Bartlett.

Our action hero, a pixelated version of Chris Martenson, stands on a platform inside an empty 4000 litre water tank. At the very bottom of the tank lies a magic drop of water. Invisible to the eye now, it doubles in size every 10 seconds.

Although the growth rate is constant, for a long time we see no change. But there's a well known limit, the capacity of the tank. Once he realises that water is rising exponentially, poor pixellated Chris has no time left to react.

Our brains are wired to predict future behaviour based on past behaviour (see here). But what happens when something growths exponentially? For a long time, the numbers are so little in relation to the scale that we hardly see the changes. But even at moderate growth rates exponential functions reach a point where the numbers grow too fast. Once we confirm that our predictions about the future have failed, very little time to react may be left.

Chris Martenson has two videos (this one and this other) from his 'Crash course' talking about the exponential function and why it is important. I recommend having a look at them.

Find me at @hanbzu.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 Exponential water tank
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 { "dialogLines" : [ { "time": 1, "text": "Hello everyone!" }, { "time": 5, "text": "Let me introduce myself." }, { "time": 10, "text": "My name is Chris Pixelated Martenson" }, { "time": 15, "text": "and I am here to tell you about the..." }, { "time": 20, "text": "...EXPONENTIAL FUNCTION!" }, { "time": 25, "text": "Look: I'm stuck in this 4000 litre water tank." }, { "time": 30, "text": "There's a drop of water down there, so small I can't even see it." }, { "time": 35, "text": "They told me it magically doubles in size every 10 seconds!" }, { "time": 39, "text": "Which means it's now growing EXPONENTIALLY." }, { "time": 45, "text": "Rationally, that tells me that in a couple of minutes" }, { "time": 50, "text": "this tank should be full of water" }, { "time": 55, "text": "and I... would drown." }, { "time": 60, "text": "But to me, it doesn't look like it's moving." }, { "time": 70, "text": "Hmmmm." }, { "time": 100, "text": "Uh-oh... now I see water rising very fast..." }, { "time": 105, "text": "Emmm... I need to find my way out of here!" }, { "time": 110, "text": "Too late!?!?!" }, { "time": 115, "text": "Uh-oh..." }, { "time": 119, "text": "Ggbgrgbhh!!!!" } ] }
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
 var width = 960, height = 700; var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); // 150 words per minute to read confortably var CPM = 150 * 5; // CPM = WPM * 5 // I need 'Blocks' to accomodate the full height of my water tank d3.select(self.frameElement).style("height", height + "px"); d3.json("monolog.json", function(error, json) { if (error) return console.warn(error); play({ monolog: json.dialogLines }); }); function play(opts) { var opts = opts || {}, doublingTime = opts.doublingTime || 10 * 1000, tankCapacity = opts.tankCapacity || 4000, startValue = opts.startValue || 1, startTime = opts.startTime || new Date("March 28, 2013 00:00:00"), interval = opts.interval || 100, headLevel = opts.headLevel || 600, monolog = opts.monolog || [] var currentTime = startTime, heSays = monolog.shift(); updateHero('idle'); var t = setInterval(timeTick, interval); function timeTick() { currentTime = new Date(currentTime.getTime() + interval); updateClock(currentTime); var value = getValue(startValue, doublingTime, currentTime - startTime), waterLevel = value*height/tankCapacity; if (waterLevel > headLevel) { updateHero('drowning'); } if (heSays && heSays.time * 1000 < currentTime - startTime) { updateText(heSays.text); heSays = monolog.shift(); } if (waterLevel > height) { console.log("Oh! The hero is drowning!") clearInterval(t); updateWatertank(headLevel, 3000, 5000) setTimeout(function() { updateHero('idle'); updateText("Pheww... please, don't try this in the real world."); }, 8000); } else { updateWatertank(waterLevel, interval) } } } function getValue(startValue, doublingTime, elapsedMilis) { return startValue * Math.pow(2, elapsedMilis / doublingTime); } function updateWatertank(waterLevel, duration, delay) { delay = delay || 0; var waterRectangle = svg.selectAll("rect").data([waterLevel]) waterRectangle.enter().append("rect") .attr("id", "water") .attr("x", 0) .attr("width", width) waterRectangle .transition().delay(delay).duration(duration) .attr("y", function(d) { return height - waterLevel; }) .attr("height", function(d) { return waterLevel; }); } function updateHero(type) { var hero = svg.selectAll("image").data([type]); hero.enter().append("image") .attr("x", "-20") .attr("y", "10") .attr("width", "432") .attr("height", "268"); hero .attr("xlink:href", function(d) { return "character_" + d + ".gif"; }) } function updateText(text) { var txt = svg.selectAll("#monolog").data([text]); txt.enter().append("text") .attr("id", "monolog"); txt .attr("x", function(d) { return randomPos(175, 185); }) .attr("y", function(d) { return randomPos(40, 80); }) .text(function(d) { return d }) .transition().duration(500).style('fill-opacity', 1) .transition().delay(function(d) { return (d.length / CPM) * 60 * 1000; }) .duration(4000).style('fill-opacity', 0) } function randomPos(min, max) { return Math.floor((Math.random() * (max - min + 1)) + min); } function updateClock(time) { var clock = svg.selectAll("#clock").data([time]); clock.enter().append("text") .attr("id", "clock") .attr("x", width - 123) .attr("y", "50"); clock .text(function(d) { return getReadableHour(d); }); } // I just use this as a counter function getReadableHour(time) { var mins = time.getMinutes(), secs = time.getSeconds(); mins = (mins < 10 ? "0" : "") + mins; secs = (secs < 10 ? "0" : "") + secs; return mins + ":" + secs; }