Last active
March 27, 2016 21:36
-
-
Save mcordingley/6464f515ce5fa1b90691 to your computer and use it in GitHub Desktop.
A speedometer-style chart for d3.
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
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