D3 line chart component not being updated inside useEffect in React

Viewed 317

I'm receiving data for my line chart from API. Every time data updated it doesn't re-draw with a smooth transition but instead it draws new lines on the top of the existing chart. It looks messy. I don't have lifecycles as I'm sing useEffect. Is it something to do with line().remove().merge()? What am I doing wrong?

CodeSandbbox

useEffect(() => {
    const svg = d3
      .select(svgRef.current)
      .append("g")
      .attr("transform", "translate(0," + -130 + ")");
    const xScale = d3.scaleTime();
    const yScale = d3.scaleLinear();
    const xAxisDateFormat = d3.timeFormat("%b");
    
    // line generator
    const line = d3
      .line()
      .curve(d3.curveCatmullRom.alpha(1))
      .x((d) => xScale(new Date(d.date)))
      .y((d) => yScale(d.value));

    // draw line chart
    const lines = svg.append("g").attr("class", "lines");
    lines
      .selectAll(".line-group")
      .data(lineChartData.values)
      .enter()
      .append("g")
      .attr("class", "line-group")
      .append("path")
      .attr("class", "line")
      .attr("stroke", (d, i) => colorScale(d.lineColor))
      .attr("d", (d) => line(d.dataset));
  }, [lineChartData]);
2 Answers

You can just clear your svg content before adding new elements.

Instead of:

const svg = d3
      .select(svgRef.current)
      .append("g")

do:

 const svgEl = d3.select(svgRef.current);

 svgEl.selectAll('*').remove();

 const svg = svgEl
      .append('g')

exit() is missing in your code:

const lines = svg.append("g")
  .classed("lines", true);
const groups = lines
  .selectAll(".line-group")
  .data(lineChartData.values);

const newGroups = groups.enter()
  .append("g")
  .classed("line-group", true);

newGroups.append("path")
  .classed("line", true)
  .attr("stroke", (d, i) => colorScale(d.lineColor))
  .attr("d", (d) => line(d.dataset));

const oldGroups = groups.exit();
oldGroups.remove();
Related