Search code examples
javascriptjsond3.jstooltipbar-chart

Constructing a tooltip values of bar from json file


I'm currently struggling making the tooltip work. Here's how the page looks like [See Photo1 below], and I'm trying to assign to every bar it's y value. The y value's are stored in resumeperday.json [See below] which has 'time' and 'y' attributes for everyday. time attribute is the date, and y is how many orders were during that day. that resumeperday.json file is stored as dataset variable in index.html file. I'm trying to parse its y value which is sum of all the three different orders (every date has 3 different values) and show it on the bar when mouse is hovering it. When I'm using this line of code

  .html(function(d) {
        return "<strong>Number of orders:</strong> <span style='color:red'>" + d3.max(dataset, function(d) {
                        return d3.max(d, function(d) {
                            return d.y0 + d.y;
                        });
                    }) + "</span>";
        })

Problem: It shows only 123 for every bar (which is the maximum value of orders), I'm trying to build a function that will return the value according to the specific bar that was hovered.

Here's the index.html code:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Pizza orders</title>
        <script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
        <script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
        <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
        <script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
        <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
        <style>
        .axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}

.axis text {
    font-family: sans-serif;
    font-size: 11px;
}

.dot {
  stroke: #000;
}


.legend {
                padding: 5px;
                font: 10px sans-serif;
                background: yellow;
                box-shadow: 2px 2px 1px #888;
            }
