Skip to content

Instantly share code, notes, and snippets.

@enjalot
Last active March 15, 2018 10:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save enjalot/f973a941606aa38fed321fbce0c8bd7f to your computer and use it in GitHub Desktop.
Save enjalot/f973a941606aa38fed321fbce0c8bd7f to your computer and use it in GitHub Desktop.
Particle viSFest
license: mit
<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 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("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 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment