block by johnburnmurdoch 0405a9e1c890f3ba5a4eac7a0ab1d61f

Procedurally generated ribbons, using Bézier curve iterations

Full Screen

An iteration on a @micahstubbs iteration on a procedural art project from @randylubin

index.html

<!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>