Skip to content

Instantly share code, notes, and snippets.

@mbecica
Created August 2, 2012 06:11
Show Gist options
  • Save mbecica/3234310 to your computer and use it in GitHub Desktop.
Save mbecica/3234310 to your computer and use it in GitHub Desktop.
Stacked Bar d3 transition in canvas
<!DOCTYPE HTML>
<head>
<title>Stacked bar d3 canvas transition</title>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/d3/2.8.1/d3.v2.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/zepto/1.0rc1/zepto.min.js"></script>
</head>
<body>
<div id="graphs"></div>
<button id="switch">Switch</button>
<script type="text/javascript">
$(function() {
var w = 720,
h = 300,
p = [0, 25, 15, 0],
transition_count = 0,
last = [],
x = d3.scale.ordinal().rangeRoundBands([0, w - p[1] - p[3]]),
y = d3.scale.linear().range([0, h - p[0] - p[2]]),
z = d3.scale.ordinal().range(["darkgray", "lightblue"]),
parse = d3.time.format("%b %e").parse,
format = d3.time.format("%b %e"),
swap = true;
$("#graphs").append("<canvas width="+w+" height="+h+" id='canvas'></canvas");
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
d3.json("revenue.json", function(json) {
//Draw graph on load
var data = d3.layout.stack()(_(json[0]).map(function(row) {
return row;
}));
render(data);
last = data;
});
$('#switch').click(function() {
if (swap) {
transition("rev-two.json", ++transition_count);
swap = false;
} else {
transition("revenue.json", ++transition_count);
swap = true;
}
});
function render(data) {
// Compute the x-domain and y-domain.
y.domain([0, d3.max(data.map(function(d) {
return d3.max(d, function(e, i) {
return data[1][i].y + data[0][i].y
})}))*1.1]);
x.domain(data[1].map(function(d) { return d.x; }));
// y axis
y.ticks(4).map(drawAxis);
// Draw bars
_(data).each(function(row, i) {
_(row).each(function(d, j) {
drawBar(i, j, d);
});
});
};
function drawBar(i, j, d) {
ctx.fillStyle = z(i);
ctx.fillRect(p[1]+x(d.x)+1, h-p[2]-y(d.y0)-y(d.y), x.rangeBand()-1, y(d.y));
// x axis
if (j%7 == 0 || j==0) {
ctx.fillStyle = "#999";
ctx.fillText(format(new Date(d.x)), x(d.x) + x.rangeBand(), h-2);
}
};
function drawAxis(a) {
//draw y axis
ctx.fillStyle = "#999";
if (a != 0) {
ctx.fillText(a, 0, h-p[2]-y(a)+12);
ctx.lineWidth = .1;
ctx.beginPath();
ctx.moveTo(0, h-p[2]-y(a));
ctx.lineTo(w, h-p[2]-y(a));
ctx.stroke();
} else {
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, h-p[2]-y(a)+1);
ctx.lineTo(w, h-p[2]-y(a)+1);
ctx.stroke();
}
};
function transition(file, count) {
d3.json(file, function(json) {
var data = d3.layout.stack()(_(json[0]).map(function(row) { return row; }));
// find next positions by interpolating arrays
var transition = d3.interpolate(position(last), position(data));
// run transition
d3.timer(function(t) {
// abort old transition
if (count < transition_count) return true;
clear();
if (t > 1000) {
last = data;
render(last);
return true
};
last = transition(t/1000);
render(resumeData(last));
});
});
};
function resumeData(data) {
return data.map(function(d) {
return d.map(function(e) {
return {"x":e[0],"y":e[1],"y0":e[2]}
})
});
}
function position(data) {
return data.map(function(d) {
return d.map(function(e) {
return [e.x,e.y,e.y0]
})
});
};
// clear canvas
function clear() {
ctx.clearRect(0,0,w,h);
};
});
</script>
[{
"gross":[{"x":1340938902744,"y":478},{"x":1341025302744,"y":487},{"x":1341111702744,"y":473},{"x":1341198102744,"y":241},{"x":1341284502744,"y":10},{"x":1341370902744,"y":293},{"x":1341457302744,"y":254},{"x":1341543702744,"y":165},{"x":1341630102744,"y":223},{"x":1341716502744,"y":69},{"x":1341802902744,"y":432},{"x":1341889302744,"y":328},{"x":1341975702744,"y":27},{"x":1342062102744,"y":273},{"x":1342148502744,"y":248},{"x":1342234902744,"y":105},{"x":1342321302744,"y":418},{"x":1342407702744,"y":320},{"x":1342494102744,"y":355},{"x":1342580502744,"y":303},{"x":1342666902744,"y":155},{"x":1342753302744,"y":89},{"x":1342839702744,"y":54},{"x":1342926102744,"y":475},{"x":1343012502744,"y":138},{"x":1343098902744,"y":436},{"x":1343185302744,"y":73},{"x":1343271702744,"y":404},{"x":1343358102744,"y":434},{"x":1343444502744,"y":327}],
"total":[{"x":1340938902744,"y":736},{"x":1341025302744,"y":440},{"x":1341111702744,"y":1089},{"x":1341198102744,"y":566},{"x":1341284502744,"y":1385},{"x":1341370902744,"y":1420},{"x":1341457302744,"y":1471},{"x":1341543702744,"y":724},{"x":1341630102744,"y":860},{"x":1341716502744,"y":392},{"x":1341802902744,"y":670},{"x":1341889302744,"y":1395},{"x":1341975702744,"y":373},{"x":1342062102744,"y":659},{"x":1342148502744,"y":169},{"x":1342234902744,"y":850},{"x":1342321302744,"y":1409},{"x":1342407702744,"y":1070},{"x":1342494102744,"y":1138},{"x":1342580502744,"y":32},{"x":1342666902744,"y":1169},{"x":1342753302744,"y":594},{"x":1342839702744,"y":79},{"x":1342926102744,"y":337},{"x":1343012502744,"y":988},{"x":1343098902744,"y":1435},{"x":1343185302744,"y":169},{"x":1343271702744,"y":17},{"x":1343358102744,"y":1181},{"x":1343444502744,"y":1268}]
}]
[{
"gross":[{"x":1340938902744,"y":69},{"x":1341025302744,"y":450},{"x":1341111702744,"y":350},{"x":1341198102744,"y":591},{"x":1341284502744,"y":283},{"x":1341370902744,"y":18},{"x":1341457302744,"y":115},{"x":1341543702744,"y":345},{"x":1341630102744,"y":302},{"x":1341716502744,"y":412},{"x":1341802902744,"y":224},{"x":1341889302744,"y":463},{"x":1341975702744,"y":566},{"x":1342062102744,"y":444},{"x":1342148502744,"y":402},{"x":1342234902744,"y":295},{"x":1342321302744,"y":586},{"x":1342407702744,"y":549},{"x":1342494102744,"y":564},{"x":1342580502744,"y":398},{"x":1342666902744,"y":326},{"x":1342753302744,"y":476},{"x":1342839702744,"y":15},{"x":1342926102744,"y":428},{"x":1343012502744,"y":566},{"x":1343098902744,"y":519},{"x":1343185302744,"y":393},{"x":1343271702744,"y":448},{"x":1343358102744,"y":42},{"x":1343444502744,"y":336}],
"total":[{"x":1340938902744,"y":3972},{"x":1341025302744,"y":1052},{"x":1341111702744,"y":2768},{"x":1341198102744,"y":2607},{"x":1341284502744,"y":341},{"x":1341370902744,"y":362},{"x":1341457302744,"y":1357},{"x":1341543702744,"y":3611},{"x":1341630102744,"y":2133},{"x":1341716502744,"y":270},{"x":1341802902744,"y":4624},{"x":1341889302744,"y":3666},{"x":1341975702744,"y":993},{"x":1342062102744,"y":4573},{"x":1342148502744,"y":4580},{"x":1342234902744,"y":3261},{"x":1342321302744,"y":4842},{"x":1342407702744,"y":3786},{"x":1342494102744,"y":554},{"x":1342580502744,"y":740},{"x":1342666902744,"y":1382},{"x":1342753302744,"y":2851},{"x":1342839702744,"y":1605},{"x":1342926102744,"y":1828},{"x":1343012502744,"y":1612},{"x":1343098902744,"y":2482},{"x":1343185302744,"y":3854},{"x":1343271702744,"y":1239},{"x":1343358102744,"y":2282},{"x":1343444502744,"y":3141}]
}]
<!DOCTYPE HTML>
<head>
<title>Stacked bar d3 svg transition</title>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/d3/2.8.1/d3.v2.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.3/underscore-min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/zepto/1.0rc1/zepto.min.js"></script>
</head>
<body>
<button id="switch">Switch</button>
<script type="text/javascript">
$(function() {
var w = 700,
h = 300,
p = [20, 50, 30, 20],
x = d3.scale.ordinal().rangeRoundBands([0, w - p[1] - p[3]]),
y = d3.scale.linear().range([0, h - p[0] - p[2]]),
z = d3.scale.ordinal().range(["darkgray", "lightblue"]),
parse = d3.time.format("%b %e").parse,
format = d3.time.format("%b %e"),
swap = true;
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h)
.append("svg:g")
.attr("transform", "translate(" + p[3] + "," + (h - p[2]) + ")");
var renderOne = function(file) {
d3.json(file, function(rData) {
var data = rData[0];
var stacks = d3.layout.stack()(_(data).map(function(row) {
return row;
}));
rerender(data, stacks);
});
};
d3.json("revenue.json", function(rData) {
var data = rData[0];
var stacks = d3.layout.stack()(_(data).map(function(row) {
return row;
}));
render(data, stacks);
});
$('#switch').click(function() {
if (swap) {
renderOne("rev-two.json");
swap = false;
} else {
renderOne("revenue.json");
swap = true;
}
});
var render = function(data, stacks) {
// Compute the x-domain and y-domain.
y.domain([0, d3.max($.map(data, function(d) {
return d3.max(d, function(e, i) {
return data["total"][i].y + data["gross"][i].y
})}))*1.1]);
x.domain(data["total"].map(function(d) { return d.x; }));
// Add a group for each bar.
var bar = svg.selectAll("g.bar")
.data(stacks)
.enter().append("svg:g")
.attr("class", "bar")
.style("fill", function(d, i) { return z(i); });
// Add a rect for each date.
var rect = bar.selectAll("rect")
.data(Object)
.enter().append("svg:rect")
.attr("x", function(d) { return x(d.x) + 1; })
.attr("y", function(d) { return -y(d.y) - y(d.y0); })
.attr("height", function(d) { return y(d.y); })
.attr("width", x.rangeBand()-1);
// Add a label per date.
var label = svg.selectAll("text.xlabel")
.data(x.domain())
.enter().append("svg:text")
.attr("x", function(d) { return x(d) + x.rangeBand() / 2; })
.attr("y", 6)
.attr("text-anchor", "middle")
.attr("dy", ".71em")
.attr("class", "xlabel")
.text(function(d) { return format(new Date(d)) });
// Add y-axis rules.
var rule = svg.selectAll("g.rule")
.data(y.ticks(3))
.enter().append("svg:g")
.attr("class", "rule")
.attr("transform", function(d) { return "translate(0," + -y(d) + ")"; });
rule.append("svg:line")
.attr("x2", w - p[1] - p[3])
.attr("class", "yline")
.style("stroke", function(d) { return d ? "#ccc" : "#000"; })
.style("stroke-opacity", function(d) { return d ? .7 : null; });
rule.append("svg:text")
.attr("x", 0)
.attr("dy", "1em")
.attr("class", "ylabel")
.text(d3.format(",d"));
};
var rerender = function(data, stacks) {
// Compute the x-domain and y-domain.
y.domain([0, d3.max($.map(data, function(d) {
return d3.max(d, function(e, i) {
return data["total"][i].y + data["gross"][i].y
})}))*1.1]);
x.domain(data["total"].map(function(d) { return d.x; }));
// Add a group for each bar.
var bar = svg.selectAll("g.bar")
.data(stacks);
// Add a rect for each date.
bar.selectAll("rect")
.data(Object)
.transition()
.duration(1000)
.attr("x", function(d) { return x(d.x) + 1; })
.attr("y", function(d) { return -y(d.y) - y(d.y0); })
.attr("height", function(d) { return y(d.y); })
.attr("width", x.rangeBand()-1);
// Add a label per date.
svg.selectAll("text.xlabel")
.data(x.domain())
.transition()
.duration(1000)
.attr("x", function(d) { return x(d) + x.rangeBand() / 2; })
.attr("y", 6)
.text(function(d) { return format(new Date(d)) });
// Add y-axis rules.
svg.selectAll("g.rule")
.data(y.ticks(3))
.transition()
.duration(1000)
.attr("transform", function(d) { return "translate(0," + -y(d) + ")"; });
svg.selectAll("line.yline")
.attr("x2", w - p[1] - p[3])
.style("stroke", function(d) { return d ? "#ccc" : "#000"; })
.style("stroke-opacity", function(d) { return d ? .7 : null; });
svg.selectAll("text.ylabel")
.text(d3.format(",d"));
};
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment