You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+D3散点图跳过空值无效问题求助

解决D3折线图散点跳过空值的问题

问题说明

我有一个含空单元格的数据集,希望在带散点的折线图中不绘制空值对应的元素。折线部分已通过.defined()方法成功跳过空值,但散点部分尝试同样方法时出现错误:

Uncaught TypeError: g.append(...).selectAll(...).data(...).enter(...).append(...).defined is not a function

同时怀疑g.append("svg")部分存在问题,需要修正。

数据格式

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,

折线跳过空值的有效代码

const linevalue = d3.line()
  .defined(function(d) { return d.value; })
  .x(function(d) { return x(d.date) })
  .y(function(d) { return y(d.value) })

散点部分错误代码

g.append("svg")
.selectAll("dotDemand")
.data(data)
.enter()
.append("circle")
.defined(function(d) { return d.value; })  // <<<< 此处尝试用同样方法
.attr("cy", function(d) { return y(d.value) } )
.attr("cx", function(d) { return x(d.date) } )
.attr("r", 5)
.attr("fill", "#7EE0FA")

完整代码

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

const BasicLinechart=(props)=>{

const [data,setData]=useState([]);
console.log(csvFile)

const {width,height} =props;

useEffect(()=>{
if(data.length>0){
drawChart();
}else{
getCSVData();
}
},[data]);

const getCSVData=async()=>{
const tempData=[];

await d3.csv(
csvFile,
function(d){
tempData.push({
date:d3.timeParse("%m/%d/%Y")(d.DATE),
month:d3.timeParse("%m/%d/%Y")(d.DATE),
value:Number(d.VALUE),
});
}
);
setData(tempData);
console.log("1");

console.log(data.date)
};

const drawChart=()=>{

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

//create the chart area
const svg=d3.select('.svg-canvas')
svg.selectAll("*").remove();
var margin={top:20,left:30};

//Add X axis --> it is a date format
var x=d3.scaleTime()
.domain(d3.extent(data,function(d){return d.date;}))
.range([0,width])

var x2=d3.scaleTime()
.domain(d3.extent(data,function(d){return d.date;}))
.range([0,width]);

var g=svg.append('g')
.attr('transform','translate('+margin.left+','+margin.top+')')
.attr("class","legendOrdinal");

var xAxisDay=d3.axisBottom()
.scale(x)
.ticks(d3.timeDay.every(1))
.tickFormat(formatDay);

g.append("g")
.attr("transform","translate(0,"+height+")")
.attr("stroke","#595959")
.attr("font-family","cursive")
.call(xAxisDay)
.selectAll("text")
.style("font-size","12px")

g.selectAll("path")
.style("stroke","#595959")

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

var xAxisMonth=d3.axisBottom()
.scale(x2)
.ticks(d3.timeMonth.every(1))
.tickFormat(formatMonth);


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

//Add Y axis
var y=d3.scaleLinear()
.domain([0,d3.max(data,function(d){return +Math.max(d.value);})])
.range([height,10]);

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


//set lines coordinates
const linevalue=d3.line()
.defined(function(d){return d.value;})
.x(function(d){return x(d.date)})
.y(function(d){return y(d.value)})

//Add the line & points for value
g.append("path")

.datum(data)
.attr("fill","none")
.attr("stroke","#7EE0FA")
.attr("stroke-width",2)
.attr("d",linevalue)

g.append("svg")
.selectAll("dotDemand")
.data(data)
.enter()
.append("circle")
//.defined("cy",function(d){return y(d.value);})
.attr("cy",function(d){return y(d.value)})

.attr("cx",function(d){return x(d.date)})
.attr("r",5)
.attr("fill","#7EE0FA")
.on('mouseover',function(d,i){
d3.select(this).transition()
.duration('100')
.attr("r",10);
div.transition()
.duration('50')
.style("opacity",0);
div.html("$"+d3.format(".2f")(d.date))
.style("left",x(d.value)+10+"px")
.style("top",y(d.date)+"px");
})
.on('mouseout',function(d,i){
d3.select(this).transition()
.duration('200')
.attr("r",5);
//makes div disappear
div.transition()
.duration('200')
.style("opacity",0);
});
}

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

export default BasicLinechart;

解决方案

1. 理解.defined()的作用范围

.defined()是D3**图形生成器(如line、area)**的专属方法,用来告诉生成器哪些数据点需要被包含在路径中,它不能直接用于DOM元素(比如circle)的链式调用,这是报错的核心原因。

对于散点,需要提前过滤掉空值数据,只给有有效value的数据创建circle元素。

2. 修正g.append("svg")的错误

g已经是svg容器中的分组元素(<g>),不需要再嵌套<svg>标签,否则会导致坐标系统混乱,直接在g上选择和创建circle即可。

3. 修正后的散点代码

// 直接在g上选择circle元素,无需嵌套svg
g.selectAll("circle")
  // 过滤掉value为空/NaN的数据
  .data(data.filter(d => d.value))
  .enter()
  .append("circle")
  .attr("cy", function(d) { return y(d.value) })
  .attr("cx", function(d) { return x(d.date) })
  .attr("r", 5)
  .attr("fill", "#7EE0FA")
  .on('mouseover',function(d,i){
    d3.select(this).transition()
      .duration('100')
      .attr("r",10);
    div.transition()
      .duration('50')
      .style("opacity",1); // 修正:设为1才会显示tooltip
    div.html("$"+d3.format(".2f")(d.value)) // 修正:显示数值而非日期
      .style("left", (d3.event.pageX + 10) + "px") // 用鼠标位置定位更准确
      .style("top", (d3.event.pageY - 10) + "px");
  })
  .on('mouseout',function(d,i){
    d3.select(this).transition()
      .duration('200')
      .attr("r",5);
    div.transition()
      .duration('200')
      .style("opacity",0);
  });

额外优化点

  • tooltip部分:之前错误显示了日期d.date,改为显示数值d.value;同时用d3.event.pageX/pageY定位,避免比例尺转换导致的位置偏差。
  • 数据过滤:data.filter(d => d.value)足够过滤空值,因为csv中空值转成Number后是NaN,在布尔判断中会被自动排除。

内容的提问来源于stack exchange,提问作者user2133561

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 17:20:44