d3.js:更新无法更新一个元素&创建格式错误的标记

时间:2014-12-17 18:49:18

标签: d3.js

我有一个相对简单的可重复使用的d3.js图表​​,其中我试图最终掌握Enter-Update-Exit模式。在昨天SO的帮助下,我非常接近工作。

我今天早上注意到2个问题:Total值没有更新,并且会产生格式错误的标记:

在更新之前,如果我查看我看到的开发人员工具:

<g class="total">
    <rect class="total-cluster" x="16" y="61.2" height="121" width="328" rx="4" ry="4"></rect>
    <text class="total-name" x="180" y="155">Total # of Widgets</text>
    <text class="total-value" x="180" y="129">1200</text>
</g>

更新后:

<g class="total">
    <rect class="total-cluster" x="16" y="61.2" height="121" width="328" rx="4" ry="4"></rect>
    <text class="total-name" x="180" y="155">Total # of Widgets</text>
    Total # of Widgets
    </text>
    <text class="total-value" x="180" y="129">1200</text>
    1200
    </text>
</g>

如果显示正确的更新数据,我可能不会注意到格式错误的标记。但它告诉我正在进行更新,但没有正确发生。

我的总更新中的错误在哪里?

这是一个小提琴:http://jsfiddle.net/rolfsf/x2yvwf43/

我的图表脚本:

/**
*  Relative Size Chart for d3.js
*/

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';
                        }
                    });

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

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

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


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

        var headers = svg
                    .selectAll("g.headers")
                    .selectAll("text.header")
                    .data(data.Headers, function(d){return d;});

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

        var cluster = svg.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');


        svg         .attr("width", width)
                    .attr("height", height)
                    .call(responsivefy); 

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

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

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

        svgEnter    .selectAll("g.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);

        var clusterLines = svg.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();

        //update with data            
        function update(data) {

            svg         .selectAll('text.total-value')
                        .transition()
                        .delay(100)
                        .duration(1000)
                        .tween( 'text', function(d) {
                            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 100 + (i * 100); })
                        .ease('elastic')   
                        .attr("r", function (d, i) { return r(d[1]); });

            svg         .selectAll('text.cluster-value')
                        .transition()
                        .delay(function(d, i) { return 100 + (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 100 + (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});
        }


        //http://www.brendansudol.com/posts/responsive-d3/
        function responsivefy(svg) {
            // get container + svg aspect ratio
            //var SVG = d3.select(selection).selectAll('svg');
            var container = d3.select(svg.node().parentNode),
                width = parseInt(svg.style("width")),
                height = parseInt(svg.style("height")),
                aspect = width / height;

            // add viewBox and preserveAspectRatio properties,
            // and call resize so that svg resizes on inital page load
            svg .attr("viewBox", "0 0 " + width + " " + height)
                .attr("preserveAspectRatio", "xMidYMin meet")
                .call(resize);

            // to register multiple listeners for same event type, 
            // you need to add namespace, i.e., 'click.foo'
            // necessary if you call invoke this function for multiple svgs
            // api docs: https://github.com/mbostock/d3/wiki/Selections#on
            d3.select(window).on("resize." + container.attr("id"), resize);

            // get width of container and resize svg to fit it
            function resize() {
                var targetWidth = parseInt(container.style("width"));
                svg.attr("width", targetWidth);
                svg.attr("height", Math.round(targetWidth / aspect));
            }
        }

        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", 1201],
    Clusters: [
        [20, 245],
        [31, 371],
        [32, 386],
        [12, 146]
    ]
}
}

我称之为:

        d3  .select('#overview-graph')
        .datum(data[term])
        .call(relativeSizeChart()
            .totalFormat(function(d) { return d })
            .clusterFormat(function(d) { return d })
            .clusterFormat2(function(d) { return d + '%'})
        );

1 个答案:

答案 0 :(得分:1)

在Firefox中,DOM结构对我来说很合适。

update功能中,您执行此操作:

    function update(data) {

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

如果您查看tween功能,则会插入值d.Total[1]。在这种情况下,d未设置为您的数据。如果您将数据绑定到元素,那么该模式通常是您使用的模式。我想你想要:

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

唯一的区别在于这一行:

var interpolator = d3.interpolateRound( currentValue, data.Total[1] );

我使用data代替d

更新的小提琴位于:http://jsfiddle.net/x2yvwf43/4/