Skip to content

Instantly share code, notes, and snippets.

@luciyer
Last active Apr 13, 2021
Embed
What would you like to do?
Configurable Gauge Chart using D3.js
/* DEMO: https://observablehq.com/@luciyer/exportable-gauge */
/* USAGE */
let chart = GaugeChart();
// Optionally - set properties - these are defaults:
/*
chart.setProperties({
rotation: 0,
thickness: 0.15,
arc: 1.15,
ticks: 11,
color_scheme: "interpolateRdYlBu",
color_step: 150,
tick_color: "#FFF",
needle_color: "#000"
});
*/
chart.setPercentage(0.68);
var svg = chart.draw();
yield svg.node();
/* FUNCTION */
function GaugeChart() {
var pi = Math.PI,
rad = pi/180,
deg = 180/pi;
var properties = {
width: 800,
height: 300,
margin: 50,
rotation: 0,
thickness: 0.15,
arc: 1,
ticks: 5,
color_scheme: "interpolateRdYlBu",
color_step: 150,
tick_color: "#FFFFFF",
needle_color: "#000000"
};
var needlePercent = 0,
center = {},
radii = {},
angles = {},
ticks = {},
gradient = [],
scales = {};
var setCenter = (function initCenter () {
center.x = properties.width / 2,
center.y = properties.height - properties.margin;
return initCenter;
})();
var setRadii = (function initRadii () {
var base = properties.height - (2 * properties.margin);
radii.base = base,
radii.cap = base / 15,
radii.inner = base * (1 - properties.thickness),
radii.outer_tick = base + 5,
radii.tick_label = base + 15;
return initRadii;
})();
var setAngles = (function initAngles () {
var arc_complement = 1 - properties.arc;
angles.arc_complement = arc_complement,
angles.start_angle = (-pi/2) + (pi * arc_complement / 2) + (properties.rotation * rad),
angles.end_angle = (pi/2) - (pi * arc_complement / 2) + (properties.rotation * rad);
return initAngles;
})();
var setTicks = (function initTicks () {
var sub_arc = (angles.end_angle - angles.start_angle) / (properties.ticks - 1),
tick_pct = 100 / (properties.ticks - 1);
ticks = d3.range(properties.ticks).map(function(d) {
var sub_angle = angles.start_angle + (sub_arc * d);
return {
label: (tick_pct * d).toFixed(0) + '%',
angle: sub_angle,
coordinates: [[sub_angle, radii.inner],
[sub_angle, radii.outer_tick]]
}
});
return initTicks;
})();
var setGradient = (function initGradient () {
var c = d3[properties.color_scheme],
samples = properties.color_step,
total_arc = angles.end_angle - angles.start_angle,
sub_arc = total_arc / (samples);
gradient = d3.range(samples).map(function(d) {
var sub_color = d / (samples - 1),
sub_start_angle = angles.start_angle + (sub_arc * d),
sub_end_angle = sub_start_angle + sub_arc;
return {
fill: c(sub_color),
start: sub_start_angle,
end: sub_end_angle
}
});
return initGradient;
})();
var setScales = (function initScales () {
scales.lineRadial = d3.lineRadial();
scales.subArcScale = d3.arc()
.innerRadius(radii.inner + 1)
.outerRadius(radii.base)
.startAngle(d => d.start)
.endAngle(d => d.end);
scales.needleScale = d3.scaleLinear()
.domain([0, 1])
.range([angles.start_angle, angles.end_angle]);
return initScales;
})();
function updateValues () {
setCenter();
setRadii();
setAngles();
setTicks();
setGradient();
setScales();
}
var GaugeChart = {};
GaugeChart.setProperties = function(params) {
Object.keys(params).map(function(d) {
if (d in properties)
properties[d] = params[d];
else
throw new Error('One or more parameters not accepted.');
}); updateValues();
}
GaugeChart.getProperties = function () {
return properties;
}
GaugeChart.debug = function () {
return { needlePercent, properties, center, radii, angles, ticks, gradient, svg };
}
GaugeChart.setPercentage = function (pct) {
needlePercent = pct;
}
GaugeChart.draw = function () {
var svg = d3.create("svg")
.attr("viewBox", [0, 0, properties.width, properties.height])
var gauge = svg.append("g")
.attr("transform", `translate(${center.x}, ${center.y})`)
.attr("class", "gauge-container");
gauge.append("g")
.attr("class", "gauge-arc")
.selectAll("path")
.data(gradient)
.enter()
.append("path")
.attr("d", scales.subArcScale)
.attr("fill", d => d.fill)
.attr("stroke-width", 0.5)
.attr("stroke", d => d.fill);
gauge.append("g")
.attr("class", "gauge-ticks")
.selectAll("path")
.data(ticks)
.enter()
.append("g")
.attr("class", "tick")
.append("path")
.attr("d", d => scales.lineRadial(d.coordinates))
.attr("stroke", properties.tick_color)
.attr("stroke-width", 2)
.attr("stroke-linecap", "round")
.attr("fill", "none");
gauge.select("g.gauge-ticks")
.selectAll("text")
.data(ticks)
.enter()
.append("g")
.attr("class", "tick-label")
.append("text")
.attr("transform", d =>
`translate(${radii.tick_label * Math.sin(d.angle)},
${-radii.tick_label * Math.cos(d.angle)})
rotate(${d.angle * deg - pi})`)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.attr("font-size", "0.67em")
.text(d => d.label);
gauge.append("g")
.attr("class", "needle")
.selectAll("path")
.data([needlePercent])
.enter()
.append("path")
.attr("d", d => scales.lineRadial([[0,0], [scales.needleScale(d), radii.outer_tick]]))
.attr("stroke", properties.needle_color)
.attr("stroke-width", 6)
.attr("stroke-linecap", "round");
gauge.select("g.needle")
.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", radii.cap)
.attr("stroke", properties.needle_color)
.attr("stroke-width", 6)
.style("fill", "white");
return svg;
}
return GaugeChart;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment