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
相关产品推荐
相关产品推荐

