1

I was trying to display the data under temperature and timestamp column of my csv file on my line d3.js chart line tool tip. I want the respective day and temperature to display on tooltip

This is the error I am getting from console "Uncaught TypeError: Cannot read property 'timestamp' of undefined at SVGPathElement. (index.js:70) at SVGPathElement. (d3.v5.js:2)"

div.html(formatTime((d.timestamp)) + "
" + (+d.temperature))

I expect the date and temperature should display on tooltip

let svg = d3.select('svg'),
  height = +svg.attr('height'),
  width = +svg.attr('width');
  let formatTime = d3.timeFormat("%m/%d/%Y");

const render = data => {
  const title = 'Engagements Over-Time(Total: 946K)';
  const xValue = d => d.timestamp;

  const yValue = d => d.temperature;

  const margin = {top: 60, right: 60, bottom: 88, left: 105};
  const innerwidth = width - margin.left - margin.right;
  const innerheight = height - margin.top - margin.bottom;
  
  const xScale = d3.scaleTime()
   .domain(d3.extent(data, xValue))
    .range([0, innerwidth - 150])
   .nice();
  
 const yScale = d3.scaleLinear()
   .domain([0, d3.max(data, d => d.temperature)])
   .range([innerheight, 0])
   .nice();
    
  const g = svg.append('g')
   .attr('transform', `translate(${margin.left}, ${margin.top+30})`);
    
    let parseDate = d3.timeFormat("%m/%d/%Y");
    const xAxis = d3.axisBottom(xScale)
                .tickFormat(parseDate)
                .tickPadding(5)

                
    // Define the div for the tooltip
  let div = d3.select("body").append("div") 
      .attr("class", "tooltip")    
      .style("opacity", 0)
      .style("display", "null");;

  let yAxisTicFormat = number => d3.format('.1s')(number)
  const yAxis = d3.axisLeft(yScale)
              .tickPadding(35)
              .tickFormat(yAxisTicFormat)
  ;

  const yAxisG = g.append('g').call(yAxis);
  yAxisG.selectAll('.domain').remove();
  
  const xAxisG = g.append('g').call(xAxis)
   .attr('transform', `translate(0, ${innerheight})`);

  xAxisG.select('.domain').remove();
  
  const lineGenerator = d3.line()
   .x(d => xScale(xValue(d)))
   .y(d => yScale(yValue(d)))
   .curve(d3.curveBasis);
   
  g.append('path')
   .attr('class', 'line-path')
    .attr('d', lineGenerator(data))
    .on("mouseover", function(d) {
       div.transition() 
       .duration(500) 
       .style("opacity", 1)
       .style("display", "block");
       console.log(xValue);

       div.html(formatTime((d.timestamp)) + "<br/>" + (+d.temperature)) 
       .style("left", (d3.event.pageX-1) + "px") 
       .style("top", (d3.event.pageY - 28) + "px"); 
      }) 
      
       .on("mouseout", function(d) {
        div.transition() 
        .duration(500) 
        .style("opacity", 0); });;
  
  g.append('text')
   .attr('class', 'title')
    .attr('y', -40)
    .text(title);

};

d3.csv('temperature-in-san-francisco.csv').then(data =>{
  //console.log(data)
    data.forEach(d => {
        d.timestamp =new Date (d.timestamp);
        d.temperature = +d.temperature*1000;
     
    });


    render(data);
    //console.log(data)
        
})

my data

timestamp,temperature

2007-04-23,93.24

2007-04-24,95.35

2007-04-25,98.84

2007-04-26,99.92

Mayor
  • 23
  • 6

1 Answers1

1

It looks like you think a different data point will be returned on the mouseover event depending on where the cursor is touching the line, but it is important to note that that line is just a single svg element defined by a path definition string.

Your data array was passed to the line generator which returned a string defining the line, which was set as the path's d (for definition not data (probably)) property. An svg path element has been created, but no data has been bound to it.

So the reason that

div.html(formatTime((d.timestamp))

is throwing the reference error is that no data has been bound to the path element, so the callback's first parameter (d) is returning undefined.

If you want a user to be able to touch different locations on the line and see data, then you can create points (circles) on that line using the same scales that you used for your line definition. The individual circle elements will have data bound to them.

There are a number of approaches to showing a tooltip on mouseover of a circle on this SO post. The simplest seems to be the one suggested by Lars Kotthoff, which doesn't need a mouseover listener, but instead uses the browser's in-built functionality where it shows the title of an element on-hover (This is from Lar's example):

d3.selectAll("circle")
.data(data)
.enter()
  .append("svg:circle")
  .attr('cx', d => xScale(d.timestamp))
  .attr('cy', d => yScale(d.temperature))
  .attr('r', 3)
  .attr('fill', 'steelblue')
  .append("svg:title")
  .text(function(d) { return d.x; });
lemming
  • 1,753
  • 14
  • 12