Эх сурвалжийг харах

[search] Added legend to the discrete map chart

Enrico Berti 10 жил өмнө
parent
commit
c2e9d87

+ 23 - 2
desktop/core/src/desktop/static/desktop/js/ko.charts.js

@@ -303,6 +303,7 @@ ko.bindingHandlers.mapChart = {
     var _mapdata = {};
     var _maphovers = {};
     var _fills = {};
+    var _legend = [];
 
     var _noncountries = [];
 
@@ -333,14 +334,30 @@ ko.bindingHandlers.mapChart = {
             }
           });
         }
-        return _found;
+        return {
+          idx: _found,
+          cat: _cat
+        };
+      }
+
+      function addToLegend(category) {
+        var _found = false;
+        _legend.forEach(function (lg) {
+          if (lg.cat == category.cat) {
+            _found = true;
+          }
+        });
+        if (!_found) {
+          _legend.push(category);
+        }
       }
 
       $(_data).each(function (cnt, item) {
+        addToLegend(getHighestCategoryValue(cnt, item));
         var _place = typeof item.label == "String" ? item.label.toUpperCase() : item.label;
         if (_place != null) {
           _mapdata[_place] = {
-            fillKey: "fill_" + (_is2d ? getHighestCategoryValue(cnt, item) : (Math.floor(item.value / _chunk) - 1)),
+            fillKey: "fill_" + (_is2d ? getHighestCategoryValue(cnt, item).idx : (Math.floor(item.value / _chunk) - 1)),
             id: _place,
             cat: item.obj.cat,
             value: item.obj.values ? item.obj.values : item.obj.value,
@@ -386,6 +403,7 @@ ko.bindingHandlers.mapChart = {
         fills: fills,
         scope: _scope,
         data: mapData,
+        legendData: _legend,
         onClick: function (data) {
           if (typeof options.onClick != "undefined") {
             chartsUpdatingState();
@@ -444,6 +462,9 @@ ko.bindingHandlers.mapChart = {
       if (options.onComplete != null) {
         options.onComplete();
       }
+      if (_is2d) {
+        _map.legend();
+      }
     }
 
     createDatamap(element, _options, _fills, _mapdata, _noncountries, _maphovers)

+ 334 - 331
desktop/core/src/desktop/static/desktop/js/nv.d3.datamaps.js

@@ -14,7 +14,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-(function() {
+(function () {
   var svg;
 
   //save off default references
@@ -25,42 +25,45 @@
     setProjection: setProjection,
     projection: 'equirectangular',
     dataType: 'json',
-    onClick: function() {},
-    done: function() {},
+    onClick: function () {
+    },
+    done: function () {
+    },
+    legendData: [],
     fills: {
       defaultFill: '#ABDDA4'
     },
     geographyConfig: {
-        dataUrl: null,
-        hideAntarctica: true,
-        borderWidth: 1,
-        borderColor: '#FDFDFD',
-        popupTemplate: function(geography, data) {
-          return '<div class="hoverinfo"><strong>' + geography.properties.name + '</strong></div>';
-        },
-        popupOnHover: true,
-        highlightOnHover: true,
-        highlightFillColor: '#FC8D59',
-        highlightBorderColor: 'rgba(250, 15, 160, 0.2)',
-        selectedFillColor: '#666666',
-        selectedBorderColor: '#666666',
-        highlightBorderWidth: 2
+      dataUrl: null,
+      hideAntarctica: true,
+      borderWidth: 1,
+      borderColor: '#FDFDFD',
+      popupTemplate: function (geography, data) {
+        return '<div class="hoverinfo"><strong>' + geography.properties.name + '</strong></div>';
+      },
+      popupOnHover: true,
+      highlightOnHover: true,
+      highlightFillColor: '#FC8D59',
+      highlightBorderColor: 'rgba(250, 15, 160, 0.2)',
+      selectedFillColor: '#666666',
+      selectedBorderColor: '#666666',
+      highlightBorderWidth: 2
     },
     bubblesConfig: {
-        borderWidth: 2,
-        borderColor: '#FFFFFF',
-        popupOnHover: true,
-        popupTemplate: function(geography, data) {
-          return '<div class="hoverinfo"><strong>' + data.name + '</strong></div>';
-        },
-        fillOpacity: 0.75,
-        animate: true,
-        highlightOnHover: true,
-        highlightFillColor: '#FC8D59',
-        highlightBorderColor: 'rgba(250, 15, 160, 0.2)',
-        highlightBorderWidth: 2,
-        highlightFillOpacity: 0.85,
-        exitDelay: 100
+      borderWidth: 2,
+      borderColor: '#FFFFFF',
+      popupOnHover: true,
+      popupTemplate: function (geography, data) {
+        return '<div class="hoverinfo"><strong>' + data.name + '</strong></div>';
+      },
+      fillOpacity: 0.75,
+      animate: true,
+      highlightOnHover: true,
+      highlightFillColor: '#FC8D59',
+      highlightBorderColor: 'rgba(250, 15, 160, 0.2)',
+      highlightBorderWidth: 2,
+      highlightFillOpacity: 0.85,
+      exitDelay: 100
     },
     arcConfig: {
       strokeColor: '#DD1C77',
@@ -70,47 +73,47 @@
     }
   };
 
-  function addContainer( element ) {
-    this.svg = d3.select( element ).append('svg')
-      .attr('width', element.offsetWidth)
-      .attr('class', 'datamap')
-      .attr('height', element.offsetHeight);
+  function addContainer(element) {
+    this.svg = d3.select(element).append('svg')
+        .attr('width', element.offsetWidth)
+        .attr('class', 'datamap')
+        .attr('height', element.offsetHeight);
 
     return this.svg;
   }
 
   // setProjection takes the svg element and options
-  function setProjection( element, options ) {
+  function setProjection(element, options) {
     var projection, path;
-    if ( options && typeof options.scope === 'undefined') {
+    if (options && typeof options.scope === 'undefined') {
       options.scope = 'world';
     }
 
-    if ( options.scope === 'usa' ) {
+    if (options.scope === 'usa') {
       projection = d3.geo.albersUsa()
-        .scale(element.offsetWidth)
-        .translate([element.offsetWidth / 2, element.offsetHeight / 2]);
+          .scale(element.offsetWidth)
+          .translate([element.offsetWidth / 2, element.offsetHeight / 2]);
     }
-    else if ( options.scope === 'world' ) {
+    else if (options.scope === 'world') {
       projection = d3.geo[options.projection]()
-        .scale((element.offsetWidth + 1) / 2 / Math.PI)
-        .translate([element.offsetWidth / 2, element.offsetHeight / (options.projection === "mercator" ? 1.45 : 1.8)]);
+          .scale((element.offsetWidth + 1) / 2 / Math.PI)
+          .translate([element.offsetWidth / 2, element.offsetHeight / (options.projection === "mercator" ? 1.45 : 1.8)]);
     }
 
     path = d3.geo.path()
-      .projection( projection );
+        .projection(projection);
 
     return {path: path, projection: projection};
   }
 
   function addStyleBlock() {
-    if ( d3.select('.datamaps-style-block').empty() ) {
+    if (d3.select('.datamaps-style-block').empty()) {
       d3.select('head').attr('class', 'datamaps-style-block').append('style')
-      .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; }');
+          .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: 3px; margin-left: 14px; border-radius: 3px;} .datamaps-legend {padding-bottom: 20px; z-index: 1001; 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; }');
     }
   }
 
-  function drawSubunits( data ) {
+  function drawSubunits(data) {
     var fillData = this.options.fills,
         colorCodeData = this.options.data || {},
         geoConfig = this.options.geographyConfig,
@@ -118,108 +121,108 @@
 
 
     var subunits = this.svg.select('g.datamaps-subunits');
-    if ( subunits.empty() ) {
+    if (subunits.empty()) {
       subunits = this.addLayer('datamaps-subunits', null, true);
     }
 
-    var geoData = topojson.feature( data, data.objects[ this.options.scope ] ).features;
-    if ( geoConfig.hideAntarctica ) {
-      geoData = geoData.filter(function(feature) {
+    var geoData = topojson.feature(data, data.objects[ this.options.scope ]).features;
+    if (geoConfig.hideAntarctica) {
+      geoData = geoData.filter(function (feature) {
         return feature.id !== "ATA";
       });
     }
 
-    var geo = subunits.selectAll('path.datamaps-subunit').data( geoData );
+    var geo = subunits.selectAll('path.datamaps-subunit').data(geoData);
 
     geo.enter()
-      .append('path')
-      .attr('d', this.path)
-      .attr('class', function(d) {
-        return 'datamaps-subunit ' + d.id;
-      })
-      .attr('data-info', function(d) {
-        return JSON.stringify( colorCodeData[d.id]);
-      })
-      .style('fill', function(d) {
-        var fillColor;
-        if ( colorCodeData[d.id] ) {
-          fillColor = fillData[ colorCodeData[d.id].fillKey ];
-          if (colorCodeData[d.id].selected){
-            fillColor = geoConfig.selectedFillColor;
+        .append('path')
+        .attr('d', this.path)
+        .attr('class', function (d) {
+          return 'datamaps-subunit ' + d.id;
+        })
+        .attr('data-info', function (d) {
+          return JSON.stringify(colorCodeData[d.id]);
+        })
+        .style('fill', function (d) {
+          var fillColor;
+          if (colorCodeData[d.id]) {
+            fillColor = fillData[ colorCodeData[d.id].fillKey ];
+            if (colorCodeData[d.id].selected) {
+              fillColor = geoConfig.selectedFillColor;
+            }
           }
-        }
-        return fillColor || fillData.defaultFill;
-      })
-      .on('click', function(d) {
-        if ( colorCodeData[d.id] && typeof onClick != "undefined") {
-          onClick(colorCodeData[d.id]);
-        }
-      })
-      .style('stroke-width', function(d) {
-        var strokeWidth = geoConfig.borderWidth;
-        if ( colorCodeData[d.id] && colorCodeData[d.id].selected ) {
-          strokeWidth = 2;
-        }
-        return strokeWidth;
-      })
-      .style('stroke', function(d) {
-        var strokeColor = geoConfig.borderColor;
-        if ( colorCodeData[d.id] && colorCodeData[d.id].selected ) {
-          strokeColor = geoConfig.selectedBorderColor;
-        }
-        return strokeColor;
-      });
+          return fillColor || fillData.defaultFill;
+        })
+        .on('click', function (d) {
+          if (colorCodeData[d.id] && typeof onClick != "undefined") {
+            onClick(colorCodeData[d.id]);
+          }
+        })
+        .style('stroke-width', function (d) {
+          var strokeWidth = geoConfig.borderWidth;
+          if (colorCodeData[d.id] && colorCodeData[d.id].selected) {
+            strokeWidth = 2;
+          }
+          return strokeWidth;
+        })
+        .style('stroke', function (d) {
+          var strokeColor = geoConfig.borderColor;
+          if (colorCodeData[d.id] && colorCodeData[d.id].selected) {
+            strokeColor = geoConfig.selectedBorderColor;
+          }
+          return strokeColor;
+        });
   }
 
-  function handleGeographyConfig () {
+  function handleGeographyConfig() {
     var hoverover;
     var svg = this.svg;
     var self = this;
     var options = this.options.geographyConfig;
 
-    if ( options.highlightOnHover || options.popupOnHover ) {
+    if (options.highlightOnHover || options.popupOnHover) {
       svg.selectAll('.datamaps-subunit')
-        .on('mouseover', function(d) {
-          var $this = d3.select(this);
-
-          if ( options.highlightOnHover ) {
-            var previousAttributes = {
-              'fill':  $this.style('fill'),
-              'stroke': $this.style('stroke'),
-              'stroke-width': $this.style('stroke-width'),
-              'fill-opacity': $this.style('fill-opacity')
-            };
-
-            $this
-              .style('fill', options.highlightFillColor)
-              .style('stroke', options.highlightBorderColor)
-              .style('stroke-width', options.highlightBorderWidth)
-              .style('fill-opacity', options.highlightFillOpacity)
-              .attr('data-previousAttributes', JSON.stringify(previousAttributes));
-
-            //as per discussion on https://github.com/markmarkoh/datamaps/issues/19
-            if ( ! /MSIE/.test(navigator.userAgent) ) {
-             moveToFront.call(this);
+          .on('mouseover', function (d) {
+            var $this = d3.select(this);
+
+            if (options.highlightOnHover) {
+              var previousAttributes = {
+                'fill': $this.style('fill'),
+                'stroke': $this.style('stroke'),
+                'stroke-width': $this.style('stroke-width'),
+                'fill-opacity': $this.style('fill-opacity')
+              };
+
+              $this
+                  .style('fill', options.highlightFillColor)
+                  .style('stroke', options.highlightBorderColor)
+                  .style('stroke-width', options.highlightBorderWidth)
+                  .style('fill-opacity', options.highlightFillOpacity)
+                  .attr('data-previousAttributes', JSON.stringify(previousAttributes));
+
+              //as per discussion on https://github.com/markmarkoh/datamaps/issues/19
+              if (!/MSIE/.test(navigator.userAgent)) {
+                moveToFront.call(this);
+              }
             }
-          }
-
-          if ( options.popupOnHover ) {
-            self.updatePopup($this, d, options, svg);
-          }
-        })
-        .on('mouseout', function() {
-          var $this = d3.select(this);
 
-          if (options.highlightOnHover) {
-            //reapply previous attributes
-            var previousAttributes = JSON.parse( $this.attr('data-previousAttributes') );
-            for ( var attr in previousAttributes ) {
-              $this.style(attr, previousAttributes[attr]);
+            if (options.popupOnHover) {
+              self.updatePopup($this, d, options, svg);
             }
-          }
-          $this.on('mousemove', null);
-          d3.selectAll('.datamaps-hoverover').style('display', 'none');
-        });
+          })
+          .on('mouseout', function () {
+            var $this = d3.select(this);
+
+            if (options.highlightOnHover) {
+              //reapply previous attributes
+              var previousAttributes = JSON.parse($this.attr('data-previousAttributes'));
+              for (var attr in previousAttributes) {
+                $this.style(attr, previousAttributes[attr]);
+              }
+            }
+            $this.on('mousemove', null);
+            d3.selectAll('.datamaps-hoverover').style('display', 'none');
+          });
     }
 
     function moveToFront() {
@@ -230,222 +233,218 @@
   //plugin to add a simple map legend
   function addLegend(layer, data, options) {
     data = data || {};
-    if ( !this.options.fills ) {
+    if (!this.options.fills) {
       return;
     }
 
-    var html = '<dl>';
-    var label = '';
-    if ( data.legendTitle ) {
-      html = '<h2>' + data.legendTitle + '</h2>' + html;
+    function compareLegendValues(a, b) {
+      if (a.idx < b.idx)
+        return -1;
+      if (a.idx > b.idx)
+        return 1;
+      return 0;
     }
-    for ( var fillKey in this.options.fills ) {
 
-      if ( fillKey === 'defaultFill') {
-        if (! data.defaultFillName ) {
-          continue;
-        }
-        label = data.defaultFillName;
-      } else {
-        if (data.labels && data.labels[fillKey]) {
-          label = data.labels[fillKey];
-        } else {
-          label= fillKey + ': ';
-        }
-      }
-      html += '<dt>' + label + '</dt>';
-      html += '<dd style="background-color:' +  this.options.fills[fillKey] + '">&nbsp;</dd>';
-    }
+    var html = '<dl>';
+    var _fills = this.options.fills;
+
+    this.options.legendData.sort(compareLegendValues).forEach(function (fill) {
+      html += '<dd style="background-color:' + _fills["fill_" + fill.idx] + '">&nbsp;</dd>';
+      html += '<dt>' + fill.cat + '</dt>';
+    });
+
+
     html += '</dl>';
 
-    var hoverover = d3.select( this.options.element ).append('div')
-      .attr('class', 'datamaps-legend')
-      .html(html);
+    var hoverover = d3.select(this.options.element).append('div')
+        .attr('class', 'datamaps-legend')
+        .html(html);
   }
 
-  function handleArcs (layer, data, options) {
+  function handleArcs(layer, data, options) {
     var self = this,
         svg = this.svg;
 
-    if ( !data || (data && !data.slice) ) {
+    if (!data || (data && !data.slice)) {
       throw "Datamaps Error - arcs must be an array";
     }
 
-    if ( typeof options === "undefined" ) {
+    if (typeof options === "undefined") {
       options = defaultOptions.arcConfig;
     }
 
-    var arcs = layer.selectAll('path.datamaps-arc').data( data, JSON.stringify );
+    var arcs = layer.selectAll('path.datamaps-arc').data(data, JSON.stringify);
 
     arcs
-      .enter()
+        .enter()
         .append('svg:path')
         .attr('class', 'datamaps-arc')
         .style('stroke-linecap', 'round')
-        .style('stroke', function(datum) {
-          if ( datum.options && datum.options.strokeColor) {
+        .style('stroke', function (datum) {
+          if (datum.options && datum.options.strokeColor) {
             return datum.options.strokeColor;
           }
           return  options.strokeColor
         })
         .style('fill', 'none')
-        .style('stroke-width', function(datum) {
-          if ( datum.options && datum.options.strokeWidth) {
+        .style('stroke-width', function (datum) {
+          if (datum.options && datum.options.strokeWidth) {
             return datum.options.strokeWidth;
           }
           return options.strokeWidth;
         })
-        .attr('d', function(datum) {
-            var originXY = self.latLngToXY(datum.origin.latitude, datum.origin.longitude);
-            var destXY = self.latLngToXY(datum.destination.latitude, datum.destination.longitude);
-            var midXY = [ (originXY[0] + destXY[0]) / 2, (originXY[1] + destXY[1]) / 2];
-            return "M" + originXY[0] + ',' + originXY[1] + "S" + (midXY[0] + (50 * options.arcSharpness)) + "," + (midXY[1] - (75 * options.arcSharpness)) + "," + destXY[0] + "," + destXY[1];
+        .attr('d', function (datum) {
+          var originXY = self.latLngToXY(datum.origin.latitude, datum.origin.longitude);
+          var destXY = self.latLngToXY(datum.destination.latitude, datum.destination.longitude);
+          var midXY = [ (originXY[0] + destXY[0]) / 2, (originXY[1] + destXY[1]) / 2];
+          return "M" + originXY[0] + ',' + originXY[1] + "S" + (midXY[0] + (50 * options.arcSharpness)) + "," + (midXY[1] - (75 * options.arcSharpness)) + "," + destXY[0] + "," + destXY[1];
         })
         .transition()
-          .delay(100)
-          .style('fill', function() {
-            /*
-              Thank you Jake Archibald, this is awesome.
-              Source: http://jakearchibald.com/2013/animated-line-drawing-svg/
-            */
-            var length = this.getTotalLength();
-            this.style.transition = this.style.WebkitTransition = 'none';
-            this.style.strokeDasharray = length + ' ' + length;
-            this.style.strokeDashoffset = length;
-            this.getBoundingClientRect();
-            this.style.transition = this.style.WebkitTransition = 'stroke-dashoffset ' + options.animationSpeed + 'ms ease-out';
-            this.style.strokeDashoffset = '0';
-            return 'none';
-          })
+        .delay(100)
+        .style('fill', function () {
+          /*
+           Thank you Jake Archibald, this is awesome.
+           Source: http://jakearchibald.com/2013/animated-line-drawing-svg/
+           */
+          var length = this.getTotalLength();
+          this.style.transition = this.style.WebkitTransition = 'none';
+          this.style.strokeDasharray = length + ' ' + length;
+          this.style.strokeDashoffset = length;
+          this.getBoundingClientRect();
+          this.style.transition = this.style.WebkitTransition = 'stroke-dashoffset ' + options.animationSpeed + 'ms ease-out';
+          this.style.strokeDashoffset = '0';
+          return 'none';
+        })
 
     arcs.exit()
-      .transition()
-      .style('opacity', 0)
-      .remove();
+        .transition()
+        .style('opacity', 0)
+        .remove();
   }
 
-  function handleLabels ( layer, options ) {
+  function handleLabels(layer, options) {
     var self = this;
     options = options || {};
     var labelStartCoodinates = this.projection([-67.707617, 42.722131]);
     this.svg.selectAll(".datamaps-subunit")
-      .attr("data-foo", function(d) {
-        var center = self.path.centroid(d);
-        var xOffset = 7.5, yOffset = 5;
-
-        if ( ["FL", "KY", "MI"].indexOf(d.id) > -1 ) xOffset = -2.5;
-        if ( d.id === "NY" ) xOffset = -1;
-        if ( d.id === "MI" ) yOffset = 18;
-        if ( d.id === "LA" ) xOffset = 13;
-
-        var x,y;
-
-        x = center[0] - xOffset;
-        y = center[1] + yOffset;
-
-        var smallStateIndex = ["VT", "NH", "MA", "RI", "CT", "NJ", "DE", "MD", "DC"].indexOf(d.id);
-        if ( smallStateIndex > -1) {
-          var yStart = labelStartCoodinates[1];
-          x = labelStartCoodinates[0];
-          y = yStart + (smallStateIndex * (2+ (options.fontSize || 12)));
-          layer.append("line")
-            .attr("x1", x - 3)
-            .attr("y1", y - 5)
-            .attr("x2", center[0])
-            .attr("y2", center[1])
-            .style("stroke", options.labelColor || "#000")
-            .style("stroke-width", options.lineWidth || 1)
-        }
+        .attr("data-foo", function (d) {
+          var center = self.path.centroid(d);
+          var xOffset = 7.5, yOffset = 5;
+
+          if (["FL", "KY", "MI"].indexOf(d.id) > -1) xOffset = -2.5;
+          if (d.id === "NY") xOffset = -1;
+          if (d.id === "MI") yOffset = 18;
+          if (d.id === "LA") xOffset = 13;
+
+          var x, y;
+
+          x = center[0] - xOffset;
+          y = center[1] + yOffset;
+
+          var smallStateIndex = ["VT", "NH", "MA", "RI", "CT", "NJ", "DE", "MD", "DC"].indexOf(d.id);
+          if (smallStateIndex > -1) {
+            var yStart = labelStartCoodinates[1];
+            x = labelStartCoodinates[0];
+            y = yStart + (smallStateIndex * (2 + (options.fontSize || 12)));
+            layer.append("line")
+                .attr("x1", x - 3)
+                .attr("y1", y - 5)
+                .attr("x2", center[0])
+                .attr("y2", center[1])
+                .style("stroke", options.labelColor || "#000")
+                .style("stroke-width", options.lineWidth || 1)
+          }
 
-        layer.append("text")
-          .attr("x", x)
-          .attr("y", y)
-          .style("font-size", (options.fontSize || 10) + 'px')
-          .style("font-family", options.fontFamily || "Verdana")
-          .style("fill", options.labelColor || "#000")
-          .text( d.id );
-        return "bar";
-      });
+          layer.append("text")
+              .attr("x", x)
+              .attr("y", y)
+              .style("font-size", (options.fontSize || 10) + 'px')
+              .style("font-family", options.fontFamily || "Verdana")
+              .style("fill", options.labelColor || "#000")
+              .text(d.id);
+          return "bar";
+        });
   }
 
 
-  function handleBubbles (layer, data, options ) {
+  function handleBubbles(layer, data, options) {
     var self = this,
         fillData = this.options.fills,
         svg = this.svg;
 
-    if ( !data || (data && !data.slice) ) {
+    if (!data || (data && !data.slice)) {
       throw "Datamaps Error - bubbles must be an array";
     }
 
-    var bubbles = layer.selectAll('circle.datamaps-bubble').data( data, JSON.stringify );
+    var bubbles = layer.selectAll('circle.datamaps-bubble').data(data, JSON.stringify);
 
     bubbles
-      .enter()
+        .enter()
         .append('svg:circle')
         .attr('class', 'datamaps-bubble')
-        .attr('cx', function ( datum ) {
+        .attr('cx', function (datum) {
           var latLng;
-          if ( datumHasCoords(datum) ) {
+          if (datumHasCoords(datum)) {
             latLng = self.latLngToXY(datum.latitude, datum.longitude);
           }
-          else if ( datum.centered ) {
+          else if (datum.centered) {
             latLng = self.path.centroid(svg.select('path.' + datum.centered).data()[0]);
           }
-          if ( latLng ) return latLng[0];
+          if (latLng) return latLng[0];
         })
-        .attr('cy', function ( datum ) {
+        .attr('cy', function (datum) {
           var latLng;
-          if ( datumHasCoords(datum) ) {
+          if (datumHasCoords(datum)) {
             latLng = self.latLngToXY(datum.latitude, datum.longitude);
           }
-          else if ( datum.centered ) {
+          else if (datum.centered) {
             latLng = self.path.centroid(svg.select('path.' + datum.centered).data()[0]);
           }
-          if ( latLng ) return latLng[1];;
+          if (latLng) return latLng[1];
+          ;
         })
         .attr('r', 0) //for animation purposes
-        .attr('data-info', function(d) {
+        .attr('data-info', function (d) {
           return JSON.stringify(d);
         })
         .style('stroke', options.borderColor)
         .style('stroke-width', options.borderWidth)
         .style('fill-opacity', options.fillOpacity)
-        .style('fill', function ( datum ) {
+        .style('fill', function (datum) {
           var fillColor = fillData[ datum.fillKey ];
           return fillColor || fillData.defaultFill;
         })
-        .on('mouseover', function ( datum ) {
+        .on('mouseover', function (datum) {
           var $this = d3.select(this);
 
           if (options.highlightOnHover) {
             //save all previous attributes for mouseout
             var previousAttributes = {
-              'fill':  $this.style('fill'),
+              'fill': $this.style('fill'),
               'stroke': $this.style('stroke'),
               'stroke-width': $this.style('stroke-width'),
               'fill-opacity': $this.style('fill-opacity')
             };
 
             $this
-              .style('fill', options.highlightFillColor)
-              .style('stroke', options.highlightBorderColor)
-              .style('stroke-width', options.highlightBorderWidth)
-              .style('fill-opacity', options.highlightFillOpacity)
-              .attr('data-previousAttributes', JSON.stringify(previousAttributes));
+                .style('fill', options.highlightFillColor)
+                .style('stroke', options.highlightBorderColor)
+                .style('stroke-width', options.highlightBorderWidth)
+                .style('fill-opacity', options.highlightFillOpacity)
+                .attr('data-previousAttributes', JSON.stringify(previousAttributes));
           }
 
           if (options.popupOnHover) {
             self.updatePopup($this, datum, options, svg);
           }
         })
-        .on('mouseout', function ( datum ) {
+        .on('mouseout', function (datum) {
           var $this = d3.select(this);
 
           if (options.highlightOnHover) {
             //reapply previous attributes
-            var previousAttributes = JSON.parse( $this.attr('data-previousAttributes') );
-            for ( var attr in previousAttributes ) {
+            var previousAttributes = JSON.parse($this.attr('data-previousAttributes'));
+            for (var attr in previousAttributes) {
               $this.style(attr, previousAttributes[attr]);
             }
           }
@@ -453,17 +452,17 @@
           d3.selectAll('.datamaps-hoverover').style('display', 'none');
         })
         .transition().duration(400)
-          .attr('r', function ( datum ) {
-            return datum.radius;
-          });
+        .attr('r', function (datum) {
+          return datum.radius;
+        });
 
     bubbles.exit()
-      .transition()
+        .transition()
         .delay(options.exitDelay)
         .attr("r", 0)
         .remove();
 
-    function datumHasCoords (datum) {
+    function datumHasCoords(datum) {
       return typeof datum !== 'undefined' && typeof datum.latitude !== 'undefined' && typeof datum.longitude !== 'undefined';
     }
 
@@ -471,7 +470,7 @@
 
   //stolen from underscore.js
   function defaults(obj) {
-    Array.prototype.slice.call(arguments, 1).forEach(function(source) {
+    Array.prototype.slice.call(arguments, 1).forEach(function (source) {
       if (source) {
         for (var prop in source) {
           if (obj[prop] == null) obj[prop] = source[prop];
@@ -480,15 +479,16 @@
     });
     return obj;
   }
+
   /**************************************
-             Public Functions
-  ***************************************/
+   Public Functions
+   ***************************************/
 
-  function Datamap( options ) {
+  function Datamap(options) {
 
-    if ( typeof d3 === 'undefined' || typeof topojson === 'undefined' ) {
+    if (typeof d3 === 'undefined' || typeof topojson === 'undefined') {
       throw new Error('Include d3.js (v3.0.3 or greater) and topojson on this page before creating a new map');
-   }
+    }
 
     //set options for global use
     this.options = defaults(options, defaultOptions);
@@ -497,8 +497,8 @@
     this.options.arcConfig = defaults(options.arcConfig, defaultOptions.arcConfig);
 
     //add the SVG container
-    if ( d3.select( this.options.element ).select('svg').length > 0 ) {
-      addContainer.call(this, this.options.element );
+    if (d3.select(this.options.element).select('svg').length > 0) {
+      addContainer.call(this, this.options.element);
     }
 
     /* Add core plugins to this instance */
@@ -508,7 +508,7 @@
     this.addPlugin('labels', handleLabels);
 
     //append style block with basic hoverover styles
-    if ( ! this.options.disableDefaultStyles ) {
+    if (!this.options.disableDefaultStyles) {
       addStyleBlock();
     }
 
@@ -516,117 +516,116 @@
   }
 
   // actually draw the features(states & countries)
-  Datamap.prototype.draw = function() {
+  Datamap.prototype.draw = function () {
     //save off in a closure
     var self = this;
     var options = self.options;
 
     //set projections and paths based on scope
-    var pathAndProjection = options.setProjection.apply(self, [options.element, options] );
+    var pathAndProjection = options.setProjection.apply(self, [options.element, options]);
 
     this.path = pathAndProjection.path;
     this.projection = pathAndProjection.projection;
 
     //if custom URL for topojson data, retrieve it and render
-    if ( options.geographyConfig.dataUrl ) {
-      d3.json( options.geographyConfig.dataUrl, function(error, results) {
-        if ( error ) throw new Error(error);
+    if (options.geographyConfig.dataUrl) {
+      d3.json(options.geographyConfig.dataUrl, function (error, results) {
+        if (error) throw new Error(error);
         self.customTopo = results;
-        draw( results );
+        draw(results);
       });
     }
     else {
-      draw( this[options.scope + 'Topo'] );
+      draw(this[options.scope + 'Topo']);
     }
 
     return this;
 
-      function draw (data) {
-        // if fetching remote data, draw the map first then call `updateChoropleth`
-        if ( self.options.dataUrl ) {
-          //allow for csv or json data types
-          d3[self.options.dataType](self.options.dataUrl, function(data) {
-            //in the case of csv, transform data to object
-            if ( self.options.dataType === 'csv' && (data && data.slice) ) {
-              var tmpData = {};
-              for(var i = 0; i < data.length; i++) {
-                tmpData[data[i].id] = data[i];
-              }
-              data = tmpData;
+    function draw(data) {
+      // if fetching remote data, draw the map first then call `updateChoropleth`
+      if (self.options.dataUrl) {
+        //allow for csv or json data types
+        d3[self.options.dataType](self.options.dataUrl, function (data) {
+          //in the case of csv, transform data to object
+          if (self.options.dataType === 'csv' && (data && data.slice)) {
+            var tmpData = {};
+            for (var i = 0; i < data.length; i++) {
+              tmpData[data[i].id] = data[i];
             }
-            Datamaps.prototype.updateChoropleth.call(self, data);
-          });
-        }
-        drawSubunits.call(self, data);
-        handleGeographyConfig.call(self);
+            data = tmpData;
+          }
+          Datamaps.prototype.updateChoropleth.call(self, data);
+        });
+      }
+      drawSubunits.call(self, data);
+      handleGeographyConfig.call(self);
 
-        if ( self.options.geographyConfig.popupOnHover || self.options.bubblesConfig.popupOnHover) {
-          hoverover = d3.select( self.options.element ).append('div')
+      if (self.options.geographyConfig.popupOnHover || self.options.bubblesConfig.popupOnHover) {
+        hoverover = d3.select(self.options.element).append('div')
             .attr('class', 'datamaps-hoverover')
             .style('z-index', 10001)
             .style('position', 'absolute');
-        }
-
-        //fire off finished callback
-        self.options.done(self);
       }
+
+      //fire off finished callback
+      self.options.done(self);
+    }
   };
   /**************************************
-                TopoJSON
-  ***************************************/
+   TopoJSON
+   ***************************************/
   Datamap.prototype.worldTopo = WORLD_TOPO;
   Datamap.prototype.usaTopo = USA_TOPO;
 
   /**************************************
-                Utilities
-  ***************************************/
+   Utilities
+   ***************************************/
 
-  //convert lat/lng coords to X / Y coords
-  Datamap.prototype.latLngToXY = function(lat, lng) {
-     return this.projection([lng, lat]);
+    //convert lat/lng coords to X / Y coords
+  Datamap.prototype.latLngToXY = function (lat, lng) {
+    return this.projection([lng, lat]);
   };
 
   //add <g> layer to root SVG
-  Datamap.prototype.addLayer = function( className, id, first ) {
+  Datamap.prototype.addLayer = function (className, id, first) {
     var layer;
-    if ( first ) {
+    if (first) {
       layer = this.svg.insert('g', ':first-child')
     }
     else {
       layer = this.svg.append('g')
     }
     return layer.attr('id', id || '')
-      .attr('class', className || '');
+        .attr('class', className || '');
   };
 
-  Datamap.prototype.updateChoropleth = function(data) {
+  Datamap.prototype.updateChoropleth = function (data) {
     var svg = this.svg;
-    for ( var subunit in data ) {
-      if ( data.hasOwnProperty(subunit) ) {
-        console.log(data);
+    for (var subunit in data) {
+      if (data.hasOwnProperty(subunit)) {
         var color;
         var subunitData = data[subunit]
-        if ( ! subunit ) {
+        if (!subunit) {
           continue;
         }
-        else if ( typeof subunitData === "string" ) {
+        else if (typeof subunitData === "string") {
           color = subunitData;
         }
-        else if ( typeof subunitData.color === "string" ) {
+        else if (typeof subunitData.color === "string") {
           color = subunitData.color;
         }
         else {
           color = this.options.fills[ subunitData.fillKey ];
         }
         //if it's an object, overriding the previous data
-        if ( subunitData === Object(subunitData) ) {
+        if (subunitData === Object(subunitData)) {
           this.options.data[subunit] = defaults(subunitData, this.options.data[subunit] || {});
           var geo = this.svg.select('.' + subunit).attr('data-info', JSON.stringify(this.options.data[subunit]));
         }
         svg
-          .selectAll('.' + subunit)
-          .transition()
-          .style('fill', color);
+            .selectAll('.' + subunit)
+            .transition()
+            .style('fill', color);
       }
     }
   };
@@ -634,31 +633,31 @@
   Datamap.prototype.updatePopup = function (element, d, options) {
     var self = this;
     element.on('mousemove', null);
-    element.on('mousemove', function() {
+    element.on('mousemove', function () {
       var position = d3.mouse(this);
       d3.select(self.svg[0][0].parentNode).select('.datamaps-hoverover')
-        .style('top', ( (position[1] + 30)) + "px")
-        .html(function() {
-          var data = JSON.parse(element.attr('data-info'));
-          //if ( !data ) return '';
-          return options.popupTemplate(d, data);
-        })
-        .style('left', ( position[0]) + "px");
+          .style('top', ( (position[1] + 30)) + "px")
+          .html(function () {
+            var data = JSON.parse(element.attr('data-info'));
+            //if ( !data ) return '';
+            return options.popupTemplate(d, data);
+          })
+          .style('left', ( position[0]) + "px");
     });
 
     d3.select(self.svg[0][0].parentNode).select('.datamaps-hoverover').style('display', 'block');
   };
 
-  Datamap.prototype.addPlugin = function( name, pluginFn ) {
+  Datamap.prototype.addPlugin = function (name, pluginFn) {
     var self = this;
-    if ( typeof Datamap.prototype[name] === "undefined" ) {
-      Datamap.prototype[name] = function(data, options, callback, createNewLayer) {
+    if (typeof Datamap.prototype[name] === "undefined") {
+      Datamap.prototype[name] = function (data, options, callback, createNewLayer) {
         var layer;
-        if ( typeof createNewLayer === "undefined" ) {
+        if (typeof createNewLayer === "undefined") {
           createNewLayer = false;
         }
 
-        if ( typeof options === 'function' ) {
+        if (typeof options === 'function') {
           callback = options;
           options = undefined;
         }
@@ -666,7 +665,7 @@
         options = defaults(options || {}, defaultOptions[name + 'Config']);
 
         //add a single layer, reuse the old layer
-        if ( !createNewLayer && this.options[name + 'Layer'] ) {
+        if (!createNewLayer && this.options[name + 'Layer']) {
           layer = this.options[name + 'Layer'];
           options = options || this.options[name + 'Options'];
         }
@@ -676,7 +675,7 @@
           this.options[name + 'Options'] = options;
         }
         pluginFn.apply(this, [layer, data, options]);
-        if ( callback ) {
+        if (callback) {
           callback(layer);
         }
       };
@@ -684,19 +683,23 @@
   };
 
   // expose library
-  if ( typeof define === "function" && define.amd ) {
-    define( "datamaps", function(require) { d3 = require('d3'); topojson = require('topojson'); return Datamap; } );
+  if (typeof define === "function" && define.amd) {
+    define("datamaps", function (require) {
+      d3 = require('d3');
+      topojson = require('topojson');
+      return Datamap;
+    });
   }
   else {
     window.Datamap = window.Datamaps = Datamap;
   }
 
-  if ( window.jQuery ) {
-    window.jQuery.fn.datamaps = function(options, callback) {
+  if (window.jQuery) {
+    window.jQuery.fn.datamaps = function (options, callback) {
       options = options || {};
       options.element = this[0];
       var datamap = new Datamap(options);
-      if ( typeof callback === "function" ) {
+      if (typeof callback === "function") {
         callback(datamap, options);
       }
       return this;