How to remove old data rect from d3 chart

Viewed 122

##ChartWrapper :

This is the first time I am posting a question. My apology if I miss something. Thanks in advance. My problem is I am receiving the updated data from api call based on some selection and passing into the CharWrapper component, which in turn call a updated() method in D3Chart class to populate the d3 chart with the updated data. But, on first call the chart is populating correctly but on the second selection when I get the updated data from api response, chart remain as it is, with the old values. It is not getting updated

//code here export default class ChartWrapper extends React.Component {

 componentDidMount() {
    this.tooltipRef = React.createRef();
    this.mySvgRef = React.createRef();
    this.legendRef = React.createRef();
    this.setState({ chart: new D3Chart(this.refs.svgChart, this.refs.tooltip, this.refs.legends) });
 }
      shouldComponentUpdate() {
    console.log("shouldComponentUpdate")
    return false;
}
    static getDerivedStateFromProps(nextProps, prevState) {
        if (prevState) {

          prevState.chart.update(nextProps.chartdata);
          console.log("getDerivedStateFromProps : ", nextProps.chartdata)
          return { chartdata: nextProps.chartdata }
     }

      return null;
  }
   render() {
       return (<div>
        <div className="svg" ref="svgChart"></div>
        <div className="tag" ref="tooltip"></div>
        <div className="legend" ref="legends">
        </div>
    </div>)
}

}enter code here

D3Chart Component code :

Below is my D3chart component. I have using it with react. Why the older data still on the window, after calling the exit().remove() on the data selection as well. I am struggling from the past three days. Any suggestion will be highly appreciated. Thanks

