2
votes

On a custom leaflet map, I am trying to build a function to fill the background color of a cluster icon from the color of the markers that are inside the cluster. For example, if a cluster has 7 green markers and 2 red markers, fill the cluster at 77% green and else in red.

I'm using markerCluster plugin, and awesome marker plugin together.

For the moment, here is what I have :

var clusters = L.markerClusterGroup({
  spiderfyOnMaxZoom: false,
  showCoverageOnHover: false,
  zoomToBoundsOnClick: true,
  iconCreateFunction: function (cluster) {
    var markers = cluster.getAllChildMarkers();
    console.log(markers);
    markers.forEach(function (m) {
      var color = m.defaultOptions.icon.options.markerColor;
      console.log(color);
    });
    var html =
      '<span class="circle circle-' + markers[0].feature.properties["Examen"] +
      '">' + markers.length + "</span>";
    return L.divIcon({ html: html, className: "marker-cluster", iconSize: L.point(32, 32) });
  },
});

I see that I can get the number of markers inside each cluster and the associated color, something like this. enter image description here

So my question is, from this, how can I loop through "color" to get the percentage of each color inside the cluster ?

My goal is then to use this percentage to fill the background color of the cluster..to get something like this ?

enter image description here

I saw many example about this, like here here here here and here but I wonder if I cannot have something similar without tons of complicated code like in those examples ?

EDIT:

Ok so thanks to the kind help of @IvanSanchez, I reproduced the provided code into my project and it is working ! I had to change it a little bit to make it work and I tried with icon-gradient and linear gradient.

Below is my final culsterGroup function and I show a full example of the two versions here (iconic css) and here ( linear-gradient). I'm sorry I cannot post it here as the code is too long for this editor :)

Once implemented, I made some changes. - Because I was getting only the first letter of my colors, the css was not working. So I wrote :

stops.push(color + ' ' + startPercent + '%');
stops.push(color + ' ' + endPercent + '%');

instead of

 stops.push(color[i] + ' ' + startPercent + '%');
 stops.push(color[i] + ' ' + endPercent + '%');
  • I also had to change my span in the 'html' var by a div, as the marker-cluster css is applied on div by default.

    var clusters = L.markerClusterGroup({ spiderfyOnMaxZoom: false, showCoverageOnHover: false, zoomToBoundsOnClick: true,

        iconCreateFunction: function (cluster) {
            var markers = cluster.getAllChildMarkers();
            var childCount = cluster.getChildCount();
            console.log(markers);
            var stops = [];
    
            for (let i=0, l=markers.length; i<l; i++) {
                var color = markers[i].defaultOptions.icon.options.markerColor;         
                let startPercent = 100 * (i/l);
                let endPercent = 100 * (i+1)/l;
                stops.push(color + ' ' + startPercent + '%');
                stops.push(color + ' ' + endPercent + '%');     
            }
            var html = '<div class="circleMarker" style="background: linear-gradient(to right,' + stops.join(',') + '" >' + markers.length + "</div>";
            return L.divIcon({ html: html, className: "marker-cluster", iconSize: L.point(40, 40) });
    
          },
    });
    
  • Also, some mixtures of colors no longer worked with this technique: If we look at the color palette of the awesomeMarker plugin, some colors don't have equivalent in css, like 'lightRed' or 'dardkRed'. So to fit to the color I use in my project, I changed the colors of the gradient so the clusters colors fit perfectly my individuals markers colors.

  • And as icon-gradient is not supported by firefox and IE, I added a condition to show icon-gradient cluster on chrome, and standard linear-gradient on firefox and IE.

Here is the final piece of code:

     var clusters = L.markerClusterGroup({
        spiderfyOnMaxZoom: false,
        showCoverageOnHover: false,
        zoomToBoundsOnClick: true,

        iconCreateFunction: function (cluster) {
            var markers = cluster.getAllChildMarkers();
            var childCount = cluster.getChildCount();
            console.log(markers);
            var stops = [];
            for (let i=0, l=markers.length; i<l; i++) {
            var color= markers[i].defaultOptions.icon.options.markerColor;  
            if (color==="red"){ 
            color="#D13D29";
            }else if( color === "orange"){
            color="#F69730";
            }else if(color === "green"){
            color="#6FAC25";
            }else if(color === "cadetblue"){
            color="#406473";
            }else if(color ==="darkred"){
            color="#A03336 ";
            }else if(color === "beige"){
            color="#FFC78C";
            }else if(color === "darkgreen"){
            color="#708023";
            }else if(color === "lightgreen"){
            color="#B8F471";
            }else if(color === "blue"){
            color="#37A7D9 ";
            }else if(color === "darkblue"){
            color="#0065A0";
            }else if(color === "lightblue"){
            color="#88DAFF";
            }else if(color === "purple"){
            color="#CD50B5";
            }else if(color === "darkpurple"){
            color="#593869";
            }else if(color === "pink"){
            color="#FF90E8";
            }else if(color === "gray"){
            color="#575757";
            }else if(color === "lightgray"){
            color="#A3A3A3";
            }
                let startPercent = 100 * (i/l);
                let endPercent = 100 * (i+1)/l;
                stops.push(color + ' ' + startPercent + '%');
                stops.push(color + ' ' + endPercent + '%');     
            }
            if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1  || navigator.userAgent.indexOf("MSIE") != -1 || !!document.documentMode == true ){
                var html = '<div class="circleMarker" style="background: linear-gradient(to right, ' + stops.join(',') + '" >' + markers.length + "</div>";
                return L.divIcon({ html: html, className: "marker-cluster", iconSize: L.point(40, 40) });
            }else{
                var html = '<div class="circleMarker" style="background: conic-gradient(' + stops.join(',') + '" >' + markers.length + "</div>";
                return L.divIcon({ html: html, className: "marker-cluster", iconSize: L.point(40, 40) });

            }
          },
    });

