// cf http://bl.ocks.org/mbostock/3916621 var spinePaths = [ 'M135.3,80.2c0,0-6.7-34-40-34s-41.3,16-42,34c-0.7,18,21,25,45,29s41.3,12.3,41.3,36.7s-24.3,33.1-43.2,33.1s-48.8-10.4-48.8-41.7', 'M164,46.2v129.7h78.3', 'M356.3,178.9L304.5,48.2l-51.8,129', 'M270.9,130.7h66.3', 'M467.4,48.2v127.7l-92-127.7v130.7', 'M498.4,48.2v127.7h48.2c0,0,48.2,2.6,48.2-63.3s-48.2-64.4-48.2-64.4H498.4z', 'M703.3,48.2h-84v127.7h84', 'M619.3,111h78.2', 'M727.5,115.3l51.8-0.2c0,0,33.1,0.1,33.1-33.1s-32.2-33.8-32.2-33.8h-52.7v130.7', 'M780,115.1l36.5,63.8' ]; var mouse = [0,0]; var svg = d3.select('body').append('svg') .on('mousemove', function() { mouse = d3.mouse(this); }); var spines = svg.selectAll('path.spine') .data(spinePaths) .enter() .append('path.spine') .attr('d', ƒ()) .each(function(d) { var pathSamples = samplePath(this,7,10); // for spiky // var pathSamples = samplePath(this,25,20); // for blobby var datum = { 'spine': this, 'samples': pathSamples } svg.append('path.spiky') .datum(datum); }); var spikys = svg.selectAll('path.spiky'); var spineShrink = d3.scale.linear() .domain([ 0, 1e3, 2e3, 4e3 ]) .range([ 10, 10, 2, 0 ]) .clamp(true); var spikeGrow = d3.scale.linear() .domain([ 1e3, 2e3, 7e3, 8e3 ]) .range([ 0, 30, 60, 300 ]) .clamp(true); var squareWave = makeSquareWave(2, 2e-4, 15); // // spiky d3.timer(function(t) { spines.style('stroke-width', spineShrink(t)); spikys.attr('d', function(d) { // w/ square wave oscillation: spikeGrow(t) + (.3 * spikeGrow(t) * (squareWave(t)+2)) return getSpikyPath(d.samples, mouse, spikeGrow(t) ); }); }); // blobby // d3.timer(function(t) { // spines.style('stroke-width', 0); // spikys.attr('d', function(d) { // return getBlobbyPath(d.samples, mouse, squareWave(t)); // }); // }); function samplePath(path, precision, jiggle) { var sampleJiggle = jiggle || 0; var samplePrecision = precision || 5; var totalLength = path.getTotalLength(); var samples = totalLength / samplePrecision; var distances = d3.range(samples).map(function(d) { var randomJiggleOffset = (sampleJiggle / totalLength) * (Math.random()-0.5); return (d===0) ? 0 : Math.max(Math.min((d / samples) + randomJiggleOffset, 1),0); }); distances.push(1); return distances.map(function(d,i) { var pt = path.getPointAtLength(d * totalLength); return [pt.x, pt.y]; }); } function getSpikyPath(spinePoints, origin, magnitude) { var axisOrigin = origin || [0,0]; var axisMagnitude = magnitude || 0; var offsetPoints = spinePoints.slice(0).map(function(pt,i) { // an additional degree of irregularity! amplitudinal irregularity, just based on sample index var amplitudinalJiggle = 2; // the lower, the bigger var mag = axisMagnitude + (axisMagnitude/amplitudinalJiggle) * Math.sin(i); var axis = [axisOrigin[0] - pt[0], axisOrigin[1] - pt[1]]; var axisNorm = distance([0,0], axis); var normalizedAxis = axis.map(function(d,i) { return (d / axisNorm) * mag; }); var direction = (i % 2 == 0) ? 1 : -1; return [pt[0] + direction * normalizedAxis[0], pt[1] + direction * normalizedAxis[1]]; }) var points = spinePoints.concat(offsetPoints.reverse()); return "M" + points.join("L"); } function getBlobbyPath(spinePoints, origin, magnitude) { var axisOrigin = origin || [0,0]; var axisMagnitude = magnitude || 0; var line = d3.svg.line().interpolate('basis'); var offsetPoints = spinePoints.slice(0).map(function(pt,i) { var axis = [axisOrigin[0] - pt[0], axisOrigin[1] - pt[1]]; var axisNorm = distance([0,0], axis); var normalizedAxis = axis.map(function(d,i) { return (d / axisNorm) * magnitude; }); var direction = (i % 2 == 0) ? 1 : -1; return [ pt[0] + normalizedAxis[0] + direction * normalizedAxis[0], pt[1] + normalizedAxis[1] + direction * normalizedAxis[1] ]; }) var offsetPoints2 = spinePoints.slice(0).map(function(pt,i) { var axis = [axisOrigin[0] - pt[0], axisOrigin[1] - pt[1]]; var axisNorm = distance([0,0], axis); var normalizedAxis = axis.map(function(d,i) { return (d / axisNorm) * magnitude; }); var direction = (i % 2 == 0) ? -1 : 1; return [ pt[0] - normalizedAxis[0] - direction * normalizedAxis[0], pt[1] - normalizedAxis[1] - direction * normalizedAxis[1] ]; }) var points = offsetPoints.concat(offsetPoints2.reverse()); return line(points) +'Z'; } function distance(a,b) { var x = b[0] - a[0]; var y = b[1] - a[1]; return Math.sqrt(Math.pow(x,2) + Math.pow(y,2)); } // https://en.wikipedia.org/wiki/Square_wave function makeSquareWave(numberOfTerms, frequency, amplitude) { return function(t) { var terms = d3.range(numberOfTerms).map(function(i) { var k = i + 1; //1-indexed return Math.sin( 2 * Math.PI * (2 * k - 1) * frequency * t ) / (2 * k - 1) }) return amplitude * 4/Math.PI * d3.sum(terms); } }