Home > OS >  React/D3 - Adding tooltip to linechart
React/D3 - Adding tooltip to linechart

Time:11-16

I want to add a tooltip to my existing linechart. I tried it in multiple ways but I cant't get it to work

I would like to end up with a tooltip like this: https://d3-graph-gallery.com/graph/interactivity_tooltip.html

I tried it multiple ways and now have the following:

g.append("g")
      .selectAll("dotDemand")
      .data(dataLine.filter(d => d.valueLine))
      .enter()
      .append("circle")
        .attr("cx", function(d) { return plotDate(d.date) } )
        .attr("cy", function(d) { return y(d.valueLine) } )
        .attr("r", 4)
        .attr("fill", "#439DD2")
        .attr("stroke-width", 0)
        .on('mouseover', function (d, i) {
          d3.select(this).transition()
               .duration('100')
               .attr("r", 10)
               .attr("stroke", "#000")
               .attr("stroke-width", 5);
               div.transition()
               .duration('50')
               .style("opacity", 0);
          div.html("$"   d3.format(".2f")(d.date))
              .style("left", plotDate(d.valueLine)   10   "px")
              .style("top", y(d.date)    "px");
     })
        .on('mouseout', function (d, i) {
          d3.select(this).transition()
               .duration('200')
               .attr("r", 4)
               .attr("fill", "#439DD2")
               .attr("stroke-width", 0);
               //makes div disappear
          div.transition()
              .duration('200')
              .style("opacity", 0);
     });

But I'm doing something wrong.

My data looks like this:

Date,value
5/31/2022,12
6/1/2022,23
6/2/2022,24
6/3/2022,26
6/4/2022,32
6/5/2022,32
6/6/2022,35
6/6/2022,32
6/7/2022,31
6/8/2022,30
6/8/2022,21
6/10/2022,23
6/11/2022,13
6/12/2022,13
6/13/2022,14
6/14/2022,
6/15/2022,
6/16/2022,
6/17/2022,
6/18/2022,
6/19/2022,
6/20/2022,
6/21/2022,
6/22/2022,
6/23/2022,
6/24/2022,
6/25/2022,
6/26/2022,
6/27/2022,

And my full d3 linechart component looks like this:

import * as d3 from 'd3';
import { useEffect, useState } from 'react';
import csvFile2 from '../../data/testData.csv';

const BasicLinechart = (props) => {

  const [dataLine, setDataLine] = useState([]);
  console.log(csvFile2)
  const { width, height } = props;

  useEffect(() => {
    if (dataLine.length > 0) {
      drawLineChart();
    } else {
      getCSVDataLine();
    }
  }, [dataLine]);

  // Read in csv
  const getCSVDataLine = async () => {
    const tempData = [];

    await d3.csv(
      csvFile2,
      function (d) {
        tempData.push({
          date: d3.timeParse("%m/%d/%Y")(d.date),
          valueLine: Number(d.value),
        });
      }
    );
    setDataLine(tempData);
  };

  const drawLineChart = () => {

    const parseDate = d3.timeParse("%m/%d/%Y"),
      formatDay = d3.timeFormat("%d"),
      formatMonth = d3.timeFormat("%b");

    var div = d3.select("body").append("div")
      .attr("class", "tooltip")
      .style("opacity", 0);

    // set margins to have enough space for the axis
    var margin = { top: 20, left: 50 };

    // Add X axis in date format: it's a double x-axis with month   year
    var xDate = d3.scaleTime()
    .domain(d3.extent(dataLine, function(d) { return d.date; }))
    .range([ 0, width ])
    
    var xAxisMonth = d3.axisBottom()
    .scale(xDate)
    .ticks(d3.timeDay.every(1))
    .tickFormat(formatMonth);
    
    // Define 'div' for tooltips
    var div = d3.select("svg")
        .append("div")  // declare the tooltip div 
        .attr("class", "tooltip")              // apply the 'tooltip' class
        .style("opacity", 0);    
  
    // create the chart area
    const svg = d3.select('.svg-basic')
    
    svg.selectAll("*").remove();

    // set base for axes
    var g = svg.append('g')
      .attr('transform', 'translate('   margin.left   ','   margin.top   ')')
      .attr("class", "legendOrdinal");

    g.append("g")
      .attr("transform", "translate(0,"   (height   40)   ")")
      .call(xAxisMonth)
      .selectAll("text")
      .style("font-size", "12px")
      .attr("stroke", "#595959");

    // styling of the axes (possible later in styling sheet)
    g.selectAll("path")
      .style("stroke", "#595959")

    g.append("g")
      .select(".domain")
      .attr("stroke", "#595959")
      .attr("stroke-width", "6")
      .attr("opacity", ".6");

    // Add Y axis
    var y = d3.scaleLinear()
      .domain([0, d3.max(dataLine, function (d) { return   Math.max(d.valueLine); })])
      .range([height, 10]);
    
    // scale x axis for month and day
    var plotDate = d3.scaleTime()
      .domain(d3.extent(dataLine, function (d) { return d.date; }))
      .range([0, width])

    // Styling y
    g.append("g")
      .call(d3.axisLeft(y))
      .attr("stroke", "#595959")
      .selectAll("text")
      .style("font-size", "12px")
      .attr("stroke", "#595959");

    // set lines coordinates
    const valueLinePlot = d3.line()
      .x(function (d) { return plotDate(d.date) })
      .y(function (d) { return y(d.valueLine) })
      .curve(d3.curveNatural)

    // Add the line for prediction
    g.append("path")
      .datum(dataLine)
      .attr("fill", "none")
      .attr("stroke", "#439DD2")
      .attr("stroke-width", 2.5)
      .attr("d", valueLinePlot)
    
      g.append("g")
      .selectAll("dotDemand")
      .data(dataLine.filter(d => d.valueLine))
      .enter()
      .append("circle")
        .attr("cx", function(d) { return plotDate(d.date) } )
        .attr("cy", function(d) { return y(d.valueLine) } )
        .attr("r", 4)
        .attr("fill", "#439DD2")
        .attr("stroke-width", 0)
        .on('mouseover', function (d, i) {
          d3.select(this).transition()
               .duration('100')
               .attr("r", 10)
               .attr("stroke", "#000")
               .attr("stroke-width", 5);
               div.transition()
               .duration('50')
               .style("opacity", 0);
          div.html("$"   d3.format(".2f")(d.date))
              .style("left", plotDate(d.valueLine)   10   "px")
              .style("top", y(d.date)    "px");
     })
        .on('mouseout', function (d, i) {
          d3.select(this).transition()
               .duration('200')
               .attr("r", 4)
               .attr("fill", "#439DD2")
               .attr("stroke-width", 0);
               //makes div disappear
          div.transition()
              .duration('200')
              .style("opacity", 0);
     });

  // text label for the y axis
    g.append("text")      
    .attr("x", '-11%' )
    .attr("y",  '-6%' )
    .style("text-anchor", "right")
    .style("transform", "rotate(-90deg)")
    .text("Total Units (#)")
    .attr("stroke", "#919191")
    .selectAll("text")
    .style("font-size", "10px")

// text label for the x axis
g.append("text")      
    .attr("x", '73%' )
    .attr("y",'70%' )
    .style("text-anchor", "right")
    .text("Date")
    .attr("stroke", "#919191")
    .selectAll("text")
    .style("font-size", "10px")

  }

  return (
    <div >
      <svg className="svg-basic" width="800px" height="600px" />
    </div>
  )

}
export default BasicLinechart;

I think that it might be something the referencing to svg-basic / svg/g or something, but I can't figure it out.

CodePudding user response:

you have to create one absolute container first

i saw your code you have defined tooltip two times, first remove those two and this

let tooltip = d3
      .select("#tooltipContainer")
      .style("position", "absolute")
      .style("top", 0)
      .style("left", 0)
      .style("display", "none");

here is you want to show tooltip div when you hover the dots on line chart then try like this

g.append("g")
      .selectAll("dotDemand")
      .data(dataLine.filter(d => d.valueLine))
      .enter()
      .append("circle")
        .attr("cx", function(d) { return plotDate(d.date) } )
        .attr("cy", function(d) { return y(d.valueLine) } )
        .attr("r", 4)
        .attr("fill", "#439DD2")
        .attr("stroke-width", 0)
        .on('mouseover', function (d, i) {
          d3.select(this).transition()
               .duration('100').attr("r", 10).attr("stroke", "#000").attr("stroke-width", 5);
          tooltip.transition().duration(0).style("display", "block");
          tooltip.html("$"   d3.format(".2f")(d.date))
          .style("left", d.pageX   10   "px")
          .style("top", d.pageY  10   "px");
         })
          .on("mouseout", (e) => {
          d3.select(this).transition().attr("r", 4);

           tooltip.transition().duration(0);
           tooltip
                  .style("left", "0px")
                  .style("top", "0px")
                  .style("display", "none");
  })

this the code you have to add but remember you are using the react right you have to predefine your jsx elements

Note- don't ever use append method in react instead of append you have to use select , join methods if you use the append method you will get duplicated copy of graph each time when your useEffect and useState changes

i will provide my recent line graph live code to you if you need you can modify according you your needs

here is my codepan javascript code https://codepen.io/codingdarci/pen/ZERpNOG

or you want this in same code in react you can mention me msg i will paste it here

  • Related