Skip to content

Instantly share code, notes, and snippets.

@ErikOnBike
Last active September 30, 2018 14:25
Show Gist options
  • Save ErikOnBike/dc95a68532f111da923ae533fc7d54d3 to your computer and use it in GitHub Desktop.
Save ErikOnBike/dc95a68532f111da923ae533fc7d54d3 to your computer and use it in GitHub Desktop.
Rewrite of Mike Rostock's Bullet Charts gist using d3-template
license: gpl-3.0

This example is a rewrite of the Bullet Charts example from Mike Bostock. By using a template the amount of code needed to create the SVG elements is reduced. The construction and layout of the SVG elements are more recognizable than from code. The SVG elements do contain more data- attributes defining the (transformed) data. You decide which form you prefer.

[
{"title":"Revenue","subtitle":"US$, in thousands","ranges":[150,225,300],"measures":[220,270],"markers":[250]},
{"title":"Profit","subtitle":"%","ranges":[20,25,30],"measures":[21,23],"markers":[26]},
{"title":"Order Size","subtitle":"US$, average","ranges":[350,500,600],"measures":[100,320],"markers":[550]},
{"title":"New Customers","subtitle":"count","ranges":[1400,2000,2500],"measures":[1000,1650],"markers":[2100]},
{"title":"Satisfaction","subtitle":"out of 5","ranges":[3.5,4.25,5],"measures":[3.2,4.7],"markers":[4.4]}
]
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
padding-top: 40px;
position: relative;
width: 960px;
}
button {
position: absolute;
right: 10px;
top: 10px;
}
.bullet { font: 10px sans-serif; }
.bullet .marker { stroke: #000; stroke-width: 2px; }
.bullet .tick line { stroke: #666; stroke-width: .5px; }
.bullet .range.s0 { fill: #eee; }
.bullet .range.s1 { fill: #ddd; }
.bullet .range.s2 { fill: #ccc; }
.bullet .measure.s0 { fill: lightsteelblue; }
.bullet .measure.s1 { fill: steelblue; }
.bullet .title { font-size: 14px; font-weight: bold; }
.bullet .subtitle { fill: #999; }
</style>
<button>Update</button>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-template-plugin/build/d3-template.min.js"></script>
<div id="template" data-repeat="{{d}}">
<svg class="bullet" width="960" height="50">
<g transform="translate(120,5)">
<g data-repeat="{{d.ranges.sort(sortDescending)}}">
<rect data-attr-class="{{`range s${i}`}}" width="0" data-attr-width="{{scale(d, this)}}" height="25" x="0" y="0"></rect>
</g>
<g data-repeat="{{d.measures.sort(sortDescending)}}">
<rect data-attr-class="{{`measure s${i}`}}" width="0" data-attr-width="{{scale(d, this)}}" height="8.333333" x="0" y="8.333333"></rect>
</g>
<g data-repeat="{{d.markers.sort(sortDescending)}}">
<line data-attr-class="{{`marker s${i}`}}" x1="0" x2="0" data-attr-x1="{{scale(d, this)}}" data-attr-x2="{{scale(d, this)}}" y1="4" y2="21"></line>
</g>
<g data-repeat="{{ticks(d)}}">
<g class="tick" transform="translate(990,0)" data-attr-transform="{{`translate(${scale(d.x, this)},0)`}}">
<line y1="25" y2="29"></line>
<text text-anchor="middle" dy="1em" y="29">{{d.text}}</text>
</g>
</g>
<g transform="translate(-6,12.5)" text-anchor="end">
<text class="title">{{d.title}}</text>
<text class="subtitle" dy="1em">{{d.subtitle}}</text>
</g>
</g>
</svg>
</div>
<script>
var margin = {top: 5, right: 40, bottom: 20, left: 120},
width = 960 - margin.left - margin.right,
height = 50 - margin.top - margin.bottom;
// Create template helper functions
function sortDescending(a, b) {
return b - a;
}
function scale(d, node) {
return d3.select(node.parentNode).datum().scale(d);
}
function ticks(d) {
return d.scale.ticks(8).map(function(tick) {
return { x: tick, text: d.scale.tickFormat(8)(tick) };
});
}
// Create template from DOM
var template = d3.select("#template").template();
// Read data and render it
d3.json("bullets.json", function(error, data) {
if (error) throw error;
// Add scales to data items
data.forEach(function(item) {
addScale(item);
});
// Render data onto template
template.render(data);
// Add event handler
d3.selectAll("button").on("click", function() {
// Update data
data.forEach(function(item) {
randomize(item);
addScale(item);
});
// Render new data with transition
template.transition()
.duration(1000)
.render(data)
;
});
});
function addScale(d) {
d.scale = d3.scaleLinear()
.domain([ 0, Math.max(
Math.max.apply(null, d.ranges),
Math.max.apply(null, d.measures),
Math.max.apply(null, d.markers)
) ])
.range([0, width])
;
}
function randomize(d) {
if (!d.randomizer) d.randomizer = randomizer(d);
d.ranges = d.ranges.map(d.randomizer);
d.markers = d.markers.map(d.randomizer);
d.measures = d.measures.map(d.randomizer);
return d;
}
function randomizer(d) {
var k = d3.max(d.ranges) * .2;
return function(d) {
return Math.max(0, d + k * (Math.random() - .5));
};
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment