├── .babelrc ├── .editorconfig ├── .github └── dependabot.yml ├── .gitignore ├── .travis.yml ├── README.md ├── dist ├── textures.esm.js └── textures.js ├── license ├── package.json ├── rollup.config.js ├── src ├── circles.js ├── lines.js ├── main.js ├── paths.js └── random.js ├── tests ├── circles-test.js ├── jsdom.js ├── lines-test.js └── paths-test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { 5 | "browsers": ["last 2 versions", "safari >= 7"] 6 | }, 7 | "modules": false 8 | }] 9 | ] 10 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 10 8 | reviewers: 9 | - riccardoscalco 10 | assignees: 11 | - riccardoscalco 12 | ignore: 13 | - dependency-name: xo 14 | versions: 15 | - 0.36.1 16 | - 0.37.1 17 | - 0.38.2 18 | - dependency-name: "@babel/preset-env" 19 | versions: 20 | - 7.12.11 21 | - 7.13.8 22 | - dependency-name: tape 23 | versions: 24 | - 5.1.0 25 | - dependency-name: np 26 | versions: 27 | - 7.2.0 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | package-lock.json 4 | yarn-error.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | language: node_js 4 | node_js: stable 5 | 6 | notifications: 7 | - email: false 8 | 9 | script: 10 | - npm run test 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # textures.js 2 | 3 | Textures.js is a JavaScript library for creating SVG patterns. 4 | Made on top of [**d3.js**](https://d3js.org/), it is designed for data visualization. 5 | 6 | Read more on http://riccardoscalco.github.io/textures/. 7 | 8 | ## Install 9 | 10 | ``` 11 | npm install textures 12 | ``` 13 | 14 | ## Usage 15 | 16 | Import `textures.js` from NPM with: 17 | 18 | ```js 19 | import textures from 'textures'; 20 | ``` 21 | 22 | You can also use `textures.js` in your HTML page with a ` 26 | ``` 27 | 28 | or by using the Unpkg CDN network: 29 | 30 | ```html 31 | 32 | ``` 33 | 34 | Then `textures.js` can be used alongside `d3` with: 35 | 36 | ```js 37 | const svg = d3 38 | .select('#example') 39 | .append("svg"); 40 | 41 | const texture = textures 42 | .lines() 43 | .thicker(); 44 | 45 | svg.call(texture); 46 | 47 | svg 48 | .append('circle') 49 | .style('fill', texture.url()); 50 | ``` 51 | 52 | ## License 53 | 54 | MIT 55 | -------------------------------------------------------------------------------- /dist/textures.esm.js: -------------------------------------------------------------------------------- 1 | function random() { 2 | return "".concat(Math.random().toString(36), "00000000000000000").replace(/[^a-z]+/g, '').slice(0, 5); 3 | } 4 | 5 | function circles() { 6 | var size = 20; 7 | var background = ''; 8 | var radius = 2; 9 | var complement = false; 10 | var fill = '#343434'; 11 | var stroke = '#343434'; 12 | var strokeWidth = 0; 13 | var id = random(); 14 | 15 | var $ = function $(selection) { 16 | var group = selection.append('defs').append('pattern').attr('id', id).attr('patternUnits', 'userSpaceOnUse').attr('width', size).attr('height', size); 17 | 18 | if (background) { 19 | group.append('rect').attr('width', size).attr('height', size).attr('fill', background); 20 | } 21 | 22 | group.append('circle').attr('cx', size / 2).attr('cy', size / 2).attr('r', radius).attr('fill', fill).attr('stroke', stroke).attr('stroke-width', strokeWidth); 23 | 24 | if (complement) { 25 | for (var _i = 0, _arr = [[0, 0], [0, size], [size, 0], [size, size]]; _i < _arr.length; _i++) { 26 | var corner = _arr[_i]; 27 | group.append('circle').attr('cx', corner[0]).attr('cy', corner[1]).attr('r', radius).attr('fill', fill).attr('stroke', stroke).attr('stroke-width', strokeWidth); 28 | } 29 | } 30 | }; 31 | 32 | $.heavier = function (_) { 33 | radius *= arguments.length === 0 ? 2 : 2 * _; 34 | return $; 35 | }; 36 | 37 | $.lighter = function (_) { 38 | radius /= arguments.length === 0 ? 2 : 2 * _; 39 | return $; 40 | }; 41 | 42 | $.thinner = function (_) { 43 | size *= arguments.length === 0 ? 2 : 2 * _; 44 | return $; 45 | }; 46 | 47 | $.thicker = function (_) { 48 | size /= arguments.length === 0 ? 2 : 2 * _; 49 | return $; 50 | }; 51 | 52 | $.background = function (_) { 53 | background = _; 54 | return $; 55 | }; 56 | 57 | $.size = function (_) { 58 | size = _; 59 | return $; 60 | }; 61 | 62 | $.complement = function (_) { 63 | complement = arguments.length === 0 ? true : _; 64 | return $; 65 | }; 66 | 67 | $.radius = function (_) { 68 | radius = _; 69 | return $; 70 | }; 71 | 72 | $.fill = function (_) { 73 | fill = _; 74 | return $; 75 | }; 76 | 77 | $.stroke = function (_) { 78 | stroke = _; 79 | return $; 80 | }; 81 | 82 | $.strokeWidth = function (_) { 83 | strokeWidth = _; 84 | return $; 85 | }; 86 | 87 | $.id = function (_) { 88 | if (arguments.length === 0) { 89 | return id; 90 | } 91 | 92 | id = _; 93 | return $; 94 | }; 95 | 96 | $.url = function () { 97 | return "url(#".concat(id, ")"); 98 | }; 99 | 100 | return $; 101 | } 102 | 103 | function _unsupportedIterableToArray(o, minLen) { 104 | if (!o) return; 105 | if (typeof o === "string") return _arrayLikeToArray(o, minLen); 106 | var n = Object.prototype.toString.call(o).slice(8, -1); 107 | if (n === "Object" && o.constructor) n = o.constructor.name; 108 | if (n === "Map" || n === "Set") return Array.from(o); 109 | if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); 110 | } 111 | 112 | function _arrayLikeToArray(arr, len) { 113 | if (len == null || len > arr.length) len = arr.length; 114 | 115 | for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; 116 | 117 | return arr2; 118 | } 119 | 120 | function _createForOfIteratorHelper(o, allowArrayLike) { 121 | var it; 122 | 123 | if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { 124 | if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { 125 | if (it) o = it; 126 | var i = 0; 127 | 128 | var F = function () {}; 129 | 130 | return { 131 | s: F, 132 | n: function () { 133 | if (i >= o.length) return { 134 | done: true 135 | }; 136 | return { 137 | done: false, 138 | value: o[i++] 139 | }; 140 | }, 141 | e: function (e) { 142 | throw e; 143 | }, 144 | f: F 145 | }; 146 | } 147 | 148 | throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 149 | } 150 | 151 | var normalCompletion = true, 152 | didErr = false, 153 | err; 154 | return { 155 | s: function () { 156 | it = o[Symbol.iterator](); 157 | }, 158 | n: function () { 159 | var step = it.next(); 160 | normalCompletion = step.done; 161 | return step; 162 | }, 163 | e: function (e) { 164 | didErr = true; 165 | err = e; 166 | }, 167 | f: function () { 168 | try { 169 | if (!normalCompletion && it.return != null) it.return(); 170 | } finally { 171 | if (didErr) throw err; 172 | } 173 | } 174 | }; 175 | } 176 | 177 | function lines() { 178 | var size = 20; 179 | var stroke = '#343434'; 180 | var strokeWidth = 2; 181 | var background = ''; 182 | var id = random(); 183 | var orientation = ['diagonal']; 184 | var shapeRendering = 'auto'; 185 | 186 | var path = function path(orientation) { 187 | var s = size; 188 | 189 | switch (orientation) { 190 | case '0/8': 191 | case 'vertical': 192 | return "M ".concat(s / 2, ", 0 l 0, ").concat(s); 193 | 194 | case '1/8': 195 | return "M ".concat(-s / 4, ",").concat(s, " l ").concat(s / 2, ",").concat(-s, " M ").concat(s / 4, ",").concat(s, " l ").concat(s / 2, ",").concat(-s, " M ").concat(s * 3 / 4, ",").concat(s, " l ").concat(s / 2, ",").concat(-s); 196 | 197 | case '2/8': 198 | case 'diagonal': 199 | return "M 0,".concat(s, " l ").concat(s, ",").concat(-s, " M ").concat(-s / 4, ",").concat(s / 4, " l ").concat(s / 2, ",").concat(-s / 2, " M ").concat(3 / 4 * s, ",").concat(5 / 4 * s, " l ").concat(s / 2, ",").concat(-s / 2); 200 | 201 | case '3/8': 202 | return "M 0,".concat(3 / 4 * s, " l ").concat(s, ",").concat(-s / 2, " M 0,").concat(s / 4, " l ").concat(s, ",").concat(-s / 2, " M 0,").concat(s * 5 / 4, " l ").concat(s, ",").concat(-s / 2); 203 | 204 | case '4/8': 205 | case 'horizontal': 206 | return "M 0,".concat(s / 2, " l ").concat(s, ",0"); 207 | 208 | case '5/8': 209 | return "M 0,".concat(-s / 4, " l ").concat(s, ",").concat(s / 2, "M 0,").concat(s / 4, " l ").concat(s, ",").concat(s / 2, " M 0,").concat(s * 3 / 4, " l ").concat(s, ",").concat(s / 2); 210 | 211 | case '6/8': 212 | return "M 0,0 l ".concat(s, ",").concat(s, " M ").concat(-s / 4, ",").concat(3 / 4 * s, " l ").concat(s / 2, ",").concat(s / 2, " M ").concat(s * 3 / 4, ",").concat(-s / 4, " l ").concat(s / 2, ",").concat(s / 2); 213 | 214 | case '7/8': 215 | return "M ".concat(-s / 4, ",0 l ").concat(s / 2, ",").concat(s, " M ").concat(s / 4, ",0 l ").concat(s / 2, ",").concat(s, " M ").concat(s * 3 / 4, ",0 l ").concat(s / 2, ",").concat(s); 216 | 217 | default: 218 | return "M ".concat(s / 2, ", 0 l 0, ").concat(s); 219 | } 220 | }; 221 | 222 | var $ = function $(selection) { 223 | var group = selection.append('defs').append('pattern').attr('id', id).attr('patternUnits', 'userSpaceOnUse').attr('width', size).attr('height', size); 224 | 225 | if (background) { 226 | group.append('rect').attr('width', size).attr('height', size).attr('fill', background); 227 | } 228 | 229 | var _iterator = _createForOfIteratorHelper(orientation), 230 | _step; 231 | 232 | try { 233 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 234 | var o = _step.value; 235 | group.append('path').attr('d', path(o)).attr('stroke-width', strokeWidth).attr('shape-rendering', shapeRendering).attr('stroke', stroke).attr('stroke-linecap', 'square'); 236 | } 237 | } catch (err) { 238 | _iterator.e(err); 239 | } finally { 240 | _iterator.f(); 241 | } 242 | }; 243 | 244 | $.heavier = function (_) { 245 | strokeWidth *= arguments.length === 0 ? 2 : 2 * _; 246 | return $; 247 | }; 248 | 249 | $.lighter = function (_) { 250 | strokeWidth /= arguments.length === 0 ? 2 : 2 * _; 251 | return $; 252 | }; 253 | 254 | $.thinner = function (_) { 255 | size *= arguments.length === 0 ? 2 : 2 * _; 256 | return $; 257 | }; 258 | 259 | $.thicker = function (_) { 260 | size /= arguments.length === 0 ? 2 : 2 * _; 261 | return $; 262 | }; 263 | 264 | $.background = function (_) { 265 | background = _; 266 | return $; 267 | }; 268 | 269 | $.size = function (_) { 270 | size = _; 271 | return $; 272 | }; 273 | 274 | $.orientation = function () { 275 | for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { 276 | args[_key] = arguments[_key]; 277 | } 278 | 279 | if (arguments.length === 0) { 280 | return $; 281 | } 282 | 283 | orientation = args; 284 | return $; 285 | }; 286 | 287 | $.shapeRendering = function (_) { 288 | shapeRendering = _; 289 | return $; 290 | }; 291 | 292 | $.stroke = function (_) { 293 | stroke = _; 294 | return $; 295 | }; 296 | 297 | $.strokeWidth = function (_) { 298 | strokeWidth = _; 299 | return $; 300 | }; 301 | 302 | $.id = function (_) { 303 | if (arguments.length === 0) { 304 | return id; 305 | } 306 | 307 | id = _; 308 | return $; 309 | }; 310 | 311 | $.url = function () { 312 | return "url(#".concat(id, ")"); 313 | }; 314 | 315 | return $; 316 | } 317 | 318 | function paths() { 319 | var width = 1; 320 | var height = 1; 321 | var size = 20; 322 | var stroke = '#343434'; 323 | var strokeWidth = 2; 324 | var background = ''; 325 | 326 | var d = function d(s) { 327 | return "M ".concat(s / 4, ",").concat(s * 3 / 4, "l").concat(s / 4, ",").concat(-s / 2, "l").concat(s / 4, ",").concat(s / 2); 328 | }; 329 | 330 | var id = random(); 331 | var fill = 'transparent'; 332 | var shapeRendering = 'auto'; 333 | 334 | var path = function path(_) { 335 | var s = size; 336 | 337 | switch (_) { 338 | case 'squares': 339 | return "M ".concat(s / 4, " ").concat(s / 4, " l ").concat(s / 2, " 0 l 0 ").concat(s / 2, " l ").concat(-s / 2, " 0 Z"); 340 | 341 | case 'nylon': 342 | return "M 0 ".concat(s / 4, " l ").concat(s / 4, " 0 l 0 ").concat(-s / 4, " M ").concat(s * 3 / 4, " ").concat(s, " l 0 ").concat(-s / 4, " l ").concat(s / 4, " 0 M ").concat(s / 4, " ").concat(s / 2, " l 0 ").concat(s / 4, " l ").concat(s / 4, " 0 M ").concat(s / 2, " ").concat(s / 4, " l ").concat(s / 4, " 0 l 0 ").concat(s / 4); 343 | 344 | case 'waves': 345 | return "M 0 ".concat(s / 2, " c ").concat(s / 8, " ").concat(-s / 4, " , ").concat(s * 3 / 8, " ").concat(-s / 4, " , ").concat(s / 2, " 0 c ").concat(s / 8, " ").concat(s / 4, " , ").concat(s * 3 / 8, " ").concat(s / 4, " , ").concat(s / 2, " 0 M ").concat(-s / 2, " ").concat(s / 2, " c ").concat(s / 8, " ").concat(s / 4, " , ").concat(s * 3 / 8, " ").concat(s / 4, " , ").concat(s / 2, " 0 M ").concat(s, " ").concat(s / 2, " c ").concat(s / 8, " ").concat(-s / 4, " , ").concat(s * 3 / 8, " ").concat(-s / 4, " , ").concat(s / 2, " 0"); 346 | 347 | case 'woven': 348 | return "M ".concat(s / 4, ",").concat(s / 4, "l").concat(s / 2, ",").concat(s / 2, "M").concat(s * 3 / 4, ",").concat(s / 4, "l").concat(s / 2, ",").concat(-s / 2, " M").concat(s / 4, ",").concat(s * 3 / 4, "l").concat(-s / 2, ",").concat(s / 2, "M").concat(s * 3 / 4, ",").concat(s * 5 / 4, "l").concat(s / 2, ",").concat(-s / 2, " M").concat(-s / 4, ",").concat(s / 4, "l").concat(s / 2, ",").concat(-s / 2); 349 | 350 | case 'crosses': 351 | return "M ".concat(s / 4, ",").concat(s / 4, "l").concat(s / 2, ",").concat(s / 2, "M").concat(s / 4, ",").concat(s * 3 / 4, "l").concat(s / 2, ",").concat(-s / 2); 352 | 353 | case 'caps': 354 | return "M ".concat(s / 4, ",").concat(s * 3 / 4, "l").concat(s / 4, ",").concat(-s / 2, "l").concat(s / 4, ",").concat(s / 2); 355 | 356 | case 'hexagons': 357 | width = 3; 358 | height = Math.sqrt(3); 359 | return "M ".concat(s, ",0 l ").concat(s, ",0 l ").concat(s / 2, ",").concat(s * Math.sqrt(3) / 2, " l ").concat(-s / 2, ",").concat(s * Math.sqrt(3) / 2, " l ").concat(-s, ",0 l ").concat(-s / 2, ",").concat(-s * Math.sqrt(3) / 2, " Z M 0,").concat(s * Math.sqrt(3) / 2, " l ").concat(s / 2, ",0 M ").concat(3 * s, ",").concat(s * Math.sqrt(3) / 2, " l ").concat(-s / 2, ",0"); 360 | 361 | default: 362 | return _(s); 363 | } 364 | }; 365 | 366 | var $ = function $(selection) { 367 | var p = path(d); 368 | var group = selection.append('defs').append('pattern').attr('id', id).attr('patternUnits', 'userSpaceOnUse').attr('width', size * width).attr('height', size * height); 369 | 370 | if (background) { 371 | group.append('rect').attr('width', size * width).attr('height', size * height).attr('fill', background); 372 | } 373 | 374 | group.append('path').attr('d', p).attr('fill', fill).attr('stroke', stroke).attr('stroke-width', strokeWidth).attr('stroke-linecap', 'square').attr('shape-rendering', shapeRendering); 375 | }; 376 | 377 | $.heavier = function (_) { 378 | strokeWidth *= arguments.length === 0 ? 2 : 2 * _; 379 | return $; 380 | }; 381 | 382 | $.lighter = function (_) { 383 | strokeWidth /= arguments.length === 0 ? 2 : 2 * _; 384 | return $; 385 | }; 386 | 387 | $.thinner = function (_) { 388 | size *= arguments.length === 0 ? 2 : 2 * _; 389 | return $; 390 | }; 391 | 392 | $.thicker = function (_) { 393 | size /= arguments.length === 0 ? 2 : 2 * _; 394 | return $; 395 | }; 396 | 397 | $.background = function (_) { 398 | background = _; 399 | return $; 400 | }; 401 | 402 | $.shapeRendering = function (_) { 403 | shapeRendering = _; 404 | return $; 405 | }; 406 | 407 | $.size = function (_) { 408 | size = _; 409 | return $; 410 | }; 411 | 412 | $.d = function (_) { 413 | d = _; 414 | return $; 415 | }; 416 | 417 | $.fill = function (_) { 418 | fill = _; 419 | return $; 420 | }; 421 | 422 | $.stroke = function (_) { 423 | stroke = _; 424 | return $; 425 | }; 426 | 427 | $.strokeWidth = function (_) { 428 | strokeWidth = _; 429 | return $; 430 | }; 431 | 432 | $.id = function (_) { 433 | if (arguments.length === 0) { 434 | return id; 435 | } 436 | 437 | id = _; 438 | return $; 439 | }; 440 | 441 | $.url = function () { 442 | return "url(#".concat(id, ")"); 443 | }; 444 | 445 | return $; 446 | } 447 | 448 | /* eslint import/no-anonymous-default-export: [2, {"allowObject": true}] */ 449 | 450 | var main = { 451 | circles: circles, 452 | lines: lines, 453 | paths: paths 454 | }; 455 | 456 | export default main; 457 | -------------------------------------------------------------------------------- /dist/textures.js: -------------------------------------------------------------------------------- 1 | !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).textures=n()}(this,function(){"use strict";function t(){return"".concat(Math.random().toString(36),"00000000000000000").replace(/[^a-z]+/g,"").slice(0,5)}function i(t,n){(null==n||n>t.length)&&(n=t.length);for(var c=0,a=new Array(n);c=t.length?{done:!0}:{done:!1,value:t[a++]}},e:function(t){throw t},f:r}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,e=!0,u=!1;return{s:function(){c=t[Symbol.iterator]()},n:function(){var t=c.next();return e=t.done,t},e:function(t){u=!0,o=t},f:function(){try{e||null==c.return||c.return()}finally{if(u)throw o}}}}return{circles:function(){function n(t){var n=t.append("defs").append("pattern").attr("id",h).attr("patternUnits","userSpaceOnUse").attr("width",o).attr("height",o);if(e&&n.append("rect").attr("width",o).attr("height",o).attr("fill",e),n.append("circle").attr("cx",o/2).attr("cy",o/2).attr("r",u).attr("fill",l).attr("stroke",f).attr("stroke-width",s),i)for(var c=0,a=[[0,0],[0,o],[o,0],[o,o]];c (http://riccardoscalco.github.io/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textures", 3 | "version": "1.2.3", 4 | "description": "SVG patterns for Data Visualization", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/riccardoscalco/textures.git" 8 | }, 9 | "keywords": [ 10 | "svg pattern", 11 | "patterns", 12 | "textures", 13 | "d3", 14 | "svg", 15 | "dataviz", 16 | "data visualisation", 17 | "maps", 18 | "visual" 19 | ], 20 | "author": { 21 | "name": "Riccardo Scalco", 22 | "email": "riccardoscalco@gmail.com", 23 | "url": "http://riccardoscalco.github.io/" 24 | }, 25 | "main": "dist/textures.js", 26 | "module": "dist/textures.esm.js", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/riccardoscalco/textures/issues" 30 | }, 31 | "homepage": "https://github.com/riccardoscalco/textures", 32 | "dependencies": {}, 33 | "devDependencies": { 34 | "@babel/core": "^7.5.5", 35 | "@babel/preset-env": "^7.5.5", 36 | "d3-selection": "^2.0.0", 37 | "faucet": "^0.0.1", 38 | "jsdom": "^19.0.0", 39 | "np": "^7.4.0", 40 | "rollup": "^1.18.0", 41 | "rollup-plugin-babel": "^4.3.3", 42 | "rollup-plugin-commonjs": "^10.0.2", 43 | "rollup-plugin-node-resolve": "^5.2.0", 44 | "rollup-plugin-uglify": "^6.0.2", 45 | "tape": "^5.0.1", 46 | "xo": "^0.39.1" 47 | }, 48 | "scripts": { 49 | "build": "rollup -c", 50 | "dev": "rollup -c -w", 51 | "test": "xo src/**/*.js && xo tests/**/*.js && tape tests/**/*.js | faucet", 52 | "pretest": "rollup -c", 53 | "pub": "np" 54 | }, 55 | "files": [ 56 | "dist" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // Import uglify from 'rollup-plugin-uglify'; 2 | // import babel from 'rollup-plugin-babel'; 3 | // import pkg from './package.json'; 4 | 5 | // export default [ 6 | // { 7 | // input: 'src/main.js', 8 | // plugins: [ 9 | // babel({ 10 | // exclude: ['node_modules/**'] 11 | // }), 12 | // uglify() 13 | // ], 14 | // output: {file: pkg.main, format: 'umd'}, 15 | // name: 'textures' 16 | // }, 17 | // { 18 | // input: 'src/main.js', 19 | // output: {file: pkg.module, format: 'es'}, 20 | // plugins: [ 21 | // babel({ 22 | // exclude: ['node_modules/**'] 23 | // }) 24 | // ], 25 | // } 26 | // ]; 27 | 28 | import {uglify} from 'rollup-plugin-uglify'; 29 | import babel from 'rollup-plugin-babel'; 30 | import resolve from 'rollup-plugin-node-resolve'; 31 | import commonjs from 'rollup-plugin-commonjs'; 32 | import pkg from './package.json'; 33 | 34 | export default [ 35 | { 36 | input: 'src/main.js', 37 | output: { 38 | name: 'textures', 39 | file: pkg.main, 40 | format: 'umd' 41 | }, 42 | plugins: [ 43 | babel({ 44 | exclude: ['node_modules/**'] 45 | }), 46 | uglify(), 47 | resolve(), 48 | commonjs() 49 | ] 50 | }, 51 | { 52 | input: 'src/main.js', 53 | external: ['ms'], 54 | output: [ 55 | {file: pkg.module, format: 'es'} 56 | ], 57 | plugins: [ 58 | babel({ 59 | exclude: ['node_modules/**'] 60 | }) 61 | ] 62 | } 63 | ]; 64 | -------------------------------------------------------------------------------- /src/circles.js: -------------------------------------------------------------------------------- 1 | import rand from './random.js'; 2 | 3 | export default function circles() { 4 | let size = 20; 5 | let background = ''; 6 | let radius = 2; 7 | let complement = false; 8 | let fill = '#343434'; 9 | let stroke = '#343434'; 10 | let strokeWidth = 0; 11 | let id = rand(); 12 | 13 | const $ = selection => { 14 | const group = selection 15 | .append('defs') 16 | .append('pattern') 17 | .attr('id', id) 18 | .attr('patternUnits', 'userSpaceOnUse') 19 | .attr('width', size) 20 | .attr('height', size); 21 | 22 | if (background) { 23 | group 24 | .append('rect') 25 | .attr('width', size) 26 | .attr('height', size) 27 | .attr('fill', background); 28 | } 29 | 30 | group 31 | .append('circle') 32 | .attr('cx', size / 2) 33 | .attr('cy', size / 2) 34 | .attr('r', radius) 35 | .attr('fill', fill) 36 | .attr('stroke', stroke) 37 | .attr('stroke-width', strokeWidth); 38 | 39 | if (complement) { 40 | for (const corner of [[0, 0], [0, size], [size, 0], [size, size]]) { 41 | group 42 | .append('circle') 43 | .attr('cx', corner[0]) 44 | .attr('cy', corner[1]) 45 | .attr('r', radius) 46 | .attr('fill', fill) 47 | .attr('stroke', stroke) 48 | .attr('stroke-width', strokeWidth); 49 | } 50 | } 51 | }; 52 | 53 | $.heavier = function (_) { 54 | radius *= arguments.length === 0 ? 2 : 2 * _; 55 | 56 | return $; 57 | }; 58 | 59 | $.lighter = function (_) { 60 | radius /= arguments.length === 0 ? 2 : 2 * _; 61 | 62 | return $; 63 | }; 64 | 65 | $.thinner = function (_) { 66 | size *= arguments.length === 0 ? 2 : 2 * _; 67 | 68 | return $; 69 | }; 70 | 71 | $.thicker = function (_) { 72 | size /= arguments.length === 0 ? 2 : 2 * _; 73 | 74 | return $; 75 | }; 76 | 77 | $.background = function (_) { 78 | background = _; 79 | return $; 80 | }; 81 | 82 | $.size = function (_) { 83 | size = _; 84 | return $; 85 | }; 86 | 87 | $.complement = function (_) { 88 | complement = arguments.length === 0 ? true : _; 89 | 90 | return $; 91 | }; 92 | 93 | $.radius = function (_) { 94 | radius = _; 95 | return $; 96 | }; 97 | 98 | $.fill = function (_) { 99 | fill = _; 100 | return $; 101 | }; 102 | 103 | $.stroke = function (_) { 104 | stroke = _; 105 | return $; 106 | }; 107 | 108 | $.strokeWidth = function (_) { 109 | strokeWidth = _; 110 | return $; 111 | }; 112 | 113 | $.id = function (_) { 114 | if (arguments.length === 0) { 115 | return id; 116 | } 117 | 118 | id = _; 119 | return $; 120 | }; 121 | 122 | $.url = function () { 123 | return `url(#${id})`; 124 | }; 125 | 126 | return $; 127 | } 128 | -------------------------------------------------------------------------------- /src/lines.js: -------------------------------------------------------------------------------- 1 | import rand from './random.js'; 2 | 3 | export default function lines() { 4 | let size = 20; 5 | let stroke = '#343434'; 6 | let strokeWidth = 2; 7 | let background = ''; 8 | let id = rand(); 9 | let orientation = ['diagonal']; 10 | let shapeRendering = 'auto'; 11 | 12 | const path = orientation => { 13 | const s = size; 14 | switch (orientation) { 15 | case '0/8': 16 | case 'vertical': 17 | return `M ${s / 2}, 0 l 0, ${s}`; 18 | case '1/8': 19 | return `M ${-s / 4},${s} l ${s / 2},${-s} M ${s / 4},${s} l ${s / 2},${-s} M ${s * 3 / 4},${s} l ${s / 2},${-s}`; 20 | case '2/8': 21 | case 'diagonal': 22 | 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}`; 23 | case '3/8': 24 | 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}`; 25 | case '4/8': 26 | case 'horizontal': 27 | return `M 0,${s / 2} l ${s},0`; 28 | case '5/8': 29 | 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}`; 30 | case '6/8': 31 | 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}`; 32 | case '7/8': 33 | 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}`; 34 | default: 35 | return `M ${s / 2}, 0 l 0, ${s}`; 36 | } 37 | }; 38 | 39 | const $ = selection => { 40 | const group = selection 41 | .append('defs') 42 | .append('pattern') 43 | .attr('id', id) 44 | .attr('patternUnits', 'userSpaceOnUse') 45 | .attr('width', size) 46 | .attr('height', size); 47 | 48 | if (background) { 49 | group 50 | .append('rect') 51 | .attr('width', size) 52 | .attr('height', size) 53 | .attr('fill', background); 54 | } 55 | 56 | for (const o of orientation) { 57 | group 58 | .append('path') 59 | .attr('d', path(o)) 60 | .attr('stroke-width', strokeWidth) 61 | .attr('shape-rendering', shapeRendering) 62 | .attr('stroke', stroke) 63 | .attr('stroke-linecap', 'square'); 64 | } 65 | }; 66 | 67 | $.heavier = function (_) { 68 | strokeWidth *= arguments.length === 0 ? 2 : 2 * _; 69 | 70 | return $; 71 | }; 72 | 73 | $.lighter = function (_) { 74 | strokeWidth /= arguments.length === 0 ? 2 : 2 * _; 75 | 76 | return $; 77 | }; 78 | 79 | $.thinner = function (_) { 80 | size *= arguments.length === 0 ? 2 : 2 * _; 81 | 82 | return $; 83 | }; 84 | 85 | $.thicker = function (_) { 86 | size /= arguments.length === 0 ? 2 : 2 * _; 87 | 88 | return $; 89 | }; 90 | 91 | $.background = function (_) { 92 | background = _; 93 | return $; 94 | }; 95 | 96 | $.size = function (_) { 97 | size = _; 98 | return $; 99 | }; 100 | 101 | $.orientation = function (...args) { 102 | if (arguments.length === 0) { 103 | return $; 104 | } 105 | 106 | orientation = args; 107 | return $; 108 | }; 109 | 110 | $.shapeRendering = function (_) { 111 | shapeRendering = _; 112 | return $; 113 | }; 114 | 115 | $.stroke = function (_) { 116 | stroke = _; 117 | return $; 118 | }; 119 | 120 | $.strokeWidth = function (_) { 121 | strokeWidth = _; 122 | return $; 123 | }; 124 | 125 | $.id = function (_) { 126 | if (arguments.length === 0) { 127 | return id; 128 | } 129 | 130 | id = _; 131 | return $; 132 | }; 133 | 134 | $.url = function () { 135 | return `url(#${id})`; 136 | }; 137 | 138 | return $; 139 | } 140 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import circles from './circles.js'; 2 | import lines from './lines.js'; 3 | import paths from './paths.js'; 4 | 5 | /* eslint import/no-anonymous-default-export: [2, {"allowObject": true}] */ 6 | export default { 7 | circles, 8 | lines, 9 | paths 10 | }; 11 | -------------------------------------------------------------------------------- /src/paths.js: -------------------------------------------------------------------------------- 1 | import rand from './random.js'; 2 | 3 | export default function paths() { 4 | let width = 1; 5 | let height = 1; 6 | let size = 20; 7 | let stroke = '#343434'; 8 | let strokeWidth = 2; 9 | let background = ''; 10 | let d = s => `M ${s / 4},${s * 3 / 4}l${s / 4},${-s / 2}l${s / 4},${s / 2}`; 11 | let id = rand(); 12 | let fill = 'transparent'; 13 | let shapeRendering = 'auto'; 14 | 15 | const path = _ => { 16 | const s = size; 17 | switch (_) { 18 | case 'squares': 19 | return `M ${s / 4} ${s / 4} l ${s / 2} 0 l 0 ${s / 2} l ${-s / 2} 0 Z`; 20 | case 'nylon': 21 | 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}`; 22 | case 'waves': 23 | 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`; 24 | case 'woven': 25 | 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}`; 26 | case 'crosses': 27 | return `M ${s / 4},${s / 4}l${s / 2},${s / 2}M${s / 4},${s * 3 / 4}l${s / 2},${-s / 2}`; 28 | case 'caps': 29 | return `M ${s / 4},${s * 3 / 4}l${s / 4},${-s / 2}l${s / 4},${s / 2}`; 30 | case 'hexagons': 31 | width = 3; 32 | height = Math.sqrt(3); 33 | 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`; 34 | default: 35 | return _(s); 36 | } 37 | }; 38 | 39 | const $ = selection => { 40 | const p = path(d); 41 | const group = selection 42 | .append('defs') 43 | .append('pattern') 44 | .attr('id', id) 45 | .attr('patternUnits', 'userSpaceOnUse') 46 | .attr('width', size * width) 47 | .attr('height', size * height); 48 | 49 | if (background) { 50 | group 51 | .append('rect') 52 | .attr('width', size * width) 53 | .attr('height', size * height) 54 | .attr('fill', background); 55 | } 56 | 57 | group 58 | .append('path') 59 | .attr('d', p) 60 | .attr('fill', fill) 61 | .attr('stroke', stroke) 62 | .attr('stroke-width', strokeWidth) 63 | .attr('stroke-linecap', 'square') 64 | .attr('shape-rendering', shapeRendering); 65 | }; 66 | 67 | $.heavier = function (_) { 68 | strokeWidth *= arguments.length === 0 ? 2 : 2 * _; 69 | 70 | return $; 71 | }; 72 | 73 | $.lighter = function (_) { 74 | strokeWidth /= arguments.length === 0 ? 2 : 2 * _; 75 | 76 | return $; 77 | }; 78 | 79 | $.thinner = function (_) { 80 | size *= arguments.length === 0 ? 2 : 2 * _; 81 | 82 | return $; 83 | }; 84 | 85 | $.thicker = function (_) { 86 | size /= arguments.length === 0 ? 2 : 2 * _; 87 | 88 | return $; 89 | }; 90 | 91 | $.background = function (_) { 92 | background = _; 93 | return $; 94 | }; 95 | 96 | $.shapeRendering = function (_) { 97 | shapeRendering = _; 98 | return $; 99 | }; 100 | 101 | $.size = function (_) { 102 | size = _; 103 | return $; 104 | }; 105 | 106 | $.d = function (_) { 107 | d = _; 108 | return $; 109 | }; 110 | 111 | $.fill = function (_) { 112 | fill = _; 113 | return $; 114 | }; 115 | 116 | $.stroke = function (_) { 117 | stroke = _; 118 | return $; 119 | }; 120 | 121 | $.strokeWidth = function (_) { 122 | strokeWidth = _; 123 | return $; 124 | }; 125 | 126 | $.id = function (_) { 127 | if (arguments.length === 0) { 128 | return id; 129 | } 130 | 131 | id = _; 132 | return $; 133 | }; 134 | 135 | $.url = function () { 136 | return `url(#${id})`; 137 | }; 138 | 139 | return $; 140 | } 141 | -------------------------------------------------------------------------------- /src/random.js: -------------------------------------------------------------------------------- 1 | export default function random() { 2 | return `${Math.random().toString(36)}00000000000000000` 3 | .replace(/[^a-z]+/g, '') 4 | .slice(0, 5); 5 | } 6 | -------------------------------------------------------------------------------- /tests/circles-test.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape'); 2 | const d3 = require('d3-selection'); 3 | const textures = require('../dist/textures'); 4 | const jsdom = require('./jsdom'); 5 | 6 | const template = () => { 7 | const texture = textures.circles(); 8 | const document = jsdom(''); 9 | const svg = d3.select(document).select('svg'); 10 | return {svg, texture}; 11 | }; 12 | 13 | tape( 14 | 'svg.call(texture) append a node ', 15 | t => { 16 | const {svg, texture} = template(); 17 | svg.call(texture); 18 | t.ok(!svg.select('defs').empty()); 19 | t.end(); 20 | } 21 | ); 22 | 23 | tape( 24 | 'svg.call(texture) append a node ', 25 | t => { 26 | const {svg, texture} = template(); 27 | svg.call(texture); 28 | t.ok(!svg.select('defs').select('pattern').empty()); 29 | t.end(); 30 | } 31 | ); 32 | 33 | tape( 34 | 'svg.call(texture) append a node with the id attribute', 35 | t => { 36 | const {svg, texture} = template(); 37 | svg.call(texture); 38 | t.notEqual(svg.select('defs').select('pattern').attr('id'), ''); 39 | t.end(); 40 | } 41 | ); 42 | 43 | tape( 44 | 'svg.call(texture) append a node with the patternUnits attribute set to userSpaceOnUse', 45 | t => { 46 | const {svg, texture} = template(); 47 | svg.call(texture); 48 | t.equal(svg.select('defs').select('pattern').attr('patternUnits'), 'userSpaceOnUse'); 49 | t.end(); 50 | } 51 | ); 52 | 53 | tape( 54 | 'svg.call(texture) append a node with the attributes width and height set to 20', 55 | t => { 56 | const {svg, texture} = template(); 57 | svg.call(texture); 58 | t.equal(svg.select('defs').select('pattern').attr('width'), '20'); 59 | t.equal(svg.select('defs').select('pattern').attr('height'), '20'); 60 | t.end(); 61 | } 62 | ); 63 | 64 | tape( 65 | 'texture.circles() append a node with some default attributes', 66 | t => { 67 | const {svg, texture} = template(); 68 | svg.call(texture); 69 | const circle = svg.select('defs').select('pattern').select('circle'); 70 | t.equal(circle.attr('fill'), '#343434'); 71 | t.equal(circle.attr('stroke'), '#343434'); 72 | t.equal(circle.attr('strokeWidth'), null); 73 | t.equal(circle.attr('r'), '2'); 74 | t.equal(circle.attr('cx'), '10'); 75 | t.equal(circle.attr('cy'), '10'); 76 | t.end(); 77 | } 78 | ); 79 | 80 | tape( 81 | 'texture.heavier() doubles the radius', 82 | t => { 83 | const {svg, texture} = template(); 84 | texture.heavier(); 85 | svg.call(texture); 86 | t.equal(svg.select('defs').select('pattern').select('circle').attr('r'), '4'); 87 | t.end(); 88 | } 89 | ); 90 | 91 | tape( 92 | 'texture.heavier(3) changes radius to radius * 2 * 3', 93 | t => { 94 | const {svg, texture} = template(); 95 | texture.heavier(3); 96 | svg.call(texture); 97 | t.equal(svg.select('defs').select('pattern').select('circle').attr('r'), '12'); 98 | t.end(); 99 | } 100 | ); 101 | 102 | tape( 103 | 'texture.lighter() divides the radius by 2', 104 | t => { 105 | const {svg, texture} = template(); 106 | texture.lighter(); 107 | svg.call(texture); 108 | t.equal(svg.select('defs').select('pattern').select('circle').attr('r'), '1'); 109 | t.end(); 110 | } 111 | ); 112 | 113 | tape( 114 | 'texture.lighter(2) changes radius to radius / (2 * 2)', 115 | t => { 116 | const {svg, texture} = template(); 117 | texture.lighter(2); 118 | svg.call(texture); 119 | t.equal(svg.select('defs').select('pattern').select('circle').attr('r'), '0.5'); 120 | t.end(); 121 | } 122 | ); 123 | 124 | tape( 125 | 'texture.thinner() doubles the size', 126 | t => { 127 | const {svg, texture} = template(); 128 | texture.thinner(); 129 | svg.call(texture); 130 | t.equal(svg.select('defs').select('pattern').attr('width'), '40'); 131 | t.end(); 132 | } 133 | ); 134 | 135 | tape( 136 | 'texture.thinner(3) changes size to size * 2 * 3', 137 | t => { 138 | const {svg, texture} = template(); 139 | texture.thinner(3); 140 | svg.call(texture); 141 | t.equal(svg.select('defs').select('pattern').attr('width'), '120'); 142 | t.end(); 143 | } 144 | ); 145 | 146 | tape( 147 | 'texture.thicker() divides the size by 2', 148 | t => { 149 | const {svg, texture} = template(); 150 | texture.thicker(); 151 | svg.call(texture); 152 | t.equal(svg.select('defs').select('pattern').attr('width'), '10'); 153 | t.end(); 154 | } 155 | ); 156 | 157 | tape( 158 | 'texture.thicker(2) changes size to size / (2 * 2)', 159 | t => { 160 | const {svg, texture} = template(); 161 | texture.thicker(2); 162 | svg.call(texture); 163 | t.equal(svg.select('defs').select('pattern').attr('width'), '5'); 164 | t.end(); 165 | } 166 | ); 167 | 168 | tape( 169 | 'texture.background("firebrick") append a node with attribute fill equal to "firebrick"', 170 | t => { 171 | const {svg, texture} = template(); 172 | texture.background('firebrick'); 173 | svg.call(texture); 174 | t.equal(svg.select('defs').select('pattern').select('rect').attr('fill'), 'firebrick'); 175 | t.end(); 176 | } 177 | ); 178 | 179 | tape( 180 | 'texture.size(40) set size to 40', 181 | t => { 182 | const {svg, texture} = template(); 183 | texture.size(40); 184 | svg.call(texture); 185 | const circle = svg.select('defs').select('pattern').select('circle'); 186 | t.equal(circle.attr('cx'), '20'); 187 | t.equal(circle.attr('cy'), '20'); 188 | t.end(); 189 | } 190 | ); 191 | 192 | tape( 193 | 'texture.complement() append 4 more nodes ', 194 | t => { 195 | const {svg, texture} = template(); 196 | texture.complement(); 197 | svg.call(texture); 198 | t.equal(svg.select('defs').select('pattern').selectAll('circle').size(), 5); 199 | t.end(); 200 | } 201 | ); 202 | 203 | tape( 204 | 'texture.radius(5) set radius to 5', 205 | t => { 206 | const {svg, texture} = template(); 207 | texture.radius(5); 208 | svg.call(texture); 209 | const circle = svg.select('defs').select('pattern').select('circle'); 210 | t.equal(circle.attr('r'), '5'); 211 | t.end(); 212 | } 213 | ); 214 | 215 | tape( 216 | 'texture.fill("red") set fill to red', 217 | t => { 218 | const {svg, texture} = template(); 219 | texture.fill('red'); 220 | svg.call(texture); 221 | const circle = svg.select('defs').select('pattern').select('circle'); 222 | t.equal(circle.attr('fill'), 'red'); 223 | t.end(); 224 | } 225 | ); 226 | 227 | tape( 228 | 'texture.stroke("red") set stroke to red', 229 | t => { 230 | const {svg, texture} = template(); 231 | texture.stroke('red'); 232 | svg.call(texture); 233 | const circle = svg.select('defs').select('pattern').select('circle'); 234 | t.equal(circle.attr('stroke'), 'red'); 235 | t.end(); 236 | } 237 | ); 238 | 239 | tape( 240 | 'texture.strokeWidth(2) set stroke-width to 2', 241 | t => { 242 | const {svg, texture} = template(); 243 | texture.strokeWidth(2); 244 | svg.call(texture); 245 | const circle = svg.select('defs').select('pattern').select('circle'); 246 | t.equal(circle.attr('stroke-width'), '2'); 247 | t.end(); 248 | } 249 | ); 250 | 251 | tape( 252 | 'texture.id("xyz") set pattern id to xyz', 253 | t => { 254 | const {svg, texture} = template(); 255 | texture.id('xyz'); 256 | svg.call(texture); 257 | t.equal(svg.select('defs').select('pattern').attr('id'), 'xyz'); 258 | t.end(); 259 | } 260 | ); 261 | 262 | tape( 263 | 'texture.url() returns a string with the pattern id', 264 | t => { 265 | const {svg, texture} = template(); 266 | texture.id('xyz'); 267 | svg.call(texture); 268 | t.equal(texture.url(), 'url(#xyz)'); 269 | t.end(); 270 | } 271 | ); 272 | 273 | tape( 274 | 'texture.size(30).radius(5) set size to 30 and radius to 5', 275 | t => { 276 | const {svg, texture} = template(); 277 | texture.size(30).radius(5); 278 | svg.call(texture); 279 | const circle = svg.select('defs').select('pattern').select('circle'); 280 | t.equal(circle.attr('r'), '5'); 281 | t.equal(circle.attr('cx'), '15'); 282 | t.end(); 283 | } 284 | ); 285 | -------------------------------------------------------------------------------- /tests/jsdom.js: -------------------------------------------------------------------------------- 1 | const jsdom = require('jsdom'); 2 | 3 | module.exports = function (html) { 4 | return (new jsdom.JSDOM(html)).window.document; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/lines-test.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape'); 2 | const d3 = require('d3-selection'); 3 | const textures = require('../dist/textures'); 4 | const jsdom = require('./jsdom'); 5 | 6 | const template = () => { 7 | const texture = textures.lines(); 8 | const document = jsdom(''); 9 | const svg = d3.select(document).select('svg'); 10 | return {svg, texture}; 11 | }; 12 | 13 | tape( 14 | 'svg.call(texture) append a node ', 15 | t => { 16 | const {svg, texture} = template(); 17 | svg.call(texture); 18 | t.ok(!svg.select('defs').empty()); 19 | t.end(); 20 | } 21 | ); 22 | 23 | tape( 24 | 'svg.call(texture) append a node ', 25 | t => { 26 | const {svg, texture} = template(); 27 | svg.call(texture); 28 | t.ok(!svg.select('defs').select('pattern').empty()); 29 | t.end(); 30 | } 31 | ); 32 | 33 | tape( 34 | 'svg.call(texture) append a node with the id attribute', 35 | t => { 36 | const {svg, texture} = template(); 37 | svg.call(texture); 38 | t.notEqual(svg.select('defs').select('pattern').attr('id'), ''); 39 | t.end(); 40 | } 41 | ); 42 | 43 | tape( 44 | 'svg.call(texture) append a node with the patternUnits attribute set to userSpaceOnUse', 45 | t => { 46 | const {svg, texture} = template(); 47 | svg.call(texture); 48 | t.equal(svg.select('defs').select('pattern').attr('patternUnits'), 'userSpaceOnUse'); 49 | t.end(); 50 | } 51 | ); 52 | 53 | tape( 54 | 'svg.call(texture) append a node with the attributes width and height set to 20', 55 | t => { 56 | const {svg, texture} = template(); 57 | svg.call(texture); 58 | t.equal(svg.select('defs').select('pattern').attr('width'), '20'); 59 | t.equal(svg.select('defs').select('pattern').attr('height'), '20'); 60 | t.end(); 61 | } 62 | ); 63 | 64 | tape( 65 | 'texture.lines() append a node with some default attributes', 66 | t => { 67 | const {svg, texture} = template(); 68 | svg.call(texture); 69 | const path = svg.select('defs').select('pattern').select('path'); 70 | t.equal(path.attr('stroke-width'), '2'); 71 | t.equal(path.attr('stroke'), '#343434'); 72 | t.equal(path.attr('shape-rendering'), 'auto'); 73 | t.equal(path.attr('stroke-linecap'), 'square'); 74 | t.equal(path.attr('d'), 'M 0,20 l 20,-20 M -5,5 l 10,-10 M 15,25 l 10,-10'); 75 | t.end(); 76 | } 77 | ); 78 | 79 | tape( 80 | 'texture.heavier() doubles the strokeWidth', 81 | t => { 82 | const {svg, texture} = template(); 83 | texture.heavier(); 84 | svg.call(texture); 85 | t.equal(svg.select('defs').select('pattern').select('path').attr('stroke-width'), '4'); 86 | t.end(); 87 | } 88 | ); 89 | 90 | tape( 91 | 'texture.heavier(3) changes strokeWidth to strokeWidth * 2 * 3', 92 | t => { 93 | const {svg, texture} = template(); 94 | texture.heavier(3); 95 | svg.call(texture); 96 | t.equal(svg.select('defs').select('pattern').select('path').attr('stroke-width'), '12'); 97 | t.end(); 98 | } 99 | ); 100 | 101 | tape( 102 | 'texture.lighter() divides the strokeWidth by 2', 103 | t => { 104 | const {svg, texture} = template(); 105 | texture.lighter(); 106 | svg.call(texture); 107 | t.equal(svg.select('defs').select('pattern').select('path').attr('stroke-width'), '1'); 108 | t.end(); 109 | } 110 | ); 111 | 112 | tape( 113 | 'texture.lighter(2) changes radius to strokeWidth / (2 * 2)', 114 | t => { 115 | const {svg, texture} = template(); 116 | texture.lighter(2); 117 | svg.call(texture); 118 | t.equal(svg.select('defs').select('pattern').select('path').attr('stroke-width'), '0.5'); 119 | t.end(); 120 | } 121 | ); 122 | 123 | tape( 124 | 'texture.thinner() doubles the size', 125 | t => { 126 | const {svg, texture} = template(); 127 | texture.thinner(); 128 | svg.call(texture); 129 | t.equal(svg.select('defs').select('pattern').attr('width'), '40'); 130 | t.end(); 131 | } 132 | ); 133 | 134 | tape( 135 | 'texture.thinner(3) changes size to size * 2 * 3', 136 | t => { 137 | const {svg, texture} = template(); 138 | texture.thinner(3); 139 | svg.call(texture); 140 | t.equal(svg.select('defs').select('pattern').attr('width'), '120'); 141 | t.end(); 142 | } 143 | ); 144 | 145 | tape( 146 | 'texture.thicker() divides the size by 2', 147 | t => { 148 | const {svg, texture} = template(); 149 | texture.thicker(); 150 | svg.call(texture); 151 | t.equal(svg.select('defs').select('pattern').attr('width'), '10'); 152 | t.end(); 153 | } 154 | ); 155 | 156 | tape( 157 | 'texture.thicker(2) changes size to size / (2 * 2)', 158 | t => { 159 | const {svg, texture} = template(); 160 | texture.thicker(2); 161 | svg.call(texture); 162 | t.equal(svg.select('defs').select('pattern').attr('width'), '5'); 163 | t.end(); 164 | } 165 | ); 166 | 167 | tape( 168 | 'texture.background("firebrick") append a node with attribute fill equal to "firebrick"', 169 | t => { 170 | const {svg, texture} = template(); 171 | texture.background('firebrick'); 172 | svg.call(texture); 173 | t.equal(svg.select('defs').select('pattern').select('rect').attr('fill'), 'firebrick'); 174 | t.end(); 175 | } 176 | ); 177 | 178 | tape( 179 | 'texture.size(40) set size to 40', 180 | t => { 181 | const {svg, texture} = template(); 182 | texture.size(40); 183 | svg.call(texture); 184 | const path = svg.select('defs').select('pattern').select('path'); 185 | t.equal(path.attr('d'), 'M 0,40 l 40,-40 M -10,10 l 20,-20 M 30,50 l 20,-20'); 186 | t.end(); 187 | } 188 | ); 189 | 190 | tape( 191 | 'texture.shapeRendering("crispEdges") set shape-rendering to crispEdges', 192 | t => { 193 | const {svg, texture} = template(); 194 | texture.shapeRendering('crispEdges'); 195 | svg.call(texture); 196 | const path = svg.select('defs').select('pattern').select('path'); 197 | t.equal(path.attr('shape-rendering'), 'crispEdges'); 198 | t.end(); 199 | } 200 | ); 201 | 202 | tape( 203 | 'texture.stroke("red") set stroke to red', 204 | t => { 205 | const {svg, texture} = template(); 206 | texture.stroke('red'); 207 | svg.call(texture); 208 | const path = svg.select('defs').select('pattern').select('path'); 209 | t.equal(path.attr('stroke'), 'red'); 210 | t.end(); 211 | } 212 | ); 213 | 214 | tape( 215 | 'texture.strokeWidth(4) set stroke-width to 4', 216 | t => { 217 | const {svg, texture} = template(); 218 | texture.strokeWidth(4); 219 | svg.call(texture); 220 | const path = svg.select('defs').select('pattern').select('path'); 221 | t.equal(path.attr('stroke-width'), '4'); 222 | t.end(); 223 | } 224 | ); 225 | 226 | tape( 227 | 'texture.id("xyz") set pattern id to xyz', 228 | t => { 229 | const {svg, texture} = template(); 230 | texture.id('xyz'); 231 | svg.call(texture); 232 | t.equal(svg.select('defs').select('pattern').attr('id'), 'xyz'); 233 | t.end(); 234 | } 235 | ); 236 | 237 | tape( 238 | 'texture.url() returns a string with the pattern id', 239 | t => { 240 | const {svg, texture} = template(); 241 | texture.id('xyz'); 242 | svg.call(texture); 243 | t.equal(texture.url(), 'url(#xyz)'); 244 | t.end(); 245 | } 246 | ); 247 | 248 | tape( 249 | 'texture.size(40).strokeWidth(5) set size to 30 and strokeWidth to 5', 250 | t => { 251 | const {svg, texture} = template(); 252 | texture.size(40).strokeWidth(5); 253 | svg.call(texture); 254 | const path = svg.select('defs').select('pattern').select('path'); 255 | t.equal(path.attr('d'), 'M 0,40 l 40,-40 M -10,10 l 20,-20 M 30,50 l 20,-20'); 256 | t.equal(path.attr('stroke-width'), '5'); 257 | t.end(); 258 | } 259 | ); 260 | 261 | tape( 262 | 'texture.orientation("vertical") set orientation to vertical', 263 | t => { 264 | const {svg, texture} = template(); 265 | texture.orientation('vertical'); 266 | svg.call(texture); 267 | const path = svg.select('defs').select('pattern').select('path'); 268 | t.equal(path.attr('d'), 'M 10, 0 l 0, 20'); 269 | t.end(); 270 | } 271 | ); 272 | 273 | tape( 274 | 'texture.orientation("0/8") set orientation to 0/8', 275 | t => { 276 | const {svg, texture} = template(); 277 | texture.orientation('0/8'); 278 | svg.call(texture); 279 | const path = svg.select('defs').select('pattern').select('path'); 280 | t.equal(path.attr('d'), 'M 10, 0 l 0, 20'); 281 | t.end(); 282 | } 283 | ); 284 | 285 | tape( 286 | 'texture.orientation("1/8") set orientation to 1/8', 287 | t => { 288 | const {svg, texture} = template(); 289 | texture.size(40).orientation('1/8'); 290 | svg.call(texture); 291 | const path = svg.select('defs').select('pattern').select('path'); 292 | t.equal(path.attr('d'), 'M -10,40 l 20,-40 M 10,40 l 20,-40 M 30,40 l 20,-40'); 293 | t.end(); 294 | } 295 | ); 296 | 297 | tape( 298 | 'texture.orientation("2/8") set orientation to 2/8', 299 | t => { 300 | const {svg, texture} = template(); 301 | texture.size(80).orientation('2/8'); 302 | svg.call(texture); 303 | const path = svg.select('defs').select('pattern').select('path'); 304 | t.equal(path.attr('d'), 'M 0,80 l 80,-80 M -20,20 l 40,-40 M 60,100 l 40,-40'); 305 | t.end(); 306 | } 307 | ); 308 | 309 | tape( 310 | 'texture.orientation("diagonal") set orientation to diagonal', 311 | t => { 312 | const {svg, texture} = template(); 313 | texture.size(80).orientation('diagonal'); 314 | svg.call(texture); 315 | const path = svg.select('defs').select('pattern').select('path'); 316 | t.equal(path.attr('d'), 'M 0,80 l 80,-80 M -20,20 l 40,-40 M 60,100 l 40,-40'); 317 | t.end(); 318 | } 319 | ); 320 | 321 | tape( 322 | 'texture.orientation("3/8") set orientation to 3/8', 323 | t => { 324 | const {svg, texture} = template(); 325 | texture.size(40).orientation('3/8'); 326 | svg.call(texture); 327 | const path = svg.select('defs').select('pattern').select('path'); 328 | t.equal(path.attr('d'), 'M 0,30 l 40,-20 M 0,10 l 40,-20 M 0,50 l 40,-20'); 329 | t.end(); 330 | } 331 | ); 332 | 333 | tape( 334 | 'texture.orientation("4/8") set orientation to 4/8', 335 | t => { 336 | const {svg, texture} = template(); 337 | texture.size(40).orientation('4/8'); 338 | svg.call(texture); 339 | const path = svg.select('defs').select('pattern').select('path'); 340 | t.equal(path.attr('d'), 'M 0,20 l 40,0'); 341 | t.end(); 342 | } 343 | ); 344 | 345 | tape( 346 | 'texture.orientation("horizontal") set orientation to horizontal', 347 | t => { 348 | const {svg, texture} = template(); 349 | texture.size(20).orientation('horizontal'); 350 | svg.call(texture); 351 | const path = svg.select('defs').select('pattern').select('path'); 352 | t.equal(path.attr('d'), 'M 0,10 l 20,0'); 353 | t.end(); 354 | } 355 | ); 356 | 357 | tape( 358 | 'texture.orientation("5/8") set orientation to 5/8', 359 | t => { 360 | const {svg, texture} = template(); 361 | texture.size(40).orientation('5/8'); 362 | svg.call(texture); 363 | const path = svg.select('defs').select('pattern').select('path'); 364 | t.equal(path.attr('d'), 'M 0,-10 l 40,20M 0,10 l 40,20 M 0,30 l 40,20'); 365 | t.end(); 366 | } 367 | ); 368 | 369 | tape( 370 | 'texture.orientation("6/8") set orientation to 6/8', 371 | t => { 372 | const {svg, texture} = template(); 373 | texture.size(40).orientation('6/8'); 374 | svg.call(texture); 375 | const path = svg.select('defs').select('pattern').select('path'); 376 | t.equal(path.attr('d'), 'M 0,0 l 40,40 M -10,30 l 20,20 M 30,-10 l 20,20'); 377 | t.end(); 378 | } 379 | ); 380 | 381 | tape( 382 | 'texture.orientation("7/8") set orientation to 7/8', 383 | t => { 384 | const {svg, texture} = template(); 385 | texture.size(40).orientation('7/8'); 386 | svg.call(texture); 387 | const path = svg.select('defs').select('pattern').select('path'); 388 | t.equal(path.attr('d'), 'M -10,0 l 20,40 M 10,0 l 20,40 M 30,0 l 20,40'); 389 | t.end(); 390 | } 391 | ); 392 | 393 | tape( 394 | 'texture.orientation("xxx") defaults to vertical', 395 | t => { 396 | const {svg, texture} = template(); 397 | texture.size(40).orientation('xxx'); 398 | svg.call(texture); 399 | const path = svg.select('defs').select('pattern').select('path'); 400 | t.equal(path.attr('d'), 'M 20, 0 l 0, 40'); 401 | t.end(); 402 | } 403 | ); 404 | 405 | tape( 406 | 'texture.orientation("3/8", "7/8") add a couple of nodes ', 407 | t => { 408 | const {svg, texture} = template(); 409 | texture.size(40).orientation('3/8', '7/8'); 410 | svg.call(texture); 411 | t.equal(svg.select('defs').select('pattern').selectAll('path').size(), 2); 412 | t.end(); 413 | } 414 | ); 415 | 416 | tape( 417 | 'texture.orientation() defaults to diagonal', 418 | t => { 419 | const {svg, texture} = template(); 420 | texture.size(80).orientation(); 421 | svg.call(texture); 422 | const path = svg.select('defs').select('pattern').select('path'); 423 | t.equal(path.attr('d'), 'M 0,80 l 80,-80 M -20,20 l 40,-40 M 60,100 l 40,-40'); 424 | t.end(); 425 | } 426 | ); 427 | -------------------------------------------------------------------------------- /tests/paths-test.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape'); 2 | const d3 = require('d3-selection'); 3 | const textures = require('../dist/textures'); 4 | const jsdom = require('./jsdom'); 5 | 6 | const template = () => { 7 | const texture = textures.paths(); 8 | const document = jsdom(''); 9 | const svg = d3.select(document).select('svg'); 10 | return {svg, texture}; 11 | }; 12 | 13 | tape( 14 | 'svg.call(texture) append a node ', 15 | t => { 16 | const {svg, texture} = template(); 17 | svg.call(texture); 18 | t.ok(!svg.select('defs').empty()); 19 | t.end(); 20 | } 21 | ); 22 | 23 | tape( 24 | 'svg.call(texture) append a node ', 25 | t => { 26 | const {svg, texture} = template(); 27 | svg.call(texture); 28 | t.ok(!svg.select('defs').select('pattern').empty()); 29 | t.end(); 30 | } 31 | ); 32 | 33 | tape( 34 | 'svg.call(texture) append a node with the id attribute', 35 | t => { 36 | const {svg, texture} = template(); 37 | svg.call(texture); 38 | t.notEqual(svg.select('defs').select('pattern').attr('id'), ''); 39 | t.end(); 40 | } 41 | ); 42 | 43 | tape( 44 | 'svg.call(texture) append a node with the patternUnits attribute set to userSpaceOnUse', 45 | t => { 46 | const {svg, texture} = template(); 47 | svg.call(texture); 48 | t.equal(svg.select('defs').select('pattern').attr('patternUnits'), 'userSpaceOnUse'); 49 | t.end(); 50 | } 51 | ); 52 | 53 | tape( 54 | 'svg.call(texture) append a node with the attributes width and height set to 20', 55 | t => { 56 | const {svg, texture} = template(); 57 | svg.call(texture); 58 | t.equal(svg.select('defs').select('pattern').attr('width'), '20'); 59 | t.equal(svg.select('defs').select('pattern').attr('height'), '20'); 60 | t.end(); 61 | } 62 | ); 63 | 64 | tape( 65 | 'texture.lines() append a node with some default attributes', 66 | t => { 67 | const {svg, texture} = template(); 68 | svg.call(texture); 69 | const path = svg.select('defs').select('pattern').select('path'); 70 | t.equal(path.attr('stroke-width'), '2'); 71 | t.equal(path.attr('stroke'), '#343434'); 72 | t.equal(path.attr('shape-rendering'), 'auto'); 73 | t.equal(path.attr('stroke-linecap'), 'square'); 74 | t.equal(path.attr('d'), 'M 5,15l5,-10l5,10'); 75 | t.end(); 76 | } 77 | ); 78 | 79 | tape( 80 | 'texture.heavier() doubles the strokeWidth', 81 | t => { 82 | const {svg, texture} = template(); 83 | texture.heavier(); 84 | svg.call(texture); 85 | t.equal(svg.select('defs').select('pattern').select('path').attr('stroke-width'), '4'); 86 | t.end(); 87 | } 88 | ); 89 | 90 | tape( 91 | 'texture.heavier(3) changes strokeWidth to strokeWidth * 2 * 3', 92 | t => { 93 | const {svg, texture} = template(); 94 | texture.heavier(3); 95 | svg.call(texture); 96 | t.equal(svg.select('defs').select('pattern').select('path').attr('stroke-width'), '12'); 97 | t.end(); 98 | } 99 | ); 100 | 101 | tape( 102 | 'texture.lighter() divides the strokeWidth by 2', 103 | t => { 104 | const {svg, texture} = template(); 105 | texture.lighter(); 106 | svg.call(texture); 107 | t.equal(svg.select('defs').select('pattern').select('path').attr('stroke-width'), '1'); 108 | t.end(); 109 | } 110 | ); 111 | 112 | tape( 113 | 'texture.lighter(2) changes radius to strokeWidth / (2 * 2)', 114 | t => { 115 | const {svg, texture} = template(); 116 | texture.lighter(2); 117 | svg.call(texture); 118 | t.equal(svg.select('defs').select('pattern').select('path').attr('stroke-width'), '0.5'); 119 | t.end(); 120 | } 121 | ); 122 | 123 | tape( 124 | 'texture.thinner() doubles the size', 125 | t => { 126 | const {svg, texture} = template(); 127 | texture.thinner(); 128 | svg.call(texture); 129 | t.equal(svg.select('defs').select('pattern').attr('width'), '40'); 130 | t.end(); 131 | } 132 | ); 133 | 134 | tape( 135 | 'texture.thinner(3) changes size to size * 2 * 3', 136 | t => { 137 | const {svg, texture} = template(); 138 | texture.thinner(3); 139 | svg.call(texture); 140 | t.equal(svg.select('defs').select('pattern').attr('width'), '120'); 141 | t.end(); 142 | } 143 | ); 144 | 145 | tape( 146 | 'texture.thicker() divides the size by 2', 147 | t => { 148 | const {svg, texture} = template(); 149 | texture.thicker(); 150 | svg.call(texture); 151 | t.equal(svg.select('defs').select('pattern').attr('width'), '10'); 152 | t.end(); 153 | } 154 | ); 155 | 156 | tape( 157 | 'texture.thicker(2) changes size to size / (2 * 2)', 158 | t => { 159 | const {svg, texture} = template(); 160 | texture.thicker(2); 161 | svg.call(texture); 162 | t.equal(svg.select('defs').select('pattern').attr('width'), '5'); 163 | t.end(); 164 | } 165 | ); 166 | 167 | tape( 168 | 'texture.background("firebrick") append a node with attribute fill equal to "firebrick"', 169 | t => { 170 | const {svg, texture} = template(); 171 | texture.background('firebrick'); 172 | svg.call(texture); 173 | t.equal(svg.select('defs').select('pattern').select('rect').attr('fill'), 'firebrick'); 174 | t.end(); 175 | } 176 | ); 177 | 178 | tape( 179 | 'texture.size(40) set size to 40', 180 | t => { 181 | const {svg, texture} = template(); 182 | texture.size(40); 183 | svg.call(texture); 184 | const path = svg.select('defs').select('pattern').select('path'); 185 | t.equal(path.attr('d'), 'M 10,30l10,-20l10,20'); 186 | t.end(); 187 | } 188 | ); 189 | 190 | tape( 191 | 'texture.shapeRendering("crispEdges") set shape-rendering to crispEdges', 192 | t => { 193 | const {svg, texture} = template(); 194 | texture.shapeRendering('crispEdges'); 195 | svg.call(texture); 196 | const path = svg.select('defs').select('pattern').select('path'); 197 | t.equal(path.attr('shape-rendering'), 'crispEdges'); 198 | t.end(); 199 | } 200 | ); 201 | 202 | tape( 203 | 'texture.stroke("red") set stroke to red', 204 | t => { 205 | const {svg, texture} = template(); 206 | texture.stroke('red'); 207 | svg.call(texture); 208 | const path = svg.select('defs').select('pattern').select('path'); 209 | t.equal(path.attr('stroke'), 'red'); 210 | t.end(); 211 | } 212 | ); 213 | 214 | tape( 215 | 'texture.strokeWidth(4) set stroke-width to 4', 216 | t => { 217 | const {svg, texture} = template(); 218 | texture.strokeWidth(4); 219 | svg.call(texture); 220 | const path = svg.select('defs').select('pattern').select('path'); 221 | t.equal(path.attr('stroke-width'), '4'); 222 | t.end(); 223 | } 224 | ); 225 | 226 | tape( 227 | 'texture.id("xyz") set pattern id to xyz', 228 | t => { 229 | const {svg, texture} = template(); 230 | texture.id('xyz'); 231 | svg.call(texture); 232 | t.equal(svg.select('defs').select('pattern').attr('id'), 'xyz'); 233 | t.end(); 234 | } 235 | ); 236 | 237 | tape( 238 | 'texture.url() returns a string with the pattern id', 239 | t => { 240 | const {svg, texture} = template(); 241 | texture.id('xyz'); 242 | svg.call(texture); 243 | t.equal(texture.url(), 'url(#xyz)'); 244 | t.end(); 245 | } 246 | ); 247 | 248 | tape( 249 | 'texture.stroke("black").strokeWidth(5) set stroke to black and strokeWidth to 5', 250 | t => { 251 | const {svg, texture} = template(); 252 | texture.stroke('black').strokeWidth(5); 253 | svg.call(texture); 254 | const path = svg.select('defs').select('pattern').select('path'); 255 | t.equal(path.attr('stroke'), 'black'); 256 | t.equal(path.attr('stroke-width'), '5'); 257 | t.end(); 258 | } 259 | ); 260 | 261 | tape( 262 | 'texture.d("squares") set squared path', 263 | t => { 264 | const {svg, texture} = template(); 265 | texture.d('squares'); 266 | svg.call(texture); 267 | const path = svg.select('defs').select('pattern').select('path'); 268 | t.equal(path.attr('d'), 'M 5 5 l 10 0 l 0 10 l -10 0 Z'); 269 | t.end(); 270 | } 271 | ); 272 | 273 | tape( 274 | 'texture.size(80).d("caps") set caps path', 275 | t => { 276 | const {svg, texture} = template(); 277 | texture.size(80).d('caps'); 278 | svg.call(texture); 279 | const path = svg.select('defs').select('pattern').select('path'); 280 | t.equal(path.attr('d'), 'M 20,60l20,-40l20,40'); 281 | t.end(); 282 | } 283 | ); 284 | 285 | tape( 286 | 'texture.d("hexagons") set hexagons path and set width and height', 287 | t => { 288 | const {svg, texture} = template(); 289 | texture.d('hexagons'); 290 | svg.call(texture); 291 | t.equal(svg.select('defs').select('pattern').attr('width'), '60'); 292 | t.end(); 293 | } 294 | ); 295 | --------------------------------------------------------------------------------