Vertical line and tooltip wherever the line hits a data point

Hello, I am back after a long sickness.

My next task is to draw a vertical line wherever the mouse is, when this line hits a data point of a graph it shows the tooltip. I already discovered that you have to use bisector for that, so will try something and get back to this topic if I am stuck.

So I’m trying to add a line that moves with the mouse and enhance the tooltip.

Using code from here:

https://vizartpandey.com/line-chart-how-to-show-data-on-mouseover-using-d3-js/

Have to use invert and bisect somehow, my case is more complicated than those examples given.
I thought I have defined circle but it says circle not defined.

A bit like here:
https://stackoverflow.com/questions/53595181/how-do-i-have-a-line-follow-the-mouse-on-hover-but-also-have-zoom-in-chart
https://jsfiddle.net/zkdxrtuc/8/

I copied this code 1:1 into my editor (upgraded to d3.v5) and both programs give me error messages.

Uncaught TypeError: Cannot read properties of undefined (reading ‘scale’) at Bisect.html:45:22

Bisect2.html:100 Uncaught TypeError: Cannot read properties of undefined (reading ‘x’)
at SVGRectElement.mousemove (Bisect2.html:100:38)
at SVGRectElement. (d3.v5.js:1526:16)

My aim was to edit those first to include a line instead of a circle and then put it into my program shown in Codepen.

