├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── d3-annotate-scatter-click-edit.gif ├── d3-annotate-scatter-click-rm.gif ├── d3-annotate-scatter-click.gif ├── d3-annotate-scatter-drag.gif ├── demo ├── iris.html └── iris.tsv ├── index.js ├── package.json ├── src ├── annotate.js └── attrs.js └── test └── annotate-test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | node_modules 4 | npm-debug.log 5 | favicon.ico 6 | -------------------------------------------------------------------------------- /.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 | # d3-annotate 2 | 3 | Interactively add, edit, move and save SVG chart annotations. 4 | 5 | ## Install 6 | 7 | If you use NPM, `npm install d3-annotate`. Otherwise, download the [latest release](https://github.com/cmpolis/d3-annotate/releases/latest). 8 | 9 | ## Interactions 10 | 11 | ##### Drag to move 12 | 13 | ![drag](https://raw.githubusercontent.com/cmpolis/d3-annotate/master/d3-annotate-scatter-drag.gif) 14 | 15 | ##### Click an element from selection to add annotation 16 | ![click](https://raw.githubusercontent.com/cmpolis/d3-annotate/master/d3-annotate-scatter-click.gif) 17 | 18 | ##### Command + Click to remove an annotation 19 | ![click](https://raw.githubusercontent.com/cmpolis/d3-annotate/master/d3-annotate-scatter-click-rm.gif) 20 | 21 | ##### Shift + Click to edit annotation copy _then [Enter] or unfocus text input to save_ 22 | ![click](https://raw.githubusercontent.com/cmpolis/d3-annotate/master/d3-annotate-scatter-click-edit.gif) 23 | 24 | ## API 25 | 26 | ####`d3.annotate()` 27 | 28 | Initialize a new annotation behavior. 29 | 30 | #### `.container([d3 or selection])` _(required)_ 31 | 32 | Sets container to render annotations to. _Note: should have `translate` to match data. 33 | 34 | #### `.key([fn])` _`(d) => d.id` by default_ 35 | 36 | Sets key to be used for serializing annotations and joining annotations with data 37 | 38 | #### `.text([fn])` _indentity by default_ 39 | 40 | Sets the default text for an annotation. _eg:_ ``.text((d) => `${d.name}: ${d.score}`)`` 41 | 42 | #### `.attr([attrName(eg: x, y, text-anchor, fill)], [value or fn])` (similar to `d3.select(...).attr`) 43 | 44 | `.attr` will get called on `` elements created from annotation. However, instead of only having access to the bound data(`d.data`) - you have access to what is returned from `.getBBox()` of the target element (`d.box`), _eg:_ 45 | 46 | ```js 47 | // Place labels to the left of target element (centered vertically and horizontally by default) 48 | .attr('x', (d) => d.box.x) 49 | .attr('text-anchor', 'end') 50 | 51 | // Color labels based on data in target element 52 | .attr('fill', (d) => palette(d.data.category)) 53 | ``` 54 | 55 | #### `.show([boolean or function])` 56 | 57 | Create annotations automatically(`true` will create an annotation for every datum) 58 | 59 | #### `.saved([annotation object])` 60 | 61 | Add object of annotations to be rendered on `.call(annotation)`, created from calling `annotation.serialize()` 62 | 63 | #### `.serialize()` 64 | 65 | Returns an object of annotations based on current state of annotations. 66 | 67 | 68 | ## Example Usage 69 | 70 | ```js 71 | // render some chart elements with data 72 | var bubbles = chartArea.selectAll('.bubble').data(cars) .... 73 | 74 | var annotation = d3.annotate() 75 | .continer(chartArea.append('g')) 76 | .key((d) => d.model + d.year) 77 | .text((d) => `${d.make} ${d.model}: ${d.mpg} miles per gallon`) 78 | .show((d) => d.year === 2016) // create annotations only for 2016 models, initially 79 | .saved({'prius2015':{text:'Most efficient',x:400,y:600}}) 80 | .attr('fill', (d) => makeColors(d.data.make)); 81 | 82 | d3.selectAll('.bubble').call(annotation); // elements get created 83 | [User interaction to move, edit, rewrite annotations] 84 | JSON.stringify(annotation.serialize()) // can be saved for creating inital annotation state 85 | ``` 86 | 87 | 88 | ## CSS 89 | 90 | To style annotations, use a selector _a la_: 91 | 92 | ```css 93 | #myChart .d3-an-container .annotation 94 | ``` 95 | 96 | For a better experience, add the following CSS to your page or CSS build system: 97 | 98 | ```css 99 | .d3-an-container .annotation { cursor: move; } 100 | .d3-an-container .annotation.dragging { 101 | cursor: grabbing; 102 | cursor: -moz-grabbing; 103 | cursor: -webkit-grabbing; 104 | text-decoration: underline; } 105 | .d3-an-text-edit { 106 | position: fixed; 107 | top: 40px; 108 | left: 40px; } 109 | ``` 110 | 111 | 112 | ### Author 113 | 114 | By [@ChrisPolis](https://twitter.com/ChrisPolis) 115 | 116 | ### License 117 | 118 | This project is licensed under the terms of the MIT license. 119 | -------------------------------------------------------------------------------- /d3-annotate-scatter-click-edit.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmpolis/d3-annotate/3bb0e521f21a2d070a2411f4dff86e08a0a9ad4f/d3-annotate-scatter-click-edit.gif -------------------------------------------------------------------------------- /d3-annotate-scatter-click-rm.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmpolis/d3-annotate/3bb0e521f21a2d070a2411f4dff86e08a0a9ad4f/d3-annotate-scatter-click-rm.gif -------------------------------------------------------------------------------- /d3-annotate-scatter-click.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmpolis/d3-annotate/3bb0e521f21a2d070a2411f4dff86e08a0a9ad4f/d3-annotate-scatter-click.gif -------------------------------------------------------------------------------- /d3-annotate-scatter-drag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmpolis/d3-annotate/3bb0e521f21a2d070a2411f4dff86e08a0a9ad4f/d3-annotate-scatter-drag.gif -------------------------------------------------------------------------------- /demo/iris.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 47 | 48 | 49 | 50 | 153 | -------------------------------------------------------------------------------- /demo/iris.tsv: -------------------------------------------------------------------------------- 1 | sepalLength sepalWidth petalLength petalWidth species 2 | 5.1 3.5 1.4 0.2 setosa 3 | 4.9 3.0 1.4 0.2 setosa 4 | 4.7 3.2 1.3 0.2 setosa 5 | 4.6 3.1 1.5 0.2 setosa 6 | 5.0 3.6 1.4 0.2 setosa 7 | 5.4 3.9 1.7 0.4 setosa 8 | 4.6 3.4 1.4 0.3 setosa 9 | 5.0 3.4 1.5 0.2 setosa 10 | 4.4 2.9 1.4 0.2 setosa 11 | 4.9 3.1 1.5 0.1 setosa 12 | 5.4 3.7 1.5 0.2 setosa 13 | 4.8 3.4 1.6 0.2 setosa 14 | 4.8 3.0 1.4 0.1 setosa 15 | 4.3 3.0 1.1 0.1 setosa 16 | 5.8 4.0 1.2 0.2 setosa 17 | 5.7 4.4 1.5 0.4 setosa 18 | 5.4 3.9 1.3 0.4 setosa 19 | 5.1 3.5 1.4 0.3 setosa 20 | 5.7 3.8 1.7 0.3 setosa 21 | 5.1 3.8 1.5 0.3 setosa 22 | 5.4 3.4 1.7 0.2 setosa 23 | 5.1 3.7 1.5 0.4 setosa 24 | 4.6 3.6 1.0 0.2 setosa 25 | 5.1 3.3 1.7 0.5 setosa 26 | 4.8 3.4 1.9 0.2 setosa 27 | 5.0 3.0 1.6 0.2 setosa 28 | 5.0 3.4 1.6 0.4 setosa 29 | 5.2 3.5 1.5 0.2 setosa 30 | 5.2 3.4 1.4 0.2 setosa 31 | 4.7 3.2 1.6 0.2 setosa 32 | 4.8 3.1 1.6 0.2 setosa 33 | 5.4 3.4 1.5 0.4 setosa 34 | 5.2 4.1 1.5 0.1 setosa 35 | 5.5 4.2 1.4 0.2 setosa 36 | 4.9 3.1 1.5 0.2 setosa 37 | 5.0 3.2 1.2 0.2 setosa 38 | 5.5 3.5 1.3 0.2 setosa 39 | 4.9 3.6 1.4 0.1 setosa 40 | 4.4 3.0 1.3 0.2 setosa 41 | 5.1 3.4 1.5 0.2 setosa 42 | 5.0 3.5 1.3 0.3 setosa 43 | 4.5 2.3 1.3 0.3 setosa 44 | 4.4 3.2 1.3 0.2 setosa 45 | 5.0 3.5 1.6 0.6 setosa 46 | 5.1 3.8 1.9 0.4 setosa 47 | 4.8 3.0 1.4 0.3 setosa 48 | 5.1 3.8 1.6 0.2 setosa 49 | 4.6 3.2 1.4 0.2 setosa 50 | 5.3 3.7 1.5 0.2 setosa 51 | 5.0 3.3 1.4 0.2 setosa 52 | 7.0 3.2 4.7 1.4 versicolor 53 | 6.4 3.2 4.5 1.5 versicolor 54 | 6.9 3.1 4.9 1.5 versicolor 55 | 5.5 2.3 4.0 1.3 versicolor 56 | 6.5 2.8 4.6 1.5 versicolor 57 | 5.7 2.8 4.5 1.3 versicolor 58 | 6.3 3.3 4.7 1.6 versicolor 59 | 4.9 2.4 3.3 1.0 versicolor 60 | 6.6 2.9 4.6 1.3 versicolor 61 | 5.2 2.7 3.9 1.4 versicolor 62 | 5.0 2.0 3.5 1.0 versicolor 63 | 5.9 3.0 4.2 1.5 versicolor 64 | 6.0 2.2 4.0 1.0 versicolor 65 | 6.1 2.9 4.7 1.4 versicolor 66 | 5.6 2.9 3.6 1.3 versicolor 67 | 6.7 3.1 4.4 1.4 versicolor 68 | 5.6 3.0 4.5 1.5 versicolor 69 | 5.8 2.7 4.1 1.0 versicolor 70 | 6.2 2.2 4.5 1.5 versicolor 71 | 5.6 2.5 3.9 1.1 versicolor 72 | 5.9 3.2 4.8 1.8 versicolor 73 | 6.1 2.8 4.0 1.3 versicolor 74 | 6.3 2.5 4.9 1.5 versicolor 75 | 6.1 2.8 4.7 1.2 versicolor 76 | 6.4 2.9 4.3 1.3 versicolor 77 | 6.6 3.0 4.4 1.4 versicolor 78 | 6.8 2.8 4.8 1.4 versicolor 79 | 6.7 3.0 5.0 1.7 versicolor 80 | 6.0 2.9 4.5 1.5 versicolor 81 | 5.7 2.6 3.5 1.0 versicolor 82 | 5.5 2.4 3.8 1.1 versicolor 83 | 5.5 2.4 3.7 1.0 versicolor 84 | 5.8 2.7 3.9 1.2 versicolor 85 | 6.0 2.7 5.1 1.6 versicolor 86 | 5.4 3.0 4.5 1.5 versicolor 87 | 6.0 3.4 4.5 1.6 versicolor 88 | 6.7 3.1 4.7 1.5 versicolor 89 | 6.3 2.3 4.4 1.3 versicolor 90 | 5.6 3.0 4.1 1.3 versicolor 91 | 5.5 2.5 4.0 1.3 versicolor 92 | 5.5 2.6 4.4 1.2 versicolor 93 | 6.1 3.0 4.6 1.4 versicolor 94 | 5.8 2.6 4.0 1.2 versicolor 95 | 5.0 2.3 3.3 1.0 versicolor 96 | 5.6 2.7 4.2 1.3 versicolor 97 | 5.7 3.0 4.2 1.2 versicolor 98 | 5.7 2.9 4.2 1.3 versicolor 99 | 6.2 2.9 4.3 1.3 versicolor 100 | 5.1 2.5 3.0 1.1 versicolor 101 | 5.7 2.8 4.1 1.3 versicolor 102 | 6.3 3.3 6.0 2.5 virginica 103 | 5.8 2.7 5.1 1.9 virginica 104 | 7.1 3.0 5.9 2.1 virginica 105 | 6.3 2.9 5.6 1.8 virginica 106 | 6.5 3.0 5.8 2.2 virginica 107 | 7.6 3.0 6.6 2.1 virginica 108 | 4.9 2.5 4.5 1.7 virginica 109 | 7.3 2.9 6.3 1.8 virginica 110 | 6.7 2.5 5.8 1.8 virginica 111 | 7.2 3.6 6.1 2.5 virginica 112 | 6.5 3.2 5.1 2.0 virginica 113 | 6.4 2.7 5.3 1.9 virginica 114 | 6.8 3.0 5.5 2.1 virginica 115 | 5.7 2.5 5.0 2.0 virginica 116 | 5.8 2.8 5.1 2.4 virginica 117 | 6.4 3.2 5.3 2.3 virginica 118 | 6.5 3.0 5.5 1.8 virginica 119 | 7.7 3.8 6.7 2.2 virginica 120 | 7.7 2.6 6.9 2.3 virginica 121 | 6.0 2.2 5.0 1.5 virginica 122 | 6.9 3.2 5.7 2.3 virginica 123 | 5.6 2.8 4.9 2.0 virginica 124 | 7.7 2.8 6.7 2.0 virginica 125 | 6.3 2.7 4.9 1.8 virginica 126 | 6.7 3.3 5.7 2.1 virginica 127 | 7.2 3.2 6.0 1.8 virginica 128 | 6.2 2.8 4.8 1.8 virginica 129 | 6.1 3.0 4.9 1.8 virginica 130 | 6.4 2.8 5.6 2.1 virginica 131 | 7.2 3.0 5.8 1.6 virginica 132 | 7.4 2.8 6.1 1.9 virginica 133 | 7.9 3.8 6.4 2.0 virginica 134 | 6.4 2.8 5.6 2.2 virginica 135 | 6.3 2.8 5.1 1.5 virginica 136 | 6.1 2.6 5.6 1.4 virginica 137 | 7.7 3.0 6.1 2.3 virginica 138 | 6.3 3.4 5.6 2.4 virginica 139 | 6.4 3.1 5.5 1.8 virginica 140 | 6.0 3.0 4.8 1.8 virginica 141 | 6.9 3.1 5.4 2.1 virginica 142 | 6.7 3.1 5.6 2.4 virginica 143 | 6.9 3.1 5.1 2.3 virginica 144 | 5.8 2.7 5.1 1.9 virginica 145 | 6.8 3.2 5.9 2.3 virginica 146 | 6.7 3.3 5.7 2.5 virginica 147 | 6.7 3.0 5.2 2.3 virginica 148 | 6.3 2.5 5.0 1.9 virginica 149 | 6.5 3.0 5.2 2.0 virginica 150 | 6.2 3.4 5.4 2.3 virginica 151 | 5.9 3.0 5.1 1.8 virginica 152 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export {default as annotate} from "./src/annotate"; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-annotate", 3 | "author": { 4 | "name": "Chris Polis", 5 | "url": "https://twitter.com/ChrisPolis" 6 | }, 7 | "version": "0.2.0", 8 | "description": "Interactively and programmatically add, edit, move and save SVG chart annotations", 9 | "keywords": [ 10 | "d3", 11 | "d3-module", 12 | "svg", 13 | "chart", 14 | "datavis", 15 | "visualization" 16 | ], 17 | "license": "MIT", 18 | "main": "build/d3-annotate.js", 19 | "jsnext:main": "index", 20 | "homepage": "https://github.com/cmpolis/d3-annotate", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/cmpolis/d3-annotate.git" 24 | }, 25 | "scripts": { 26 | "pretest": "rm -rf build && mkdir build && rollup -g d3-drag:d3,d3-selection:d3,d3-selection-multi:d3 -f umd -n d3 -o build/d3-annotate.js -- index.js", 27 | "test": "tape 'test/**/*-test.js'", 28 | "prepublish": "npm run test && echo 'skipping: ./node_modules/.bin/babel build/d3-annotate.js -o build/d3-annotate-es5.js && uglifyjs build/d3-annotate-es5.js -c -m -o build/d3-annotate.min.js'", 29 | "postpublish": "zip -j build/d3-annotate.zip -- LICENSE README.md build/d3-annotate.js build/d3-annotate.min.js" 30 | }, 31 | "dependencies": { 32 | "d3-drag": "1.0", 33 | "d3-selection": "1.0", 34 | "d3-selection-multi": "1.0" 35 | }, 36 | "devDependencies": { 37 | "babel-cli": "^6.14.0", 38 | "babel-preset-es2015": "^6.14.0", 39 | "rollup": "0.35", 40 | "tape": "4", 41 | "uglify-js": "2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/annotate.js: -------------------------------------------------------------------------------- 1 | import {drag} from "d3-drag"; 2 | import {event, select, selection} from "d3-selection"; 3 | 4 | // hacky :( could not get rollup to play nice with d3-selection-multi. << TODO 5 | // import "d3-selection-multi"; 6 | import selection_attrs from "./attrs"; 7 | selection.prototype.attrs = selection_attrs; 8 | 9 | export default function() { 10 | var keyFn = (_, ndx) => ndx, 11 | textFn = (d) => d, 12 | getKey = (d) => d.key, 13 | container, 14 | saved, 15 | mapAnnotationData = (node) => { 16 | return { data: node.__data__, 17 | key: node.__key__, 18 | box: node.getBBox() }; }, 19 | displayAttrs = { 20 | x: (d) => d.box.x + (d.box.width / 2), 21 | y: (d) => d.box.y + (d.box.height / 2), 22 | 'text-anchor': 'middle' 23 | }, 24 | show = true, 25 | dragControl = drag() 26 | .on("start", function() { this.classList.add('dragging'); }) 27 | .on("end", function() { this.classList.remove('dragging'); }) 28 | .on("drag", function () { 29 | var el = select(this); 30 | el.attr('x', +el.attr('x') + event.dx); 31 | el.attr('y', +el.attr('y') + event.dy); 32 | }); 33 | 34 | // 35 | // svg.selectAll('.city').call(annotation) -> 36 | function annotate(_selection) { 37 | 38 | // serialize keys for saving/joining 39 | _selection.nodes().forEach((el, ndx) => { 40 | el.__key__ = keyFn(el.__data__, ndx).toString() }); 41 | 42 | // click selection el to create annotation 43 | _selection.on('click', function() { appendText(select(this)); }); 44 | 45 | // prepopulate and/or add saved annotations 46 | if(show) { appendText(_selection, true); } 47 | if(saved) { appendTextFromData(_selection, saved); } 48 | } 49 | 50 | // 51 | // 52 | function buildAnnotation(sel) { 53 | sel.attr('class', 'annotation with-data') 54 | .attrs(displayAttrs) 55 | .call(dragControl) 56 | .on('click', function() { 57 | if(event.metaKey) { this.remove(); } 58 | else if(event.shiftKey) { _editText(select(this)); } 59 | }); 60 | } 61 | 62 | // 63 | // add new data bound annotation 64 | function appendText(sel, filter) { 65 | var _sel = (show instanceof Function && filter) ? sel.filter(show) : sel, 66 | _textFn = (d) => textFn(d.data), 67 | annotationData = _sel.nodes().map(mapAnnotationData); 68 | 69 | var textSel = container.selectAll('text.with-data').data(annotationData, getKey); 70 | textSel.enter().append('text') 71 | .text(_textFn) 72 | .call(buildAnnotation); 73 | } 74 | function appendTextFromData(sel) { 75 | var savedKeys = Object.keys(saved), 76 | savedNodes = sel.filter(function() { 77 | return savedKeys.indexOf(this.__key__) !== -1; }), 78 | savedData = savedNodes.nodes().map(mapAnnotationData); 79 | 80 | var savedSel= container.selectAll('text.with-data').data(savedData, getKey); 81 | savedSel.enter().append('text').call(buildAnnotation) 82 | .merge(savedSel) 83 | .text((d) => saved[d.key].text) 84 | .attr('x', (d) => saved[d.key].x) 85 | .attr('y', (d) => saved[d.key].y); 86 | } 87 | 88 | // 89 | // text editor 90 | function _editText(el) { 91 | select('body').append('input') 92 | .attr('type', 'text') 93 | .attr('class', 'd3-an-text-edit') 94 | .attr('value', el.text()) 95 | .on('keyup', function() { event.keyCode === 13 && this.blur(); }) // ESC 96 | .on('focusout', function() { el.text(this.value) && this.remove(); }) 97 | .node().focus(); 98 | } 99 | 100 | // 101 | // return serialize pojo of annotations 102 | annotate.serialize = function() { 103 | var annotations = {}; 104 | container.selectAll('text.with-data').each(function() { 105 | var sel = d3.select(this); 106 | annotations[this.__data__.key] = { 107 | x: sel.attr('x'), 108 | y: sel.attr('y'), 109 | text: sel.text() 110 | }; 111 | }); 112 | return annotations; 113 | }; 114 | 115 | // 116 | // properties 117 | annotate.container = function(_) { 118 | if(!arguments.length) return container; 119 | container = _; 120 | container.classed('d3-an-container', true); 121 | return annotate; 122 | }; 123 | 124 | // TODO: 125 | // - handle Array for dataless annotation 126 | // - joining multiple .saved() calls 127 | annotate.saved = function(_) { 128 | saved = _; 129 | return annotate; 130 | }; 131 | annotate.text = function(_) { 132 | if(!arguments.length) return text; 133 | textFn = _; return annotate; 134 | }; 135 | annotate.key = function(_) { 136 | if(!arguments.length) return keyFn; 137 | keyFn = _; return annotate; 138 | }; 139 | annotate.show = function(_) { 140 | if(!arguments.length) return show; 141 | show = _; return annotate; 142 | }; 143 | annotate.attr = function(attrName) { 144 | if(!attrName) { 145 | return displayAttrs; 146 | } else if(arguments.length === 1) { 147 | return displayAttrs[attrName]; 148 | } else { 149 | arguments[1] === null ? (delete displayAttrs[attrName]) : 150 | (displayAttrs[attrName] = arguments[1]); 151 | return annotate; 152 | } 153 | }; 154 | 155 | return annotate; 156 | }; 157 | -------------------------------------------------------------------------------- /src/attrs.js: -------------------------------------------------------------------------------- 1 | // https://github.com/d3/d3-selection-multi/blob/master/src/selection/attrs.js 2 | import {select} from "d3-selection"; 3 | 4 | function attrsFunction(selection, map) { 5 | return selection.each(function() { 6 | var x = map.apply(this, arguments), s = select(this); 7 | for (var name in x) s.attr(name, x[name]); 8 | }); 9 | } 10 | 11 | function attrsObject(selection, map) { 12 | for (var name in map) selection.attr(name, map[name]); 13 | return selection; 14 | } 15 | 16 | export default function(map) { 17 | return (typeof map === "function" ? attrsFunction : attrsObject)(this, map); 18 | } 19 | -------------------------------------------------------------------------------- /test/annotate-test.js: -------------------------------------------------------------------------------- 1 | var tape = require("tape"), 2 | annotate = require("../"); 3 | 4 | 5 | tape("annotate is a thing", function(test) { 6 | test.ok(annotate); 7 | test.end(); 8 | }); 9 | --------------------------------------------------------------------------------