Change the axes from absolute values to percentages via button or pull down menu, and tooltip

What worked for the circles doesn’t for the lines? Or should I group the circles/dots the same way?

That is the thing, only minor changes and it works. Thanks.

I experimented with the numbers, the x + 100 is right since 100 is added in the translate statement above.

Go back and look at what you called scatter :wink:

Also a group. :laughing:

The tooltip… right. Well, first thing’s first:

that sounds like a recipe for disaster right there. Stick with one thing. Make it work. Then get fancy with it.

I’m going to do things slightly differently than most of these examples, but lets walk through my theory.

Step 1: Create the tooltip.

Right, well, we know how to append stuff to our SVG. So lets do that. I’ma make a group, with a rect in it, and plop a text element in there so i can play around with it later.

           let tooltip = svg.append("g")
           .attr("transform","translate(100,100)");

           tooltip.append("rect")
                  .attr("width",70)
                  .attr("height",30)
                  .attr("fill", "#CCC")
                  .attr("stroke", "black")
                  .attr("rx",10);

           tooltip.append("text")
                  .attr("y", 19)
                  .attr("x",4)
                  .attr("font-family","Verdana")
                  .attr("font-size", 10)
                  .text("I'm a Tooltip");

Most of this stuff is pretty self-explanatory or just “because I felt like making it look that way.”
Important things to note: I’ve got a reference to my tooltip’s group element, tooltip. That will be necessary later.

Step 2: Babby’s First Movement.

(Remember; keep it simple, make it work, then make it fancy.)
Right, so we’ve got our tooltip. What do we want the tooltip to do?
We want the tooltip to move when we hover over a bit of our graph with some data.
What in our graph has data?
The circles.

So we want, when we hover over a circle, to put the tooltip there, and make it show us the data.

Obviously, this means we need a mouseover event. And we want to go on our circles. So lets go attach our event to the circles.

            scatter
            .selectAll("dot")
            .data(dataset1)
            .enter()
            .append("circle")
            .attr("cx", function (d) { return xScale(d[0]); } )
            .attr("cy", function (d) { return yScale(d[1]); } )
            .attr("r", 3)
            .attr("transform", "translate(" + 100 + "," + 100 + ")")
            .style("fill", "#CC0000")
            .on("mouseover",movetooltip)

annd…the javascript engine starts screaming at us cause we havent defined movetooltip. Understandable. So let’s write one.

When we trigger this event, it will carry with it two parameters (note that this is because it’s a d3 on bind, and not a standard eventListener): the first is the data point of the object, the second is it’s index (may or may not be relevant for other things. We dont really care about it at the moment).

So we hover over something, and we get its data. Well, its data tells us where to put the tooltip, because we can run the numbers through our scales the same as we did when we created the circle.

The tooltip group has a translate applied to it. We can manipulate that, to move the rect and the text along with it.

           function movetooltip(d,i) {

I’m not going to be using i, but its good to show it anyway. You can leave it out.

We want to accomodate for zooming, so we need to use our modified scales - newX and newY.

  tooltip.attr("transform","translate("+newX(d[0])+","+newY(d[1])+")")
}

(we’re closing the function at the end there).

Run the script, annnnd… well thats not in the right place, is it?
We forgot that the graph is itself translated by 100,100. So we need to add that in, because our transform is grounded at 0,0…

               tooltip.attr("transform","translate("+(100+newX(d[0]))+","+(100+newY(d[1]))+")")

And now our tooltip moves along the graph with our mouse. But it still just says “I’m a Tooltip”. Not very helpful. So we need to fix the text of the text element within our tooltip.

We’ve got a local scope for our tooltip group, so lets run a select inside that scope.

tooltip.select("text")

and now modify the text of that text element. Keeping it simple and straightforward;

       .text(d[0] + ": "+ d[1])

And now our tooltip updates itself.
At a basic level; job done, right?

Step 3: Improvements.

So we’ve kept it simple, we’ve made it work… now to get fancier.

There’s a few ways to go here, and it depends on the features you want in your tooltip.

Want the tooltip to disappear when not in use? you’ll need a mouseout event. This is a one-liner that doesnt need the data, so i’m going to inline it. Feel free to separate into a different function if you want for cleaner code.

  .on("mouseout", function(){ return tooltip.style("visibility", "hidden"); });

We’ll hide the tooltip on pageload:

           let tooltip = svg.append("g")
           .attr("transform","translate(100,100)")
           .style("visibility","hidden");

and make it appear when we hover over something:

               tooltip.attr("transform","translate("+(100+newX(d[0]))+","+(100+newY(d[1]))+")")
                      .style("visibility","visible")

You can add your own fancy transitions if you like.

I’d like some clarity on my tooltip, what i’m looking at when i hover.
this inside of a d3 event binder points to the element that caused the event to fire. In this case, that would be our circle. we can select this to get the d3 representation of that object, so we can use d3 methods on it. In this case, i’d like to know what color the circle was; i’m going to color my tooltip border with that color, so the tooltip looks like it belongs to the line, even though i’ve only got 1 tooltip for my entire graph.

We’ll grab the circle (though we could do this inline, maybe we want the circle for other things later; note that we could have used this reference to the circle to get its cx/cy for positioning our tooltip!), and use its Fill to Stroke the rect. We’ll need to select the rect, obviously, to do so.

let circle = d3.select(this);
tooltip.select("rect")
       .attr("stroke", circle.style("fill"))

So now my tooltip will let me know what line i’m hovering over, too.

You’ve got the basics of a tooltip now. There are a few extra enhancements that i think you’ll come across you feel you need if you play with this code. I stuck with some basics here, even in the “fancy” section.

1 Like

Thanks a lot, it looks like I have to add the on. statement to every data. Gonna try and add durations to it and move the tooltip a bit.

When I changed to percentages it still gives me the absolute values, probably a matter of selecting.

The last code about the circle gives me an error message, I placed it below all the other code.

Well keep in mind that even though we’re changing the axis labels, the underlying data is not being modified.

If you want to show the percentages instead, you’ll basically just steal the if code we used elsewhere to tell the axis what to render, and apply it to the text command.

I added a transition, duration, but all it does is taking a few moments to fade from the original colour to a lesser opacity. I wanted to change the duration until it disappears, right now the tooltip disappears instantly when the mouse moves away from a circle.

The “IF” code into the movetooltip function?

                if (vv == "percentage") {
                    console.log("In Prozent angegeben");
                    const maxval = d3.max(dataset1, x => x[1]);
                    axLft.call(
                    d3.axisLeft(newY)
                    .tickFormat(d => Math.round(d*100/maxval) + "%")
                    )                    

                } else if (vv == "absoluteValue") {
                    console.log("In Zahlen angegeben");

                    const maxval = d3.max(dataset1, x => x[1]);
                    axLft.call(
                    d3.axisLeft(newY)

That would probably be because “visibility” doesnt have a continual transition to ease through - you are just “visible” or “hidden”, there’s no in-between.
Might need to switch from visibility to opacity, and go from 1 to 0 (which is a continuous range and can be eased through).

If the dropdown has “Percentage” selected, make the text element say Math.round(d[1]*100/maxval) + "%" . If it doesnt, just make it say d[1].

This works, had to repeat the declarations:

        function movetooltip(d,i) {
            tooltip.attr("transform","translate("+(110+newX(d[0]))+","+(70+newY(d[1]))+")")
                   .style("visibility","visible")

            var select = document.getElementById('changeAxisYLabel');
			var option = select.options[select.selectedIndex];

            let vv = option.value;

            const maxval = d3.max(dataset1, x => x[1]);

            tooltip.select("text")
            .text((vv == "percentage") ? d[0] + " : "+ Math.round(d[1]*100/maxval) + "%" : d[0] + " : "+ d[1])
           }

Will save a new file for my next task cause there I wanna have a vertical line appear where the mouse is and where it intersects with a graph line/circle it should display that tooltip. Like here, the complex version:

I sometimes see that an invisible rectangle is appended, what is that good for? My program works without so far.

I… am going to need more information than that to be able to begin to formulate an answer.

Where is the rect in the DOM? What attributes does it have? What does your current code look like?

My code still looks like this:

It was a general question.
This “rect” example is shown here:

 svg.append("rect")
        .attr("width", width)
        .attr("height", height)
        .style("fill", "none")
        .style("pointer-events", "all")
        .on("mouseover", function() { focus.style("display", null); })
        .on("mouseout", function() { focus.style("display", "none"); })
        .on("mousemove", mousemove);

So there’s currently 2 rects in your actual code; the first is the rect added to the tooltip group i created:

            tooltip.append("rect") // <- Here
               .attr("width",70)
               .attr("height",30)
               .attr("fill", "#FFFFFF")
               .attr("stroke", "black")
               .attr("rx",10);

and the other is one generated by the clipPath to define the drawable area of your chart:

            var clip = svg.append("defs")
               .append("SVG:clipPath")
               .attr("id", "clip")
               .append("SVG:rect") // <-- Here
               .attr("width", width)
               .attr("height", height)
               .attr("x", 100)
               .attr("y", 100);

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.