Unable to add animation to my stacked bar plot in d3js? - d3.js

I am having a stacked bar chart and i would like to animate the stacked bar chart. I am using d3js v5.
my project is available at https://stackblitz.com/edit/angular-2xgkwr
https://angular-2xgkwr.stackblitz.io
the animation i like is bars growing up as show here https://bl.ocks.org/guilhermesimoes/8913c15adf7dd2cab53a
really appreciate if you can help

I just updated the code and it starts to work
g.append('g')
.selectAll('g')
.data(d3.stack().keys(keys)(data))
.enter().append('g')
// tslint:disable-next-line:only-arrow-functions
.attr('class', d => z(d.key))
.selectAll('rect')
.data(d => d )
.enter().append('rect')
.attr('x', d =>
x(new Date(d.data.dateRange))
)
.attr("height", 0)
.attr('height', function(d) { return y(d[0]) - y(d[1]); })
.transition().duration(750).delay(calculateDelayFn)
.attr('y', function(d) {
return y(d[1]); }
)

Related

d3.js general update pattern for stacked bar chart

I am making a stacked bar chart in d3.js and have the following code which currently works for what I need:
const stackedData = d3.stack().keys(keys)
const layers = stackedData(this.data)
let time = this.chart.selectAll('.time').data(layers);
// removes time from the DOM
time.exit().remove();
// adds time to the DOM
this.chart.append("g")
.selectAll("g")
.data(layers)
.join("g")
.style("fill", (d, i) => colors[i])
.selectAll('.time')
.data(d => d)
.enter().append('rect')
.attr('class', 'time')
.attr('x', d => this.xScale(d.data.date))
.attr('y', d => this.yScale(0))
.attr("width", this.xScale.bandwidth())
.attr('height', 0)
.transition()
.delay((d, i) => i * 10)
.attr('y', d => this.yScale(d[1]))
.attr('height', d => this.yScale(d[0]) - this.yScale(d[1]))
However, I am unsure on what the general update pattern should be to update the shapes already present in the DOM on refresh. for a standard (non-stacked) chart, it would be as follows, however these does not work for a stacked chart:
// update existing time
this.chart.selectAll('.time').transition()
.attr('x', d => this.xScale(d.date))
.attr('y', d => this.yScale(d.revenue))
.attr('width', this.xScale.bandwidth())
.attr('height', d => this.graphHeight - this.yScale(d.revenue))
.attr("fill", "#007bff")

How do I make d3 bars grow in height at same pace during an animation

My bar charts start growing in height slowly and then rapidly towards the end.
I want them to grow at the same pace
I have set ease to easeLinear but it doesn't appear to have any impact.
As an experiment, I used .ease(d3.easeBounce) but nothing changed. The bars started growing slowly and then rapidly towards the end.
Where am I going wrong?
function renderVerticalBars(data, measurements, metric, countryID) {
let selectDataForBarCharts = d3.select("svg")
.selectAll("rect")
.data(data, d => d[countryID])
selectDataForBarCharts
.enter()
.append("rect")
.attr('width', measurements.xScale.bandwidth())
.attr("height", 0)
.attr('y', d => measurements.yScale(0))
.merge(selectDataForBarCharts)
.attr("fill", d => setBarColor(d))
.attr("transform", `translate(0, ${measurements.margin.top})`)
.attr('width', measurements.xScale.bandwidth())
.attr('x', (d) => measurements.xScale(d[countryID]))
.on('mouseover', (event, barData) => { displayComparisons(event, barData, data, metric, countryID, measurements) })
.on('mouseout', (event) => { renderValuesInVerticalBars(data, metric, countryID, measurements) })
.transition()
.ease(d3.easeLinear)
.duration(4000)
.attr("height", d => measurements.innerHeight - measurements.yScale(d[metric]))
.attr("y", (d) => measurements.yScale(d[metric]))
}

Transition height stacked bar chart

I have a codepen here - https://codepen.io/anon/pen/GybENz
I've created a simple stacked bar chart with a legend to filter the chart.
I'd like to animated the height of the bar from the bottom axis up.
Currently its animating from the left and down
let layersBar = layersBarArea.selectAll('.layer').data(stackedSeries)
.enter()
.append('g')
.attr('class', 'layer')
.style('fill', (d, i) => {
return colors[i];
});
layersBar.selectAll('rect')
.data((d) => {
return d
})
.enter()
.append('rect')
.attr('height', 100)
.transition()
.duration(400)
.attr('height', (d, i) => {
return y(d[0]) - y(d[1]);
})
.attr('y', 0)
.attr('y', (d) => {
return y(d[1]);
})
.attr('x', (d, i) => {
return x(d.data.date)
})
.attr('width', x.bandwidth());
}
Set the x position, the width, the y position (as the baseline) and the height (as zero) before the transition:
.attr('height', 0)
.attr("y", h - margin.bottom - margin.top)
.attr('x', (d, i) => {
return x(d.data.date)
})
.attr('width', x.bandwidth())
Here is the updated CodePen: https://codepen.io/anon/pen/ypdoMK?editors=0010
PS: It would be a good idea transitioning each rectangle individually. For instance, if the user clicked usedInf, you should transition only those rectangles... however, since you did this...
layersBarArea.selectAll('g.layer').remove();
... at the beginning of your drawChart function, which is a wrong approach, such suggestion will need a big refactor, out of the scope of this question/answer.

Transition effects in population pyramid

I am working on a population pyramid that has an updating function.
http://bricbracs.com/hh/
As you can see the bars expand and contract in a horizontal line when you update it with new data. I want to modify the transition effect so that the bars enter and exit vertically like this:
http://vis.stanford.edu/jheer/d3/pyramid/shift.html
I have been following this tutorial and modifying the code but so far no luck.
https://strongriley.github.io/d3/tutorial/bar-2.html
Here is the code that first draws the bars on loading. (this is the male bar group, the female bar group is the same)
leftBarGroup.selectAll('.bar.left')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar left')
.attr('y', function(d) { return yScale(d.group); })
.attr("width", 0)
.attr("opacity", 0)
.transition()
.duration(500)
.attr('width', function(d) { return xScale(d.male); })
.attr('height', yScale.rangeBand())
.attr("opacity", 1)
And here is the corresponding part of the code in the updating function which changes the bars.
var sel = leftBarGroup.selectAll('.bar.left')
.attr('class', 'bar left')
.data(data)
.data(data, function(d) { return d.male; })
.transition()
.attr('y',0)
.duration(500)
.attr('y', function(d) { return yScale(d.group); })
.attr('height', yScale.rangeBand())
.attr('width', function(d) { return xScale(d.male); })
.attr('height', yScale.rangeBand())
Thanks in advance.
Here's one way to reproduce the effect in your linked example. I offset the bars and then slide them back into place. You then handle the top and bottom bars slightly different.
Note, I only did the slide down on the male side of the pyramid, if you need help going the rest of the way just leave me a comment.
var sel = leftBarGroup.selectAll('.bar.left')
.attr('class', 'bar left')
.data(data)
.data(data, function(d) {
return d.male;
})
// offset y to slide down
.attr('y', function(d){
var self = d3.select(this);
return +self.attr('y') - yScale.rangeBand();
})
.transition()
.duration(500)
// slide it back into place
.attr('y', function(d) {
return yScale(d.group);
})
// and set new width
.attr('width', function(d) {
return xScale(d.male);
});
// for the very top bar
// not only slide it but "fade it in"
leftBarGroup.select(':last-child')
.style('opacity', 0)
.transition()
.duration(500)
.style('opacity', 1)
.attr('y', function(d) {
return yScale(d.group);
});
// append a fake bar on the bottom
// to slide and fade out
leftBarGroup.append('rect')
.attr('y', function(d) {
return yScale('0-4');
})
.attr('height', yScale.rangeBand())
.attr('width', function(){
return leftBarGroup.select(':first-child').attr('width');
})
.style('fill', 'steelblue')
.style('opacity', 0.6)
.transition()
.duration(500)
.attr('y', function(d) {
return yScale('0-4') + yScale.rangeBand();
})
.style('opacity', 0)
.remove();
EDITS
Going up is just a matter of reversing the logic:
var sel = leftBarGroup.selectAll('.bar.left')
.attr('class', 'bar left')
.data(data)
.data(data, function(d) {
return d.male;
})
// offset y to slide up
.attr('y', function(d){
var self = d3.select(this);
return +self.attr('y') + yScale.rangeBand()
})
.transition()
.duration(500)
// slide it back into place
.attr('y', function(d) {
return yScale(d.group);
})
// and set new width
.attr('width', function(d) {
return xScale(d.male);
});
// for the very bottom bar
// not only slide it but "fade it in"
leftBarGroup.select(':first-child')
.style('opacity', 0)
.transition()
.duration(500)
.style('opacity', 1)
.attr('y', function(d) {
return yScale(d.group);
});
// append a fake bar on the top
// to slide and fade out
var w = leftBarGroup.select(':last-child').attr('width');
leftBarGroup.append('rect')
.attr('class','fake')
.attr('y', function(d) {
return yScale('90+');
})
.attr('height', yScale.rangeBand())
.attr('width', w)
.style('fill', 'steelblue')
.style('opacity', 0.6)
.transition()
.duration(500)
.attr('y', function(d) {
return yScale('90+') - yScale.rangeBand();
})
.style('opacity', 0)
.remove();
Updated code sample here.

How to add space between bars in a grouped bar chart in a nvd3 grouped multibar chart?

I'm trying to add some space/padding for a nvd3 multi bar chart. "groupSpacing" is not what I need, since it only adds space between groups. I'll need space between each bar inside group. I found one link in github support. Can you post any solution or tweak?
I also found a d3 example of grouped bar chart. Any help in this example also very helpful to me.
Thanks.
I have draw a d3 group barchart:
fiddle
You can adjust the groupSpacing by change the code on line 56:
var groupSpacing = 6;
Technically i just achieve it by change the width of each rects' width:
var barsEnter = bars.enter().append('rect')
.attr('class', 'stm-d3-bar')
.attr('x', function(d,i,j) {
return (j * x1.rangeBand() );
})
.attr('y', function(d) { return y(d.y); })
.attr('height', function(d) { return height - y(d.y); })
.attr('width', x0.rangeBand() / barData.length - groupSpacing )
.attr('transform', function(d,i) {
return 'translate(' + x0(d.x) + ',0)';
})
.style("fill", function(d, i, j) {
return color(data[j].key);
});
Hope it helps you understand how you can achieve it in d3.
I minus the number of group spacing from the "width" attribute also. I found that the x-axis label looks a little off after I did that so I add the (group spacing / 2) to the "x" attribute. Here is the example of my code.
var groupSpacing = 15;
var rect = groups.selectAll("rect")
.data(function (d) { return d; })
.enter()
.append("rect")
.attr("x", function (d) { return x(d.x) + (groupSpacing / 2) ; })
.attr("y", function (d) { return y(d.y0 + d.y); })
.attr("height", function (d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.rangeBand() - groupSpacing)

Resources