Created
April 30, 2021 07:49
-
-
Save tomgp/986f82c87dee35d7e8b78b07d612128a to your computer and use it in GitHub Desktop.
grouped bars
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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