<!--
Library     : d3blocks
Author      : E.Taskesen
Mail        : erdogant@gmail.com
Github      : https://github.com/d3blocks/d3blocks
License     : GPL3
Forked      : Ian Johnson; http://bl.ocks.org/enjalot/f973a941606aa38fed321fbce0c8bd7f
-->

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ TITLE }}</title>

    <style>
      body { background: {{ COLOR_BACKGROUND }} }
      circle.mouse { fill: none; }
    </style>

</head>
<body>

<script>
	{% include "d3.v4.min.js" %}
	{% include "d3-scale-chromatic.v1.min.js" %}
</script>


<script>
    var text = "{{ json_data }}";

    // Default mousemove
    var mouse_or_touch="mousemove"

    // CHANGE TOUCH OR MOUSE MOVE BASED ON OS TYPE
   	function getPlatform() {
   		const allPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE', 'Android', 'iPhone', 'iPad', 'iPod'];
   		return allPlatforms.find(item => item === navigator.platform);
   	}
    // Change into touchmove
    if (navigator.platform === "Android" || navigator.platform === "iPad" || navigator.platform === "iPhone") {
        mouse_or_touch="touchmove"
    }
    
	/// console.log(getPlatform(), mouse_or_touch);

    var width = {{ WIDTH }},
    		height = {{ HEIGHT }},
    		radius = {{ RADIUS }},
    		collisionStrength = {{ COLLISION }};

    var options = {
    	width: width,
    	height: height,
    	spacing: {{ SPACING }},
    	fontSize: {{ FONTSIZE }}
    };
    
    // https://observablehq.com/@d3/color-schemes
    // d3.interpolateTurbo
    // d3.interpolateViridis
    var color = d3.scaleSequential(d3.interpolate{{ CMAP }})
      .domain([0, width])
    
    // Rasterized grid of points that represent the text
    var pixels = rasterizeText(text, options)	
    	.map(function(d) {
        var x = Math.random() * width
    		return {
    			x: x,
    			y: Math.random() * height,
    			xTarget: d[0],
    			yTarget: d[1],
    			rTarget: radius + Math.abs(width/2 - x)/width*2 * 2
    		};
    	});
    
    // Wrecking ball node (associated with mouse movement)
    var mouseNode = {
    	x: 0,
    	y: height / 2,
    	xTarget: width + 100,
    	yTarget: height / 2,
    	rTarget: 50
    };
    
    // Combine mouse node with pixel nodes
    var nodes = [mouseNode].concat(pixels);
    
    var svg = d3.select("body").append("svg")
    	.attr("width", width)
    	.attr("height", height);
    
    // Gooey effect filter
    // See http://www.visualcinnamon.com/2016/06/fun-data-visualizations-svg-gooey-effect.html
    var filter = svg.append("defs")
    	.append("filter")
    		.attr("id","gooey");
    
    filter.append("feGaussianBlur")
    	.attr("in", "SourceGraphic")
    	.attr("stdDeviation", "8")
    	.attr("color-interpolation-filters", "sRGB") 
    	.attr("result", "blur");
    
    filter.append("feColorMatrix")
    	.attr("in" ,"blur")
    	.attr("mode", "matrix")
    	.attr("values", "1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 18 -7")
    	.attr("result", "gooey");
    
    // Circles that form the text
    var circle = svg.append("g")
    		.attr("class", "circles")
    		//.style("filter", "url(#gooey)")
    	.selectAll("circle").data(pixels)
    		.enter()
    		//.append("circle")
        .append("rect")
        .classed("mouse", function(d, i) { return i == 0; })
        .attr("x", function(d) { return d.x; })
        .attr("y", function(d) { return d.y; })
        .attr("width", function(d) { return d.rTarget * 1.5; })
        .attr("height", function(d) { return d.rTarget * 1.5; })
    
    
    // Nodes want to form the text but won't overlap
    var simulation = d3.forceSimulation(nodes)
    	.velocityDecay(0.2)
    	.force("x", d3.forceX(function(d) { return d.xTarget; }).strength(collisionStrength))
    	.force("y", d3.forceY(function(d) { return d.yTarget; }).strength(collisionStrength))
    	.force("collide", d3.forceCollide().radius(function(d) { return d.rTarget; }))
    	.on("tick", ticked);
    
    function ticked() {
    	circle
        .attr("x", function(d) { return d.x; })
        .attr("y", function(d) { return d.y; })
        .style("fill", function(d){
          return color(d.x)
        })
    }
    
   
    // Slosh the nodes around with the mouse
    d3.select("body")
    	.on(mouse_or_touch, mousemove);
    
    function mousemove() {
    	var p = d3.mouse(this);
    	mouseNode.xTarget = p[0];
    	mouseNode.yTarget = p[1];
    
    	simulation
    		.force("x", d3.forceX(function(d) { return d.xTarget; }).strength(collisionStrength))
    		.force("y", d3.forceY(function(d) { return d.yTarget; }).strength(collisionStrength))
    		.alpha(1)
    		.restart();
    }
    
    // Convert text into grid of points that lay on top of the text
    // Inspired by FizzyText. See http://bl.ocks.org/tophtucker/978513bc74d0b32d3795
    function rasterizeText(text, options) {
    	var o = options || {};
    
    	var fontSize = o.fontSize || "200px",
    			fontWeight = o.fontWeight || "600",
    			fontFamily = o.fontFamily || "sans-serif",
    			textAlign = o.center || "center",
    			textBaseline = o.textBaseline || "middle",
    			spacing = o.spacing || 10,
    			width = o.width || 960,
    			height = o.height || 500,
    			x = o.x || (width / 2),
    			y = o.y || (height / 2);
    	
    	var canvas = document.createElement("canvas");
    	canvas.width = width;
    	canvas.height = height;
    
    	var context = canvas.getContext("2d");
    
    	context.font = [fontWeight, fontSize, fontFamily].join(" ");
    	context.textAlign = textAlign;
    	context.textBaseline = textBaseline;
    
    	var dx = context.measureText(text).width,
    			dy = +fontSize.replace("px", ""),
    			bBox = [[x - dx / 2, y - dy / 2], [x + dx / 2, y + dy / 2]];
    
    	context.fillText(text, x, y);
    
    	var imageData = context.getImageData(0, 0, width, height);
    
    	var pixels = [];
    	for (var x = bBox[0][0]; x < bBox[1][0]; x += spacing) {
    		for (var y = bBox[0][1]; y < bBox[1][1]; y += spacing) {
    			var pixel = getPixel(imageData, x, y);
    			if (pixel[3] != 0) pixels.push([x, y]);
    		}
    	}
    	
    	return pixels;
    }
    
    function getPixel(imageData, x, y) {
    	var i = 4 * (parseInt(x) + parseInt(y) * imageData.width);
    	var d = imageData.data;
    	return [ d[i], d[i+1], d[i+2], d[i+3] ];
    }

</script>

</body>
</html>