block by enjalot f973a941606aa38fed321fbce0c8bd7f

Particle viSFest

Full Screen

Move your mouse to slosh the text around.

The SVG filter comes from Nadieh Bremer’s great blog post about making gooey effects.

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

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



// 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("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>