This example uses texture.js to create custom patterns.
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="x.css">
<link href='//fonts.googleapis.com/css?family=Raleway:400,700' rel='stylesheet' type='text/css'>
</head>
<body>
<h2>Draw in the Box to Create a Pattern</h2>
<div id="clickToClear"></div>
<div id="example"></div>
<script type="text/javascript" src="datgui-min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="textures.min.js"></script>
<script src="x.js"></script>
</body>
// from https://github.com/riccardoscalco/textures/blob/master/textures.min.js, which has an MIT license
(function(){var rand,root,__slice=[].slice;root=typeof exports!=="undefined"&&exports!==null?exports:this;rand=function(){return(Math.random().toString(36)+"00000000000000000").replace(/[^a-z]+/g,"").slice(0,5)};root.textures={circles:function(){var background,circles,complement,fill,id,radius,size,stroke,strokeWidth;size=20;background="";radius=2;complement=false;fill="#343434";stroke="#343434";strokeWidth=0;id=rand();circles=function(){var corner,g,_i,_len,_ref,_results;g=this.append("defs").append("pattern").attr("id",id).attr("patternUnits","userSpaceOnUse").attr("width",size).attr("height",size);if(background){g.append("rect").attr("width",size).attr("height",size).attr("fill",background)}g.append("circle").attr("cx",size/2).attr("cy",size/2).attr("r",radius).attr("fill",fill).attr("stroke",stroke).attr("stroke-width",strokeWidth);if(complement){_ref=[[0,0],[0,size],[size,0],[size,size]];_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++){corner=_ref[_i];_results.push(g.append("circle").attr("cx",corner[0]).attr("cy",corner[1]).attr("r",radius).attr("fill",fill).attr("stroke",stroke).attr("stroke-width",strokeWidth))}return _results}};circles.heavier=function(_){if(!arguments.length){radius=radius*2}else{radius=_?radius*2*_:radius*2}return circles};circles.lighter=function(_){if(!arguments.length){radius=radius/2}else{radius=_?radius/(2*_):radius/2}return circles};circles.thinner=function(_){if(!arguments.length){size=size*2}else{size=_?size*2*_:size*2}return circles};circles.thicker=function(_){if(!arguments.length){size=size/2}else{size=_?size/(2*_):size/2}return circles};circles.background=function(_){background=_;return circles};circles.size=function(_){size=_;return circles};circles.complement=function(){complement=true;return circles};circles.radius=function(_){radius=_;return circles};circles.fill=function(_){fill=_;return circles};circles.stroke=function(_){stroke=_;return circles};circles.strokeWidth=function(_){strokeWidth=_;return circles};circles.id=function(_){if(!arguments.length){return id}else{id=_;return circles}};circles.url=function(){return"url(#"+circles.id()+")"};return circles},lines:function(){var background,id,lines,orientation,path,shapeRendering,size,stroke,strokeWidth;size=20;strokeWidth=2;stroke="#343434";id=rand();background="";orientation=["diagonal"];shapeRendering="auto";path=function(orientation){switch(orientation){case"0/8":return function(s){return"M "+s/2+", 0 l 0, "+s}(size);case"vertical":return function(s){return"M "+s/2+", 0 l 0, "+s}(size);case"1/8":return function(s){return"M "+s/4+",0 l "+s/2+","+s+" M "+-s/4+",0 l "+s/2+","+s+" M "+s*3/4+",0 l "+s/2+","+s}(size);case"2/8":return function(s){return"M 0,"+s+" l "+s+","+-s+" M "+-s/4+","+s/4+" l "+s/2+","+-s/2+" M "+3/4*s+","+5/4*s+" l "+s/2+","+-s/2}(size);case"diagonal":return function(s){return"M 0,"+s+" l "+s+","+-s+" M "+-s/4+","+s/4+" l "+s/2+","+-s/2+" M "+3/4*s+","+5/4*s+" l "+s/2+","+-s/2}(size);case"3/8":return function(s){return"M 0,"+3/4*s+" l "+s+","+-s/2+" M 0,"+s/4+" l "+s+","+-s/2+" M 0,"+s*5/4+" l "+s+","+-s/2}(size);case"4/8":return function(s){return"M 0,"+s/2+" l "+s+",0"}(size);case"horizontal":return function(s){return"M 0,"+s/2+" l "+s+",0"}(size);case"5/8":return function(s){return"M 0,"+-s/4+" l "+s+","+s/2+"M 0,"+s/4+" l "+s+","+s/2+"M 0,"+s*3/4+" l "+s+","+s/2}(size);case"6/8":return function(s){return"M 0,0 l "+s+","+s+" M "+-s/4+","+3/4*s+" l "+s/2+","+s/2+" M "+s*3/4+","+-s/4+" l "+s/2+","+s/2}(size);case"7/8":return function(s){return"M "+-s/4+",0 l "+s/2+","+s+" M "+s/4+",0 l "+s/2+","+s+" M "+s*3/4+",0 l "+s/2+","+s}(size);default:return function(s){return"M "+s/2+", 0 l 0, "+s}(size)}};lines=function(){var g,o,_i,_len,_results;g=this.append("defs").append("pattern").attr("id",id).attr("patternUnits","userSpaceOnUse").attr("width",size).attr("height",size);if(background){g.append("rect").attr("width",size).attr("height",size).attr("fill",background)}_results=[];for(_i=0,_len=orientation.length;_i<_len;_i++){o=orientation[_i];_results.push(g.append("path").attr("d",path(o)).attr("stroke-width",strokeWidth).attr("shape-rendering",shapeRendering).attr("stroke",stroke).attr("stroke-linecap","square"))}return _results};lines.background=function(_){background=_;return lines};lines.shapeRendering=function(_){shapeRendering=_;return lines};lines.heavier=function(_){if(!arguments.length){strokeWidth=strokeWidth*2}else{strokeWidth=_?strokeWidth*2*_:strokeWidth*2}return lines};lines.lighter=function(_){if(!arguments.length){strokeWidth=strokeWidth/2}else{strokeWidth=_?strokeWidth/(2*_):strokeWidth/2}return lines};lines.thinner=function(_){if(!arguments.length){size=size*2}else{size=_?size*2*_:size*2}return lines};lines.thicker=function(_){if(!arguments.length){size=size/2}else{size=_?size/(2*_):size/2}return lines};lines.orientation=function(){var args;args=1<=arguments.length?__slice.call(arguments,0):[];orientation=args;return lines};lines.size=function(_){size=_;return lines};lines.stroke=function(_){stroke=_;return lines};lines.strokeWidth=function(_){strokeWidth=_;return lines};lines.id=function(_){if(!arguments.length){return id}else{id=_;return lines}};lines.url=function(){return"url(#"+lines.id()+")"};return lines},paths:function(){var background,d,fill,height,id,paths,shapeRendering,size,stroke,strokeWidth,svgPath,width;size=20;height=1;width=1;strokeWidth=2;stroke="#343434";background="";d="";shapeRendering="auto";fill="transparent";id=void 0;svgPath=function(_){switch(_){case"squares":return function(s){return"M "+s/4+" "+s/4+" l "+s/2+" 0 l 0 "+s/2+" l "+-s/2+" 0 Z"}(size);case"nylon":return function(s){return"M 0 "+s/4+" l "+s/4+" 0 l 0 "+-s/4+" M "+s*3/4+" "+s+" l 0 "+-s/4+" l "+s/4+" 0 M "+s/4+" "+s/2+" l 0 "+s/4+" l "+s/4+" 0 M "+s/2+" "+s/4+" l "+s/4+" 0 l 0 "+s/4}(size);case"waves":return function(s){return"M 0 "+s/2+" c "+s/8+" "+-s/4+" , "+s*3/8+" "+-s/4+" , "+s/2+" 0 c "+s/8+" "+s/4+" , "+s*3/8+" "+s/4+" , "+s/2+" 0 M "+-s/2+" "+s/2+" c "+s/8+" "+s/4+" , "+s*3/8+" "+s/4+" , "+s/2+" 0 M "+s+" "+s/2+" c "+s/8+" "+-s/4+" , "+s*3/8+" "+-s/4+" , "+s/2+" 0"}(size);case"woven":return function(s){return"M "+s/4+","+s/4+"l"+s/2+","+s/2+"M"+s*3/4+","+s/4+"l"+s/2+","+-s/2+"M"+s/4+","+s*3/4+"l"+-s/2+","+s/2+"M"+s*3/4+","+s*5/4+"l"+s/2+","+-s/2+"M"+-s/4+","+s/4+"l"+s/2+","+-s/2}(size);case"crosses":return function(s){return"M "+s/4+","+s/4+"l"+s/2+","+s/2+"M"+s/4+","+s*3/4+"l"+s/2+","+-s/2}(size);case"caps":return function(s){return"M "+s/4+","+s*3/4+"l"+s/4+","+-s/2+"l"+s/4+","+s/2}(size);case"hexagons":return function(s){width=3;height=Math.sqrt(3);return"M "+s+",0 l "+s+",0 l "+s/2+","+s*Math.sqrt(3)/2+" l "+-s/2+","+s*Math.sqrt(3)/2+" l "+-s+",0 l "+-s/2+","+-s*Math.sqrt(3)/2+" Z M 0,"+s*Math.sqrt(3)/2+" l "+s/2+",0 M "+3*s+","+s*Math.sqrt(3)/2+" l "+-s/2+",0"}(size);default:return _(size)}};paths=function(){var g,path;path=svgPath(d);id=rand();g=this.append("defs").append("pattern").attr("id",id).attr("patternUnits","userSpaceOnUse").attr("width",size*width).attr("height",size*height);if(background){g.append("rect").attr("width",size*width).attr("height",size*height).attr("fill",background)}return g.append("path").attr("d",path).attr("fill",fill).attr("stroke-width",strokeWidth).attr("shape-rendering",shapeRendering).attr("stroke",stroke).attr("stroke-linecap","square")};paths.background=function(_){background=_;return paths};paths.shapeRendering=function(_){shapeRendering=_;return paths};paths.heavier=function(_){if(!arguments.length){strokeWidth=strokeWidth*2}else{strokeWidth=_?strokeWidth*2*_:strokeWidth*2}return paths};paths.lighter=function(_){if(!arguments.length){strokeWidth=strokeWidth/2}else{strokeWidth=_?strokeWidth/(2*_):strokeWidth/2}return paths};paths.thinner=function(_){if(!arguments.length){size=size*2}else{size=_?size*2*_:size*2}return paths};paths.thicker=function(_){if(!arguments.length){size=size/2}else{size=_?size/(2*_):size/2}return paths};paths.d=function(_){d=_;return paths};paths.size=function(_){size=_;return paths};paths.stroke=function(_){stroke=_;return paths};paths.strokeWidth=function(_){strokeWidth=_;return paths};paths.id=function(_){if(!arguments.length){return id}else{id=_;return paths}};paths.url=function(){return"url(#"+paths.id()+")"};return paths}}}).call(this);
body {
margin: 30px;
font-family: 'Raleway', sans-serif;
width: 1000px;
}
p {
color: grey;
}
h1 {
color: darkgrey;
}
.hidden {
display: none;
}
.dashedLines {
stroke: darkorange;
stroke-dasharray: 5, 5;
shape-rendering: "crispEdges";
stroke-width: 1
}
.clickToClear {
fill: grey;
text-size: 16px;
}
.clickToClear:hover {
fill: orange;
}
"use strict";
// goals:
// choose: color, width, interpolation
// show code
// so that touchmove is not scrolling
document.body.addEventListener('touchmove', function(event) {
event.preventDefault();
}, false);
// create svg
var width = 1000;
var height = 1000;
var margin = {left: 22, top: 18, right: 0, bottom: 0};
var svg = d3.select('#example')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// set major dimensions
var rectDim = {"x": 10,"y":10, "height": 400, "width": 400}
// drawable dimensions
var drawDim = {"x": 0,"y":0, "height": 440, "width": 440}
////////////////
// click to clear
////////////////
d3.select("#clickToClear").append("svg").attr("width", 250).attr("height", 30)
.append("text")
.on("click", function(d){
d3.select(".userShape").classed('hidden',true);
d3.select("circle").style("fill", "none");
points = [];
texturePoints = [];
})
.attr("class", "clickToClear")
.attr({x: 0, y:20})
.text("Click here to Clear")
///////////////////////////////////
// set up input box
//////////////////////////////////
var dashedLinesVert = []
var dashedLinesHori = []
var bufferDist = 10;
for (var i = 0; i < 3; i++){
dashedLinesVert[i] = {
"x1": rectDim.x + (i + 1) * rectDim.width / 4,
"x2": rectDim.x + (i + 1) * rectDim.width / 4,
"y1": rectDim.y - bufferDist,
"y2": rectDim.y + rectDim.height + bufferDist
}
}
for (var i = 0; i < 3; i++){
dashedLinesHori[i] = {
"y1": rectDim.y + (i + 1) * rectDim.height / 4,
"y2": rectDim.y + (i + 1) * rectDim.height / 4,
"x1": rectDim.x - bufferDist,
"x2": rectDim.x + rectDim.width + bufferDist
}
}
function drawDashed(data, direction){
svg.selectAll("." + direction)
.data(data)
.enter()
.append("line")
.attr("x1", function(d){return d.x1})
.attr("x2", function(d){return d.x2})
.attr("y1", function(d){return d.y1})
.attr("y2", function(d){return d.y2})
.attr("class", "dashedLines " + direction);
}
drawDashed(dashedLinesVert, "vert");
drawDashed(dashedLinesHori, "hori");
var points = [];
var texturePoints = [];
// draw rect
var canvas = svg.append("rect")
.attr(rectDim)
.style({
"fill": "transparent",
"stroke": "darkorange",
"shape-rendering": "crispEdges",
"stroke-width": 2
});
var drawSurface = svg.append("rect")
.attr(drawDim)
.style({
fill: "transparent"
})
// thanks to enjalot for replacing mouseover w/ click & drag functionality
// http://tributary.io/inlet/8c7d398f505adda2cb08
var drag = d3.behavior.drag()
.on("drag", function(d) {
updateArray([d3.event.x, d3.event.y])
})
.on("dragstart", function(d) {
points.push([]);
texturePoints.push([]);
})
drawSurface.call(drag)
////////////////////////
// set up easy options
///////////////////////
var userOptions = function() {
this.basesize = 120
this.baseEdge = this.basesize / 4;
this.thickness = 2;
this.patternColor = '#ffffff';
this.interpolation = 'basis';
};
var text = new userOptions();
/////////////////////
// set up Texture
/////////////////////
var makeItaPath = d3.svg.line()
.x(function(d){return d.x})
.y(function(d){return d.y})
.interpolate("linear");
var t = textures.paths()
.d(function(s) {
return makeItaPath([]);
})
.size(text.basesize)
.strokeWidth(1)
.thicker(2)
.stroke("blue");
svg.call(t);
svg.append("circle")
.attr({"cx": rectDim.x + rectDim.width * 3/2 + 50, "cy": rectDim.height / 2 + rectDim.y, "r": rectDim.height / 2, "stroke": "darkorange"})
.style("fill", t.url());
svg.append("path")
.attr("d", makeItaPath([]))
.attr("class", "userShape")
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-width", 5);
function allPaths(arrayOfPoints) {
//console.log(arrayOfPoints)
var path = ""
for(var i = 0; i < arrayOfPoints.length; i++){
path = path + makeItaPath(arrayOfPoints[i])
}
return path;
}
//////////////////////
// let user draw
//////////////////////
function updateArray(coord){
points[points.length - 1].push({
x: coord[0],
y: coord[1]
})
texturePoints[texturePoints.length - 1].push({
x: (coord[0] - rectDim.x) / (rectDim.width / text.baseEdge),
y: (coord[1] - rectDim.y) / (rectDim.width / text.baseEdge)
})
d3.select(".userShape").attr("d", allPaths(points)).classed("hidden", false);
updateTexture();
}
function updateTexture(){
var t = textures.paths()
.d(function(s) {
return allPaths(texturePoints);
})
.size(text.basesize)
.strokeWidth(2)
.thicker(2)
.stroke("blue");
svg.call(t);
d3.select("circle").style("fill", t.url());
}