Skip to content

Instantly share code, notes, and snippets.

@guilhermesimoes
Last active Apr 6, 2020
Embed
What would you like to do?
D3.js: Animating bars "going down"

One confusing aspect of animating bars (and working with SVG in general) is that the coordinate system origin is at the top-left corner, which means that y increases downwards.

One would expect that to animate a bar diminishing in size ("going down") changing the height attribute would be all that's needed. However that's not true. This is a visual explanation of why it's necessary to animate both y and height attributes.

Another solution would be to surround the bar with a clipPath element and then transition the bar down. This way, everything on the inside of the clipping area is allowed to show through but everything on the outside is masked out.

This other approach would only work for a simple bar chart though. For a more complex, animated stacked chart, animating both y and height attributes is the only solution.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
html, body {
height: 100%;
margin: 0;
}
.chart-container {
position: relative;
box-sizing: border-box;
height: 100%;
padding: 15px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.controls {
display: flex;
flex-direction: column;
position: absolute;
bottom: 15px;
left: 15px;
}
@media screen and (min-width: 310px) {
.controls {
display: block;
bottom: initial;
top: 15px;
}
}
.chart {
width: 100%;
height: 100%;
overflow: visible;
}
.chart path,
.chart line,
.chart rect {
shape-rendering: crispEdges;
}
.chart .bar {
fill: steelblue;
}
.chart .baseline {
fill: none;
stroke: black;
}
</style>
<body>
<div class="chart-container js-chart-container">
<form class="controls js-controls">
<label><button type="button" value="height">Animate height</button></label>
<label><button type="button" value="y">Animate y</button></label>
<label><button type="button" value="both">Animate y and height</button></label>
</form>
<svg class="chart js-chart"></svg>
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script type="text/javascript">
"use strict";
var container = { width: 1000, height: 500 },
margin = { top: 60, right: 0, bottom: container.height / 6, left: 0 },
width = container.width - margin.left - margin.right,
height = container.height - margin.top - margin.bottom,
animationDuration = 400,
barWidth = 40,
barX = (width - barWidth) / 2,
toggling = true;
var svg = d3.select(".js-chart")
.attr("viewBox", "0 0 " + container.width + " " + container.height);
var mainContainer = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var bar = mainContainer.append("rect")
.attr("class", "bar")
.attr("x", barX)
.attr("y", 0)
.attr("width", barWidth)
.attr("height", height);
mainContainer.append("line")
.attr("class", "baseline")
.attr("x1", 0)
.attr("x2", width)
.attr("y1", height)
.attr("y2", height);
var buttons = d3.selectAll(".js-controls button");
buttons.on("click", toggleBar);
function toggleBar() {
if (toggling) {
hideBar(this.value);
buttons.attr("disabled", "disabled");
d3.select(this).attr("disabled", null);
} else {
showBar(this.value);
buttons.attr("disabled", null);
}
toggling = !toggling;
}
function hideBar(togglingMode) {
var transition = bar.transition().duration(animationDuration);
switch (togglingMode) {
case "height":
transition.attr("height", 0);
break;
case "y":
transition.attr("y", height);
break;
case "both":
transition.attr("y", height).attr("height", 0);
break;
}
}
function showBar(togglingMode) {
var transition = bar.transition().duration(animationDuration);
switch (togglingMode) {
case "height":
transition.attr("height", height);
break;
case "y":
transition.attr("y", 0);
break;
case "both":
transition.attr("y", 0).attr("height", height);
break;
}
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment