Skip to content

Instantly share code, notes, and snippets.

@rflow
Last active September 21, 2016 22:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rflow/836d213591362ac8e94a13a8494c3371 to your computer and use it in GitHub Desktop.
Save rflow/836d213591362ac8e94a13a8494c3371 to your computer and use it in GitHub Desktop.
viSFest
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.0.0/pixi.min.js"></script>
<script>
// measure doc
const width = document.body.clientWidth;
const height = document.body.clientHeight;
// determine proportions
const radius = (width > 1024) ? 3 : 2;
const spacing = radius * 3;
const fontSize = Math.floor(0.2 * width) + "px";
console.log("window is", width, "x", height, "px");
console.log("dot radius is", radius + "px");
console.log("font size is", fontSize);
// tweak these!
const options = {
width: width,
height: height,
imgWidth: width * 0.439790, // daft magic to ensure eye outline has appropriate point count
imgHeight: width * 0.439790 * 0.595000, // as above -- the golden ratio :)
x: width / 2,
y: height / 3,
radius: radius,
spacing: spacing,
fontSize: fontSize,
fill: 0x2e88fd, //"rgba(46, 136, 253, 1)", // #2e88fd
collisionStrength: 0.1,
velocityDecay: 0.2
};
const titles = ["viSFest"];//, "d3.unconf", "oct 16-17"];
// create pixi renderer and stage objects
//var renderer = new PIXI.CanvasRenderer(800, 600);
const renderer = new PIXI.autoDetectRenderer(width, height, { backgroundColor : 0xffffff });
const stage = new PIXI.Container();
// snapshot a circle to a texture for optimal rendering perf
const gfx = new PIXI.Graphics();
const tileSize = options.spacing;
const texture = PIXI.RenderTexture.create(tileSize, tileSize);
gfx.beginFill(options.fill);
gfx.drawCircle(tileSize/2, tileSize/2, options.radius);
gfx.endFill();
renderer.render(gfx, texture);
// add fx filters
// stage.filters = createEffectFilters();
// rasterize title text to build point maps
const titleCoords = titles.map(function(title){
return rasterizeText(title, options);
});
// determine coords for svg outline and start animation
getOutlineForSVG("eye.svg", options, function (eyeCoords) {
// each state contains a list of x/y coords to target
const states = [eyeCoords].concat(titleCoords);
// determine how many nodes needed for longest list of coords
const nodeCount = d3.max(states, function(state){
return state.length;
})
// create required nodes
const nodes = d3.range(nodeCount).map(function (index) {
// create a new Sprite using the texture
const sprite = new PIXI.Sprite(texture);
// center the sprite's anchor point
sprite.anchor.x = 0.5 * tileSize;
sprite.anchor.y = 0.5 * tileSize;
return {
_id: index,
sprite: sprite,
rTarget: options.radius,
active: false
};
});
console.log("created", nodeCount, "nodes");
// create force simulation that will animate the node positions
const simulation = d3.forceSimulation(nodes);
const strength = options.collisionStrength;
const decay = options.velocityDecay;
// define forces that will act on the above
const xForce = d3.forceX(function(d) { return d.xTarget; }).strength(strength);
const yForce = d3.forceY(function(d) { return d.yTarget; }).strength(strength);
const collisionForce = d3.forceCollide().radius(function(d) { return d.rTarget; });
const updateNodeLocations = function () {
nodes.forEach(function(node) {
node.sprite.position.x = node.x;
node.sprite.position.y = node.y;
})
}
const updateNodeTargets = function (nodes, coords, options) {
const coordCount = coords.length;
for (var i = 0, node; i < nodeCount; i++) {
node = nodes[i];
if (i < coordCount) {
// bring in previously inactive notes at random locations
if (!node.active) {
node.x = Math.random() * options.width;
node.y = Math.random() * options.height;
}
// set targets of force simulation according to next coords
node.xTarget = coords[i][0];
node.yTarget = coords[i][1];
node.active = true;
stage.addChild(node.sprite);
} else {
node.active = false;
stage.removeChild(node.sprite);
}
};
}
const restartSimulation = function (simulation, nodes, xForce, yForce) {
simulation
.nodes(nodes.filter(function(n){
return n.active;
}))
.force("x", xForce)
.force("y", yForce)
.alpha(1)
.restart();
}
var state = 0;
var targetCoords = states[state];
var gotoNextState = function () {
state = (state + 1) % states.length;
targetCoords = states[state];
console.log("advancing to state", state);
updateNodeTargets(nodes, targetCoords, options);
restartSimulation(simulation, nodes, xForce, yForce);
}
var animateSprites = function () {
requestAnimationFrame(animateSprites);
renderer.render(stage);
}
updateNodeTargets(nodes, targetCoords, options);
simulation
.velocityDecay(decay)
.force("x", xForce)
.force("y", yForce)
.force("collide", collisionForce)
.on("tick", updateNodeLocations)
.on("end", gotoNextState);
document.body.appendChild(renderer.view);
animateSprites();
})
//////////// welcome to the library ///////////////////////////////////////////
// Convert text into grid of points that lay on top of the text
// Inspired by FizzyText. cf 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);
return findPoints(imageData, bBox, spacing);
}
// scan image data for filled pixels,
// return list of x,y coords spaced as required
function findPoints (imageData, rect, spacing) {
var points = [];
for (var x = rect[0][0]; x < rect[1][0]; x += spacing) {
for (var y = rect[0][1]; y < rect[1][1]; y += spacing) {
var pixel = getPixel(imageData, x, y);
if (pixel[3] != 0) points.push([x, y]);
}
}
return points;
}
// read pixel from imageData at required coords
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] ];
}
// Blur effect filter
function createEffectFilters () {
const colorMatrix = new PIXI.filters.ColorMatrixFilter();
const blurFilter = new PIXI.filters.BlurFilter();
colorMatrix.saturate(2);
blurFilter.blur = 0.5;
return [colorMatrix, blurFilter];
}
// legacy version using svg
function createSVGCircles (svg, nodes, options) {
// create group to hold circle elements
const layer = svg.append("g").attr("class", "circles");
// bind svg circle elements to all nodes
const circles = layer.selectAll("circle")
.data(nodes)
.enter()
.append("circle");
// apply glow filter
layer.style("filter", "url(#glow)");
// init class, position, radius of circle elements
circles
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", function(d) { return d.rTarget; })
.style("fill", options.fill);
return circles;
}
// get outline for contents of img element
// using same approach as text rasterizer
// c.f. http://jsfiddle.net/AbdiasSoftware/Y3K57/
const getOutlineForImage = function (img, width, height, spacing) {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const bounds = [[0,0],[width,height]];
canvas.width = width;
canvas.height = height;
context.drawImage(img, 0, 0, width, height);
return findPoints(
context.getImageData(0, 0, width, height),
bounds,
spacing
);
}
// load svg from path into img element, feed into routine above
// c.f. http://jsfiddle.net/AbdiasSoftware/Y3K57/
function getOutlineForSVG (path, options, onComplete) {
const img = new Image;
const spacing = options.spacing;
img.onload = function () {
const width = img.width;
const height = img.height;
// read outline coords and translate to viewport
const coords = getOutlineForImage(img, width, height, spacing);
const offset = {
x: options.x - width / 2,
y: options.y - height / 2
}
for (var i = 0; i < coords.length; i++) {
coords[i][0] += offset.x;
coords[i][1] += offset.y;
};
// return outline coords to callback
onComplete(coords);
};
img.crossOrigin = 'anonymous';
img.width = options.imgWidth;
img.height = options.imgHeight;
img.src = path;
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment