Skip to content

Instantly share code, notes, and snippets.

@dgwyer
Last active July 25, 2017 10:42
Show Gist options
  • Save dgwyer/f1f1c1fafc8086f19260e1316fc8a3c0 to your computer and use it in GitHub Desktop.
Save dgwyer/f1f1c1fafc8086f19260e1316fc8a3c0 to your computer and use it in GitHub Desktop.
Grouped Bar Chart - WP
license: gpl-3.0

This grouped bar chart is constructed from a CSV file storing the populations of different states by age group. The chart employs conventional margins and a number of D3 features:

forked from mbostock's block: Grouped Bar Chart

State Under 5 Years 5 to 13 Years 14 to 17 Years 18 to 24 Years 25 to 44 Years 45 to 64 Years 65 Years and Over
CA 2704659 4499890 2159981 3853788 10604510 8819342 4114496
TX 2027307 3277946 1420518 2454721 7017731 5656528 2472223
NY 1208495 2141490 1058031 1999120 5355235 5120254 2607672
FL 1140516 1938695 925060 1607297 4782119 4746856 3187797
IL 894368 1558919 725973 1311479 3596343 3239173 1575308
PA 737462 1345341 679201 1203944 3157759 3414001 1910571
<!DOCTYPE html>
<div class="pc-123 pc-chart"></div><!-- outputted via the shortcode. -->
<style>
.pc-chart .svg-area { fill: #bbb; }
.pc-chart .chart-area { fill: #ececec; }
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<script src="pcUtility.js"></script>
<script>
(function () {
// todo:
//
// 1. Abstract out the duplicate code from update and resize into a utility function.
// 3. Add resize function.
// 4. Allow customization of attributes (font size etc.) in the utility functions too. Maybe via global config object.
// 5. Need to make sure we can add multiple charts to the same page, including the same chart (just to check for bugs).
// 6. Manage ticks at small sizes?
// 7. Add vars to set container width, or let it be 100% width.
var lblTxt = { x: 'states', y: 'population', title: 'population vs states' };
var data, keys, svg, dim, ar = 0.4, w, h, cw, ch, chartEl = '.pc-123', x0, x1, y, z;
var visibleEl = { legend: true, xLbl: true, yLbl: true, chartLbl: true };
var chartOffset = { xLbl: 0, yLbl: 0, chartLbl: 0, legend: 23 },
margin = {top: 45, right: 20, bottom: 45, left: 65};
function defineScales() {
// define scales
x0 = d3.scaleBand().rangeRound([0, dim.cw]).paddingInner(0.1);
x1 = d3.scaleBand().padding(0.05);
y = d3.scaleLinear().rangeRound([dim.ch, 0]);
z = d3.scaleOrdinal().range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
}
// process data
d3.csv("data.csv", formatData, function(error, fileData) {
if (error) throw error;
data = fileData;
keys = data.columns.slice(1);
fields = data.columns.slice();
dim = pcChartDimensions(chartEl, ar, margin);
svg = pcCreateSVG(chartEl, dim, margin); defineScales();
pcSetupVisibleElements(svg, dim, z, margin, visibleEl, fields);
update(); // update chart
});
function update() {
x0.domain(data.map(function(d) { return d[fields[0]]; }));
x1.domain(keys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { return d[key]; }); })]).nice();
// create bars
svg.select('.pc-bars-g')
.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(" + x0(d[fields[0]]) + ",0)"; })
.selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
.enter().append("rect")
.attr('class', function(d,i) { return 'bar' + (i + 1); } )
.attrs({
x: function(d) { return x1(d.key); },
y: function(d) { return y(d.value); },
width: function () {return x1.bandwidth() },
height: function(d) { return dim.ch - y(d.value); },
fill: function(d) { return z(d.key); }
});
svg.select('.x-axis')
.call(d3.axisBottom(x0));
svg.select('.y-axis')
.call(d3.axisLeft(y).ticks(null, "s"));
// update axis labels, chart title, and legend
pcUpdateVisibleElements(svg, visibleEl, keys, dim, chartOffset, z, lblTxt, margin);
}
function formatData(d, i, columns) {
for (var i = 1, n = columns.length; i < n; ++i) d[columns[i]] = +d[columns[i]];
return d;
}
})();
</script>
function pcCreateSVG(el, dim, margin) {
// create SVG element
var svg = d3.select(el)
.append("svg")
.attr('class', 'pc-svg')
.attr('width', dim.w)
.attr('height', dim.h)
.append("g")
.attr('class', 'pc-main-g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('width', dim.cw)
.attr('height', dim.ch);
// add sub group containers
svg.append('g').attr('class', 'pc-svg-bg-g' );
svg.append('g').attr('class', 'pc-chart-bg-g' );
svg.append('g').attr('class', 'pc-grid-g' );
svg.append('g').attr('class', 'pc-axes-g' );
svg.append('g').attr('class', 'pc-bars-g');
svg.append('g').attr('class', 'pc-legend-g');
svg.append('g').attr('class', 'pc-labels-g' );
return svg;
}
// set chart width/height based on container, and aspect ratio
function pcChartDimensions(el, ar, margin) {
// calc chart container width / height
var w = document.querySelector(el).offsetWidth;
var h = w * ar;
// set height of container to match
document.querySelector(el).style.height = h + "px";
// chart width / height
var cw = w - margin.left - margin.right;
var ch = h - margin.top - margin.bottom;
return {
w: w,
h: h,
cw: cw,
ch: ch
};
}
function pcSetupVisibleElements(svg, dim, z, margin, ve, fields) {
svg.select('.pc-axes-g')
.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + dim.ch + ")");
svg.select('.pc-axes-g')
.append("g")
.attr("class", "axis y-axis");
if( ve.legend) {
svg.select('.pc-legend-g')
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end");
}
// text label for the x axis
if(ve.xLbl) {
svg.select('.pc-labels-g')
.append('text')
.attr("class", "x-axis-label")
.attr("dy", "1em")
.style("text-anchor", "middle");
}
// text label for the y axis
if(ve.yLbl) {
svg.select('.pc-labels-g')
.append('text')
.attr("class", "y-axis-label")
.attr("transform", "rotate(-90)")
.attr("dy", "1em")
.style("text-anchor", "middle");
}
// text label for the chart title
if(ve.chartLbl) {
svg.select('.pc-labels-g')
.append('text')
.attr("class", "chart-label")
.attr("dy", "1em")
.style("font-size", "16px")
.style("text-anchor", "middle");
}
// create svg bg rect
svg.select('.pc-svg-bg-g')
.append('rect')
.attr('class', 'svg-area')
.attrs({
x: 0 - margin.left,
y: 0 - margin.top,
width: dim.w,
height: dim.h
});
// create chart bg rect
svg.select('.pc-chart-bg-g')
.append('rect')
.attr('class', 'chart-area')
.attrs({
x: 0,
y: 0,
width: dim.cw,
height: dim.ch
});
}
function pcUpdateVisibleElements(svg, visibleEl, keys, dim, chartOffset, z, lblTxt, margin) {
// text label for the chart title
if(visibleEl.chartLbl) {
svg.select('.chart-label')
.attrs({
x: (dim.cw / 2),
y: 0 - (margin.top/2) - 10
})
.text(lblTxt.title);
}
// text label for the x axis
if(visibleEl.xLbl) {
if(lblTxt.x === '') lblTxt.x = fields[0];
svg.select('.x-axis-label')
.attrs({
transform: "translate(" + (dim.cw / 2) + " ," + (dim.ch + 12) + ")"
})
.text(lblTxt.x);
}
// text label for the y axis
if(visibleEl.yLbl) {
if(lblTxt.y === '') lblTxt.y = fields[1];
svg.select('.y-axis-label')
.attrs({
x: 0 - (dim.ch / 2),
y: 10 - margin.left
})
.text(lblTxt.y);
}
// update legend if enabled
if(visibleEl.legend) {
legend = svg.select('.pc-legend-g')
.selectAll("g")
.data(keys)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect").attr("x", dim.cw - chartOffset.legend).attr("y", 6).attr("width", 16).attr("height", 16).attr("fill", z);
legend.append("text").attr("x", dim.cw - chartOffset.legend - 5).attr("y", 14).attr("dy", "0.32em").text(function(d) { return d; });
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment