Skip to content

Instantly share code, notes, and snippets.

@jlyon
Forked from emeeks/d3.sketchy.js
Last active October 12, 2017 06:38
Show Gist options
  • Save jlyon/1869a543afe081c1a30f7b0f1a9dac2f to your computer and use it in GitHub Desktop.
Save jlyon/1869a543afe081c1a30f7b0f1a9dac2f to your computer and use it in GitHub Desktop.
Sketchy Network II
/*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")
.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;
};
creditor debtor amount risk
Britain France 22.4 3
Britain Greece 0.55 0
Britain Italy 26 0
Britain Portugal 19.4 0
Britain United States 345 1
France Germany 53.8 1
France Greece 53.9 0
France Ireland 17.3 0
France Italy 366 0
France Japan 7.73 1
France Portugal 18.3 0
France Spain 118 2
France United States 322 1
Germany Britain 321 1
Germany Greece 19.3 0
Germany Ireland 48.9 0
Germany Portugal 32.5 0
Germany Spain 57.6 2
Germany United States 324 1
Ireland Britain 12 1
Ireland Greece 0.34 0
Ireland Spain 6.38 2
Italy Germany 111 1
Italy Greece 3.22 0
Italy Ireland 2.83 0
Italy Portugal 0.87 0
Japan Britain 28.2 1
Japan Germany 88.5 1
Japan Greece 1.37 0
Japan Ireland 18.9 0
Japan Italy 38.8 0
Japan Portugal 2.18 0
Japan Spain 25.9 2
Japan United States 796 1
Portugal Greece 10.1 0
Portugal Ireland 3.77 0
Portugal United States 0.52 1
Spain Britain 326 1
Spain Greece 0.78 0
Spain Italy 9.79 0
Spain Portugal 62 0
Spain United States 163 1
United States Greece 3.1 0
United States Ireland 11.1 0
United States Italy 3.16 0
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>All the Datavizzes</title>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="noceiling.css" />
<style type="text/css">
g.sketchy-stroke > path {
fill: none;
stroke: black;
stroke-width: 1px;
stroke-opacity: 0.75;
}
path.sketchy-stroke, path.sketchy-fill, path.original {
fill: none;
stroke: black;
stroke-width: 1px;
stroke-opacity: 0.75;
}
svg {
}
</style>
</head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js" charset="utf-8" type="text/javascript"></script>
<script src="d3.sketchy.js" charset="utf-8" type="text/javascript"></script>
<script>
sketchy = d3sketchy();
function cheapSketchy(path) {
var length = path.getTotalLength();
var drawCode = "";
var x = 0;
var step = 2;
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 jitterLine(pathNode) {
var length = pathNode.getTotalLength();
var j = 2;
var x = j + (Math.random() * (j * 5));
var jitteredPoints = [];
var lineGen = d3.svg.line()
.x(function (d) {return d.x})
.y(function (d) {return d.y}).interpolate("basis");
var newPoint = pathNode.getPointAtLength(0);
jitteredPoints.push(newPoint);
while (x < length) {
newPoint = pathNode.getPointAtLength(x);
var newX = newPoint.x + ((Math.random() * j) - j/2);
var 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 forceNetwork() {
d3.csv("debt.csv", drawNetwork);
}
function drawNetwork(data) {
var nodes = [];
var nodeHash = {};
var edges = [];
data.forEach(function (link) {
if (!nodeHash[link.creditor]) {
nodeHash[link.creditor] = {id: link.creditor, r: 7};
nodes.push(nodeHash[link.creditor]);
}
if (!nodeHash[link.debtor]) {
nodeHash[link.debtor] = {id: link.debtor, r: 7};
nodes.push(nodeHash[link.debtor]);
}
if (link.amount > 50) {
var newEdge = {source: nodeHash[link.creditor], target: nodeHash[link.debtor], amount: parseFloat(link.amount), risk: parseInt(link.risk)};
edges.push(newEdge);
}
});
weightscale = d3.scale.linear().domain([0,10]).range([5,25]);
amountscale = d3.scale.linear().domain([50,300]).range([2,6]);
edgescale = d3.scale.quantile().domain(
edges.map(function(d) {return d.amount}))
.range(["#fdbe85","#fd8d3c","#d94701"]);
force = d3.layout.force()
.links(edges)
.nodes(nodes)
.charge(-2000)
.linkStrength(function (d) {return amountscale(d.amount) * 0.05})
.gravity(0.2)
.size([500, 500])
.on("tick", updateNetwork);
d3.select("svg").selectAll("g.edge")
.data(edges)
.enter()
.append("g")
.attr("class", "edge")
.append("path")
.attr("class", "original")
.style("fill", "none")
.style("stroke", function (d) {return edgescale(d.amount)})
.style("stroke-linecap", "round")
.style("stroke-width", function(d) {return amountscale(d.amount)});
d3.select("svg").selectAll("g.node")
.data(nodes)
.enter()
.append("g")
.call(force.drag)
.attr("class", "node")
.append("path")
.attr("class", "original")
.attr("d", "M 1.5081697,-5.2415042 A 6.0326772,6.0326772 0 1 1 -10.557185,-5.2415042 6.0326772,6.0326772 0 1 1 1.5081697,-5.2415042 z")
function updateNetwork() {
d3.selectAll("g.edge")
.select("path.original")
.attr("d", function (d) {return "M " + d.source.x + "," + d.source.y + " L" + d.target.x + "," + d.target.y})
.attr("d", function (d) {return jitterLine(this)});
d3.selectAll("g.node")
.attr("transform", function (d) {return "translate(" + (d.x + 5) + "," + (d.y + 5) + ")" })
.each(function (d) {
d.r = weightscale(d.weight);
d3.select(this).selectAll("path, g").remove();
sketchy.circleStroke({svg: d3.select(this), x:-(d.r/2), y:-(d.r/2), r:d.r, density:3, sketch:1.3});
d3.select(this).select("path.sketchy-stroke").style("stroke-width", "2px");
d3.select(this).insert("path", "g").attr("class", "sketchy-fill")
.style("fill", "none")
.style("stroke-width", "2px")
.style("stroke-linecap", "round")
.style("stroke", "blue")
.attr("transform", "translate(" + (-d.r/2) +","+(-d.r/2) + ")")
.attr("d", cheapSketchy(d3.select(this).select("path.sketchy-stroke").node()));
});
}
force.start();
}
</script>
<body onload="forceNetwork();">
<div id="viz">
<svg style="background:white;height:100%;width:100%;" id="svg">
</svg>
</div>
<footer>
</footer>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment