Home > Enterprise >  d.label is reading undefined on D3 Line chart
d.label is reading undefined on D3 Line chart

Time:10-18

I'm quite new to d3 and found this tutorial from Ms. Urvashi Link Here

I am now able to see the plotted line chart however the mouse tooltip would cause an undefined error. It's undefined and unable to find the root cause of this, since the data is totally randomly generated even console.log() shows undefined.

So here's the error:

TypeError: Cannot read properties of undefined (reading 'label')
SVGRectElement.mousemove
http://localhost:3000/static/js/main.chunk.js:269:54
  266 |   const x0 = bisect(data, xScale.invert(xPos));
  267 |   const d0 = data[x0];
  268 |   console.log(data[x0]);
> 269 |   focus.attr('transform', `translate(${xScale(d0.label)},${yScale(d0.value)})`);
      |                                                  ^  270 |   tooltip.transition().duration(300).style('opacity', 0.9);
  271 |   tooltip.html(d0.tooltipContent || d0.label).style('transform', `translate(${xScale(d0.label)   30}px,${yScale(d0.value) - 30}px)`);
  272 | } // d3.select('#container')

I've been trying to figure out why the parameter d is undefined along with its attributes, however the graph renders except when hovering on the graph.

Here's the raw code:

import React, { useEffect } from 'react';
import './Linechart.scss';
import * as d3 from 'd3';

const Linechart = (props) => {
    const { data, width, height } = props;
    
    useEffect(() => {
        drawChart();
    }, [data])

    const drawChart = () =>  {
        //clears previous chart render
        d3.select('#container')
            .select('svg')
            .remove();

        d3.select('#container')
            .select('.tooltip')
            .remove();

        const margin = { top: 50, right: 50, bottom: 50, left: 50 };
        const yMinValue = d3.min(data, d => d.value);
        const yMaxValue = d3.max(data, d => d.value);
        const xMinValue = d3.min(data, d => d.label);
        const xMaxValue = d3.max(data, d => d.label);

        const svg = d3
            .select('#container')
            .append('svg')
            .attr('width', width   margin.left   margin.right)
            .attr('height', height   margin.top   margin.bottom)
            .append('g')
            .attr('transform', `translate(${margin.left},${margin.top})`);

        const xScale = d3 //x-axis
            .scaleLinear()
            .domain([xMinValue, xMaxValue])
            .range([0, width]);

        const yScale = d3 //y-axis
            .scaleLinear()
            .range([height, 0])
            .domain([0, yMaxValue]);

        const line = d3
            .line()
            .x(d => xScale(d.label))
            .y(d => yScale(d.value))    
            .curve(d3.curveMonotoneX);   

        svg
            .append('g')
            .attr('class', 'grid')
            .attr('transform', `translate(0,${height})`)
            .call(
            d3.axisBottom(xScale)
                .tickSize(-height)
                .tickFormat(''),
            );

        svg
            .append('g')
            .attr('class', 'grid')
            .call(
                d3.axisLeft(yScale)
                .tickSize(-width)
                .tickFormat(''),
            );
        svg
            .append('g')
            .attr('class', 'x-axis')
            .attr('transform', `translate(0,${height})`)
            .call(d3.axisBottom().scale(xScale).tickSize(15));
        svg
            .append('g')
            .attr('class', 'y-axis')
            .call(d3.axisLeft(yScale));
        svg
            .append('path')
            .datum(data)
            .attr('fill', 'none')
            .attr('stroke', '#f6c3d0')
            .attr('stroke-width', 4)
            .attr('class', 'line') 
            .attr('d', line);

        const focus = svg
            .append('g')
            .attr('class', 'focus')
            .style('display', 'none');

        focus.append('circle').attr('r', 5).attr('class', 'circle');

        const tooltip = d3
            .select('#container')
            .append('div')
            .attr('class', 'tooltip')
            .style('opacity', 0);

        svg
            .append('rect')
            .attr('class', 'overlay')
            .attr('width', width)
            .attr('height', height)
            .style('opacity', 0)
            .on('mouseover', () => {
                focus.style('display', null);
            })
            .on('mouseout', () => {
                tooltip
                    .transition()
                    .duration(300)
                    .style('opacity', 0);
            })
            .on('mousemove', mousemove);

        function mousemove(event) {
            const bisect = d3.bisector(d => d.label).left;
            const xPos = d3.pointer(this)[0]; 
            const x0 = bisect(data, xScale.invert(xPos));
            const d0 = data[x0];
            console.log(data[x0]);
            
            focus.attr(
                'transform',
                `translate(${xScale(d0.label)},${yScale(d0.value)})`,
            );

            tooltip
                .transition()
                .duration(300)
                .style('opacity', 0.9);
            tooltip
                .html(d0.tooltipContent || d0.label)
                .style(
                    'transform',
                    `translate(${xScale(d0.label)   30}px,${yScale(d0.value) - 30}px)`,
            );
        }

    }
   

    return(
        <div id="container"></div>
    )
}


export default Linechart;

And the data is generated through this function:

const [data, setData] = useState([]);

    const regenerateData = () => {
        var chartData = [];
        for (let index = 0; index < 20; index  ) {
            var value = Math.floor(Math.random() * index   3);
            var dataObject = {
                label: index,
                value,
                tooltipContent: 
                `<b>x: </b> ${index} <br />
                <b>y: </b> ${value}`,
            }
            chartData.push(dataObject); 

        }
        setData(chartData);
    }

If it is only a parameter, why is it needed to be defined? I'm quite puzzled about this, I am a bit of a slow learner so I'm sorry if I may have to ask a handful of questions.

CodePudding user response:

Found it, Finally after countless console.log() I was able to figure it out then.

const xPos = d3.pointer(this)[0]; 
const x0 = bisect(data, xScale.invert(xPos));

Seems like this is an old method and I just happen to replace mouse with pointer to migrate however when I console.log(xPos), its just throwing undefined like why? It's just basically needed to be like this.

const xPos = d3.pointer(event); 
const x0 = bisect(data, xScale.invert(xPos[0]));
const d0 = data[x0];

Now the tooltip circle shows up onhover with auto identify coordinates.

  • Related