├── .npmignore
├── index.js
├── test
└── tip-test.js
├── package.json
├── LICENSE
├── src
├── d3-tip.css
└── d3-tip.js
├── index.html
├── .gitignore
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | build/*.zip
2 | test/
3 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export {default as tip} from "./src/d3-tip";
2 |
--------------------------------------------------------------------------------
/test/tip-test.js:
--------------------------------------------------------------------------------
1 | var tape = require("tape");
2 |
3 | tape("foo() returns the answer to the ultimate question of life, the universe, and everything.", function(test) {
4 | test.end();
5 | });
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "d3-v6-tip",
3 | "version": "1.0.9",
4 | "description": "d3-v6-tip Module",
5 | "keywords": [
6 | "d3",
7 | "d3-module"
8 | ],
9 | "license": "BSD-3-Clause",
10 | "main": "build/d3-v6-tip.js",
11 | "jsnext:main": "index",
12 | "homepage": "https://github.com/bumbeishvili/d3.tip-for-d3.v6",
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/bumbeishvili/d3.tip-for-d3.v6.git"
16 | },
17 | "scripts": {
18 | "pretest": "rm -rf build && mkdir build && rollup -g d3-selection:d3 -f umd -n d3 -o build/d3-v6-tip.js -- index.js",
19 | "test": "tape 'test/**/*-test.js'",
20 | "prepublish": "npm run test && uglifyjs build/d3-v6-tip.js -c -m -o build/d3-v6-tip.min.js",
21 | "postpublish": "zip -j build/d3-v6-tip.zip -- LICENSE README.md build/d3-v6-tip.js build/d3-v6-tip.min.js"
22 | },
23 | "devDependencies": {
24 | "rollup": "0.27",
25 | "tape": "4",
26 | "uglify-es": "3"
27 | },
28 | "dependencies": {
29 | "d3-selection": "2"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 David Bumbeishvili
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/d3-tip.css:
--------------------------------------------------------------------------------
1 | .d3-tip {
2 | font-family: Arial, Helvetica, sans-serif;
3 | line-height: 1.4;
4 | padding: 20px;
5 | pointer-events: none !important;
6 | color: #203d5d;
7 | box-shadow: 0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2);
8 | background-color: #fff;
9 | border-radius: 4px;
10 | }
11 |
12 | /* Creates a small triangle extender for the tooltip */
13 | .d3-tip:after {
14 | box-sizing: border-box;
15 | display: inline;
16 | font-size: 10px;
17 | width: 100%;
18 | line-height: 1;
19 | color: #fff;
20 | position: absolute;
21 | pointer-events: none;
22 | }
23 |
24 | /* Northward tooltips */
25 | .d3-tip.n:after {
26 | content: "▼";
27 | margin: -1px 0 0 0;
28 | top: 100%;
29 | left: 0;
30 | text-align: center;
31 | }
32 |
33 | /* Eastward tooltips */
34 | .d3-tip.e:after {
35 | content: "◀";
36 | margin: -4px 0 0 0;
37 | top: 50%;
38 | left: -8px;
39 | }
40 |
41 | /* Southward tooltips */
42 | .d3-tip.s:after {
43 | content: "▲";
44 | margin: 0 0 1px 0;
45 | top: -8px;
46 | left: 0;
47 | text-align: center;
48 | }
49 |
50 | /* Westward tooltips */
51 | .d3-tip.w:after {
52 | content: "▶";
53 | margin: -4px 0 0 -1px;
54 | top: 50%;
55 | left: 100%;
56 | }
57 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hover over bars to see tooltip!
7 |
8 |
9 |
10 |
11 |
12 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # Snowpack dependency directory (https://snowpack.dev/)
45 | web_modules/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 | .parcel-cache
78 |
79 | # Next.js build output
80 | .next
81 | out
82 |
83 | # Nuxt.js build / generate output
84 | .nuxt
85 | dist
86 |
87 | # Gatsby files
88 | .cache/
89 | # Comment in the public line in if your project uses Gatsby and not Next.js
90 | # https://nextjs.org/blog/next-9-1#public-directory-support
91 | # public
92 |
93 | # vuepress build output
94 | .vuepress/dist
95 |
96 | # Serverless directories
97 | .serverless/
98 |
99 | # FuseBox cache
100 | .fusebox/
101 |
102 | # DynamoDB Local files
103 | .dynamodb/
104 |
105 | # TernJS port file
106 | .tern-port
107 |
108 | # Stores VSCode versions used for testing VSCode extensions
109 | .vscode-test
110 |
111 | # yarn v2
112 | .yarn/cache
113 | .yarn/unplugged
114 | .yarn/build-state.yml
115 | .yarn/install-state.gz
116 | .pnp.*
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
2 |
3 |
4 | # d3-v6-tip
5 | A famous d3-tip lib adapted to the latest - d3.v6 version.
6 |
7 | 
8 |
9 |
10 | ## Foreword
11 |
12 | d3.v6 introduced several changes and some of them concerns d3-tip.
13 |
14 | Those are:
15 |
16 | * Global d3.event has been removed
17 | * Every event handler, from now on, will receive event as a first argument
18 |
19 | d3-tip version which lies under this repository, is adapted to this change.
20 |
21 | It also fixes one annoying bug, when several DOM tip instances were being created , which eventually would lead unexpected and undesirable results.
22 |
23 | See [original documentation](https://github.com/caged/d3-tip/blob/master/docs/index.md), but please note changes in `tip.html` API.
24 | Short story is that, you will get same arguments in `tip.html()` as `tip.show()` receives, in the same order.
25 |
26 | ## Installing
27 | If you are using npm
28 |
29 | ```npm i d3-v6-tip```
30 |
31 | And then use it like this in your application
32 | ```javascript
33 | import { tip as d3tip } from "d3-v6-tip";
34 |
35 | const tip = d3tip()
36 | ```
37 |
38 | Otherwise, you can load as a standalone library or as part of D3. ES modules, AMD, CommonJS, and vanilla environments are supported. In vanilla, a d3 global is exported:
39 |
40 | If you want to load it as part of d3
41 | ```html
42 |
43 |
44 |
45 |
46 |
51 | ```
52 |
53 | If you want to load it as standalone
54 | ```html
55 |
56 |
57 |
58 |
59 |
93 | ```
94 |
95 |
96 | See [minimal jsfiddle example here](https://jsfiddle.net/aftjeb0g/2/)
97 |
98 |
99 |
100 |
101 | ## Style
102 | for default styling, include this [css file](https://github.com/bumbeishvili/d3-v6-tip/blob/master/src/d3-tip.css) into your app
103 | ```css
104 | .d3-tip {
105 | font-family: Arial, Helvetica, sans-serif;
106 | line-height: 1.4;
107 | padding: 20px;
108 | pointer-events: none !important;
109 | color: #203d5d;
110 | box-shadow: 0 4px 20px 4px rgba(0, 20, 60, .1), 0 4px 80px -8px rgba(0, 20, 60, .2);
111 | background-color: #fff;
112 | border-radius: 4px;
113 | }
114 |
115 | /* Creates a small triangle extender for the tooltip */
116 | .d3-tip:after {
117 | box-sizing: border-box;
118 | display: inline;
119 | font-size: 10px;
120 | width: 100%;
121 | line-height: 1;
122 | color: #fff;
123 | position: absolute;
124 | pointer-events: none;
125 | }
126 |
127 | /* Northward tooltips */
128 | .d3-tip.n:after {
129 | content: "▼";
130 | margin: -1px 0 0 0;
131 | top: 100%;
132 | left: 0;
133 | text-align: center;
134 | }
135 |
136 | /* Eastward tooltips */
137 | .d3-tip.e:after {
138 | content: "◀";
139 | margin: -4px 0 0 0;
140 | top: 50%;
141 | left: -8px;
142 | }
143 |
144 | /* Southward tooltips */
145 | .d3-tip.s:after {
146 | content: "▲";
147 | margin: 0 0 1px 0;
148 | top: -8px;
149 | left: 0;
150 | text-align: center;
151 | }
152 |
153 | /* Westward tooltips */
154 | .d3-tip.w:after {
155 | content: "▶";
156 | margin: -4px 0 0 -1px;
157 | top: 50%;
158 | left: 100%;
159 | }
160 | ```
161 |
162 |
163 | ## History
164 |
165 | * [Caged](https://github.com/caged/d3-tip) created d3-tip for d3.v3
166 | * [cgav](https://github.com/cgav/d3-tip) converted it to d3-v4 and ES6 code
167 | * [VACLAB](https://github.com/VACLab/d3-tip) removed ES6 specific code for wider browser support
168 | * [bumbeishvili](https://github.com/bumbeishvili/d3-tip-v6) adapted for newer d3.v6 version and [published to NPM](https://www.npmjs.com/package/d3-v6-tip)
169 |
170 | ## Author
171 | [David B (twitter)](https://twitter.com/dbumbeishvili)
172 | [David B (linkedin)](https://www.linkedin.com/in/bumbeishvili/)
173 |
174 | I am available for freelance data visualization work. Please [contact me](https://davidb.dev/about) in case you'd like me to help you with my experience and expertise
175 |
176 | You can also [book data viz related consultation session](https://www.fiverr.com/share/4XxG21) with me
177 |
--------------------------------------------------------------------------------
/src/d3-tip.js:
--------------------------------------------------------------------------------
1 | // d3.tip
2 | // Copyright (c) 2013 Justin Palmer
3 | // ES6 / D3 v4 Adaption Copyright (c) 2016 Constantin Gavrilete
4 | // Removal of ES6 for D3 v4 Adaption Copyright (c) 2016 David Gotz
5 | // Adaptation for d3.v6 and publish on NPM by bumbeishvili (github.com/bumbeishvili) - npm: d3-v6-tip
6 | //
7 |
8 | // Tooltips for d3.js SVG visualizations
9 | // import * as d3 from d3;
10 |
11 | import {select,selection} from "d3-selection";
12 |
13 |
14 | export default function () {
15 | const functor = function functor(v) {
16 | return typeof v === "function" ? v : function () {
17 | return v;
18 | };
19 | };
20 |
21 | var direction = d3_tip_direction,
22 | offset = d3_tip_offset,
23 | html = d3_tip_html,
24 | node = initNode(),
25 | svg = null,
26 | point = null,
27 | target = null,
28 | rootElement = functor(document.body)
29 |
30 | function tip(vis) {
31 | svg = getSVGNode(vis)
32 | point = svg.createSVGPoint()
33 | rootElement().appendChild(node)
34 | }
35 |
36 | // Public - show the tooltip on the screen
37 | //
38 | // Returns a tip
39 | tip.show = function () {
40 | var args = Array.prototype.slice.call(arguments)
41 | if (args[0] instanceof Event) {
42 | target = args[0].target;
43 | }
44 | if (args[args.length - 1] instanceof SVGElement) target = args.pop()
45 |
46 | var content = html.apply(this, args),
47 | poffset = offset.apply(this, args),
48 | dir = direction.apply(this, args),
49 | nodel = getNodeEl(),
50 | i = directions.length,
51 | coords,
52 | scrollTop = document.documentElement.scrollTop || rootElement().scrollTop,
53 | scrollLeft = document.documentElement.scrollLeft || rootElement().scrollLeft
54 |
55 | nodel.html(content)
56 | .style('position', 'absolute')
57 | .style('opacity', 1)
58 | .style('pointer-events', 'all')
59 |
60 | while (i--) nodel.classed(directions[i], false)
61 | coords = direction_callbacks[dir].apply(this)
62 | nodel.classed(dir, true)
63 | .style('top', (coords.top + poffset[0]) + scrollTop + 'px')
64 | .style('left', (coords.left + poffset[1]) + scrollLeft + 'px')
65 |
66 | return tip
67 | }
68 |
69 | // Public - hide the tooltip
70 | //
71 | // Returns a tip
72 | tip.hide = function () {
73 | var nodel = getNodeEl()
74 | nodel
75 | .style('opacity', 0)
76 | .style('pointer-events', 'none')
77 | return tip
78 | }
79 |
80 | // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value.
81 | //
82 | // n - name of the attribute
83 | // v - value of the attribute
84 | //
85 | // Returns tip or attribute value
86 | tip.attr = function (n, v) {
87 | if (arguments.length < 2 && typeof n === 'string') {
88 | return getNodeEl().attr(n)
89 | } else {
90 | var args = Array.prototype.slice.call(arguments)
91 | if (n == 'class') {
92 | // Prevent internal class override internally
93 | getNodeEl()
94 | .classed(v, true);
95 | } else {
96 | selection.prototype.attr.apply(getNodeEl(), args)
97 | }
98 |
99 | }
100 |
101 | return tip
102 | }
103 |
104 | // Public: Proxy style calls to the d3 tip container. Sets or gets a style value.
105 | //
106 | // n - name of the property
107 | // v - value of the property
108 | //
109 | // Returns tip or style property value
110 | tip.style = function (n, v) {
111 | if (arguments.length < 2 && typeof n === 'string') {
112 | return getNodeEl().style(n)
113 | } else {
114 | var args = Array.prototype.slice.call(arguments);
115 | if (args.length === 1) {
116 | var styles = args[0];
117 | Object.keys(styles).forEach(function (key) {
118 | return selection.prototype.style.apply(getNodeEl(), [key, styles[key]]);
119 | });
120 | }
121 | }
122 |
123 | return tip
124 | }
125 |
126 | // Public: Set or get the direction of the tooltip
127 | //
128 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
129 | // sw(southwest), ne(northeast) or se(southeast)
130 | //
131 | // Returns tip or direction
132 | tip.direction = function (v) {
133 | if (!arguments.length) return direction
134 | direction = v == null ? v : functor(v)
135 |
136 | return tip
137 | }
138 |
139 | // Public: Sets or gets the offset of the tip
140 | //
141 | // v - Array of [x, y] offset
142 | //
143 | // Returns offset or
144 | tip.offset = function (v) {
145 | if (!arguments.length) return offset
146 | offset = v == null ? v : functor(v)
147 |
148 | return tip;
149 | }
150 |
151 | // Public: sets or gets the html value of the tooltip
152 | //
153 | // v - String value of the tip
154 | //
155 | // Returns html value or tip
156 | tip.html = function (v) {
157 | if (!arguments.length) return html
158 | html = v == null ? v : functor(v)
159 |
160 | return tip
161 | }
162 |
163 | // Public: sets or gets the root element anchor of the tooltip
164 | //
165 | // v - the html rootElement
166 | //
167 | // Returns root node of tip
168 | tip.rootElement = function (v) {
169 | if (!arguments.length) return rootElement
170 | rootElement = v == null ? v : functor(v)
171 |
172 | return tip
173 | }
174 |
175 | // Public: destroys the tooltip and removes it from the DOM
176 | //
177 | // Returns a tip
178 | tip.destroy = function () {
179 | if (node) {
180 | getNodeEl().remove();
181 | node = null;
182 | }
183 | return tip;
184 | }
185 |
186 | function d3_tip_direction() { return 'n' }
187 | function d3_tip_offset() { return [0, 0] }
188 | function d3_tip_html() { return ' ' }
189 |
190 | var direction_callbacks = {
191 | n: direction_n,
192 | s: direction_s,
193 | e: direction_e,
194 | w: direction_w,
195 | nw: direction_nw,
196 | ne: direction_ne,
197 | sw: direction_sw,
198 | se: direction_se
199 | };
200 |
201 | var directions = Object.keys(direction_callbacks);
202 |
203 | function direction_n() {
204 | var bbox = getScreenBBox()
205 | return {
206 | top: bbox.n.y - node.offsetHeight,
207 | left: bbox.n.x - node.offsetWidth / 2
208 | }
209 | }
210 |
211 | function direction_s() {
212 | var bbox = getScreenBBox()
213 | return {
214 | top: bbox.s.y,
215 | left: bbox.s.x - node.offsetWidth / 2
216 | }
217 | }
218 |
219 | function direction_e() {
220 | var bbox = getScreenBBox()
221 | return {
222 | top: bbox.e.y - node.offsetHeight / 2,
223 | left: bbox.e.x
224 | }
225 | }
226 |
227 | function direction_w() {
228 | var bbox = getScreenBBox()
229 | return {
230 | top: bbox.w.y - node.offsetHeight / 2,
231 | left: bbox.w.x - node.offsetWidth
232 | }
233 | }
234 |
235 | function direction_nw() {
236 | var bbox = getScreenBBox()
237 | return {
238 | top: bbox.nw.y - node.offsetHeight,
239 | left: bbox.nw.x - node.offsetWidth
240 | }
241 | }
242 |
243 | function direction_ne() {
244 | var bbox = getScreenBBox()
245 | return {
246 | top: bbox.ne.y - node.offsetHeight,
247 | left: bbox.ne.x
248 | }
249 | }
250 |
251 | function direction_sw() {
252 | var bbox = getScreenBBox()
253 | return {
254 | top: bbox.sw.y,
255 | left: bbox.sw.x - node.offsetWidth
256 | }
257 | }
258 |
259 | function direction_se() {
260 | var bbox = getScreenBBox()
261 | return {
262 | top: bbox.se.y,
263 | left: bbox.e.x
264 | }
265 | }
266 |
267 | function initNode() {
268 | let tipNode2 = 2;
269 | let tipNode = select('.d3-tip-lib-node');
270 | if (!tipNode.node()) {
271 | tipNode = select(document.createElement('div'))
272 | }
273 | tipNode
274 | .classed('d3-tip-lib-node', true)
275 | .style('position', 'absolute')
276 | .style('top', '0')
277 | .style('opacity', '0')
278 | .style('pointer-events', 'none')
279 | .style('box-sizing', 'border-box')
280 |
281 | return tipNode.node()
282 | }
283 |
284 | function getSVGNode(el) {
285 | el = el.node()
286 | if (el.tagName.toLowerCase() === 'svg')
287 | return el
288 |
289 | return el.ownerSVGElement
290 | }
291 |
292 | function getNodeEl() {
293 | if (node === null) {
294 | node = initNode();
295 | // re-add node to DOM
296 | document.body.appendChild(node);
297 | };
298 | return select(node);
299 | }
300 |
301 | // Private - gets the screen coordinates of a shape
302 | //
303 | // Given a shape on the screen, will return an SVGPoint for the directions
304 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
305 | // sw(southwest).
306 | //
307 | // +-+-+
308 | // | |
309 | // + +
310 | // | |
311 | // +-+-+
312 | //
313 | // Returns an Object {n, s, e, w, nw, sw, ne, se}
314 | function getScreenBBox() {
315 | var targetel = target || d3.event.target;
316 |
317 | while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) {
318 | targetel = targetel.parentNode;
319 | }
320 |
321 | var bbox = {},
322 | matrix = targetel.getScreenCTM(),
323 | tbbox = targetel.getBBox(),
324 | width = tbbox.width,
325 | height = tbbox.height,
326 | x = tbbox.x,
327 | y = tbbox.y
328 |
329 | point.x = x
330 | point.y = y
331 | bbox.nw = point.matrixTransform(matrix)
332 | point.x += width
333 | bbox.ne = point.matrixTransform(matrix)
334 | point.y += height
335 | bbox.se = point.matrixTransform(matrix)
336 | point.x -= width
337 | bbox.sw = point.matrixTransform(matrix)
338 | point.y -= height / 2
339 | bbox.w = point.matrixTransform(matrix)
340 | point.x += width
341 | bbox.e = point.matrixTransform(matrix)
342 | point.x -= width / 2
343 | point.y -= height / 2
344 | bbox.n = point.matrixTransform(matrix)
345 | point.y += height
346 | bbox.s = point.matrixTransform(matrix)
347 |
348 | return bbox
349 | }
350 |
351 | return tip
352 | };
353 |
--------------------------------------------------------------------------------