Skip to content

Instantly share code, notes, and snippets.

@nstrayer
Forked from ejb/.block
Last active July 26, 2016 23:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nstrayer/776ca46537c557e59b994aa439fdb26c to your computer and use it in GitHub Desktop.
Save nstrayer/776ca46537c557e59b994aa439fdb26c to your computer and use it in GitHub Desktop.
Reusable D3 Histograms
license: mit

A fork of the code from @ejb 's block for his blog post “A better way to structure D3 code”.

Code for the histogram itself was heavily borrowed from this bl.ock by Mike Bostock.

Takes data in the form of a vector of numerical values.

Also see my second visualization using @ejb's style: the dotplot.

This is part of my larger project of making a d3/r library for dynamic and interactive basic statistical plots.

var Chart = function(opts) {
// load in arguments from config object
this.data = opts.data;
this.element = opts.element;
this.bins = opts.bins;
this.color = "steelblue" //default color
// create the chart
this.draw();
}
Chart.prototype.draw = function() {
// define width, height and paddings
this.padding = 20;
this.width = this.element.offsetWidth - (2*this.padding);
this.height = (this.width / 2) - (2*this.padding);
// set up parent element and SVG
this.element.innerHTML = '';
var svg = d3.select(this.element).append('svg');
svg.attr('width', this.width + (2*this.padding));
svg.attr('height', this.height + (2*this.padding));
// we'll actually be appending to a <g> element
this.plot = svg.append('g')
.attr("transform", "translate(" + this.padding + "," + this.padding + ")");
// create the other stuff
this.createXScale();
this.generateHist(this.bins); //convert data to histogram layout
this.createYScale(); //make the y scale based on that hist data.
this.drawHist(); //actually draw the histogram
this.addAxes(); //draw the axes
this.setColor(this.color) //color the chart.
}
Chart.prototype.createXScale = function(){
// shorthand to save typing later
var w = this.width,
raw_data = this.data;
this.xScale = d3.scale.linear()
.domain(d3.extent(raw_data))
.range([0, w]);
}
Chart.prototype.generateHist = function(){
// Generate a histogram w/ uniformly-spaced bins.
this.hist_data = d3.layout.histogram()
.bins(this.xScale.ticks(this.bins))
(this.data);
}
Chart.prototype.createYScale = function(){
this.yScale = d3.scale.linear()
.domain([0, d3.max(this.hist_data, function(d) { return d.y; })])
.range([this.height, 0]);
}
Chart.prototype.addAxes = function(){
var h = this.height,
w = this.width;
var xAxis = d3.svg.axis()
.scale(this.xScale)
.orient("bottom");
this.plot.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", "translate( 0 ," + -4 + ")");
}
Chart.prototype.updateAxes = function(){
var h = this.height,
w = this.width;
var xAxis = d3.svg.axis()
.scale(this.xScale)
.orient("bottom");
this.plot.select(".x.axis").transition()
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", "translate( 0 ," + -4 + ")");
}
Chart.prototype.drawHist = function(){
//minumum observed data, needed for calculating bin width
this.min = d3.min(this.data)
var h = this.height,
w = this.width,
x = this.xScale,
y = this.yScale
hist_data = this.hist_data,
bin_width = x(this.min + hist_data[0].dx);
// A formatter for counts.
var formatCount = d3.format(",.0f");
var hist = this.plot.selectAll(".bar")
.data(this.hist_data)
hist.transition()
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; })
.each(function(){
d3.select(this).select("rect") //update the bars
.transition()
.attr("x", 1)
.attr("width", bin_width - 1)
.attr("height", function(d) { return h - y(d.y); })
// .attr("fill", this.color || "steelblue");
d3.select(this).select("text") //update the text
.transition()
.attr("x", bin_width / 2)
.attr("y", -14)
.text(function(d) { return d.y != 0 ? formatCount(d.y) : ""; });
});
hist.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; })
.each(function(){
d3.select(this).append("rect") //draw the bars
.attr("x", 1)
.attr("width", bin_width - 1)
.attr("height", function(d) { return h - y(d.y); })
// .attr("fill", this.color || "steelblue");
d3.select(this).append("text") //draw the text
.attr("dy", ".75em")
.attr("y", -14)
.attr("x", bin_width / 2)
.attr("text-anchor", "middle")
.attr("fill", "#fff;")
.text(function(d) { return formatCount(d.y); });
});
hist.exit()
.remove()
}
// the following are "public methods"
// which can be used by code outside of this file
Chart.prototype.setColor = function(newColor) {
this.plot.selectAll('rect')
.style('fill',newColor);
// store for use when redrawing
this.color = newColor;
}
Chart.prototype.setData = function(newData) {
this.data = newData;
this.createXScale();
this.generateHist(this.bins);
this.createYScale();
this.updateAxes();
this.drawHist();
this.setColor(this.color);
}
<!-- load in D3 and Chart constructor scripts -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="chart.js"></script>
<style>
/* a little bit of CSS to make the chart readable */
.axis path, .tick line {
fill: none;
stroke: #333;
}
</style>
<!-- here's the div our chart will be injected into -->
<div class="chart-container" style="max-width: 1000px;"></div>
<!-- these will be made useful in the script below -->
<button class="color" data-color="red">red bars</button>
<button class="color" data-color="green">green bars</button>
<button class="color" data-color="blue">blue bars</button>
<button class="data">change data</button>
<script>
// create new chart using Chart constructor
var chart = new Chart({
element: document.querySelector('.chart-container'),
data: d3.range(1000).map(d3.random.normal()),
bins: 20
});
// change line colour on click
d3.selectAll('button.color').on('click', function(){
var color = d3.select(this).text().split(' ')[0];
chart.setColor( color );
});
// change data on click to something randomly-generated
d3.selectAll('button.data').on('click', function(){
chart.setData(d3.range(1000).map(function(){return Math.random() * 50}));
});
// redraw chart on each resize
// in a real-world example, it might be worth ‘throttling’ this
// more info: http://sampsonblog.com/749/simple-throttle-function
d3.select(window).on('resize', function(){
chart.draw();
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment