block by nbremer 1acc6c95e3bb374dc78329e94f85a9b0

Minimal Regl version of "A Breathing Earth"

Full Screen

Built with blockbuilder.org

index.html

<!DOCTYPE html>
	<head>
	    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	    <meta http-equiv="X-UA-Compatible" content="IE=edge">
	    <meta name="viewport" content="width=device-width, initial-scale=1">
	    <!-- <meta name="viewport" content="user-scalable = yes"> -->

	    <title>A Breathing Earth - Minimal Regl version</title>
		<meta name="author" content="Nadieh Bremer">

		<!-- JavaScript files -->
		<script src="https://d3js.org/d3.v4.min.js"></script>
		<script src="regl.min.js"></script>

		<!-- Styling -->
		<style>
			body {
				text-align: center;
				font-family: 'Source Code Pro', monospace;
			}

			#container {
				width: 1000px;
				margin-left: auto;
				margin-right: auto;
				text-align: center;
				padding-left: 20px;
				padding-right: 20px;
			}

			#chart {
				text-align: center;
				margin-left: auto;
				margin-right: auto;
				box-shadow: 0px 0px 5px #c1c1c1;
			}

			#week {
				text-align: left;
				font-size: 18px;
				margin-top: 15px;
				margin-bottom: 10px;
			}
		</style>

		<!-- Google fonts -->
		<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:200,300,400" rel="stylesheet">

	</head>
	<body>

		<div id="container">

			<div id="week"></div>
			<div id="chart">
				<canvas id="canvas"></canvas>
			</div>

		</div>

		<script src="main.js"></script>

	</body>
</html>

main.js


createReglMap();

//Function to draw the regl based map
function createReglMap() {

	///////////////////////////////////////////////////////////////////////////
	//////////////////////////// Set up the canvas ////////////////////////////
	///////////////////////////////////////////////////////////////////////////

	var width = 2000; 
	//Mercator map ratio
	var mapRatio = 0.5078871;
	var height = Math.round(mapRatio * width);

	var canvas = document.getElementById("canvas");
	canvas.width = width;
	canvas.height = height;
	//Get the visible size back to 1000 px
	canvas.style.width = (width*0.5) + 'px';
	canvas.style.height = (height*0.5) + 'px';
	//canvas.getContext("2d").scale(0.5, 0.5);

	var regl = createREGL({
		extensions: ['OES_standard_derivatives'],
		canvas: canvas,
		attributes: {
			alpha: false,
			depth: false,
			antialias: true
		}
	});
  regl.clear({color: [1, 1, 1, 1]});

	///////////////////////////////////////////////////////////////////////////
	///////////////////////// Create global variables /////////////////////////
	///////////////////////////////////////////////////////////////////////////

	const nWeeks = 52;						//Number of weeks in the year

	var mapPoints;	//Will save the coordinate mapping

	//The minimum and maximum values of the layer variable
	const maxL = 0.8,
		  minL = 0; //-0.06;

	///////////////////////////////////////////////////////////////////////////
	/////////////////////////////// Create scales /////////////////////////////
	///////////////////////////////////////////////////////////////////////////

	var xScale = d3.scaleLinear()
		.domain([1, 500])
		.range([-1, 1]);

	var yScale = d3.scaleLinear()
		.domain([1, 250])
		.range([-1,1]);

	var radiusScale = d3.scaleSqrt()
		.domain([minL, maxL])
		.range([0, 5])
		.clamp(true);

	var opacityScale = d3.scaleLinear()
		.domain([minL, maxL])
		.range([1, 0.5]);

	var greenColor = d3.scaleLinear()
		.domain([-0.08, 0.1, maxL])
		//.range(["#FFBE1F", "#d9e537", "#054501"])
		.range(["#FAECAB", "#f2ec82", "#0c750c"])
		//.clamp(true);

	//Wrap d3 color scales so they produce vec3s with values 0-1
	function wrapColorScale(scale) {
		return function(t) {
			const rgb = d3.rgb(scale(t));
			return [rgb.r / 255, rgb.g / 255, rgb.b / 255];
		};
	}//wrapColorScale
	var greenColorRGB = wrapColorScale(greenColor);

	///////////////////////////////////////////////////////////////////////////
	/////////////////////////// Map drawing settings //////////////////////////
	///////////////////////////////////////////////////////////////////////////

	function createMapMaker() {
		var drawMap = regl({

			vert: `
				precision highp float;

				//The progess along the transition
				uniform float progress;

				//Needed to calculate fopacity
				attribute float currOpacity;
				attribute float nextOpacity;
				//Needed to set gl_Pointsize
				attribute float currSize;
				attribute float nextSize;
				//Needed to set gl_Position
				attribute vec2 position;
				//Needed to set fcolor
				attribute vec3 currColor;
				attribute vec3 nextColor;

				//Will get values used in the fragment shader
				varying float fopacity;
				//varying float fsize;
				varying vec3 fcolor;

				void main () {

					fcolor = mix(currColor, nextColor, progress);
					fopacity = mix(currOpacity, nextOpacity, progress);
					//fsize = size/7.5;

					gl_PointSize = mix(currSize, nextSize, progress);
					gl_Position = vec4(position, 0, 1);
				}
			`,

			frag: `

				precision highp float;

				#ifdef GL_OES_standard_derivatives
					#extension GL_OES_standard_derivatives : enable
				#endif

				varying float fopacity;
				//varying float fsize;
				varying vec3 fcolor;

				void main (){

					//Creating a circle out of a square:

					//Determine normalized distance from center of point
					float point_dist = length(gl_PointCoord * 2. - 1.);

					//Based in part on: 
					//http://bl.ocks.org/monfera/85aa9627de1ae521d3ac5b26c9cd1c49
					//http://www.numb3r23.net/2015/08/17/using-fwidth-for-distance-based-anti-aliasing
					//https://www.desultoryquest.com/blog/drawing-anti-aliased-circular-points-using-opengl-slash-webgl
					#ifdef GL_OES_standard_derivatives
						//Anti-aliasing-factor
						float aaf = fwidth(point_dist)/2.0;
						//if(point_dist + aaf > 1.0) discard; //strangley this doesn't work as inteded at all
						float alpha = fopacity*(1.0 - smoothstep(1.0 - aaf, 1.0 + aaf, point_dist));
					#else
						if(point_dist > 1.0) discard;
						float alpha = fopacity;
					#endif

					//From:https://gist.github.com/rflow/39692bd181fb1eb0b077a4caf886b077
					//But seems to discard too much for smaller circles
					//Calc scale at which to start fading out the circle
					//float min_dist = fsize * 0.7;
					//Calc scale at which we find the edge of the circle
					//float max_dist = fsize;
					//From https://thebookofshaders.com/glossary/?search=smoothstep
					//float alpha = fopacity * (1.0 - smoothstep(min_dist, max_dist, point_dist));

					//The most basic way to get a (pixelated) circle
					//if (point_dist > 1.0) discard;
					
					//gl_FragColor = vec4(alpha*fcolor, alpha*fopacity);// - correct for no multiply but with pre-opacity multiplication (& the //2 func below)
					//gl_FragColor = vec4(fcolor, alpha*fopacity); // correct for nu multiply and without pre-opacity multiplication (& the //1 func below)

					// premultiplying the alpha like this means we can use *just* the multiplicative
					// part of the blending function (src * dst). But alpha = 0 corresponds to
					// multiplying by one, so we have to do this little 'one minus' trick:
					gl_FragColor = vec4(1.0 - alpha * (1.0 - fcolor), 1);

				}//void main
			`,

			depth: {enable: false, mask: false},

			blend: {
				enable: true,
				//func: { srcRGB: 'src alpha', dstRGB: 'one minus src alpha', srcAlpha: 1, dstAlpha: 'one minus src alpha' }, //1
				//func: {src: 1, dst: 'one minus src alpha'}, //2
				func: { srcRGB: 'dst color', dstRGB: 0, srcAlpha: 1, dstAlpha: 1 },
				//func: {src: 'one minus dst alpha', dst: 'src color'}, //never got this working correctly with opacities, but it does look like a multiply blend
				//func: {src: 'dst color', dst: 'one minus src alpha'}, //seems to work for Pixi, but I just get a white screen...
				equation: { rgb: 'add', alpha: 'add' },
			},

			attributes: {
				position: mapPoints,
				currColor: regl.prop('currColor'),
				nextColor: regl.prop('nextColor'),
				currOpacity: regl.prop('currOpacity'),
				nextOpacity: regl.prop('nextOpacity'),
				currSize: regl.prop('currSize'),
				nextSize: regl.prop('nextSize'),
			},

			uniforms: {
				progress: regl.prop('progress'),
			},//uniforms

			count: mapPoints.length,
			primitive: 'points',

		});//drawTriangle

		return drawMap;
	}//function createMapMaker

	///////////////////////////////////////////////////////////////////////////
	//////////////////////////// Read in the data /////////////////////////////
	///////////////////////////////////////////////////////////////////////////

	d3.queue() 
		.defer(d3.csv, "worldMap_coordinates.csv")
		.defer(d3.csv, "mapData-week-22.csv")
		.defer(d3.csv, "mapData-week-23.csv")
		.await(drawFirstMap);

	function drawFirstMap(error, coordRaw, data, data2) {

		///////////////////////////////////////////////////////////////////////////
		///////////////////////////// Final data prep /////////////////////////////
		///////////////////////////////////////////////////////////////////////////
		
		if (error) throw error;

		//The locations of every map are the same, so save the in a variable
		mapPoints = coordRaw.map(function(d) { return [xScale(+d.x), yScale(+d.y)]; });

		//Prepare the map data
		data.forEach(function(d) {
			d.layer = +d.layer;
			d.opacity = opacityScale(d.layer);
			d.color = greenColorRGB(d.layer);
			//Premultiply the colors by the alpha
			// d.color[0] = d.color[0] * d.opacity;
			// d.color[1] = d.color[1] * d.opacity;
			// d.color[2] = d.color[2] * d.opacity;
			d.size = radiusScale(d.layer); 
		});

		//Create a new array that shuffles the data a bit
		var currMap = {
			colors: data.map(function(d) { return d.color; }),
			opacities: data.map(function(d) { return d.opacity; }),
			sizes: data.map(function(d) { return 2*d.size; }),	
		};

		//Prepare the map data
		data2.forEach(function(d) {
			d.layer = +d.layer;
			d.opacity = opacityScale(d.layer);
			d.color = greenColorRGB(d.layer);
			//Premultiply the colors by the alpha
			// d.color[0] = d.color[0] * d.opacity;
			// d.color[1] = d.color[1] * d.opacity;
			// d.color[2] = d.color[2] * d.opacity;
			d.size = radiusScale(d.layer); 
		});

		//Create a new array that shuffles the data a bit
		var nextMap = {
			colors: data2.map(function(d) { return d.color; }),
			opacities: data2.map(function(d) { return d.opacity; }),
			sizes: data2.map(function(d) { return 2*d.size; }),	
		};
		///////////////////////////////////////////////////////////////////////////
		//////////////////////////// Draw the first map ///////////////////////////
		///////////////////////////////////////////////////////////////////////////

		//Create the regl function
		var drawMap = createMapMaker();

		//Adjust the title
		d3.select("#week").text("Week " + 22 + ", " + "May"+ ", 2016");

		regl.clear({color: [1, 1, 1, 1]});

		//Draw the current map
		drawMap({ 
			currColor: currMap.colors,
			nextColor: nextMap.colors,
			currOpacity: currMap.opacities,
			nextOpacity: nextMap.opacities,
			currSize: currMap.sizes,
			nextSize: nextMap.sizes,
			progress: 0.0 
		});

	}//function drawFirstMap

}//function createReglMap