nv.d3.datamaps.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. (function() {
  17. var svg;
  18. //save off default references
  19. var d3 = window.d3, topojson = window.topojson;
  20. var defaultOptions = {
  21. scope: 'world',
  22. setProjection: setProjection,
  23. projection: 'equirectangular',
  24. dataType: 'json',
  25. onClick: function() {},
  26. done: function() {},
  27. fills: {
  28. defaultFill: '#ABDDA4'
  29. },
  30. geographyConfig: {
  31. dataUrl: null,
  32. hideAntarctica: true,
  33. borderWidth: 1,
  34. borderColor: '#FDFDFD',
  35. popupTemplate: function(geography, data) {
  36. return '<div class="hoverinfo"><strong>' + geography.properties.name + '</strong></div>';
  37. },
  38. popupOnHover: true,
  39. highlightOnHover: true,
  40. highlightFillColor: '#FC8D59',
  41. highlightBorderColor: 'rgba(250, 15, 160, 0.2)',
  42. selectedFillColor: '#666666',
  43. selectedBorderColor: '#666666',
  44. highlightBorderWidth: 2
  45. },
  46. bubblesConfig: {
  47. borderWidth: 2,
  48. borderColor: '#FFFFFF',
  49. popupOnHover: true,
  50. popupTemplate: function(geography, data) {
  51. return '<div class="hoverinfo"><strong>' + data.name + '</strong></div>';
  52. },
  53. fillOpacity: 0.75,
  54. animate: true,
  55. highlightOnHover: true,
  56. highlightFillColor: '#FC8D59',
  57. highlightBorderColor: 'rgba(250, 15, 160, 0.2)',
  58. highlightBorderWidth: 2,
  59. highlightFillOpacity: 0.85,
  60. exitDelay: 100
  61. },
  62. arcConfig: {
  63. strokeColor: '#DD1C77',
  64. strokeWidth: 1,
  65. arcSharpness: 1,
  66. animationSpeed: 600
  67. }
  68. };
  69. function addContainer( element ) {
  70. this.svg = d3.select( element ).append('svg')
  71. .attr('width', element.offsetWidth)
  72. .attr('class', 'datamap')
  73. .attr('height', element.offsetHeight);
  74. return this.svg;
  75. }
  76. // setProjection takes the svg element and options
  77. function setProjection( element, options ) {
  78. var projection, path;
  79. if ( options && typeof options.scope === 'undefined') {
  80. options.scope = 'world';
  81. }
  82. if ( options.scope === 'usa' ) {
  83. projection = d3.geo.albersUsa()
  84. .scale(element.offsetWidth)
  85. .translate([element.offsetWidth / 2, element.offsetHeight / 2]);
  86. }
  87. else if ( options.scope === 'world' ) {
  88. projection = d3.geo[options.projection]()
  89. .scale((element.offsetWidth + 1) / 2 / Math.PI)
  90. .translate([element.offsetWidth / 2, element.offsetHeight / (options.projection === "mercator" ? 1.45 : 1.8)]);
  91. }
  92. path = d3.geo.path()
  93. .projection( projection );
  94. return {path: path, projection: projection};
  95. }
  96. function addStyleBlock() {
  97. if ( d3.select('.datamaps-style-block').empty() ) {
  98. d3.select('head').attr('class', 'datamaps-style-block').append('style')
  99. .html('.datamap path {stroke: #FFFFFF; stroke-width: 1px;} .datamaps-legend dt, .datamaps-legend dd { float: left; margin: 0 3px 0 0;} .datamaps-legend dd {width: 20px; margin-right: 6px; border-radius: 3px;} .datamaps-legend {padding-bottom: 20px; z-index: 1001; position: absolute; left: 4px; font-size: 12px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;} .datamaps-hoverover {display: none; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } .hoverinfo {padding: 4px; border-radius: 1px; background-color: #FFF; box-shadow: 1px 1px 5px #CCC; font-size: 12px; border: 1px solid #CCC; } .hoverinfo hr {border:1px dotted #CCC; }');
  100. }
  101. }
  102. function drawSubunits( data ) {
  103. var fillData = this.options.fills,
  104. colorCodeData = this.options.data || {},
  105. geoConfig = this.options.geographyConfig,
  106. onClick = this.options.onClick;
  107. var subunits = this.svg.select('g.datamaps-subunits');
  108. if ( subunits.empty() ) {
  109. subunits = this.addLayer('datamaps-subunits', null, true);
  110. }
  111. var geoData = topojson.feature( data, data.objects[ this.options.scope ] ).features;
  112. if ( geoConfig.hideAntarctica ) {
  113. geoData = geoData.filter(function(feature) {
  114. return feature.id !== "ATA";
  115. });
  116. }
  117. var geo = subunits.selectAll('path.datamaps-subunit').data( geoData );
  118. geo.enter()
  119. .append('path')
  120. .attr('d', this.path)
  121. .attr('class', function(d) {
  122. return 'datamaps-subunit ' + d.id;
  123. })
  124. .attr('data-info', function(d) {
  125. return JSON.stringify( colorCodeData[d.id]);
  126. })
  127. .style('fill', function(d) {
  128. var fillColor;
  129. if ( colorCodeData[d.id] ) {
  130. fillColor = fillData[ colorCodeData[d.id].fillKey ];
  131. if (colorCodeData[d.id].selected){
  132. fillColor = geoConfig.selectedFillColor;
  133. }
  134. }
  135. return fillColor || fillData.defaultFill;
  136. })
  137. .on('click', function(d) {
  138. if ( colorCodeData[d.id] && typeof onClick != "undefined") {
  139. onClick(colorCodeData[d.id]);
  140. }
  141. })
  142. .style('stroke-width', function(d) {
  143. var strokeWidth = geoConfig.borderWidth;
  144. if ( colorCodeData[d.id] && colorCodeData[d.id].selected ) {
  145. strokeWidth = 2;
  146. }
  147. return strokeWidth;
  148. })
  149. .style('stroke', function(d) {
  150. var strokeColor = geoConfig.borderColor;
  151. if ( colorCodeData[d.id] && colorCodeData[d.id].selected ) {
  152. strokeColor = geoConfig.selectedBorderColor;
  153. }
  154. return strokeColor;
  155. });
  156. }
  157. function handleGeographyConfig () {
  158. var hoverover;
  159. var svg = this.svg;
  160. var self = this;
  161. var options = this.options.geographyConfig;
  162. if ( options.highlightOnHover || options.popupOnHover ) {
  163. svg.selectAll('.datamaps-subunit')
  164. .on('mouseover', function(d) {
  165. var $this = d3.select(this);
  166. if ( options.highlightOnHover ) {
  167. var previousAttributes = {
  168. 'fill': $this.style('fill'),
  169. 'stroke': $this.style('stroke'),
  170. 'stroke-width': $this.style('stroke-width'),
  171. 'fill-opacity': $this.style('fill-opacity')
  172. };
  173. $this
  174. .style('fill', options.highlightFillColor)
  175. .style('stroke', options.highlightBorderColor)
  176. .style('stroke-width', options.highlightBorderWidth)
  177. .style('fill-opacity', options.highlightFillOpacity)
  178. .attr('data-previousAttributes', JSON.stringify(previousAttributes));
  179. //as per discussion on https://github.com/markmarkoh/datamaps/issues/19
  180. if ( ! /MSIE/.test(navigator.userAgent) ) {
  181. moveToFront.call(this);
  182. }
  183. }
  184. if ( options.popupOnHover ) {
  185. self.updatePopup($this, d, options, svg);
  186. }
  187. })
  188. .on('mouseout', function() {
  189. var $this = d3.select(this);
  190. if (options.highlightOnHover) {
  191. //reapply previous attributes
  192. var previousAttributes = JSON.parse( $this.attr('data-previousAttributes') );
  193. for ( var attr in previousAttributes ) {
  194. $this.style(attr, previousAttributes[attr]);
  195. }
  196. }
  197. $this.on('mousemove', null);
  198. d3.selectAll('.datamaps-hoverover').style('display', 'none');
  199. });
  200. }
  201. function moveToFront() {
  202. this.parentNode.appendChild(this);
  203. }
  204. }
  205. //plugin to add a simple map legend
  206. function addLegend(layer, data, options) {
  207. data = data || {};
  208. if ( !this.options.fills ) {
  209. return;
  210. }
  211. var html = '<dl>';
  212. var label = '';
  213. if ( data.legendTitle ) {
  214. html = '<h2>' + data.legendTitle + '</h2>' + html;
  215. }
  216. for ( var fillKey in this.options.fills ) {
  217. if ( fillKey === 'defaultFill') {
  218. if (! data.defaultFillName ) {
  219. continue;
  220. }
  221. label = data.defaultFillName;
  222. } else {
  223. if (data.labels && data.labels[fillKey]) {
  224. label = data.labels[fillKey];
  225. } else {
  226. label= fillKey + ': ';
  227. }
  228. }
  229. html += '<dt>' + label + '</dt>';
  230. html += '<dd style="background-color:' + this.options.fills[fillKey] + '">&nbsp;</dd>';
  231. }
  232. html += '</dl>';
  233. var hoverover = d3.select( this.options.element ).append('div')
  234. .attr('class', 'datamaps-legend')
  235. .html(html);
  236. }
  237. function handleArcs (layer, data, options) {
  238. var self = this,
  239. svg = this.svg;
  240. if ( !data || (data && !data.slice) ) {
  241. throw "Datamaps Error - arcs must be an array";
  242. }
  243. if ( typeof options === "undefined" ) {
  244. options = defaultOptions.arcConfig;
  245. }
  246. var arcs = layer.selectAll('path.datamaps-arc').data( data, JSON.stringify );
  247. arcs
  248. .enter()
  249. .append('svg:path')
  250. .attr('class', 'datamaps-arc')
  251. .style('stroke-linecap', 'round')
  252. .style('stroke', function(datum) {
  253. if ( datum.options && datum.options.strokeColor) {
  254. return datum.options.strokeColor;
  255. }
  256. return options.strokeColor
  257. })
  258. .style('fill', 'none')
  259. .style('stroke-width', function(datum) {
  260. if ( datum.options && datum.options.strokeWidth) {
  261. return datum.options.strokeWidth;
  262. }
  263. return options.strokeWidth;
  264. })
  265. .attr('d', function(datum) {
  266. var originXY = self.latLngToXY(datum.origin.latitude, datum.origin.longitude);
  267. var destXY = self.latLngToXY(datum.destination.latitude, datum.destination.longitude);
  268. var midXY = [ (originXY[0] + destXY[0]) / 2, (originXY[1] + destXY[1]) / 2];
  269. return "M" + originXY[0] + ',' + originXY[1] + "S" + (midXY[0] + (50 * options.arcSharpness)) + "," + (midXY[1] - (75 * options.arcSharpness)) + "," + destXY[0] + "," + destXY[1];
  270. })
  271. .transition()
  272. .delay(100)
  273. .style('fill', function() {
  274. /*
  275. Thank you Jake Archibald, this is awesome.
  276. Source: http://jakearchibald.com/2013/animated-line-drawing-svg/
  277. */
  278. var length = this.getTotalLength();
  279. this.style.transition = this.style.WebkitTransition = 'none';
  280. this.style.strokeDasharray = length + ' ' + length;
  281. this.style.strokeDashoffset = length;
  282. this.getBoundingClientRect();
  283. this.style.transition = this.style.WebkitTransition = 'stroke-dashoffset ' + options.animationSpeed + 'ms ease-out';
  284. this.style.strokeDashoffset = '0';
  285. return 'none';
  286. })
  287. arcs.exit()
  288. .transition()
  289. .style('opacity', 0)
  290. .remove();
  291. }
  292. function handleLabels ( layer, options ) {
  293. var self = this;
  294. options = options || {};
  295. var labelStartCoodinates = this.projection([-67.707617, 42.722131]);
  296. this.svg.selectAll(".datamaps-subunit")
  297. .attr("data-foo", function(d) {
  298. var center = self.path.centroid(d);
  299. var xOffset = 7.5, yOffset = 5;
  300. if ( ["FL", "KY", "MI"].indexOf(d.id) > -1 ) xOffset = -2.5;
  301. if ( d.id === "NY" ) xOffset = -1;
  302. if ( d.id === "MI" ) yOffset = 18;
  303. if ( d.id === "LA" ) xOffset = 13;
  304. var x,y;
  305. x = center[0] - xOffset;
  306. y = center[1] + yOffset;
  307. var smallStateIndex = ["VT", "NH", "MA", "RI", "CT", "NJ", "DE", "MD", "DC"].indexOf(d.id);
  308. if ( smallStateIndex > -1) {
  309. var yStart = labelStartCoodinates[1];
  310. x = labelStartCoodinates[0];
  311. y = yStart + (smallStateIndex * (2+ (options.fontSize || 12)));
  312. layer.append("line")
  313. .attr("x1", x - 3)
  314. .attr("y1", y - 5)
  315. .attr("x2", center[0])
  316. .attr("y2", center[1])
  317. .style("stroke", options.labelColor || "#000")
  318. .style("stroke-width", options.lineWidth || 1)
  319. }
  320. layer.append("text")
  321. .attr("x", x)
  322. .attr("y", y)
  323. .style("font-size", (options.fontSize || 10) + 'px')
  324. .style("font-family", options.fontFamily || "Verdana")
  325. .style("fill", options.labelColor || "#000")
  326. .text( d.id );
  327. return "bar";
  328. });
  329. }
  330. function handleBubbles (layer, data, options ) {
  331. var self = this,
  332. fillData = this.options.fills,
  333. svg = this.svg;
  334. if ( !data || (data && !data.slice) ) {
  335. throw "Datamaps Error - bubbles must be an array";
  336. }
  337. var bubbles = layer.selectAll('circle.datamaps-bubble').data( data, JSON.stringify );
  338. bubbles
  339. .enter()
  340. .append('svg:circle')
  341. .attr('class', 'datamaps-bubble')
  342. .attr('cx', function ( datum ) {
  343. var latLng;
  344. if ( datumHasCoords(datum) ) {
  345. latLng = self.latLngToXY(datum.latitude, datum.longitude);
  346. }
  347. else if ( datum.centered ) {
  348. latLng = self.path.centroid(svg.select('path.' + datum.centered).data()[0]);
  349. }
  350. if ( latLng ) return latLng[0];
  351. })
  352. .attr('cy', function ( datum ) {
  353. var latLng;
  354. if ( datumHasCoords(datum) ) {
  355. latLng = self.latLngToXY(datum.latitude, datum.longitude);
  356. }
  357. else if ( datum.centered ) {
  358. latLng = self.path.centroid(svg.select('path.' + datum.centered).data()[0]);
  359. }
  360. if ( latLng ) return latLng[1];;
  361. })
  362. .attr('r', 0) //for animation purposes
  363. .attr('data-info', function(d) {
  364. return JSON.stringify(d);
  365. })
  366. .style('stroke', options.borderColor)
  367. .style('stroke-width', options.borderWidth)
  368. .style('fill-opacity', options.fillOpacity)
  369. .style('fill', function ( datum ) {
  370. var fillColor = fillData[ datum.fillKey ];
  371. return fillColor || fillData.defaultFill;
  372. })
  373. .on('mouseover', function ( datum ) {
  374. var $this = d3.select(this);
  375. if (options.highlightOnHover) {
  376. //save all previous attributes for mouseout
  377. var previousAttributes = {
  378. 'fill': $this.style('fill'),
  379. 'stroke': $this.style('stroke'),
  380. 'stroke-width': $this.style('stroke-width'),
  381. 'fill-opacity': $this.style('fill-opacity')
  382. };
  383. $this
  384. .style('fill', options.highlightFillColor)
  385. .style('stroke', options.highlightBorderColor)
  386. .style('stroke-width', options.highlightBorderWidth)
  387. .style('fill-opacity', options.highlightFillOpacity)
  388. .attr('data-previousAttributes', JSON.stringify(previousAttributes));
  389. }
  390. if (options.popupOnHover) {
  391. self.updatePopup($this, datum, options, svg);
  392. }
  393. })
  394. .on('mouseout', function ( datum ) {
  395. var $this = d3.select(this);
  396. if (options.highlightOnHover) {
  397. //reapply previous attributes
  398. var previousAttributes = JSON.parse( $this.attr('data-previousAttributes') );
  399. for ( var attr in previousAttributes ) {
  400. $this.style(attr, previousAttributes[attr]);
  401. }
  402. }
  403. d3.selectAll('.datamaps-hoverover').style('display', 'none');
  404. })
  405. .transition().duration(400)
  406. .attr('r', function ( datum ) {
  407. return datum.radius;
  408. });
  409. bubbles.exit()
  410. .transition()
  411. .delay(options.exitDelay)
  412. .attr("r", 0)
  413. .remove();
  414. function datumHasCoords (datum) {
  415. return typeof datum !== 'undefined' && typeof datum.latitude !== 'undefined' && typeof datum.longitude !== 'undefined';
  416. }
  417. }
  418. //stolen from underscore.js
  419. function defaults(obj) {
  420. Array.prototype.slice.call(arguments, 1).forEach(function(source) {
  421. if (source) {
  422. for (var prop in source) {
  423. if (obj[prop] == null) obj[prop] = source[prop];
  424. }
  425. }
  426. });
  427. return obj;
  428. }
  429. /**************************************
  430. Public Functions
  431. ***************************************/
  432. function Datamap( options ) {
  433. if ( typeof d3 === 'undefined' || typeof topojson === 'undefined' ) {
  434. throw new Error('Include d3.js (v3.0.3 or greater) and topojson on this page before creating a new map');
  435. }
  436. //set options for global use
  437. this.options = defaults(options, defaultOptions);
  438. this.options.geographyConfig = defaults(options.geographyConfig, defaultOptions.geographyConfig);
  439. this.options.bubblesConfig = defaults(options.bubblesConfig, defaultOptions.bubblesConfig);
  440. this.options.arcConfig = defaults(options.arcConfig, defaultOptions.arcConfig);
  441. //add the SVG container
  442. if ( d3.select( this.options.element ).select('svg').length > 0 ) {
  443. addContainer.call(this, this.options.element );
  444. }
  445. /* Add core plugins to this instance */
  446. this.addPlugin('bubbles', handleBubbles);
  447. this.addPlugin('legend', addLegend);
  448. this.addPlugin('arc', handleArcs);
  449. this.addPlugin('labels', handleLabels);
  450. //append style block with basic hoverover styles
  451. if ( ! this.options.disableDefaultStyles ) {
  452. addStyleBlock();
  453. }
  454. return this.draw();
  455. }
  456. // actually draw the features(states & countries)
  457. Datamap.prototype.draw = function() {
  458. //save off in a closure
  459. var self = this;
  460. var options = self.options;
  461. //set projections and paths based on scope
  462. var pathAndProjection = options.setProjection.apply(self, [options.element, options] );
  463. this.path = pathAndProjection.path;
  464. this.projection = pathAndProjection.projection;
  465. //if custom URL for topojson data, retrieve it and render
  466. if ( options.geographyConfig.dataUrl ) {
  467. d3.json( options.geographyConfig.dataUrl, function(error, results) {
  468. if ( error ) throw new Error(error);
  469. self.customTopo = results;
  470. draw( results );
  471. });
  472. }
  473. else {
  474. draw( this[options.scope + 'Topo'] );
  475. }
  476. return this;
  477. function draw (data) {
  478. // if fetching remote data, draw the map first then call `updateChoropleth`
  479. if ( self.options.dataUrl ) {
  480. //allow for csv or json data types
  481. d3[self.options.dataType](self.options.dataUrl, function(data) {
  482. //in the case of csv, transform data to object
  483. if ( self.options.dataType === 'csv' && (data && data.slice) ) {
  484. var tmpData = {};
  485. for(var i = 0; i < data.length; i++) {
  486. tmpData[data[i].id] = data[i];
  487. }
  488. data = tmpData;
  489. }
  490. Datamaps.prototype.updateChoropleth.call(self, data);
  491. });
  492. }
  493. drawSubunits.call(self, data);
  494. handleGeographyConfig.call(self);
  495. if ( self.options.geographyConfig.popupOnHover || self.options.bubblesConfig.popupOnHover) {
  496. hoverover = d3.select( self.options.element ).append('div')
  497. .attr('class', 'datamaps-hoverover')
  498. .style('z-index', 10001)
  499. .style('position', 'absolute');
  500. }
  501. //fire off finished callback
  502. self.options.done(self);
  503. }
  504. };
  505. /**************************************
  506. TopoJSON
  507. ***************************************/
  508. Datamap.prototype.worldTopo = WORLD_TOPO;
  509. Datamap.prototype.usaTopo = USA_TOPO;
  510. /**************************************
  511. Utilities
  512. ***************************************/
  513. //convert lat/lng coords to X / Y coords
  514. Datamap.prototype.latLngToXY = function(lat, lng) {
  515. return this.projection([lng, lat]);
  516. };
  517. //add <g> layer to root SVG
  518. Datamap.prototype.addLayer = function( className, id, first ) {
  519. var layer;
  520. if ( first ) {
  521. layer = this.svg.insert('g', ':first-child')
  522. }
  523. else {
  524. layer = this.svg.append('g')
  525. }
  526. return layer.attr('id', id || '')
  527. .attr('class', className || '');
  528. };
  529. Datamap.prototype.updateChoropleth = function(data) {
  530. var svg = this.svg;
  531. for ( var subunit in data ) {
  532. if ( data.hasOwnProperty(subunit) ) {
  533. console.log(data);
  534. var color;
  535. var subunitData = data[subunit]
  536. if ( ! subunit ) {
  537. continue;
  538. }
  539. else if ( typeof subunitData === "string" ) {
  540. color = subunitData;
  541. }
  542. else if ( typeof subunitData.color === "string" ) {
  543. color = subunitData.color;
  544. }
  545. else {
  546. color = this.options.fills[ subunitData.fillKey ];
  547. }
  548. //if it's an object, overriding the previous data
  549. if ( subunitData === Object(subunitData) ) {
  550. this.options.data[subunit] = defaults(subunitData, this.options.data[subunit] || {});
  551. var geo = this.svg.select('.' + subunit).attr('data-info', JSON.stringify(this.options.data[subunit]));
  552. }
  553. svg
  554. .selectAll('.' + subunit)
  555. .transition()
  556. .style('fill', color);
  557. }
  558. }
  559. };
  560. Datamap.prototype.updatePopup = function (element, d, options) {
  561. var self = this;
  562. element.on('mousemove', null);
  563. element.on('mousemove', function() {
  564. var position = d3.mouse(this);
  565. d3.select(self.svg[0][0].parentNode).select('.datamaps-hoverover')
  566. .style('top', ( (position[1] + 30)) + "px")
  567. .html(function() {
  568. var data = JSON.parse(element.attr('data-info'));
  569. //if ( !data ) return '';
  570. return options.popupTemplate(d, data);
  571. })
  572. .style('left', ( position[0]) + "px");
  573. });
  574. d3.select(self.svg[0][0].parentNode).select('.datamaps-hoverover').style('display', 'block');
  575. };
  576. Datamap.prototype.addPlugin = function( name, pluginFn ) {
  577. var self = this;
  578. if ( typeof Datamap.prototype[name] === "undefined" ) {
  579. Datamap.prototype[name] = function(data, options, callback, createNewLayer) {
  580. var layer;
  581. if ( typeof createNewLayer === "undefined" ) {
  582. createNewLayer = false;
  583. }
  584. if ( typeof options === 'function' ) {
  585. callback = options;
  586. options = undefined;
  587. }
  588. options = defaults(options || {}, defaultOptions[name + 'Config']);
  589. //add a single layer, reuse the old layer
  590. if ( !createNewLayer && this.options[name + 'Layer'] ) {
  591. layer = this.options[name + 'Layer'];
  592. options = options || this.options[name + 'Options'];
  593. }
  594. else {
  595. layer = this.addLayer(name);
  596. this.options[name + 'Layer'] = layer;
  597. this.options[name + 'Options'] = options;
  598. }
  599. pluginFn.apply(this, [layer, data, options]);
  600. if ( callback ) {
  601. callback(layer);
  602. }
  603. };
  604. }
  605. };
  606. // expose library
  607. if ( typeof define === "function" && define.amd ) {
  608. define( "datamaps", function(require) { d3 = require('d3'); topojson = require('topojson'); return Datamap; } );
  609. }
  610. else {
  611. window.Datamap = window.Datamaps = Datamap;
  612. }
  613. if ( window.jQuery ) {
  614. window.jQuery.fn.datamaps = function(options, callback) {
  615. options = options || {};
  616. options.element = this[0];
  617. var datamap = new Datamap(options);
  618. if ( typeof callback === "function" ) {
  619. callback(datamap, options);
  620. }
  621. return this;
  622. };
  623. }
  624. })();