Plotting thousands of points in Leaflet - a way to improve the speed?


Hi friends,

I've created a dot-density map of a particular location, which involves around 60,000 points (each point = 100 people). I was wondering if there was a way to improve the speed with which the map renders when you zoom in and out.

The map is produced using Leaflet, which I want to publish on my blogdown site. However, with 60,000 points, the map is understandably quite slow.

Here's a simplified example of what my code and map look like:


# Randomly generate 60,000 coordinate pairs
num <- 60000
df <- data.frame(
        lon = runif(num, min = -97.06, max = -96.5),
        lat = runif(num, min = 32.540, max = 32.957))

# Convert to a simple feature
df <- st_as_sf(df, coords = c("lon", "lat"), crs = 4326, agr = "constant")

# Create map
leaflet() %>% 
  addProviderTiles(providers$Esri.WorldGrayCanvas) %>% 
  addCircles(data = df, weight = 0)

I was wondering if there was a way to improve this, without reducing the number of dots or using clusters?

For example, could I make it so that the points appear as an image or a tile on the map instead? (I am probably using the wrong terminology here, but hopefully you get my meaning).

Grateful for any tips you can provide. Thanks for your time.


You're right: 60 000 is a lot of points. I have a couple of ideas, but be warned that I'm more familiar with Leaflet itself (in JavaScript) than the R wrapper for it, so it might take a bit of work on your part to translate the examples in the Leaflet reference to the R wrapper.

If it's only performance while zooming and panning that you're concerned about, you could try adding the options updateWhenZooming: false and updateWhenIdle: true to your base map layer (it's defined in GridLayers, not vector ones like your points, but it might help):

addProviderTiles(providers$Esri.WorldGrayCanvas, options = providerTileOptions(
  updateWhenZooming = FALSE,      # map won't update tiles until zoom is done
  updateWhenIdle = TRUE           # map won't load new tiles when panning
)) %>% ...

A heavier option might be try to force the map to render using canvas, since canvas apparently does better with large numbers of points:

leaflet(options = leafletOptions(preferCanvas = TRUE)) %>% ...

Hope that helps! Let me know :slight_smile:


@rensa, thank you very much for this response!

I'm away from my computer for a couple of days but will take a look at these when I get back. I did try the preferCanvas option but it did not appear to make a difference - I will have another go to ensure I did it correctly. Otherwise, I will try the updateWhen... options you suggest. Thanks again for your time, much appreciated.


No worries, @SteveXD! I've been doing a big project with Leaflet over the last few months and started hitting performance problems with raster layers, so I'm interested to see how you go.

You can verify whether you're actually using the Canvas renderer by right-clicking on the map. If it gives you image-related menu options, you're working with Canvas. You might also want t overify that you're using an up-to-date version of the leaflet R package, since it only migrated to leaflet.js 1.x semi-recently (about a month ago, I think), and that changed a lot under the hood.

Also, if you're interested in points rather than _areas, you might want to add addCircleMarkers rather than addCircles. The former are a fixed size regardless of zoom (their radii are measured in pixels rather than metres).


@rensa - perfect! It runs like a dream compared to before.

I did all the three things you suggested:

  • Updated Leaflet (from v1.1.0 to v2.0.0)
  • Set the updateWhen... options to FALSE
  • Set preferCanvas to TRUE

The map handles nicely when I run it locally in my browser without freezing like before. Thanks again!

(Should it be of interest, I've attached an image of the end product below: a dot-density map of my local area, at the block group level, where each dot = 100 people. So ~6 million people = 60k dots.).


Nice! That map is intense :sunglasses: