Skip to content

Instantly share code, notes, and snippets.

@chornbaker
Last active December 14, 2016 21:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chornbaker/0b675edd9a2eb74dc60c8949521df520 to your computer and use it in GitHub Desktop.
Save chornbaker/0b675edd9a2eb74dc60c8949521df520 to your computer and use it in GitHub Desktop.
Matplotlib Barchart in D3
license: mit

Matplotlib Barchart in D3

This is a useful example for converting a matplotlib barchart to D3 with an initial animation. Using this template, you can quickly generate a nice dynamic barchart using all of your favorite matplotlib styles, and add more advanced interactions without dealing with a lot of additional formatting.

See barchart.py for code used to generate barchart.svg. It generates a simple barchart with some basic formatting. Matplotlib's savefig function will automatically output an svg format if the filepath has a .svg extension. The key is to assign a unique gid to each bar using set_gid, so it is simple to find the bars when you read the svg using d3.xml.

# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np
def main():
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(111)
width = .9
offset = 1.5-width
Y = np.random.randint(low=10, high=50, size=20)
X = np.arange(1, len(Y) + 1)
ax.bar(X, Y, width=width, align='center', color='steelblue')
# Assign unique id to each rectangle
for i, p in enumerate(ax.axes.patches):
p.set_gid("item_" + str(X[i]))
# Place bars and ticks
ax.set_xlim([min(X) - offset, max(X) + offset])
ax.set_xticks(X)
ax.set_xticklabels(X)
ax.xaxis.set_tick_params(size=0)
# Only show ticks on the left and bottom spines
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')
# Hide the right and top spines
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.savefig('barchart.svg', bbox_inches='tight', transparent=True)
if __name__ == '__main__':
main()
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<!-- CDN resource versions -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-ease.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
</style>
</head>
<body>
<!-- chart SVGs-->
<div id="chart"></div>
<script type="text/javascript">
d3.queue()
.defer(d3.xml, "barchart.svg")
.await(ready);
function ready(error, xml) {
if (error) throw error;
// Load SVG into chart
d3.select("#chart").node().appendChild(xml.documentElement);
///////////////////////////////////////////////////////////////////////////
/////////////////////////// Helper Functions //////////////////////////////
///////////////////////////////////////////////////////////////////////////
// Temporarily disable user interractions to allow animations to complete
var disableUserInterractions = function (time) {
isTransitioning = true;
setTimeout(function(){
isTransitioning = false;
}, time);
}//disableUserInterractions
// Convert rectangles into paths
function getPathFromRect(x,y,w,h) {
return "M " + x + " " + y +
" L " + (x+w) + " " + y +
" L " + (x+w) + " " + (y-h) +
" L " + x + " " + (y-h) + " z"
}//getPathFromRect
// Convert paths into rectangles
function getRectFromPath(path) {
// TODO: Generalize this for more robust parsing
path = path.split(" ")
return {"x": parseFloat(path[0].split(" ")[1]),
"y": parseFloat(path[0].split(" ")[2]),
"w": (parseFloat(path[1].split(" ")[1]) - parseFloat(path[0].split(" ")[1])),
"h": (parseFloat(path[0].split(" ")[2]) - parseFloat(path[2].split(" ")[2]))
}
}//getRectFromPath
///////////////////////////////////////////////////////////////////////////
///////////////////////// Animation Elements //////////////////////////////
///////////////////////////////////////////////////////////////////////////
// Set initial transition state
var isTransitioning = false;
// Basic plot elements
var svg, plot, bars, bar_locs,
bar_label = "item_";
svg = d3.select("#chart").select("svg")
plot = svg.select("#figure_1")
var height = parseFloat(svg.style("height"));
var width = parseFloat(svg.style("width"))
bars = plot.selectAll("g").filter(function(d,i,j) {
return new RegExp(bar_label, 'g').test(j[i].id)
});
bar_locs = {}
bars.nodes().forEach(function(d,i,j) {
var bar = d3.select(j[i]).selectAll("path");
var bar_id = j[i].id;
var bar_rect = getRectFromPath(bar.attr("d"))
bar_locs[bar_id] = bar_rect
bar_rect["c"] = bar.style("fill")
})
svg.on("click", function(){
if (isTransitioning == false) {
init();
}
});
///////////////////////////////////////////////////////////////////////////
////////////////// Initialize Graphic and Animations //////////////////////
///////////////////////////////////////////////////////////////////////////
function init() {
//////////////////////////////////////////////////////
///////////////////// Actions ////////////////////////
//////////////////////////////////////////////////////
var DURATION = 1000;
var OPACITY = 1
disableUserInterractions(DURATION * 3);
bars.each(function(d,i,j) {
var loc = bar_locs[j[i].id]
d3.select(this).selectAll("path")
.attr("d", getPathFromRect(loc.x,loc.y,loc.w,0))
})
bars.transition()
.each(function(d,i,j) {
var loc = bar_locs[j[i].id]
d3.select(this).selectAll("path")
.transition().delay(i*100).duration(DURATION + (i*10))
.ease(d3.easeCubic)
.attr("d", getPathFromRect(loc.x,loc.y,loc.w,loc.h))
})
}//init
init()
};
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment