How can I wrap long text strings in tmap HTML polygon popups or fixed widths? Can tmap recognize line breaks?

Viewed 288

I am using the tmap package (v. 3.0 and I also tried v. 3.2) in R to make an HTML map. I have a spatial polygon of both numeric and character text fields. In a few of my text fields, I have a list of items, which are semi-colon (;) separated. For some of these polygons, the text strings are so long that the popups can be wider than the HTML window. I have to zoom out of the map in order to see the contents of these popups.

I tried to incorporate breaks in these text strings to make new lines within that data field for the popup, but tmap does not recognize them. Such breaks as \n are not creating new lines in the popups.

Below is an example of trying to add different kinds of breaks to a column called long_names and its output:

# install latest version of `tmap` and `tmaptools`
# library(remotes)
# install_github("mtennekes/tmaptools")
# install_github("mtennekes/tmap")

# load libraries
  library(sf)
  library(raster)
  library(spData)
  library(tmaptools)
  library(tmap)

# use spData dataset called nz
# add example row with long list of names
  nz$long_names <- 'long name 1; long name 2; long name 3; long name 4; long name 5'

# try to make multiple lines in string for the popups
# try different codes for breaks
  nz$long_names_n <- gsub("; ", "\n", nz$long_names)
  nz$long_names_r <- gsub("; ", "\r", nz$long_names)
  nz$long_names_br1 <- gsub("; ", "<br/>", nz$long_names)
  nz$long_names_br2 <- gsub("; ", "<br>", nz$long_names)

# change tmap to view mode
  tmap_mode("view")
 
# plot interactive map
  tm_shape(nz) +
    tm_fill()

Output screenshot: long string in HTML popup and it doesn't wrap

In my real dataset, I have differing amounts of items listed within long_names, so I am hoping to have popups of different lengths (and up and down scrolling is fine) but similar widths.

I also tried using tmap_leaflet() with popupOptions() to adjust the maximum popup width, but it ignores it and gives me the same map as before:

# interactive map
  map1 <- tm_shape(nz) +
          tm_fill()

# convert to leaflet object
  lf <- tmap_leaflet(map1)

# assign minimum and maximum width to popup options
  lf <- lf %>% leaflet::popupOptions(minWidth = 50,
                      maxWidth = 50)
# view leaflet version
  lf

Output:

$maxWidth
[1] 50

$minWidth
[1] 50

$maxHeight

$autoPan
[1] TRUE

$keepInView
[1] FALSE

$closeButton
[1] TRUE

$className
[1] ""

Screenshot: same result as before with popup being too wide

I also tried popup.format = list(), but I think it's not working because the data are not numeric (see here under popup.format). Please correct me if I am wrong about that. Under legend.format of that same link, it says I can put in functions, but I do not know how to make that work these semi-colon-separated strings.

# plot interactive map
  tm_shape(nz) +
  tm_fill(popup.format = list(long_names=list(maxWidth=50)))

Error:

