d.label 在 D3 折线图上读取未定义

d.label is reading undefined on D3 Line chart

我是 d3 的新手,从 Urvashi 女士那里找到了这个教程 Link Here

我现在可以看到绘制的折线图,但是鼠标工具提示会导致未定义的错误。它是未定义的,无法找到根本原因,因为数据是完全随机生成的,即使 console.log() 显示未定义。

所以这是错误:

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')

我一直在试图弄清楚为什么参数 d 及其属性未定义,但是图形呈现,除非将鼠标悬停在图形上。

原始代码如下:

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;

并且数据是通过这个函数生成的:

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);
    }

如果只是一个参数,为什么还要定义呢?我对此很困惑,我学得有点慢,所以如果我不得不问一些问题,我很抱歉。

找到了,经过无数次console.log()终于搞明白了

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

似乎这是一种旧方法,我只是碰巧用指针替换了鼠标以进行迁移,但是当我 console.log(xPos) 时,它只是抛出 undefined ,为什么?基本上就是这样。

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

现在,工具提示圆圈会在悬停时显示并带有自动识别坐标。