简体   繁体   中英

d3.js stacked bar chart - modify stack order logic

I would like to create a stacked bar chart whereby the order of the rects is determined by data values (ie largest to smallest, tallest to shortest, richest to poorest, ect). To the best of my knowledge, after stacking data, the initial order seems to be preserved. This can be seen in my snippet, hardcoded data lets us see what's happening before and after d3.stack() . Note that the third rect fmc3 goes from being the third largest in t1 to the largest of all rects in t3 despite its position in the stack remaining the same:

 var margins = {top:100, bottom:300, left:100, right:100}; var height = 600; var width = 900; var totalWidth = width+margins.left+margins.right; var totalHeight = height+margins.top+margins.bottom; var svg = d3.select('body').append('svg').attr('width', totalWidth).attr('height', totalHeight); var graphGroup = svg.append('g').attr('transform', "translate("+margins.left+","+margins.top+")"); var data = [ {period:'t1', fmc1:2, fmc2:5, fmc3:6, fmc4:9, fmc5:10}, {period:'t2', fmc1:3, fmc2:4, fmc3:9, fmc4:8, fmc5:11}, {period:'t3', fmc1:3, fmc2:5, fmc3:15, fmc4:12, fmc5:10}, ]; var groups = d3.map(data, function(d){return(d.period)}).keys(); var subgroups = Object.keys(data[0]).slice(1); var stackedData = d3.stack().keys(subgroups) (data); //console.log(stackedData); var yScale = d3.scaleLinear().domain([0,80]).range([height,0]); var xScale = d3.scaleBand().domain(['t1','t2','t3']).range([0,width]).padding([.5]); var colorScale = d3.scaleOrdinal().domain(subgroups).range(["#003366","#366092","#4f81b9","#95b3d7","#b8cce4","#e7eef8","#a6a6a6","#d9d9d9","#ffffcc","#f6d18b","#e4a733","#b29866","#a6a6a6","#d9d9d9","#e7eef8","#b8cce4","#95b3d7","#4f81b9","#366092","#003366"].reverse()); graphGroup.append("g").selectAll("g").data(stackedData).enter().append("g").attr("fill", function(d) { return colorScale(d.key); }).selectAll("rect").data(function(d) { return d; }).enter().append("rect").attr("x", function(d) { return xScale(d.data.period); }).attr("y", function(d) { return yScale(d[1]); }).attr("height", function(d) { return yScale(d[0]) - yScale(d[1]); }).attr("width",xScale.bandwidth());
 <script src="https://d3js.org/d3.v5.min.js"></script>

I suspect preserving the initial order may be somewhat necessary to calculate adjacent rects in the stack. However, on the other hand, ordering data before visualizing it is a very common, even preferred practice in the field of visualization and I would be surprised if no one has found a solution to this issue yet.

Question

Given there are no built-in features to specify the ordering of the rects in a stack, how should I approach the sort logic to achieve largest to smallest ordering?

Well, there is a built-in feature to specify the order, which is stack.order() . However, it specifies the order computing the entire series , not every single value the stack (which I believe is what you want... in that case, you'll have to create your own function).

So, for instance, using stack.order(d3.stackOrderDescending) :

 var margins = { top: 0, bottom: 0, left: 0, right: 0 }; var height = 300; var width = 500; var totalWidth = width + margins.left + margins.right; var totalHeight = height + margins.top + margins.bottom; var svg = d3.select('body').append('svg').attr('width', totalWidth).attr('height', totalHeight); var graphGroup = svg.append('g').attr('transform', "translate(" + margins.left + "," + margins.top + ")"); var data = [{ period: 't1', fmc1: 2, fmc2: 5, fmc3: 6, fmc4: 9, fmc5: 10 }, { period: 't2', fmc1: 3, fmc2: 4, fmc3: 9, fmc4: 8, fmc5: 11 }, { period: 't3', fmc1: 3, fmc2: 5, fmc3: 15, fmc4: 12, fmc5: 10 }, ]; var groups = d3.map(data, function(d) { return (d.period) }).keys(); var subgroups = Object.keys(data[0]).slice(1); var stackedData = d3.stack().keys(subgroups).order(d3.stackOrderDescending) (data); //console.log(stackedData); var yScale = d3.scaleLinear().domain([0, 60]).range([height, 0]); var xScale = d3.scaleBand().domain(['t1', 't2', 't3']).range([0, width]).padding([.5]); var colorScale = d3.scaleOrdinal().domain(subgroups).range(["#003366", "#366092", "#4f81b9", "#95b3d7", "#b8cce4", "#e7eef8", "#a6a6a6", "#d9d9d9", "#ffffcc", "#f6d18b", "#e4a733", "#b29866", "#a6a6a6", "#d9d9d9", "#e7eef8", "#b8cce4", "#95b3d7", "#4f81b9", "#366092", "#003366"].reverse()); graphGroup.append("g").selectAll("g").data(stackedData).enter().append("g").attr("fill", function(d) { return colorScale(d.key); }).selectAll("rect").data(function(d) { return d; }).enter().append("rect").attr("x", function(d) { return xScale(d.data.period); }).attr("y", function(d) { return yScale(d[1]); }).attr("height", function(d) { return yScale(d[0]) - yScale(d[1]); }).attr("width", xScale.bandwidth());
 <script src="https://d3js.org/d3.v5.min.js"></script>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM