var width = innerWidth, height = innerHeight, initX = width/2, initY = height/2, ƒ = d3.f, curIndex = 0, isTop = false t = 0, frames = 100, fips = d3.shuffle([12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 01, 02, 04, 05, 06, 08, 09]) var projection = d3.geoAlbersUsa() .scale(1000) .translate([width/2, height/2]) var path = d3.geoPath().projection(projection) d3.loadData(['us.json'], function(err, res){ us = res[0] var svg = d3.select('body').html('').append('svg').at({width, height}) function updateCurState(){ isTop = !isTop if (isTop) curIndex++ var curFips = fips[curIndex % fips.length] curState = topojson.feature(us, { type: "GeometryCollection", geometries: us.objects.states.geometries.filter(function(d){ return d.id == curFips }) }) projection.fitSize([width, height], curState) latLongs = curState.features[0].geometry.coordinates[0][0] points = latLongs.map(projection) var startI = isTop ? 0 : points.length/2 if (!isTop){ var minY = d3.max(points, ƒ(1)) points.forEach(function(d, i){ if (d[1] == minY) startI = i }) } var i = 0 while (i++ < startI) points.push(points.shift()) if (!isTop) points.reverse() initX = points[0][0] initY = points[0][1] turns = pointsToTurns(points) } updateCurState() var pathSel = svg.append('path.line').at({stroke: '#000', fill: 'rgba(0,0,0,.07)'}) if (window.drawTimer) window.drawTimer.stop() window.drawTimer = d3.timer(function(time){ var r = (t % (frames*2))/frames if (r > 1) r = 2 - r r = Math.pow(r, 1/2) points = turnsToPoints(turns.map(d => ({a: d.a*r, l: d.l }))) var s = width/d3.sum(turns, ƒ('l')) var xAdjust = width/2 - d3.mean(points, ƒ('0'))*s var yAdjust = height/2 - d3.mean(points, ƒ('1'))*s var pathStr = points.map(([x, y]) => [x*s + xAdjust, y*s + yAdjust]).join('L') pathSel.at({d: 'M' + pathStr}) t++ if (t % (frames) == 0) updateCurState() }) }) function turnsToPoints(turns){ var curPos = [initX, initY] var curA = Math.PI var points = [curPos] turns.forEach(function(d){ curA += d.a var x = curPos[0] + Math.cos(curA)*d.l var y = curPos[1] + Math.sin(curA)*d.l curPos = [x, y] points.push(curPos) }) return points } function pointsToTurns(points){ var turns = [] var pPos = [width/2 + 1, height/2] var prevA = 0 points.forEach(function(d, i){ var nPos = points[i + 1] if (!nPos) return var a = calcAngle(pPos, d, nPos) var l = calcDist(d, nPos) var x = d[0] - nPos[0] var y = d[1] - nPos[1] var curA = Math.atan2(y, x) turns.push({a: curA - prevA, l}) prevA = curA }) return turns } function calcAngle(a, b, c){ var v1 = [b[0] - a[0], b[1] - a[1]] var v2 = [c[0] - b[0], c[1] - b[1]] return Math.atan(v2[0]/v2[1]) - Math.atan(v1[0]/v1[1]) } function calcDist([x0, y0], [x1, y1]){ var dx = x0 - x1 var dy = y0 - y1 return Math.sqrt(dx*dx + dy*dy) }