|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<body> |
|
<script src="http://d3js.org/d3.v3.min.js"></script> |
|
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script> |
|
<script src="http://d3js.org/topojson.v1.min.js"></script> |
|
<script> |
|
width = Math.max(window.innerWidth, 960) |
|
height = Math.max(window.innerHeight, 500) |
|
config = {"theta": 1, "density" : 10, "scale": 25, "careful": true, "smooth": true}; |
|
gui = new dat.GUI({width: 130}); |
|
var examples = gui.addFolder('Examples'); |
|
examples.open() |
|
config.Sketch = function(){ |
|
config["theta"] = 1 |
|
config["density"] = 10 |
|
config["scale"] = 25 |
|
config["careful"] = true |
|
config["smooth"] = true |
|
draw() |
|
} |
|
examples.add(config, "Sketch") |
|
config.Geom = function(){ |
|
config["theta"] = 90 |
|
config["density"] = 10 |
|
config["scale"] = 25 |
|
config["careful"] = true |
|
config["smooth"] = true |
|
draw() |
|
} |
|
examples.add(config, "Geom") |
|
config.Squares = function(){ |
|
config["theta"] = 90 |
|
config["density"] = 30 |
|
config["scale"] = 5 |
|
config["careful"] = true |
|
config["smooth"] = false |
|
draw() |
|
} |
|
examples.add(config, "Squares") |
|
config.Triangles = function(){ |
|
config["theta"] = 60 |
|
config["density"] = 1 |
|
config["scale"] = 25 |
|
config["careful"] = false |
|
config["smooth"] = false |
|
draw() |
|
} |
|
examples.add(config, "Triangles") |
|
config.Random = function(){ |
|
gui.__folders.Settings.__controllers.forEach(function(c){ |
|
if(typeof(c.__select) != 'undefined') { |
|
c.setValue(c.__select[Math.floor(Math.random()*(c.__select.length-1))].value) |
|
} else { |
|
if(typeof c.initialValue == "boolean") { |
|
c.setValue(Math.round(Math.random()) == true) |
|
} else if(typeof c.initialValue == "number") { |
|
c.setValue(Math.floor(Math.random() * c.__max) + c.__min) |
|
} |
|
} |
|
}) |
|
draw() |
|
} |
|
examples.add(config, "Random") |
|
|
|
var settings = gui.addFolder('Settings'); |
|
thetaChanger = settings.add(config, "theta", 1, 180).step(1).listen() |
|
thetaChanger.onChange(function(value) { |
|
draw() |
|
}); |
|
scaleChanger = settings.add(config, "scale", 1, 25).step(1).listen() |
|
scaleChanger.onChange(function(value) { |
|
draw() |
|
}); |
|
densityChanger = settings.add(config, "density", 1, 50).listen() |
|
densityChanger.onChange(function(value) { |
|
draw() |
|
}); |
|
carefulChanger = settings.add(config, "careful").listen() |
|
carefulChanger.onChange(function(value) { |
|
draw() |
|
}); |
|
smoothChanger = settings.add(config, "smooth").listen() |
|
smoothChanger.onChange(function(value) { |
|
draw() |
|
}); |
|
config.redraw = function(){ |
|
draw() |
|
} |
|
settings.add(config, "redraw") |
|
|
|
var zoom = d3.behavior.zoom() |
|
.scale(config["theta"]) |
|
.scaleExtent([1, 180]) |
|
.on("zoom", function(d,i) { |
|
config["theta"] = Math.floor(d3.event.scale) |
|
draw() |
|
}); |
|
|
|
var projection = d3.geo.albersUsa() |
|
.scale(1000) |
|
.translate([(width-100) / 2, (height-25) / 2]); |
|
|
|
var path = d3.geo.path() |
|
.projection(projection); |
|
|
|
canvas = d3.select("body").append("canvas") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.call(zoom) |
|
|
|
context = canvas.node().getContext("2d") |
|
|
|
color = d3.scale.category20() |
|
line = d3.svg.line() |
|
.interpolate(config["interpolation"]) |
|
.tension(config["tension"]) |
|
.x(function(d, i) { return d.xo }) |
|
.y(function(d, i) { return d.yo }) |
|
|
|
function pointInPolygon(point, polygon) { |
|
for (var n = polygon.length, i = 0, j = n - 1, x = point[0], y = point[1], inside = false; i < n; j = i++) { |
|
var xi = polygon[i][0], yi = polygon[i][1], |
|
xj = polygon[j][0], yj = polygon[j][1]; |
|
if ((yi > y ^ yj > y) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) inside = !inside; |
|
} |
|
return inside; |
|
} |
|
|
|
function randInt(min, max) { |
|
return Math.floor(Math.random() * (max - min + 1)) + min |
|
} |
|
|
|
d3.json('us.json', function(error, us) { |
|
us_json = us |
|
us_json.objects.states.geometries = us_json.objects.states.geometries.filter(function(d) { return d.id < 60 }) |
|
states = topojson.feature(us_json, us_json.objects.states).features, |
|
neighbors = topojson.neighbors(us_json.objects.states.geometries); |
|
draw() |
|
}) |
|
|
|
function draw(){ |
|
context.clearRect(0, 0, width, height) |
|
context.lineJoin = 'round' |
|
for (var i0 = 0; i0 < states.length; i0++) { |
|
d = states[i0] |
|
context.beginPath() |
|
context.strokeStyle = states[i0].color ? states[i0].color : states[i0].color = color(states[i0].colorIndex = d3.max(neighbors[i0], function(n) { return states[n].colorIndex; }) + 1 | 0) |
|
|
|
for(var i1=0; i1<d.geometry.coordinates.length; i1++) { |
|
if(d.geometry.type=="MultiPolygon") { |
|
p = d.geometry.coordinates[i1][0] |
|
}else{ |
|
p = d.geometry.coordinates[i1] |
|
} |
|
d2 = {type: "Feature", properties: {}, geometry: {type: "Polygon", coordinates: [p]}} |
|
centroid = path.centroid(d2) |
|
area = path.area(d2) |
|
|
|
// skip small lands unless they're AK or HI |
|
if(area<100 && d.id != 2 && d.id != 15) continue |
|
|
|
last = {xo: centroid[0], yo: centroid[1], pip: true} |
|
context.moveTo(last.xo, last.yo) |
|
|
|
strokes = area*(config["density"]*.01) |
|
for(var i2=0;i2<(strokes < 4 ? 4 : strokes);i2++){ |
|
xo = last.xo |
|
yo = last.yo |
|
|
|
if(last.pip) { |
|
// random angle that's a multiple of theta |
|
r = randInt(1, 360/config["theta"]) |
|
angle = r * config["theta"] |
|
theta = angle * (Math.PI/180) |
|
} else { |
|
// if we're outside of the polygon, turn towards the centroid |
|
dx = centroid[0] - xo |
|
dy = centroid[1] - yo |
|
theta = Math.atan2(dy, dx) |
|
thetaDeg = theta * (180/Math.PI) |
|
|
|
// random angle towards the centroid clamped to theta |
|
randAngle = randInt(thetaDeg - 90, thetaDeg + 90) |
|
angle = Math.floor(randAngle/config["theta"])*config["theta"] |
|
theta = Math.PI * (angle/180) |
|
} |
|
|
|
scale = Math.sqrt(area) < config["scale"] ? Math.sqrt(area) : config["scale"] |
|
xo += Math.cos(theta)*scale |
|
yo += Math.sin(theta)*scale |
|
|
|
pip = pointInPolygon(projection.invert([xo, yo]), d2.geometry.coordinates[0]) |
|
last = {xo: xo, yo: yo, pip: pip} |
|
if(pip || i2==0 || !(pip || config["careful"])) context.lineTo(last.xo, last.yo) |
|
if(!config["smooth"]) context.moveTo(last.xo, last.yo) |
|
} |
|
} |
|
context.stroke() |
|
} |
|
} |
|
|
|
</script> |