D3.js:更新图表上的数据不起作用

时间:2014-12-16 22:36:08

标签: d3.js

继续尝试掌握enter-update-exit模式......

我有一个相对简单的可重复使用的d3.js图表​​,我希望能够在两个数据集之间更新图表。我越来越近了,但图表没有正确更新。

你可以在这里看到一个小提琴:http://jsfiddle.net/rolfsf/vba6n4sh/2/

我在哪里弄乱了enter-update-exit模式?

图表代码如下所示:

function relativeSizeChart() {
var width = 1200,
    margin = 0,
    padding = 16,
    r = d3.scale.linear(),

    onTotalMouseOver = null,
    onTotalClick = null,
    onClusterMouseOver = null,
    onClusterClick = null,

    val = function(d){return d;};
    totalFormat = function(d){return d;};
    clusterFormat = function(d){return d;};
    clusterFormat2 = function(d){return d;};

function chart(selection) {
    selection.each(function(data) {
        //console.log(data);

        var clusterCount = data.Clusters.length,
            totalColWidth = 0.3*width,
            colWidth = (width - totalColWidth)/clusterCount,
            height = colWidth + 2*padding,
            maxRadius = (colWidth - 10)/2;


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

        var svgEnter = svg
                .enter().append("svg")
                    .attr('class', function(d){
                        if( onTotalMouseOver !== null || onTotalClick !== null ||onClusterMouseOver !== null || onClusterClick !== null){
                            return 'clickable';
                        }else{
                            return 'static';
                        }
                    })
                    .attr("width", width)
                    .attr("height", height);

        var background, clusterLines;

            background = svgEnter.append("g")
                    .attr('class', 'background');

        var headers = svgEnter.append("g")
                    .attr('class', 'headers')
                    .selectAll("text.header")
                    .data(data.Headers, function(d){return d;});

        var total = svgEnter.append("g")
                    .attr('class', 'total');

        var cluster = svgEnter.selectAll('g.cluster')
                    .data(data.Clusters,function(d){ return d;});

        var clusterEnter = cluster 
                .enter().append("g")
                    .attr('class', 'cluster')
                    .attr('transform', function (d, i) {
                        return 'translate(' + (totalColWidth + i*colWidth) + ',0)';
                    });

        var clusters = svg.selectAll('g.cluster');

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

        background  .append("rect")
                    .attr("class", "chart-bg")
                    .attr('x', 0)
                    .attr('y', padding)
                    .attr('height', (height-padding))
                    .attr('width', width)
                    .attr('class', 'chart-bg');

        background  .append("g")
                    .attr('class', 'cluster-lines');

        background  .append("line")
                    .attr("class", "centerline")
                    .attr('x1', (totalColWidth - padding))
                    .attr('x2', width - (colWidth/2))
                    .attr('y1', (height+padding)/2)
                    .attr('y2', (height+padding)/2);

        clusterLines = background.select('g.cluster-lines')
                    .selectAll("line")
                    .data(data.Clusters,function(d){ return d;})
                    .enter().append('line')
                    .attr('class', 'cluster-line');

        headers     .enter().append('text')
                    .attr('class', 'header');

        total       .append("rect")
                    .attr("class", "total-cluster")
                    .attr('x', padding)
                    .attr('y', 0.2*(height+(4*padding)))
                    .attr('height', 0.5*(height))
                    .attr('width', totalColWidth-(2*padding))
                    .attr('rx', 4)
                    .attr('ry', 4)
                    .on('mouseover', onTotalMouseOver)
                    .on('click', onTotalClick);

        total       .append("text")
                    .attr("class", "total-name")
                    .attr('x', totalColWidth/2 )
                    .attr('y', function(d, i) { return ((height+padding)/2) + (padding + 10); });

        total       .append("text")
                    .attr("class", "total-value")
                    .attr('x', totalColWidth/2 )
                    .attr('y', function(d, i) { return ((height+padding)/2); })
                    .text(totalFormat(0));

        clusterEnter.append('circle')
                    .attr('class', 'bubble')
                    .attr('cx', function(d, i) { return colWidth/2; })
                    .attr('cy', function(d, i) { return (height+padding)/2;})
                    .attr("r", "50")
                    .on('mouseover', function(d, i, j) {
                        if (onClusterMouseOver != null) onClusterMouseOver(d, i, j);
                    })
                    .on('mouseout', function() { /*do something*/ })
                    .on('click', function(d, i){ 
                        onClusterClick(this, d, i); 
                    });  


        clusterEnter.append('text')
                    .attr('class', 'cluster-value')
                    .attr('x', function(d, i) { return colWidth/2; })
                    .attr('y', function(d, i) { return ((height+padding)/2); })
                    .text(clusterFormat(0)); 


        clusterEnter.append('text')
                    .attr('class', 'cluster-value-2')
                    .attr('x', function(d, i) { return colWidth/2; })
                    .attr('y', function(d, i) { return ((height+padding)/2) + (padding + 10); })
                    .text(clusterFormat2(0));

        //update attributes
        clusterLines.attr('x1', function(d, i) { return totalColWidth + i*colWidth })
                    .attr('x2', function(d, i) { return totalColWidth + i*colWidth })
                    .attr('y1', function(d, i) { return padding })
                    .attr('y2', function(d, i) { return (height); });

        headers     .attr('x', function(d, i) { 
                        if(i === 0){
                            return (totalColWidth/2);
                        }else{
                            return (totalColWidth + (i*colWidth) - (colWidth/2))
                        }
                    })
                    .attr('y', 12);

       //clean up old 
        svg          .exit().remove();

        cluster      .exit().selectAll('circle.bubble')
                     .style("opacity", 1)
                     .style("fill", "#DDD")
                     .style("stroke", "#DDD")
                     .transition()
                     .duration(500)
                     .style("opacity", 0);

        cluster      .exit().remove();
        headers      .exit().remove();

        function update(data) {

            //update with data
            svg         .selectAll('text.total-value')
                        .transition()
                        .delay(400)
                        .duration(1000)
                        .tween( 'text', function(d, i) {
                            var currentValue = +this.textContent.replace(/\D/g,''); 
                            var interpolator = d3.interpolateRound( currentValue, d.Total[1] );
                            return function( t ) {
                                this.textContent = totalFormat(interpolator(t));
                            };
                        });  

            svg         .selectAll('text.total-name')
                        .text(val(data.Total[0]));

            svg         .selectAll('circle')
                        .attr('class', function(d, i) { 
                            if(d[1] === 0){ return 'bubble empty';}
                            else {return 'bubble';}
                        })
                        .transition()
                        .duration(1000)
                        .delay(function(d, i) { return 500 + (i * 100); })
                        .ease('elastic')   
                        .attr("r", function (d, i) { return r(d[1]); });

            svg         .selectAll('text.cluster-value')
                        .transition()
                        .delay(function(d, i) { return 500 + (i * 100); })
                        .duration(1000)
                        .tween( 'text', function(d, i) {
                            var currentValue = +this.textContent.replace(/\D/g,'');
                            var interpolator = d3.interpolateRound( currentValue, d[1] );
                            return function( t ) {
                                this.textContent = clusterFormat(interpolator(t));
                            };
                        });  


            svg         .selectAll('text.cluster-value-2')
                        .transition()
                        .delay(function(d, i) { return 500 + (i * 100); })
                        .duration(1000)
                        .tween( 'text', function(d, i) {
                            var currentValue = +this.textContent.replace(/\D/g,'');
                            var interpolator = d3.interpolateRound( currentValue, d[0] );
                            return function( t ) {
                                this.textContent = clusterFormat2(interpolator(t));
                            };
                        });  

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

        update(data);

    });


}

chart.totalFormat = function(_) {
    if (!arguments.length) return totalFormat;
    totalFormat = _;
    return chart;
};
chart.clusterFormat = function(_) {
    if (!arguments.length) return clusterFormat;
    clusterFormat = _;
    return chart;
};
chart.clusterFormat2 = function(_) {
    if (!arguments.length) return clusterFormat2;
    clusterFormat2 = _;
    return chart;
};



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

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

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

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


return chart;
}

我的示例数据看起来像这样

var data = {
"data1": {
    Headers: ["Total", "Col 1A", "Col 2A", "Col 3A", "Col 4A"],
    Total: ["Total # of Widgets", 1200],
    Clusters: [
        [100, 1200],
        [67, 800],
        [42, 500],
        [17, 198]
    ]
},
"data2": {
    Headers: ["Total", "Col 1B", "Col 2B", "Col 3B", "Col 4B"],
    Total: ["Total # of Widgets", 1200],
    Clusters: [
        [20, 245],
        [31, 371],
        [32, 386],
        [12, 145]
    ]
}
}

谢谢!

1 个答案:

答案 0 :(得分:1)

您的输入/更新/退出模式存在一些问题:

        background = svgEnter.append("g")
                .attr('class', 'background');

    var headers = svgEnter.append("g")
                .attr('class', 'headers')
                .selectAll("text.header")
                .data(data.Headers, function(d){return d;});

    var total = svgEnter.append("g")
                .attr('class', 'total');

此代码引用svgEnter,这是第一次正常,因为svgEnter具有非空选择(它包含您之前创建的svg)。

在对此函数的后续调用中,svgEnter将包含空选择,因为svg元素已存在。所以,我已经将这部分代码修改为:

        svgEnter.append('g')
                .attr('class', 'background');
        var background = svg.selectAll('g.background');

        svgEnter.append('g')
                .attr('class', 'headers')

        var headers = svg.selectAll('g.headers').selectAll('text.header')
                         .data(data.Headers, function(d) { return d; });            
        svgEnter.append('g')
                .attr('class', 'total');

        var total = svg.selectAll('g.total');

如果我们还必须创建g元素,这将创建svg元素。然后,它将使用svg元素中的选择创建与现有代码类似的变量。

我认为他们是我所做的唯一更改,其余代码按预期工作。

更新的小提琴位于http://jsfiddle.net/vba6n4sh/9/