Search code examples
javascriptd3.jstooltip

d3 multiple line chart tooltip value data undefined


I'm having an issue with part of my tooltip text for this multiple line chart. It finds the name correctly, but when I try to access the y-value of the line, it's coming back as undefined.

The data structure looks like this in the console:

screenshot of console

I need the value from the "arrests:" piece to show in the tooltip. Right now it says undefined.

Here's my code. Tooltip bit is at the end.

        <div id="graph">
            <div id="tooltip" class="hidden">
                <p id="value">value</p>
            </div>
     </div>
           <script type="text/javascript">
                  var w = 700
                      h = 500

        var margin = {top: 20, right: 140, bottom: 130, left: 100},
            width = w - margin.left - margin.right,
            height = h - margin.top - margin.bottom;

        var parseDate = d3.time.format("%Y").parse;

        var x = d3.time.scale()
            .range([0, width]);
        var y = d3.scale.linear()
            .range([height, 0]);

        var color = d3.scale.ordinal()
            .range([ "#43736C", "#29403C", "#f4b400", '#000'])
            //"#29403C", "#463573", "#f4b400", "#43736C", "#43736C", "#f4b400"        ]);

        var xAxis = d3.svg.axis()
            .scale(x)
            .orient("bottom");
        var yAxis = d3.svg.axis()
            .scale(y)
            .orient("left");

        var line = d3.svg.line()
            .x(function(d) {return x(d.year); })
            .y(function(d) {return y(d.arrests); });

        var svg = d3.select("#graph").append("svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
            .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        d3.csv("https://spreadsheets.google.com/tq?key=1DNo1v8lYRzzZA6kPdQ77HrREhzkW1nDwktNQm2MXSio&tqx=out:csv", function(error, data) {
            if (error) throw error;

            color.domain(d3.keys(data[0]).filter(function(key) {return key !== "year"; }));

            data.forEach(function(d) {
                d.year = parseDate(d.year);
                });

            var populations = color.domain().map(function(name) {
                return {
                    name: name,
                    values: data.map(function(d) {
                        return {year: d.year, arrests: +d[name]};
                        })
                    };
                });

            var populations = populations.filter(function(d){
                return d.name!== "";
                });

            console.log(populations);

            x.domain(d3.extent(data, function(d) {return d.year; }));

            y.domain([0, 15000]);

            svg.append("g")
                .attr("class", "x axis")
                .attr("transform", "translate(0,"+ height + ")")
                .call(xAxis);

            svg.append("g")
                .attr("class", "y axis")
                .call(yAxis)
                .append("text")
                    .attr("class", "yLabel")
                    .attr("transform", "rotate(-90)")
                    .attr("y", 0)
                    .attr("dy", "-6em")
                    .style("text-anchor", "end")
                    .text("Number of arrests");

            var pop = svg.selectAll(".pop")
                .data(populations)
                .enter().append("g")
                .attr("class", "pop");

            pop.append("path")
                .attr("class", "line")
                .attr("d", function(d) { return line(d.values); })
                .style("stroke", function(d) {return color(d.name); });

            var legend = svg.selectAll(".legend")
                .data(populations.slice())
                .enter().append("g")
                    .attr("class", "legend")
                    .attr("transform", function(d, i) {return "translate(0,"+ i * 20 + ")"});


            legend.append("rect")
                .data(populations)
                .attr("x", width)
                .attr("width", 18)
                .attr("height", 18)
                .style("fill", function(d) {return color(d.name);});

            legend.append("text")
                .attr("x", w - 220)
                .attr("y", 9)
                .attr("dy", ".35em")
                .style("text-align", "left")
                .text(function(d) {return d.name});


            svg.selectAll("path")
                .on("mouseover", function(d) {
                    var xPosition = d3.event.pageX;
                    var yPosition = d3.event.pageY;
                    console.log(d.values);
                    d3.select("#tooltip")
                        .style("left", xPosition + "px")
                        .style("top", yPosition + "px") 
                        .select("#value")
                        .text(d.name + " " + d.values.arrests);

                    d3.select("#tooltip").classed("hidden", false);             
                        })
                .on("mouseout", function(){
                    d3.select("#tooltip").classed("hidden", true);
                    });



            });

Solution

  • It looks as though your problem is that your mouseover behavior is on the path. The data for the path is the entire series, even if you're thinking of it as though you're mousing over just one year. So the value of d is not what you expect it to be. d.values is the entire array of data for each year for that series (e.g. Misdemeanor).

    Instead, you should add invisible point markers for each place where a year intersects the line for each series and put the tooltip behavior on those -- the answer to this SO question -- d3.js tooltips on path -- may help.