├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── rollup ├── build ├── d3-jetpack-module.js └── d3v4+jetpack.js ├── d3-index.js ├── index.js ├── package.json ├── src ├── append.js ├── appendMany.js ├── ascendingKey.js ├── at.js ├── attachTooltip.js ├── conventions.js ├── descendingKey.js ├── f.js ├── loadData.js ├── nestBy.js ├── parent.js ├── parseAttributes.js ├── polygonClip.js ├── round.js ├── selectAppend.js ├── st.js ├── translate-selection.js ├── tspans.js └── wordwrap.js └── test ├── test-append.js ├── test-at.js ├── test-f.js ├── test-nestBy.js ├── test-parseAttributes.js ├── test-selectAppend.js ├── test-st.js ├── test-translate-selection.js ├── test-tspans.js └── test-wordwrap.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | test.html -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build/*.zip 2 | test/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright {YEAR}, {OWNER} 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the author nor the names of contributors may be used to 15 | endorse or promote products derived from this software without specific prior 16 | written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # update: merged into [d3-jetpack](https://github.com/gka/d3-jetpack) 2 | 3 | # d3-jetpack-module 4 | 5 | [d3-jetpack](https://github.com/gka/d3-jetpack) and [d3-starterkit](https://github.com/1wheel/d3-starterkit) updated to use the [D3 4.0 module pattern](https://bost.ocks.org/mike/d3-plugin/). 6 | 7 | ## Installing 8 | 9 | If you use NPM, `npm install d3-jetpack-module`. Otherwise, download the latest [d3v4+jetpack.js](https://raw.githubusercontent.com/1wheel/d3-jetpack-module/master/build/d3v4%2Bjetpack.js). 10 | 11 | ## Documentation 12 | 13 | coming soon! So far there are only minor changes from jetpack and starterkit: 14 | 15 | # selection.at(name[, value]) [<>](https://github.com/1wheel/d3-jetpack-module/blob/master/src/at.js "Source") 16 | 17 | Works like d3v3's `.attr`. Passing an object to name sets multiple attributes, passing a string returns a single attribute and passing a string & second argument sets a single attribute. 18 | 19 | To avoid having to use quotes around attributes and styles with hyphens when using the object notation, camelCase keys are hyphenated. Instead of: 20 | 21 | selection 22 | .attr('stroke-width', 10) 23 | .attr('text-anchor', 'end') 24 | .attr('font-weight', 600) 25 | 26 | or with [d3-selection-multi](https://github.com/d3/d3-selection-multi): 27 | 28 | selection.attrs({'stroke-width': 10, 'text-anchor': 'end', 'font-weight': 600}) 29 | 30 | you can write: 31 | 32 | selection.at({fontSize: 10, textAnchor: 'end', fontWeight: 600}) 33 | 34 | With syntax highlighting on, it is a little easier to see the difference between keys and values when everything isn't a string. Plus there's less typing! 35 | 36 | 37 | # selection.st(name[, value]) [<>](https://github.com/1wheel/d3-jetpack-module/blob/master/src/st.js "Source") 38 | 39 | Like `at`, but for `style`. Additionally, when a number is passed to a style that requires a unit of measure, like `margin-top` or `font-size`, `px` is automatically appended. Instead of 40 | 41 | selection 42 | .style('margin-top', height/2 + 'px') 43 | .style('font-size', '40px') 44 | .style('width', width - 80 + 'px') 45 | 46 | The `+ px`s can also be dropped: 47 | 48 | selection.st({marginTop: height/2, fontSize: 40, width: width - 80}) 49 | 50 | # d3.loadData(files, callback) [<>](https://github.com/1wheel/d3-jetpack-module/blob/master/src/loadData.js "Source") 52 | 53 | Takes an array of files paths and loads them with `queue`, `d3.csv` and `d3.json`. After all the files have loaded, calls the `callback` function with the first error (or null if none) as the first arguement and an array of the loaded files as the secound. Instead of: 54 | 55 | ```js 56 | d3.queue() 57 | .defer(d3.csv, 'state-data.csv') 58 | .defer(d3.csv, 'county-data.csv') 59 | .defer(d3.json, 'us.json') 60 | .awaitAll(function(err, res){ 61 | var states = res[0], 62 | counties = res[1], 63 | us = res[2] 64 | }) 65 | ``` 66 | 67 | if your file types match their extensions, you can use: 68 | 69 | ```js 70 | d3.loadData(['state-data.csv', 'county-data.csv', 'us.json'], function(err, res){ 71 | var states = res[0], 72 | counties = res[1], 73 | us = res[2] 74 | }) 75 | ``` 76 | 77 | # d3.nestBy(array, key) [<>](https://github.com/1wheel/d3-jetpack-module/blob/master/src/nestBy.js "Source") 79 | 80 | Shorthand for `d3.nest().key(key).entries(array)`. Returns an array of arrays, instead of a `key`/`value` pairs. The `key` property of each array is equal the value returned by the `key` function when it is called with element of the array. 81 | 82 | ```js 83 | d3.nest() 84 | .key(ƒ('year')) 85 | .entries(yields) 86 | .forEach(function(d){ 87 | console.log('Count in ' + d.key + ': ' + d.values.length) }) 88 | ``` 89 | 90 | to 91 | 92 | ```js 93 | d3.nestBy(yields, ƒ('year')).forEach(function(d){ 94 | console.log('Count in ' + d.key + ': ' + d.length) }) 95 | ``` 96 | 97 | # d3.selectAppend(selector) [<>](https://github.com/1wheel/d3-jetpack-module/blob/master/src/selectAppend.js "Source") 98 | 99 | Selects the first element that matches the specified selector string or if no elements match the selector, it will append an element. This is often handy for elements which are required as part of the DOM hierachy, especially when making repeated calls to the same code. When appending it will also add id and classes, same as Jetpack's [append](#append) 100 | 101 | ```js 102 | d3.selectAppend('ul.fruits') 103 | .selectAll('li') 104 | .data(data) 105 | ``` 106 | 107 | -------------------------------------------------------------------------------- /bin/rollup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var rollup = require("rollup"), 4 | ascii = require("rollup-plugin-ascii"), 5 | nodeResolve = require("rollup-plugin-node-resolve"); 6 | 7 | rollup.rollup({ 8 | entry: "d3-index.js", 9 | plugins: [nodeResolve({jsnext: true}), ascii()] 10 | }).then(function(bundle) { 11 | return bundle.write({ 12 | banner: process.argv[2], 13 | format: "umd", 14 | moduleName: "d3", 15 | dest: "build/d3v4+jetpack.js" 16 | }); 17 | }).then(function() { 18 | console.warn("↳ build/d3v4+jetpack.js"); 19 | }).catch(abort); 20 | 21 | function abort(error) { 22 | console.error(error.stack); 23 | } 24 | -------------------------------------------------------------------------------- /build/d3-jetpack-module.js: -------------------------------------------------------------------------------- 1 | // https://github.com/1wheel/d3-jetpack-module Version 0.0.18. Copyright 2017 Adam Pearce. 2 | (function (global, factory) { 3 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-transition'), require('d3-axis'), require('d3-scale'), require('d3-collection'), require('d3-queue'), require('d3-request')) : 4 | typeof define === 'function' && define.amd ? define(['exports', 'd3-selection', 'd3-transition', 'd3-axis', 'd3-scale', 'd3-collection', 'd3-queue', 'd3-request'], factory) : 5 | (factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3,global.d3,global.d3,global.d3,global.d3)); 6 | }(this, (function (exports,d3Selection,d3Transition,d3Axis,d3Scale,d3Collection,d3Queue,d3Request) { 'use strict'; 7 | 8 | var translateSelection = function(xy) { 9 | return this.attr('transform', function(d,i) { 10 | return 'translate('+[typeof xy == 'function' ? xy.call(this, d,i) : xy]+')'; 11 | }); 12 | }; 13 | 14 | var parseAttributes = function(name) { 15 | if (typeof name === "string") { 16 | var attr = {}, 17 | parts = name.split(/([\.#])/g), p; 18 | name = parts.shift(); 19 | while ((p = parts.shift())) { 20 | if (p == '.') attr['class'] = attr['class'] ? attr['class'] + ' ' + parts.shift() : parts.shift(); 21 | else if (p == '#') attr.id = parts.shift(); 22 | } 23 | return {tag: name, attr: attr}; 24 | } 25 | return name; 26 | }; 27 | 28 | var append = function(name) { 29 | var n = parseAttributes(name), s; 30 | name = d3Selection.creator(n.tag); 31 | s = this.select(function() { 32 | return this.appendChild(name.apply(this, arguments)); 33 | }); 34 | 35 | //attrs not provided by default in v4 36 | for (var key in n.attr) { s.attr(key, n.attr[key]); } 37 | return s; 38 | }; 39 | 40 | var parent = function() { 41 | var parents = []; 42 | return this.filter(function() { 43 | if (parents.indexOf(this.parentNode) > -1) return false; 44 | parents.push(this.parentNode); 45 | return true; 46 | }).select(function() { 47 | return this.parentNode; 48 | }); 49 | }; 50 | 51 | var selectAppend = function(name) { 52 | var select$$1 = d3Selection.selector(name), 53 | n = parseAttributes(name), s; 54 | 55 | name = d3Selection.creator(n.tag); 56 | 57 | s = this.select(function() { 58 | return select$$1.apply(this, arguments) 59 | || this.appendChild(name.apply(this, arguments)); 60 | }); 61 | 62 | //attrs not provided by default in v4 63 | for (var key in n.attr) { s.attr(key, n.attr[key]); } 64 | return s; 65 | }; 66 | 67 | var tspans = function(lines, lh) { 68 | return this.selectAll('tspan') 69 | .data(function(d) { 70 | return (typeof(lines) == 'function' ? lines(d) : lines) 71 | .map(function(l) { 72 | return { line: l, parent: d } 73 | }); 74 | }) 75 | .enter() 76 | .append('tspan') 77 | .text(function(d) { return d.line; }) 78 | .attr('x', 0) 79 | .attr('dy', function(d, i) { return i ? (typeof(lh) == 'function' ? lh(d.parent, d.line, i) : lh) || 15 : 0; }); 80 | }; 81 | 82 | var appendMany = function(data, name){ 83 | return this.selectAll(null).data(data).enter().append(name); 84 | }; 85 | 86 | var at = function(name, value) { 87 | if (typeof(name) == 'object'){ 88 | for (var key in name){ 89 | this.attr(key.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase(), name[key]); 90 | } 91 | return this 92 | } else{ 93 | return arguments.length == 1 ? this.attr(name) : this.attr(name, value) 94 | } 95 | }; 96 | 97 | function f(){ 98 | var functions = arguments; 99 | 100 | //convert all string arguments into field accessors 101 | var i = 0, l = functions.length; 102 | while (i < l) { 103 | if (typeof(functions[i]) === 'string' || typeof(functions[i]) === 'number'){ 104 | functions[i] = (function(str){ return function(d){ return d[str] } })(functions[i]); 105 | } 106 | i++; 107 | } 108 | 109 | //return composition of functions 110 | return function(d) { 111 | var i=0, l = functions.length; 112 | while (i++ < l) d = functions[i-1].call(this, d); 113 | return d 114 | } 115 | } 116 | 117 | f.not = function(d){ return !d }; 118 | f.run = function(d){ return d() }; 119 | f.objToFn = function(obj, defaultVal){ 120 | if (arguments.length == 1) defaultVal = undefined; 121 | 122 | return function(str){ 123 | return typeof(obj[str]) !== undefined ? obj[str] : defaultVal } 124 | }; 125 | 126 | var st = function(name, value) { 127 | if (typeof(name) == 'object'){ 128 | for (var key in name){ 129 | addStyle(this, key, name[key]); 130 | } 131 | return this 132 | } else{ 133 | return arguments.length == 1 ? this.style(name) : addStyle(this, name, value) 134 | } 135 | 136 | 137 | function addStyle(sel, style, value){ 138 | var style = style.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase(); 139 | 140 | var pxStyles = 'top left bottom right padding-top padding-left padding-bottom padding-right border-top b-width border-left-width border-botto-width m border-right-width margin-top margin-left margin-bottom margin-right font-size width height stroke-width line-height margin padding border max-width min-width'; 141 | 142 | if (~pxStyles.indexOf(style) ){ 143 | sel.style(style, typeof value == 'function' ? f(value, addPx) : addPx(value)); 144 | } else{ 145 | sel.style(style, value); 146 | } 147 | 148 | return sel 149 | } 150 | 151 | function addPx(d){ return d.match ? d : d + 'px' } 152 | }; 153 | 154 | var wordwrap = function(line, maxCharactersPerLine) { 155 | var w = line.split(' '), 156 | lines = [], 157 | words = [], 158 | maxChars = maxCharactersPerLine || 40, 159 | l = 0; 160 | 161 | w.forEach(function(d) { 162 | if (l+d.length > maxChars) { 163 | lines.push(words.join(' ')); 164 | words.length = 0; 165 | l = 0; 166 | } 167 | l += d.length; 168 | words.push(d); 169 | }); 170 | if (words.length) { 171 | lines.push(words.join(' ')); 172 | } 173 | return lines.filter(function(d){ return d != '' }); 174 | }; 175 | 176 | var ascendingKey = function(key) { 177 | return typeof key == 'function' ? function (a, b) { 178 | return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : key(a) >= key(b) ? 0 : NaN; 179 | } : function (a, b) { 180 | return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : a[key] >= b[key] ? 0 : NaN; 181 | }; 182 | }; 183 | 184 | var descendingKey = function(key) { 185 | return typeof key == 'function' ? function (a, b) { 186 | return key(b) < key(a) ? -1 : key(b) > key(a) ? 1 : key(b) >= key(a) ? 0 : NaN; 187 | } : function (a, b) { 188 | return b[key] < a[key] ? -1 : b[key] > a[key] ? 1 : b[key] >= a[key] ? 0 : NaN; 189 | }; 190 | }; 191 | 192 | var conventions = function(c){ 193 | c = c || {}; 194 | 195 | c.margin = c.margin || {top: 20, right: 20, bottom: 20, left: 20} 196 | ;['top', 'right', 'bottom', 'left'].forEach(function(d){ 197 | if (!c.margin[d] && c.margin[d] != 0) c.margin[d] = 20; 198 | }); 199 | 200 | c.width = c.width || c.totalWidth - c.margin.left - c.margin.right || 900; 201 | c.height = c.height || c.totalHeight - c.margin.top - c.margin.bottom || 460; 202 | 203 | c.totalWidth = c.width + c.margin.left + c.margin.right; 204 | c.totalHeight = c.height + c.margin.top + c.margin.bottom; 205 | 206 | c.parentSel = c.parentSel || d3Selection.select('body'); 207 | 208 | c.rootsvg = c.parentSel.append('svg'); 209 | 210 | c.svg = c.rootsvg 211 | .attr('width', c.totalWidth) 212 | .attr('height', c.totalHeight) 213 | .append('g') 214 | .attr('transform', 'translate(' + c.margin.left + ',' + c.margin.top + ')'); 215 | 216 | c.x = c.x || d3Scale.scaleLinear().range([0, c.width]); 217 | c.y = c.y || d3Scale.scaleLinear().range([c.height, 0]); 218 | 219 | c.xAxis = c.xAxis || d3Axis.axisBottom().scale(c.x); 220 | c.yAxis = c.yAxis || d3Axis.axisLeft().scale(c.y); 221 | 222 | c.drawAxis = function(){ 223 | c.svg.append('g') 224 | .attr('class', 'x axis') 225 | .attr('transform', 'translate(0,' + c.height + ')') 226 | .call(c.xAxis); 227 | 228 | c.svg.append('g') 229 | .attr('class', 'y axis') 230 | .call(c.yAxis); 231 | }; 232 | 233 | return c 234 | }; 235 | 236 | var attachTooltip = function(sel, tooltipSel, fieldFns){ 237 | if (!sel.size()) return 238 | 239 | tooltipSel = tooltipSel || d3Selection.select('.tooltip'); 240 | 241 | sel 242 | .on('mouseover.attachTooltip', ttDisplay) 243 | .on('mousemove.attachTooltip', ttMove) 244 | .on('mouseout.attachTooltip', ttHide) 245 | .on('click.attachTooltip', function(d){ console.log(d); }); 246 | 247 | var d = sel.datum(); 248 | fieldFns = fieldFns || d3Collection.keys(d) 249 | .filter(function(str){ 250 | return (typeof d[str] != 'object') && (d[str] != 'array') 251 | }) 252 | .map(function(str){ 253 | return function(d){ return str + ': ' + d[str] + ''} }); 254 | 255 | function ttDisplay(d){ 256 | tooltipSel 257 | .classed('tooltip-hidden', false) 258 | .html('') 259 | .appendMany(fieldFns, 'div') 260 | .html(function(fn){ return fn(d) }); 261 | 262 | d3Selection.select(this).classed('tooltipped', true); 263 | } 264 | 265 | function ttMove(d){ 266 | var tt = tooltipSel; 267 | if (!tt.size()) return 268 | var e = d3Selection.event, 269 | x = e.clientX, 270 | y = e.clientY, 271 | n = tt.node(), 272 | nBB = n.getBoundingClientRect(), 273 | doctop = (window.scrollY)? window.scrollY : (document.documentElement && document.documentElement.scrollTop)? document.documentElement.scrollTop : document.body.scrollTop, 274 | topPos = y+doctop-nBB.height-18; 275 | 276 | tt.style('top', (topPos < 0 ? 18 + y : topPos)+'px'); 277 | tt.style('left', Math.min(Math.max(20, (x-nBB.width/2)), window.innerWidth - nBB.width - 20)+'px'); 278 | } 279 | 280 | function ttHide(d){ 281 | tooltipSel.classed('tooltip-hidden', true); 282 | 283 | d3Selection.selectAll('.tooltipped').classed('tooltipped', false); 284 | } 285 | }; 286 | 287 | var loadData = function(files, cb){ 288 | var q = d3Queue.queue(); 289 | files.forEach(function(d){ 290 | var type = d.split('.').reverse()[0]; 291 | 292 | var loadFn = {csv: d3Request.csv, tsv: d3Request.tsv, json: d3Request.json}[type]; 293 | if (!loadFn) return cb(new Error('Invalid type', d)) 294 | q.defer(loadFn, d); 295 | }); 296 | q.awaitAll(cb); 297 | }; 298 | 299 | var nestBy = function(array, key){ 300 | return d3Collection.nest().key(key).entries(array).map(function(d){ 301 | d.values.key = d.key; 302 | return d.values 303 | }) 304 | }; 305 | 306 | var round = function(n, p) { 307 | return p ? Math.round(n * (p = Math.pow(10, p))) / p : Math.round(n); 308 | }; 309 | 310 | // Clips the specified subject polygon to the specified clip polygon; 311 | // requires the clip polygon to be counterclockwise and convex. 312 | // https://en.wikipedia.org/wiki/Sutherland–Hodgman_algorithm 313 | var polygonClip = function(clip, subject) { 314 | var input, 315 | closed = polygonClosed(subject), 316 | i = -1, 317 | n = clip.length - polygonClosed(clip), 318 | j, 319 | m, 320 | a = clip[n - 1], 321 | b, 322 | c, 323 | d; 324 | 325 | while (++i < n) { 326 | input = subject.slice(); 327 | subject.length = 0; 328 | b = clip[i]; 329 | c = input[(m = input.length - closed) - 1]; 330 | j = -1; 331 | while (++j < m) { 332 | d = input[j]; 333 | if (polygonInside(d, a, b)) { 334 | if (!polygonInside(c, a, b)) { 335 | subject.push(polygonIntersect(c, d, a, b)); 336 | } 337 | subject.push(d); 338 | } else if (polygonInside(c, a, b)) { 339 | subject.push(polygonIntersect(c, d, a, b)); 340 | } 341 | c = d; 342 | } 343 | if (closed) subject.push(subject[0]); 344 | a = b; 345 | } 346 | 347 | return subject; 348 | }; 349 | 350 | function polygonInside(p, a, b) { 351 | return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); 352 | } 353 | 354 | // Intersect two infinite lines cd and ab. 355 | function polygonIntersect(c, d, a, b) { 356 | var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, 357 | y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, 358 | ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); 359 | return [x1 + ua * x21, y1 + ua * y21]; 360 | } 361 | 362 | // Returns true if the polygon is closed. 363 | function polygonClosed(coordinates) { 364 | var a = coordinates[0], 365 | b = coordinates[coordinates.length - 1]; 366 | return !(a[0] - b[0] || a[1] - b[1]); 367 | } 368 | 369 | d3Selection.selection.prototype.translate = translateSelection; 370 | d3Transition.transition.prototype.translate = translateSelection; 371 | d3Selection.selection.prototype.append = append; 372 | d3Selection.selection.prototype.parent = parent; 373 | d3Selection.selection.prototype.selectAppend = selectAppend; 374 | d3Selection.selection.prototype.tspans = tspans; 375 | d3Selection.selection.prototype.appendMany = appendMany; 376 | d3Selection.selection.prototype.at = at; 377 | d3Selection.selection.prototype.st = st; 378 | d3Transition.transition.prototype.at = at; 379 | d3Transition.transition.prototype.st = st; 380 | d3Selection.selection.prototype.prop = d3Selection.selection.prototype.property; 381 | d3Selection.selection.prototype.zzz = 'zzz'; 382 | 383 | 384 | //testing text 385 | 386 | exports.wordwrap = wordwrap; 387 | exports.parseAttributes = parseAttributes; 388 | exports.f = f; 389 | exports.ascendingKey = ascendingKey; 390 | exports.descendingKey = descendingKey; 391 | exports.conventions = conventions; 392 | exports.attachTooltip = attachTooltip; 393 | exports.loadData = loadData; 394 | exports.nestBy = nestBy; 395 | exports.round = round; 396 | exports.polygonClip = polygonClip; 397 | 398 | Object.defineProperty(exports, '__esModule', { value: true }); 399 | 400 | }))); 401 | -------------------------------------------------------------------------------- /d3-index.js: -------------------------------------------------------------------------------- 1 | // export { 2 | // version 3 | // } from "./build/package"; 4 | 5 | export * from "d3" 6 | 7 | import "d3-selection-multi"; 8 | import "./index.js"; 9 | 10 | export { 11 | wordwrap, 12 | parseAttributes, 13 | f, 14 | ascendingKey, 15 | descendingKey, 16 | conventions, 17 | attachTooltip, 18 | loadData, 19 | nestBy, 20 | round, 21 | polygonClip 22 | } from "./index.js"; 23 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import {selection} from "d3-selection"; 2 | import {transition} from "d3-transition"; 3 | 4 | import translateSelection from "./src/translate-selection"; 5 | import append from "./src/append"; 6 | import parent from "./src/parent"; 7 | import selectAppend from "./src/selectAppend"; 8 | import tspans from "./src/tspans"; 9 | import appendMany from "./src/appendMany"; 10 | import at from "./src/at"; 11 | import st from "./src/st"; 12 | 13 | selection.prototype.translate = translateSelection; 14 | transition.prototype.translate = translateSelection; 15 | selection.prototype.append = append; 16 | selection.prototype.parent = parent; 17 | selection.prototype.selectAppend = selectAppend; 18 | selection.prototype.tspans = tspans; 19 | selection.prototype.appendMany = appendMany; 20 | selection.prototype.at = at; 21 | selection.prototype.st = st; 22 | transition.prototype.at = at; 23 | transition.prototype.st = st; 24 | selection.prototype.prop = selection.prototype.property; 25 | 26 | export {default as wordwrap} from "./src/wordwrap"; 27 | export {default as parseAttributes} from "./src/parseAttributes"; 28 | export {default as f} from "./src/f"; 29 | export {default as ascendingKey} from "./src/ascendingKey"; 30 | export {default as descendingKey} from "./src/descendingKey"; 31 | export {default as conventions} from "./src/conventions"; 32 | export {default as attachTooltip} from "./src/attachTooltip"; 33 | export {default as loadData} from "./src/loadData"; 34 | export {default as nestBy} from "./src/nestBy"; 35 | export {default as round} from "./src/round"; 36 | export {default as polygonClip} from "./src/polygonClip"; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-jetpack-module", 3 | "version": "0.0.19", 4 | "description": "handy shortcuts for d3", 5 | "keywords": [ 6 | "d3", 7 | "d3-module" 8 | ], 9 | "license": "BSD-3-Clause", 10 | "main": "build/d3-jetpack-module.js", 11 | "jsnext:main": "index", 12 | "module": "index", 13 | "homepage": "https://github.com/1wheel/d3-jetpack-module", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/1wheel/d3-jetpack-module.git" 17 | }, 18 | "scripts": { 19 | "pretest": "rm -rf build && mkdir build && rollup --banner \"$(preamble)\" -f umd -g d3-selection:d3,d3-transition:d3,d3-axis:d3,d3-scale:d3,d3-queue:d3,d3-collection:d3,d3-request:d3 -n d3 -o build/d3-jetpack-module.js -- index.js", 20 | "test": "tape 'test/test-*.js'", 21 | "prepublish": "npm run pretest && bin/rollup", 22 | "postpublish": "bin/rollup" 23 | }, 24 | "devDependencies": { 25 | "d3": "4", 26 | "d3-selection-multi": "1", 27 | "eslint": "2", 28 | "jsdom": "9", 29 | "package-preamble": "0.0", 30 | "rollup": "0.41", 31 | "rollup-plugin-ascii": "0.0", 32 | "rollup-plugin-node-resolve": "2", 33 | "tape": "4", 34 | "uglify-js": "2" 35 | }, 36 | "dependencies": { 37 | "d3": "4" 38 | }, 39 | "author": { 40 | "name": "Adam Pearce", 41 | "email": "1wheel@gmail.com", 42 | "url": "http://roadtolarissa.com/" 43 | }, 44 | "contributors": [ 45 | { 46 | "name": "Gregor Aisch", 47 | "email": "mail@driven-by-data.net", 48 | "url": "http://driven-by-data.net/" 49 | }, 50 | { 51 | "name": "Adam Pearce", 52 | "email": "1wheel@gmail.com", 53 | "url": "http://roadtolarissa.com/" 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/append.js: -------------------------------------------------------------------------------- 1 | import {creator} from "d3-selection"; 2 | import parseAttributes from "./parseAttributes"; 3 | 4 | export default function(name) { 5 | var n = parseAttributes(name), s; 6 | name = creator(n.tag); 7 | s = this.select(function() { 8 | return this.appendChild(name.apply(this, arguments)); 9 | }); 10 | 11 | //attrs not provided by default in v4 12 | for (var key in n.attr) { s.attr(key, n.attr[key]) } 13 | return s; 14 | }; 15 | -------------------------------------------------------------------------------- /src/appendMany.js: -------------------------------------------------------------------------------- 1 | export default function(data, name){ 2 | return this.selectAll(null).data(data).enter().append(name); 3 | }; -------------------------------------------------------------------------------- /src/ascendingKey.js: -------------------------------------------------------------------------------- 1 | export default function(key) { 2 | return typeof key == 'function' ? function (a, b) { 3 | return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : key(a) >= key(b) ? 0 : NaN; 4 | } : function (a, b) { 5 | return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : a[key] >= b[key] ? 0 : NaN; 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /src/at.js: -------------------------------------------------------------------------------- 1 | export default function(name, value) { 2 | if (typeof(name) == 'object'){ 3 | for (var key in name){ 4 | this.attr(key.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase(), name[key]) 5 | } 6 | return this 7 | } else{ 8 | return arguments.length == 1 ? this.attr(name) : this.attr(name, value) 9 | } 10 | }; -------------------------------------------------------------------------------- /src/attachTooltip.js: -------------------------------------------------------------------------------- 1 | import {select} from 'd3-selection'; 2 | import {selectAll} from 'd3-selection'; 3 | import {event as d3event} from 'd3-selection'; 4 | import {keys as d3keys} from 'd3-collection'; 5 | 6 | export default function(sel, tooltipSel, fieldFns){ 7 | if (!sel.size()) return 8 | 9 | tooltipSel = tooltipSel || select('.tooltip') 10 | 11 | sel 12 | .on('mouseover.attachTooltip', ttDisplay) 13 | .on('mousemove.attachTooltip', ttMove) 14 | .on('mouseout.attachTooltip', ttHide) 15 | .on('click.attachTooltip', function(d){ console.log(d) }) 16 | 17 | var d = sel.datum() 18 | fieldFns = fieldFns || d3keys(d) 19 | .filter(function(str){ 20 | return (typeof d[str] != 'object') && (d[str] != 'array') 21 | }) 22 | .map(function(str){ 23 | return function(d){ return str + ': ' + d[str] + ''} }) 24 | 25 | function ttDisplay(d){ 26 | tooltipSel 27 | .classed('tooltip-hidden', false) 28 | .html('') 29 | .appendMany(fieldFns, 'div') 30 | .html(function(fn){ return fn(d) }) 31 | 32 | select(this).classed('tooltipped', true) 33 | } 34 | 35 | function ttMove(d){ 36 | var tt = tooltipSel 37 | if (!tt.size()) return 38 | var e = d3event, 39 | x = e.clientX, 40 | y = e.clientY, 41 | n = tt.node(), 42 | nBB = n.getBoundingClientRect(), 43 | doctop = (window.scrollY)? window.scrollY : (document.documentElement && document.documentElement.scrollTop)? document.documentElement.scrollTop : document.body.scrollTop, 44 | topPos = y+doctop-nBB.height-18; 45 | 46 | tt.style('top', (topPos < 0 ? 18 + y : topPos)+'px'); 47 | tt.style('left', Math.min(Math.max(20, (x-nBB.width/2)), window.innerWidth - nBB.width - 20)+'px'); 48 | } 49 | 50 | function ttHide(d){ 51 | tooltipSel.classed('tooltip-hidden', true); 52 | 53 | selectAll('.tooltipped').classed('tooltipped', false) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/conventions.js: -------------------------------------------------------------------------------- 1 | import {axisLeft} from 'd3-axis'; 2 | import {axisBottom} from 'd3-axis'; 3 | import {select} from "d3-selection"; 4 | import {scaleLinear} from "d3-scale"; 5 | 6 | export default function(c){ 7 | c = c || {} 8 | 9 | c.margin = c.margin || {top: 20, right: 20, bottom: 20, left: 20} 10 | ;['top', 'right', 'bottom', 'left'].forEach(function(d){ 11 | if (!c.margin[d] && c.margin[d] != 0) c.margin[d] = 20 12 | }) 13 | 14 | c.width = c.width || c.totalWidth - c.margin.left - c.margin.right || 900 15 | c.height = c.height || c.totalHeight - c.margin.top - c.margin.bottom || 460 16 | 17 | c.totalWidth = c.width + c.margin.left + c.margin.right 18 | c.totalHeight = c.height + c.margin.top + c.margin.bottom 19 | 20 | c.parentSel = c.parentSel || select('body') 21 | 22 | c.rootsvg = c.parentSel.append('svg') 23 | 24 | c.svg = c.rootsvg 25 | .attr('width', c.totalWidth) 26 | .attr('height', c.totalHeight) 27 | .append('g') 28 | .attr('transform', 'translate(' + c.margin.left + ',' + c.margin.top + ')') 29 | 30 | c.x = c.x || scaleLinear().range([0, c.width]) 31 | c.y = c.y || scaleLinear().range([c.height, 0]) 32 | 33 | c.xAxis = c.xAxis || axisBottom().scale(c.x) 34 | c.yAxis = c.yAxis || axisLeft().scale(c.y) 35 | 36 | c.drawAxis = function(){ 37 | c.svg.append('g') 38 | .attr('class', 'x axis') 39 | .attr('transform', 'translate(0,' + c.height + ')') 40 | .call(c.xAxis); 41 | 42 | c.svg.append('g') 43 | .attr('class', 'y axis') 44 | .call(c.yAxis); 45 | } 46 | 47 | return c 48 | } 49 | -------------------------------------------------------------------------------- /src/descendingKey.js: -------------------------------------------------------------------------------- 1 | export default function(key) { 2 | return typeof key == 'function' ? function (a, b) { 3 | return key(b) < key(a) ? -1 : key(b) > key(a) ? 1 : key(b) >= key(a) ? 0 : NaN; 4 | } : function (a, b) { 5 | return b[key] < a[key] ? -1 : b[key] > a[key] ? 1 : b[key] >= a[key] ? 0 : NaN; 6 | }; 7 | }; -------------------------------------------------------------------------------- /src/f.js: -------------------------------------------------------------------------------- 1 | function f(){ 2 | var functions = arguments 3 | 4 | //convert all string arguments into field accessors 5 | var i = 0, l = functions.length 6 | while (i < l) { 7 | if (typeof(functions[i]) === 'string' || typeof(functions[i]) === 'number'){ 8 | functions[i] = (function(str){ return function(d){ return d[str] } })(functions[i]) 9 | } 10 | i++ 11 | } 12 | 13 | //return composition of functions 14 | return function(d) { 15 | var i=0, l = functions.length 16 | while (i++ < l) d = functions[i-1].call(this, d) 17 | return d 18 | } 19 | } 20 | 21 | f.not = function(d){ return !d } 22 | f.run = function(d){ return d() } 23 | f.objToFn = function(obj, defaultVal){ 24 | if (arguments.length == 1) defaultVal = undefined 25 | 26 | return function(str){ 27 | return typeof(obj[str]) !== undefined ? obj[str] : defaultVal } 28 | } 29 | 30 | export default f -------------------------------------------------------------------------------- /src/loadData.js: -------------------------------------------------------------------------------- 1 | import {queue} from 'd3-queue'; 2 | import {csv, tsv, json} from 'd3-request' 3 | 4 | export default function(files, cb){ 5 | var q = queue() 6 | files.forEach(function(d){ 7 | var type = d.split('.').reverse()[0] 8 | 9 | var loadFn = {csv: csv, tsv: tsv, json: json}[type] 10 | if (!loadFn) return cb(new Error('Invalid type', d)) 11 | q.defer(loadFn, d) 12 | }) 13 | q.awaitAll(cb) 14 | } -------------------------------------------------------------------------------- /src/nestBy.js: -------------------------------------------------------------------------------- 1 | import {nest} from 'd3-collection'; 2 | 3 | export default function(array, key){ 4 | return nest().key(key).entries(array).map(function(d){ 5 | d.values.key = d.key 6 | return d.values 7 | }) 8 | } -------------------------------------------------------------------------------- /src/parent.js: -------------------------------------------------------------------------------- 1 | import {creator} from "d3-selection"; 2 | 3 | export default function() { 4 | var parents = []; 5 | return this.filter(function() { 6 | if (parents.indexOf(this.parentNode) > -1) return false; 7 | parents.push(this.parentNode); 8 | return true; 9 | }).select(function() { 10 | return this.parentNode; 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/parseAttributes.js: -------------------------------------------------------------------------------- 1 | export default function(name) { 2 | if (typeof name === "string") { 3 | var attr = {}, 4 | parts = name.split(/([\.#])/g), p; 5 | name = parts.shift(); 6 | while ((p = parts.shift())) { 7 | if (p == '.') attr['class'] = attr['class'] ? attr['class'] + ' ' + parts.shift() : parts.shift(); 8 | else if (p == '#') attr.id = parts.shift(); 9 | } 10 | return {tag: name, attr: attr}; 11 | } 12 | return name; 13 | } -------------------------------------------------------------------------------- /src/polygonClip.js: -------------------------------------------------------------------------------- 1 | // Clips the specified subject polygon to the specified clip polygon; 2 | // requires the clip polygon to be counterclockwise and convex. 3 | // https://en.wikipedia.org/wiki/Sutherland–Hodgman_algorithm 4 | export default function(clip, subject) { 5 | var input, 6 | closed = polygonClosed(subject), 7 | i = -1, 8 | n = clip.length - polygonClosed(clip), 9 | j, 10 | m, 11 | a = clip[n - 1], 12 | b, 13 | c, 14 | d; 15 | 16 | while (++i < n) { 17 | input = subject.slice(); 18 | subject.length = 0; 19 | b = clip[i]; 20 | c = input[(m = input.length - closed) - 1]; 21 | j = -1; 22 | while (++j < m) { 23 | d = input[j]; 24 | if (polygonInside(d, a, b)) { 25 | if (!polygonInside(c, a, b)) { 26 | subject.push(polygonIntersect(c, d, a, b)); 27 | } 28 | subject.push(d); 29 | } else if (polygonInside(c, a, b)) { 30 | subject.push(polygonIntersect(c, d, a, b)); 31 | } 32 | c = d; 33 | } 34 | if (closed) subject.push(subject[0]); 35 | a = b; 36 | } 37 | 38 | return subject; 39 | }; 40 | 41 | function polygonInside(p, a, b) { 42 | return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]); 43 | } 44 | 45 | // Intersect two infinite lines cd and ab. 46 | function polygonIntersect(c, d, a, b) { 47 | var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, 48 | y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, 49 | ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21); 50 | return [x1 + ua * x21, y1 + ua * y21]; 51 | } 52 | 53 | // Returns true if the polygon is closed. 54 | function polygonClosed(coordinates) { 55 | var a = coordinates[0], 56 | b = coordinates[coordinates.length - 1]; 57 | return !(a[0] - b[0] || a[1] - b[1]); 58 | } 59 | -------------------------------------------------------------------------------- /src/round.js: -------------------------------------------------------------------------------- 1 | export default function(n, p) { 2 | return p ? Math.round(n * (p = Math.pow(10, p))) / p : Math.round(n); 3 | }; 4 | -------------------------------------------------------------------------------- /src/selectAppend.js: -------------------------------------------------------------------------------- 1 | import {selector} from "d3-selection"; 2 | import {creator} from "d3-selection"; 3 | import parseAttributes from "./parseAttributes"; 4 | 5 | export default function(name) { 6 | var select = selector(name), 7 | n = parseAttributes(name), s; 8 | 9 | name = creator(n.tag); 10 | 11 | s = this.select(function() { 12 | return select.apply(this, arguments) 13 | || this.appendChild(name.apply(this, arguments)); 14 | }); 15 | 16 | //attrs not provided by default in v4 17 | for (var key in n.attr) { s.attr(key, n.attr[key]) } 18 | return s; 19 | }; 20 | -------------------------------------------------------------------------------- /src/st.js: -------------------------------------------------------------------------------- 1 | import ƒ from "./f"; 2 | 3 | export default function(name, value) { 4 | if (typeof(name) == 'object'){ 5 | for (var key in name){ 6 | addStyle(this, key, name[key]) 7 | } 8 | return this 9 | } else{ 10 | return arguments.length == 1 ? this.style(name) : addStyle(this, name, value) 11 | } 12 | 13 | 14 | function addStyle(sel, style, value){ 15 | var style = style.replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase() 16 | 17 | var pxStyles = 'top left bottom right padding-top padding-left padding-bottom padding-right border-top b-width border-left-width border-botto-width m border-right-width margin-top margin-left margin-bottom margin-right font-size width height stroke-width line-height margin padding border max-width min-width' 18 | 19 | if (~pxStyles.indexOf(style) ){ 20 | sel.style(style, typeof value == 'function' ? ƒ(value, addPx) : addPx(value)) 21 | } else{ 22 | sel.style(style, value) 23 | } 24 | 25 | return sel 26 | } 27 | 28 | function addPx(d){ return d.match ? d : d + 'px' } 29 | }; 30 | -------------------------------------------------------------------------------- /src/translate-selection.js: -------------------------------------------------------------------------------- 1 | export default function(xy) { 2 | return this.attr('transform', function(d,i) { 3 | return 'translate('+[typeof xy == 'function' ? xy.call(this, d,i) : xy]+')'; 4 | }); 5 | }; -------------------------------------------------------------------------------- /src/tspans.js: -------------------------------------------------------------------------------- 1 | export default function(lines, lh) { 2 | return this.selectAll('tspan') 3 | .data(function(d) { 4 | return (typeof(lines) == 'function' ? lines(d) : lines) 5 | .map(function(l) { 6 | return { line: l, parent: d } 7 | }); 8 | }) 9 | .enter() 10 | .append('tspan') 11 | .text(function(d) { return d.line; }) 12 | .attr('x', 0) 13 | .attr('dy', function(d, i) { return i ? (typeof(lh) == 'function' ? lh(d.parent, d.line, i) : lh) || 15 : 0; }); 14 | }; 15 | -------------------------------------------------------------------------------- /src/wordwrap.js: -------------------------------------------------------------------------------- 1 | export default function(line, maxCharactersPerLine) { 2 | var w = line.split(' '), 3 | lines = [], 4 | words = [], 5 | maxChars = maxCharactersPerLine || 40, 6 | l = 0; 7 | 8 | w.forEach(function(d) { 9 | if (l+d.length > maxChars) { 10 | lines.push(words.join(' ')); 11 | words.length = 0; 12 | l = 0; 13 | } 14 | l += d.length; 15 | words.push(d); 16 | }); 17 | if (words.length) { 18 | lines.push(words.join(' ')); 19 | } 20 | return lines.filter(function(d){ return d != '' }); 21 | }; 22 | -------------------------------------------------------------------------------- /test/test-append.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'), 3 | jsdom = require('jsdom'), 4 | d3 = require('d3-selection'); 5 | 6 | 7 | tape('append adds a class and id', function(test) { 8 | var document = jsdom.jsdom('
'); 9 | 10 | d3.select(document.querySelector('div')).append('span#id.class'); 11 | 12 | var span = document.querySelector('span'); 13 | test.equal(span.getAttribute('id'), 'id'); 14 | test.equal(span.getAttribute('class'), 'class'); 15 | test.end(); 16 | }); -------------------------------------------------------------------------------- /test/test-at.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'), 3 | jsdom = require('jsdom'), 4 | d3 = require('d3-selection'); 5 | 6 | 7 | tape('at can look up attributes', function(test) { 8 | var document = jsdom.jsdom('
'); 9 | 10 | var prop = d3.select(document.querySelector('div')).at('prop') 11 | test.equal(prop, 'propVal'); 12 | test.end(); 13 | }); 14 | 15 | tape('at can set attributes', function(test) { 16 | var document = jsdom.jsdom('
'); 17 | 18 | d3.select(document.querySelector('div')).at('prop', 'propVal'); 19 | 20 | test.equal(document.querySelector('div').getAttribute('prop'), 'propVal'); 21 | test.end(); 22 | }); 23 | 24 | tape('at can set attributes with an object', function(test) { 25 | var document = jsdom.jsdom('
'); 26 | 27 | d3.select(document.querySelector('div')).at({prop: 'propVal', width: 100}); 28 | 29 | test.equal(document.querySelector('div').getAttribute('prop'), 'propVal'); 30 | test.equal(document.querySelector('div').getAttribute('width'), '100'); 31 | test.end(); 32 | }); 33 | 34 | tape('camelcase is converted to hypens', function(test) { 35 | var document = jsdom.jsdom('
'); 36 | 37 | d3.select(document.querySelector('div')).at({fillOpacity: 'propVal', maxWidth: 100}); 38 | 39 | test.equal(document.querySelector('div').getAttribute('fill-opacity'), 'propVal'); 40 | test.equal(document.querySelector('div').getAttribute('max-width'), '100'); 41 | test.end(); 42 | }); -------------------------------------------------------------------------------- /test/test-f.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'), 3 | ƒ = jetpack.f 4 | 5 | 6 | tape('ƒ with no arguements is the identity function', function(test) { 7 | test.equal(ƒ()(10), 10); 8 | test.end(); 9 | }); 10 | 11 | tape('strings return object accessors', function(test) { 12 | test.equal(ƒ('prop')({prop: 'myProp'}), 'myProp'); 13 | test.end(); 14 | }); 15 | 16 | tape('function are composed', function(test) { 17 | function addOne(d){ return d + 1 } 18 | test.equal(ƒ('num', addOne)({num: 10.3}), 11.3); 19 | test.end(); 20 | }); 21 | 22 | tape('function are composed', function(test) { 23 | function addOne(d){ return d + 1 } 24 | test.equal(ƒ(addOne, addOne)(10) , 12); 25 | test.end(); 26 | }); -------------------------------------------------------------------------------- /test/test-nestBy.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'); 3 | 4 | 5 | tape('nestBy sets returns an array of arrays with key props', function(test) { 6 | var bySign = jetpack.nestBy([1, 2, 3, 4], function(d){ return d > 0 }) 7 | console.log(bySign[0].key) 8 | test.equal(bySign[0].length, 4); 9 | test.equal(bySign[0].key, 'true'); 10 | test.end(); 11 | }); 12 | 13 | tape('nestBy divides items into seperate groups', function(test) { 14 | var byEven = jetpack.nestBy([1, 2, 3, 4], function(d){ return d % 2 == 0 }) 15 | test.equal(byEven[0].length, 2); 16 | test.equal(byEven[1].length, 2); 17 | test.equal(byEven[0].key == 'true', byEven[1].key != 'true'); 18 | test.end(); 19 | }); 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/test-parseAttributes.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'); 3 | 4 | 5 | tape('parseAttributes can read a class and id', function(test) { 6 | var obj = jetpack.parseAttributes('div.className#idName'); 7 | test.equal(obj.tag, 'div'); 8 | test.equal(obj.attr.class, 'className'); 9 | test.equal(obj.attr.id, 'idName'); 10 | test.end(); 11 | }); 12 | 13 | 14 | tape('parseAttributes can read multiple classes', function(test) { 15 | var obj = jetpack.parseAttributes('div.class1.class2'); 16 | test.equal(obj.attr.class, 'class1 class2'); 17 | test.end(); 18 | }); 19 | 20 | tape('parseAttributes can read tags', function(test) { 21 | var obj = jetpack.parseAttributes('span'); 22 | test.equal(obj.tag, 'span'); 23 | test.end(); 24 | }); -------------------------------------------------------------------------------- /test/test-selectAppend.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'), 3 | jsdom = require('jsdom'), 4 | d3 = require('d3-selection'); 5 | 6 | 7 | tape('selectAppend selects when element exists', function(test) { 8 | var document = jsdom.jsdom('
'); 9 | 10 | var span = document.querySelector('span') 11 | 12 | var d3Span = d3.select(document.querySelector('div')) 13 | .selectAppend('span').node(); 14 | 15 | test.equal(span, d3Span); 16 | test.end(); 17 | }); 18 | 19 | tape('selectAppend appends when element doesn\'t exist', function(test) { 20 | var document = jsdom.jsdom('
'); 21 | 22 | var d3Span = d3.select(document.querySelector('div')) 23 | .selectAppend('span').node(); 24 | 25 | var span = document.querySelector('span') 26 | 27 | test.equal(span, d3Span); 28 | test.end(); 29 | }); 30 | 31 | tape('selectAppend selects each child when element exists', function(test) { 32 | var document = jsdom.jsdom('
'); 33 | 34 | var spans = document.querySelectorAll('span') 35 | 36 | var d3Spans = d3.select(document).selectAll('div') 37 | .selectAppend('span'); 38 | 39 | d3Spans.each(function(d, i) { 40 | test.equal(spans[i], this); 41 | }) 42 | 43 | test.end(); 44 | }); 45 | 46 | tape('selectAppend append each child when element exists', function(test) { 47 | var document = jsdom.jsdom('
'); 48 | 49 | var d3Spans = d3.select(document).selectAll('div') 50 | .selectAppend('span'); 51 | 52 | var spans = document.querySelectorAll('span') 53 | 54 | d3Spans.each(function(d, i) { 55 | test.equal(spans[i], this); 56 | }) 57 | 58 | test.end(); 59 | }); 60 | 61 | tape('selectAppend should select or append each child element based on whether they exist', function(test) { 62 | var document = jsdom.jsdom('
'); 63 | 64 | var d3Spans = d3.select(document).selectAll('div') 65 | .selectAppend('span'); 66 | 67 | var spans = document.querySelectorAll('span') 68 | 69 | test.equal(d3Spans.size(), 2); 70 | 71 | d3Spans.each(function(d, i) { 72 | test.equal(spans[i], this); 73 | }) 74 | 75 | test.end(); 76 | }); 77 | 78 | 79 | tape('selectAppend adds a class and id', function(test) { 80 | var document = jsdom.jsdom('
'); 81 | 82 | d3.select(document.querySelector('div')).selectAppend('span#id.class'); 83 | 84 | var span = document.querySelector('span'); 85 | test.equal(span.getAttribute('id'), 'id'); 86 | test.equal(span.getAttribute('class'), 'class'); 87 | test.end(); 88 | }); 89 | -------------------------------------------------------------------------------- /test/test-st.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'), 3 | jsdom = require('jsdom'), 4 | d3 = require('d3-selection') 5 | 6 | 7 | tape('st can look up styles', function(test) { 8 | var document = jsdom.jsdom('
') 9 | 10 | var prop = d3.select(document.querySelector('div')).st('background') 11 | test.equal(prop, 'green') 12 | test.end() 13 | }) 14 | 15 | tape('st can set styles', function(test) { 16 | var document = jsdom.jsdom('
') 17 | 18 | d3.select(document.querySelector('div')).st('background', 'green') 19 | 20 | test.equal(d3.select(document.querySelector('div')).st('background'), 'green') 21 | test.end() 22 | }) 23 | 24 | tape('st can set style with an object', function(test) { 25 | var document = jsdom.jsdom('
') 26 | 27 | var sel = d3.select(document.querySelector('div')) 28 | 29 | sel.st({color: 'blue', width: '100px'}) 30 | 31 | test.equal(sel.style('color'), 'blue') 32 | test.equal(sel.style('width'), '100px') 33 | test.end() 34 | }) 35 | 36 | tape('camelcase is converted to hypens', function(test) { 37 | var document = jsdom.jsdom('
') 38 | 39 | var sel = d3.select(document.querySelector('div')) 40 | 41 | sel.st({fillOpacity: 1, maxWidth: '100px'}) 42 | 43 | test.equal(sel.style('fill-opacity'), '1') 44 | test.equal(sel.style('max-width'), '100px') 45 | test.end() 46 | }) 47 | 48 | tape('px is appened to numbers', function(test) { 49 | var document = jsdom.jsdom('
') 50 | 51 | var sel = d3.select(document.querySelector('div')) 52 | 53 | sel.st({margin: 1, maxWidth: 100}) 54 | sel.st('height', 20) 55 | 56 | test.equal(sel.style('margin'), '1px') 57 | test.equal(sel.style('max-width'), '100px') 58 | test.equal(sel.style('height'), '20px') 59 | test.end() 60 | }) -------------------------------------------------------------------------------- /test/test-translate-selection.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'), 3 | jsdom = require('jsdom'), 4 | d3 = require('d3-selection'); 5 | 6 | 7 | tape('translate can take an array and set transform string', function(test) { 8 | var document = jsdom.jsdom(); 9 | d3.select(document.body).translate([10, 10]); 10 | test.equal(document.body.getAttribute('transform'), 'translate(10,10)'); 11 | test.end(); 12 | }); 13 | 14 | 15 | tape('translate can take a function and set transform string', function(test) { 16 | var document = jsdom.jsdom(); 17 | d3.select(document.body).translate(function(){ return [10, 10] }); 18 | test.equal(document.body.getAttribute('transform'), 'translate(10,10)'); 19 | test.end(); 20 | }); 21 | -------------------------------------------------------------------------------- /test/test-tspans.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'), 3 | jsdom = require('jsdom'), 4 | d3 = require('d3-selection'); 5 | 6 | 7 | tape('tspans adds a tspans and sets text', function(test) { 8 | var document = global.document = jsdom.jsdom(""); 9 | 10 | try { 11 | d3.select('text').tspans([1, 2, 3]) 12 | test.equal(d3.selectAll('tspan').size(), 3); 13 | test.equal(d3.select('text').text(), '123'); 14 | test.end(); 15 | } finally { 16 | delete global.document; 17 | } 18 | }); -------------------------------------------------------------------------------- /test/test-wordwrap.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'), 2 | jetpack = require('../'); 3 | 4 | 5 | tape('wordwrap second arg sets line length', function(test) { 6 | test.equal(jetpack.wordwrap('two words', 4)[0], 'two'); 7 | test.end(); 8 | }); 9 | 10 | tape('wordwrap default line length is 40', function(test) { 11 | test.equal(jetpack.wordwrap('the default wrap length is 40 - this line will wrap')[1], 'wrap'); 12 | test.end(); 13 | }); 14 | 15 | tape('no blank lines', function(test) { 16 | test.equal(jetpack.wordwrap('thiswordistoolong', 5)[0], 'thiswordistoolong'); 17 | test.end(); 18 | }); 19 | 20 | --------------------------------------------------------------------------------