|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
svg { |
|
display: block; |
|
width: 100%; |
|
} |
|
text { |
|
text-anchor: middle; |
|
font-size: 112px; |
|
font-weight: bold; |
|
font-family: sans-serif; |
|
fill: rgba(30,30,30,0.75); |
|
} |
|
</style> |
|
<svg viewBox="0 0 960 500"></svg> |
|
<script src="//d3js.org/d3.v5.min.js"></script> |
|
<script src="./d3-sine-wave.js"></script> |
|
<script> |
|
|
|
var svg = d3.select('svg') |
|
var viewBox = svg.attr('viewBox').split(' '); |
|
var width = +viewBox[2]; |
|
var height = +viewBox[3]; |
|
var donutWidth = 30; |
|
var donutPadding = 2; |
|
var value = 0.5; |
|
var valueScale = d3.scaleLinear().domain([0, 1]).range([height - donutWidth, donutWidth]); |
|
|
|
var line = d3.line() |
|
.curve(d3.curveBasis); |
|
|
|
var waves = [ |
|
d3.sineWave() |
|
.amplitude(10) |
|
.wavelength(2) |
|
.phase(0) |
|
.xTransform(function(d) { return d * width; }) |
|
.yTransform(function(d) { return d + valueScale(value); }), |
|
d3.sineWave() |
|
.amplitude(10) |
|
.wavelength(1.5) |
|
.phase(3) |
|
.xTransform(function(d) { return d * width; }) |
|
.yTransform(function(d) { return d + valueScale(value) + 15; }), |
|
d3.sineWave() |
|
.amplitude(10) |
|
.wavelength(1) |
|
.phase(6) |
|
.xTransform(function(d) { return d * width; }) |
|
.yTransform(function(d) { return d + valueScale(value) + 30; }) |
|
]; |
|
|
|
var arc = d3.arc() |
|
.innerRadius(Math.min(width / 2 - donutWidth + donutPadding, height / 2 - donutWidth + donutPadding)) |
|
.outerRadius(Math.min(width / 2, height / 2)) |
|
.startAngle(0) |
|
.endAngle(Math.PI * 2); |
|
|
|
|
|
var defs = svg.append('defs'); |
|
|
|
defs.append('clipPath') |
|
.attr('id', 'donut-hole-clip-path') |
|
.append('circle') |
|
.attr('cx', width / 2) |
|
.attr('cy', height / 2) |
|
.attr('r', Math.min(width / 2 - donutWidth, height / 2 - donutWidth)); |
|
|
|
defs.append('clipPath') |
|
.attr('id', 'donut-clip-path') |
|
.append('path') |
|
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')') |
|
.attr('d', arc()); |
|
|
|
var sine = svg.append('g') |
|
.attr('class', 'sine') |
|
.attr('clip-path', 'url(#donut-hole-clip-path'); |
|
|
|
var donut = svg.append('g') |
|
.attr('class', 'donut'); |
|
|
|
donut.append('rect') |
|
.attr('width', width) |
|
.attr('height', height) |
|
.attr('clip-path', 'url(#donut-clip-path)') |
|
.style('fill', '#ccc'); |
|
|
|
var donutScale = d3.scaleLinear().domain([0, 1]).range([donutWidth + donutPadding, height - donutWidth - donutPadding]); |
|
|
|
var filledDonut = donut.append('rect') |
|
.attr('y', donutScale(1 - value)) |
|
.attr('height', donutScale(value)) |
|
.attr('width', width) |
|
.attr('clip-path', 'url(#donut-clip-path)') |
|
.style('fill', d3.color('steelblue').darker(2)); |
|
|
|
var textValue = svg.append('text') |
|
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')') |
|
.attr('dy', '0.2em') |
|
.text(100 * value + '%'); |
|
|
|
update(value); |
|
|
|
var init = false; |
|
|
|
function update(val) { |
|
value = val; |
|
|
|
waves.forEach(function(wave) { |
|
wave.phase(10 * Math.random() - 5 + wave.phase()); |
|
}); |
|
|
|
var path = sine.selectAll('path') |
|
.data([ |
|
{ fill: d3.color('steelblue').brighter(), samples: waves[0].samples() }, |
|
{ fill: d3.color('steelblue'), samples: waves[1].samples() }, |
|
{ fill: d3.color('steelblue').darker(), samples: waves[2].samples() } |
|
]); |
|
|
|
var t = d3.transition() |
|
.delay(init ? 1000 : 0) |
|
.duration(3000) |
|
.on('end', update.bind(null, Math.random())); |
|
|
|
path.enter().append('path') |
|
.style('fill', function(d) { return d.fill; }) |
|
.merge(path) |
|
.transition(t) |
|
.ease(d3.easeCubicInOut) |
|
.attr('d', function(d) { |
|
return line(d.samples) + 'L' + width + ',' + height + 'L0,' + height + 'L' + d.samples[0][0] + ',' + d.samples[0][1]; |
|
}); |
|
|
|
filledDonut.transition(t) |
|
.attr('y', donutScale(1 - value)) |
|
.attr('height', donutScale(value)); |
|
|
|
textValue.transition(t) |
|
.on('start', function() { |
|
d3.active(this) |
|
.tween('text', function() { |
|
var format = d3.format('.0%'); |
|
var interpolate = d3.interpolateNumber(textValue.text().replace(/%/g, "") / 100, value); |
|
return function(time) { |
|
textValue.text(format(interpolate(time))); |
|
}; |
|
}); |
|
}); |
|
|
|
init = true; |
|
|
|
} |
|
|
|
</script> |