.d3-tip {
  line-height: 1;
  font-weight: bold;
  padding: 12px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  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: 1;
  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;
}-align: center;
}
        </style>
    </head>
    <body>
        <div>
            <div class="btn-group pull-right">
                <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
                         Pizza orders per hour <span class="caret"></span>
                </button>
                <ul class="dropdown-menu" role="menu">
                    <li><a class="m" value="2018-07-15" href="#">2018-07-15</a></li>
                    <li><a class="m" value="2018-07-16" href="#">2018-07-16</a></li>
                    <li><a class="m" value="2018-07-17" href="#">2018-07-17</a></li>
                    <li><a class="m" value="2018-07-18" href="#">2018-07-18</a></li>
                    <li><a class="m" value="2018-07-19" href="#">2018-07-19</a></li>
                    <li><a class="m" value="2018-07-20" href="#">2018-07-20</a></li>
                    <li><a class="m" value="2018-07-21" href="#">2018-07-21</a></li>
                </ul>
            </div>

        <div id="mbars">
        </div>
        </div>
        <script type="text/javascript">
        var w = 700;                        //width
        var h = 600;                        //height
        var padding = {top: 40, right: 40, bottom: 40, left:25};
        var dataset;
        var tip = d3.tip()
        .attr('class', 'd3-tip')
        .offset([-10, 0])
        .html(function(d) {
        return "<strong>Number of orders:</strong> <span style='color:red'>" + d3.min(dataset, function(d) {
                        return d3.min(d, function(d) {
                            return d.y0 + d.y;
                        });
                    }) + "</span>";
        })
        //Set up stack method
        var stack = d3.layout.stack();

        d3.json("resumeperday.json",function(json){
            dataset = json;

            //Data, stacked
            stack(dataset);

            var color_hash = {
                    0 : ["50-70₪","#0000FF"],
                    1 : ["70.00-120₪","#FF0000"],
                    2 : ["120+₪","#04B404"]

            };


            //Set up scales
            var xScale = d3.time.scale()
                .domain([new Date(dataset[0][0].time),d3.time.day.offset(new Date(dataset[0][dataset[0].length-1].time),2)])
                .rangeRound([0, w-padding.left-padding.right]);

            var yScale = d3.scale.linear()
                .domain([0,             
                    d3.max(dataset, function(d) {
                        return d3.max(d, function(d) {
                            return d.y0 + d.y;
                        });
                    })
                ])
                .range([h-padding.bottom-padding.top,0]);

            var xAxis = d3.svg.axis()
                           .scale(xScale)
                           .orient("bottom")
                           .ticks(d3.time.days,1);

            var yAxis = d3.svg.axis()
                           .scale(yScale)
                           .orient("left")
                           .ticks(10);



            //Easy colors accessible via a 10-step ordinal scale
            var colors = d3.scale.category10();

            //Create SVG element
            var svg = d3.select("#mbars")
                        .append("svg")
                        .attr("width", w)
                        .attr("height", h);

            svg.call(tip);


            // Add a group for each row of data
            var groups = svg.selectAll("g")
                .data(dataset)
                .enter()
                .append("g")
                .attr("class","rgroups")
                .attr("transform","translate("+ (padding.left) + "," + (h - padding.bottom) +")")
                .style("fill", function(d, i) {
                    return color_hash[dataset.indexOf(d)][1];
                })
                .on('mouseover', tip.show)
                .on('mouseout', tip.hide);

            // Add a rect for each data value
            var rects = groups.selectAll("rect")
                .data(function(d) { return d; })
                .enter()
                .append("rect")
                .attr("width", 2)
                .style("fill-opacity",1e-6);


            rects.transition()
                 .duration(function(d,i){
                     return 500 * i;
                 })
                 .ease("linear")
                .attr("x", function(d) {
                    return xScale(new Date(d.time));
                })
                .attr("y", function(d) {
                    return -(- yScale(d.y0) - yScale(d.y) + (h - padding.top - padding.bottom)*2);
                })
                .attr("height", function(d) {
                    return -yScale(d.y) + (h - padding.top - padding.bottom);
                })
                .attr("width", 15)
                .style("fill-opacity",1);

                svg.append("g")
                    .attr("class","x axis")
                    .attr("transform","translate(40," + (h - padding.bottom) + ")")
                    .call(xAxis);


                svg.append("g")
                    .attr("class","y axis")
                    .attr("transform","translate(" + padding.left + "," + padding.top + ")")
                    .call(yAxis);

                // adding legend

                var legend = svg.append("g")
                                .attr("class","legend")
                                .attr("x", w - padding.right - 65)
                                .attr("y", 25)
                                .attr("height", 100)
                                .attr("width",100);

                legend.selectAll("g").data(dataset)
                      .enter()
                      .append('g')
                      .each(function(d,i){
                        var g = d3.select(this);
                        g.append("rect")
                            .attr("x", w - padding.right - 30)
                            .attr("y", i*25 + 10)
                            .attr("width", 10)
                            .attr("height",10)
                            .style("fill",color_hash[String(i)][1]);

                        g.append("text")
                         .attr("x", w - padding.right - 15)
                         .attr("y", i*25 + 20)
                         .attr("height",30)
                         .attr("width",100)
                         .style("fill",color_hash[String(i)][1])
                         .text(color_hash[String(i)][0]);
                      });

                svg.append("text")
                .attr("transform","translate(5,20) rotate(0)")
                .attr("font-weight","bold")
                .text("Pizza Orders");

            svg.append("text")
               .attr("class","xtext")
               .attr("x",w/2 - padding.left)
               .attr("y",h - 5)
               .attr("text-anchor","middle")
               .attr("font-weight","bold")
               .text("Days");

            svg.append("text")
            .attr("class","title")
            .attr("x", (w / 2))             
            .attr("y", 20)
            .attr("text-anchor", "middle")
            .attr("font-weight","bold")         
            .style("font-size", "16px") 
            .style("text-decoration", "underline")  
            .text("Number of Pizza Orders per day");

            //On click, update with new data            
            d3.selectAll(".m")
                .on("click", function() {
                    var date = this.getAttribute("value");

                    var str;
                    if(date == "2018-07-15"){
                        str = "15.json";
                    }else if(date == "2018-07-16"){
                        str = "16.json";
                    }else if(date == "2018-07-17"){
                        str = "17.json";
                    }else if(date == "2018-07-18"){
                        str = "18.json";
                    }else if(date == "2018-07-19"){
                        str = "19.json";
                    }
                    else if(date == "2018-07-20"){
                        str = "20.json";
                    }else{
                        str = "21.json";
                    }

                    d3.json(str,function(json){

                        dataset = json;
                        stack(dataset);

                        console.log(dataset);

                        xScale.domain([new Date(0, 0, 0,dataset[0][0].time,0, 0, 0),new Date(0, 0, 0,dataset[0][dataset[0].length-1].time,0, 0, 0)])
                        .rangeRound([0, w-padding.left-padding.right]);

                        yScale.domain([0,               
                                        d3.max(dataset, function(d) {
                                            return d3.max(d, function(d) {
                                                return d.y0 + d.y;
                                            });
                                        })
                                    ])
                                    .range([h-padding.bottom-padding.top,0]);

                        xAxis.scale(xScale)
                             .ticks(d3.time.hour,1)
                             .tickFormat(d3.time.format("%H"));

                        yAxis.scale(yScale)
                             .orient("left")
                             .ticks(10);

                         groups = svg.selectAll(".rgroups")
                            .data(dataset);

                            groups.enter().append("g")
                            .attr("class","rgroups")
                            .attr("transform","translate("+ padding.left + "," + (h - padding.bottom) +")")
                            .style("fill",function(d,i){
                                return color(i);
                            });


                            rect = groups.selectAll("rect")
                            .data(function(d){return d;});

                            rect.enter()
                              .append("rect")
                              .attr("x",w)
                              .attr("width",1)
                              .style("fill-opacity",1e-6);

                        rect.transition()
                            .duration(1000)
                            .ease("linear")
                            .attr("x",function(d){
                                return xScale(new Date(0, 0, 0,d.time,0, 0, 0));
                            })
                            .attr("y",function(d){
                                return -(- yScale(d.y0) - yScale(d.y) + (h - padding.top - padding.bottom)*2);
                            })
                            .attr("height",function(d){
                                return -yScale(d.y) + (h - padding.top - padding.bottom);
                            })
                            .attr("width",15)
                            .style("fill-opacity",1);

                        rect.exit()
                           .transition()
                           .duration(1000)
                           .ease("circle")
                           .attr("x",w)
                           .remove();

                        groups.exit()
                           .transition()
                           .duration(1000)
                           .ease("circle")
                           .attr("x",w)
                           .remove();


                        svg.select(".x.axis")
                           .transition()
                           .duration(1000)
                           .ease("circle")
                           .call(xAxis);

                        svg.select(".y.axis")
                           .transition()
                           .duration(1000)
                           .ease("circle")
                           .call(yAxis);

                        svg.select(".xtext")
                           .text("Hours")
                           .attr("font-weight","bold");

                        svg.select(".title")
                        .text("Number of Pizza Orders per hour on " + date)
                        .attr("font-weight","bold");
                    });         
                });


        });

        </script>
    </body>
</html>

resumeperday.json file:

[
    [
        {
            "time": "2018-07-15",
            "y": 27
        },
        {
            "time": "2018-07-16",
            "y": 23
        },
        {
            "time": "2018-07-17",
            "y": 28
        },
        {
            "time": "2018-07-18",
            "y": 20
        },
        {
            "time": "2018-07-19",
            "y": 22
        },
        {
            "time": "2018-07-20",
            "y": 27
        },
        {
            "time": "2018-07-21",
            "y": 21
        }
    ],
    [
        {
            "time": "2018-07-15",
            "y": 29
        },
        {
            "time": "2018-07-16",
            "y": 26
        },
        {
            "time": "2018-07-17",
            "y": 31
        },
        {
            "time": "2018-07-18",
            "y": 27
        },
        {
            "time": "2018-07-19",
            "y": 31
        },
        {
            "time": "2018-07-20",
            "y": 65
        },
        {
            "time": "2018-07-21",
            "y": 37
        }
    ],
    [
        {
            "time": "2018-07-15",
            "y": 17
        },
        {
            "time": "2018-07-16",
            "y": 16
        },
        {
            "time": "2018-07-17",
            "y": 16
        },
        {
            "time": "2018-07-18",
            "y": 15
        },
        {
            "time": "2018-07-19",
            "y": 22
        },
        {
            "time": "2018-07-20",
            "y": 31
        },
        {
            "time": "2018-07-21",
            "y": 23
        }
    ]
]

[Photo1] Only for that specific bar "123" is correct.

enter image description here


Solution

  • The problem is that you group the rects by color. If you hover over a rect you get the datum that belongs to that color group. You have no indication of the bar you are hovering over.

    Select the groups by class not by element type.

    var groups = svg.selectAll(".rgroups")
                    .data(dataset)
                    .enter()
                    .append("g")
                    .attr("class","rgroups")
                    .attr("transform","translate("+ (padding.left) + "," + (h - padding.bottom) +")")
                    .style("fill", function(d, i) {
                        return color_hash[dataset.indexOf(d)][1];
                    })
                    .on('mouseover', tip.show)
                    .on('mouseout', tip.hide);
    

    You have to transform your dataset so you have an object/array that represent all the data of a particular bar. d3.nest might help.

    Then construct a g per bar and add the rects to this g. Then in the tooltip you will get the datum that belongs to this bar.

    Have a closer look at the style for .d3-tip.n:after. It has an error.