block by enjalot 6ef5797e44540179f622a2bab0c945fc

Particle viSFest animated

Full Screen

Remix of armollica’s block, animating the colliding particle instead of relying on mousemove.

The idea of drawing text on a <canvas> element and using the bitmap to identify points within the text came from Toph Tucker’s block that reverse-engineers FizzyText.

forked from armollica‘s block: Slimy Text

forked from enjalot‘s block: Particle viSFest

index.html

<html>
<head>
<style>
  body { background: black }
circle.mouse { fill: none; }
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>  
<script>

var text = "viSFest";

var width = 960,
		height = 500,
		radius = 3,
		collisionStrength = 0.1;

var options = {
	width: width,
	height: height,
	spacing: 10,
	fontSize: "250px"
};
  
var color = d3.scaleSequential(d3.interpolateRainbow)
  .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 x0 = 0;
var y0 = height/2;
var fps = 60;
var dx = 150/fps; //speed
var baseRadius = 50;

var mouseNode = {
	x: x0,
	y: y0,
	xTarget: width + 100,
	yTarget: height / 2,
	rTarget: baseRadius
};
  

// Combine mouse node with pixel nodes
var nodes = [mouseNode].concat(pixels);

  
// 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);

d3.timer(function() {
  x0 += dx;
  if(x0 > width/2) x0 = 0;
  var x = width * Math.sin(x0/width * Math.PI * 2)
  mouseNode.xTarget = x
  mouseNode.rTarget = baseRadius /2 * Math.abs(Math.cos(x0/width * Math.PI * 6)) + 10 + baseRadius / 4 * Math.abs(Math.sin(x0/width * Math.PI * 7))
  console.log(mouseNode.rTarget)
	simulation
		.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; }))
		.alpha(1)
})

  
var svg = d3.select("body").append("svg")
	.attr("width", width)
	.attr("height", height);

// Gooey effect filter
// See //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("cx", function(d) { return d.x; })
// 			.attr("cy", function(d) { return d.y; })
			.attr("x", function(d) { return d.x; })
			.attr("y", function(d) { return d.y; })
// 			.attr("r", function(d) { return d.rTarget; })
			.attr("width", function(d) { return d.rTarget * 1.5; })
			.attr("height", function(d) { return d.rTarget * 1.5; })





function ticked() {
	circle
		//.attr("cx", function(d) { return d.x; })
		//.attr("cy", function(d) { return d.y; })
    .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("mousemove", 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 //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>