为Web书籍完成优秀的交互式数据可视化,并创建(一个怪异的)脚本来创建交互式条形图:
我添加了一个鼠标悬停事件监听器,用于在悬停时更改条形图的颜色。问题是通过上面添加的条形图不会改变颜色。据我所知,条形图正在被正确选中,但无论出于何种原因,鼠标悬停事件永远不会被这些条形图触发:
svg.select(".bars").selectAll("rect")
.on("mouseover", function() {
d3.select(this)
.transition()
.attr("fill", "red");
})
在此先感谢您的帮助,我们非常感谢。
以下是完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Interactive Data Visualization for the Web Program-Along</title>
<style>
/* axes are made up of path, line, and text elements */
.axis path,
.axis line {
fill: none;
stroke: navy;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 11px;
/* color is CSS property, but need SVG property fill to change color */
fill: navy;
}
</style>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
<p>Click on this text to update the chart with new data values.</p>
<script type="text/javascript">
var n = 50;
var domain = Math.random() * 1000;
function gen_data(n, domain) {
var d = [];
for (var i = 0; i < n; i++) {
d.push(
{ id: i, val: Math.random() * domain }
);
}
return d;
}
// define key function once for use in .data calls
var key = function(d) {
return d.id;
};
var dataset = gen_data(n, domain);
// define graphic dimensions
var w = 500, h = 250, pad = 30;
// get input domains
var ylim = d3.extent(dataset, function(d) {
return d.val;
});
// define scales
var x_scale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w - pad], 0.15);
var y_scale = d3.scale.linear()
.domain([ylim[0], ylim[1] + pad]) // could have ylim[0] instead
// range must be backward [upper, lower] to accommodate svg y inversion
.range([h, 0]); // tolerance to avoid clipping points
var color_scale = d3.scale.linear()
.domain([ylim[0], ylim[1]])
.range([0, 255]);
// create graphic
var svg = d3.select("body").append("div").append("svg")
.attr("width", w)
.attr("height", h);
svg.append("g")
.attr("class", "bars")
.selectAll(".bars rect")
.data(dataset)
.enter()
.append("rect")
.attr({
x: function(d, i) {
return x_scale(i) + pad;
},
y: function(d) {
return y_scale(d.val);
},
width: x_scale.rangeBand(), // calculates width automatically
height: function(d) { return h - y_scale(d.val); },
opacity: 0.6,
fill: function(d) {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
}
});
// add axes
var yAxis = d3.svg.axis()
.scale(y_scale) // must be passed data-to-pixel mapping (scale)
.ticks(3) // optional (d3 can assign ticks automatically)
.orient("left");
// since function, must be called
// create <g> to keep things tidy, to style via CSS, & to adjust placement
svg.append("g")
.attr({
class: "axis",
transform: "translate(" + pad + ",0)"
})
.call(yAxis);
// add event listener for clearing/adding all new values
d3.select("p")
.on("click", function() {
// generate new dataset
dataset = gen_data(n, domain);
// remove extra bars
d3.selectAll(".bars rect")
.data(dataset, function(d, i) { if (i < 50) { return d; }})
.exit()
.transition()
.attr("opacity", 0)
.remove();
// update scales
x_scale.domain(d3.range(dataset.length))
.rangeRoundBands([0, w - pad], 0.15);
ylim = d3.extent(dataset, function(d) {
return d.val;
});
y_scale.domain([ylim[0], ylim[1] + pad]);
// update bar values & colors
d3.selectAll(".bars rect")
.data(dataset)
.transition()
.duration(500)
.attr("x", function(d, i) { return x_scale(i) + pad; })
.transition() // yes, it's really this easy...feels like cheating
.delay(function(d, i) { return i * (1000 / dataset.length); }) // set dynamically
.duration(1000) // optional: control transition duration in ms
.each("start", function() {
// "start" results in immediate effect (no nesting transitions)
d3.select(this) // this to select each element (ie, rect)
.attr("fill", "magenta")
.attr("opacity", 0.2);
})
.attr({
y: function(d) { return y_scale(d.val); },
height: function(d) { return h - y_scale(d.val); }
})
.each("end", function() {
d3.selectAll(".bars rect")
.transition()
// needs delay or may interrupt previous transition
.delay(700)
.attr("fill", function(d) {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
})
.attr("opacity", 0.6)
.transition()
.duration(100)
.attr("fill", "red")
.transition()
.duration(100)
.attr("fill", function(d) {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
});
});
// update axis (no need to update axis-generator function)
svg.select(".axis")
.transition()
.duration(1000)
.call(yAxis);
});
// extend dataset by 1 for each click on svg
svg.on("click", function() {
// extend dataset & update x scale
dataset.push({ id: dataset.length, val: Math.random() * domain });
x_scale.domain(d3.range(dataset.length));
// add this datum to the bars <g> tag as a rect
var bars = svg.select(".bars")
.selectAll("rect")
.data(dataset, key);
bars.enter() // adds new data point(s)
.append("rect")
.attr({
x: w,
y: function(d) {
return y_scale(d.val);
},
width: x_scale.rangeBand(), // calculates width automatically
height: function(d) { return h - y_scale(d.val); },
opacity: 0.6,
fill: function(d) {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
}
});
// how does this move all the other bars!?
// because the entire dataset is mapped to bars
bars.transition()
.duration(500)
.attr("x", function(d, i) {
return x_scale(i) + pad;
});
});
// add mouseover color change transition using d3 (vs CSS)
svg.select(".bars").selectAll("rect")
.on("mouseover", function() {
d3.select(this)
.transition()
.attr("fill", "red");
})
.on("mouseout", function(d) {
d3.select(this)
.transition()
.attr("fill", function() {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
})
.attr("opacity", 0.6);
})
// print to console when clicking on bar = good for debugging
.on("click", function(d) { console.log(d); });
</script>
</body>
</html>
更新
感谢Miroslav的建议,我开始尝试不同的方法解决问题,并遇到了Makyen对this related SO post的回答。
虽然我想有一种更高效的方法来处理这个问题,但每次鼠标使用以下代码进入svg元素时,我决定重新绑定鼠标悬停事件监听器:
svg.on("mouseover", mouse_over_highlight);
// add mouseover color change transition using d3 (vs CSS)
function mouse_over_highlight() {
d3.selectAll("rect")
.on("mouseover", function () {
d3.select(this)
.transition()
.attr("fill", "red");
})
.on("mouseout", function (d) {
d3.select(this)
.transition()
.attr("fill", function () {
return "rgb(50, 0, " + Math.floor(color_scale(d.val)) + ")";
})
.attr("opacity", 0.6);
})
// print to console when clicking on bar = good for debugging
.on("click", function (d) {
console.log(d);
});
}
答案 0 :(得分:2)
您的事件仅针对第一个栏而不是动态事件触发的原因是因为您添加了事件侦听器的方式。
您的方式只将事件放在页面上已存在的元素上(它们是DOM结构)。任何新元素都不会将此事件侦听器绑定到它们。
您可以通过将代码置于
之类的功能来快速检查function setListeners() {
svg.select(".bars").selectAll("rect").on("mouseover", function() {
d3.select(this)
.transition()
.attr("fill", "red");
})
}
在屏幕上添加任何新条形图后,添加此函数调用并查看它是否适用于所有元素。如果确实如此,则需要以适用于所有元素的方式编写事件侦听器,包括动态添加的元素。这样做的方法是将事件设置为某些父DOM节点,然后检查您是否正在悬停在您想要触发事件的事物上。
示例:
$(document).on(EVENT, SELECTOR, function(){
code;
});
这会将事件放在正文上,如果你已经超过了正确的元素,你可以在它被触发后检查它。然而,自从我与D3合作并且我不确定D3,SVG和jQuery如何一起玩之后有一段时间,上次我这样做时他们遇到了一些麻烦。 在这种情况下,事件应该是鼠标悬停,选择器应该是你的矩形条,功能应该是你想要运行的。
如果其他所有内容都失败,以防他们不合作,只需使用该函数设置事件监听器,并在动态添加新元素后每次调用它。