Using D3.js to create a double bar chart

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
3
down vote

favorite












I created this double bar chart using d3.js.



enter image description here



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.



CODE HERE.







share|improve this question



























    up vote
    3
    down vote

    favorite












    I created this double bar chart using d3.js.



    enter image description here



    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.



    CODE HERE.







    share|improve this question























      up vote
      3
      down vote

      favorite









      up vote
      3
      down vote

      favorite











      I created this double bar chart using d3.js.



      enter image description here



      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.



      CODE HERE.







      share|improve this question













      I created this double bar chart using d3.js.



      enter image description here



      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.



      CODE HERE.









      share|improve this question












      share|improve this question




      share|improve this question








      edited May 17 at 5:31









      200_success

      123k14143399




      123k14143399









      asked May 15 at 4:02









      Buster

      434




      434




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          3
          down vote



          accepted










          Your code is certainly improvable, and several changes should be done. The main problem, that immediately comes to attention, is the repetition of the code.



          However, the problem here is not (only) the DRY issue itself... the problem is more fundamental, regarding D3 best practices: since you have two data arrays (which are almost the same), you're binding different data to different selections. Because of that, you're bending over backwards to do some simple tasks, like creating the tooltip (I'm the author of the answer to your previous question at S.O. regarding this).



          So, let's get rid of those repetitions not just because they are repetitions, but for having a single data set which we can bind to a single enter selection.



          Here are the steps:




          1. First, we'll merge the datasets:



            testData2.forEach(function(d)
            var found = testData1.find(function(e)
            return e.country === d.country && e.year === d.year
            );
            found.rate = +d.rate;
            );


          Now we have just one dataset (testData1) with all the relevant information, which we'll use in our enter selection further down. If you can guarantee that both arrays have the same years and are always in the same order, the function above can be substantially simplified.




          1. You don't need to hardcode the years, you can get them from the data:



            var years = [...new Set(testData1.map(function(d)return +d.year))].sort()



          2. Let's create a single SVG, you don't need two of them:



            var svg = d3.select('#doublebar')
            .append('svg')
            .attr('width', widthSingleChart)
            .attr('height', heightSingleChart);



          3. Change the scales accordingly, and do not hardcode the domains. Instead, use d3.max and d3.min:



            .domain([d3.max(testData1, function(d)return d.rate), 0.1]);


          Since now we have just 1 SVG, pay attention to the ranges.




          1. Now you can have a regular tooltip, without that awkward code:



            .html(function(d) 
            return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
            '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
            '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
            );



          2. Put the x axis in the middle of the SVG:



            .attr('transform', 'translate(0, ' + (height/2) + ')') 


          Here comes the important part. We'll create everything in a single enter selection, getting rid of makingDoublebar1 and makingDoublebar2.




          1. Let's create the groups to which you're binding the data. Remember, always name your selections:



            var barsGroups = svg.selectAll(null)
            .data(testData1)
            .enter()
            .append('g')
            .attr('transform', function(d)
            return 'translate(' + x(d.year) + ',0)'
            );


          As you can see, I'm selecting null. I'm doing this because I suppose you don't have an update selection here. To read more about selecting null, see my explanation in this answer at S.O.




          1. Once those groups are created, let's append the bars, two to each group:



            barsGroups.append('rect')
            .attr('fill', 'steelblue')
            .attr('class', 'bar')
            .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 / 2 - y1(d.perc);
            );

            barsGroups.append('rect')
            .attr('fill', 'tomato')
            .attr('class', 'bar')
            .attr('y', function(d) // top start point
            if (isNaN(d.rate))
            d.rate = 0;

            return height / 2;
            )
            .attr('width', x.bandwidth())
            .attr('height', function(d)
            if (isNaN(d.rate))
            d.perc = 0;

            console.log(d)
            return y2(d.rate);
            );



          2. Finally, set the events to the groups:



            barsGroups.on('mouseover', function(d) 
            tip.show(d);
            )
            .on('mouseout', function(d)
            tip.hide(d);
            );


          You'll have to re-select the bars, but that's not too complicated.



          Also, mind some minor details: there is no attribute year for a SVG rectangle. Also, your data contain strings, not numbers. Change that (you can change the data itself or you can use an unary plus, like I used to merge the rates).



          Here is the refactored code:






          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'

          /*,
          country: 'DE', year: '2016', perc: '91',
          country: 'DE', year: '2015', perc: '85',
          country: 'DE', year: '2014', perc: '78',
          country: 'DE', year: '2013', perc: '95',
          country: 'DE', year: '2012', perc: '99',
          country: 'DE', year: '2011', perc: '80',
          country: 'DE', year: '2010', perc: '82',
          country: 'DE', year: '2009', perc: '85',
          country: 'DE', year: '2008', perc: '75',
          country: 'DE', year: '2007', perc: '90',
          country: 'DE', year: '2006', perc: '90',
          country: 'DE', year: '2005', perc: '82',
          country: 'DE', year: '2004', perc: '81',
          country: 'DE', year: '2003', perc: 'NaN',
          country: 'DE', year: '2002', perc: '78',
          country: 'DE', year: '2001', perc: '75',
          country: 'DE', year: '2000', perc: '50',
          country: 'DE', year: '1999', perc: '95',
          country: 'DE', year: '1998', perc: '93',
          country: 'DE', year: '1997', perc: '94',
          country: 'DE', year: '1996', perc: '90',
          country: 'DE', year: '1995', perc: '85',
          country: 'DE', year: '1994', perc: '80',
          country: 'DE', year: '1993', perc: '77',
          country: 'DE', year: '1992', perc: '91',
          country: 'DE', year: '1991', perc: 'NaN',
          country: 'DE', year: '1990', perc: '86'*/
          ];
          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'

          /*,
          country: 'DE', year: '2017', rate: '5',
          country: 'DE', year: '2016', rate: '1',
          country: 'DE', year: '2015', rate: '200',
          country: 'DE', year: '2014', rate: '350',
          country: 'DE', year: '2013', rate: '100',
          country: 'DE', year: '2012', rate: '200',
          country: 'DE', year: '2011', rate: '2',
          country: 'DE', year: '2010', rate: '15',
          country: 'DE', year: '2009', rate: '25',
          country: 'DE', year: '2008', rate: '150',
          country: 'DE', year: '2007', rate: '5',
          country: 'DE', year: '2006', rate: 'NaN',
          country: 'DE', year: '2005', rate: '1',
          country: 'DE', year: '2004', rate: '2',
          country: 'DE', year: '2003', rate: '1',
          country: 'DE', year: '2002', rate: '7',
          country: 'DE', year: '2001', rate: '5',
          country: 'DE', year: '2000', rate: '0.1',
          country: 'DE', year: '1999', rate: '0',
          country: 'DE', year: '1998', rate: '',
          country: 'DE', year: '1997', rate: '813',
          country: 'DE', year: '1996', rate: '222',
          country: 'DE', year: '1995', rate: '181',
          country: 'DE', year: '1994', rate: '11',
          country: 'DE', year: '1993', rate: '2.3',
          country: 'DE', year: '1992', rate: '1',
          country: 'DE', year: '1991', rate: '0',
          country: 'DE', year: '1990', rate: 'NaN'*/
          ];

          testData2.forEach(function(d)
          var found = testData1.find(function(e)
          return e.country === d.country && e.year === d.year
          );
          found.rate = +d.rate;
          );


          var years = [...new Set(testData1.map(function(d)
          return +d.year
          ))].sort()

          /////////////////////////////////////////////////////////////////////////////

          var widthSingleChart = 150;
          var heightSingleChart = 100;
          var margin =
          top: 0,
          right: 0,
          bottom: 0,
          left: 10
          ;

          var svg = d3.select('#doublebar')
          .append('svg')
          .attr('width', widthSingleChart)
          .attr('height', heightSingleChart);

          var width = svg.attr('width') - margin.left - margin.right;
          var height = svg.attr('height') - margin.top - margin.bottom;

          svg = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

          var x = d3.scaleBand()
          .rangeRound([0, width])
          .padding(0.15)
          .domain(years);

          var y1 = d3.scaleLinear()
          .rangeRound([height / 2, 0])
          .domain([0, 100]);

          var y2 = d3.scaleSqrt()
          .rangeRound([height / 2, 0])
          .domain([d3.max(testData1, function(d)
          return d.rate
          ), 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);


          ////////////////////////////////////////////////////////////////////////
          // Tooltip.
          ////////////////////////////////////////////////////////////////////////

          var tip = d3.tip()
          .attr('class', 'd3-tip')
          .offset([-5, 0])
          .html(function(d)
          return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
          '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
          '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
          );

          svg.call(tip);

          ////////////////////////////////////////////////////////////////////////
          // Draw a single double bar
          ////////////////////////////////////////////////////////////////////////

          // create x axis
          svg.append('g')
          .attr('class', 'x axis')
          .attr('transform', 'translate(0, ' + (height / 2) + ')')
          .call(xAxis)
          .selectAll('text')
          .style('text-anchor', 'end')
          .attr('dx', '-.8em')
          .attr('dy', '.15em')
          .attr('transform', 'rotate(-65)');

          // create y axis
          svg.append('g')
          .attr('class', 'y axis')
          .attr('transform', 'translate(' + margin.left + ',0)')
          .call(yAxis1)
          .append('text')
          .attr('transform', 'rotate(-90)')
          .attr('y', 6)
          .attr('dy', '.71em')
          .style('text-anchor', 'end');

          svg.append('g')
          .attr('class', 'y axis')
          .attr('transform', 'translate(' + margin.left + ',' + (height/2) + ')')
          .call(yAxis2)
          .append('text')
          .style('text-anchor', 'end');

          var barsGroups = svg.selectAll(null)
          .data(testData1)
          .enter()
          .append('g')
          .attr('transform', function(d)
          return 'translate(' + x(d.year) + ',0)'
          );

          barsGroups.on('mouseover', function(d)
          tip.show(d);
          )
          .on('mouseout', function(d)
          tip.hide(d);
          );

          // create bar rect
          var percRect = barsGroups.append('rect')
          .attr('fill', 'steelblue')
          .attr('class', 'bar')
          .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 / 2 - y1(d.perc);
          );

          var rateRect = barsGroups.append('rect')
          .attr('fill', 'tomato')
          .attr('class', 'bar')
          .attr('y', function(d) // top start point
          if (isNaN(d.rate))
          d.rate = 0;

          return height / 2;
          )
          .attr('width', x.bandwidth())
          .attr('height', function(d)
          if (isNaN(d.rate))
          d.perc = 0;

          return y2(d.rate);
          );

          .x.axis.tick line, text 
          opacity: 0;


          #doublebar
          width: 150px; /* stesso di widthSingleChart */
          background: whitesmoke;


          /**
          * Tooltip.
          */
          .d3-tip
          line-height: 1;
          font: 10px sans-serif;
          padding: 12px;
          background: rgba(0, 0, 0, 0.8);
          color: rgb(185, 185, 185);
          border-radius: 2px;


          /* Creates a small triangle extender for the tooltip */
          .d3-tip:after
          box-sizing: border-box;
          display: inline;
          font-size: 10px;
          width: 100%;
          line-height: 0.9;
          color: rgba(0, 0, 0, 0.8);
          content: '25BC';
          position: absolute;
          text-align: center;


          /* Style northward tooltips differently */
          .d3-tip.n:after
          margin: -1px 0 0 0;
          top: 100%;
          left: 0;

          <script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
          <script src='https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js'></script>
          <div style="height:50px;"></div>
          <div id='doublebar'></div>





          It has 260 lines compared to the original 330. But reducing the number of lines is not the most important thing here: the most important thing is having just 1 dataset, and just 1 enter selection based on that dataset.






          share|improve this answer























            Your Answer




            StackExchange.ifUsing("editor", function ()
            return StackExchange.using("mathjaxEditing", function ()
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            );
            );
            , "mathjax-editing");

            StackExchange.ifUsing("editor", function ()
            StackExchange.using("externalEditor", function ()
            StackExchange.using("snippets", function ()
            StackExchange.snippets.init();
            );
            );
            , "code-snippets");

            StackExchange.ready(function()
            var channelOptions =
            tags: "".split(" "),
            id: "196"
            ;
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function()
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled)
            StackExchange.using("snippets", function()
            createEditor();
            );

            else
            createEditor();

            );

            function createEditor()
            StackExchange.prepareEditor(
            heartbeatType: 'answer',
            convertImagesToLinks: false,
            noModals: false,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            );



            );








             

            draft saved


            draft discarded


















            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194419%2fusing-d3-js-to-create-a-double-bar-chart%23new-answer', 'question_page');

            );

            Post as a guest






























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes








            up vote
            3
            down vote



            accepted










            Your code is certainly improvable, and several changes should be done. The main problem, that immediately comes to attention, is the repetition of the code.



            However, the problem here is not (only) the DRY issue itself... the problem is more fundamental, regarding D3 best practices: since you have two data arrays (which are almost the same), you're binding different data to different selections. Because of that, you're bending over backwards to do some simple tasks, like creating the tooltip (I'm the author of the answer to your previous question at S.O. regarding this).



            So, let's get rid of those repetitions not just because they are repetitions, but for having a single data set which we can bind to a single enter selection.



            Here are the steps:




            1. First, we'll merge the datasets:



              testData2.forEach(function(d)
              var found = testData1.find(function(e)
              return e.country === d.country && e.year === d.year
              );
              found.rate = +d.rate;
              );


            Now we have just one dataset (testData1) with all the relevant information, which we'll use in our enter selection further down. If you can guarantee that both arrays have the same years and are always in the same order, the function above can be substantially simplified.




            1. You don't need to hardcode the years, you can get them from the data:



              var years = [...new Set(testData1.map(function(d)return +d.year))].sort()



            2. Let's create a single SVG, you don't need two of them:



              var svg = d3.select('#doublebar')
              .append('svg')
              .attr('width', widthSingleChart)
              .attr('height', heightSingleChart);



            3. Change the scales accordingly, and do not hardcode the domains. Instead, use d3.max and d3.min:



              .domain([d3.max(testData1, function(d)return d.rate), 0.1]);


            Since now we have just 1 SVG, pay attention to the ranges.




            1. Now you can have a regular tooltip, without that awkward code:



              .html(function(d) 
              return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
              '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
              '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
              );



            2. Put the x axis in the middle of the SVG:



              .attr('transform', 'translate(0, ' + (height/2) + ')') 


            Here comes the important part. We'll create everything in a single enter selection, getting rid of makingDoublebar1 and makingDoublebar2.




            1. Let's create the groups to which you're binding the data. Remember, always name your selections:



              var barsGroups = svg.selectAll(null)
              .data(testData1)
              .enter()
              .append('g')
              .attr('transform', function(d)
              return 'translate(' + x(d.year) + ',0)'
              );


            As you can see, I'm selecting null. I'm doing this because I suppose you don't have an update selection here. To read more about selecting null, see my explanation in this answer at S.O.




            1. Once those groups are created, let's append the bars, two to each group:



              barsGroups.append('rect')
              .attr('fill', 'steelblue')
              .attr('class', 'bar')
              .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 / 2 - y1(d.perc);
              );

              barsGroups.append('rect')
              .attr('fill', 'tomato')
              .attr('class', 'bar')
              .attr('y', function(d) // top start point
              if (isNaN(d.rate))
              d.rate = 0;

              return height / 2;
              )
              .attr('width', x.bandwidth())
              .attr('height', function(d)
              if (isNaN(d.rate))
              d.perc = 0;

              console.log(d)
              return y2(d.rate);
              );



            2. Finally, set the events to the groups:



              barsGroups.on('mouseover', function(d) 
              tip.show(d);
              )
              .on('mouseout', function(d)
              tip.hide(d);
              );


            You'll have to re-select the bars, but that's not too complicated.



            Also, mind some minor details: there is no attribute year for a SVG rectangle. Also, your data contain strings, not numbers. Change that (you can change the data itself or you can use an unary plus, like I used to merge the rates).



            Here is the refactored code:






            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'

            /*,
            country: 'DE', year: '2016', perc: '91',
            country: 'DE', year: '2015', perc: '85',
            country: 'DE', year: '2014', perc: '78',
            country: 'DE', year: '2013', perc: '95',
            country: 'DE', year: '2012', perc: '99',
            country: 'DE', year: '2011', perc: '80',
            country: 'DE', year: '2010', perc: '82',
            country: 'DE', year: '2009', perc: '85',
            country: 'DE', year: '2008', perc: '75',
            country: 'DE', year: '2007', perc: '90',
            country: 'DE', year: '2006', perc: '90',
            country: 'DE', year: '2005', perc: '82',
            country: 'DE', year: '2004', perc: '81',
            country: 'DE', year: '2003', perc: 'NaN',
            country: 'DE', year: '2002', perc: '78',
            country: 'DE', year: '2001', perc: '75',
            country: 'DE', year: '2000', perc: '50',
            country: 'DE', year: '1999', perc: '95',
            country: 'DE', year: '1998', perc: '93',
            country: 'DE', year: '1997', perc: '94',
            country: 'DE', year: '1996', perc: '90',
            country: 'DE', year: '1995', perc: '85',
            country: 'DE', year: '1994', perc: '80',
            country: 'DE', year: '1993', perc: '77',
            country: 'DE', year: '1992', perc: '91',
            country: 'DE', year: '1991', perc: 'NaN',
            country: 'DE', year: '1990', perc: '86'*/
            ];
            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'

            /*,
            country: 'DE', year: '2017', rate: '5',
            country: 'DE', year: '2016', rate: '1',
            country: 'DE', year: '2015', rate: '200',
            country: 'DE', year: '2014', rate: '350',
            country: 'DE', year: '2013', rate: '100',
            country: 'DE', year: '2012', rate: '200',
            country: 'DE', year: '2011', rate: '2',
            country: 'DE', year: '2010', rate: '15',
            country: 'DE', year: '2009', rate: '25',
            country: 'DE', year: '2008', rate: '150',
            country: 'DE', year: '2007', rate: '5',
            country: 'DE', year: '2006', rate: 'NaN',
            country: 'DE', year: '2005', rate: '1',
            country: 'DE', year: '2004', rate: '2',
            country: 'DE', year: '2003', rate: '1',
            country: 'DE', year: '2002', rate: '7',
            country: 'DE', year: '2001', rate: '5',
            country: 'DE', year: '2000', rate: '0.1',
            country: 'DE', year: '1999', rate: '0',
            country: 'DE', year: '1998', rate: '',
            country: 'DE', year: '1997', rate: '813',
            country: 'DE', year: '1996', rate: '222',
            country: 'DE', year: '1995', rate: '181',
            country: 'DE', year: '1994', rate: '11',
            country: 'DE', year: '1993', rate: '2.3',
            country: 'DE', year: '1992', rate: '1',
            country: 'DE', year: '1991', rate: '0',
            country: 'DE', year: '1990', rate: 'NaN'*/
            ];

            testData2.forEach(function(d)
            var found = testData1.find(function(e)
            return e.country === d.country && e.year === d.year
            );
            found.rate = +d.rate;
            );


            var years = [...new Set(testData1.map(function(d)
            return +d.year
            ))].sort()

            /////////////////////////////////////////////////////////////////////////////

            var widthSingleChart = 150;
            var heightSingleChart = 100;
            var margin =
            top: 0,
            right: 0,
            bottom: 0,
            left: 10
            ;

            var svg = d3.select('#doublebar')
            .append('svg')
            .attr('width', widthSingleChart)
            .attr('height', heightSingleChart);

            var width = svg.attr('width') - margin.left - margin.right;
            var height = svg.attr('height') - margin.top - margin.bottom;

            svg = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

            var x = d3.scaleBand()
            .rangeRound([0, width])
            .padding(0.15)
            .domain(years);

            var y1 = d3.scaleLinear()
            .rangeRound([height / 2, 0])
            .domain([0, 100]);

            var y2 = d3.scaleSqrt()
            .rangeRound([height / 2, 0])
            .domain([d3.max(testData1, function(d)
            return d.rate
            ), 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);


            ////////////////////////////////////////////////////////////////////////
            // Tooltip.
            ////////////////////////////////////////////////////////////////////////

            var tip = d3.tip()
            .attr('class', 'd3-tip')
            .offset([-5, 0])
            .html(function(d)
            return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
            '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
            '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
            );

            svg.call(tip);

            ////////////////////////////////////////////////////////////////////////
            // Draw a single double bar
            ////////////////////////////////////////////////////////////////////////

            // create x axis
            svg.append('g')
            .attr('class', 'x axis')
            .attr('transform', 'translate(0, ' + (height / 2) + ')')
            .call(xAxis)
            .selectAll('text')
            .style('text-anchor', 'end')
            .attr('dx', '-.8em')
            .attr('dy', '.15em')
            .attr('transform', 'rotate(-65)');

            // create y axis
            svg.append('g')
            .attr('class', 'y axis')
            .attr('transform', 'translate(' + margin.left + ',0)')
            .call(yAxis1)
            .append('text')
            .attr('transform', 'rotate(-90)')
            .attr('y', 6)
            .attr('dy', '.71em')
            .style('text-anchor', 'end');

            svg.append('g')
            .attr('class', 'y axis')
            .attr('transform', 'translate(' + margin.left + ',' + (height/2) + ')')
            .call(yAxis2)
            .append('text')
            .style('text-anchor', 'end');

            var barsGroups = svg.selectAll(null)
            .data(testData1)
            .enter()
            .append('g')
            .attr('transform', function(d)
            return 'translate(' + x(d.year) + ',0)'
            );

            barsGroups.on('mouseover', function(d)
            tip.show(d);
            )
            .on('mouseout', function(d)
            tip.hide(d);
            );

            // create bar rect
            var percRect = barsGroups.append('rect')
            .attr('fill', 'steelblue')
            .attr('class', 'bar')
            .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 / 2 - y1(d.perc);
            );

            var rateRect = barsGroups.append('rect')
            .attr('fill', 'tomato')
            .attr('class', 'bar')
            .attr('y', function(d) // top start point
            if (isNaN(d.rate))
            d.rate = 0;

            return height / 2;
            )
            .attr('width', x.bandwidth())
            .attr('height', function(d)
            if (isNaN(d.rate))
            d.perc = 0;

            return y2(d.rate);
            );

            .x.axis.tick line, text 
            opacity: 0;


            #doublebar
            width: 150px; /* stesso di widthSingleChart */
            background: whitesmoke;


            /**
            * Tooltip.
            */
            .d3-tip
            line-height: 1;
            font: 10px sans-serif;
            padding: 12px;
            background: rgba(0, 0, 0, 0.8);
            color: rgb(185, 185, 185);
            border-radius: 2px;


            /* Creates a small triangle extender for the tooltip */
            .d3-tip:after
            box-sizing: border-box;
            display: inline;
            font-size: 10px;
            width: 100%;
            line-height: 0.9;
            color: rgba(0, 0, 0, 0.8);
            content: '25BC';
            position: absolute;
            text-align: center;


            /* Style northward tooltips differently */
            .d3-tip.n:after
            margin: -1px 0 0 0;
            top: 100%;
            left: 0;

            <script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
            <script src='https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js'></script>
            <div style="height:50px;"></div>
            <div id='doublebar'></div>





            It has 260 lines compared to the original 330. But reducing the number of lines is not the most important thing here: the most important thing is having just 1 dataset, and just 1 enter selection based on that dataset.






            share|improve this answer



























              up vote
              3
              down vote



              accepted










              Your code is certainly improvable, and several changes should be done. The main problem, that immediately comes to attention, is the repetition of the code.



              However, the problem here is not (only) the DRY issue itself... the problem is more fundamental, regarding D3 best practices: since you have two data arrays (which are almost the same), you're binding different data to different selections. Because of that, you're bending over backwards to do some simple tasks, like creating the tooltip (I'm the author of the answer to your previous question at S.O. regarding this).



              So, let's get rid of those repetitions not just because they are repetitions, but for having a single data set which we can bind to a single enter selection.



              Here are the steps:




              1. First, we'll merge the datasets:



                testData2.forEach(function(d)
                var found = testData1.find(function(e)
                return e.country === d.country && e.year === d.year
                );
                found.rate = +d.rate;
                );


              Now we have just one dataset (testData1) with all the relevant information, which we'll use in our enter selection further down. If you can guarantee that both arrays have the same years and are always in the same order, the function above can be substantially simplified.




              1. You don't need to hardcode the years, you can get them from the data:



                var years = [...new Set(testData1.map(function(d)return +d.year))].sort()



              2. Let's create a single SVG, you don't need two of them:



                var svg = d3.select('#doublebar')
                .append('svg')
                .attr('width', widthSingleChart)
                .attr('height', heightSingleChart);



              3. Change the scales accordingly, and do not hardcode the domains. Instead, use d3.max and d3.min:



                .domain([d3.max(testData1, function(d)return d.rate), 0.1]);


              Since now we have just 1 SVG, pay attention to the ranges.




              1. Now you can have a regular tooltip, without that awkward code:



                .html(function(d) 
                return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
                '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
                '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
                );



              2. Put the x axis in the middle of the SVG:



                .attr('transform', 'translate(0, ' + (height/2) + ')') 


              Here comes the important part. We'll create everything in a single enter selection, getting rid of makingDoublebar1 and makingDoublebar2.




              1. Let's create the groups to which you're binding the data. Remember, always name your selections:



                var barsGroups = svg.selectAll(null)
                .data(testData1)
                .enter()
                .append('g')
                .attr('transform', function(d)
                return 'translate(' + x(d.year) + ',0)'
                );


              As you can see, I'm selecting null. I'm doing this because I suppose you don't have an update selection here. To read more about selecting null, see my explanation in this answer at S.O.




              1. Once those groups are created, let's append the bars, two to each group:



                barsGroups.append('rect')
                .attr('fill', 'steelblue')
                .attr('class', 'bar')
                .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 / 2 - y1(d.perc);
                );

                barsGroups.append('rect')
                .attr('fill', 'tomato')
                .attr('class', 'bar')
                .attr('y', function(d) // top start point
                if (isNaN(d.rate))
                d.rate = 0;

                return height / 2;
                )
                .attr('width', x.bandwidth())
                .attr('height', function(d)
                if (isNaN(d.rate))
                d.perc = 0;

                console.log(d)
                return y2(d.rate);
                );



              2. Finally, set the events to the groups:



                barsGroups.on('mouseover', function(d) 
                tip.show(d);
                )
                .on('mouseout', function(d)
                tip.hide(d);
                );


              You'll have to re-select the bars, but that's not too complicated.



              Also, mind some minor details: there is no attribute year for a SVG rectangle. Also, your data contain strings, not numbers. Change that (you can change the data itself or you can use an unary plus, like I used to merge the rates).



              Here is the refactored code:






              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'

              /*,
              country: 'DE', year: '2016', perc: '91',
              country: 'DE', year: '2015', perc: '85',
              country: 'DE', year: '2014', perc: '78',
              country: 'DE', year: '2013', perc: '95',
              country: 'DE', year: '2012', perc: '99',
              country: 'DE', year: '2011', perc: '80',
              country: 'DE', year: '2010', perc: '82',
              country: 'DE', year: '2009', perc: '85',
              country: 'DE', year: '2008', perc: '75',
              country: 'DE', year: '2007', perc: '90',
              country: 'DE', year: '2006', perc: '90',
              country: 'DE', year: '2005', perc: '82',
              country: 'DE', year: '2004', perc: '81',
              country: 'DE', year: '2003', perc: 'NaN',
              country: 'DE', year: '2002', perc: '78',
              country: 'DE', year: '2001', perc: '75',
              country: 'DE', year: '2000', perc: '50',
              country: 'DE', year: '1999', perc: '95',
              country: 'DE', year: '1998', perc: '93',
              country: 'DE', year: '1997', perc: '94',
              country: 'DE', year: '1996', perc: '90',
              country: 'DE', year: '1995', perc: '85',
              country: 'DE', year: '1994', perc: '80',
              country: 'DE', year: '1993', perc: '77',
              country: 'DE', year: '1992', perc: '91',
              country: 'DE', year: '1991', perc: 'NaN',
              country: 'DE', year: '1990', perc: '86'*/
              ];
              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'

              /*,
              country: 'DE', year: '2017', rate: '5',
              country: 'DE', year: '2016', rate: '1',
              country: 'DE', year: '2015', rate: '200',
              country: 'DE', year: '2014', rate: '350',
              country: 'DE', year: '2013', rate: '100',
              country: 'DE', year: '2012', rate: '200',
              country: 'DE', year: '2011', rate: '2',
              country: 'DE', year: '2010', rate: '15',
              country: 'DE', year: '2009', rate: '25',
              country: 'DE', year: '2008', rate: '150',
              country: 'DE', year: '2007', rate: '5',
              country: 'DE', year: '2006', rate: 'NaN',
              country: 'DE', year: '2005', rate: '1',
              country: 'DE', year: '2004', rate: '2',
              country: 'DE', year: '2003', rate: '1',
              country: 'DE', year: '2002', rate: '7',
              country: 'DE', year: '2001', rate: '5',
              country: 'DE', year: '2000', rate: '0.1',
              country: 'DE', year: '1999', rate: '0',
              country: 'DE', year: '1998', rate: '',
              country: 'DE', year: '1997', rate: '813',
              country: 'DE', year: '1996', rate: '222',
              country: 'DE', year: '1995', rate: '181',
              country: 'DE', year: '1994', rate: '11',
              country: 'DE', year: '1993', rate: '2.3',
              country: 'DE', year: '1992', rate: '1',
              country: 'DE', year: '1991', rate: '0',
              country: 'DE', year: '1990', rate: 'NaN'*/
              ];

              testData2.forEach(function(d)
              var found = testData1.find(function(e)
              return e.country === d.country && e.year === d.year
              );
              found.rate = +d.rate;
              );


              var years = [...new Set(testData1.map(function(d)
              return +d.year
              ))].sort()

              /////////////////////////////////////////////////////////////////////////////

              var widthSingleChart = 150;
              var heightSingleChart = 100;
              var margin =
              top: 0,
              right: 0,
              bottom: 0,
              left: 10
              ;

              var svg = d3.select('#doublebar')
              .append('svg')
              .attr('width', widthSingleChart)
              .attr('height', heightSingleChart);

              var width = svg.attr('width') - margin.left - margin.right;
              var height = svg.attr('height') - margin.top - margin.bottom;

              svg = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

              var x = d3.scaleBand()
              .rangeRound([0, width])
              .padding(0.15)
              .domain(years);

              var y1 = d3.scaleLinear()
              .rangeRound([height / 2, 0])
              .domain([0, 100]);

              var y2 = d3.scaleSqrt()
              .rangeRound([height / 2, 0])
              .domain([d3.max(testData1, function(d)
              return d.rate
              ), 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);


              ////////////////////////////////////////////////////////////////////////
              // Tooltip.
              ////////////////////////////////////////////////////////////////////////

              var tip = d3.tip()
              .attr('class', 'd3-tip')
              .offset([-5, 0])
              .html(function(d)
              return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
              '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
              '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
              );

              svg.call(tip);

              ////////////////////////////////////////////////////////////////////////
              // Draw a single double bar
              ////////////////////////////////////////////////////////////////////////

              // create x axis
              svg.append('g')
              .attr('class', 'x axis')
              .attr('transform', 'translate(0, ' + (height / 2) + ')')
              .call(xAxis)
              .selectAll('text')
              .style('text-anchor', 'end')
              .attr('dx', '-.8em')
              .attr('dy', '.15em')
              .attr('transform', 'rotate(-65)');

              // create y axis
              svg.append('g')
              .attr('class', 'y axis')
              .attr('transform', 'translate(' + margin.left + ',0)')
              .call(yAxis1)
              .append('text')
              .attr('transform', 'rotate(-90)')
              .attr('y', 6)
              .attr('dy', '.71em')
              .style('text-anchor', 'end');

              svg.append('g')
              .attr('class', 'y axis')
              .attr('transform', 'translate(' + margin.left + ',' + (height/2) + ')')
              .call(yAxis2)
              .append('text')
              .style('text-anchor', 'end');

              var barsGroups = svg.selectAll(null)
              .data(testData1)
              .enter()
              .append('g')
              .attr('transform', function(d)
              return 'translate(' + x(d.year) + ',0)'
              );

              barsGroups.on('mouseover', function(d)
              tip.show(d);
              )
              .on('mouseout', function(d)
              tip.hide(d);
              );

              // create bar rect
              var percRect = barsGroups.append('rect')
              .attr('fill', 'steelblue')
              .attr('class', 'bar')
              .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 / 2 - y1(d.perc);
              );

              var rateRect = barsGroups.append('rect')
              .attr('fill', 'tomato')
              .attr('class', 'bar')
              .attr('y', function(d) // top start point
              if (isNaN(d.rate))
              d.rate = 0;

              return height / 2;
              )
              .attr('width', x.bandwidth())
              .attr('height', function(d)
              if (isNaN(d.rate))
              d.perc = 0;

              return y2(d.rate);
              );

              .x.axis.tick line, text 
              opacity: 0;


              #doublebar
              width: 150px; /* stesso di widthSingleChart */
              background: whitesmoke;


              /**
              * Tooltip.
              */
              .d3-tip
              line-height: 1;
              font: 10px sans-serif;
              padding: 12px;
              background: rgba(0, 0, 0, 0.8);
              color: rgb(185, 185, 185);
              border-radius: 2px;


              /* Creates a small triangle extender for the tooltip */
              .d3-tip:after
              box-sizing: border-box;
              display: inline;
              font-size: 10px;
              width: 100%;
              line-height: 0.9;
              color: rgba(0, 0, 0, 0.8);
              content: '25BC';
              position: absolute;
              text-align: center;


              /* Style northward tooltips differently */
              .d3-tip.n:after
              margin: -1px 0 0 0;
              top: 100%;
              left: 0;

              <script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
              <script src='https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js'></script>
              <div style="height:50px;"></div>
              <div id='doublebar'></div>





              It has 260 lines compared to the original 330. But reducing the number of lines is not the most important thing here: the most important thing is having just 1 dataset, and just 1 enter selection based on that dataset.






              share|improve this answer

























                up vote
                3
                down vote



                accepted







                up vote
                3
                down vote



                accepted






                Your code is certainly improvable, and several changes should be done. The main problem, that immediately comes to attention, is the repetition of the code.



                However, the problem here is not (only) the DRY issue itself... the problem is more fundamental, regarding D3 best practices: since you have two data arrays (which are almost the same), you're binding different data to different selections. Because of that, you're bending over backwards to do some simple tasks, like creating the tooltip (I'm the author of the answer to your previous question at S.O. regarding this).



                So, let's get rid of those repetitions not just because they are repetitions, but for having a single data set which we can bind to a single enter selection.



                Here are the steps:




                1. First, we'll merge the datasets:



                  testData2.forEach(function(d)
                  var found = testData1.find(function(e)
                  return e.country === d.country && e.year === d.year
                  );
                  found.rate = +d.rate;
                  );


                Now we have just one dataset (testData1) with all the relevant information, which we'll use in our enter selection further down. If you can guarantee that both arrays have the same years and are always in the same order, the function above can be substantially simplified.




                1. You don't need to hardcode the years, you can get them from the data:



                  var years = [...new Set(testData1.map(function(d)return +d.year))].sort()



                2. Let's create a single SVG, you don't need two of them:



                  var svg = d3.select('#doublebar')
                  .append('svg')
                  .attr('width', widthSingleChart)
                  .attr('height', heightSingleChart);



                3. Change the scales accordingly, and do not hardcode the domains. Instead, use d3.max and d3.min:



                  .domain([d3.max(testData1, function(d)return d.rate), 0.1]);


                Since now we have just 1 SVG, pay attention to the ranges.




                1. Now you can have a regular tooltip, without that awkward code:



                  .html(function(d) 
                  return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
                  '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
                  '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
                  );



                2. Put the x axis in the middle of the SVG:



                  .attr('transform', 'translate(0, ' + (height/2) + ')') 


                Here comes the important part. We'll create everything in a single enter selection, getting rid of makingDoublebar1 and makingDoublebar2.




                1. Let's create the groups to which you're binding the data. Remember, always name your selections:



                  var barsGroups = svg.selectAll(null)
                  .data(testData1)
                  .enter()
                  .append('g')
                  .attr('transform', function(d)
                  return 'translate(' + x(d.year) + ',0)'
                  );


                As you can see, I'm selecting null. I'm doing this because I suppose you don't have an update selection here. To read more about selecting null, see my explanation in this answer at S.O.




                1. Once those groups are created, let's append the bars, two to each group:



                  barsGroups.append('rect')
                  .attr('fill', 'steelblue')
                  .attr('class', 'bar')
                  .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 / 2 - y1(d.perc);
                  );

                  barsGroups.append('rect')
                  .attr('fill', 'tomato')
                  .attr('class', 'bar')
                  .attr('y', function(d) // top start point
                  if (isNaN(d.rate))
                  d.rate = 0;

                  return height / 2;
                  )
                  .attr('width', x.bandwidth())
                  .attr('height', function(d)
                  if (isNaN(d.rate))
                  d.perc = 0;

                  console.log(d)
                  return y2(d.rate);
                  );



                2. Finally, set the events to the groups:



                  barsGroups.on('mouseover', function(d) 
                  tip.show(d);
                  )
                  .on('mouseout', function(d)
                  tip.hide(d);
                  );


                You'll have to re-select the bars, but that's not too complicated.



                Also, mind some minor details: there is no attribute year for a SVG rectangle. Also, your data contain strings, not numbers. Change that (you can change the data itself or you can use an unary plus, like I used to merge the rates).



                Here is the refactored code:






                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'

                /*,
                country: 'DE', year: '2016', perc: '91',
                country: 'DE', year: '2015', perc: '85',
                country: 'DE', year: '2014', perc: '78',
                country: 'DE', year: '2013', perc: '95',
                country: 'DE', year: '2012', perc: '99',
                country: 'DE', year: '2011', perc: '80',
                country: 'DE', year: '2010', perc: '82',
                country: 'DE', year: '2009', perc: '85',
                country: 'DE', year: '2008', perc: '75',
                country: 'DE', year: '2007', perc: '90',
                country: 'DE', year: '2006', perc: '90',
                country: 'DE', year: '2005', perc: '82',
                country: 'DE', year: '2004', perc: '81',
                country: 'DE', year: '2003', perc: 'NaN',
                country: 'DE', year: '2002', perc: '78',
                country: 'DE', year: '2001', perc: '75',
                country: 'DE', year: '2000', perc: '50',
                country: 'DE', year: '1999', perc: '95',
                country: 'DE', year: '1998', perc: '93',
                country: 'DE', year: '1997', perc: '94',
                country: 'DE', year: '1996', perc: '90',
                country: 'DE', year: '1995', perc: '85',
                country: 'DE', year: '1994', perc: '80',
                country: 'DE', year: '1993', perc: '77',
                country: 'DE', year: '1992', perc: '91',
                country: 'DE', year: '1991', perc: 'NaN',
                country: 'DE', year: '1990', perc: '86'*/
                ];
                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'

                /*,
                country: 'DE', year: '2017', rate: '5',
                country: 'DE', year: '2016', rate: '1',
                country: 'DE', year: '2015', rate: '200',
                country: 'DE', year: '2014', rate: '350',
                country: 'DE', year: '2013', rate: '100',
                country: 'DE', year: '2012', rate: '200',
                country: 'DE', year: '2011', rate: '2',
                country: 'DE', year: '2010', rate: '15',
                country: 'DE', year: '2009', rate: '25',
                country: 'DE', year: '2008', rate: '150',
                country: 'DE', year: '2007', rate: '5',
                country: 'DE', year: '2006', rate: 'NaN',
                country: 'DE', year: '2005', rate: '1',
                country: 'DE', year: '2004', rate: '2',
                country: 'DE', year: '2003', rate: '1',
                country: 'DE', year: '2002', rate: '7',
                country: 'DE', year: '2001', rate: '5',
                country: 'DE', year: '2000', rate: '0.1',
                country: 'DE', year: '1999', rate: '0',
                country: 'DE', year: '1998', rate: '',
                country: 'DE', year: '1997', rate: '813',
                country: 'DE', year: '1996', rate: '222',
                country: 'DE', year: '1995', rate: '181',
                country: 'DE', year: '1994', rate: '11',
                country: 'DE', year: '1993', rate: '2.3',
                country: 'DE', year: '1992', rate: '1',
                country: 'DE', year: '1991', rate: '0',
                country: 'DE', year: '1990', rate: 'NaN'*/
                ];

                testData2.forEach(function(d)
                var found = testData1.find(function(e)
                return e.country === d.country && e.year === d.year
                );
                found.rate = +d.rate;
                );


                var years = [...new Set(testData1.map(function(d)
                return +d.year
                ))].sort()

                /////////////////////////////////////////////////////////////////////////////

                var widthSingleChart = 150;
                var heightSingleChart = 100;
                var margin =
                top: 0,
                right: 0,
                bottom: 0,
                left: 10
                ;

                var svg = d3.select('#doublebar')
                .append('svg')
                .attr('width', widthSingleChart)
                .attr('height', heightSingleChart);

                var width = svg.attr('width') - margin.left - margin.right;
                var height = svg.attr('height') - margin.top - margin.bottom;

                svg = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

                var x = d3.scaleBand()
                .rangeRound([0, width])
                .padding(0.15)
                .domain(years);

                var y1 = d3.scaleLinear()
                .rangeRound([height / 2, 0])
                .domain([0, 100]);

                var y2 = d3.scaleSqrt()
                .rangeRound([height / 2, 0])
                .domain([d3.max(testData1, function(d)
                return d.rate
                ), 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);


                ////////////////////////////////////////////////////////////////////////
                // Tooltip.
                ////////////////////////////////////////////////////////////////////////

                var tip = d3.tip()
                .attr('class', 'd3-tip')
                .offset([-5, 0])
                .html(function(d)
                return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
                '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
                '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
                );

                svg.call(tip);

                ////////////////////////////////////////////////////////////////////////
                // Draw a single double bar
                ////////////////////////////////////////////////////////////////////////

                // create x axis
                svg.append('g')
                .attr('class', 'x axis')
                .attr('transform', 'translate(0, ' + (height / 2) + ')')
                .call(xAxis)
                .selectAll('text')
                .style('text-anchor', 'end')
                .attr('dx', '-.8em')
                .attr('dy', '.15em')
                .attr('transform', 'rotate(-65)');

                // create y axis
                svg.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate(' + margin.left + ',0)')
                .call(yAxis1)
                .append('text')
                .attr('transform', 'rotate(-90)')
                .attr('y', 6)
                .attr('dy', '.71em')
                .style('text-anchor', 'end');

                svg.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate(' + margin.left + ',' + (height/2) + ')')
                .call(yAxis2)
                .append('text')
                .style('text-anchor', 'end');

                var barsGroups = svg.selectAll(null)
                .data(testData1)
                .enter()
                .append('g')
                .attr('transform', function(d)
                return 'translate(' + x(d.year) + ',0)'
                );

                barsGroups.on('mouseover', function(d)
                tip.show(d);
                )
                .on('mouseout', function(d)
                tip.hide(d);
                );

                // create bar rect
                var percRect = barsGroups.append('rect')
                .attr('fill', 'steelblue')
                .attr('class', 'bar')
                .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 / 2 - y1(d.perc);
                );

                var rateRect = barsGroups.append('rect')
                .attr('fill', 'tomato')
                .attr('class', 'bar')
                .attr('y', function(d) // top start point
                if (isNaN(d.rate))
                d.rate = 0;

                return height / 2;
                )
                .attr('width', x.bandwidth())
                .attr('height', function(d)
                if (isNaN(d.rate))
                d.perc = 0;

                return y2(d.rate);
                );

                .x.axis.tick line, text 
                opacity: 0;


                #doublebar
                width: 150px; /* stesso di widthSingleChart */
                background: whitesmoke;


                /**
                * Tooltip.
                */
                .d3-tip
                line-height: 1;
                font: 10px sans-serif;
                padding: 12px;
                background: rgba(0, 0, 0, 0.8);
                color: rgb(185, 185, 185);
                border-radius: 2px;


                /* Creates a small triangle extender for the tooltip */
                .d3-tip:after
                box-sizing: border-box;
                display: inline;
                font-size: 10px;
                width: 100%;
                line-height: 0.9;
                color: rgba(0, 0, 0, 0.8);
                content: '25BC';
                position: absolute;
                text-align: center;


                /* Style northward tooltips differently */
                .d3-tip.n:after
                margin: -1px 0 0 0;
                top: 100%;
                left: 0;

                <script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
                <script src='https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js'></script>
                <div style="height:50px;"></div>
                <div id='doublebar'></div>





                It has 260 lines compared to the original 330. But reducing the number of lines is not the most important thing here: the most important thing is having just 1 dataset, and just 1 enter selection based on that dataset.






                share|improve this answer















                Your code is certainly improvable, and several changes should be done. The main problem, that immediately comes to attention, is the repetition of the code.



                However, the problem here is not (only) the DRY issue itself... the problem is more fundamental, regarding D3 best practices: since you have two data arrays (which are almost the same), you're binding different data to different selections. Because of that, you're bending over backwards to do some simple tasks, like creating the tooltip (I'm the author of the answer to your previous question at S.O. regarding this).



                So, let's get rid of those repetitions not just because they are repetitions, but for having a single data set which we can bind to a single enter selection.



                Here are the steps:




                1. First, we'll merge the datasets:



                  testData2.forEach(function(d)
                  var found = testData1.find(function(e)
                  return e.country === d.country && e.year === d.year
                  );
                  found.rate = +d.rate;
                  );


                Now we have just one dataset (testData1) with all the relevant information, which we'll use in our enter selection further down. If you can guarantee that both arrays have the same years and are always in the same order, the function above can be substantially simplified.




                1. You don't need to hardcode the years, you can get them from the data:



                  var years = [...new Set(testData1.map(function(d)return +d.year))].sort()



                2. Let's create a single SVG, you don't need two of them:



                  var svg = d3.select('#doublebar')
                  .append('svg')
                  .attr('width', widthSingleChart)
                  .attr('height', heightSingleChart);



                3. Change the scales accordingly, and do not hardcode the domains. Instead, use d3.max and d3.min:



                  .domain([d3.max(testData1, function(d)return d.rate), 0.1]);


                Since now we have just 1 SVG, pay attention to the ranges.




                1. Now you can have a regular tooltip, without that awkward code:



                  .html(function(d) 
                  return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
                  '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
                  '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
                  );



                2. Put the x axis in the middle of the SVG:



                  .attr('transform', 'translate(0, ' + (height/2) + ')') 


                Here comes the important part. We'll create everything in a single enter selection, getting rid of makingDoublebar1 and makingDoublebar2.




                1. Let's create the groups to which you're binding the data. Remember, always name your selections:



                  var barsGroups = svg.selectAll(null)
                  .data(testData1)
                  .enter()
                  .append('g')
                  .attr('transform', function(d)
                  return 'translate(' + x(d.year) + ',0)'
                  );


                As you can see, I'm selecting null. I'm doing this because I suppose you don't have an update selection here. To read more about selecting null, see my explanation in this answer at S.O.




                1. Once those groups are created, let's append the bars, two to each group:



                  barsGroups.append('rect')
                  .attr('fill', 'steelblue')
                  .attr('class', 'bar')
                  .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 / 2 - y1(d.perc);
                  );

                  barsGroups.append('rect')
                  .attr('fill', 'tomato')
                  .attr('class', 'bar')
                  .attr('y', function(d) // top start point
                  if (isNaN(d.rate))
                  d.rate = 0;

                  return height / 2;
                  )
                  .attr('width', x.bandwidth())
                  .attr('height', function(d)
                  if (isNaN(d.rate))
                  d.perc = 0;

                  console.log(d)
                  return y2(d.rate);
                  );



                2. Finally, set the events to the groups:



                  barsGroups.on('mouseover', function(d) 
                  tip.show(d);
                  )
                  .on('mouseout', function(d)
                  tip.hide(d);
                  );


                You'll have to re-select the bars, but that's not too complicated.



                Also, mind some minor details: there is no attribute year for a SVG rectangle. Also, your data contain strings, not numbers. Change that (you can change the data itself or you can use an unary plus, like I used to merge the rates).



                Here is the refactored code:






                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'

                /*,
                country: 'DE', year: '2016', perc: '91',
                country: 'DE', year: '2015', perc: '85',
                country: 'DE', year: '2014', perc: '78',
                country: 'DE', year: '2013', perc: '95',
                country: 'DE', year: '2012', perc: '99',
                country: 'DE', year: '2011', perc: '80',
                country: 'DE', year: '2010', perc: '82',
                country: 'DE', year: '2009', perc: '85',
                country: 'DE', year: '2008', perc: '75',
                country: 'DE', year: '2007', perc: '90',
                country: 'DE', year: '2006', perc: '90',
                country: 'DE', year: '2005', perc: '82',
                country: 'DE', year: '2004', perc: '81',
                country: 'DE', year: '2003', perc: 'NaN',
                country: 'DE', year: '2002', perc: '78',
                country: 'DE', year: '2001', perc: '75',
                country: 'DE', year: '2000', perc: '50',
                country: 'DE', year: '1999', perc: '95',
                country: 'DE', year: '1998', perc: '93',
                country: 'DE', year: '1997', perc: '94',
                country: 'DE', year: '1996', perc: '90',
                country: 'DE', year: '1995', perc: '85',
                country: 'DE', year: '1994', perc: '80',
                country: 'DE', year: '1993', perc: '77',
                country: 'DE', year: '1992', perc: '91',
                country: 'DE', year: '1991', perc: 'NaN',
                country: 'DE', year: '1990', perc: '86'*/
                ];
                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'

                /*,
                country: 'DE', year: '2017', rate: '5',
                country: 'DE', year: '2016', rate: '1',
                country: 'DE', year: '2015', rate: '200',
                country: 'DE', year: '2014', rate: '350',
                country: 'DE', year: '2013', rate: '100',
                country: 'DE', year: '2012', rate: '200',
                country: 'DE', year: '2011', rate: '2',
                country: 'DE', year: '2010', rate: '15',
                country: 'DE', year: '2009', rate: '25',
                country: 'DE', year: '2008', rate: '150',
                country: 'DE', year: '2007', rate: '5',
                country: 'DE', year: '2006', rate: 'NaN',
                country: 'DE', year: '2005', rate: '1',
                country: 'DE', year: '2004', rate: '2',
                country: 'DE', year: '2003', rate: '1',
                country: 'DE', year: '2002', rate: '7',
                country: 'DE', year: '2001', rate: '5',
                country: 'DE', year: '2000', rate: '0.1',
                country: 'DE', year: '1999', rate: '0',
                country: 'DE', year: '1998', rate: '',
                country: 'DE', year: '1997', rate: '813',
                country: 'DE', year: '1996', rate: '222',
                country: 'DE', year: '1995', rate: '181',
                country: 'DE', year: '1994', rate: '11',
                country: 'DE', year: '1993', rate: '2.3',
                country: 'DE', year: '1992', rate: '1',
                country: 'DE', year: '1991', rate: '0',
                country: 'DE', year: '1990', rate: 'NaN'*/
                ];

                testData2.forEach(function(d)
                var found = testData1.find(function(e)
                return e.country === d.country && e.year === d.year
                );
                found.rate = +d.rate;
                );


                var years = [...new Set(testData1.map(function(d)
                return +d.year
                ))].sort()

                /////////////////////////////////////////////////////////////////////////////

                var widthSingleChart = 150;
                var heightSingleChart = 100;
                var margin =
                top: 0,
                right: 0,
                bottom: 0,
                left: 10
                ;

                var svg = d3.select('#doublebar')
                .append('svg')
                .attr('width', widthSingleChart)
                .attr('height', heightSingleChart);

                var width = svg.attr('width') - margin.left - margin.right;
                var height = svg.attr('height') - margin.top - margin.bottom;

                svg = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

                var x = d3.scaleBand()
                .rangeRound([0, width])
                .padding(0.15)
                .domain(years);

                var y1 = d3.scaleLinear()
                .rangeRound([height / 2, 0])
                .domain([0, 100]);

                var y2 = d3.scaleSqrt()
                .rangeRound([height / 2, 0])
                .domain([d3.max(testData1, function(d)
                return d.rate
                ), 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);


                ////////////////////////////////////////////////////////////////////////
                // Tooltip.
                ////////////////////////////////////////////////////////////////////////

                var tip = d3.tip()
                .attr('class', 'd3-tip')
                .offset([-5, 0])
                .html(function(d)
                return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
                '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
                '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
                );

                svg.call(tip);

                ////////////////////////////////////////////////////////////////////////
                // Draw a single double bar
                ////////////////////////////////////////////////////////////////////////

                // create x axis
                svg.append('g')
                .attr('class', 'x axis')
                .attr('transform', 'translate(0, ' + (height / 2) + ')')
                .call(xAxis)
                .selectAll('text')
                .style('text-anchor', 'end')
                .attr('dx', '-.8em')
                .attr('dy', '.15em')
                .attr('transform', 'rotate(-65)');

                // create y axis
                svg.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate(' + margin.left + ',0)')
                .call(yAxis1)
                .append('text')
                .attr('transform', 'rotate(-90)')
                .attr('y', 6)
                .attr('dy', '.71em')
                .style('text-anchor', 'end');

                svg.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate(' + margin.left + ',' + (height/2) + ')')
                .call(yAxis2)
                .append('text')
                .style('text-anchor', 'end');

                var barsGroups = svg.selectAll(null)
                .data(testData1)
                .enter()
                .append('g')
                .attr('transform', function(d)
                return 'translate(' + x(d.year) + ',0)'
                );

                barsGroups.on('mouseover', function(d)
                tip.show(d);
                )
                .on('mouseout', function(d)
                tip.hide(d);
                );

                // create bar rect
                var percRect = barsGroups.append('rect')
                .attr('fill', 'steelblue')
                .attr('class', 'bar')
                .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 / 2 - y1(d.perc);
                );

                var rateRect = barsGroups.append('rect')
                .attr('fill', 'tomato')
                .attr('class', 'bar')
                .attr('y', function(d) // top start point
                if (isNaN(d.rate))
                d.rate = 0;

                return height / 2;
                )
                .attr('width', x.bandwidth())
                .attr('height', function(d)
                if (isNaN(d.rate))
                d.perc = 0;

                return y2(d.rate);
                );

                .x.axis.tick line, text 
                opacity: 0;


                #doublebar
                width: 150px; /* stesso di widthSingleChart */
                background: whitesmoke;


                /**
                * Tooltip.
                */
                .d3-tip
                line-height: 1;
                font: 10px sans-serif;
                padding: 12px;
                background: rgba(0, 0, 0, 0.8);
                color: rgb(185, 185, 185);
                border-radius: 2px;


                /* Creates a small triangle extender for the tooltip */
                .d3-tip:after
                box-sizing: border-box;
                display: inline;
                font-size: 10px;
                width: 100%;
                line-height: 0.9;
                color: rgba(0, 0, 0, 0.8);
                content: '25BC';
                position: absolute;
                text-align: center;


                /* Style northward tooltips differently */
                .d3-tip.n:after
                margin: -1px 0 0 0;
                top: 100%;
                left: 0;

                <script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
                <script src='https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js'></script>
                <div style="height:50px;"></div>
                <div id='doublebar'></div>





                It has 260 lines compared to the original 330. But reducing the number of lines is not the most important thing here: the most important thing is having just 1 dataset, and just 1 enter selection based on that dataset.






                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'

                /*,
                country: 'DE', year: '2016', perc: '91',
                country: 'DE', year: '2015', perc: '85',
                country: 'DE', year: '2014', perc: '78',
                country: 'DE', year: '2013', perc: '95',
                country: 'DE', year: '2012', perc: '99',
                country: 'DE', year: '2011', perc: '80',
                country: 'DE', year: '2010', perc: '82',
                country: 'DE', year: '2009', perc: '85',
                country: 'DE', year: '2008', perc: '75',
                country: 'DE', year: '2007', perc: '90',
                country: 'DE', year: '2006', perc: '90',
                country: 'DE', year: '2005', perc: '82',
                country: 'DE', year: '2004', perc: '81',
                country: 'DE', year: '2003', perc: 'NaN',
                country: 'DE', year: '2002', perc: '78',
                country: 'DE', year: '2001', perc: '75',
                country: 'DE', year: '2000', perc: '50',
                country: 'DE', year: '1999', perc: '95',
                country: 'DE', year: '1998', perc: '93',
                country: 'DE', year: '1997', perc: '94',
                country: 'DE', year: '1996', perc: '90',
                country: 'DE', year: '1995', perc: '85',
                country: 'DE', year: '1994', perc: '80',
                country: 'DE', year: '1993', perc: '77',
                country: 'DE', year: '1992', perc: '91',
                country: 'DE', year: '1991', perc: 'NaN',
                country: 'DE', year: '1990', perc: '86'*/
                ];
                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'

                /*,
                country: 'DE', year: '2017', rate: '5',
                country: 'DE', year: '2016', rate: '1',
                country: 'DE', year: '2015', rate: '200',
                country: 'DE', year: '2014', rate: '350',
                country: 'DE', year: '2013', rate: '100',
                country: 'DE', year: '2012', rate: '200',
                country: 'DE', year: '2011', rate: '2',
                country: 'DE', year: '2010', rate: '15',
                country: 'DE', year: '2009', rate: '25',
                country: 'DE', year: '2008', rate: '150',
                country: 'DE', year: '2007', rate: '5',
                country: 'DE', year: '2006', rate: 'NaN',
                country: 'DE', year: '2005', rate: '1',
                country: 'DE', year: '2004', rate: '2',
                country: 'DE', year: '2003', rate: '1',
                country: 'DE', year: '2002', rate: '7',
                country: 'DE', year: '2001', rate: '5',
                country: 'DE', year: '2000', rate: '0.1',
                country: 'DE', year: '1999', rate: '0',
                country: 'DE', year: '1998', rate: '',
                country: 'DE', year: '1997', rate: '813',
                country: 'DE', year: '1996', rate: '222',
                country: 'DE', year: '1995', rate: '181',
                country: 'DE', year: '1994', rate: '11',
                country: 'DE', year: '1993', rate: '2.3',
                country: 'DE', year: '1992', rate: '1',
                country: 'DE', year: '1991', rate: '0',
                country: 'DE', year: '1990', rate: 'NaN'*/
                ];

                testData2.forEach(function(d)
                var found = testData1.find(function(e)
                return e.country === d.country && e.year === d.year
                );
                found.rate = +d.rate;
                );


                var years = [...new Set(testData1.map(function(d)
                return +d.year
                ))].sort()

                /////////////////////////////////////////////////////////////////////////////

                var widthSingleChart = 150;
                var heightSingleChart = 100;
                var margin =
                top: 0,
                right: 0,
                bottom: 0,
                left: 10
                ;

                var svg = d3.select('#doublebar')
                .append('svg')
                .attr('width', widthSingleChart)
                .attr('height', heightSingleChart);

                var width = svg.attr('width') - margin.left - margin.right;
                var height = svg.attr('height') - margin.top - margin.bottom;

                svg = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

                var x = d3.scaleBand()
                .rangeRound([0, width])
                .padding(0.15)
                .domain(years);

                var y1 = d3.scaleLinear()
                .rangeRound([height / 2, 0])
                .domain([0, 100]);

                var y2 = d3.scaleSqrt()
                .rangeRound([height / 2, 0])
                .domain([d3.max(testData1, function(d)
                return d.rate
                ), 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);


                ////////////////////////////////////////////////////////////////////////
                // Tooltip.
                ////////////////////////////////////////////////////////////////////////

                var tip = d3.tip()
                .attr('class', 'd3-tip')
                .offset([-5, 0])
                .html(function(d)
                return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
                '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
                '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
                );

                svg.call(tip);

                ////////////////////////////////////////////////////////////////////////
                // Draw a single double bar
                ////////////////////////////////////////////////////////////////////////

                // create x axis
                svg.append('g')
                .attr('class', 'x axis')
                .attr('transform', 'translate(0, ' + (height / 2) + ')')
                .call(xAxis)
                .selectAll('text')
                .style('text-anchor', 'end')
                .attr('dx', '-.8em')
                .attr('dy', '.15em')
                .attr('transform', 'rotate(-65)');

                // create y axis
                svg.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate(' + margin.left + ',0)')
                .call(yAxis1)
                .append('text')
                .attr('transform', 'rotate(-90)')
                .attr('y', 6)
                .attr('dy', '.71em')
                .style('text-anchor', 'end');

                svg.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate(' + margin.left + ',' + (height/2) + ')')
                .call(yAxis2)
                .append('text')
                .style('text-anchor', 'end');

                var barsGroups = svg.selectAll(null)
                .data(testData1)
                .enter()
                .append('g')
                .attr('transform', function(d)
                return 'translate(' + x(d.year) + ',0)'
                );

                barsGroups.on('mouseover', function(d)
                tip.show(d);
                )
                .on('mouseout', function(d)
                tip.hide(d);
                );

                // create bar rect
                var percRect = barsGroups.append('rect')
                .attr('fill', 'steelblue')
                .attr('class', 'bar')
                .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 / 2 - y1(d.perc);
                );

                var rateRect = barsGroups.append('rect')
                .attr('fill', 'tomato')
                .attr('class', 'bar')
                .attr('y', function(d) // top start point
                if (isNaN(d.rate))
                d.rate = 0;

                return height / 2;
                )
                .attr('width', x.bandwidth())
                .attr('height', function(d)
                if (isNaN(d.rate))
                d.perc = 0;

                return y2(d.rate);
                );

                .x.axis.tick line, text 
                opacity: 0;


                #doublebar
                width: 150px; /* stesso di widthSingleChart */
                background: whitesmoke;


                /**
                * Tooltip.
                */
                .d3-tip
                line-height: 1;
                font: 10px sans-serif;
                padding: 12px;
                background: rgba(0, 0, 0, 0.8);
                color: rgb(185, 185, 185);
                border-radius: 2px;


                /* Creates a small triangle extender for the tooltip */
                .d3-tip:after
                box-sizing: border-box;
                display: inline;
                font-size: 10px;
                width: 100%;
                line-height: 0.9;
                color: rgba(0, 0, 0, 0.8);
                content: '25BC';
                position: absolute;
                text-align: center;


                /* Style northward tooltips differently */
                .d3-tip.n:after
                margin: -1px 0 0 0;
                top: 100%;
                left: 0;

                <script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
                <script src='https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js'></script>
                <div style="height:50px;"></div>
                <div id='doublebar'></div>





                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'

                /*,
                country: 'DE', year: '2016', perc: '91',
                country: 'DE', year: '2015', perc: '85',
                country: 'DE', year: '2014', perc: '78',
                country: 'DE', year: '2013', perc: '95',
                country: 'DE', year: '2012', perc: '99',
                country: 'DE', year: '2011', perc: '80',
                country: 'DE', year: '2010', perc: '82',
                country: 'DE', year: '2009', perc: '85',
                country: 'DE', year: '2008', perc: '75',
                country: 'DE', year: '2007', perc: '90',
                country: 'DE', year: '2006', perc: '90',
                country: 'DE', year: '2005', perc: '82',
                country: 'DE', year: '2004', perc: '81',
                country: 'DE', year: '2003', perc: 'NaN',
                country: 'DE', year: '2002', perc: '78',
                country: 'DE', year: '2001', perc: '75',
                country: 'DE', year: '2000', perc: '50',
                country: 'DE', year: '1999', perc: '95',
                country: 'DE', year: '1998', perc: '93',
                country: 'DE', year: '1997', perc: '94',
                country: 'DE', year: '1996', perc: '90',
                country: 'DE', year: '1995', perc: '85',
                country: 'DE', year: '1994', perc: '80',
                country: 'DE', year: '1993', perc: '77',
                country: 'DE', year: '1992', perc: '91',
                country: 'DE', year: '1991', perc: 'NaN',
                country: 'DE', year: '1990', perc: '86'*/
                ];
                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'

                /*,
                country: 'DE', year: '2017', rate: '5',
                country: 'DE', year: '2016', rate: '1',
                country: 'DE', year: '2015', rate: '200',
                country: 'DE', year: '2014', rate: '350',
                country: 'DE', year: '2013', rate: '100',
                country: 'DE', year: '2012', rate: '200',
                country: 'DE', year: '2011', rate: '2',
                country: 'DE', year: '2010', rate: '15',
                country: 'DE', year: '2009', rate: '25',
                country: 'DE', year: '2008', rate: '150',
                country: 'DE', year: '2007', rate: '5',
                country: 'DE', year: '2006', rate: 'NaN',
                country: 'DE', year: '2005', rate: '1',
                country: 'DE', year: '2004', rate: '2',
                country: 'DE', year: '2003', rate: '1',
                country: 'DE', year: '2002', rate: '7',
                country: 'DE', year: '2001', rate: '5',
                country: 'DE', year: '2000', rate: '0.1',
                country: 'DE', year: '1999', rate: '0',
                country: 'DE', year: '1998', rate: '',
                country: 'DE', year: '1997', rate: '813',
                country: 'DE', year: '1996', rate: '222',
                country: 'DE', year: '1995', rate: '181',
                country: 'DE', year: '1994', rate: '11',
                country: 'DE', year: '1993', rate: '2.3',
                country: 'DE', year: '1992', rate: '1',
                country: 'DE', year: '1991', rate: '0',
                country: 'DE', year: '1990', rate: 'NaN'*/
                ];

                testData2.forEach(function(d)
                var found = testData1.find(function(e)
                return e.country === d.country && e.year === d.year
                );
                found.rate = +d.rate;
                );


                var years = [...new Set(testData1.map(function(d)
                return +d.year
                ))].sort()

                /////////////////////////////////////////////////////////////////////////////

                var widthSingleChart = 150;
                var heightSingleChart = 100;
                var margin =
                top: 0,
                right: 0,
                bottom: 0,
                left: 10
                ;

                var svg = d3.select('#doublebar')
                .append('svg')
                .attr('width', widthSingleChart)
                .attr('height', heightSingleChart);

                var width = svg.attr('width') - margin.left - margin.right;
                var height = svg.attr('height') - margin.top - margin.bottom;

                svg = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

                var x = d3.scaleBand()
                .rangeRound([0, width])
                .padding(0.15)
                .domain(years);

                var y1 = d3.scaleLinear()
                .rangeRound([height / 2, 0])
                .domain([0, 100]);

                var y2 = d3.scaleSqrt()
                .rangeRound([height / 2, 0])
                .domain([d3.max(testData1, function(d)
                return d.rate
                ), 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);


                ////////////////////////////////////////////////////////////////////////
                // Tooltip.
                ////////////////////////////////////////////////////////////////////////

                var tip = d3.tip()
                .attr('class', 'd3-tip')
                .offset([-5, 0])
                .html(function(d)
                return '<div><span>Country:</span> <span style='color:white'>' + d.country + '</span></div>' +
                '<div><span>Perc:</span> <span style='color:white'>' + d.perc + '%</span></div>' +
                '<div><span>Rate:</span> <span style='color:white'>' + d.rate + '</span></div>';
                );

                svg.call(tip);

                ////////////////////////////////////////////////////////////////////////
                // Draw a single double bar
                ////////////////////////////////////////////////////////////////////////

                // create x axis
                svg.append('g')
                .attr('class', 'x axis')
                .attr('transform', 'translate(0, ' + (height / 2) + ')')
                .call(xAxis)
                .selectAll('text')
                .style('text-anchor', 'end')
                .attr('dx', '-.8em')
                .attr('dy', '.15em')
                .attr('transform', 'rotate(-65)');

                // create y axis
                svg.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate(' + margin.left + ',0)')
                .call(yAxis1)
                .append('text')
                .attr('transform', 'rotate(-90)')
                .attr('y', 6)
                .attr('dy', '.71em')
                .style('text-anchor', 'end');

                svg.append('g')
                .attr('class', 'y axis')
                .attr('transform', 'translate(' + margin.left + ',' + (height/2) + ')')
                .call(yAxis2)
                .append('text')
                .style('text-anchor', 'end');

                var barsGroups = svg.selectAll(null)
                .data(testData1)
                .enter()
                .append('g')
                .attr('transform', function(d)
                return 'translate(' + x(d.year) + ',0)'
                );

                barsGroups.on('mouseover', function(d)
                tip.show(d);
                )
                .on('mouseout', function(d)
                tip.hide(d);
                );

                // create bar rect
                var percRect = barsGroups.append('rect')
                .attr('fill', 'steelblue')
                .attr('class', 'bar')
                .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 / 2 - y1(d.perc);
                );

                var rateRect = barsGroups.append('rect')
                .attr('fill', 'tomato')
                .attr('class', 'bar')
                .attr('y', function(d) // top start point
                if (isNaN(d.rate))
                d.rate = 0;

                return height / 2;
                )
                .attr('width', x.bandwidth())
                .attr('height', function(d)
                if (isNaN(d.rate))
                d.perc = 0;

                return y2(d.rate);
                );

                .x.axis.tick line, text 
                opacity: 0;


                #doublebar
                width: 150px; /* stesso di widthSingleChart */
                background: whitesmoke;


                /**
                * Tooltip.
                */
                .d3-tip
                line-height: 1;
                font: 10px sans-serif;
                padding: 12px;
                background: rgba(0, 0, 0, 0.8);
                color: rgb(185, 185, 185);
                border-radius: 2px;


                /* Creates a small triangle extender for the tooltip */
                .d3-tip:after
                box-sizing: border-box;
                display: inline;
                font-size: 10px;
                width: 100%;
                line-height: 0.9;
                color: rgba(0, 0, 0, 0.8);
                content: '25BC';
                position: absolute;
                text-align: center;


                /* Style northward tooltips differently */
                .d3-tip.n:after
                margin: -1px 0 0 0;
                top: 100%;
                left: 0;

                <script src='https://d3js.org/d3.v5.js' charset='utf-8'></script>
                <script src='https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js'></script>
                <div style="height:50px;"></div>
                <div id='doublebar'></div>






                share|improve this answer















                share|improve this answer



                share|improve this answer








                edited May 18 at 1:38


























                answered May 17 at 4:33









                Gerardo Furtado

                1,1342420




                1,1342420






















                     

                    draft saved


                    draft discarded


























                     


                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function ()
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f194419%2fusing-d3-js-to-create-a-double-bar-chart%23new-answer', 'question_page');

                    );

                    Post as a guest













































































                    Popular posts from this blog

                    Python Lists

                    Aion

                    JavaScript Array Iteration Methods