0

If I use d3@7 I have nice labeling but no circle with toltip moves along the line as it supposed by code. A piece of circle is available in the upper left corner. If I use d3@6 I have absurd labeling and circle and tooltip with strange coordinates in it stick on the middle of the line. Other versions do not work at all.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatable" content="ie=edge">
    <title>Document</title>
  </head>
  <div id="container"></div>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
    <script type="module">
      // Curved line declaration
      const data = [{xpoint: 10, ypoint: 0.2},
            {xpoint: 4, ypoint: 1},
            {xpoint: 3, ypoint: 2},
            {xpoint: 2, ypoint: 3},
            {xpoint: 1, ypoint: 10}];
// Declare the chart dimensions and margins.
const width = 640;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;

// Declare the x (horizontal position) scale.
      const x = d3.scaleLog()
        .domain([0.2, 60])
    .range([marginLeft, width - marginRight]);

// Declare the y (vertical position) scale.
const y = d3.scaleLog()
    .domain([0.1, 100])
    .range([height - marginBottom, marginTop]);

  // Declare the line generator.
      const line = d3.line()
    .x((d) => x(d.xpoint))
    .y((d) => y(d.ypoint))
        .curve(d3.curveCatmullRom.alpha(0.5));
      
// Create the SVG container.
const svg = d3.create("svg")
     .attr("width", width)
      .attr("height", height);
      

// Add the x-axis.
svg.append("g")
    .attr("transform", `translate(0,${height - marginBottom})`)
    .call(d3.axisBottom(x));

// Add the y-axis.
svg.append("g")
    .attr("transform", `translate(${marginLeft},0)`)
    .call(d3.axisLeft(y));

  // Append a path for the line.
  svg.append("path")
    .attr("fill", "none")
      .attr("stroke", "steelblue")
      .attr("stroke-width", 1.5)
    .attr("d", line(data));

 // This allows to find the closest X index of the mouse:
  const bisect = d3.bisector(function(d) { return d.xpoint; }).left;

  // Create the circle that travels along the curve of chart
  const focus = svg
    .append('g')
    .append('circle')
      .style("fill", "none")
      .attr("stroke", "black")
      .attr('r', 8.5)
      .style("opacity", 0)

 // Create the text that travels along the curve of chart
  const focusText = svg
    .append('g')
    .append('text')
      .style("opacity", 0)
      .attr("text-anchor", "left")
      .attr("alignment-baseline", "middle")

 // Create a rect on top of the svg area: this rectangle recovers mouse position
  svg.append('rect')
    .style("fill", "none")
    .style("pointer-events", "all")
    .attr('width', width)
    .attr('height', height)
    .on('mouseover', mouseover)
    .on('mousemove', mousemove)
    .on('mouseout', mouseout);
      
// What happens when the mouse move -> show the annotations at the right positions.
  function mouseover() {
      focus.style("opacity", 1)
      focusText.style("opacity",1)
  };

function mousemove() {
    // recover coordinate we need
    const x0 = x.invert(d3.pointer(this)[0]);
    // console.log(x0)
    const i = bisect(data, x0, 1);
    //console.log(i)
    const selectedData = data[i]
    focus
      .attr("cx", x(selectedData.xpoint))
      .attr("cy", y(selectedData.ypoint))
    focusText
      .html("x:" + selectedData.xpoint + "  -  " + "y:" + selectedData.ypoint)
      .attr("x", x(selectedData.xpoint)+15)
    .attr("y", y(selectedData.ypoint))
};
      function mouseout() {
    focus.style("opacity", 0)
      focusText.style("opacity", 0)
      };

    
// Append the SVG element.
      container.append(svg.node());

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

1 Answer 1

0

A couple issues:

1 - d3.pointer(this) is no longer valid. this used to represent the event, now the event is passed as an argument to the mousemove callback:

function mousemove(event) {
    const x0 = x.invert(d3.pointer(event)[0]);
    ...

2 - your bisector function is wrong. Your data is not sorted ascending. To handle the descending sort, the correct function would be:

const bisect = d3.bisector(function (d, x) {
    return x - d.xpoint;
}).left;

Putting it all together:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatable" content="ie=edge" />
    <title>Document</title>
  </head>
  <div id="container"></div>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
    <script type="module">
      // Curved line declaration
      const data = [
        { xpoint: 10, ypoint: 0.2 },
        { xpoint: 4, ypoint: 1 },
        { xpoint: 3, ypoint: 2 },
        { xpoint: 2, ypoint: 3 },
        { xpoint: 1, ypoint: 10 },
      ];
      // Declare the chart dimensions and margins.
      const width = 640;
      const height = 400;
      const marginTop = 20;
      const marginRight = 20;
      const marginBottom = 30;
      const marginLeft = 40;

      // Declare the x (horizontal position) scale.
      const x = d3
        .scaleLog()
        .domain([0.2, 60])
        .range([marginLeft, width - marginRight]);

      // Declare the y (vertical position) scale.
      const y = d3
        .scaleLog()
        .domain([0.1, 100])
        .range([height - marginBottom, marginTop]);

      // Declare the line generator.
      const line = d3
        .line()
        .x((d) => x(d.xpoint))
        .y((d) => y(d.ypoint))
        .curve(d3.curveCatmullRom.alpha(0.5));

      // Create the SVG container.
      const svg = d3.create('svg').attr('width', width).attr('height', height);

      // Add the x-axis.
      svg
        .append('g')
        .attr('transform', `translate(0,${height - marginBottom})`)
        .call(d3.axisBottom(x));

      // Add the y-axis.
      svg
        .append('g')
        .attr('transform', `translate(${marginLeft},0)`)
        .call(d3.axisLeft(y));

      // Append a path for the line.
      svg
        .append('path')
        .attr('fill', 'none')
        .attr('stroke', 'steelblue')
        .attr('stroke-width', 1.5)
        .attr('d', line(data));

      // This allows to find the closest X index of the mouse:
      const bisect = d3.bisector(function (d, x) {
        return x - d.xpoint;
      }).left;

      // Create the circle that travels along the curve of chart
      const focus = svg
        .append('g')
        .append('circle')
        .style('fill', 'none')
        .attr('stroke', 'black')
        .attr('r', 8.5)
        .style('opacity', 0);

      // Create the text that travels along the curve of chart
      const focusText = svg
        .append('g')
        .append('text')
        .style('opacity', 0)
        .attr('text-anchor', 'left')
        .attr('alignment-baseline', 'middle');

      // Create a rect on top of the svg area: this rectangle recovers mouse position
      svg
        .append('rect')
        .style('fill', 'none')
        .style('pointer-events', 'all')
        .attr('width', width)
        .attr('height', height)
        .on('mouseover', mouseover)
        .on('mousemove', mousemove)
        .on('mouseout', mouseout);

      // What happens when the mouse move -> show the annotations at the right positions.
      function mouseover() {
        focus.style('opacity', 1);
        focusText.style('opacity', 1);
      }

      function mousemove(event) {
        const x0 = x.invert(d3.pointer(event)[0]);
        const i = bisect(data, x0);
        const selectedData = data[i];
        if (!selectedData) return;
        focus
          .attr('cx', x(selectedData.xpoint))
          .attr('cy', y(selectedData.ypoint));
        focusText
          .html(
            'x:' + selectedData.xpoint + '  -  ' + 'y:' + selectedData.ypoint
          )
          .attr('x', x(selectedData.xpoint) + 15)
          .attr('y', y(selectedData.ypoint));
      }
      function mouseout() {
        focus.style('opacity', 0);
        focusText.style('opacity', 0);
      }

      // Append the SVG element.
      container.append(svg.node());
    </script>
  </body>
</html>

3
  • As I recall my struggling the first issue, it is d3.mouse(this) is no longer valid. May you show how to change step parameter, so to speak, between consecutive tooltips? Thank you.
    – Igor
    Commented Jul 2 at 16:07
  • In versions prior to V6 this was the event in the callback. In later versions this is the element on which the event happens. And I don't understand your follow-up question.
    – Mark
    Commented Jul 2 at 18:42
  • Thank you for explaining "this". My follow-up question was as to obtain coordinates along the curve with getTotalLength and getPointAtLength methods. Just learn of them.
    – Igor
    Commented Jul 3 at 3:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.