Skip to content

Instantly share code, notes, and snippets.

@ljegou
Last active December 31, 2016 13:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ljegou/3781735a61008d73305e5c3027877734 to your computer and use it in GitHub Desktop.
Save ljegou/3781735a61008d73305e5c3027877734 to your computer and use it in GitHub Desktop.
Sketchy map - test 1
/*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));
}
};
/**
* 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+")");
//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
sketchy.drawLine({
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;
}
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;
//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;
var x = opts.x, y = opts.y;
if(Math.abs(angle) === 90){
while(y < opts.y+opts.height){
sketchy.drawLine({
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){
sketchy.drawLine({
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);
}
sketchy.drawLine({
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);
}
sketchy.drawLine({
svg:svg,
x1:tx,
y1:ty,
x2:x1,
y2:y1,
sketch:{
x:x_dist*opts.sketch,
y:y_dist*opts.sketch
}
});
y -= y_dist;
}
}
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("id", opts.id)
.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.append("g").attr("id", opts.id);
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;
}
sketchy.drawLine({
svg:svg,
x1:x1,
y1:y1,
x2:x2,
y2:y2,
sketch:{
x:opts.sketch,
y:opts.sketch
}
});
}
}
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); });
for(var i = 0; i<merged_opts.count; i++){
svg.append('path')
.attr('d', function() {
return 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));
});
}
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;
};
/*global d3:false *//*jshint unused:false*//**
* Initiate the sketchy library
* @constructor
*/var d3sketchy=function(){function t(t){e=n(e,t);return e}function n(e,t){var n={},r;for(r in e)n[r]=e[r];for(r in t)n[r]=t[r];return n}function r(e,t){return Math.random()*(t-e)+e}function i(){}function s(e,t,n,i,s,o,u){var a=.551915024494,f=Math.atan(a),l=Math.sqrt(a*a+1),c=e,h=r(i,s)*Math.PI/180,p="M";p+=[c*Math.sin(h),c*Math.cos(h)];p+=" C"+[l*c*Math.sin(h+f),l*c*Math.cos(h+f)];for(var d=0;d<4;d++){h+=Math.PI/2*(1+r(o,u));c*=1+r(t,n);p+=" "+(d?"S":"")+[l*c*Math.sin(h-f),l*c*Math.cos(h-f)];p+=" "+[c*Math.sin(h),c*Math.cos(h)]}return p}function o(e,t,n,i){var s=r(n,i);return"rotate("+s+")"+"scale(1,"+r(e,t)+")"}var e={x:0,y:0,width:20,height:20,sketch:1,density:1,radius:10,angle:45,count:2,shape:"circle",clip:"",margin:2};i.drawLine=function(e){for(var t=1;t<3;t++){var n=r(.2,.8),i=e.x1+(e.x2-e.x1)*n+r(-1,1),s=e.y1+(e.y2-e.y1)*n+r(-1,1),o=n+r(-0.3,-0.2),u=e.x1+(e.x2-e.x1)*o+r(-1,1),a=e.y1+(e.y2-e.y1)*o+r(-1,1);e.svg.append("path").attr("d","M"+(e.x1+r(-1,0)*e.sketch.x/t)+" "+(e.y1+r(-1,1)*e.sketch.y/t)+" Q"+u+" "+a+" "+i+" "+s+" T"+(e.x2+r(0,1)*e.sketch.x/t)+" "+(e.y2+r(-1,1)*e.sketch.y/t))}};i.circleFill=function(t){var r=n(e,t),s=r.svg.append("g").attr("transform","translate("+r.x+" "+r.y+") rotate("+r.angle+")"),o=0;while(o>-2*t.r){var u;r.shape==="cut"?u=Math.sqrt(Math.pow(r.r,2)-Math.pow(r.r-Math.abs(o),2)):r.shape==="star"?u=r.r-Math.sqrt(Math.pow(r.r,2)-Math.pow(r.r-Math.abs(o),2)):u=Math.sqrt(Math.pow(r.r,2)-Math.pow(r.r-Math.abs(o),2));i.drawLine({svg:s,x1:-u,y1:o+r.r,x2:u,y2:o+r.r,sketch:{x:r.density*r.sketch,y:r.density*r.sketch}});o-=r.density}return s};i.rectFill=function(e){var t=e.svg.append("g").attr("transform","translate("+e.x+" "+e.y+")");e.svg=t;return i.drawPattern(e)};i.drawPattern=function(e){var t=e.svg,n=e.angle;while(n>360)n-=360;n>180&&(n-=180);var r=Math.PI/180*(90-n),s={y:1,x:-1/Math.tan(r)},o=e.density,u,a,f,l,c,h,p,d,v=e.x,m=e.y;if(Math.abs(n)===90)while(m<e.y+e.height){i.drawLine({svg:t,x1:v,y1:m,x2:v+e.width,y2:m,sketch:{x:o*e.sketch,y:o*e.sketch}});m+=o}else if(Math.abs(n)===180||n===0)while(v<e.x+e.width){i.drawLine({svg:t,x1:v,y1:m,x2:v,y2:m+e.height,sketch:{x:o*e.sketch,y:o*e.sketch}});v+=o}else if(n<90){p=Math.abs(o/Math.sin(Math.PI/180*n));d=Math.abs(o/Math.sin(Math.PI/180*(90-n)));m+=p;c=e.y;h=e.x;while(c<e.y+e.height){l=e.width/s.x;h=e.width+v;c=m+s.y*l;f=m;a=v;if(c<e.y){u=(m-e.y)/s.y;h=v+Math.abs(s.x)*u;c=e.y}else if(m>e.y+e.height){f=e.y+e.height;u=(f-c)/s.y;a=v+e.width-u*Math.abs(s.x)}i.drawLine({svg:t,x1:a,y1:f,x2:h,y2:c,sketch:{x:d*e.sketch,y:p*e.sketch}});m+=p}}else{p=Math.abs(o/Math.sin(Math.PI/180*n));d=Math.abs(o/Math.sin(Math.PI/180*(180-n)));m=e.y+e.height;m-=p;c=e.y+e.height;h=e.x;while(c>e.y){l=e.width/s.x;h=e.width+v;c=m+s.y*l;f=m;a=v;if(c>e.y+e.height){u=(m-(e.y+e.height))/s.y;h=v+Math.abs(s.x*u);c=e.y+e.height}else if(m<e.y){f=e.y;u=(f-c)/s.y;a=v+e.width-Math.abs(u*s.x)}i.drawLine({svg:t,x1:a,y1:f,x2:h,y2:c,sketch:{x:d*e.sketch,y:p*e.sketch}});m-=p}}return t};i.fill=function(t){var r=n(e,t),s=r.svg.append("g").attr("clip-path","url(#"+r.clip+")"),o=d3.select("#"+r.clip).node().getBBox();r.x=o.x-r.margin;r.y=o.y-r.margin;r.width=o.width+2*r.margin;r.height=o.height+2*r.margin;r.svg=s;return i.drawPattern(r)};i.alterPath=function(t){var i=n(e,t),s=[];for(var o=0;o<i.count;o++){var u=[];for(var a=0;a<i.path.length;a++)u.push({x:i.path[a].x+r(-1,1)*i.sketch/(o+1),y:i.path[a].y+r(-1,1)*i.sketch/(o+1)});s.push(u)}return s};i.pathStroke=function(e){var t=i.alterPath(e),n=e.svg.append("g");for(var r=0;r<t.length;r++)for(var s=0;s<t[r].length;s++){var o=t[r][s].x,u=t[r][s].y,a,f;if(s<t[r].length-1){a=t[r][s+1].x;f=t[r][s+1].y}else{a=t[r][0].x;f=t[r][0].y}i.drawLine({svg:n,x1:o,y1:u,x2:a,y2:f,sketch:{x:e.sketch,y:e.sketch}})}return n};i.circleStroke=function(t){var r=n(e,t),i=r.svg.append("g").attr("transform",function(){return"translate("+r.x+" "+r.y+") "+o(1,1,0,360)});for(var u=0;u<r.count;u++)i.append("path").attr("d",function(){return s(r.r,r.sketch/-50/(u+1),r.sketch/10/(u+1),200,240,0,r.sketch/5/(u+1))});return i};i.rectStroke=function(t){var r=n(e,t),s=r.svg.append("g"),o=[{x:r.x,y:r.y},{x:r.x+r.width,y:r.y},{x:r.x+r.width,y:r.y+r.height},{x:r.x,y:r.y+r.height}];return i.pathStroke({svg:s,path:o,count:r.count,sketch:r.sketch})};return i};
<!DOCTYPE html>
<meta charset="utf-8">
<head>
<style type="text/css">
#c1{
fill:none;
stroke: #888888;
stroke-width:0.2;
stroke-linecap:round;
stroke-linejoin:round;
}
#p1{
fill:none;
stroke: #AAAAAA;
stroke-width:0.3;
stroke-linecap:round;
stroke-linejoin:round;
}
#c2{
fill:none;
stroke: #666666;
stroke-width:0.2;
stroke-linecap:round;
stroke-linejoin:round;
}
#p2{
fill:none;
stroke: #444444;
stroke-width:0.3;
stroke-linecap:round;
stroke-linejoin:round;
}
</style>
<script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/lodash/4.17.3/lodash.min.js"></script>
<script src="d3.sketchy.js"></script>
<script src="svg-to-wkt.js"></script>
</head>
<body>
<svg width="800" height="600">
</svg>
<script type="text/javascript">
var svg = d3.select('svg');
var sketchy = d3sketchy();
// Formes polygonales de test
var d = [];
d[0] = "m 183.65691,147.92113 c 58.14265,-5.5374 78.44642,41.53046 84.90672,69.21743 6.46029,27.68697 24.91827,61.83424 -9.22899,80.29222 -30.47526,16.47312 -64.60294,6.46029 -76.60063,-14.76639 -11.99768,-21.22668 -65.52583,-126.43717 0.9229,-134.74326 z";
d[1] = "m 295.2603,138.79105 c 71.57323,1.52505 72.83813,75.35978 73.14871,125.26578 0.17692,28.43013 -25.31229,37.98752 -63.75971,32.64628 -34.31299,-4.76686 -20.62009,-21.07915 -27.14942,-47.09929 -5.91696,-23.57974 -12.84496,-44.83187 -17.4201,-58.87064 -7.91295,-24.28081 6.01987,-52.27989 35.18052,-51.94213 z";
d[2] = "m 420.06475,144.61289 c 45.23756,-0.58298 56.96172,5.89115 72.41015,18.61947 22.63277,18.64766 -8.6454,112.16536 -32.91581,137.2448 -19.77129,20.43035 -43.70156,31.9354 -78.7855,15.32666 -31.3112,-14.82272 -15.44332,-16.80522 -12.95667,-45.43656 1.21428,-13.9812 0.10813,-39.4598 -2.45535,-58.64159 -1.87723,-14.04682 -4.53597,-22.89702 -7.63727,-30.49375 -8.29286,-20.3136 42.01887,-36.88567 62.34045,-36.61903 z";
d[3] = "m 637.62835,308.07718 c 23.89945,42.21237 -28.99057,66.44668 -55.94001,69.49217 -30.96511,3.49929 -100.89118,49.76726 -134.84174,41.68081 -27.65699,-6.58742 -60.4078,-21.78075 -53.3639,-59.95295 3.64929,-19.77619 8.39406,-19.69222 30.84048,-36.93373 17.51738,-3.03043 38.987,-21.29912 47.22772,-38.55966 5.82691,-12.20471 7.61202,-13.2745 16.44845,-36.83758 4.45928,-11.89105 66.05264,-66.97161 84.87685,-48.07387 13.6982,13.75171 60.48355,101.76503 64.75215,109.18481 z";
d[4] = "m 371.83976,497.79572 c -64.68363,15.61359 -98.61439,-16.16148 -97.39359,-44.56594 0.87187,-20.28595 3.71705,-36.08254 7.0812,-46.44984 4.90186,-15.10606 5.90037,-23.86693 5.68557,-34.87687 -0.20655,-10.5871 -2.20752,-23.26103 -5.5176,-35.35741 -2.69107,-9.83427 12.65232,-18.25752 22.74082,-21.07145 23.70552,-6.61205 24.47406,7.2558 67.03388,12.3859 15.56918,1.87669 19.39791,28.1528 20.47478,38.59235 1.1356,11.00903 1.56585,33.49706 38.45565,48.17247 32.6056,12.97108 -8.84089,71.16923 -58.56071,83.17079 z";
d[5] = "m 104.06796,391.92379 c -1.85391,-23.87072 -1.10215,-51.50731 12.56321,-71.92252 19.66999,-29.38577 56.27467,-46.02724 67.58237,-33.635 20.77338,22.76579 44.33595,20.85136 61.94092,17.50328 6.56902,-1.24928 26.24798,0.95712 31.18249,15.88243 10.8744,32.89153 20.70134,93.67009 -18.16908,106.18239 -23.20984,7.4712 -147.95407,32.57292 -155.09991,-34.01058 z";
var path2xy = function(p){
// Conversion du path en polygones, avec remplacement progressif (et inversion des y)
var coords = SVGtoWKT.path(p, 1);
coords = coords.replace(/(.*?\(\()/, '');
coords = coords.replace('))', '');
coords = coords.replace(/(,)/g, '}$1{x:');
coords = coords.replace(/\s/g, ', y:');
coords = coords.replace(/y:-/g, 'y:');
coords = '[{x:'+coords+'}]';
return eval(coords);
}
// L'objet defs du svg, qui va contenir plus tar les formes de découpe (clips)
var defs = svg.append("defs");
// Objet ligne de D3, reliant les points avec une interpolation simple entre les points
var line = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveLinear);
// Fonction de dessin des formes approximatives + contours
var dessin = function(style_id, p, density, angle, sketch){
ap = path2xy(d[p]);
defs.append("clipPath")
.attr("id", "clip"+p)
.append("path")
.attr("d", line(ap));
sketchy.fill({
id: "p"+style_id,
clip:"clip"+p,
svg: svg,
density: density,
angle: angle,
sketch: sketch
});
sketchy.pathStroke({svg:svg, path:ap, count:1, density:density, sketch:sketch, id:"c"+style_id});
}
// Dessin des six formes avec un degré de "sketchiness" croissant
dessin(1, 0, 1, 40, 1);
dessin(2, 1, 2, 40, 2);
dessin(1, 2, 3, 40, 3);
dessin(2, 3, 4, 40, 4);
dessin(1, 4, 5, 40, 5);
dessin(2, 5, 6, 40, 6);
</script>
</body>
</html>
/**
* SVG-to-WKT.js
*
* @package svg-to-wkt
* @copyright 2012 David McClure
* @license http://www.apache.org/licenses/LICENSE-2.0.html
*/
(function() {
var SVGNS = 'http://www.w3.org/2000/svg';
var SVGtoWKT = {};
/**
* The number of decimal places computed during curve interpolation when
* generating points for `<circle>`, `<ellipse>`, and `<path>` elements.
*
* @public
*/
SVGtoWKT.PRECISION = 3;
/**
* The number of points computed during curve interpolation per unit of
* linear pixel length. For example, if a a path is 10px in length, and
* `DENSITY` is set to 2, the path will be rendered with 20 points.
*
* @public
*/
SVGtoWKT.DENSITY = 1;
/**
* SVG => WKT.
*
* @param {String} svg: SVG markup.
* @return {String}: Generated WKT.
*
* @public
*/
SVGtoWKT.convert = function(svg) {
// Halt if svg is undefined or empty.
if (_.isUndefined(svg) || _.isEmpty(svg.trim())) {
throw new Error('Empty XML.');
}
var els = [];
var xml;
// Strip out tabs and linebreaks.
svg = svg.replace(/\r\n|\r|\n|\t/g, '');
try {
// Parse the raw XML.
xml = $($.parseXML(svg));
} catch (e) {
// Halt if malformed.
throw new Error('Invalid XML.');
}
// Match `<polygon>` elements.
xml.find('polygon').each(function(i, polygon) {
els.push(SVGtoWKT.polygon($(polygon).attr('points')));
});
// Match `<polyline>` elements.
xml.find('polyline').each(function(i, polyline) {
els.push(SVGtoWKT.polyline($(polyline).attr('points')));
});
// Match `<line>` elements.
xml.find('line').each(function(i, line) {
els.push(SVGtoWKT.line(
parseFloat($(line).attr('x1')),
parseFloat($(line).attr('y1')),
parseFloat($(line).attr('x2')),
parseFloat($(line).attr('y2'))
));
});
// Match `<rect>` elements.
xml.find('rect').each(function(i, rect) {
els.push(SVGtoWKT.rect(
parseFloat($(rect).attr('x')),
parseFloat($(rect).attr('y')),
parseFloat($(rect).attr('width')),
parseFloat($(rect).attr('height'))
));
});
// Match `<circle>` elements.
xml.find('circle').each(function(i, circle) {
els.push(SVGtoWKT.circle(
parseFloat($(circle).attr('cx')),
parseFloat($(circle).attr('cy')),
parseFloat($(circle).attr('r'))
));
});
// Match `<ellipse>` elements.
xml.find('ellipse').each(function(i, circle) {
els.push(SVGtoWKT.ellipse(
parseFloat($(circle).attr('cx')),
parseFloat($(circle).attr('cy')),
parseFloat($(circle).attr('rx')),
parseFloat($(circle).attr('ry'))
));
});
// Match `<path>` elements.
xml.find('path').each(function(i, path) {
els.push(SVGtoWKT.path($(path).attr('d')));
});
return 'GEOMETRYCOLLECTION(' + els.join(',') + ')';
};
/**
* Construct a WKT line from SVG start/end point coordinates.
*
* @param {Number} x1: Start X.
* @param {Number} y1: Start Y.
* @param {Number} x2: End X.
* @param {Number} y2: End Y.
* @return {String}: Generated WKT.
*
* @public
*/
SVGtoWKT.line = function(x1, y1, x2, y2) {
return 'LINESTRING('+x1+' '+-y1+','+x2+' '+-y2+')';
};
/**
* Construct a WKT linestrimg from SVG `points` attribute value.
*
* @param {String} points: <polyline> `points` attribute value.
* @return {String}: Generated WKT.
*
* @public
*/
SVGtoWKT.polyline = function(points) {
// "1,2 3,4 " => "1 2,3 4"
var pts = _.map(points.trim().split(' '), function(pt) {
pt = pt.split(','); pt[1] = -pt[1];
return pt.join(' ');
});
return 'LINESTRING(' + pts.join() + ')';
};
/**
* Construct a WKT polygon from SVG `points` attribute value.
*
* @param {String} points: <polygon> `points` attribute value.
* @return {String}: Generated WKT.
*
* @public
*/
SVGtoWKT.polygon = function(points) {
// "1,2 3,4 " => "1 2,3 4"
var pts = _.map(points.trim().split(' '), function(pt) {
pt = pt.split(','); pt[1] = -pt[1];
return pt.join(' ');
});
// Close.
pts.push(pts[0]);
return 'POLYGON((' + pts.join() + '))';
};
/**
* Construct a WKT polygon from SVG rectangle origin and dimensions.
*
* @param {Number} x: Top left X.
* @param {Number} y: Top left Y.
* @param {Number} width: Rectangle width.
* @param {Number} height: Rectangle height.
* @return {String}: Generated WKT.
*
* @public
*/
SVGtoWKT.rect = function(x, y, width, height) {
var pts = [];
// 0,0 origin by default.
if (!_.isNumber(x)) x = 0;
if (!_.isNumber(y)) y = 0;
// No corner rounding.
pts.push(String(x)+' '+String(-y)); // top left
pts.push(String(x+width)+' '+String(-y)); // top right
pts.push(String(x+width)+' '+String(-y-height)); // bottom right
pts.push(String(x)+' '+String(-y-height)); // bottom left
pts.push(String(x)+' '+String(-y)); // close
// TODO: Corner rounding.
return 'POLYGON((' + pts.join() + '))';
};
/**
* Construct a WKT polygon for a circle from origin and radius.
*
* @param {Number} cx: Center X.
* @param {Number} cy: Center Y.
* @param {Number} r: Radius.
* @return {String} wkt: Generated WKT.
*
* @public
*/
SVGtoWKT.circle = function(cx, cy, r) {
var wkt = 'POLYGON((';
var pts = [];
// Compute number of points.
var circumference = Math.PI * 2 * r;
var point_count = Math.round(circumference * SVGtoWKT.DENSITY);
// Compute angle between points.
var interval_angle = 360 / point_count;
// Genrate the circle.
_(point_count).times(function(i) {
var angle = (interval_angle * i) * (Math.PI / 180);
var x = __round(cx + r * Math.cos(angle));
var y = __round(cy + r * Math.sin(angle));
pts.push(String(x)+' '+String(-y));
});
// Close.
pts.push(pts[0]);
return wkt + pts.join() + '))';
};
/**
* Construct a WKT polygon for an ellipse from origin and radii.
*
* @param {Number} cx: Center X.
* @param {Number} cy: Center Y.
* @param {Number} rx: Horizontal radius.
* @param {Number} ry: Vertical radius.
* @return {String} wkt: Generated WKT.
*
* @public
*/
SVGtoWKT.ellipse = function(cx, cy, rx, ry) {
var wkt = 'POLYGON((';
var pts = [];
// Approximate the circumference.
var circumference = 2 * Math.PI * Math.sqrt(
(Math.pow(rx, 2) + Math.pow(ry, 2)) / 2
);
// Compute number of points and angle between points.
var point_count = Math.round(circumference * SVGtoWKT.DENSITY);
var interval_angle = 360 / point_count;
// Generate the ellipse.
_(point_count).times(function(i) {
var angle = (interval_angle * i) * (Math.PI / 180);
var x = __round(cx + rx * Math.cos(angle));
var y = __round(cy + ry * Math.sin(angle));
pts.push(String(x)+' '+String(-y));
});
// Close.
pts.push(pts[0]);
return wkt + pts.join() + '))';
};
/**
* Construct a WKT polygon from a SVG path string. Approach from:
* http://whaticode.com/2012/02/01/converting-svg-paths-to-polygons/
*
* @param {String} d: <path> `d` attribute value.
* @return {String}: Generated WKT.
*
* @public
*/
SVGtoWKT.path = function(d) {
// Try to extract polygon paths closed with 'Z'.
var polys = _.map(d.trim().match(/[^z|Z]+[z|Z]/g), function(p) {
return __pathElement(p.trim()+'z');
});
// If closed polygon paths exist, construct a `POLYGON`.
if (!_.isEmpty(polys)) {
var parts = [];
_.each(polys, function(poly) {
parts.push('(' + __pathPoints(poly, true).join() + ')');
});
return 'POLYGON(' + parts.join() + ')';
}
// Otherwise, construct a `LINESTRING` from the unclosed path.
else {
var line = __pathElement(d);
return 'LINESTRING(' + __pathPoints(line).join() + ')';
}
};
/**
* Construct a SVG path element.
*
* @param {String} d: <path> `d` attribute value.
* @return {SVGPathElement}: The new <path> element.
*
* @private
*/
var __pathElement = function(d) {
var path = document.createElementNS(SVGNS, 'path');
path.setAttributeNS(null, 'd', d);
return path;
};
/**
* Construct a SVG path element.
*
* @param {SVGPathElement} path: A <path> element.
* @param {Boolean} closed: True if the path should be closed.
* @return array: An array of space-delimited coords.
*
* @private
*/
var __pathPoints = function(path, closed) {
closed = closed || false;
var pts = [];
// Get number of points.
var length = path.getTotalLength();
var count = Math.round(length * SVGtoWKT.DENSITY);
// Interpolate points.
_(count+1).times(function(i) {
var point = path.getPointAtLength((length * i) / count);
pts.push(String(__round(point.x))+' '+String(__round(-point.y)));
});
// If requested, close the shape.
if (closed) pts.push(pts[0]);
return pts;
};
/**
* Round a number to the number of decimal places in `PRECISION`.
*
* @param {Number} val: The number to round.
* @return {Number}: The rounded value.
*
* @private
*/
var __round = function(val) {
var root = Math.pow(10, SVGtoWKT.PRECISION);
return Math.round(val * root) / root;
};
this.SVGtoWKT = SVGtoWKT;
}.call(this));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment