简体   繁体   中英

D3 chart can't update -- enter and exit property of selection both empty

I'm trying to make a scatter plot using a .json file. It will let the user to select which group of data in the json file to be displayed. So I'm trying to use the update pattern.

The following code will make the first drawing, but every time selectGroup() is called(the code is in the html file), nothing got updated. The console.log(selection) did come back with a new array each time, but the enter and exit property of that selection is always empty. Can anyone help me take a look? Thanks a lot!

var margin = {
    top: 30,
    right: 40,
    bottom: 30,
    left: 40
}
var width = 640 - margin.right - margin.left,
    height = 360 - margin.top - margin.bottom;
var dataGroup;
var groupNumDefault = "I";
var maxX, maxY;
var svg, xAxis, xScale, yAxis, yScale;



//select and read data by group
function init() {
    d3.json("data.json", function (d) {
        maxX = d3.max(d, function (d) {
            return d.x;
        });
        maxY = d3.max(d, function (d) {
            return d.y;
        });
        console.log(maxY);

        svg = d3.select("svg")
            .attr("id", "scatter_plot")
            .attr("width", 960)
            .attr("height", 500)
            .append("g")
            .attr("id", "drawing_area")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        //x-axis 
        xScale = d3.scale.linear().range([0, width]).domain([0, maxX]);
        xAxis = d3.svg.axis()
            .scale(xScale).orient("bottom").ticks(6);

        //y-axis
        yScale = d3.scale.linear().range([0, height]).domain([maxY, 0]);
        yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(6);

        svg.append("g")
            .attr("class", "x_axis")
            .attr("transform", "translate(0," + (height) + ")")
            .call(xAxis);

        svg.append("g")
            .attr("class", "y_axis")
            .call(yAxis);

    });

    selectGroup(groupNumDefault);
}

//update data 
function selectGroup(groupNum) {
    d3.json("/data.json", function (d) {
        dataGroup = d.filter(function (el) {
            return el.group == groupNum;
        });
        console.log(dataGroup);
        drawChart(dataGroup);

    });
}


//drawing function
function drawChart(data) {
    var selection = d3.select("svg").selectAll("circle")
        .data(data);
    console.log(selection);

    selection.enter()
        .append("circle")
        .attr("class", "dots")
        .attr("cx", function (d) {
            console.log("updating!");
            return xScale(d.x);
        })
        .attr("cy", function (d) {
            return yScale(d.y);
        })
        .attr("r", function (d) {
            return 10;
        })
        .attr("fill", "red");

    selection.exit().remove();
}

init();

The problem here is on two fronts:


Firstly, your lack of a key function in your data() call means data is matched by index (position in data array) by default, which will mean no enter and exit selections if the old and current datasets sent to data() are of the same size. Instead, most (perhaps all) of the data will be put in the update selection when d3 matches by index (first datum in old dataset = first datum in new dataset, second datum in old dataset = second datum in new dataset etc etc)

var selection = d3.select("svg").selectAll("circle")
        .data(data);

See: https://bl.ocks.org/mbostock/3808221

Basically, you need your data call adjusted to something like this (if your data has an .id property or anything else that can uniquely identify each datum)

var selection = d3.select("svg").selectAll("circle")
            .data(data, function(d) { return d.id; });

This will generate enter() and exit() (and update) selections based on the data's actual contents rather than just their index.


Secondly, not everything the second time round is guaranteed be in the enter or exit selections. Some data may be just an update of existing data and not in either of those selections (in your case it may be intended to be completely new each time). However, given the situation just described above it's pretty much guaranteed most of your data will be in the update selection, some of it by mistake. To show updates you will need to alter the code like this (I'm assuming d3 v3 here, apparently it's slightly different for v4)

selection.enter()
        .append("circle")
        .attr("class", "dots")
        .attr("r", function (d) {
            return 10;
        })
        .attr("fill", "red");

// this new bit is the update selection (which includes the just added enter selection
// now, the syntax is different in v4)
selection // v3 version
// .merge(selection)  // v4 version (remove semi-colon off preceding enter statement)
        .attr("cx", function (d) {
            console.log("updating!");
            return xScale(d.x);
        })
        .attr("cy", function (d) {
            return yScale(d.y);
        })

   selection.exit().remove();

Those two changes should see your visualisation working, unless of course the problem is something as simple as an empty set of data the second time around which would also explain things :-)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM