Skip to content

Instantly share code, notes, and snippets.

@johnburnmurdoch
Last active October 15, 2017 19:25
Show Gist options
  • Save johnburnmurdoch/0405a9e1c890f3ba5a4eac7a0ab1d61f to your computer and use it in GitHub Desktop.
Save johnburnmurdoch/0405a9e1c890f3ba5a4eac7a0ab1d61f to your computer and use it in GitHub Desktop.
Procedurally generated ribbons, using Bézier curve iterations
license: Apache-2.0
height: 900
border: no
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,user-scalable=no">
<title>curveArt</title>
<script src="https://unpkg.com/d3/build/d3.min.js"></script>
<script src="https://unpkg.com/d3-selection-multi"></script>
<script src="https://unpkg.com/d3-scale-chromatic"></script>
<script src="https://josephg.com/perlin/3/perlin.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.3/seedrandom.min.js"></script>
<style>
</style>
</head>
<body>
<canvas id=canvas></canvas>
<script type=text/javascript charset=UTF-8>
let canvas = d3.select("canvas"),
ctx = canvas.node().getContext('2d'),
width = 1000,
height = 900,
DPR = window.devicePixelRatio || 1,
scaledWidth = width * DPR,
scaledHeight = height * DPR;
canvas
.attrs({
width: scaledWidth,
height: scaledHeight
})
.styles({
width: width + "px",
height: height + "px"
});
// Config array takes seven arguments:
// 1. Total duration of drawing stage
// 2. Frame-rate (the given number of frames per second, or the closest your browser can get)
// 3. A seed for our random number generator. Using the same seed again will yield exactly the same results
// 4. One of d3's inbuilt colour scales
// 5. The canvas background colour
// 6. Alpha (transparency/opacity) value for the lines. Can be very low as there's lots of over-painting
// 7. Number of lines to draw. Depending on your machine's performance (and your aesthetic tastes), best results tend to come with anything between 2 and 15
// Some particularly striking combinations of random seed and colour palette:
// config = [10000, 60, 25, "Magma", "#212121", 0.01 ,15],
// config = [5000, 120, 40, "Cool", "#212121", 0.01 ,15],
// config = [10000, 60, 40, "Plasma", "#212121", 0.01 ,15],
// config = [5000, 60, 41, "Rainbow", "#ffffff", 0.01, 10],
// config = [5000, 20, 40, "Inferno", "#ffffff", 0.01, 15],
// config = [5000, 60, 102, "Inferno", "#ffffff", 0.03, 3],
// config = [5000, 60, 2, "Viridis", "#ffffff", 0.02, 5],
// config = [3000, 120, 49, "Rainbow", "#212121", 0.08, 2],
// config = [5000, 30, 58, "Cool", "#212121", 0.05, 7],
let
config = [10000, 60, 25, "Magma", "#212121", 0.01 ,15],
duration = config[0],
fps = config[1],
palette = config[3],
alpha = config[5],
bg = config[4],
tickDuration = 1000/fps,
lines = [],
nLines = config[6];
Math.seedrandom(config[2]);
let randomSeed = Math.random();
noise.seed(Math.random());
ctx.fillStyle = bg;
ctx.fillRect(0,0,scaledWidth,scaledHeight);
let noiseScale = d3.scaleLinear().range([0,1]).domain([-1,1]);
let colourScale = d3.scaleSequential(d3["interpolate" + palette]).domain([0,1]);
function lineData(n){
for(let i = 0; i < n; i++){
let col = d3.rgb(colourScale(Math.random()));
let r = col.r;
let g = col.g;
let b = col.b;
let x1 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())),
x2 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())),
x3 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())),
x4 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())),
y1 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())),
y2 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())),
y3 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())),
y4 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random()));
let colorString = `rgba(${r}, ${g}, ${b}, ${alpha})`;
let lineData = [x1,x2,x3,x4, y1,y2,y3,y4, colorString];
lineData[9] = (x1 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())));
lineData[10] = (x2 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())));
lineData[11] = (x3 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())));
lineData[12] = (x4 = scaledWidth * noiseScale(noise.perlin2(Math.random(), Math.random())));
lineData[13] = (y1 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())));
lineData[14] = (y2 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())));
lineData[15] = (y3 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())));
lineData[16] = (y4 = scaledHeight * noiseScale(noise.perlin2(Math.random(), Math.random())));
lines.push(lineData);
}
let xMin = d3.min(lines.map(d => d.slice(0,4).concat(d.slice(9,13))).reduce((previous, current) => previous.concat(current))),
xMax = d3.max(lines.map(d => d.slice(0,4).concat(d.slice(9,13))).reduce((previous, current) => previous.concat(current))),
yMin = d3.min(lines.map(d => d.slice(4,8).concat(d.slice(13,17))).reduce((previous, current) => previous.concat(current))),
yMax = d3.max(lines.map(d => d.slice(4,8).concat(d.slice(13,17))).reduce((previous, current) => previous.concat(current)));
let xScale = d3.scaleLinear().range([0, scaledWidth]).domain([xMin, xMax]),
yScale = d3.scaleLinear().range([0, scaledHeight]).domain([yMin, yMax]);
lines.forEach(function(l,li){
l.forEach(function(d,i){
if([0,1,2,3,9,10,11,12].indexOf(i) >= 0){
lines[li][i] = xScale(d);
}else if(i == 8){
d = d;
}else if([0,1,2,3,9,10,11,12].indexOf(i) < 0){
lines[li][i] = yScale(d);
}
})
});
drawLines();
}
function drawLines(){
lines.forEach(function(l){
ctx.strokeStyle = l[8];
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(l[0], l[4]);
ctx.bezierCurveTo(l[1], l[5], l[2], l[6], l[3], l[7]);
ctx.stroke();
let timer = d3.interval(function(elapsed) {
let p = elapsed/duration;
update(p);
if (elapsed > duration) timer.stop();
}, tickDuration);
})
}
function update(t){
function interpolatePoint(data, index){
return data[index] + (data[index+8] - data[index]) * t
}
lines.forEach(d => {
ctx.strokeStyle = d[8];
ctx.beginPath();
ctx.moveTo(interpolatePoint(d,0), interpolatePoint(d,4));
ctx.bezierCurveTo(interpolatePoint(d,1), interpolatePoint(d,5), interpolatePoint(d,2), interpolatePoint(d,6), interpolatePoint(d,3), interpolatePoint(d,7));
ctx.stroke();
ctx.closePath();
});
}
lineData(nLines);
// canvas.on("dblclick", function(){
// drawLines();
// });
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment