Skip to content

Instantly share code, notes, and snippets.

@mtaptich
Created January 11, 2016 19:33
Show Gist options
  • Save mtaptich/f460974477394032e398 to your computer and use it in GitHub Desktop.
Save mtaptich/f460974477394032e398 to your computer and use it in GitHub Desktop.
Impulse Response
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
body {
width: 1024px;
margin-top: 0;
margin: auto;
font-family: "Lato", "PT Serif", serif;
color: #222222;
padding: 0;
font-weight: 300;
line-height: 33px;
font-size: 24px;
-webkit-font-smoothing: antialiased;
}
.line{
fill: none;
stroke: #000;;
stroke-width: 4px;
}
.co2{
stroke: #7f8c8d;
}
.ch4{
stroke: #f1c40f;
}
.axis path,
.axis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
.box{
fill:#ecf0f1;
stroke: #2c3e50;
stroke-dasharray: 2px,2px;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus{
fill:#2c3e50;
font-size: 24px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = { top: 20, right: 200, bottom: 70, left: 200 },
width = 960 - margin.left - margin.right,
height = 540 - margin.top - margin.bottom,
chart_top_margin = height*2/3,
n = 100,
methane_lifetime = 12; // years
var svg = d3.select('body').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// Legend
legendco2 = svg.append('g').attr('transform', 'translate(' + 10 + ',' + 0 + ')');
legendco2.append('rect')
.attr('x', -10)
.attr('y', 10)
.attr('width', 100)
.attr('height', 30)
.attr('rx', 4)
.attr('ry', 4)
.style('fill', '#7f8c8d');
legendco2.append('text')
.attr('x', 40)
.attr('y', 33)
.style('text-anchor', 'middle')
.style('fill', '#fff')
.text('CO2')
legendch4 = svg.append('g').attr('transform', 'translate(' + (width - 90) + ',' + 0 + ')');
legendch4.append('rect')
.attr('x', -10)
.attr('y', 10)
.attr('width', 100)
.attr('height', 30)
.attr('rx', 4)
.attr('ry', 4)
.style('fill', '#f1c40f');
legendch4.append('text')
.attr('x', 40)
.attr('y', 33)
.style('text-anchor', 'middle')
.text('CH4')
// Circles and Hull
var fill = d3.scale.threshold().domain([0.5]).range(['#7f8c8d', '#f1c40f']),
nodes = d3.range(200).map(function(d,i){ return {id: i% 2 ? 1:0} }),
hulls = svg.append('g'),
counts = {c:100, m: 100},
mouselast = 0;
var groups = d3.nest().key(function(d) { return d.id % 2; }).entries(nodes);
var groupPath = function(d) {
return "M" +
d3.geom.hull(d.values.map(function(i) { return [i.x, i.y]; }))
.join("L")
+ "Z";
};
var groupFill = function(d, i) { return fill(+d.key); };
var circleFill = function(d, i) { return fill(+d.id); };
var force = d3.layout.force()
.nodes(nodes)
.links([])
.size([width, height*0.6])
.gravity(0.3)
.friction(0.7)
.start();
var node = hulls.selectAll(".node")
start()
force.on("tick", function(e) {
// Push different nodes in different directions for clustering.
var k = 2.5 * e.alpha;
nodes.forEach(function(o, i) {
o.x += o.id % 2 ? k : -k;
o.y += o.id % 2 ? k : -k;
});
node.attr("cx", function(d) {
var radius = d3.select(this).attr('r')
return d.x = Math.max(radius, Math.min(width - radius, d.x));
})
.attr("cy", function(d) {
var radius = d3.select(this).attr('r')
return d.y = Math.max(radius, Math.min(height*0.6 - radius, d.y))
});
hulls.selectAll("path")
.data(groups)
.attr("d", groupPath)
.enter().insert("path", "circle")
.style("fill", groupFill)
.style("stroke", groupFill)
.style("stroke-width", 20)
.style("stroke-linejoin", "round")
.style("opacity", .2)
.attr("d", groupPath);
node.style("fill", function(d){ return circleFill(d)})
});
function start(){
node = node.data(force.nodes());
node.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 8)
.style("fill", function(d){ return circleFill(d)})
.style("stroke", 'none')
.style("stroke-width", 1.5)
.call(force.drag);
node.exit().remove();
groups = d3.nest().key(function(d) { return d.id % 2; }).entries(force.nodes());
force.start();
}
// Chart and Updates
var bisectDate = d3.bisector(function(d) { return d.x; }).left;
var inpulse_responce_co2 = function(t){
// valid for t < 1000 years
return 0.217 + 0.259*Math.exp(-t/172.9) + 0.338*Math.exp(-t/18.51) + 0.186*Math.exp(-t/1.186)
}
var inpulse_responce_ch4 = function(t){
// valid for t < 1000 years
return Math.exp(-t/methane_lifetime)
}
var bisect = function(data, x0){
var i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.x > d1.x - x0 ? d1 : d0;
return d
}
var co2data = d3.range(n+1).map(function(d,i) { return {x:i, y:inpulse_responce_co2(i)}; });
var ch4data = d3.range(n+1).map(function(d,i) { return {x:i, y:inpulse_responce_ch4(i)}; });
var chart = svg.append('g')
.attr('transform', 'translate(' + 0 + ',' + chart_top_margin + ')');
var x = d3.scale.linear().domain([0, n]).range([0, width]);
var y = d3.scale.linear().domain([0, 1]).range([height/3, 0]);
var line = d3.svg.line().interpolate("basis")
.x(function(d, i) { return x(d.x); })
.y(function(d, i) { return y(d.y); });
chart.append("rect")
.attr("width", width)
.attr("height", height/3)
.attr('class', 'box');
var yaxis = chart.append("g")
.attr("class", "y axis")
.call(y.axis = d3.svg.axis().scale(y).orient("left").ticks(3))
.append("text")
.attr("class", "label")
//.attr("transform", "rotate(-90)")
.attr("y", 10)
.attr("x", width-50)
.attr("dy", ".71em")
.style("text-anchor", "end")
.style('font-size', '12')
.text("Fraction of Emissions Impulse Remaining")
var xaxis = chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (height/3) + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom").ticks(5))
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", 60)
.style("text-anchor", "end")
.style('font-size', '18')
.text("Time (years after pulse) ");
var co2_impulse = chart.append("path")
.datum(co2data)
.attr("class", "line co2")
.attr("d", line);
var methane_impulse = chart.append("path")
.datum(ch4data)
.attr("class", "line ch4")
.attr("d", line);
var focusco2 = chart.append("g").attr("class", "focus").style("display", "none");
focusco2.append("circle").attr("r", 4.5);
focusco2.append("text").attr("x", 0).attr("dy", "-0.35em").style('text-anchor', 'middle');
// methane
var focusch4 = chart.append("g").attr("class", "focus").style("display", "none");
focusch4.append("circle").attr("r", 4.5);
focusch4.append("text").attr("x", 0).attr("dy", "-0.35em").style('text-anchor', 'middle');
chart.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focusco2.style("display", null);
focusch4.style("display", null);
})
.on("mouseout", function() {
focusco2.style("display", null);
focusch4.style("display", null);
})
.on("mousemove", mousemove);
function mousemove(callback) {
var x0 = x.invert(d3.mouse(this)[0]),
newdata = [],
dir = mouselast < x0 ? 1 : -1;
// update co2
d = bisect(co2data, x0);
focusco2.attr("transform", "translate(" + x(d.x) + "," + y(d.y) + ")");
focusco2.select("text").text(Math.round(d.y*100)/100);
if (dir == 1){
while( counts.c > Math.floor(d.y*100)) {
var index = get(0)
force.nodes().splice(index, 1)
counts.c-=1
}
}else{
while( counts.c <= Math.floor(d.y*100)) {
force.nodes().push({id: 0, x: width*0.4, y: height/3})
counts.c+=1
}
}
// update co2
var d = bisect(ch4data, x0)
focusch4.attr("transform", "translate(" + x(d.x) + "," + y(d.y) + ")");
focusch4.select("text").text(Math.round(d.y*100)/100);
if (dir == 1){
while( counts.m > Math.floor(d.y*100)) {
var index = get(1)
force.nodes().splice(index, 1)
counts.m-=1
}
}else{
while( counts.m <= Math.floor(d.y*100)) {
force.nodes().push({id: 1, x: width*0.7, y: height/3})
counts.m+=1
}
}
mouselast = x0
start()
}
function get(id){
var c = force.nodes()
for (var i = 0; i < c.length; i++) {
if(c[i].id == id){
return i
}
};
return false
}
d3.select(self.frameElement).style("height", (height + margin.top + margin.bottom) + "px");
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment