Experimental demo of a water-like effect using sine waves. This demo uses a tiny d3 plugin d3-sine-wave to help manage sine wave related state.
Related:
| screenshots |
Experimental demo of a water-like effect using sine waves. This demo uses a tiny d3 plugin d3-sine-wave to help manage sine wave related state.
Related:
| (function(d3) { | |
| d3.sineWave = function() { | |
| var amplitude = 1; | |
| var wavelength = 2; | |
| var phase = 2 | |
| var samplesCount = 20; | |
| var xTransform = function(d) { return d; }; | |
| var yTransform = function(d) { return d; }; | |
| function sineWave() { } | |
| sineWave.amplitude = function(_) { | |
| if (!arguments.length) return amplitude; | |
| amplitude = _; | |
| return sineWave; | |
| } | |
| sineWave.wavelength = function(_) { | |
| if (!arguments.length) return wavelength; | |
| wavelength = _; | |
| return sineWave; | |
| } | |
| sineWave.phase = function(_) { | |
| if (!arguments.length) return phase; | |
| phase = _; | |
| return sineWave; | |
| } | |
| sineWave.samplesCount = function(_) { | |
| if (!arguments.length) return samplesCount; | |
| samplesCount = _; | |
| return sineWave; | |
| } | |
| /** | |
| * Transforms x value post calculation. Transformation function is passed a x value that ranges between 0 and 1 inclusively. | |
| * @param {function} _ Transforming function | |
| */ | |
| sineWave.xTransform = function(_) { | |
| if (!arguments.length) return xTransform; | |
| xTransform = _; | |
| return sineWave; | |
| } | |
| /** | |
| * Transforms y value post calculation. Transformation function reflects sine parameters. If amplitude is 1 values will range from -1 to 1. | |
| * @param {function} _ Transforming function | |
| */ | |
| sineWave.yTransform = function(_) { | |
| if (!arguments.length) return yTransform; | |
| yTransform = _; | |
| return sineWave; | |
| } | |
| sineWave.samples = function() { | |
| return d3.range(samplesCount).map(function(d) { | |
| var t = d / (samplesCount - 1); | |
| var y = Math.sin(2 * Math.PI * t * wavelength + phase) * amplitude; | |
| return [xTransform(t), yTransform(y)]; | |
| }); | |
| } | |
| return sineWave; | |
| } | |
| }(window.d3 )); |
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| svg { | |
| display: block; | |
| width: 100%; | |
| } | |
| </style> | |
| <svg viewBox="0 0 960 500"></svg> | |
| <script src="https://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 value = 0.5; | |
| var valueScale = d3.scaleLinear().domain([0, 1]).range([0, height]); | |
| var waves = [ | |
| d3.sineWave() | |
| .amplitude(10) | |
| .wavelength(2) | |
| .phase(0) | |
| .xTransform(function(d) { return d * width; }) | |
| .yTransform(function(d) { return d + valueScale(value) - 30; }), | |
| d3.sineWave() | |
| .amplitude(10) | |
| .wavelength(1.5) | |
| .phase(3) | |
| .xTransform(function(d) { return d * width; }) | |
| .yTransform(function(d) { return d + valueScale(value); }), | |
| d3.sineWave() | |
| .amplitude(10) | |
| .wavelength(1) | |
| .phase(6) | |
| .xTransform(function(d) { return d * width; }) | |
| .yTransform(function(d) { return d + valueScale(value) + 30; }) | |
| ]; | |
| var line = d3.line() | |
| .curve(d3.curveBasis); | |
| var sine = svg.append('g') | |
| .attr('class', 'sine'); | |
| var color = d3.scaleOrdinal() | |
| .domain([0, 1, 2]) | |
| .range([d3.color('steelblue').brighter(), d3.color('steelblue'), d3.color('steelblue').darker()]); | |
| d3.timer(tick, 150); | |
| function tick(elapsed) { | |
| // Sine wave will complete phase every 5 seconds | |
| var tweenValue = elapsed % 5000 / 5000; | |
| var data = waves.map(function(wave, i) { | |
| var reverse = i % 2 ? 1 : -1; | |
| // update the phase based on tweenValue | |
| wave.phase(2 * Math.PI * tweenValue * reverse); | |
| return { | |
| fill: color(i), | |
| samples: wave.samples() | |
| }; | |
| }); | |
| var path = sine.selectAll('path') | |
| .data(data); | |
| path.enter().append('path') | |
| .style('fill', function(d) { return d.fill; }) | |
| .merge(path) | |
| .attr('d', function(d) { | |
| // extend path area to bottom of viewport so that it can have a fill | |
| return line(d.samples) + 'L' + width + ',' + height + 'L0,' + height + 'L' + d.samples[0][0] + ',' + d.samples[0][1]; | |
| }); | |
| } | |
| </script> |