So right now, your mousemove code is looking for something called circle, but that’s not defined. (It was only defined in terms of the movetooltip function, because that function needed to know what “circle” was being hovered over.

Even if it did resolve the reference to circle, it would then complain that it cant resolve a function called x, because that should be your scale. It would also complain that your data does not contain an x property (because your data isnt an object, its a 2 dimensional array).

I’m… somewhat unclear what you’re specifically trying to do at this exact moment, but lets try this;

Instead of moving the circle, tell your VertLine to move itself on the x axis.

Things I had to do to get your code to work correctly: (No, i’m not giving you the code yet, but pointers :P)

  • The mousemove event should instead by a pointermove event bound to the SVG itself.
  • You will need a bisector with an accessor function pointing at the x value of the two dimensional data array.
  • The mouse, now bound to the SVG, is not translated; so you’ll need to adjust the x-coordinate received from the mouse to compensate.
  • Because the SVG is wider than your dataset, you will need to handle the edge case that the bisector returns a value that would be at the end of the new array.

Bonus, which may be helpful later:

  • I used a line instead of a rect. Because it’s a line.

My original code in Codepen or the copied code from those two examples? My one is more important.

The code that you Codepenned in post 2.

Most code I have seen place an invisible rect over the graph for all the fancy graphic actions.

First thing I wanna do is display a vertical line wherever the mouse is. Like here:

You can see in my result that I added a static line.

No tooltip yet.

Copied that part of the code here:

var mouseG = svg.append("g")
      .attr("class", "mouse-over-effects");

mouseG.append("path") // this is the black vertical line to follow mouse
      .attr("class", "mouse-line")
      .style("stroke", "black")
      .style("stroke-width", "1px")
      .style("opacity", "0");

var lines = focus.selectAll('path');

var mousePerLine = mouseG.selectAll('.mouse-per-line')
      .data(d3.range(lines.length))
      .enter()
      .append("g")
      .attr("class", "mouse-per-line")
      .attr('pointer-events', 'none');

// the circle
mousePerLine.append("circle")
  .attr("r", 7)
  
  .style("stroke", function(d) {
    return 'red';
  })
  .style("fill", "none")
  .style("stroke-width", "1px")
  .style("opacity", "0");

function showLine(){
   d3.select(".mouse-line")
      .style("opacity", "0");
 }
 function hideLine(){
    d3.select(".mouse-line")
      .style("opacity", "1");
 }
  
svg.select(".zoom")
  .on('mouseenter', showLine)
  .on('mouseleave', hideLine)
  .on('mousemove', function() { // mouse moving over canvas
    var mouse = d3.mouse(this);
    //showLine();
    // move the vertical line
    d3.select(".mouse-line")
      .attr("d", function() {
        var d = "M" + (mouse[0] + margin.left) + "," + (height + margin.top);
        d += " " + (mouse[0] + margin.left) + "," + margin.top;
        return d;
      });

You can do it that way too, it was just easier from your current graph construction for me to bind it to the SVG as a whole. shrug either way works.

That JSFiddle example code doesn’t work for me, there is an error message in the D3.

I wanna add this line functionality to the existing one, i.e. keep the current tooltip code for now and only remove it when the line version finally works. Tried this now, no error messages but not working either:

        let VertLine = svg.append('line')
            .attr("transform","translate(150,100)")
            .style("stroke", "black")
            .style("stroke-width", 2)
            .attr("x1", 0)
            .attr("y1", 0)
            .attr("x2", 0)
            .attr("y2", height);

        function mousemove(){
            var x0 = xScale.invert(d3.mouse(this)[0]);
            var i1 = bisect(dataset1, x0, 1);
            var i2 = bisect(dataset2, x0, 1);
            var i3 = bisect(dataset3, x0, 1);
            selectedData1 = dataset1[i1]
            selectedData2 = dataset1[i2]
            selectedData3 = dataset1[i3]
            VertLine
              .attr("cx", xScale(selectedData1.xScale))
              .attr("cy", yScale(selectedData1.yScale))
        }

Well for starters, a line doesnt have a cx or cy

Secondly, that code should be throwing an error, because your datapoint doesnt have a “xScale” attribute.

x1 and y1, that code still references to a rectangle. The code where I took it from uses x as a variable, I used xScale instead.

 var x = d3.scaleLinear()
    .domain([1,100])
    .range([ 0, width ]);
  svg.append("g")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x));

So… let’s tackle the points that I laid out before, one at a time.

Right now, your code binds a function on mousemove, and tries to bind it to:
a nonexistant element class .zoom,
each dot of the red line,
each dot of the blue line,
and each dot of the green line.

Instead, you will need to bind a pointermove event to an existing element.

Trying to combine it with that I found here:

        var maus = d3.select("svg");
        maus.on("mousemove", function () {
            var mouse = d3.mouse(this);
            maus
                .append("line")
                .attr("x", mouse[0])
                .attr("y", mouse[1])
                .attr("stroke", "#039BE5")
                .attr("stroke-width", "1px");

                // VertLine in here
        });

o…kay… i mean, you can keep throwing other code into the mix until its a big jumbled mess, or you can fix what you’ve got now, but…

a line is defined by 4 attributes; x1,x2,y1, and y2. So you will have to define those attributes. A line will be drawn from (x1,y1) to (x2,y2).

I finally got a line drawn but not the way I want it.

You can see it in the pen.

I mean, that’s a pretty neat effect, but not quite what we were going for :wink:

Lets review a bit.

We’ve got this line, which you’re drawing all by itself:

            let VertLine = svg.append('line')
                .attr("transform","translate(150,100)")
                .style("stroke", "black")
                .style("stroke-width", 2)
                .attr("x1", 0)
                .attr("y1", 0)
                .attr("x2", 0)
                .attr("y2", height);

so that goes from 0,0 to 0,700. A nice, vertical line.

                    .attr("x1", mouse[0])
                    .attr("y1", 0)
                    .attr("x2", mouse[1])
                    .attr("y2", height)

Which… let’s say the mouse is at 205, 384. This will then draw a line from 205,0 to 384,700 … well, thats not going to be a vertical line, is it?

  1. What’s true about the first line, which is vertical, and not true about the second, which isn’t? How can that be corrected?

  2. Right now, you’re appending a new line every time the mouse moves. Instead, make it move VertLine.

Use mouse[0] twice.

Still the line isn’t drawn exactly where the mouse is.

Is it that easy? Instead of
maus
.append(“line”)

maus
.move(“VertLine”)?

You wont use maus at all inside of the function. Change the attr’s of VertLine.

It wont draw the line exactly where the mouse is, because of:

Yes, I used

        var maus = d3.select("svg");
        maus.on("mousemove", function () {
            var mouse = d3.mouse(this);
            maus
                .append("line")
                .attr("transform","translate(0,100)")
                .attr("x1", mouse[0])
                .attr("y1", 0)
                .attr("x2", mouse[0])
                .attr("y2", height)
                .attr("stroke", "#000")
                .attr("stroke-width", "1px");

                // VertLine in here
        });

True, it is quite confusing to have maus and mouse.