I am trying to use an anonymous function to call two other functions but can't really get it to work. I followed this question on SO: Calling two functions on same click event with d3.js but still can't get it right.
The desired effect is that my barchart changes bars and changes color at the same time. This is what I am trying:
My two functions I want to call:
function selectDataset(d) {
let value = this.value;
if (value == "total") {
change(datasetTotal);
} else if (value == "option1") {
change(datasetOption1);
} else if (value == "option2") {
change(datasetOption2);
}
}
function changeColor(d) {
let value = this.value;
if (value == "total") {
d3.selectAll("rect")
.transition()
.duration(2000)
.style("fill", 'blue')
} else if (value == "option1") {
d3.selectAll("rect")
.transition()
.duration(2000)
.style("fill", 'red')
} else if (value == "option2") {
d3.selectAll("rect")
.transition()
.duration(2000)
.style("fill", 'yellow')
}
}
My anonymous function:
d3.selectAll("input").on("change", function(d) {
selectDataset.call(this, d);
changeColor.call(this, d);
});
and here my change function:
function change(dataset) {
y.domain(dataset.map(function(d) {
return d.label;
}));
x.domain([0, d3.max(dataset, function(d) {
return d.value;
})]);
svg.select(".y.axis").remove();
svg.select(".x.axis").remove();
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(0)")
.attr("x", 50)
.attr("dx", ".1em")
.style("text-anchor", "end")
.text("Option %");
var bar = svg.selectAll(".bar")
.data(dataset, function(d) {
return d.label;
});
var barExit = bar.exit().remove();
var barEnter = bar.enter()
.append("g")
.attr("class", "bar");
var barRects = barEnter.append("rect")
.attr("x", function(d) {
return x(0);
})
.attr("y", function(d) {
return y(d.label);
})
.attr("width", function(d) {
return x(d.value);
})
.attr("height", y.bandwidth());
var barTexts = barEnter.append("text")
.attr("x", function(d) {
return x(d.value) + 10;
})
.attr("y", function(d) {
return y(d.label) + y.bandwidth() / 2;
})
.attr("dy", ".35em")
.text(function(d) {
return d.value;
});
var barRectUpdate = bar.select("rect")
.transition()
.duration(3050)
.attr("x", function(d) {
return x(0);
})
.attr("y", function(d) {
return y(d.label);
})
.attr("width", function(d) {
return x(d.value);
})
.attr("height", y.bandwidth());
var barTextsUpdate = bar.select("text")
.transition()
.duration(3050)
.attr("x", function(d) {
return x(d.value) + 10;
})
.attr("y", function(d) {
return y(d.label) + y.bandwidth() / 2;
})
.attr("dy", ".35em")
.text(function(d) {
return d.value;
});
};
my chart
var margin = {
top: (parseInt(d3.select('.area-heat-cool').style('height'), 10) / 20),
right: (parseInt(d3.select('.area-heat-cool').style('width'), 10) / 20),
bottom: (parseInt(d3.select('.area-heat-cool').style('height'), 10) / 20),
left: (parseInt(d3.select('.area-heat-cool').style('width'), 10) / 5)
},
width = parseInt(d3.select('.area-heat-cool').style('width'), 10) - margin.left - margin.right,
height = parseInt(d3.select('.area-heat-cool').style('height'), 10) - margin.top - margin.bottom;
var div = d3.select(".area-heat-cool").append("div").attr("class", "toolTip");
var y = d3.scaleBand()
.rangeRound([height, 0], .2, 0.5)
.paddingInner(0.1);
var x = d3.scaleLinear()
.range([0, width]);
var xAxis = d3.axisBottom()
.scale(x);
var yAxis = d3.axisLeft()
.scale(y);
var svg = d3.select(".area-heat-cool").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
d3.select("input[value=\"total\"]").property("checked", true);
change(datasetTotal);
dataset example:
data1 = [{label: "example 1", value: 156}
{label: "example 2", value: 189}
{label: "example 3", value: 234}
{label: "example 4", value: 345}
{label: "example 5", value: 346}
{label: "example 6", value: 456}
{label: "example 7", value: 489}
{label: "example 8", value: 567}];
data2 = [{label: "example 1", value: 23}
{label: "example 2", value: 211}
{label: "example 3", value: 45}
{label: "example 4", value: 64}
{label: "example 5", value: 95}
{label: "example 6", value: 32}
{label: "example 7", value: 0}
{label: "example 8", value: 234}];
And my radio buttons:
<div class="area-heat-cool">
<form>
<label><input type="radio" name="dataset" id="dataset" value="total" checked> Total</label>
<label><input type="radio" name="dataset" id="dataset" value="option1"> Option 1</label>
<label><input type="radio" name="dataset" id="dataset" value="option2"> Option 2</label>
</form>
</div>
When I do this it somehow messes up my data. The color changes but the bars don't move to their right positions anymore. I don't get any errors thrown but the behaviour is not the expected.
Am I using this anonymous function correctly? Any help is very much appreciated. Thanks a lot in advance
What happens is that you are applying two transitions to your rect
elements when triggering an event. However, when doing that the second transition overwrites the first one.
Then first transition is applied in the change
function where you change the size of the rect
elements. When the change
function returns, you immediately call the changeColor
function, which selects all rect
elements and applies a different transition that changes the color of the rect
elements. Thus the color transition overwrites the size transition.
To fix this, you could apply all transitions to the same elements in one place. In my example I'm going to putting the code from the changeColor function into the change
function.
First, change the change
function declaration to enable including the radio select value:
function change(dataset, optionSelect) {
// code not shown
}
Then, update the code in the change
that performs the transition of the rect
elements:
var barRectUpdate = bar.select("rect")
.transition()
.duration(3050)
.attr("x", function(d) {
return x(0);
})
.attr("y", function(d) {
return y(d.label);
})
.attr("width", function(d) {
return x(d.value);
})
.attr("height", y.bandwidth())
.style('fill', function () {
if (optionSelect === "total") {
return 'blue'
} else if (optionSelect === "option1") {
return 'red'
} else if (optionSelect === "option2") {
return 'yellow'
}
});
Update the selectDataset
function to include the radio select value when calling change
:
function selectDataset(d) {
let value = this.value;
if (value === "total") {
change(datasetTotal, value);
} else if (value === "option1") {
change(datasetOption1, value);
} else if (value === "option2") {
change(datasetOption2, value);
}
}
Remove the call to the changeColor
function:
d3.selectAll("input").on("change", function(d) {
selectDataset.call(this, d);
});
And, finally, remove the changeColor
function altogether.
A couple of other things to consider:
id
attributes should be unique. Right now all your input
elements have the same id.selectDataset
and changeColor
to ===
instead of ==
to avoid unexpected type conversion when comparing values: Difference between == and === in JavaScript
bar.select("rect")
will work just as well as var barRectUpdate = bar.select("rect")
.Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With