سؤال

I've got a fairly simple reusable chart built in D3.js -- some circles and some text.

I'm struggling to figure out how to update the chart with new data, without redrawing the entire chart.

With the current script, I can see that the new data is bound to the svg element, but none of the data-driven text or attributes is updating. Why isn't the chart updating?

Here's a fiddle: http://jsfiddle.net/rolfsf/em5kL/1/

I'm calling the chart like this:

d3.select('#clusters')
.datum({
    Name: 'Total Widgets',
    Value: 224,
    Clusters: [
        ['Other', 45],
        ['FooBars', 30],
        ['Foos', 50],
        ['Bars', 124],
        ['BarFoos', 0]
    ]
})
.call( clusterChart() );

When the button is clicked, I'm simply calling the chart again, with different data:

    $("#doSomething").on("click", function(){

    d3.select('#clusters')
        .datum({
            Name: 'Total Widgets',
            Value: 122,
            Clusters: [
                ['Other', 14],
                ['FooBars', 60],
                ['Foos', 22],
                ['Bars', 100],
                ['BarFoos', 5]
            ]
        })
        .call( clusterChart() );

});

The chart script:

function clusterChart() {
var width = 450,
    margin = 0,
    radiusAll = 72,
    maxRadius = radiusAll - 5,
    r = d3.scale.linear(),
    padding = 1, 
    height = 3 * (radiusAll*2 + padding),
    startAngle = Math.PI / 2,
    onTotalMouseOver = null,
    onTotalClick = null,
    onClusterMouseOver = null,
    onClusterClick = null;
    val = function(d){return d};

function chart(selection) {
    selection.each(function(data) {

        var cx = width / 2,
            cy = height / 2,
            stepAngle = 2 * Math.PI / data.Clusters.length,
            outerRadius = 2*radiusAll + padding;

        r = d3.scale.linear()
                    .domain([0, d3.max(data.Clusters, function(d){return d[1];})])
                    .range([50, maxRadius]);

        var svg = d3.select(this).selectAll("svg")
                    .data([data])
                    .enter().append("svg");

        //enter
        var totalCircle = svg.append("circle")
                    .attr("class", "total-cluster")
                    .attr('cx', cx)
                    .attr('cy', cy)
                    .attr('r', radiusAll)
                    .on('mouseover', onTotalMouseOver)
                    .on('click', onTotalClick);

        var totalName = svg.append("text")
                    .attr("class", "total-name")
                    .attr('x', cx)
                    .attr('y', cy + 16);

        var totalValue = svg.append("text")
                    .attr("class", "total-value")
                    .attr('x', cx)
                    .attr('y', cy + 4);



        var clusters =  svg.selectAll('circle.cluster')
                    .data(data.Clusters)
                    .enter().append('circle')
                    .attr("class", "cluster");

        var clusterValues = svg.selectAll("text.cluster-value")
                    .data(data.Clusters)
                    .enter().append('text')
                    .attr('class', 'cluster-value');

        var clusterNames = svg.selectAll("text.cluster-name")
                    .data(data.Clusters)
                    .enter().append('text')
                    .attr('class', 'cluster-name');


        clusters    .attr('cx', function(d, i) { return cx + Math.cos(startAngle + stepAngle * i) * outerRadius; })
                    .attr('cy', function(d, i) { return cy + Math.sin(startAngle + stepAngle * i) * outerRadius; })
                    .attr("r", "10")
                    .on('mouseover', function(d, i, j) {
                        if (onClusterMouseOver != null) onClusterMouseOver(d, i, j);
                    })
                    .on('mouseout', function() { /*do something*/ })
                    .on('click', function(d, i){ onClusterClick(d); });  

        clusterNames
                    .attr('x', function(d, i) { return cx + Math.cos(startAngle + stepAngle * i) * outerRadius; })
                    .attr('y', function(d, i) { return cy + Math.sin(startAngle + stepAngle * i) * outerRadius + 16; });

        clusterValues  
                    .attr('x', function(d, i) { return cx + Math.cos(startAngle + stepAngle * i) * outerRadius; })
                    .attr('y', function(d, i) { return cy + Math.sin(startAngle + stepAngle * i) * outerRadius - 4; });


        //update with data
        svg         .selectAll('text.total-value')
                    .text(val(data.Value));

        svg         .selectAll('text.total-name')
                    .text(val(data.Name));

        clusters
                    .attr('class', function(d, i) { 
                        if(d[1] === 0){ return 'cluster empty'}
                        else {return 'cluster'}
                    })
                    .attr("r", function (d, i) { return r(d[1]); });

        clusterValues   
                    .text(function(d) { return d[1] });

        clusterNames    
                    .text(function(d, i) { return d[0] });


        $(window).resize(function() {
          var w = $('.cluster-chart').width(); //make this more generic
          svg.attr("width", w);
          svg.attr("height", w * height / width);
        });

    });


}

chart.width = function(_) {
    if (!arguments.length) return width;
    width = _;
    return chart;
};

chart.onClusterClick = function(_) {
    if (!arguments.length) return onClusterClick;
    onClusterClick = _;
    return chart;
};

return chart;
}
هل كانت مفيدة؟

المحلول

I have applied the enter/update/exit pattern across all relevant svg elements (including the svg itself). Here is an example segment:

var clusterValues = svg.selectAll("text.cluster-value")
    .data(data.Clusters,function(d){ return d[1];});

clusterValues.exit().remove();

clusterValues
    .enter().append('text')
    .attr('class', 'cluster-value');
...

Here is a complete FIDDLE with all parts working.

NOTE: I tried to touch your code as little as possible since you have carefully gone about applying a re-usable approach. This the reason why the enter/update/exit pattern is a bit different between the total circle (and text), and the other circles (and text). I might have gone about this using a svg:g element to group each circle and associated text.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top