Error in formatC(x = c(12500.5611491, 4941.5725565, 23900.0363831, 12071.1446886, : unused argument (maxWidth = 50)

My key questions:

  1. Is there a way to wrap text in popups using tmap or have tmap recognize new line breaks within strings?
  2. If not, how can I use tm_leaflet() and the leaflet to modify tmap popup options? My real data set has 30 fields within the polygon, and 6 data fields with this problem--including varying numbers of items in each list--so I'd like to do this without having to enter all 30 data fields individually, if possible (see here).
  3. Or, is it possible to include left to right scroll bars within the popups, instead?

Thanks so much for your help!

1 Answers

So to address question 1 I've made an example using a single point as a slightly simplified example, where I set some coords and also make up some sample columns to include in labels.

tibble(
  lon = 149.068778, 
  lat = -35.230741, 
) %>% 
  st_as_sf(
    coords = c(1:2), 
    crs = 4326
  ) %>% 
  mutate(
    city = 'Canberra', 
    country = 'Australia', 
    weather = 'sunny'
    ) %>% 
  {. ->> my_popup}

my_popup 

# Simple feature collection with 1 feature and 3 fields
# Geometry type: POINT
# Dimension:     XY
# Bounding box:  xmin: 149.0688 ymin: -35.23074 xmax: 149.0688 ymax: -35.23074
# Geodetic CRS:  WGS 84
# # A tibble: 1 x 4
#               geometry city     country   weather
# *          <POINT [°]> <chr>    <chr>     <chr>  
# 1 (149.0688 -35.23074) Canberra Australia sunny  

Option 1 - tell tmap what info to show on each line in the popup

This is pretty simple, and doesn't rely on any HTML coding or specifying line breaks. However for this same reason, it's probably reasonably specific to tmap and a bit different to how you might do this in leaflet, or how you would make labels in ggplot. Basically you tell tmap what to show in the labels using popup.vars, and supply a "line title" and the data for that line.

tm_shape(my_popup)+
  tm_dots(size = 1,
          popup.vars = c('City: ' = 'city', 
                         'Country' = 'country', 
                         'Weather: ' = 'weather')
          )

enter image description here

The "line titles" (left hand side of the =) can be whitespace, but not empty; ' ' is ok but '' is not ok.

Option 2 - Format the label in HTML

Make a new column in my_popup, called label. This is a single text string, written in HTML style (this is a common way to make custom popups in leaflet). It can include text strings, line breaks (<br>), values from columns in the data and even a URL link with custom hyperlink name (and I assume other HTML stuff).

my_popup %>% 
  mutate(
    label = str_c('First line of my label <br>',
                  country, '<br>',
                  'Weather: ', weather, '<br>', 
                  'Link to website: <a href = "https://visitcanberra.com.au/"> Custom link name </a>'
                  )
  ) %>% 
  {. ->> my_popup_1}

my_popup_1

# Simple feature collection with 1 feature and 4 fields
# Geometry type: POINT
# Dimension:     XY
# Bounding box:  xmin: 149.0688 ymin: -35.23074 xmax: 149.0688 ymax: -35.23074
# Geodetic CRS:  WGS 84
# # A tibble: 1 x 5
#               geometry city     country   weather label                                                                                                                                         
# *          <POINT [°]> <chr>    <chr>     <chr>   <chr>                                                                                                                                         
# 1 (149.0688 -35.23074) Canberra Australia sunny   "First line of my label<br>Australia<br>Weather: sunny<br>Link to website: <a href = \"https://visitcanberra.com.au/\"> Custom link name </a>"

Then, we can specify the label column as the (only) popup.var in tm_dots(). In order for tmap to acknowledge the HTML style, we must tell it NOT to escape HTML using popup.format. By default, html.escape = TRUE; see ?tm_dots for explanation.

tm_shape(my_popup_1)+
  tm_dots(size = 1,
          popup.vars = 'label',
          popup.format = list(html.escape = FALSE)
  )

enter image description here

Notice that tmap automatically includes the name of the column where the popup info comes from as grey text on the left hand side of the popup - in this case our label column. This is unnecessary in our somewhat complex and self-explanatory label, but might be handy if you had a single value in the popup.

To get rid of this, we specify the "line title" of the popup in popup.vars as we did in Option 1, but this time as whitespace (whitespace seems to be ok, empty string not ok).

tm_shape(my_popup_1)+
  tm_dots(size = 2,
          popup.vars = c(' ' = 'label'),
          popup.format = list(html.escape = FALSE)
  )

enter image description here

Or, you can somewhat combine the two methods to include HTML (eg the hyperlink), and also "line titles" (including empties):

my_popup %>% 
  mutate(
    line_1 = 'First line of my label', 
    line_2 = country,
    line_3 = str_c('<a href = "https://visitcanberra.com.au/"> Custom link name </a>'),
    ) %>% 
  {. ->> my_popup_2}

my_popup_2

# Simple feature collection with 1 feature and 6 fields
# Geometry type: POINT
# Dimension:     XY
# Bounding box:  xmin: 149.0688 ymin: -35.23074 xmax: 149.0688 ymax: -35.23074
# Geodetic CRS:  WGS 84
# # A tibble: 1 x 7
#               geometry city     country   weather line_1                 line_2    line_3                                                              
# *          <POINT [°]> <chr>    <chr>     <chr>   <chr>                  <chr>     <chr>                                                               
# 1 (149.0688 -35.23074) Canberra Australia sunny   First line of my label Australia "<a href = \"https://visitcanberra.com.au/\"> Custom link name </a>"

tm_shape(my_popup_2)+
  tm_dots(size = 1,
          popup.vars = c('Line 1: ' = 'line_1', 
                         ' ' = 'country', 
                         'Weather: ' = 'weather',
                         'Link to website: ' = 'line_3'), 
          popup.format = list(html.escape = FALSE)
  )

enter image description here

So, Option 1 is pretty simple, but if you want to get fancy you will probably want to use Option 2 to take advantage of the features of HTML, including text colours and alignment (especially as it looks like the popups are aligned to the right by default, which may not suit).

For example, text colour:

my_popup %>% 
  mutate(
    label = str_c('<p style="color:red">This is my label</p>')
  ) %>% 
  {. ->> my_popup_3}

tm_shape(my_popup_3)+
  tm_dots(size = 1,
          popup.vars = c(' ' = 'label'),
          popup.format = list(html.escape = FALSE)
  )

enter image description here

Or adjust the alignment:

my_popup %>% 
  mutate(
    label = str_c('<p align="left">This is my long label<br>More<br>More</p>')
  ) %>% 
  {. ->> my_popup_4}

tm_shape(my_popup_4)+
  tm_dots(size = 1,
          popup.vars = c(' ' = 'label'),
          popup.format = list(html.escape = FALSE)
  )

enter image description here

my_popup %>% 
  mutate(
    label = str_c('<p align="left">This is my long label<br>More<br>More</p>', 
                  '<p align="right">More<br>and<br>More</p>')
  ) %>% 
  {. ->> my_popup_5}

tm_shape(my_popup_5)+
  tm_dots(size = 1,
          popup.vars = c(' ' = 'label'),
          popup.format = list(html.escape = FALSE)
  )

enter image description here

In terms of question 2, rather than "modify the tmap popup options", I would suggest you would move the layers to leaflet rather than tmap, and adjust popups using HTML as above. Hopefully this isn't required with the above solutions, but you'd use something like:

tm_leaflet(my_tmap) %>% 
  addMarkers(popup = ~my_label)

Question 3 - scroll bars. I'm not sure how/if these can be implemented, I have a feeling I've seen them when I have included a large image in the popup (using HTML), which must trigger a maximum popup size. Hopefully the 2 options above sort you out enough you don't need scroll bars anyway.

Related