Search code examples
javascriptd3.jshovertooltip

D3 js How do I get data specific tooltips to display on-hover?


My goal is to have an interactive chart which displays additional information on the arc next to the chart. The code below is not clean, but is functional and does (almost) everything I want it to.

Specifically, it draws a little arc-pie chart, when you hover over an arc it lightens the arc and draws a line from the center of that arc to the data area to the right where a spend and a description are listed.

HOWEVER, the code seems inelegant and the datapoint (text) it is displaying are all the same one. It doesn't seem to be adding the additional data elements in order to display them, or it IS but is only ever selecting the first to display!

The HTML, which I haven't bothered to copy in is just a blank web page with a single div: <div id="pie">1,2,3</div>

 var format = d3.format(",.2f");

    var data = [
        {'spend': 15, 'description': 'Controlled'},
        {'spend': 3, 'description': 'Data Usage'},
        {'spend': 21, 'description': 'International Roaming'}
    ];
    var kulor = d3.scale.ordinal()
        .range(['#648631','#D84B4B','#A05AE0']);

    var width = 160,
        height = 80,
        radius = height / 2 - 10, // how wide is the circle within the box?
        innerRadius = radius - 10, // how thich is the donut (the -# is the thickness)
        cornerRadius = 3, // how wide are the curves of each section
        padAngle = .07, // distance between items (in angles)
        startLine = [40, -25]; // where the description line starts

    var arc = d3.svg.arc()
        .innerRadius(innerRadius)
        .outerRadius(radius)
        .cornerRadius(cornerRadius);

    var pie = d3.layout.pie()
        .padAngle(padAngle)
        .value(function (d) {
            return d.spend;
        });

    var arcs = d3.select('#pie').html('').append('svg')
        .data([data])
        .attr('width', width)
        .attr('height', height)
      .append('g')
        // keep centered but left
        .attr('transform', 'translate(' + height / 2 + ',' + height / 2 + ')');

       arcs.selectAll('path')
            .data(pie).enter()
                .append('path')
                    .style('fill', function(d, i) { return kulor(i); })
                    .attr('d', arc);

            arcs.selectAll('line')
                .data(pie).enter()
                    .append('line')
                        .classed('slice', true)
                        .attr('lineNum', function(d,i){ return 'sl'+i; })
                        .attr('x1', function(d, i) {
                            return arc.centroid(d, i)[0];
                        })
                        .attr('y1', function(d, i) {
                            return arc.centroid(d, i)[1];
                        })
                        .attr('x2', startLine[0]).attr('y2', startLine[1])
                        .attr('stroke-width', 2).attr('stroke','#333')
                        .style('visibility', 'hidden');

            // fixed line
            arcs.append('line')
                .classed('fixedLine', true)
                .attr('x1', startLine[0]).attr('y1', startLine[1])
                .attr('x2', 120).attr('y2', startLine[1])
                .attr('stroke-width', 2).attr('stroke','#333')
                .style('visibility', 'hidden');

            // Spend
            arcs.each(function(d, i) {
                console.log(d[i].spend + " " + i);
                arcs.append('text')
                        .classed('spend', true)
                        .text(function(d,i){ 
                            console.log(d);
                            return '$' + format(d[i].spend); 
                        })
                        .attr('x', 80).attr('y', startLine[1] - 4)
                        .attr('font-size',12)
                        .attr('font-family','Helvetica, sans-serif')
                        .attr('text-anchor','middle')
                        .attr('font-weigt','bold')
                        .style('visibility', 'hidden');
            })


            // Spend Description
            arcs.append('text')
                .text(function(d,i){ return d[i].description; })
                .attr('x', 80).attr('y', startLine[1] + 12)
                .attr('font-size',12)
                .attr('font-family','Helvetica, sans-serif')
                .attr('text-anchor','middle')
                .attr('font-weigt','bold')
                .style('visibility', 'hidden');;

            arcs.selectAll('path')
                .on('mouseover', function(d, i) {
                    var index = i + 1;
                    var nodeSel = d3.select(this).style({opacity:'0.8'});
                    nodeSel.select('text').style({opacity:'1.0'});
                    d3.select('.slice:nth-of-type('+index+')').style('visibility', 'visible');
                    d3.select('.spend:nth-of-type('+index+')').style('visibility', 'visible');
                    d3.select('.fixedLine').style('visibility', 'visible');
                    d3.selectAll('text').style('visibility', 'visible');
                })
               .on('mouseout', function(d, i) {
                    var nodeSel = d3.select(this).style({opacity:'1.0'});
                    nodeSel.select('text').style({opacity:'0'});
                    arcs.selectAll('.slice').style('visibility', 'hidden');
                    arcs.selectAll('.fixedLine').style('visibility', 'hidden');
                    arcs.selectAll('text').style('visibility', 'hidden');
                });

Solution

  • Problem1:

    You don't have to add as many text as the number of arcs. So this is wrong.

    arcs.each(function(d, i) {
                    console.log(d[i].spend + " " + i);
                    arcs.append('text')
                            .classed('spend', true)
                            .text(function(d,i){ 
                                console.log(d);
                                return '$' + format(d[i].spend); 
                            })
                            .attr('x', 80).attr('y', startLine[1] - 4)
                            .attr('font-size',12)
                            .attr('font-family','Helvetica, sans-serif')
                            .attr('text-anchor','middle')
                            .attr('font-weigt','bold')
                            .style('visibility', 'hidden');
                })
    

    The right way is just add one text DOM like this (and to that DOM give the description on mouse over)

            arcs.append('text')
                    .classed('spend', true)
                    .attr('x', 80).attr('y', startLine[1] - 4)
                    .attr('font-size',12)
                    .attr('font-family','Helvetica, sans-serif')
                    .attr('text-anchor','middle')
                    .attr('font-weigt','bold')
                    .style('visibility', 'hidden');
    

    Problem 2:

    You don't need to set the text while making the text DOM like this:

     .classed('spend', true)
                                .text(function(d,i){ 
                                    console.log(d);
                                    return '$' + format(d[i].spend); 
                                })
    

    You need to add the description on mouse over like this

    .on('mouseover', function(d, i) {
                        var index = i + 1;
                        var nodeSel = d3.select(this).style({opacity:'0.8'});
                        nodeSel.select('text').style({opacity:'1.0'});
                        d3.select('.slice:nth-of-type('+index+')').style('visibility', 'visible');
                        d3.select('.spend:nth-of-type('+index+')').style('visibility', 'visible');
                        d3.select('.fixedLine').style('visibility', 'visible');
                        d3.selectAll('text').style('visibility', 'visible');
                        //setting the description data to the text dom
                        d3.selectAll(".description").text(d.data.description);
                        //setting the spend data to the text dom
                        d3.selectAll(".spend").text('$' + format(d.data.spend))
                    });
    

    Working code here.