I am using d3 to make a stacked bar chart.
Thanks to this previous question I am binding data associated with a parent node to a child node using parentNode.__ data__.key.
The data is an array with one object for each bar (e.g 'likes'). Then each object contains an array of values which drive the individual rectangles per bar:
data = [{
key = 'likes', values = [
{key = 'blue-frog', value = 1},
{key = 'goodbye', value = 2}
]
}, {
key = 'dislikes, values = [
{key = 'blue-frog', value = 3},
{key = 'goodbye', value = 4}
]
}]
The chart is working fine, and so is binding the parent metric data to a child svg attribute:
// Create canvas
bars = svg.append("g");
// Create individual bars, and append data
// 'likes' are bound to first bar, 'dislikes' to second
bar = bars.selectAll(".bar")
.data(data)
.enter()
.append("g");
// Create rectangles per bar, and append data
// 'blue-frog' is bound to first rectangle, etc.
rect = bar.selectAll("rect")
.data(function(d) { return d.values;})
.enter()
.append("rect");
// Append parent node information (e.g. 'likes') to each rectangle
// per the SO question referenced above
rect.attr("metric", function(d, i, j) {
return rect[j].parentNode.__data__.key;
});
This then allows the creation of tooltips per rectangle which say things like "likes: 2." So far so good.
The problem is how to associate this same information with a click event, building on:
rect.on("click", function(d) {
return _this.onChartClick(d);
});
// or
rect.on("click", this.onChartClick.bind(this));
It's problematic because the onChartClick method needs access to the bound data (d) and the chart execution context ('this'). If it didn't I could just switch the execution context and call d3.select(this).attr("metric")
within the onChartClick method.
Another idea I had was to pass the metric as an additional parameter but the trick of using function(d, i, j) here doesn't seem to work because it isn't run until a click event happens.
Can you suggest a solution?
I am using d3 to make a stacked bar chart.
Thanks to this previous question I am binding data associated with a parent node to a child node using parentNode.__ data__.key.
The data is an array with one object for each bar (e.g 'likes'). Then each object contains an array of values which drive the individual rectangles per bar:
data = [{
key = 'likes', values = [
{key = 'blue-frog', value = 1},
{key = 'goodbye', value = 2}
]
}, {
key = 'dislikes, values = [
{key = 'blue-frog', value = 3},
{key = 'goodbye', value = 4}
]
}]
The chart is working fine, and so is binding the parent metric data to a child svg attribute:
// Create canvas
bars = svg.append("g");
// Create individual bars, and append data
// 'likes' are bound to first bar, 'dislikes' to second
bar = bars.selectAll(".bar")
.data(data)
.enter()
.append("g");
// Create rectangles per bar, and append data
// 'blue-frog' is bound to first rectangle, etc.
rect = bar.selectAll("rect")
.data(function(d) { return d.values;})
.enter()
.append("rect");
// Append parent node information (e.g. 'likes') to each rectangle
// per the SO question referenced above
rect.attr("metric", function(d, i, j) {
return rect[j].parentNode.__data__.key;
});
This then allows the creation of tooltips per rectangle which say things like "likes: 2." So far so good.
The problem is how to associate this same information with a click event, building on:
rect.on("click", function(d) {
return _this.onChartClick(d);
});
// or
rect.on("click", this.onChartClick.bind(this));
It's problematic because the onChartClick method needs access to the bound data (d) and the chart execution context ('this'). If it didn't I could just switch the execution context and call d3.select(this).attr("metric")
within the onChartClick method.
Another idea I had was to pass the metric as an additional parameter but the trick of using function(d, i, j) here doesn't seem to work because it isn't run until a click event happens.
Can you suggest a solution?
Share Improve this question edited May 23, 2017 at 11:44 CommunityBot 11 silver badge asked Dec 6, 2012 at 19:52 Derek HillDerek Hill 6,4645 gold badges58 silver badges79 bronze badges3 Answers
Reset to default 6You could use the closure to keep a reference to the parent data like this:
bar.each(function(dbar) { // dbar refers to the data bound to the bar
d3.select(this).selectAll("rect")
.on("click", function(drect) { // drect refers to the data bound to the rect
console.log(dbar.key); // dbar.key will be either 'likes' or 'dislikes'
});
});
update:
See below for various ways to access different levels in your DOM structure. Mix and match! See the live version of this and try to click on the .rect divs: http://bl.ocks/4235050
var data = [
{
key: 'likes',
values: [{ key: 'blue-frog', value: 1 }, { key: 'goodbye', value: 2 }]
},
{
key: 'dislikes',
values: [{ key: 'blue-frog', value: 3 }, { key: 'goodbye', value: 4 }]
}];
var chartdivs = d3.select("body").selectAll("div.chart")
.data([data]) // if you want to make multiple charts: .data([data1, data2, data3])
.enter().append("div")
.attr("class", "chart")
.style("width", "500px")
.style("height", "400px");
chartdivs.call(chart); // chartdivs is a d3.selection of one or more chart divs. The function chart is responsible for creating the contents in those divs
function chart(selection) { // selection is one or more chart divs
selection.each(function(d,i) { // for each chartdiv do the following
var chartdiv = d3.select(this);
var bar = chartdiv.selectAll(".bar")
.data(d)
.enter().append("div")
.attr("class", "bar")
.style("width", "100px")
.style("height", "100px")
.style("background-color", "red");
var rect = bar.selectAll(".rect")
.data(function(d) { return d.values; })
.enter().append("div")
.attr("class", "rect")
.text(function(d) { return d.key; })
.style("background-color", "steelblue");
bar.each(function(dbar) {
var bardiv = d3.select(this);
bardiv.selectAll(".rect")
.on("click", function(drect) {
d3.select(this).call(onclickfunc, bardiv);
});
});
function onclickfunc(rect, bar) { // has access to chart, bar, and rect
chartdiv.style("background-color", bar.datum().key === 'likes' ? "green" : "grey");
console.log(rect.datum().key); // will print either 'blue-frog' or 'goodbye'
}
});
}
rect.on("click", this.onChartClick.bind(this));
won't work because you're not passing a function, you're passing the return value of the function (by appending (this)
).
If you want to pass this
and data (d
), try this:
// assuming you have this somewhere earlier
var onChartClick = function () {}
// change your parent click to
rect.on("click", function(d) {
return onChartClick.call(this, d);
});
Another way could be f.e passing an object/json or in my case a class instance which creates that rectangle to the event to access it inside
var rect = svgContainer.append("rect");
rect.datum(this);
rect.on('click', function(d){
alert(d);
alert(d.id); // etc.
});