Hello. I have a D3 chart that re-renders data when a model is refreshed an although I have the chart working I am having a lot of trouble adding it as an Ember component. Here is what I have so far:
(note that the appropriate d3 modules are imported before the component object is extended)
export default Component.extend({
data: [],
didInsertElement() {
this.buildChart()
},
didUpdateAttrs() {
this.buildChart()
},
buildChart() {
var data = this.get('data')
var margin = {top: 35, right: 145, bottom: 35, left: 45},
width = 650 - margin.left - margin.right,
height = 450 - margin.top - margin.bottom;
var svg = select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")");
var x = scaleBand()
.rangeRound([0, width])
.padding(0.1);
var y = scaleLinear()
.rangeRound([height, 0]);
var z = scaleOrdinal()
.range(["steelblue","darkorange", "red"]);
svg.append("g")
.attr("class","x-axis");
svg.append("g")
.attr("class", "y-axis");
var input = selectAll(".opt").property("value");
selectAll(".opt").on("change", function() {
update(data, this.value)
})
update(data);
function update(data) {
var keys = ["count1", "count2", "count3"];
var series = stack()
.keys(keys)
.offset(stackOffsetDiverging)
(data);
x.domain(data.map(d => d.label));
y.domain([
min(series, stackMin),
max(series, stackMax)
]).nice();
var barGroups = svg.selectAll("g.layer")
.data(series);
barGroups.exit().remove();
barGroups.enter().insert("g", ".x-axis")
.classed('layer', true);
svg.selectAll("g.layer")
.transition().duration(750)
.attr("fill", d => z(d.key));
var bars = svg.selectAll("g.layer").selectAll("rect")
.data(function(d) { return d; });
bars.exit().remove()
bars = bars
.enter()
.append("rect")
.attr("width", x.bandwidth())
.attr("x", d => x(d.data.label))
.merge(bars)
bars.transition().duration(750)
.attr("y", d => y(d[1]))
.attr("height", d => Math.abs(y(d[0])) - y(d[1]));
svg.selectAll(".x-axis").transition().duration(750)
.attr("transform", "translate(0," + y(0) + ")")
.call(axisBottom(x));
svg.selectAll(".y-axis").transition().duration(750)
.call(axisLeft(y));
function stackMin(serie) {
return min(serie, function(d) { return d[0]; });
}
function stackMax(serie) {
return max(serie, function(d) { return d[1]; });
}
}
}
});
In the first instance the chart renders correctly. The problem occurs when the model refreshes.
Rather than simply transition the bars, the whole chart is re-rendered and is placed ‘on top’ of the existing chart.
I know that this is because when the attrs update I am redrawing the whole chart but I am struggling to divide my code between the insertElement hook and the buildChart function. I tried to move all of the standard elements (such as svg, x, y etc) to insertElement and use setters/getters to reference them in buildChart but I constantly run into ‘is undefined’ errors.
Does anyone have any experience in similar problems?