d3_tooltip.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. // d3.tip
  2. // Copyright (c) 2013 Justin Palmer
  3. //
  4. // Tooltips for d3.js SVG visualizations
  5. (function (root, factory) {
  6. if (typeof define === 'function' && define.amd) {
  7. // AMD. Register as an anonymous module with d3 as a dependency.
  8. define(['d3'], factory)
  9. } else if (typeof module === 'object' && module.exports) {
  10. // CommonJS
  11. module.exports = function(d3) {
  12. d3.tip = factory(d3)
  13. return d3.tip
  14. }
  15. } else {
  16. // Browser global.
  17. root.d3.tip = factory(root.d3)
  18. }
  19. }(this, function (d3) {
  20. // Public - contructs a new tooltip
  21. //
  22. // Returns a tip
  23. return function() {
  24. var direction = d3_tip_direction,
  25. offset = d3_tip_offset,
  26. html = d3_tip_html,
  27. node = initNode(),
  28. svg = null,
  29. point = null,
  30. target = null
  31. function tip(vis) {
  32. svg = getSVGNode(vis)
  33. point = svg.createSVGPoint()
  34. document.body.appendChild(node)
  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[args.length - 1] instanceof SVGElement) target = args.pop()
  42. var content = html.apply(this, args),
  43. poffset = offset.apply(this, args),
  44. dir = direction.apply(this, args),
  45. nodel = getNodeEl(),
  46. i = directions.length,
  47. coords,
  48. scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
  49. scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft
  50. nodel.html(content)
  51. .style({ display: 'block', 'pointer-events': 'all' })
  52. .attr('class', 'd3-tip')
  53. nodel.append('div').attr('class', 'd3-tip-arrow')
  54. while(i--) nodel.classed(directions[i], false)
  55. coords = direction_callbacks.get(dir).apply(this)
  56. nodel.classed(dir, true).style({
  57. top: (coords.top + poffset[0]) + scrollTop + 'px',
  58. left: (coords.left + poffset[1]) + scrollLeft + 'px'
  59. })
  60. return tip
  61. }
  62. // Public - hide the tooltip
  63. //
  64. // Returns a tip
  65. tip.hide = function() {
  66. var nodel = getNodeEl()
  67. nodel.style({ display: 'none', 'pointer-events': 'none' });
  68. return tip
  69. }
  70. // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value.
  71. //
  72. // n - name of the attribute
  73. // v - value of the attribute
  74. //
  75. // Returns tip or attribute value
  76. tip.attr = function(n, v) {
  77. if (arguments.length < 2 && typeof n === 'string') {
  78. return getNodeEl().attr(n)
  79. } else {
  80. var args = Array.prototype.slice.call(arguments)
  81. d3.selection.prototype.attr.apply(getNodeEl(), args)
  82. }
  83. return tip
  84. }
  85. // Public: Proxy style calls to the d3 tip container. Sets or gets a style value.
  86. //
  87. // n - name of the property
  88. // v - value of the property
  89. //
  90. // Returns tip or style property value
  91. tip.style = function(n, v) {
  92. if (arguments.length < 2 && typeof n === 'string') {
  93. return getNodeEl().style(n)
  94. } else {
  95. var args = Array.prototype.slice.call(arguments)
  96. d3.selection.prototype.style.apply(getNodeEl(), args)
  97. }
  98. return tip
  99. }
  100. // Public: Set or get the direction of the tooltip
  101. //
  102. // v - One of n(north), s(south), e(east), or w(west), nw(northwest),
  103. // sw(southwest), ne(northeast) or se(southeast)
  104. //
  105. // Returns tip or direction
  106. tip.direction = function(v) {
  107. if (!arguments.length) return direction
  108. direction = v == null ? v : d3.functor(v)
  109. return tip
  110. }
  111. // Public: Sets or gets the offset of the tip
  112. //
  113. // v - Array of [x, y] offset
  114. //
  115. // Returns offset or
  116. tip.offset = function(v) {
  117. if (!arguments.length) return offset
  118. offset = v == null ? v : d3.functor(v)
  119. return tip
  120. }
  121. // Public: sets or gets the html value of the tooltip
  122. //
  123. // v - String value of the tip
  124. //
  125. // Returns html value or tip
  126. tip.html = function(v) {
  127. if (!arguments.length) return html
  128. html = v == null ? v : d3.functor(v)
  129. return tip
  130. }
  131. // Public: destroys the tooltip and removes it from the DOM
  132. //
  133. // Returns a tip
  134. tip.destroy = function() {
  135. if(node) {
  136. getNodeEl().remove();
  137. node = null;
  138. }
  139. return tip;
  140. }
  141. function d3_tip_direction() { return 'n' }
  142. function d3_tip_offset() { return [0, 0] }
  143. function d3_tip_html() { return ' ' }
  144. var direction_callbacks = d3.map({
  145. n: direction_n,
  146. s: direction_s,
  147. e: direction_e,
  148. w: direction_w,
  149. nw: direction_nw,
  150. ne: direction_ne,
  151. sw: direction_sw,
  152. se: direction_se
  153. }),
  154. directions = direction_callbacks.keys()
  155. function direction_n() {
  156. var bbox = getScreenBBox()
  157. return {
  158. top: bbox.n.y - node.offsetHeight,
  159. left: bbox.n.x - node.offsetWidth / 2
  160. }
  161. }
  162. function direction_s() {
  163. var bbox = getScreenBBox()
  164. return {
  165. top: bbox.s.y,
  166. left: bbox.s.x - node.offsetWidth / 2
  167. }
  168. }
  169. function direction_e() {
  170. var bbox = getScreenBBox()
  171. return {
  172. top: bbox.e.y - node.offsetHeight / 2,
  173. left: bbox.e.x
  174. }
  175. }
  176. function direction_w() {
  177. var bbox = getScreenBBox()
  178. return {
  179. top: bbox.w.y - node.offsetHeight / 2,
  180. left: bbox.w.x - node.offsetWidth
  181. }
  182. }
  183. function direction_nw() {
  184. var bbox = getScreenBBox()
  185. return {
  186. top: bbox.nw.y - node.offsetHeight,
  187. left: bbox.nw.x - node.offsetWidth
  188. }
  189. }
  190. function direction_ne() {
  191. var bbox = getScreenBBox()
  192. return {
  193. top: bbox.ne.y - node.offsetHeight,
  194. left: bbox.ne.x
  195. }
  196. }
  197. function direction_sw() {
  198. var bbox = getScreenBBox()
  199. return {
  200. top: bbox.sw.y,
  201. left: bbox.sw.x - node.offsetWidth
  202. }
  203. }
  204. function direction_se() {
  205. var bbox = getScreenBBox()
  206. return {
  207. top: bbox.se.y,
  208. left: bbox.e.x
  209. }
  210. }
  211. function initNode() {
  212. var node = d3.select(document.createElement('div'))
  213. node.style({
  214. position: 'absolute',
  215. top: 0,
  216. display: 'none',
  217. 'pointer-events': 'none',
  218. 'box-sizing': 'border-box'
  219. })
  220. return node.node()
  221. }
  222. function getSVGNode(el) {
  223. el = el.node()
  224. if(el.tagName.toLowerCase() === 'svg')
  225. return el
  226. return el.ownerSVGElement
  227. }
  228. function getNodeEl() {
  229. if(node === null) {
  230. node = initNode();
  231. // re-add node to DOM
  232. document.body.appendChild(node);
  233. };
  234. return d3.select(node);
  235. }
  236. // Private - gets the screen coordinates of a shape
  237. //
  238. // Given a shape on the screen, will return an SVGPoint for the directions
  239. // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest),
  240. // sw(southwest).
  241. //
  242. // +-+-+
  243. // | |
  244. // + +
  245. // | |
  246. // +-+-+
  247. //
  248. // Returns an Object {n, s, e, w, nw, sw, ne, se}
  249. function getScreenBBox() {
  250. var targetel = target || d3.event.target;
  251. while ('undefined' === typeof targetel.getScreenCTM && 'undefined' === targetel.parentNode) {
  252. targetel = targetel.parentNode;
  253. }
  254. var bbox = {},
  255. matrix = targetel.getScreenCTM(),
  256. tbbox = targetel.getBBox(),
  257. width = tbbox.width,
  258. height = tbbox.height,
  259. x = tbbox.x,
  260. y = tbbox.y
  261. point.x = x
  262. point.y = y
  263. bbox.nw = point.matrixTransform(matrix)
  264. point.x += width
  265. bbox.ne = point.matrixTransform(matrix)
  266. point.y += height
  267. bbox.se = point.matrixTransform(matrix)
  268. point.x -= width
  269. bbox.sw = point.matrixTransform(matrix)
  270. point.y -= height / 2
  271. bbox.w = point.matrixTransform(matrix)
  272. point.x += width
  273. bbox.e = point.matrixTransform(matrix)
  274. point.x -= width / 2
  275. point.y -= height / 2
  276. bbox.n = point.matrixTransform(matrix)
  277. point.y += height
  278. bbox.s = point.matrixTransform(matrix)
  279. return bbox
  280. }
  281. return tip
  282. };
  283. }));