Finaly after testing, the linear gradient doen't work on Ie browser. So I ended up with these condition

var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
        if(isChrome){
        var html = '<div class="circleMarker" style="background: conic-gradient(' + stops.join(',') + '" >' + markers.length + '</div>';
            return L.divIcon({ html: html, className: "marker-cluster", iconSize: L.point(40, 40) });
        }

        else if(navigator.userAgent.toLowerCase().indexOf('firefox') > -1  ){
            var html = '<div class="circleMarker" style="background: linear-gradient(to right, ' + stops.join(',') + '" >' + markers.length + '</div>';
            return L.divIcon({ html: html, className: "marker-cluster", iconSize: L.point(40, 40) });
        }else {
             var html = '<div class="markerCluster"><span>' + markers.length + '</span></div>';
            return L.divIcon({ html: html, className: "marker-cluster" });
            }
      },

Now if it's chrome, I use icon-gradient, if it's Firefox, linear-gradient, and if it's IE, I draw circles like the original cluster icon.

I didn't find a way to restore the normal cluster icon just for IE browser..

1
You're already looping through the markers in each cluster - but what's the desired final result? A miniature pie chart? A color blend? A square with vertical/horizontal stripes with the colors?IvanSanchez
Hello Ivan, thanks for your reply again !! I edited the question with an image..In fact I thought about circles, ,same shape as the clusters, but filled proportionally with the color of the markers. I'm looking at the mapbox example trying to see if I can do something with it..blogob

1 Answers

3
votes

Since you're explicitly mentioning the background colour of a cluster marker, I would suggest leveraging the conic-gradient CSS function, which is a specific type of CSS gradient. As explained in the MDN article about using CSS gradients, using gradients with duplicated stops allows for creating sharp edges in such a gradient.

e.g. something like...

<div style='width:50px; height:50px;
  background:  linear-gradient(to right, 
    lime 0%, lime 25%, 
    red 25%, red 50%,
    cyan 50%, cyan 75%, 
    yellow 75%, yellow 100% ); 
'></div>

...will look like:

enter image description here

And something like...

<div style='width:50px; height:50px;
  border-radius:25px;
  background:  conic-gradient(
    lime 0%, lime 40%, 
    red 40%, red 60%,
    cyan 60%, cyan 88%, 
    yellow 88%, yellow 100% ); 
'></div>

...will look like...

sample conic gradient

So assuming that you've got an array of strings representing CSS colors, you can do a bit of string manipulation to turn that into a string representing the CSS function for a gradient, e.g:

let colours = ['red','red','red','purple','green','green'];

let stops = [];

for (let i=0, l=colours.length; i<l; i++) {
  let startPercent = 100 * (i/l);
  let endPercent = 100 * (i+1)/l;
  stops.push(colours[i] + ' ' + startPercent + '%');
  stops.push(colours[i] + ' ' + endPercent + '%');
}

let gradient = "conic-gradient(" + stops.join(',') + ")";

...that'll create a gradient variable holding a string like...

conic-gradient(red 0%,red 16.666666666666668%,red 16.666666666666664%,red 33.333333333333336%,red 33.33333333333333%,red 50%,purple 50%,purple 66.66666666666667%,green 66.66666666666666%,green 83.33333333333333%,green 83.33333333333334%,green 100%)

...and when applied to an element in a webpage, that'll look like:

dynamic conic gradient

See a working demo here.


You might need to tweak things a bit to adapt this technique to your code, but I'd suggest something like:

var stops = [];

for (let i=0, l=markers.length; i<l; i++) {
    var color = m.defaultOptions.icon.options.markerColor;
    let startPercent = 100 * (i/l);
    let endPercent = 100 * (i+1)/l;
    stops.push(colours[i] + ' ' + startPercent + '%');
    stops.push(colours[i] + ' ' + endPercent + '%');    
});
var html = '<span ' +
    'class="circle circle-' + markers[0].feature.properties["Examen"] + '" ' +
    'style="background: conic-gradient(' + stops.join(',') + '" ' +
>' + markers.length + "</span>";

Please note that, at the time of this writing, browser support for the conic-gradient CSS function is not consistent. Therefore, this technique shouldn't be used if you want stuff to work with people using Firefox, at least for the time being.