Dana Silver @DanaRSilver GitHub

April 14, 2014

GitHub Language Stats with D3.js

The D3.js Bar Chart Tutorials with GitHub Data

These charts are largely based on the charts in Mike Bostock's Let's Make a Bar Chart tutorial with some modifications to use git repository language data from the GitHub API.

Fetching the Data

I am using the language data from my GitHub repository for this website. The GitHub API returns an object containing each language and the number of bytes of that language in the repository. I request the data using d3.json and call makeChart(languageJSON) on the response. The code for the examples below is contained in makeChart.


var url = "https://api.github.com/repos/danasilver/danasilver.github.io/languages";
d3.json(url, makeChart);

function makeChart(languageJSON) {

  // Format the data to work with D3
  var dataArray = [],
      dataKeyVal = [];

  for (lang in languageJSON) {
    dataArray.push(languageJSON[lang]);
  }

  for (lang in languageJSON) {
    dataKeyVal.push({"lang": lang, "bytes": languageJSON[lang]});
  }

  // Render charts

}

Bar Chart

The value in each bar is the number of bytes of each language in the repository. This chart, though rendered with D3, is pure HTML. Each bar is a div element.


x = d3.scale.linear()
  .domain([0, d3.max(dataArray)])
  .range([0, 834]);

d3.select(".bar-chart")
  .selectAll("div")
    .data(dataArray)
  .enter().append("div")
    .style("width", function(d) { return x(d) + "px"; })
    .text(function(d) { return d; });

SVG Bar Chart

Scalable Vector Graphics (SVG) is an XML markup language for describing two-dimensional vector graphics. SVG is essentially to graphics what XHTML is to text.
Mozilla Developer Network - SVG

This chart looks identical but uses SVG instead of plain HTML. Each bar is a g (group) element, containing a rect and a text element.


var width = 840,
    barHeight = 20;

var x = d3.scale.linear()
    .domain([0, d3.max(dataArray)])
    .range([0, width]);

var chart = d3.select(".bar-chart-svg")
    .attr("width", width)
    .attr("height", barHeight * dataArray.length);

var bar = chart.selectAll("g")
    .data(dataArray)
  .enter().append("g")
    .attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });

bar.append("rect")
    .attr("width", x)
    .attr("height", barHeight - 1);

bar.append("text")
    .attr("x", function(d) { return x(d) - 3; })
    .attr("y", barHeight / 2)
    .attr("dy", ".35em")
    .text(function(d) { return d; });

Vertical SVG Bar Chart

This chart, besides vertical orientation, features axes and labels to give the data context. I also did not hard code the bar widths this time, instead using D3's ordinal.rangeRoundBands to compute the output range given minimum and maximum values. ordinal.rangeRoundBands divides the range into n evenly spaced bands, where n is the number of unique values in the input domain (the number of languages, in this case).

ordinal.rangeBands illustration
ordinal.rangeBands illustration from the d3.js wiki

D3 provides ordinal.rangeBands and ordinal.rangeRoundBands. The two do the same, however ordinal.rangeRoundBands sets the width and offset values of each band to integers to avoid antialiasing artifacts.


var margin = {top: 10, right: 20, bottom: 20, left: 60},
    width = 500 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

var x = d3.scale.ordinal()
    .domain(dataKeyVal.map(function(d) { return d.lang; } ))
    .rangeRoundBands([0, width], .1);

var y = d3.scale.linear()
    .domain([0, d3.max(dataKeyVal, function(d) { return d.bytes })])
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var chart = d3.select(".vertical-bar-chart")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

chart.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

chart.append("g")
    .attr("class", "y axis")
    .call(yAxis)

d3.select(".vertical-bar-chart").append("text")
    .style("text-anchor", "end")
    .attr("transform", "translate(15," + height / 2 + "), rotate(-90)")
    .text("Bytes");

var bar = chart.selectAll(".bar")
    .data(dataKeyVal)
  .enter().append("g")
    .attr("class", "bar")
    .attr("transform", function(d) { return "translate(" + x(d.lang) + ",0)"; })

bar.append("rect")
    .attr("class", "bar")
    .attr("y", function(d) { return y(d.bytes); })
    .attr("height", function(d) { return height - y(d.bytes); })
    .attr("width", x.rangeBand())

bar.append("text")
    .attr("x", x.rangeBand() / 2)
    .attr("y", function(d) { return y(d.bytes) + 3; })
    .attr("dy", ".75em")
    .text(function(d) { return d.bytes; });