I created this double bar chart using d3.js.
The blue chart bar grows upwards, uses a linear scale and the corresponding data is testData1
, the orange one is developed downwards, uses a sqrt scale and the corresponding data is testData2
.
testData1
and testData2
are:
var testData1 = [
{country: 'FR', year: '2017', perc: 'NaN'},
{country: 'FR', year: '2016', perc: '91'},
{country: 'FR', year: '2015', perc: '85'},
{country: 'FR', year: '2014', perc: '78'},
{country: 'FR', year: '2013', perc: '95'},
{country: 'FR', year: '2012', perc: '99'},
{country: 'FR', year: '2011', perc: '80'},
{country: 'FR', year: '2010', perc: '82'},
{country: 'FR', year: '2009', perc: '85'},
{country: 'FR', year: '2008', perc: '75'},
{country: 'FR', year: '2007', perc: '90'},
{country: 'FR', year: '2006', perc: '90'},
{country: 'FR', year: '2005', perc: '82'},
{country: 'FR', year: '2004', perc: '81'},
{country: 'FR', year: '2003', perc: 'NaN'},
{country: 'FR', year: '2002', perc: '78'},
{country: 'FR', year: '2001', perc: '75'},
{country: 'FR', year: '2000', perc: '50'},
{country: 'FR', year: '1999', perc: '95'},
{country: 'FR', year: '1998', perc: '93'},
{country: 'FR', year: '1997', perc: '94'},
{country: 'FR', year: '1996', perc: '90'},
{country: 'FR', year: '1995', perc: '85'},
{country: 'FR', year: '1994', perc: '80'},
{country: 'FR', year: '1993', perc: '77'},
{country: 'FR', year: '1992', perc: '91'},
{country: 'FR', year: '1991', perc: 'NaN'},
{country: 'FR', year: '1990', perc: '86'},
{country: 'DE', year: '2017', perc: 'NaN'}
];
var testData2 = [
{country: 'FR', year: '2017', rate: '5'},
{country: 'FR', year: '2016', rate: '1'},
{country: 'FR', year: '2015', rate: '200'},
{country: 'FR', year: '2014', rate: '350'},
{country: 'FR', year: '2013', rate: '100'},
{country: 'FR', year: '2012', rate: '200'},
{country: 'FR', year: '2011', rate: '2'},
{country: 'FR', year: '2010', rate: '15'},
{country: 'FR', year: '2009', rate: '25'},
{country: 'FR', year: '2008', rate: '150'},
{country: 'FR', year: '2007', rate: '5'},
{country: 'FR', year: '2006', rate: 'NaN'},
{country: 'FR', year: '2005', rate: '1'},
{country: 'FR', year: '2004', rate: '2'},
{country: 'FR', year: '2003', rate: '1'},
{country: 'FR', year: '2002', rate: '7'},
{country: 'FR', year: '2001', rate: '5'},
{country: 'FR', year: '2000', rate: '0.1'},
{country: 'FR', year: '1999', rate: '0'},
{country: 'FR', year: '1998', rate: ''},
{country: 'FR', year: '1997', rate: '813'},
{country: 'FR', year: '1996', rate: '222'},
{country: 'FR', year: '1995', rate: '181'},
{country: 'FR', year: '1994', rate: '11'},
{country: 'FR', year: '1993', rate: '2.3'},
{country: 'FR', year: '1992', rate: '1'},
{country: 'FR', year: '1991', rate: '0'},
{country: 'FR', year: '1990', rate: 'NaN'}
];
This is the entire code:
var years = [1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017];
console.log('testData1:', testData1);
console.log('testData2:', testData2);
/////////////////////////////////////////////////////////////////////////////
var widthSingleChart = 150;
var heightSingleChart = 50;
var margin = {top: 0, right: 0, bottom: 0, left: 0};
// coverage bars
var doublebarSvgCov = d3.select('#doublebar')
.append('svg')
.attr('class', 'doublebarSvgCov')
.attr('width', widthSingleChart)
.attr('height', heightSingleChart);
// cases bars
var doublebarSvgCas = d3.select('#doublebar')
.append('svg')
.attr('class', 'doublebarSvgCas')
.attr('width', widthSingleChart)
.attr('height', heightSingleChart);
var width = doublebarSvgCov.attr('width') - margin.left - margin.right;
var height = doublebarSvgCov.attr('height') - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.domain(years);
var y1 = d3.scaleLinear()
.rangeRound([height, 0])
.domain([0, 100]);
var y2 = d3.scaleSqrt()
.rangeRound([height, 0])
.domain([813, 0.1]); // max value 812.05 but domain is [0, 100000]
// define the axes
var xAxis = d3.axisBottom(x).tickSize(0, 0);
var yAxis1 = d3.axisLeft(y1);
var yAxis2 = d3.axisLeft(y2);
// create g element inside svg
var doublebarSvgGCov = doublebarSvgCov.append('g').attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
var doublebarSvgGCas = doublebarSvgCas.append('g').attr('transform', 'translate(' + margin.left + ', ' + margin.top + ')');
////////////////////////////////////////////////////////////////////////
// Tooltip.
////////////////////////////////////////////////////////////////////////
var svgTip = doublebarSvgCov.append('svg').attr('id', 'tooltip');
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-5, 0])
.html(function(d) {
var thisPerc = testData1.find(function(e) {
return e.year === d.year;
}).perc;
var thisRate = testData2.find(function(e) {
return e.year === d.year;
}).rate;
var st = '[rate=\'' + thisRate + '\']';
d3.select(st).on('mouseover', function(d) {
d3.select(this).attr('fill', 'yellow');
});
return '<div><span>Country:</span> <span style=\'color:white\'>' + d.country + '</span></div>' +
'<div><span>Perc:</span> <span style=\'color:white\'>' + thisPerc + '%</span></div>' +
'<div><span>Rate:</span> <span style=\'color:white\'>' + thisRate + '</span></div>';
});
svgTip.call(tip);
////////////////////////////////////////////////////////////////////////
// Draw a single double bar
////////////////////////////////////////////////////////////////////////
makeDoublebar1();
function makeDoublebar1() {
// create x axis
doublebarSvgGCov.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis)
.selectAll('text')
.style('text-anchor', 'end')
.attr('dx', '-.8em')
.attr('dy', '.15em')
.attr('transform', 'rotate(-65)');
// create y axis
doublebarSvgGCov.append('g')
.attr('class', 'y axis')
.call(yAxis1)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'end');
// create bar rect
doublebarSvgGCov.selectAll('.bar')
.data(testData1)
.enter().append('rect')
.attr('year', function(d) {
return d.year;
})
.attr('fill', 'steelblue')
.attr('class', 'bar')
.attr('x', function(d) {
return x(d.year);
})
.attr('y', function(d) {
if(isNaN(d.perc)) {
d.perc = 0;
}
return y1(d.perc);
})
.attr('width', x.bandwidth())
.attr('height', function(d) {
if(isNaN(d.perc)) {
d.perc = 0;
}
return height - y1(d.perc);
})
.on('mouseover', function(d) {
// change color to this bar (coverage)
d3.select(this).attr('fill', 'darkblue');
// change color to corresponding cases bar (cases)
var selCas = doublebarSvgGCas.select('rect[year=\'' + d.year + '\']');
selCas.attr('fill', 'orange');
// show tooltip
tip.show(d);
})
.on('mouseout', function(d) {
// change color to this bar (coverage)
d3.select(this).attr('fill', 'steelblue');
// change color to corresponding cases bar (cases)
var selCas = doublebarSvgGCas.select('rect[year=\'' + d.year + '\']');
selCas.attr('fill', 'tomato');
// hide tooltip
tip.hide(d);
});
}
////////////////////////////////////////////////////////////////////////
// Draw a single double bar
////////////////////////////////////////////////////////////////////////
makeDoublebar2();
function makeDoublebar2() {
// create x axis
doublebarSvgGCas.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, 0)')
.call(xAxis)
.selectAll('text')
.style('text-anchor', 'end')
.attr('dx', '-.8em')
.attr('dy', '.15em')
.attr('transform', 'rotate(-65)');
// create y axis
doublebarSvgGCas.append('g')
.attr('class', 'y axis')
.call(yAxis2)
.append('text')
.style('text-anchor', 'end');
// create bar rect
doublebarSvgGCas.selectAll('.bar')
.data(testData2)
.enter().append('rect')
.attr('year', function(d) {
return d.year;
})
.attr('fill', 'tomato')
.attr('class', 'bar')
.attr('x', function(d) { // left start point
return x(d.year);
})
.attr('y', function(d) { // top start point
if(isNaN(d.rate)) {
d.rate = 0;
}
return 0;
})
.attr('width', x.bandwidth())
.attr('height', function(d) {
if(isNaN(d.rate)) {
d.perc = 0;
}
return y2(d.rate);
})
.on('mouseover', function(d) {
// change color to this bar (cases)
d3.select(this).attr('fill', 'orange');
// change color to corresponding coverage bar (coverage)
var selCov = doublebarSvgGCov.select('rect[year=\'' + d.year + '\']');
selCov.attr('fill', 'darkblue');
// show tooltip
tip.show(d);
})
.on('mouseout', function(d) {
// change color to this bar (cases)
d3.select(this).attr('fill', 'tomato');
// change color to corresponding coverage bar (coverage)
var selCov = doublebarSvgGCov.select('rect[year=\'' + d.year + '\']');
selCov.attr('fill', 'steelblue');
// hide tooltip
tip.hide(d);
});
}
The code works but it is certainly improvable. Unfortunately I use very little d3.js and I still have to find a way to better organize the code.
I would like to find a smarter way to create this type of chart.