Incorrect SVG group being deleted

2019-07-31 07:48发布

I want to remove the bar that is clicked, but for some reason it is always removing the last bar instead. I think the answer may be in the following article (http://pothibo.com/2013/09/d3-js-how-to-handle-dynamic-json-data/), but I'm not sure if manually assigning a key to each element is necessary. I have spent hours trying to get this to work, so I'm hoping someone can point me in the right direction. Thanks in advance.

<html>
<head>
    <title>Interactive Div-based Bar Chart</title>
    <style type="text/css">
    .chart rect {
        fill: teal;
    }
    .chart text {
        fill: white;
        font: 30px sans-serif;
        text-anchor: end;
    }
    </style>
    <script src="Scripts/d3.min.js"></script>
</head>
<body>
    <div id="data-field"></div>
    <button id="add-btn">Add Data</button>
    <p>Click on a bar to remove it</p>

    <script>
        var barHeight = 75, margin = 3, padding = 3;

        var chartData = [6,12,15,21,29,41];

        var chart = d3.select("body").append("svg").attr("class","chart");

        drawChart();

        function drawChart() {

            var selection = chart.selectAll("g")
                .data(chartData);

            // Remove extra bars
            selection.exit()
                .remove();

            // Add more bars
            var groups = selection.enter()
                .append("g");

            var bars = groups
                .append("rect");

            var labels = groups
                .append("text");

            // Update existing bars
            groups
                .attr("transform", function (d, i) { return "translate(0," + i * (barHeight + margin) + ")"; })
                .on("click", function (d, i) { chartData.splice(i, 1); drawChart(); });      

            bars
                .attr("width", function (d) { return d * 10 + "px"; })
                .attr("height", barHeight);     

            labels
                .attr("x", function (d) { return d * 10 - padding + "px"; })
                .attr("y", barHeight / 3 + padding + "px")
                .text(function (d) { return d; });

            // update list of numbers
            d3.select("#data-field").text("numbers: [" + chartData.join(", ") + "]");
        }

        // add more data
        d3.select("#add-btn")
            .on("click", function(d) { chartData.push(Math.round(Math.random() * 100)); drawChart(); });

    </script>

</body>
</html>

2条回答
聊天终结者
2楼-- · 2019-07-31 08:20

Here are the changes you need to make, with comments:

// Update existing bars; do not use groups here since it represents
// only the ENTER selection, and not ALL g elements
selection
    .attr("transform", function (d, i) { return "translate(0," + i * (barHeight + margin) + ")"; })
    .on("click", function (d, i) { 
        d3.select(this).remove(); // remove appropriate DOM element
        chartData.splice(i, 1); 
        drawChart(); 
    });

And here is the FIDDLE.

IMPORTANT EDIT:

After @loucatsfan asked me why not simply getting rid of selection.exit().remove(); since it made no difference, I realized the importance of making my answer more idiomatic. So, I created an updated fiddle that leverages the exit selection and removes the need to manually delete the DOM element.

Also, and just as important, there are some subtle differences between handling updates for grouped vs. non-grouped elements (use of select vs. selectAll). So, I revised the code to more appropriately handle grouped elements.

You can find more info on the proper handling of grouped elements here and here. But here is the promised improvement:

UPDATED FIDDLE.

查看更多
爷、活的狠高调
3楼-- · 2019-07-31 08:40

Check my solution here: http://fiddle.jshell.net/sydvpsfp/

Major problem was that you were not deleting all the old bars at the begining of drawChart function. I had added this:

        // remove any old bars
        var oldBars=chart.selectAll("g");
        if (oldBars.size()>0)
            oldBars.remove();
查看更多
登录 后发表回答