Skip to content

Instantly share code, notes, and snippets.

@lsbardel
Last active October 23, 2019 20:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lsbardel/d686414f38742cb60c3bf3f21b79b9df to your computer and use it in GitHub Desktop.
Save lsbardel/d686414f38742cb60c3bf3f21b79b9df to your computer and use it in GitHub Desktop.
Wavy Sunset
license: bsd-3-clause

Water waves are an example of waves that involve a combination of both longitudinal and transverse motions. As a wave travels through the waver, the particles travel in clockwise circles. A d3 adaptation with ideas from acustic and vibration animations

Single code base visualisations on svg and canvas thanks to d3-canvas-transition module.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Wavy Sunset</title>
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://unpkg.com/d3-canvas-transition@0.3.7/build/d3-canvas-transition.js"></script>
<style>
#panel {
background-color: rgba(245,245,245,0.9);
padding: 5px;
position: absolute;
display: block;
}
</style>
</head>
<body>
<div id="panel">
<div id="paper">
<label>
<input id='svg' name="type" type="radio" checked>
<span>svg</span>
</label>
<label>
<input id='canvas' name="type" type="radio">
<span>canvas</span>
</label>
</div>
</div>
<div id="example" style="max-width: 960px"></div>
<script src="./script.js">
</script>
</body>
(function () {
d3.select('#svg').on('click', function () {
draw('svg');
});
d3.select('#canvas').on('click', function () {
draw('canvas');
});
if (d3.resolution() > 1) {
d3.select('#paper').append('label').html(
"<input id='canvas-low' name='type' type='radio'><span>canvas low resolution</span>"
);
d3.select('#canvas-low').on('click', function () {
draw('canvas', 1);
});
}
var color = d3.scaleSequential(d3.interpolateBlues),
waves, particles, x, y, r, data, boat;
draw('svg');
d3.timer(animate);
function draw(type, r) {
var example = d3.select("#example"),
width = d3.getSize(example.style('width')),
height = Math.min(500, width);
x = d3.scaleLinear().range([0, width]);
y = d3.scaleLinear().range([height, 0]);
if (!data)
data = [0.7, 0.6, 0.4, 0.2].map(function (d, i) {
var w = wave()
.radius(0.02*(i+1)*height)
.waveLength(0.2*(i+1))
.y(d);
w.area.x(function (dd) {
return x(dd.x) + dd.dx;
}).y1(function (dd) {
return y(dd.y) - dd.dy;
}).y0(function () {
return y(0);
});
return w;
});
example.select('.paper').remove();
var paper = example
.append(type)
.classed('paper', true)
.attr('width', width).attr('height', height).canvasResolution(r).canvas(true)
.style('stroke-width', 0.5);
sun(paper)
.append('rect')
.attr('width', width)
.attr('height', height)
.style('fill', 'url(#sun)');
waves = paper
.append('g')
.classed('waves', true)
.selectAll('path')
.data(data)
.enter()
.append('path')
.style('stroke', 'none')
.each(function (d) {
d3.select(this).attr('d', d.context(null)).style('fill', color(d.y()));
});
var circles = paper.selectAll('g.circles')
.data(data)
.enter()
.append('g')
.classed('circles', true)
.selectAll('circles')
.data(function (d) {return d.points();})
.enter()
.append('circle')
.attr('r', function (d) {return d.radius;})
.style('fill', 'none')
.style('stroke', '#666')
.attr('cx', function (d) {return x(d.x);})
.attr('cy', function (d) {return y(d.y);});
particles = paper.selectAll('g.particles')
.data(data)
.enter()
.append('g')
.classed('particles', true)
.selectAll('circles')
.data(function (d) {return d.points();})
.enter()
.append('circle')
.attr('r', 3)
.style('fill', '#666')
.style('stroke-width', 0)
.attr('cx', function (d) {return x(d.x) + d.dx;})
.attr('cy', function (d) {return y(d.y) - d.dy;});
boat = paper.append('text')
.text("⛵")
.style('text-anchor', 'middle')
.style('alignment-baseline', 'middle')
.style("font-size", "60px");
moveBoat();
}
function animate () {
waves.each(function (d) {
d3.select(this).attr('d', d.tick());
});
particles.data(function (d) {return d.points();})
.attr('cx', function (d) {return x(d.x) + d.dx;})
.attr('cy', function (d) {return y(d.y) - d.dy;});
moveBoat();
}
function moveBoat() {
var d = data[1].point(20);
boat.attr("transform", "translate(" + (x(d.x) + d.dx) + ", " + (y(d.y) - d.dy) + ")");
}
function sun (paper) {
paper
.append('defs')
.append('radialGradient')
.attr('id', 'sun')
.attr('cx', '70%')
.attr('cy', '30%')
.attr('fx', '60%')
.attr('fy', '30%')
.selectAll('stop')
.data([
{color: "#e31a1c", offset: '0%'},
{color: '#fd8d3c', offset: '60%'}
])
.enter()
.append('stop')
.attr('offset', function (d) {
return d.offset;
})
.attr('stop-color', function (d) {
return d.color;
});
return paper;
}
function wave() {
var radius = 0.1, // intensity of wave
waveLength = 1, // wave length
y = 0,
area = d3.area().curve(d3.curveNatural),
extent = [0, 1],
pi = Math.PI,
cos = Math.cos,
sin = Math.sin,
N = 8,
speed = 0.01,
time = 0;
function wave (d) {
return area(wave.points(d));
}
wave.area = area;
wave.tick = function () {
time += 1;
return wave;
};
wave.context = function (_) {
if (!arguments.length) return area.context();
area.context(_);
return wave;
};
wave.extent = function (_) {
if (!arguments.length) return extent;
extent = _;
return wave;
};
wave.N = function (_) {
if (!arguments.length) return N;
N = +_;
return wave;
};
wave.waveLength = function (_) {
if (!arguments.length) return waveLength;
waveLength = _;
return wave;
};
wave.y = function (_) {
if (!arguments.length) return y;
y = _;
return wave;
};
wave.radius = function (_) {
if (!arguments.length) return radius;
radius = _;
return wave;
};
wave.speed = function (_) {
if (!arguments.length) return speed;
speed = _;
return wave;
};
wave.points = function () {
var w = extent[1] - extent[0] + 2*waveLength,
dx = waveLength/N,
Nx = Math.round(w/dx) + 1;
return d3.range(Nx).map(point);
};
wave.point = point;
function point (i) {
var da = 2*pi/N,
dx = waveLength/N,
x0 = extent[0] - waveLength,
a = i*da - time*speed*pi;
return {
x: x0 + i * dx,
y: y,
angle: a,
radius: radius,
dx: radius * cos(a),
dy: radius * sin(a)
};
}
return wave;
}
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment