Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add text clipping option on symbol layers #4064

Open
pixnlove opened this issue Jan 29, 2017 · 32 comments
Open

Add text clipping option on symbol layers #4064

pixnlove opened this issue Jan 29, 2017 · 32 comments

Comments

@pixnlove
Copy link

pixnlove commented Jan 29, 2017

Hi everybody !

I have a problem with my symbol layer : my text is not hidden by other symbols, how to do like on the second screenshot (screenshot from https://roadtrippers.com who is using Mapbox too)? FYI, my layer looks like :

var layer = {
        "id": "marker",
        "type": "symbol",
        "source": "markers",
        "layout": {
          "text-font": ["Lato Black"],
          "text-padding": 20,
          "text-anchor": "top",
          "text-offset": [0, -2.3],
          "text-size": 14,
          "text-allow-overlap": true,
          "text-field": "{index}",
          "text-justify": "center",
          "icon-offset": [0, -22],
          "icon-image": "marker",
          "icon-allow-overlap": true
        },
        "paint": {
          "text-color": "#FFFFFF"
        }
     };

issue_mapbox

@pixnlove pixnlove changed the title Text of symbol layer doesn't Text of symbol layer isn't hidden Jan 29, 2017
@mollymerp
Copy link
Contributor

Hi @pixnlove,
I would try setting "text-allow-overlap": false and "text-optional": true. Also, Roadtrippers is using the Marker class, not a symbol layer.

This doesn't appear to be a bug or feature request, and unfortunately we don't have the bandwidth to offer support on Github. I suggest you post future questions like this on StackOverflow.

@pixnlove
Copy link
Author

pixnlove commented Feb 1, 2017

Thanks for your help @mollymerp. I think it's a feature request but maybe I'm doing something wrong. When I'm adding these two properties, I'm losing some text values and text index behaves badly. My map looks like now:

capture d ecran 2017-02-01 a 18 05 24

And my layer is now:

var layer = {
        "id": "marker",
        "type": "symbol",
        "source": "markers",
        "layout": {
          "text-font": ["Lato Black"],
          "text-padding": 20,
          "text-anchor": "top",
          "text-offset": [0, -2.3],
          "text-size": 14,
          "text-allow-overlap": true,
          "text-field": "{index}",
          "text-justify": "center",
          "icon-offset": [0, -22],
          "icon-image": "marker",
          "icon-allow-overlap": true
        },
        "paint": {
          "text-color": "#FFFFFF"
        }
     };

Using marker like Roadtrippers is not very powerful with a large dataset, I would like to do the same thing with a symbol layer but you seem to consider the text as an other layer. It will be cool to have a layout property "text-clip-icon".

@mollymerp mollymerp changed the title Text of symbol layer isn't hidden Add text clipping option on symbol layers Feb 1, 2017
@mollymerp
Copy link
Contributor

@pixnlove I see what you mean. I think this might be a technical limitation of our current implementation of symbol layers – we draw all icons in a layer, and then all text on top of that, so there is no maintained relationship between a single icon and its text label in the render order.
I agree this would be a great enhancement, but it would require a significant refactor.

@pixnlove
Copy link
Author

@mollymerp, do you have some news about this feature request ?

@mollymerp
Copy link
Contributor

@pixnlove this is not on our immediate roadmap unfortunately.

@nrako
Copy link

nrako commented Apr 10, 2017

I have the same undesired behavior but with a symbol layer. Using an icon with some inner text, I'm using that workaround with text-optional. But that isn't a really neat result. I wish the text and icon/marker wouldn't appear to be drawn on two different layer.

screen shot 2017-04-10 at 09 34 06
An example here, 10 collides with 9 and therefore is hidden, but 9 is still above the icon of 10 😑.

@jfirebaugh
Copy link
Contributor

Equivalent issue for native: mapbox/mapbox-gl-native#8235.

@wthorp
Copy link

wthorp commented Oct 12, 2017

I'll go ahead and upvote this feature.

@sjorsrijsdam
Copy link

For those interested, we also ran into this issue and we "solved" it by generating icons on the fly on a off-screen canvas and using token substitution to match the correct image with the correct point.

Roughly what we did is:

prices.forEach((price) => {
    const imageData = createIcon(price);
    map.addImage('house-' + price, imageData);
});

map.addLayer({
    layout: {
        'icon-name': 'house-{price}'
    }
});

@nrako
Copy link

nrako commented Nov 16, 2017

Oh that's a very interesting approach! Thanks @sjorsrijsdam

@nrako
Copy link

nrako commented Mar 15, 2018

@sjorsrijsdam – I was looking at the API doc for map#addImage and I noticed the following :

An Map#error event will be fired if there is not enough space in the sprite to add this image.

Then I found this information:

Sprites can have a maximum size of 1024x1024 pixels (2048x2048 for high DPI displays) – that means the whole sprite containing all icons must be smaller than 1024x1024 pixels.

Did you ever encountered that issue, what type of scale do you have experience with? Could you confirm that 1024x1024 is the limit?

@nrako
Copy link

nrako commented Mar 15, 2018

Not sure this limit is actually enforced... I can't spot anything on Map.addImage on Style.addImage nor the ImageManager.addImage

🤔

@sjorsrijsdam
Copy link

@nrako We never ran into limits of the sprite size. We did have to abandon this idea though because of performance problems on mobile. Every time you would move or zoom the map, Mapbox would need to transfer the icon data of each icon to the WebGL context. That turned out to be a bottle neck.

@nrako
Copy link

nrako commented Mar 15, 2018

@sjorsrijsdam – Thank you for sharing your experience. I'm not totally sure I understand why Mapbox would need to transfer all images data to the WebGL context on all "camera change" – my quick research on that topic was vain. I might just try and inspect that by myself.

Could you share what approach you ended up taking?

@jfirebaugh
Copy link
Contributor

Mapbox GL JS does not transfer all images data to the WebGL context on every camera change -- only when a tile is loaded or the image is changed.

@sjorsrijsdam
Copy link

We ended up using just a single image for all items on the map and used a popup to show the price.
I probably should have added that we also fetched new data for that layer on every moveend en zoomend. So, it was probably the setData call that would trigger a regeneration of tiles.

@Sirpion
Copy link

Sirpion commented Jul 1, 2018

At the moment it turns out that there are 3 possible options (to create behavior as in the image in the first post):
1. Use the markers
https://bl.ocks.org/stevage/23d881a66e2bcca385d8cc074691b674
2. Generating icons on the fly
https://jsfiddle.net/gxc5ca9d/15/
3. Adding each icon to a new layer
https://codepen.io/Sirpion/pen/MXLvbO/

Suppose there is a need to create> 400 labels with texts that will be moved on the map in real time.
Which of these options will cause less damage to performance?
Or there were new options to implement this behavior (without hiding the text in a collision)?

@andrewharvey
Copy link
Collaborator

One solution to this may be to group these layers in a way that tells the renderer to draw these on a per feature level first. ie. feature A layer 1,2,3 . then render feature B layer 1,2,3, then composite those two.

@kapone3047
Copy link

This is a ridiculously simple and common use case that I can't believe is not supported. Have people found any practical workarounds for this?

@mushon
Copy link

mushon commented Oct 27, 2019

Almost 3 years after this issue was opened, I still can't believe there is no proper solution from Mapbox to this basic issue. This is causing us major grief and is a huge blindspot for Mapbox

Screen Shot 2019-10-27 at 17 45 10

Please tell me this is already fixed and I'm just looking in the wrong place

@vjeranc
Copy link

vjeranc commented Dec 13, 2019

There seems to be a symbolTextAndIcon shader present. It draws the text and the icon at appropriate times but is not used for the purposes of our symbol layers.

1215bf1

I've tried it out, drawing an image in the text-field and it does work. Although, it would be nice that something like below works:

'text-field': ['format',
          ['get', 'number_field'], { text-offset: [0, 0.5] },
          "\n", {},
          ['image', ['get', 'icon_field']], { text-offset: [0, 0.5] }
        ],

I guess, given the shader code one could make their own renderer/painter/customlayer for drawing text and icon in a single pass.

@gotestrand
Copy link

Hi, any news on this feature request? Any possible workaround?

@gitcatrat
Copy link

Sorry, repeating the question: has anyone found reasonable workaround?

@lukashass
Copy link

@gotestrand @gitcatrat We ended up generating icons on the fly with #map.event:styleimagemissing as suggested above.

Have a look at Map.vue#L258. We did not notice any performance problems, but keep in mind our icons are limited to only around 200 different ones. You can see the result of this at https://kiel-live.github.io/map

@gitcatrat
Copy link

@lukashass Not really an option because text-field contains a float that can be anything from 0 to millions.

@tristyntech
Copy link

tristyntech commented Jan 26, 2021

I don't know if this will work for everyone, but this worked for me. On the bottom you can see a number of the circles do NOT have the text overlay. On the top you can see it is fixed. The way I did it was:

  1. set the text symbol layers 'text-padding': 0. The text padding defaults to 2, so it makes the text clustering a little over-sensitive
  2. 2 and 3 digit numbers have a wider radius than the single digit numbers (obviously). So for the 2 digit numbers I made the text smaller than the single digit numbers. And I made the 3 digit numbers slightly smaller than that, using an expression.
    I wish that I was able to keep the text the same size but this was my work around. And that's it. Hope it helps someone.

Screen Shot 2021-01-26 at 1 07 55 AM

Screen Shot 2021-01-25 at 2 56 05 PM

@xabbu42
Copy link

xabbu42 commented Jan 28, 2021

this seems to be similar to the issue #10002 and could also be helped by #10123

@julianmlr
Copy link

      let markers = {};
      this.realEstates.features.forEach(realEstate => {
        let el = document.createElement('div');
          el.innerHTML = "<span style=\"font-family:quicksand\" class='text-body-1'>" + this.$n(realEstate.properties.price, 'currencyNoCents') + "</span>" ;
        el.className = 'rounded-label';
        markers[realEstate.id] = new Mapbox.Marker(el)
            .setLngLat(realEstate.geometry.coordinates)
            .addTo(this.map);
      })

.rounded-label {
background: white;
padding: 0.25em 0.5em;
font-size: 16pt;
border-radius: 1em;
border:1px solid lightgrey;

}

Here another version of this here https://bl.ocks.org/stevage/23d881a66e2bcca385d8cc074691b674
Makers really have a nice behavior. Maybe this helps you.

image

@pratik-kanthi
Copy link

@julianmlr markers aren't as performant as symbols when you have to draw hundreds of them

@Merynek
Copy link

Merynek commented Apr 26, 2023

Any updates? MapBox still has this issue, really?

@vjeranc
Copy link

vjeranc commented Apr 26, 2023

@Merynek

mapbox-gl-js isn't under an open/permissive license anymore. Any contributions coming from Mapbox will probably align with their business goals.

Any other company is probably using the older version that is under a permissive license and changes made to that would be in some public fork. Not sure if it exists.

https://github.com/maplibre/maplibre-gl-js This seems to be a fork.

And here's the same issue maplibre/maplibre-gl-js#49

The reason why this is still not implemented is because it requires significant nontrivial changes to the shaders that render elements.

@gtsl-2
Copy link

gtsl-2 commented Nov 7, 2023

  1. Set geojson to connect all the id's that go into cluster and set them to the id of clusterProperties.
    cluster: true,
    clusterMaxZoom: 20,
    clusterRadius: 100,
    clusterProperties: {
      id: ["concat", ["get", "id"]],
  1. Next, for each rendering, if the source's multiple cluster ids overlap, only the first one should be used. Then, Marker is created from feature3 data.
map.current.on("render", async function renderListener() {
    const features = map.current.querySourceFeatures("spotsources");
    const features2 = Object.values(
       features.reduce((acc, item) => {
         acc[item.properties.id] = item;
         return acc;
       }, {})
     );
 function filterArray(array) {
       var result = [];
       for (let i = 0; i < array.length; i++) {
         var isSubstring = false;
         for (let j = 0; j < array.length; j++) {
           if (i !== j) {
             if (array[j].properties.id.includes(array[i].properties.id)) {
               isSubstring = true;
               break;
             }
           }
         }
         if (!isSubstring) {
           result.push(array[i]);
         }
       }
       return result;
     }
     const features3 = filterArray(features2);

This way, there is absolutely no overlap of multiple clusters!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests