block by timelyportfolio cd961c10a861d1d084a8

Random Walk USA

Full Screen

Created by Christopher Manning

Summary

This is an experiment using a random walk to draw shapes. Adjusting theta results in a very organic or procedural drawing. The random walk stays in the geometry by using a point in polygon test.

Controls

References

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//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="//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>