block by emeeks 912e24404b85a2af7dc7cb17927e816d

Wargames Aesthetic

Full Screen

Strange game. The only way to win is not to play.

index.html

<html>
<head>
  <title>Strange Game</title>
  <meta charset="utf-8" />
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="//d3js.org/d3-geo-projection.v1.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="d3.sketchy.js"></script>
<script src="cheapMarky.js"></script>

</head>
<style>
body {
  background: black;
}
  svg {
    height: 1000px;
    width: 1280px;
    border: none;
  }

  .countries {
    fill: $443636;
    stroke: none;
  }

</style>
<body>

<div id="viz">
  <svg>
  </svg>
</div>
<div id="controls" />
</body>
  <footer>

<script>
var colorScale = d3.scaleOrdinal().range(["#f8a313", "#a5c4c5", "#bab218", "#f0d976", "#e8381b", "#ee791e"]);

var filter = d3.select("svg").append("defs").append("filter").attr("id", "gooeyCodeFilter");
filter.append("feGaussianBlur").attr("id", "gaussblurrer").attr("in", "SourceGraphic").attr("stdDeviation", 4).attr("color-interpolation-filters", "sRGB").attr("result", "blur");
filter.append("feColorMatrix").attr("in", "blur").attr("mode", "matrix").attr("values", "1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 34 -7").attr("result", "gooey");

var svg = d3.select("svg");

d3.select("svg").append("g").attr("class", "bg")

d3.select("svg").append("g").attr("class", "fill")
    .style("filter", "url(#gooeyCodeFilter)")

d3.select("svg").append("g").attr("class", "border")

d3.select("svg").append("g").attr("class", "explosions")
    .style("filter", "url(#gooeyCodeFilter)")

var sketchy = d3sketchy();

var projection = d3.geoArmadillo()
.scale(300)
.translate([600, 360])
.parallel(20)
.rotate([-12, 0])
.precision(.1);

var geoPath = d3.geoPath().projection(projection);

var line = d3.line()
  .x(function(d) { return d.x; })
  .y(function(d) { return d.y; });


var numSides = 6
var sources
var countries
var sideHash
  d3.json("simpleworld.json", createMap);

  var powers = ["USA", "RUS", "CHN", "ISR", "IND", "PAK", "GBR", "FRA"]

  function createMap(world) {

    countries = topojson.feature(world, world.objects.world);

      renderMap(countries, projection)
  }

  function renderMap(countries, projection) {
  sideHash = {}
  countries.features = countries.features.filter(function (d) {return d.id !== "ATA"})
  countries.features.forEach(function (d) {
    sideHash[d.id] = parseInt(Math.random() * numSides)
  })

  sources = countries.features.filter(function (p) {return powers.indexOf(p.id) !== -1})

d3.select("g.bg")
.selectAll("path")
.data(countries.features)
.enter()
.append("path")
.attr("d", geoPath)
.attr("class", "countries")
.each(function (d, i) {
  d.centroid = geoPath.centroid(d)

  var originalNode = this;
  d.geometry.coordinates.forEach(function (p) {
    if (p.length < 2) {
      p = p[0];
    }
    var projectedArea = [];
      p.forEach(function (coordinate) {
        var proj = projection(coordinate);
        projectedArea.push({x: proj[0], y: proj[1]});
      })

        sketchy.pathStroke({svg: d3.select("g.border"), path:projectedArea, density:3, sketch:2, stroke: colorScale(sideHash[d.id]), strokeWidth: "2px"});
    })
})

d3.select("g.fill").selectAll("path")
  .attr("stroke-dasharray", function () {return "0," + this.getTotalLength()})
  .transition()
  .delay(function (d,i) {return i * 10})
      .transition()
      .duration(500)
      .attrTween("stroke-dasharray", tweenDash);

d3.select("g.border").selectAll("path")
  .style("fill", "none")
  .attr("stroke-dasharray", function () {return "0," + this.getTotalLength()})
  .transition()
  .delay(function (d,i) {return i * 10})
      .transition()
      .duration(500)
      .attrTween("stroke-dasharray", tweenDash);

      nuke()
    }

function nuke() {
d3.select("svg")
  .selectAll("path.nukelines")
  .data(d3.range(100))
  .enter()
  .append("path")
  .attr("class", "nukelines")
  .each(function (d,i) {
    var p1 = parseInt(Math.random() * sources.length)
    var targets = countries.features.filter(function (p) {return sideHash[p.id] !== sideHash[sources[p1].id]})

    var p2 = parseInt(Math.random() * targets.length)
    var x1 = sources[p1].centroid[0] + (Math.random() * 30) - 15
    var y1 = sources[p1].centroid[1]
    var x2 = targets[p2].centroid[0] + (Math.random() * 50) - 25
    var y2 = targets[p2].centroid[1] + (Math.random() * 50) - 25

    var delay = i < 10 ? 2000 + i * 150 : 3500 + i * 10

    d3.select(this)
    .attr("d", "M" + x1 + "," + y1 + " S" + ((x1 + x2)/2) + "," + ((y1 + y2)/2 - 200) + " " + x2 + "," + y2)
    .style("fill", "none")
    .style("stroke", d3.rgb(colorScale(sideHash[sources[p1].id])).brighter(.25))
    .style("stroke-width", 1)
    .style("stroke-dasharray", "0 " + this.getTotalLength())
    .transition()
    .delay(delay)
    .transition()
    .duration(1000)
    .style("stroke-dasharray", this.getTotalLength() + " 0")

    createExplosion(x2, y2, delay + 500, d3.rgb(colorScale(sideHash[sources[p1].id])).brighter(.5))
  })

  setTimeout(function(){ d3.selectAll("g.blast").remove(); d3.selectAll("path.nukelines").remove(); nuke() }, 8000);
}

function tweenDash() {
  var l = this.getTotalLength(),
      i = d3.interpolateString("0," + l, l + "," + l);
  return function(t) { return i(t); };
}

function cheapSketchy(path) {
  var length = path.getTotalLength();
  var drawCode = "";
  var x = 0;
  var step = 2.5;

  while (x < length / 2) {
    var start = path.getPointAtLength(x);
    var end = path.getPointAtLength(length - x);

    drawCode += " M" + (start.x + (Math.random() * step - step/2)) + " " + (start.y + (Math.random() * step - step/2)) + "L" + (end.x + (Math.random() * step - step/2)) + " " + (end.y + (Math.random() * step - step/2));

    x += step + (Math.random() * step);
  }

  return drawCode;
}

function createExplosion(x, y, delay, color) {
  var explosionG = d3.select("g.explosions")
    .append("g")
    .attr("class", "blast")
    .attr("transform", "translate(" + x + "," + y + ")" )
    .style("filter", "url(#gooeyCodeFilter)")

    explosionG
    .append("path")
    .style("fill", "none")
    .style("stroke", "none")
    .attr("d", circlePath(20))

    explosionG
    .each(function (d,i) {
      var artCircles = cheapPopArtsy(d3.select(this).select("path").node())

        d3.select(this)
          .selectAll("circle")
          .data(artCircles)
          .enter()
          .append("circle")
          .attr("r", 0)
          .style("fill", color)
    })

var artBarTransition = explosionG
      .transition()
      .delay(delay)

      artBarTransition
      .selectAll("circle")
      .transition()
      .delay(function (d) {return Math.abs(d[0] + d[1])})
      .transition()
      .duration(100)
      .attr("r", function () {return Math.random() * 5})
      .attr("cx", function (d) {return d[0]})
      .attr("cy", function (d) {return d[1]})

}
</script>
  </footer>

</html>

cheapMarky.js

function circlePath(radius) {
    let arc = d3.arc().outerRadius(radius);
    return arc({ startAngle: 0, endAngle: 8 })
}

function rectPath(width,height) {
    return "M0,0L" + width + ",0L" + width + "," + height + "L0," + height + "Z"
}

function linePath(x1,x2,y1,y2) {
    return "M" + x1 + "," + y1 + "L" + x2 + "," + y2 + "L"
}

function jitterLine (pathNode) {
      let length = pathNode.getTotalLength();
      let j = 2;
      let x = j + Math.random() * j * 5;
      let jitteredPoints = [];
      let lineGen = d3.line()
      .x(d => d.x)
      .y(d => d.y)
      .curve(d3.curveCatmullRom.alpha(0.5));

      let newPoint = pathNode.getPointAtLength(0);
      jitteredPoints.push(newPoint);

      while (x < length) {
        newPoint = pathNode.getPointAtLength(x);
        let newX = newPoint.x + (Math.random() * j - j/2);
        let newY = newPoint.y + (Math.random() * j - j/2)
        jitteredPoints.push({ x: newX, y: newY })
        x += j + Math.random() * j * 5;
      }
      newPoint = pathNode.getPointAtLength(length);
      jitteredPoints.push(newPoint);

      return lineGen(jitteredPoints);
    }

function cheapSketchy(path) {
  let length = path.getTotalLength();
  let drawCode = "";
  let x = 0;
  let step = 8;

  while (x < length / 2) {
    let start = path.getPointAtLength(x);
    let end = path.getPointAtLength(length - x);

    drawCode += " M" +
        (start.x + (Math.random() * step - step/2)) + " " +
        (start.y + (Math.random() * step - step/2)) + "L" +
        (end.x + (Math.random() * step - step/2)) + " " +
        (end.y + (Math.random() * step - step/2));

    x += step + Math.random() * step;
  }

  return drawCode;
}

function cheapPopArtsy (path) {
  let length = path.getTotalLength();
  let circles = []
  let x = 0;
  let step = 8;

  while (x < length / 2) {
    let start = path.getPointAtLength(x);
    let end = path.getPointAtLength(length - x);
    let begin = 0.75
    while (begin < step) {
        const percent = begin / step
        const circleXa = percent * start.x
        const circleXb = (1 - percent) * end.x
        const circleYa = percent * start.y
        const circleYb = (1 - percent) * end.y
        circles.push([circleXa + circleXb, circleYa + circleYb])
        begin = begin + (1 + Math.random())
    }
    x = x + step
  }

  return circles;
}

randomColor = function (baseColor,range) {
    var hslBase = d3.hsl(baseColor)
    hslBase.h = hslBase.h + (Math.floor(Math.random() * (range * 255)) - Math.floor(range / 2));
    hslBase.s = hslBase.s + (Math.floor(Math.random() * range) - Math.floor(range / 2));
    hslBase.l = hslBase.l + (Math.floor(Math.random() * range) - Math.floor(range / 2));
    return hslBase.toString();
}

d3.sketchy.js

/*global d3:false */
/*jshint unused:false*/

/**
 * Initiate the sketchy library
 * @constructor
 */
var d3sketchy = function(){

	/**
	* Default attributes for generating the shapes, doing this we don't need to check if all parameters are provided
	* And if someone wants to build a lot of shapes with the same properties she just needs to use "setDefaults" to change them
	* @type {object}
	* @defaultvalue
	*/
	var defaults = {
		x:0,
		y:0,
		width:20,
		height:20,
		sketch:1,
		density:1,
		radius:10,
		angle:45,
		count:2,
		shape:"circle",
		clip:"",
		margin:2
	};

	/**
	* Changing the default attributes
	* @param {object} opts - object with default attributes see "var defaults"
	* @return {object} defaults - the full default object
	*/
	function setDefaults(opts){
		defaults = extend(defaults, opts);
		return defaults;
	}

	/**
	* merging two objects, source will replace duplicates in destination
	* @param {object} destination
	* @param {object} source
	*/
	function extend(destination, source) {
		var returnObj = {}, attrname;
		for (attrname in destination) { returnObj[attrname] = destination[attrname]; }
		for (attrname in source) { returnObj[attrname] = source[attrname]; }
		return returnObj;
	}

	/**
	* Generate random number between min and max
	* @param {float|int} min
	* @param {float|int} max
	* @return {float}
	*/
	function rand(min, max){
		return Math.random()* (max-min) + min;
	}

	/**
	* Create sketchy
	* @constructor
	*/
	function sketchy(){
	}

	/**
	* drawing a sketchy line
	* this is kind of the heart of the whole tool.
	* so if you want to make changes to the appearance of the lines, tweak the following lines
	* @param {object} opts
	* @param {d3.selection} opts.svg
	* @param {float|int} opts.x1 - x point 1
	* @param {float|int} opts.y1 - y point 1
	* @param {float|int} opts.x2 - x point 2
	* @param {float|int} opts.y2 - y point 2
	* @param {object} opts.sketch
	* @param {object} opts.sketch.x - sketchiness on the x-axis
	* @param {object} opts.sketch.y - sketchiness on the y-axis
	*/
	sketchy.drawLine = function(opts){
		//Each line is drawn twice the increase sketchiness
		for(var i = 1; i<3; i++){

			var or2 = rand(0.2, 0.8);

			var cx2 = opts.x1+ (opts.x2-opts.x1)*or2+rand(-1,1);
			var cy2 = opts.y1+ (opts.y2-opts.y1)*or2+rand(-1,1);

			var or1 = or2 + rand(-0.3, -0.2);

			var cx1 = opts.x1+ (opts.x2-opts.x1)*or1+rand(-1,1);
			var cy1 = opts.y1+ (opts.y2-opts.y1)*or1+rand(-1,1);

			opts.svg.append("path")
				.attr("d", "M"+
								(opts.x1 + rand(-1,0)*opts.sketch.x/i)+" "+
								(opts.y1 + rand(-1,1)*opts.sketch.y/i)+" Q"+
								cx1+" "+cy1+" "+
								cx2+" "+cy2+" T"+
								(opts.x2 + rand(0,1)*opts.sketch.x/i)+" "+
								(opts.y2 + rand(-1,1)*opts.sketch.y/i));

		}
	};

	sketchy.drawLineSinglePath = function(opts){
		//Each line is drawn twice the increase sketchiness
		var sketching = "";
		for(var i = 1; i<3; i++){

			var or2 = rand(0.2, 0.8);

			var cx2 = opts.x1+ (opts.x2-opts.x1)*or2+rand(-1,1);
			var cy2 = opts.y1+ (opts.y2-opts.y1)*or2+rand(-1,1);

			var or1 = or2 + rand(-0.3, -0.2);

			var cx1 = opts.x1+ (opts.x2-opts.x1)*or1+rand(-1,1);
			var cy1 = opts.y1+ (opts.y2-opts.y1)*or1+rand(-1,1);

			sketching += " M"+
								(opts.x1 + rand(-1,0)*opts.sketch.x/i)+" "+
								(opts.y1 + rand(-1,1)*opts.sketch.y/i)+" Q"+
								cx1+" "+cy1+" "+
								cx2+" "+cy2+" T"+
								(opts.x2 + rand(0,1)*opts.sketch.x/i)+" "+
								(opts.y2 + rand(-1,1)*opts.sketch.y/i);

		}
		return sketching;
	};

	/**
	* drawing a circle shape
	* no outline just the fill
	* @param {object} opts - object containing the attributes
	* @param {float|int} opts.x - x position
	* @param {float|int} opts.y - y position
	* @param {float|int} opts.r - radius
	* @param {float|int} opts.angle - angle of the lines (0-360)
	* @param {float|int} opts.density - distance between lines
	* @param {float|int} opts.sketch - sketchiness factor
	* @param {string} opts.shape - this is a development relic, default is "circle", alternatives "cut" and "star"
	* @return {object} svg - d3.selection of a group object, containing the circle
	*/
	sketchy.circleFill = function(opts){
		//merging default attributes with user attributes
		var merged_opts = extend(defaults, opts);

		//create a container, this is used to translate and rotate the circle, this container will be returned at the end of this function
		var svg = merged_opts.svg.append("g").attr("transform", "translate("+merged_opts.x+" "+merged_opts.y+") rotate("+merged_opts.angle+")");

		var fillLines = "";

		//Looping through the lines
		var y_dist = 0;
		while(y_dist > -2*opts.r){
			var x;
			//During the development i accidentaly generated those shapes and kept them :)
			if(merged_opts.shape==="cut"){
				x = Math.sqrt( ( Math.pow(merged_opts.r, 2) - Math.pow((merged_opts.r-Math.abs(y_dist)), 2) ) );
			}else if(merged_opts.shape==="star"){
				x = merged_opts.r - Math.sqrt( ( Math.pow(merged_opts.r, 2) - Math.pow((merged_opts.r-Math.abs(y_dist)), 2) ) );
			}else{
				x = Math.sqrt( ( Math.pow(merged_opts.r, 2) - Math.pow((merged_opts.r-Math.abs(y_dist)), 2) ) );
			}

			//Draw the sketchy lines
			fillLines += sketchy.drawLineSinglePath({
				svg:svg,
				x1:-x,
				y1:y_dist+merged_opts.r,
				x2:x,
				y2:y_dist+merged_opts.r,
				sketch:{
					x:merged_opts.density*merged_opts.sketch,
					y:merged_opts.density*merged_opts.sketch
				}
			});

			y_dist -= merged_opts.density;
		}

		svg.append("path").attr("d", fillLines);

		return svg;
	};

	/**
	* draws a rectangle
	* no outline just the fill
	* @param {object} opts - object containing the attributes
	* @param {float|int} opts.x - x position
	* @param {float|int} opts.y - y position
	* @param {float|int} opts.width - width
	* @param {float|int} opts.height - height
	* @param {float|int} opts.angle - angle of the lines (0-360)
	* @param {float|int} opts.density - distance between lines
	* @param {float|int} opts.sketch - sketchiness factor
	* @return {object} svg - d3.selection of a group object, containing the rectangle
	*/
	sketchy.rectFill = function(opts){
		var svg = opts.svg.append("g").attr("transform", "translate("+opts.x+" "+opts.y+")");
		opts.svg = svg;
		return sketchy.drawPattern(opts);
	};

	/**
	* draws a background pattern in the shape of a square according to x,y,with,height
	* @param {object} opts - object containing the attributes
	* @param {float|int} opts.x - x position
	* @param {float|int} opts.y - y position
	* @param {float|int} opts.width - width
	* @param {float|int} opts.height - height
	* @param {float|int} opts.angle - angle of the lines (0-360)
	* @param {float|int} opts.density - distance between lines
	* @param {float|int} opts.sketch - sketchiness factor
	* @return {object} svg - d3.selection of a group object, containing the background
	*/
	sketchy.drawPattern = function(opts){
		var svg = opts.svg;

		var drawCode = "";

		//angle for strokes
		var angle = opts.angle;
		while(angle > 360){angle -= 360;}
		if(angle > 180){angle -= 180;}
		var radian = (Math.PI/180)*(90-angle);
		var vector = {
			y:1,
			x:-1/Math.tan(radian)
		};

		//distance between strokes
		var dist = opts.density;

		var vy, tx, ty, vx, y1, x1, y_dist, x_dist;

		opts.x = 0;
		opts.y = 0;

		var x = opts.x, y = opts.y;
		if(Math.abs(angle) === 90){
			while(y < opts.y+opts.height){
				drawCode += sketchy.drawLineSinglePath({
					svg:svg,
					x1:x,
					y1:y,
					x2:x+opts.width,
					y2:y,
					sketch:{
						x:dist*opts.sketch,
						y:dist*opts.sketch
					}
				});
				y += dist;
			}
		}else if((Math.abs(angle) === 180)||(angle === 0)){
			while(x < opts.x+opts.width){
				drawCode += sketchy.drawLineSinglePath({
					svg:svg,
					x1:x,
					y1:y,
					x2:x,
					y2:y+opts.height,
					sketch:{
						x:dist*opts.sketch,
						y:dist*opts.sketch
					}
				});
				x += dist;
			}
		}else if(angle < 90){
			y_dist = Math.abs(dist / Math.sin(Math.PI/180*angle));
			x_dist = Math.abs(dist / Math.sin(Math.PI/180*(90-angle)));

			y += y_dist;
			y1 = opts.y;
			x1 = opts.x;
			while(y1 < opts.y+opts.height){
				vx = opts.width / vector.x;
				x1 = opts.width + x;
				y1 = y + vector.y * vx;
				ty = y;
				tx = x;

				if(y1<opts.y){
					vy = (y-opts.y)/vector.y;
					x1 = x + Math.abs(vector.x) * vy;
					y1 = opts.y;
				}else if(y > (opts.y+opts.height)){
					ty = opts.y+opts.height;
					vy = (ty-y1)/vector.y;
					tx = x + opts.width - vy*Math.abs(vector.x);
				}

				drawCode += sketchy.drawLineSinglePath({
					svg:svg,
					x1:tx,
					y1:ty,
					x2:x1,
					y2:y1,
					sketch:{
						x:x_dist*opts.sketch,
						y:y_dist*opts.sketch
					}
				});

				y += y_dist;
			}

		}else{
			y_dist = Math.abs(dist / Math.sin(Math.PI/180*angle));
			x_dist = Math.abs(dist / Math.sin(Math.PI/180*(180-angle)));

			y = opts.y+opts.height;
			y -= y_dist;
			y1 = opts.y+opts.height;
			x1 = opts.x;
			while(y1 > opts.y){
				vx = opts.width / vector.x;
				x1 = opts.width + x;
				y1 = y + vector.y * vx;
				ty = y;
				tx = x;

				if(y1>(opts.y+opts.height)){
					vy = (y-(opts.y+opts.height))/vector.y;
					x1 = x + Math.abs(vector.x * vy);
					y1 = opts.y+opts.height;
				}else if(y < opts.y){
					ty = opts.y;
					vy = (ty-y1)/vector.y;
					tx = x + opts.width - Math.abs(vy*vector.x);
				}

				drawCode += sketchy.drawLineSinglePath({
					svg:svg,
					x1:tx,
					y1:ty,
					x2:x1,
					y2:y1,
					sketch:{
						x:x_dist*opts.sketch,
						y:y_dist*opts.sketch
					}
				});

				y -= y_dist;
			}
		}

		svg.append("path")
		.attr("d", drawCode);

		return svg;
	};

	/**
	* draws a background pattern in the shape of a square according to the position and size of the clip-path object
	* @param {object} opts - object containing the attributes
	* @param {string} opts.clip - id of the clip path
	* @param {float|int} opts.angle - angle of the lines (0-360)
	* @param {float|int} opts.density - distance between lines
	* @param {float|int} opts.sketch - sketchiness factor
	* @param {float|int} opts.margin - extra margin for the background
	* @return {object} svg - d3.selection of a group object, containing the background
	*/
	sketchy.fill = function(opts){
		var merged_opts = extend(defaults, opts);

		var svg = merged_opts.svg.append("g")
			.attr("clip-path", "url(#"+merged_opts.clip+")");

		//Get the bounding box of the object that wants a background
		var bb = d3.select("#"+merged_opts.clip).node().getBBox();

		//To make sure that the background covers the whole are we increase the background by a few pixels
		merged_opts.x = bb.x-merged_opts.margin;
		merged_opts.y = bb.y-merged_opts.margin;
		merged_opts.width = bb.width + 2*merged_opts.margin;
		merged_opts.height = bb.height + 2*merged_opts.margin;
		merged_opts.svg = svg;

		return sketchy.drawPattern(merged_opts);
	};

	/**
	* draws a background pattern in the shape of a square according to the position and size of the clip-path object
	* @param {object} opts - object containing the attributes
	* @param {array} opts.path - array of points {x:float|integer, y:float|integer}
	* @param {int} opts.count - how many altered paths should be generated
	* @param {float|int} opts.sketch - sketchiness factor
	* @return {array} paths - altered paths
	*/
	sketchy.alterPath = function(opts){
		var merged_opts = extend(defaults, opts);
		var paths = [];
		for(var i = 0; i<merged_opts.count; i++){
			var t_path = [];
			for(var j = 0; j<merged_opts.path.length; j++){
				t_path.push({
					x:merged_opts.path[j].x + rand(-1,1)*merged_opts.sketch/(i+1),
					y:merged_opts.path[j].y + rand(-1,1)*merged_opts.sketch/(i+1)
				});
			}
			paths.push(t_path);
		}
		return paths;
	};

	/**
	* Draws alterPath() paths
	* only straight lines, use alterPath and your own drawing function to draw curves etc.
	* @param {object} opts - object containing the attributes
	* @param {array} opts.svg - d3.selection of an svg
	* @param {array} opts.path - array of points {x:float|integer, y:float|integer}
	* @param {int} opts.count - how many altered paths should be generated
	* @param {float|int} opts.sketch - sketchiness factor
	* @return {object} svg - svg with the strokes
	*/
	sketchy.pathStroke = function(opts){
		var paths = sketchy.alterPath(opts);
		var svg = opts.svg;

		var drawCode = "";
		for(var i = 0; i<paths.length; i++){
			for(var j = 0; j<paths[i].length; j++){
				var x1 = paths[i][j].x;
				var y1 = paths[i][j].y, x2, y2;
				if(j<(paths[i].length-1)){
					x2 = paths[i][j+1].x;
					y2 = paths[i][j+1].y;
				}else{
					x2 = paths[i][0].x;
					y2 = paths[i][0].y;
				}
				drawCode += sketchy.drawLineSinglePath({
					svg:svg,
					x1:x1,
					y1:y1,
					x2:x2,
					y2:y2,
					sketch:{
						x:opts.sketch,
						y:opts.sketch
					}
				});
			}
		}

		svg.append("path")
		.style("stroke", opts.stroke)
		.style("stroke-width", opts.strokeWidth)
		.attr("d", drawCode);
		return svg;
	};

	/**
	* Helper function for circleStroke
	* Adapted from http://codepen.io/spencerthayer/pen/nhjwu
	* Generates an altered circle path
	* @param {float|int} radius - radius of the circle
	* @param {float|int} radius_min - alternating radius min
	* @param {float|int} radius_max - alternating radius max
	* @param {float|int} s_angle_min - alternating angle min
	* @param {float|int} s_angle_max - alternating angle max
	* @param {float|int} rotation_min - alternating rotation min
	* @param {float|int} rotation_max - alternating rotation max
	* @return {string} path - altered circle svg path
	*/
	function circlePath(radius, radius_min,radius_max, s_angle_min, s_angle_max, rotation_min,rotation_max) {
		var c = 0.551915024494,
			b = Math.atan(c),
			d = Math.sqrt(c*c+1*1),
			r = radius,
			o = rand(s_angle_min, s_angle_max)*Math.PI/180,
			path = 'M';

		path += [r * Math.sin(o), r * Math.cos(o)];
		path += ' C' + [d * r * Math.sin(o + b), d * r * Math.cos(o + b)];

		for (var i=0; i<4; i++) {
			o += Math.PI/2 * (1 + rand(rotation_min, rotation_max));
			r *= (1 + rand(radius_min, radius_max));
			path += ' ' + (i?'S':'') + [d * r * Math.sin(o - b), d * r * Math.cos(o - b)];
			path += ' ' + [r * Math.sin(o), r * Math.cos(o)];
		}

		return path;
	}

	/**
	* Helper function for circleStroke
	* Adapted from http://codepen.io/spencerthayer/pen/nhjwu
	* Generates the transform value for squashing and rotating
	* @param {float|int} squash_min - squashing min
	* @param {float|int} squash_max - squashing max
	* @param {float|int} squash_rotation_min - squashing rotation min
	* @param {float|int} squash_rotation_max - squashing rotation max
	* @return {string} path - transform string
	*/
	function circleTransform(squash_min, squash_max, squash_rotation_min, squash_rotation_max) {
		var o = rand(squash_rotation_min, squash_rotation_max);
		return 'rotate('+o+')'+'scale(1,'+rand(squash_min, squash_max) + ')';
	}

	/**
	* Draw a sketch circle stroke
	* Adapted from http://codepen.io/spencerthayer/pen/nhjwu
	* @param {object} opts - object containing the attributes
	* @param {object} opts.svg - svg container
	* @param {float|int} opts.x - center x of circle
	* @param {float|int} opts.y - center y of circle
	* @param {float|int} opts.r - radius of circle
	* @param {int} count - number of strokes
	* @param {float|int} sketch - sketchiness factor
	* @return {object} svg - d3.selection of the svg containing the circles
	*/
	sketchy.circleStroke = function(opts){
		var merged_opts = extend(defaults, opts);

		var svg =  merged_opts.svg.append("g").attr('transform', function() { return "translate("+merged_opts.x+" "+merged_opts.y+") "+circleTransform(1,1, 0,360); });

		var drawCode = "";

		for(var i = 0; i<merged_opts.count; i++){
				drawCode += " " + circlePath(merged_opts.r, merged_opts.sketch/-50/(i+1),merged_opts.sketch/10/(i+1), 200,240, 0,merged_opts.sketch/5/(i+1));
		}

		svg.append('path')
			.attr("class", "sketchy-stroke")
			.attr('d', drawCode);

		return svg;
	};

	/**
	* Draw a sketch rectangle stroke
	* @param {object} opts - object containing the attributes
	* @param {object} opts.svg - svg container
	* @param {float|int} opts.x - x coordinate
	* @param {float|int} opts.y - y coordinate
	* @param {float|int} opts.width - width
	* @param {float|int} opts.height - height
	* @param {int} count - number of strokes
	* @param {float|int} sketch - sketchiness factor
	* @return {object} svg - d3.selection of the svg containing the rectangles
	*/
	sketchy.rectStroke = function(opts){
		var merged_opts = extend(defaults, opts);
		var svg = merged_opts.svg.append("g");


		var path = [
			{x:merged_opts.x, y:merged_opts.y},
			{x:merged_opts.x+merged_opts.width, y:merged_opts.y},
			{x:merged_opts.x+merged_opts.width, y:merged_opts.y+merged_opts.height},
			{x:merged_opts.x, y:merged_opts.y+merged_opts.height}
		];

		return sketchy.pathStroke({svg:svg, path:path, count:merged_opts.count, sketch:merged_opts.sketch});
	};

	return sketchy;
};