3
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.