├── .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 |
--------------------------------------------------------------------------------