Skip to content

Instantly share code, notes, and snippets.

@Saigesp
Last active October 22, 2018 04:06
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 Saigesp/f906ae2bab36f03a59c78e8d669aa68c to your computer and use it in GitHub Desktop.
Save Saigesp/f906ae2bab36f03a59c78e8d669aa68c to your computer and use it in GitHub Desktop.
D3v4 barchart with js classes
f906ae2bab36f03a59c78e8d669aa68c

Horizontal barchart

D3 implementation of horizontal barchar

See the demo and more charts from d3graphs repository

Features:

  • Object oriented approach
  • Responsive
  • Resizable

Requires:

  • D3 v4+

Default options:

{
    margin: {top: 40, right: 30, bottom: 50, left: 40},
    key: 'key',
    label: 'date',
    color: 'steelblue',
    greycolor: '#CCC',
    yscaleformat: '.0f',
    currentkey: false,
    title: false,
    source: false,
    mean: false,
    meanlabel: false,
}
year ton
1982 2650
1983 315
1984 1790
1985 3963
1986 9018
1987 6941
1988 7276
1989 3078
1990 1059
1991 7712
1992 4748
1993 5526
1994 8574
1995 15805
1996 14582
1997 26694
1998 35205
1999 45944
2000 78595
2001 78530
2002 45407
2003 54044
2004 69165
2005 61798
2006 63686
2007 76973
2008 47325
2009 55800
2010 44981
2011 60783
2012 47548
2013 47595
2014 40520
2015 32198
2016 23290
2017 16958
class BarChart{
constructor(selection, data, config = {}) {
let self = this;
this.selection = selection;
this.data = data;
// Graph configuration
this.cfg = {
margin: {top: 40, right: 30, bottom: 50, left: 40},
key: 'key',
label: 'date',
color: 'steelblue',
greycolor: '#CCC',
yscaleformat: '.0f',
currentkey: false,
title: false,
source: false,
mean: false,
meanlabel: false,
};
Object.keys(config).forEach(function(key) {
if(config[key] instanceof Object && config[key] instanceof Array === false){
Object.keys(config[key]).forEach(function(sk) {
self.cfg[key][sk] = config[key][sk];
});
} else self.cfg[key] = config[key];
});
this.cfg.width = parseInt(this.selection.node().offsetWidth) - this.cfg.margin.left - this.cfg.margin.right,
this.cfg.height = parseInt(this.selection.node().offsetHeight)- this.cfg.margin.top - this.cfg.margin.bottom;
this.xScale = d3.scaleBand().rangeRound([0, this.cfg.width]).padding(0.1);
this.yScale = d3.scaleLinear().rangeRound([0, self.cfg.height]);
window.addEventListener("resize", function(){self.resize()});
this.initGraph();
}
initGraph() {
var self = this;
this.xScale.domain(this.data.map(function(d) { return d[self.cfg.label]; }));
this.yScale.domain([d3.max(this.data, function(d){ return +d[self.cfg.key]}),0])
this.svg = this.selection.append('svg')
.attr("class", "chart barchart")
//.attr("viewBox", "0 0 "+(this.cfg.width + this.cfg.margin.left + this.cfg.margin.right)+" "+(this.cfg.height + this.cfg.margin.top + this.cfg.margin.bottom))
.attr("width", this.cfg.width + this.cfg.margin.left + this.cfg.margin.right)
.attr("height", this.cfg.height + this.cfg.margin.top + this.cfg.margin.bottom);
this.g = this.svg.append("g")
.attr("transform", "translate(" + (self.cfg.margin.left) + "," + (self.cfg.margin.top) + ")");
// TITLE
if(self.cfg.title){
this.title = this.svg.append('text')
.attr('class', 'title label')
.attr('text-anchor', 'middle')
.attr('transform', 'translate('+ (self.cfg.width/2) +',20)')
.text(self.cfg.title)
}
// SOURCE
if(self.cfg.source){
this.source = this.svg.append('text')
.attr('class', 'source label')
.attr('transform', 'translate('+ (self.cfg.margin.left) +','+(self.cfg.height + self.cfg.margin.top + self.cfg.margin.bottom - 5)+')')
.html(self.cfg.source)
}
// GRID
this.yGrid = this.g.append("g")
.attr("class", "grid grid--y")
.call(self.make_y_gridlines()
.tickSize(-self.cfg.width)
.ticks(3, self.cfg.yscaleformat));
// AXIS
this.xAxis = this.g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + this.cfg.height + ")")
.call(d3.axisBottom(self.xScale));
this.xAxis.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.6em")
.attr("transform", "rotate(-90)");
this.itemg = this.g.selectAll('.itemgroup')
.data(this.data)
.enter().append('g')
.attr('class', 'itemgroup')
.attr('transform', function(d, i){
return 'translate('+ self.xScale(d[self.cfg.label]) +',0)';
})
this.rects = this.itemg.append('rect')
.attr('x', 0)
.attr('y', function(d, i){
return self.yScale(+d[self.cfg.key]);
})
.attr('width', this.xScale.bandwidth())
.attr('height', function(d){
return self.cfg.height - self.yScale(+d[self.cfg.key]);
})
.attr('fill', function(d){
return !self.cfg.currentkey || d[self.cfg.label] == self.cfg.currentkey ? self.cfg.color : self.cfg.greycolor;
});
this.rects.append("title")
.text(function(d) { return d[self.cfg.key]});
if(this.cfg.mean){
this.mean = this.g.append('line')
.attr('class', 'axis axis--mean')
.attr('stroke', 'black')
.attr('stroke-width', 1)
.attr('x1', 0)
.attr('y1', this.yScale(this.cfg.mean))
.attr('x2', this.cfg.width)
.attr('y2', this.yScale(this.cfg.mean))
if(this.cfg.meanlabel){
this.meanlabel = this.g.append('text')
.attr('class', 'label label--mean')
.attr('text-anchor', 'end')
.attr('x', this.cfg.width)
.attr('y', this.yScale(this.cfg.mean) - 3)
.text(this.cfg.meanlabel)
}
}
}
// gridlines in x axis function
make_x_gridlines() {
return d3.axisBottom(this.xScale);
}
// gridlines in y axis function
make_y_gridlines() {
return d3.axisLeft(this.yScale);
}
resize(){
var self = this;
this.cfg.width = parseInt(this.selection.node().offsetWidth) - this.cfg.margin.left - this.cfg.margin.right;
this.cfg.height = parseInt(this.selection.node().offsetHeight)- this.cfg.margin.top - this.cfg.margin.bottom;
this.xScale.rangeRound([0, this.cfg.width]);
this.yScale.rangeRound([0, this.cfg.height]);
this.svg
.attr("viewBox", "0 0 "+(this.cfg.width + this.cfg.margin.left + this.cfg.margin.right)+" "+(this.cfg.height + this.cfg.margin.top + this.cfg.margin.bottom))
.attr("width", this.cfg.width + this.cfg.margin.left + this.cfg.margin.right)
.attr("height", this.cfg.height + this.cfg.margin.top + this.cfg.margin.bottom);
this.yGrid.call(self.make_y_gridlines()
.tickSize(-self.cfg.width)
.tickFormat(d3.format("d")));
this.xAxis.attr("transform", "translate(0," + this.cfg.height + ")")
.call(d3.axisBottom(self.xScale))
this.itemg.attr('transform', function(d, i){
return 'translate('+ self.xScale(d[self.cfg.label]) +',0)';
})
this.rects.attr('width', this.xScale.bandwidth())
.attr('y', function(d, i){
return self.yScale(+d[self.cfg.key]);
})
.attr('height', function(d){
return self.cfg.height - self.yScale(+d[self.cfg.key]);
})
if(this.cfg.mean){
this.mean.attr('y1', this.yScale(self.cfg.mean))
.attr('x2', self.cfg.width)
.attr('y2', self.yScale(self.cfg.mean))
if(this.cfg.meanlabel) this.meanlabel.attr('x', this.cfg.width).attr('y', this.yScale(this.cfg.mean) - 3)
}
if(self.cfg.title) this.title.attr('transform', 'translate('+ (self.cfg.width/2) +',20)')
if(self.cfg.source) this.source.attr('transform', 'translate('+ (self.cfg.margin.left) +','+(self.cfg.height + self.cfg.margin.top + self.cfg.margin.bottom - 5)+')')
}
}
<html>
<head>
<meta charset="utf-8">
</head>
<style>
.chart .grid line {
opacity: 0.1;
}
.chart .grid path {
fill: transparent;
stroke: transparent;
}
.label {
font-family: sans-serif;
font-size: 12px;
cursor: default;
}
.chart .source {
fill: #7a7a7a;
font-size: 10px;
}
.chart .source a {
text-decoration: underline;
}
.chart .title {
font-weight: 700;
}
/* BARCHART */
.chart .axis--mean {
shape-rendering: crispEdges;
}
</style>
<body>
<div id="exports" style="width: 100%; height: 360px;"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="d3.barchart.js"></script>
<script>
d3.csv('clementinesexport.csv', function(data) {
var grafico = new BarChart(d3.select('#exports'), data, {
'key': 'ton',
'label': 'year',
'mean': 28000,
'meanlabel': 'Media predefinida',
'color': '#DD8500',
'title': 'Exportaciones de clementinas a EEUU',
'source': 'Fuente: <a href="https://www.icex.es/icex/GetDocumento?dDocName=DOC2015504468&urlNoAcceso=/icex/es/registro/iniciar-sesion/index.html?urlDestino=http://www.icex.es/icex/es/navegacion-principal/todos-nuestros-servicios/informacion-de-mercados/sectores/agroalimentarios/documentos/DOC2015504468.html">Instituto Español de comercio Exterior</a>',
})
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment