Skip to content

Instantly share code, notes, and snippets.

@tomgp
Created April 30, 2021 07:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomgp/986f82c87dee35d7e8b78b07d612128a to your computer and use it in GitHub Desktop.
Save tomgp/986f82c87dee35d7e8b78b07d612128a to your computer and use it in GitHub Desktop.
grouped bars
<script src="https://d3js.org/d3.v6.min.js"></script>
<style>
svg{border:1px solid black;}
*{font-family: sans-serif;}
</style>
<body>
<svg width="500" height="500">
</svg>
<p>Example of grouped bars where each group has a different number of members</p>
<p>A response to this question on Stack Overflow: <a href="">d3js grouped bar chart, is this possible?</a></p>
</body>
<script>
// define the groups:
// each has a name and some arbitrary number of values
let groups = [
{ name:'one', values:[1,3,6,2] },
{ name:'two', values:[3,5,7,3,2,5] },
{ name:'three', values:[9,2,5] },
{ name:'four', values:[6] }
];
// set up some basic infomation about the chart, height, width and margins
const width= 500;
const height=500;
const margin= {top:10,left:10,bottom:20,right:10};
const plotHeight = height-(margin.top + margin.bottom);
const plotWidth = width-(margin.left + margin.right);
const groupPadding = 1.3; // the gap between each group as a proportion of a single bar
// we need to know how much horizontal space each group needs
// this depends on the number of values for that group
// we also need to know where each group starts int he final chart layout so
// cumulatively add to their starting point via the currentWidth variable
// in terms of our 'bar' unit
let currentWidth = 0;
groups = groups.map(group=>{
group.width = group.values.length;
group.startPosition = currentWidth;
currentWidth += group.width+groupPadding;
return group;
});
// work out the width needed in terms of bars
// the padding times the number of gaps + the total number of bars
const dataXDomain = [0, (groups.length-1)*groupPadding + d3.sum(groups, d=>d.width)];
// make a scale for the values,
// hard coding it here, but in reality you probably want the domain to relect
// the values in the data so that if the data changes so does the scale
const yScale = d3.scaleLinear()
.domain([0,10])
.range([plotHeight, margin.top]);
// ... and for the bars
const xScale = d3.scaleLinear()
.domain(dataXDomain)
.range([0,plotWidth]);
// Now we're ready to draw the chart!
//get the SVG and add a group, offset by our defined margins
const chart = d3.select('svg')
.append('g')
.attr(`transform`,`translate(${margin.left},${margin.top})`)
// for each group in the data add a group on the chart
const barGroups = chart.selectAll('g.bar-group')
.data(groups)
.enter()
.append('g')
.classed('bar-group', true)
.attr('transform', group=>`translate(${xScale(group.startPosition)}, 0)`); // postion each bargroup according to its calculated start position and our x-scale
// for each group...
barGroups.each(function(group){
const barGroup = d3.select(this)
// add and position a rectangle for each value
barGroup.selectAll('rect')
.data(group.values)
.enter()
.append('rect')
.attr('width',xScale(1))
.attr('height',value=>yScale(value))
.attr('x',(value, i)=>xScale(i))
.attr('y',value=>plotHeight - yScale(value))
.attr('fill','#FFAAAA')
.attr('stroke','#FF3333')
//add some labels for the group
barGroup.append('line')
.attr('x1',0)
.attr('x2',xScale(group.width))
.attr('y1',plotHeight)
.attr('y2',plotHeight)
.attr('stroke-width',2)
.attr('stroke','black')
barGroup.append('text')
.text(group=>group.name)
.attr('transform',`translate(0,${height-margin.bottom+4})`)
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment