forked from enjalot's block: visfest logo - technical experiment #2
forked from enjalot's block: visfest logo - technical experiment #3
forked from enjalot's block: visfest logo - technical experiment #4
forked from enjalot's block: visfest logo - technical experiment #2
forked from enjalot's block: visfest logo - technical experiment #3
forked from enjalot's block: visfest logo - technical experiment #4
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> | |
<script src="sampler.js"></script> | |
</head> | |
<style> | |
body { | |
background-color: #192247; | |
} | |
</style> | |
<body> | |
<svg width=960 height=500> | |
<g transform="translate(-50,0)" opacity=0> | |
<path id="outer" fill="none" stroke="#000000" stroke-width="0.8" stroke-miterlimit="10" d="M174.7,85c-24.5,14-57,18-45.8,45.7 | |
c9.5,23.4-28.2,15.1-45.8-25.7s20.5-65.8,45.8-65.8S196.7,72.5,174.7,85Z"/> | |
<path id="inner" fill="none" stroke="#000000" stroke-width="0.8" stroke-miterlimit="10" d="M128.4,64.4c-1.9-0.1-5.9,6-5.9,6s-2.3-3.6-1-7.4 | |
c1.4-3.8,3.4-3.5,5-3.1C128.3,60.2,141.8,65.1,128.4,64.4z"/> | |
</g> | |
<g id="output"> | |
</g> | |
</svg> | |
<script> | |
var svg = d3.select("svg"); | |
var inner = d3.select("#inner") | |
var outer = d3.select("#outer") | |
var numSamples = 20; | |
var numLines = 13; | |
var scale = 3; | |
var xOffset = 0; | |
var yOffset = 40; | |
var line = d3.svg.line() | |
.x(function(d) { return d.x }) | |
.y(function(d) { return d.y }) | |
//.interpolate("linear-closed") | |
.interpolate("cardinal-closed") | |
.interpolate("basis-closed") | |
var ins = Sampler.getSamples(inner.node(), numSamples); | |
var outs = Sampler.getSamples(outer.node(), numSamples); | |
var output = d3.select("#output") | |
var lines = []; | |
d3.range(numLines).forEach(function(index) { | |
var samples = [] | |
var ratio = index / numLines; | |
var i, x, y; | |
for(i = 0; i < numSamples; i++) { | |
x = ins[i].x * (1 - ratio) + outs[i].x * ratio; | |
y = ins[i].y * (1 - ratio) + outs[i].y * ratio; | |
samples.push({ x: x * scale + xOffset, y: y * scale + yOffset}) | |
} | |
lines.push(samples) | |
}) | |
var blended = output.selectAll("path.blend") | |
.data(lines) | |
blended | |
.enter() | |
.append("path").classed("blend", true) | |
.attr({ | |
d: function(d) { return line(d) }, | |
fill: "none", | |
stroke: "#ff005d", | |
"stroke-width": 2, | |
}) | |
var bbox = blended.node().getBoundingClientRect(); | |
console.log("BBOX", bbox) | |
var center = { | |
x: bbox.left + bbox.width/2 - 15, | |
y: bbox.top + bbox.height/2 -15 | |
} | |
blended.attr({ | |
transform: "rotate(0, " + [center.x, center.y] + ")" | |
}) | |
output.append("circle") | |
.attr({ | |
r: 5, | |
cx: center.x, | |
cy: center.y, | |
//fill: "white" | |
fill: "none" | |
}) | |
function transition() { | |
blended.transition() | |
.ease("cubic") | |
.duration(800) | |
.delay(function(d,i) { return i * 100 }) | |
.attrTween("transform", function(d) { | |
return function(t) { | |
var deg = d3.interpolate(0, 180)(t) | |
return "rotate(" + deg + "," + [center.x, center.y] + ")" | |
} | |
}) | |
.each("end", function(d,i) { | |
blended.filter(function(d,j) { return i === j }) | |
.transition() | |
.ease("sin") | |
.duration(600) | |
.delay(function(d,i) { return i * 100 + 800 }) | |
.attrTween("transform", function(d) { | |
return function(t) { | |
var deg = d3.interpolate(180, 0)(t) | |
return "rotate(" + deg + "," + [center.x, center.y] + ")" | |
} | |
}) | |
.each("end", function(d,k) { | |
if(i === blended.length-1) { | |
transition(); | |
} | |
}) | |
}) | |
} | |
transition() | |
</script> | |
</body> | |
var Sampler = function() {} | |
Sampler.getSamples = function(path, num) { | |
var len = path.getTotalLength() | |
var p, t; | |
var result = [] | |
for(var i = 0; i < num; i++) { | |
p = path.getPointAtLength(i * len/num); | |
t = Sampler.getTangent(path, i/num * 100); | |
result.push({ | |
x: p.x, | |
y: p.y, | |
point: p, | |
tangent: t, | |
perp: Sampler.rotate2d(t.v, 90) | |
}); | |
} | |
return result | |
} | |
Sampler.getTangent = function(path, percent) { | |
// returns a normalized vector that describes the tangent | |
// at the point that is found at *percent* of the path's length | |
var fraction = percent/100; | |
if(fraction < 0) fraction = 0; | |
if(fraction > 0.99) fraction = 1; | |
var len = path.getTotalLength(); | |
var point1 = path.getPointAtLength(fraction * len - 0.1); | |
var point2 = path.getPointAtLength(fraction * len + 0.1); | |
var vector = { x: point2.x - point1.x, y: point2.y - point1.y } | |
var magnitude = Math.sqrt(vector.x*vector.x + vector.y*vector.y); | |
vector.x /= magnitude; | |
vector.y /= magnitude; | |
return {p: point1, v: vector }; | |
} | |
Sampler.rotate2d = function(vector, angle) { | |
//rotate a vector | |
angle *= Math.PI/180; //convert to radians | |
return { | |
x: vector.x * Math.cos(angle) - vector.y * Math.sin(angle), | |
y: vector.x * Math.sin(angle) + vector.y * Math.cos(angle) | |
} | |
} | |