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")
Related
So, I am faced with a situation where I have little to no experience coding in typescript and I still have to create a custom visual for PowerBI.
I am slowly slowly getting there through online tutorials and scavenging code...
I do not however understand the difference between update's handling of the barchart boxes and the labels in my code.
I tried implementing them identically, but while the chart itself updates fine, the old labels remain.
I have tried googling but it is a classic case of simply not knowing the term to google for so I am at a loss on how to proceed.
Here is the (hopefully) relevant part of my code. Any help is much appreciated:
const bars = this.barContainer
.selectAll('.bar')
.data(extractedData); /*select all bar elements and then passes data using "data" command and stores it in "bars" variable */
bars.enter()
.append('rect')
.classed('bar', true)
.attr('height', y.bandwidth())
.attr('width', dataPoint => width - cm - x(dataPoint.value))
.attr('x', dataPoint => (x(dataPoint.value) + cm) / 2)
.attr('y', dataPoint => y(dataPoint.category))
.attr('fill', dataPoint => dataPoint.color);
bars
.attr('height', y.bandwidth())
.attr('width', dataPoint => width - cm - x(dataPoint.value))
.attr('x', dataPoint => (x(dataPoint.value) + cm) / 2)
.attr('y', dataPoint => y(dataPoint.category));
const mylabels = this.textLabel
.selectAll('.textlabel')
.data(extractedData);
mylabels.enter()
.append('text')
.classed('textlabel', true)
.attr("class", "label")
.attr('x', dataPoint => (x(dataPoint.value) + cm) / 2)
.attr('y', dataPoint => y(dataPoint.category))
.attr("dy", ".75em")
.text(function (d) { return d.value; });
mylabels
.attr('x', dataPoint => (x(dataPoint.value) + cm) / 2)
.attr('y', dataPoint => y(dataPoint.category))
bars.exit().remove();
mylabels.exit().remove();
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]))
}
I am following the video tutorial (https://channel9.msdn.com/Blogs/Azure/Building-Power-BI-custom-visuals-that-meet-your-app-needs) to create a bar chart (custom visual) within Power BI.
Code snippet (in the video at 13.40):
let bars = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
bars.enter().append('rect').classed('bar', true);
bars
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
bars.exit().remove();
The above code is written in update method of visual.ts file.
As per my understanding, the enter() method will create rect elements with class bar based on the viewModel.dataPoints data.
The problem is I am not able to get it to work upon initial load, when the visual is loaded, this works only when the visual is resized. I cannot see any attributes getting applied on the rect elements upon load, but its getting applied when a resize is made on the visual.
If I changed the code to:
bars.enter().append('rect').classed('bar', true)
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
bars.transition()
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
bars.exit().remove();
The above code works fine, The attributes are getting applied for both initial load and resize.
Additional Info (Constructor code)
let svg = this.svg = d3.select(options.element).append('svg').classed('barChart', true);
this.barContainer = svg.append('g').classed('barContainer', true);
this.xAxis = svg.append('g').classed('xAxis', true);
Is this the way its supposed to work in the latest version (D3 Js 5.0)? or I am doing anything wrong?
The .merge() function does the trick.
The solution is:
let bars = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
const a = bars.enter().append('rect').merge(<any>bars); // merge before attr()
a.classed('bar', true);
a
.transition().duration(50)
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category)); // This code now works for initial
// load and update.
bars.exit().remove();
This should help talk you through merge. There is a link at the bottom if you would prefer to use the new join method in d3.js v5.
https://www.codementor.io/#milesbryony/d3-js-merge-in-depth-jm1pwhurw
Or for more complex use see Mike Bostock's
https://observablehq.com/#d3/selection-join
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]); }
)
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.