Skip to content

Instantly share code, notes, and snippets.

@ColinEberhardt
Last active August 9, 2018 13:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ColinEberhardt/dea5ae59475675e87d765b3a4a8dc057 to your computer and use it in GitHub Desktop.
Save ColinEberhardt/dea5ae59475675e87d765b3a4a8dc057 to your computer and use it in GitHub Desktop.
Small multiples
license: mit

This is a re-implementation of this neat small mutliple example using d3fc components.

Some notable things ...

  • The area, line and point series renderers remove some of the basic SVG rendering logic.
  • The cartesian chart component is being rendered via a data-join to create the small multiples. Because the chart uses a mixture of SVG and DOM, we can hide the y axis for repeated charts using CSS.
  • The responsive behaviour where the chart height is reduced for narrower screens uses a simple media query. This is made possible because the chart components uses the underlying d3fc element API which handles redrawing svg / canvas elements when their size changes.
<!DOCTYPE html>
<!-- include polyfills for custom event, Symbol and Custom Elements -->
<script src="//unpkg.com/babel-polyfill@6.26.0/dist/polyfill.js"></script>
<script src="//unpkg.com/custom-event-polyfill@0.3.0/custom-event-polyfill.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/document-register-element/1.8.0/document-register-element.js"></script>
<!-- use babel so that we can use arrow functions and other goodness in this block! -->
<script src="//unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="//unpkg.com/d3@5.5.0"></script>
<script src="//unpkg.com/d3fc@14.0.1"></script>
<style>
body {
font: 14px sans-serif;
}
g text {
font-size: 10px;
}
#chart {
display: flex;
}
#chart > div {
flex: 5;
height: 300px;
}
@media (min-width: 768px) {
#chart > div {
height: 500px;
}
body {
font: 16px sans-serif;
}
g text {
font-size: 12px;
}
}
#chart>div:not(:first-child) .y-axis,
#chart>div:not(:first-child) .y-axis-label {
opacity: 0;
}
/* Some slighlty hacky looking CSS - I'll look into this! */
.y-axis {
width: 1em !important;
}
.x-axis, .chart-label {
margin-left: 2em !important;
}
.area {
fill: #badcfe;
}
.annotation-line line, .line {
fill: none;
stroke: #8ba5be;
stroke-width: 1.5px;
}
.point {
fill: white;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
<div id='chart'></div>
<script src="small-multiple.js" type='text/babel'></script>
var sales = [
{'Year':2011,'Month':'Jan','Sales':320},
{'Year':2011,'Month':'Feb','Sales':230},
{'Year':2011,'Month':'Mar','Sales':365},
{'Year':2011,'Month':'Apr','Sales':385},
{'Year':2011,'Month':'May','Sales':300},
{'Year':2012,'Month':'Jan','Sales':380},
{'Year':2012,'Month':'Feb','Sales':180},
{'Year':2012,'Month':'Mar','Sales':275},
{'Year':2012,'Month':'Apr','Sales':450},
{'Year':2012,'Month':'May','Sales':410},
{'Year':2013,'Month':'Jan','Sales':320},
{'Year':2013,'Month':'Feb','Sales':170},
{'Year':2013,'Month':'Mar','Sales':375},
{'Year':2013,'Month':'Apr','Sales':510},
{'Year':2013,'Month':'May','Sales':390},
{'Year':2014,'Month':'Jan','Sales':420},
{'Year':2014,'Month':'Feb','Sales':125},
{'Year':2014,'Month':'Mar','Sales':310},
{'Year':2014,'Month':'Apr','Sales':450},
{'Year':2014,'Month':'May','Sales':410},
{'Year':2015,'Month':'Jan','Sales':460},
{'Year':2015,'Month':'Feb','Sales':195},
{'Year':2015,'Month':'Mar','Sales':360},
{'Year':2015,'Month':'Apr','Sales':410},
{'Year':2015,'Month':'May','Sales':385}
];
// group by month, giving our per-month small multiples
var groupedByMonth = d3.nest()
.key(d => d.Month)
.entries(sales);
// the various series components - setting the same crossValue
// and main-value for each is a bit repetitive.
var area = fc.seriesSvgArea()
.crossValue(d => d.Year)
.mainValue(d => d.Sales);
var line = fc.seriesSvgLine()
.crossValue(d => d.Year)
.mainValue(d => d.Sales);
var point = fc.seriesSvgPoint()
.crossValue(d => d.Year)
.mainValue(d => d.Sales);
// the average line
var average = fc.annotationSvgLine()
.value(d => d);
// bring all these renderers together into one using the multi-series. The area/point/line
// are mapped to the 'values' returned by d3.nest, and the line annotation maps to the average
var multi = fc.seriesSvgMulti()
.series([area, line, point, average])
.mapping((data, index, series) => {
switch (series[index]) {
case average:
return [d3.mean(data.values.map(function(d) { return d.Sales; }))];
default:
return data.values;
}
});
// compute the extent of the y-values, adding some padding
var yExtent = fc.extentLinear()
.include([0])
.pad([0, 0.2])
.accessors([d => d.Sales]);
// the chart!
var chart = fc.chartSvgCartesian(
d3.scaleLinear(),
d3.scaleLinear()
)
.xDomain([2010.5, 2015.5])
.yDomain(yExtent(sales))
.yOrient('left')
.xTicks(2)
.chartLabel(d => d.key)
.plotArea(multi);
// data-join to render our small multiples
d3.select('#chart')
.selectAll('div')
.data(groupedByMonth)
.enter()
.append('div')
.call(chart);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment