Last active
August 29, 2015 14:27
-
-
Save tzengerink/556357d00c5db88d6db2 to your computer and use it in GitHub Desktop.
Light Tunnel - http://bl.ocks.org/mytho/556357d00c5db88d6db2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Light Tunnel</title> | |
</head> | |
<body> | |
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script> | |
<script type="text/javascript" src="light-tunnel.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*jshint latedef: nofunc */ | |
(function(window) { | |
'use strict'; | |
var centerCoords, debug, options, Math, d3, parseFloat, svg; | |
d3 = window.d3; | |
debug = false; | |
parseFloat = window.parseFloat; | |
Math = window.Math; | |
options = { | |
colors: [ | |
'#f0f', | |
'#93f', | |
'#36f', | |
'#0cf', | |
'#0cc', | |
'#0f0', | |
'#ff3', | |
'#f60', | |
'#c33' | |
], // Sequence of colors used for lines | |
height: 500, // Height of the SVG | |
width: 960 // Width of the SVG | |
}; | |
centerCoords = [options.width / 2, options.height / 2]; | |
// Append the root SVG element | |
svg = d3.select('body').append('svg') | |
.attr('width', options.width) | |
.attr('height', options.height); | |
// Draw a background rectangle | |
svg.append('rect') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('width', options.width) | |
.attr('height', options.height) | |
.attr('fill', '#000'); | |
animateBendedLines(svg.append('g')); | |
/** | |
* Animated bended lines. | |
* | |
* @param {Object} group element | |
*/ | |
function animateBendedLines(group) { | |
animatePaths(); | |
doGroupTransitions(); | |
/** | |
* Animate all paths. | |
*/ | |
function animatePaths() { | |
var duration, ease, endR, generator, i, middleCoords, | |
middlePathCoords, middlePathR, middleR, numberOfLines, path, | |
startCoords, startR; | |
numberOfLines = 24; | |
duration = 2000; | |
ease = 'linear'; | |
generator = d3.svg.line() | |
.x(function(d) { return d.x; }) | |
.y(function(d) { return d.y; }) | |
.interpolate('cardinal'); | |
startR = d3.min([options.height, options.width]) * 0.17; | |
middlePathR = d3.min([options.height, options.width]) * 0.13; | |
middleR = startR * 1.9; | |
endR = Math.sqrt(Math.pow(centerCoords[0], 2) + Math.pow(centerCoords[1], 2)) + startR; | |
if (debug) { | |
endR = d3.min([options.height, options.width]) / 2; | |
} | |
makeBlurFilter(); | |
resetCoords(); | |
if (debug) { | |
makeDebugElements(); | |
} | |
for (i = 0; i < numberOfLines; i++) { | |
makePath(i); | |
} | |
doTransition(); | |
function dashArray(len) { | |
return len * 0.25 + ' ' + len * 0.025; | |
} | |
function doTransition() { | |
resetCoords(); | |
group.selectAll('path') | |
.transition() | |
.duration(duration) | |
.ease(ease) | |
.attr('d', function(d, i) { | |
return makeD(i); | |
}) | |
.attr('stroke-dasharray', function() { | |
return dashArray(d3.select(this).node().getTotalLength()); | |
}) | |
.attrTween('stroke-dashoffset', function() { | |
var len = d3.select(this).node().getTotalLength(); | |
return function(t) { | |
return 1.2 * t * len; | |
}; | |
}) | |
.call(endAll, doTransition); | |
doTransitionDebugElements(); | |
} | |
function doTransitionDebugElements() { | |
group.selectAll('circle.start') | |
.transition() | |
.ease(ease) | |
.duration(duration) | |
.attr('cx', startCoords[0]) | |
.attr('cy', startCoords[1]); | |
group.selectAll('circle.middle') | |
.transition() | |
.ease(ease) | |
.duration(duration) | |
.attr('cx', middleCoords[0]) | |
.attr('cy', middleCoords[1]); | |
group.selectAll('circle.end') | |
.transition() | |
.ease(ease) | |
.duration(duration) | |
.attr('cx', centerCoords[0]) | |
.attr('cy', centerCoords[1]); | |
group.selectAll('circle.middle-path') | |
.transition() | |
.ease(ease) | |
.duration(duration) | |
.attr('cx', middlePathCoords[0]) | |
.attr('cy', middlePathCoords[1]); | |
} | |
function makeBlurFilter() { | |
group.append('filter') | |
.attr('id', 'blur') | |
.append('feGaussianBlur') | |
.attr('in', 'SourceGraphic') | |
.attr('stdDeviation', 2); | |
} | |
function makeD(i) { | |
var rad = Math.PI * 2 * i / numberOfLines; | |
return generator([{ | |
x: centerCoords[0] + (endR * Math.cos(rad)), | |
y: centerCoords[1] + (endR * Math.sin(rad)) | |
}, { | |
x: middleCoords[0] + (middleR * Math.cos(rad)), | |
y: middleCoords[1] + (middleR * Math.sin(rad)) | |
}, { | |
x: startCoords[0] + (startR * Math.cos(rad)), | |
y: startCoords[1] + (startR * Math.sin(rad)) | |
}]); | |
} | |
function makeDebugElements() { | |
var debugGroup = group.append('g') | |
.attr('fill-opacity', 0) | |
.attr('opacity', 0.6) | |
.attr('stroke-width', 2); | |
debugGroup.append('circle') | |
.attr('class', 'start') | |
.attr('stroke', 'red') | |
.attr('cx', startCoords[0]) | |
.attr('cy', startCoords[1]) | |
.attr('r', startR); | |
debugGroup.append('circle') | |
.attr('class', 'middle') | |
.attr('stroke', 'red') | |
.attr('cx', middleCoords[0]) | |
.attr('cy', middleCoords[1]) | |
.attr('r', middleR); | |
debugGroup.append('circle') | |
.attr('class', 'end') | |
.attr('stroke', 'purple') | |
.attr('cx', centerCoords[0]) | |
.attr('cy', centerCoords[1]) | |
.attr('r', endR); | |
debugGroup.append('circle') | |
.attr('class', 'middle-path') | |
.attr('stroke', 'blue') | |
.attr('cx', middlePathCoords[0]) | |
.attr('cy', middlePathCoords[1]) | |
.attr('r', middlePathR); | |
} | |
function makePath(i) { | |
path = group.append('path') | |
.attr('opacity', 0.8) | |
.attr('filter', 'url(#blur)') | |
.attr('fill-opacity', 0) | |
.attr('stroke-width', 8) | |
.attr('d', makeD(i)) | |
.attr('stroke-dasharray', function() { | |
return dashArray(d3.select(this).node().getTotalLength()); | |
}) | |
.attr('stroke-dashoffset', 0); | |
} | |
function resetCoords() { | |
startCoords = randomCoordsOnCircle(centerCoords, endR); | |
middlePathCoords = randomCoordsOnCircle(centerCoords, middlePathR); | |
middleCoords = randomCoordsOnCircle(middlePathCoords, middlePathR); | |
} | |
} | |
/** | |
* Transition the colors of the group. | |
*/ | |
function doGroupTransitions() { | |
group.transition() | |
.ease('linear') | |
.duration(10000) | |
.attrTween('stroke', tweenColor) | |
.each('end', doGroupTransitions); | |
/** | |
* Custom attribute tween used for the color transitions. | |
*/ | |
function tweenColor() { | |
return function(t0) { | |
var end, start, t1; | |
if (t0 >= 1) { | |
return options.colors[options.colors.length - 1]; | |
} | |
t1 = t0 * (options.colors.length - 1); | |
start = options.colors[Math.floor(t1)]; | |
end = options.colors[Math.floor(t1) + 1]; | |
return d3.interpolateHsl(start, end)(t1 % 1); | |
}; | |
} | |
} | |
} | |
/** | |
* Execute a callback when a transition has ended for all elements. | |
* | |
* @param {Object} transition | |
* @param {Function} callback | |
*/ | |
function endAll(transition, callback) { | |
var n = 0; | |
transition.each(function() { ++n; }) | |
.each('end', function() { | |
if ( ! --n) { | |
callback.apply(this, arguments); | |
} | |
}); | |
} | |
/** | |
* Create a random number between 0 (inclusive) and max (exclusive). | |
* | |
* @param {Number} max | |
* @param {Number} min (default to 0) | |
* | |
* @return {Number} random | |
*/ | |
function random(max, min) { | |
min = min || 0; | |
return (Math.random() * (max - min) + min); | |
} | |
/** | |
* Get random coordinates on a virtual circle. | |
* | |
* @param {Array} startCoords | |
* @param {Number} r | |
* | |
* @return {Array} coords [x, y] | |
*/ | |
function randomCoordsOnCircle(startCoords, r) { | |
var rad = random(2 * Math.PI); | |
return [startCoords[0] + (r * Math.cos(rad)), | |
startCoords[1] + (r * Math.sin(rad))]; | |
} | |
})(window); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment