我有一个相对简单的可重复使用的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 + '%'})
);
答案 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/