Skip to content

Instantly share code, notes, and snippets.

@hanbzu
Last active August 29, 2015 13:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hanbzu/9787042 to your computer and use it in GitHub Desktop.
Save hanbzu/9787042 to your computer and use it in GitHub Desktop.
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.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Exponential water tank</title>
<style type="text/css">
html { -webkit-font-smoothing: antialiased; }
#water {
fill: rgba(103, 175, 189, 0.31);
}
svg text {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", "Helvetica LT Light", Helvetica, Arial, "Lucida Grande", sans-serif;
}
#monolog { font-size: 19px; fill: black; }
#clock { font-size: 40px; fill: #ECECEC; }
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="watertank.js" charset="utf-8"></script>
</body>
</html>
{ "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!!!!" }
]
}
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment