Search code examples
javascriptmarkerclusterer

MarkerClustererPlus: set icon color/url independent of size


I'm plotting a few hundred thousand points via MarkerClustererPlus, and I want to set groups of cluster icons (colour) based on some exteral property (not based on number of markers represented).

The only way I can think to do this is by creating multiple MarkerClusterer objects and passing in a different options object, but I feel like I'll take a big performance hit doing that. Is there a better way?

Marker Clusterer Plus with differently sized icons scaled to fit.

Given the image above, I'd like 139, 24, and 5 to be yellow and 213, 25, 30, and 2 to be red; and if possible, update their styles/options via setOptions:
mc.group[0].setOptions({"url": imgPath +lookupThreshold(severity)+ '.svg' });
mc.group[1].setOptions({"url": imgPath +lookupThreshold(severity)+ '.svg' });

P.S. If anyone is interested, I tweaked the lib so the cluster icon scales to its size by supplying an svg image and increasing the width & height in the options object:

var mcOptions = {
  "styles": [{
    "height": 19,
    "url": img/map/clusters/",
    "width": 19
  },{
    "height": 24,
    "url": img/map/clusters/",
    "width": 24
  }, {…}]
};
for ( var s = mcOptions.styles.length-1; s >= 0; s-- )
{ mcOptions.styles[s].url += lookupThreshold(severity) + '.svg'; }
// lookupThreshold switches severity and returns a string: red, orange, …

Then added the following to markerclusterer.js:

line 275: this.backgroundSize_ = style.backgroundSize || "contain";
line 300: style.push('background-size:' + this.backgroundSize_ + ';');

Works in Ffx 19.0.2, Chrome 26.x, Chrome Canary 28.x, Safari 6.0.2, IE 9.0.8 (but not Opera 12.15).

EDIT It seems there is not much of a performance hit from creating multiple instances of MarkerClusterer; however, it appears that the properties/options object passed to MC is shared amoung the instances of MCs.

Solved I had to modify the MarkerClustererPlus library near line 665 to clone opt_options (the lib was using a reference, which caused all previous opt_options to be overwritten with the newest/last one passed).


Solution

  • So it turns out the trouble was coming from the MarkerClustererPlus lib itself:

    656:  function MarkerClusterer(map, opt_markers, opt_options) {
    …
    665:    opt_options = opt_options || {};
    

    Line 665 creates a reference to the existing object, instead of a new copy. I couldn't use MarkerClusterer.prototype.extend from line 1539 because it does not make a deep copy (and it extends only an object's prototype).

    So, I wrote my own deep-copy function (jsfiddle), which I made globally available (rather than add it to the prototypes of both Array and Object):

    function deepCopy(obj) {  
      this.cloneArr = function (arr) {
        var newArr = [];
        for ( var i = arr.length-1; i >= 0; i-- ) newArr[i] = this.evalObj( arr[i] );
        return newArr;
      };
      this.cloneObj = function(obj) {
        var newObj = {};
        for ( var prop in obj ) newObj[prop] = this.evalObj( obj[prop] );
        return newObj;
      };
      this.evalObj = function(obj) {
        switch ( typeof obj ) {
          case 'object':
            if ( Array.isArray( obj ) ) return this.cloneArr( obj );
            if ( obj instanceof Date === false ) return this.cloneObj( obj );
            // pass thru dates, strings, numbers, booleans, and functions
          default: return obj; // primitive
        }
      };
      return this.evalObj(obj);
    }
    

    I then altered MarkerClustererPlus.js to the following:

    656:  function MarkerClusterer(map, opt_markers, opt_optionsG) {
    …
    665:    var opt_options = deepCopy( opt_optionsG ) || {};
    

    I tested having 5 instances of MarkerClustererPlus (each with 5000 markers, 25000 total), and there was no decernable performance impact compared to having a single MC+ instance.

    Screenshot of multiple instances of MarkerClustererPlus