Skip to main content
Bumped by Community user
found a partial answer, but still hope for better.
Source Link
Troy Buerge
  • 391
  • 1
  • 3
  • 13

Ok, I have a working hack here. I'll share the code, and hope for improvements. I wrote my own functions that loop through the points and labels and show, or hide the tooltips based on whether or not they would overlap any existing point or tooltip. I'm using the permanent: true, option to show them in the first place. I have my map load zoomed out and then use <body onload="getmap()"> to call a sub that resets the zoom. I have my labels hide on mymap.on('zoomstart', function(e) and then show on mymap.on('zoomend', function(e). I'm clueless how bad or slow this is, but it works.

tt = tooltip, l = layer.

    var ct = 0;  //this is so if a lbl can't show move it and try again
                 // and use this to prevent a recursive loop.
    let labels = [];  //holds all my geometries for hit testing

    function getpointrect() {    //gets point circles
        geojsonLayer.eachLayer(function (l) {
            var pt = mymap.latLngToLayerPoint({
                lat: l.feature.geometry.coordinates[1],
                lon: l.feature.geometry.coordinates[0]
                });
                var size = l.getRadius() + l.options.weight; 
            let rects = [{x:pt.x - size,y:pt.y - size, w: size * 2, h: size * 2}];
            labels.push(rects);
        });
    };
    
    
    
    function setlabel(tt, l){     //loops through labels and gets geometries
                               //and checks for hits
    var pt = mymap.latLngToLayerPoint({
                lat: l.feature.geometry.coordinates[1],
                lon: l.feature.geometry.coordinates[0]
                });
       var wd = getTextWidth(tt.getContent(), "regular 12px sans-serif");
       var y;
       if (tt.options.offset.y > 0) {   //fancy stuff for different lbl positions.
           y = -((tt.options.offset.y / 2) + 14);//manually set label height.
       }else{
           y = -tt.options.offset.y / 2;
       }; 
       let rects = [{x:pt.x - (wd / 2), y:pt.y + y, w: wd, h: 14}];          
       if (labels.length == 0) {//if array is empty adds first lable to the map
           labels.push(rects);
           mymap.openTooltip(tt);
       }else{
           if (testhit(rects) == false) { //checks the rest of the labels
               labels.push(rects);//adds visible labels to array
               mymap.openTooltip(tt);  //shows label
               ct = 0;
           }else{
              if (ct == 0) {//if label can't show, moves it, and tries again
                 tt.options.offset.y = -tt.options.offset.y;
                 ct++;
                 setlabel(tt, l);
              }else {
              ct = 0;
              };
           };
       };
    };
    
    function testhit(rect){//this gets the rectangles for the following math.
    var arrayLength = labels.length;
       var isoverlap = false;
       for (var i = 0; i < arrayLength; i++) {//loops through labels and points
         var rct = (labels[i]);
         var ck = intersect(rect,labels[i]);
         if (ck == true){
           isoverlap = ck;
           return isoverlap;/overlaps
         };
       };
       return isoverlap;  does not overlap
    };
    function intersect(a, b) {   // all the fancy math that checks stuff
      return (a[0].x < b[0].x + b[0].w &&
      b[0].x < a[0].x + a[0].w &&          
      a[0].y < b[0].y + b[0].h &&          
      b[0].y < a[0].y + a[0].h)         
    };

    function getTextWidth(text, font) {//checks the label length.
       var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
       var context = canvas.getContext("2d");
       context.font = font;
       var metrics = context.measureText(text);
       return metrics.width;
    }; 

Ok, I have a working hack here. I'll share the code, and hope for improvements. I wrote my own functions that loop through the points and labels and show, or hide the tooltips based on whether or not they would overlap any existing point or tooltip. I'm using the permanent: true, option to show them in the first place. I have my map load zoomed out and then use <body onload="getmap()"> to call a sub that resets the zoom. I have my labels hide on mymap.on('zoomstart', function(e) and then show on mymap.on('zoomend', function(e). I'm clueless how bad or slow this is, but it works.

tt = tooltip, l = layer.

    var ct = 0;  //this is so if a lbl can't show move it and try again
                 // and use this to prevent a recursive loop.
    let labels = [];  //holds all my geometries for hit testing

    function getpointrect() {    //gets point circles
        geojsonLayer.eachLayer(function (l) {
            var pt = mymap.latLngToLayerPoint({
                lat: l.feature.geometry.coordinates[1],
                lon: l.feature.geometry.coordinates[0]
                });
                var size = l.getRadius() + l.options.weight; 
            let rects = [{x:pt.x - size,y:pt.y - size, w: size * 2, h: size * 2}];
            labels.push(rects);
        });
    };
    
    
    
    function setlabel(tt, l){     //loops through labels and gets geometries
                               //and checks for hits
    var pt = mymap.latLngToLayerPoint({
                lat: l.feature.geometry.coordinates[1],
                lon: l.feature.geometry.coordinates[0]
                });
       var wd = getTextWidth(tt.getContent(), "regular 12px sans-serif");
       var y;
       if (tt.options.offset.y > 0) {   //fancy stuff for different lbl positions.
           y = -((tt.options.offset.y / 2) + 14);//manually set label height.
       }else{
           y = -tt.options.offset.y / 2;
       }; 
       let rects = [{x:pt.x - (wd / 2), y:pt.y + y, w: wd, h: 14}];          
       if (labels.length == 0) {//if array is empty adds first lable to the map
           labels.push(rects);
           mymap.openTooltip(tt);
       }else{
           if (testhit(rects) == false) { //checks the rest of the labels
               labels.push(rects);//adds visible labels to array
               mymap.openTooltip(tt);  //shows label
               ct = 0;
           }else{
              if (ct == 0) {//if label can't show, moves it, and tries again
                 tt.options.offset.y = -tt.options.offset.y;
                 ct++;
                 setlabel(tt, l);
              }else {
              ct = 0;
              };
           };
       };
    };
    
    function testhit(rect){//this gets the rectangles for the following math.
    var arrayLength = labels.length;
       var isoverlap = false;
       for (var i = 0; i < arrayLength; i++) {//loops through labels and points
         var rct = (labels[i]);
         var ck = intersect(rect,labels[i]);
         if (ck == true){
           isoverlap = ck;
           return isoverlap;/overlaps
         };
       };
       return isoverlap;  does not overlap
    };
    function intersect(a, b) {   // all the fancy math that checks stuff
      return (a[0].x < b[0].x + b[0].w &&
      b[0].x < a[0].x + a[0].w &&          
      a[0].y < b[0].y + b[0].h &&          
      b[0].y < a[0].y + a[0].h)         
    };

    function getTextWidth(text, font) {//checks the label length.
       var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
       var context = canvas.getContext("2d");
       context.font = font;
       var metrics = context.measureText(text);
       return metrics.width;
    }; 
deleted 2047 characters in body
Source Link
Troy Buerge
  • 391
  • 1
  • 3
  • 13

Ok, this edit is to show what I've come up with so far. This is not a complete solution yet. I added the following code and can make it tooltips come and go as wanted on zoomend. I haven't figured out how to make it work when the data is loading the first time, and I haven't tested it with lots of points. tt = toolTip, l = layer

    function setlabel(tt, l){
       var pt = mymap.latLngToLayerPoint({
                lat: l.feature.geometry.coordinates[1],
                lon: l.feature.geometry.coordinates[0]
                });
       var wd = getTextWidth(tt.getContent(), "regular 12px sans-serif"); 
       let rects = [{x:pt.x - (wd / 2),y:pt.y - 7, w: wd, h: 14}];
       if (labels.length < 1) {
            labels.push(rects);
            mymap.openTooltip(tt);
       }else{
            if (testhit(rects)) {
               mymap.openTooltip(tt);
            };
       };
    }
    
    
    function testhit(rect){
    var arrayLength = labels.length;
       for (var i = 0; i < arrayLength; i++) {
         var rct = (labels[i]);
         var ck = intersect(rect,labels[i]);
         if (ck == true){
         break;
         }else{
         labels.push(rect);
         return true;
         };
       };
    };
    function intersect(a, b) {   
      return (a[0].x <= b[0].x + b[0].w &&
      b[0].x <= a[0].x + a[0].w &&          
      a[0].y <= b[0].y + b[0].h &&          
      b[0].y <= a[0].y + a[0].h)         
    };

It requires an array

    let labels = [];

And to call it

    mymap.on('zoomend', function() {
      labels.length = 0;
      geojsonLayer.eachLayer(function (l) {  
         if (l.getTooltip) {
            var toolTip = l.getTooltip();
            if (toolTip) {
              setlabel(toolTip, l);   
            }
          }
      });
});

Ok, this edit is to show what I've come up with so far. This is not a complete solution yet. I added the following code and can make it tooltips come and go as wanted on zoomend. I haven't figured out how to make it work when the data is loading the first time, and I haven't tested it with lots of points. tt = toolTip, l = layer

    function setlabel(tt, l){
       var pt = mymap.latLngToLayerPoint({
                lat: l.feature.geometry.coordinates[1],
                lon: l.feature.geometry.coordinates[0]
                });
       var wd = getTextWidth(tt.getContent(), "regular 12px sans-serif"); 
       let rects = [{x:pt.x - (wd / 2),y:pt.y - 7, w: wd, h: 14}];
       if (labels.length < 1) {
            labels.push(rects);
            mymap.openTooltip(tt);
       }else{
            if (testhit(rects)) {
               mymap.openTooltip(tt);
            };
       };
    }
    
    
    function testhit(rect){
    var arrayLength = labels.length;
       for (var i = 0; i < arrayLength; i++) {
         var rct = (labels[i]);
         var ck = intersect(rect,labels[i]);
         if (ck == true){
         break;
         }else{
         labels.push(rect);
         return true;
         };
       };
    };
    function intersect(a, b) {   
      return (a[0].x <= b[0].x + b[0].w &&
      b[0].x <= a[0].x + a[0].w &&          
      a[0].y <= b[0].y + b[0].h &&          
      b[0].y <= a[0].y + a[0].h)         
    };

It requires an array

    let labels = [];

And to call it

    mymap.on('zoomend', function() {
      labels.length = 0;
      geojsonLayer.eachLayer(function (l) {  
         if (l.getTooltip) {
            var toolTip = l.getTooltip();
            if (toolTip) {
              setlabel(toolTip, l);   
            }
          }
      });
});
found a partial answer.
Source Link
Troy Buerge
  • 391
  • 1
  • 3
  • 13

Ok, this edit is to show what I've come up with so far. This is not a complete solution yet. I added the following code and can make it tooltips come and go as wanted on zoomend. I haven't figured out how to make it work when the data is loading the first time, and I haven't tested it with lots of points. tt = toolTip, l = layer

    function setlabel(tt, l){
       var pt = mymap.latLngToLayerPoint({
                lat: l.feature.geometry.coordinates[1],
                lon: l.feature.geometry.coordinates[0]
                });
       var wd = getTextWidth(tt.getContent(), "regular 12px sans-serif"); 
       let rects = [{x:pt.x - (wd / 2),y:pt.y - 7, w: wd, h: 14}];
       if (labels.length < 1) {
            labels.push(rects);
            mymap.openTooltip(tt);
       }else{
            if (testhit(rects)) {
               mymap.openTooltip(tt);
            };
       };
    }
    
    
    function testhit(rect){
    var arrayLength = labels.length;
       for (var i = 0; i < arrayLength; i++) {
         var rct = (labels[i]);
         var ck = intersect(rect,labels[i]);
         if (ck == true){
         break;
         }else{
         labels.push(rect);
         return true;
         };
       };
    };
    function intersect(a, b) {   
      return (a[0].x <= b[0].x + b[0].w &&
      b[0].x <= a[0].x + a[0].w &&          
      a[0].y <= b[0].y + b[0].h &&          
      b[0].y <= a[0].y + a[0].h)         
    };

It requires an array

    let labels = [];

And to call it

    mymap.on('zoomend', function() {
      labels.length = 0;
      geojsonLayer.eachLayer(function (l) {  
         if (l.getTooltip) {
            var toolTip = l.getTooltip();
            if (toolTip) {
              setlabel(toolTip, l);   
            }
          }
      });
});

Ok, this edit is to show what I've come up with so far. This is not a complete solution yet. I added the following code and can make it tooltips come and go as wanted on zoomend. I haven't figured out how to make it work when the data is loading the first time, and I haven't tested it with lots of points. tt = toolTip, l = layer

    function setlabel(tt, l){
       var pt = mymap.latLngToLayerPoint({
                lat: l.feature.geometry.coordinates[1],
                lon: l.feature.geometry.coordinates[0]
                });
       var wd = getTextWidth(tt.getContent(), "regular 12px sans-serif"); 
       let rects = [{x:pt.x - (wd / 2),y:pt.y - 7, w: wd, h: 14}];
       if (labels.length < 1) {
            labels.push(rects);
            mymap.openTooltip(tt);
       }else{
            if (testhit(rects)) {
               mymap.openTooltip(tt);
            };
       };
    }
    
    
    function testhit(rect){
    var arrayLength = labels.length;
       for (var i = 0; i < arrayLength; i++) {
         var rct = (labels[i]);
         var ck = intersect(rect,labels[i]);
         if (ck == true){
         break;
         }else{
         labels.push(rect);
         return true;
         };
       };
    };
    function intersect(a, b) {   
      return (a[0].x <= b[0].x + b[0].w &&
      b[0].x <= a[0].x + a[0].w &&          
      a[0].y <= b[0].y + b[0].h &&          
      b[0].y <= a[0].y + a[0].h)         
    };

It requires an array

    let labels = [];

And to call it

    mymap.on('zoomend', function() {
      labels.length = 0;
      geojsonLayer.eachLayer(function (l) {  
         if (l.getTooltip) {
            var toolTip = l.getTooltip();
            if (toolTip) {
              setlabel(toolTip, l);   
            }
          }
      });
});
Source Link
Troy Buerge
  • 391
  • 1
  • 3
  • 13
Loading