/*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+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