Note : The code inside the D3Chart class constructor function will execute only once and update() function is getting called as soon I get the updated data as props from api response. enter code here

  export default class D3Chart {
     constructor(mySvgRef, tooltipRef, legendRef) {
        const vis = this;

        vis.tooltipRef = tooltipRef;
        vis.ganttSvgRef = d3.select(mySvgRef)
        .append("svg")
        .attr("width", w + margin.left + margin.right)
        .attr("height", h + margin.top + margin.bottom)
        .attr("class", "svg")
        .append('g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

      vis.timeScale = d3.scaleTime()
        .range([0, w - 100]);

      vis.yScale = d3.scaleBand().rangeRound([0, h - margin.top - margin.bottom], 0);

      vis.xAxisGroup = vis.ganttSvgRef.append("g")
        .attr("className", "x top axis")
        .attr('transform', 'translate(' + 20 + ', ' + 0 + ')')

      vis.yAxisGroup = vis.ganttSvgRef.append("g")
        .attr("className", "y axis")
        .attr('transform', 'translate(' + margin.left + ', ' + 0 + ')')


      vis.legendRef = d3.select(legendRef)
        .style("width", w + margin.left + margin.right + 'px')

      vis.ganttSvgRef.append("text")
        .attr("transform",
            "translate(" + 0 + " ," +
            (h / 2) + ") rotate(-90)")
        .style("text-anchor", "middle")
        .text("Vessel in Terminal");

  } 

     update = (chartData) => {
       var barHeight = 50;
       var gap = 95;
       var topPadding = 0;
       var sidePadding = 20;
       const vis = this;
       console.log("I am updated with : ", chartData)
       var parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S");

       var slotNumber = [];
       var vesselsNames = [];
       var serviceName = [];
       var serviceColorSelector = {}
    
     for (var i = 0; i < chartData.length; i++) {
           slotNumber.push(chartData[i].slot);
           serviceName.push(chartData[i].serviceName_data);
           vesselsNames.push(chartData[i].vesselName_data);
    }


        var uniqueService = [...new Set(serviceName)];
       for (var i = 0; i < uniqueService.length; i++) {
           serviceColorSelector[uniqueService[i]] = colArray[i];
    }
    slotNumber = this.checkUnique(slotNumber);
    slotNumber.sort(function (a, b) {
        return Number(a) - Number(b);
    });

    vis.timeScale.domain([d3.min(chartData, function (d) { return parseTime(d.arrivalTime_data); }),
    d3.max(chartData, function (d) { return parseTime(d.departureTime_data); })])
    vis.yScale.domain(slotNumber)

    var xAxistop = d3.axisTop(vis.timeScale).ticks().tickSizeInner((-h + margin.top + margin.bottom))
    vis.xAxisGroup
        // .transition()
        .call(xAxistop);

    var yAxis = d3.axisLeft(vis.yScale)
    vis.yAxisGroup
        // .transition()
        .call(yAxis);
    console.log("vis.ganttSvgRef :", vis.ganttSvgRef)


    if (slotNumber.length > 6) {
        barHeight = this.calculateBarHeight(slotNumber.length, barHeight);
    }
    var rectangles = vis.ganttSvgRef
        .selectAll("rect")
        .data(chartData);
    console.log("Reactangles : ", rectangles)
    rectangles.exit().remove();

    var innerRects = rectangles.enter().append("rect")
        .attr("x", function (d) {
            return vis.timeScale(parseTime(d.arrivalTime_data)) + sidePadding;
        })
        .attr("y", function (d, i) {
            for (var j = 0; j < slotNumber.length; j++) {
                if (d.slot == slotNumber[j]) {
                    return vis.yScale(d.slot);
                }
            }
        })
        .attr("width", function (d) {
            return (vis.timeScale(parseTime(d.departureTime_data)) - vis.timeScale(parseTime(d.arrivalTime_data)));
        })
        .attr("height", barHeight)
        .attr("stroke", "none")
        .attr("fill", function (d) {
            for (var i = 0; i < vesselsNames.length; i++) {
                return serviceColorSelector[d.serviceName_data]
            }
        })

    innerRects.on('mouseover', function (e) {
        var tag = "";

        tag = "Imo: " + d3.select(this).data()[0].imo_data + "<br/>" +
            "Vessel: " + d3.select(this).data()[0].vesselName_data + "<br/>" +
            "Service: " + d3.select(this).data()[0].serviceName_data;


        var x = (this.x.animVal.value + this.width.animVal.value / 2) + "px";
        var y = this.y.animVal.value + 25 + "px";

        vis.tooltipRef.innerHTML = tag;
        vis.tooltipRef.style.top = y;
        vis.tooltipRef.style.left = x;
        vis.tooltipRef.style.display = "block";
    }).on('mouseout', function () {
        vis.tooltipRef.style.display = "none";

    });


    var legend5 = vis.legendRef.selectAll(".legend5")

        .data(serviceName)

    legend5.exit().remove();
    var p = legend5.enter().append("div")
        .attr("class", "legends5").append("p")
        .attr("class", "country-name")
    p.append("span")
        .attr("class", "key-dot")
        .style("background", function (d, i) {
            return serviceColorSelector[d]
        })

    p.insert("text")
        .attr("class", "text-style")
        .style("text-anchor", 'start')

        .text(function (d, i) { return d })

}

checkUnique = (arr) => {
    var hash = {}, result = [];
    for (var i = 0, l = arr.length; i < l; ++i) {
        if (!hash.hasOwnProperty(arr[i])) {
            hash[arr[i]] = true;
            result.push(arr[i]);
        }
    }
    return result;
}
calculateBarHeight = (totalSlot, barheight) => {
    let height = barheight;
    let franction_x = totalSlot - 6;
    let franction_y = 6;
    let decrementRatio = franction_x / (franction_x + franction_y);
    let decresedHeight = decrementRatio * barheight;
    let rectHeight = Math.trunc(barheight - decresedHeight);
    return rectHeight;
}

}

3 Answers

Call

d3.select(mySvgRef).selectAll('svg').remove();

before the line

d3.select(mySvgRef).append('svg')...

d3 V5 :

I have make the use of merge() method which solve my issue. Hope it help for other also``

    ` var rectangles = vis.ganttSvgRef.selectAll("rect")
                        .data(chartData);
   
                       rectangles.exit().remove();

    var innerRects = rectangles.enter().append("rect").merge(rectangles)
                     .attr("x", function (d) {
                     return vis.timeScale(parseTime(d.arrivalTime_data)) + 
                             sidePadding;
        })`

If you are developing a dashboard having multiple widget showing different d3 charts then use the following code-

d3.selectAll("#d3-donutChart > *").remove();

this will only clear the specific chart, not all the svg's in the webpage.

Add this line just before redrawing the d3 graph for updated data.

Related