Skip to content

Instantly share code, notes, and snippets.

@mcordingley
Last active March 27, 2016 21:36
Show Gist options
  • Save mcordingley/6464f515ce5fa1b90691 to your computer and use it in GitHub Desktop.
Save mcordingley/6464f515ce5fa1b90691 to your computer and use it in GitHub Desktop.
A speedometer-style chart for d3.
function gauge() {
'use strict';
var settings = {
'arc-size': 10,
angle: 90.0, // Degrees
arcGenerator: d3.svg.arc(),
angleScale: d3.scale.linear().range([-45, 45]), // Value to degrees
domain: null, // Raw values to that correspond to each color in range.
margin: {
bottom: 20,
left: 20,
right: 20,
top: 20
},
range: null, // Colors to use behind each segment.
transition: 500
};
function deg2Rad(degrees) {
return degrees * Math.PI / 180;
}
function getterSetter(variable, onSet) {
if (typeof onSet !== 'function') {
onSet = function() {};
}
return function(value) {
if (typeof value === 'undefined') {
return settings[variable];
}
settings[variable] = value;
onSet(value);
return chart;
};
}
var chart = function(selection) {
if (!settings.domain) {
throw 'Cannot render a gauge without a domain.';
}
if (!settings.range) {
throw 'Cannot render a gauge without a range.';
}
var content = selection.select('.content');
if (content.empty()) {
content = selection.append('g')
.classed('content', true)
.attr('transform', 'translate(' + settings.margin.left + ',' + settings.margin.top + ')');
}
content.transition(settings.transition)
.attr('transform', 'translate(' + settings.margin.left + ',' + settings.margin.top + ')');
var datum = selection.datum(),
node = selection.node(),
boundingRect = node.getBoundingClientRect(),
height = parseInt(boundingRect.height, 10) - settings.margin.bottom - settings.margin.top,
width = parseInt(boundingRect.width, 10) - settings.margin.left - settings.margin.right,
width_2 = width / 2,
radius = Math.min(width_2 / Math.cos(deg2Rad(90.0 - (settings.angle / 2))), height);
// Draw gauge arc.
settings.arcGenerator.outerRadius(radius).innerRadius(radius - settings['arc-size']);
var arc = content.select('.arc');
if (arc.empty()) {
var arc = content.append('g')
.classed('arc', true)
.attr("transform", "translate(" + width_2 + "," + height + ")");
}
var arcSegmentData = settings.range.map(function(d, i) {
return {
color: settings.range[i],
endAngle: deg2Rad(settings.angleScale(settings.domain[i + 1])),
startAngle: deg2Rad(settings.angleScale(settings.domain[i]))
};
});
var arcSegments = arc.selectAll('path').data(arcSegmentData);
arcSegments.enter().append('path')
.each(function (d) { this._current = d; })
.attr('d', settings.arcGenerator)
.attr('fill', function (d) { return d.color });
arcSegments.exit().remove();
arcSegments
.transition(settings.transition)
.attrTween('d', function(d) {
var interpolator = d3.interpolate(this._current, d);
this._current = interpolator(0);
return function(t) {
return settings.arcGenerator(interpolator(t));
};
})
.attr('fill', function (d) { return d.color })
// Draw gauge labels.
var tickGroup = content.select('.tick-group');
if (tickGroup.empty()) {
tickGroup = content.append('g').classed('tick-group', true);
}
var labelGroup = content.select('.label-group');
if (labelGroup.empty()) {
labelGroup = content.append('g').classed('label-group', true);
}
var tickData = settings.angleScale.ticks().map(function(tick) {
return {
angle: settings.angleScale(tick),
text: tick
};
});
var ticks = tickGroup.selectAll('line').data(tickData);
ticks.enter()
.append('line')
.each(function(d) { this._current = d.angle; })
.attr({
stroke: 'black',
x1: width_2,
y1: height - radius + settings['arc-size'],
x2: width_2,
y2: height - radius,
transform: function(d) {
return 'rotate(' + d.angle + ',' + width_2 + ',' + height + ')';
}
});
ticks.exit().remove();
ticks.transition(settings.transition)
.attr({
x1: width_2,
y1: height - radius + settings['arc-size'],
x2: width_2,
y2: height - radius
})
.attrTween('transform', function(d) {
var interpolator = d3.interpolate(this._current, d.angle);
this._current = d.angle;
return function(t) {
return 'rotate(' + interpolator(t) + ',' + width_2 + ',' + height + ')';
}
});
var labels = labelGroup.selectAll('text').data(tickData);
labels.enter()
.append('text')
.each(function(d) { this._current = d.angle; })
.text(function(d) { return d.text; })
.attr({
'text-anchor': 'middle',
x: width_2,
y: height - radius,
transform: function(d) {
return 'rotate(' + d.angle + ',' + width_2 + ',' + height + ')';
}
});
labels.exit().remove();
labels.transition(settings.transition)
.attr({
x: width_2,
y: height - radius - 5
})
.attrTween('transform', function(d) {
var interpolator = d3.interpolate(this._current, d.angle);
this._current = d.angle;
return function(t) {
return 'rotate(' + interpolator(t) + ',' + width_2 + ',' + height + ')';
}
});
// Draw gauge indicator
var indicator = content.select('.indicator');
if (indicator.empty()) {
content.append('circle')
.classed('indicator__knob', true)
.attr({
cx: width_2,
cy: height,
r: 5
});
indicator = content.append('line').datum(datum.value)
.classed('indicator', true)
.each(function(d) { this._current = d; })
.attr({
stroke: 'black',
'stroke-width': 2,
x1: width_2,
y1: height,
x2: width_2,
y2: height - radius
})
.attr('transform', function(d) {
return 'rotate(' + settings.angleScale(d) + ',' + width_2 + ',' + height + ')'
});
}
indicator.datum(datum.value)
.transition(settings.transition)
.attr({
x1: width_2,
y1: height,
x2: width_2,
y2: height - radius
})
.attrTween('transform', function(d) {
var interpolator = d3.interpolate(this._current, d);
this._current = d;
return function(t) {
return 'rotate(' + settings.angleScale(interpolator(t)) + ',' + width_2 + ',' + height + ')';
}
});
};
chart.arcSize = getterSetter('arc-size');
chart.angle = getterSetter('angle', function(value) {
settings.angleScale.range([-value / 2, value / 2]);
});
chart.domain = getterSetter('domain', function(value) {
settings.angleScale.domain([value[0], value[value.length -1]]);
});
chart.margin = getterSetter('margin');
chart.range = getterSetter('range');
chart.transition = getterSetter('transition');
return chart;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment