Skip to main content

Section 5.3 Text

There are important pieces of information with every map which are represented by text. Titles, subtitles, legend labels, and annotations all help to make the message communicated by your map more clear and obvious to your readers. Further, important information about the underlying data can be communicated through product notes. You want to acknowledge the sources of your data (both attribute data and geometry data), as well as leave some information about yourself as the map-maker, so consumers of your map can understand who is behind this map, and leave some contact information to get in touch with any questions. In this section we go through how to add such text information to your maps.

Subsection 5.3.1 Titles and subtitles

To give your map a title and subtitle, you can use the appropriate functions from the ggplot2() package. In this case we can add both within the ggtitle function. Make sure that your title is short and specific, so it is clear what your map is about. You can include a subtitle to elaborate on this, or you can add additional information such as the period for which your map represents data (in this case, January 2020).
First, let’s save our map into an object, call it map. Then we can add to this object.
map <- ggplot(data = hu_dd) +
  geom_sf(aes(fill = total_quantiles),
          lwd = 0.5, col = "white") +
  scale_fill_brewer(type = "seq", palette = "Greens") +
  theme_void()
Now we can add a title, with the ggtitle() function.
map <- map +
  # specify both title and subtitle:
  ggtitle(label = "Number of breathalyser tests per county in Hungary",
          subtitle = "January 2020")

map
The previous green map now has a title, ’Number of breathalyser tests per county in Hungary’, and a subtitle ’January 2020’.
Figure 5.3.1. Add title(s) to map

Subsection 5.3.2 Legend

Besides an informative title/subtitle, you need your legend to be clear to your readers as well. To modify the title to your legend, you can use the name = parameter in your scale_fill_brewer() function, where we specified the colour palette.
map <- map +
  scale_fill_brewer(type = "seq", palette = "Greens",
                    name = "Total tests (quantiles)")  # desired legend title

map
The same titled map again, with the legend updated. It now features a better label, ’Total tests (quantiles)’.
Figure 5.3.2. Add title to map legend
Besides the legend title, we can also change how the levels are labeled. For this, we can use text manipulation functions, such as gsub() which substitutes one string for another. For example, we can replace the "," with a " - " if we’d like using the gsub() function. We can create a new object, here new_levels, which has the desired labels:
# create object new_levels with desired labels
new_levels <- gsub(","," - ",levels(hu_dd$total_quantiles))
We can then assign this new levels object in the labels = parameter of the scale_fill_brewer() function:
map <- map +
  scale_fill_brewer(type = "seq", palette = "Greens",
                    name = "Total tests (quantiles)",
                    labels = new_levels)   # specify our new labels

map
The legend continues to improve, with the ranges next to the colours changing from the form ’x comma y’ to ’x dash y’.
Figure 5.3.3. Edit labels on map legend
Of course it is possible that we want to completely re-write the levels, rather than just swap out one character for another. In this case, we can completely rename the levels if we liked by passing the new, desired labels into this new_levels object:
new_levels <- c("< 4796", "4796 to < 10785", "10785  to < 15070", "> 15070")
And once again, we specify to use these labels in the labels = parameter of the scale_fill_brewer() function:
map +
  scale_fill_brewer(type = "seq", palette = "Greens",
                    name = "Total tests (quantiles)",
                    labels = new_levels) # again specify labels object
Further changes to the same map of Hungary, the ranges next to the colours now use the connecting word ’to’, and ’less than’ and ’greater than’ signs where needed.
Figure 5.3.4. Re-write values on map legend
You can change the labels you would like, but do keep in mind any loss of information you may introduce. For example with this second version, we no longer know what are the minimum and maximum values on our map, as we’ve removed that information with our new levels. Again, there are no wrong answers here, but whatever best fits the data and the purpose of the map. Depending on the context, for example, you may use the title to draw home the key finding or lesson you want the reader to take from the map (rather than describing the plot variable).

Subsection 5.3.3 Annotation

In certain cases, it might be that we want to point out something specific on our map. This would be the case if we imagine showing someone the map in person, and pointing to a specific region, or area, to highlight it. Or we might just want to label all polygons, for clarity. If we are not present to discuss our map, we might want to include some text annotation instead, which will do this for us. From ggplot2 versions v.3.1.0, the functions geom_sf_text() and geom_sf_label() make it very smooth for us to do this.
In the below example, let’s say we want to label each polygon with the name of the county which it represents. In this case, we use the geom_sf_label() geometry, and inside it we use aes() to point to which column in our dataframe we want to use (in this case, the column is called name).
map  +
  geom_sf_label(aes(label = name))  # add layer of labels from the name column
Once more the same map has been altered, gaining rounded white boxes with a thin black outline overlaid on each county. These boxes include the names of the counties in black text. Many boxes cover one another. The legend has also reverted to one with a dash between values.
Figure 5.3.5. Add labels to map
You may notice, there is some overlapping here which renders some names unreadable. Well while there is work in this space to develop the function geom_sf_label_repel(), at the time of writing this is not yet available. However this application of the geom_label_repel() function from the ggrepel package advised by [152] achieves the same outcome:
library(ggrepel)

map +
  geom_label_repel(data = hu_dd,       # add repel layer, specify dataframe
                   aes(label = name,   # specify where to find label (name column)
                       geometry = geometry),  # specify geometry
                   stat = "sf_coordinates",  # transformation to use on the data
                   min.segment.length = 0) # don't draw segments shorter than this
The boxes have shifted around to mostly prevent overlapping. A thin black line connects each box to the centre of the county it corresponds to. Most of these lines are very short, though a few labels are some distance from their county.
Figure 5.3.6. Repel labels for legibility
This way we can display the names of all the counties without overlap, so they all become legible. While this is achievable, think back to the principles of good design. This seems busy, and it may overwhelm the reader. Not to mention, is it important that all polygons are labelled here? It might be; remember this depends on the message the map is intended to communicate! But here let’s consider a different scenario, where we want to use annotation to label only those counties which meet some specific criteria. For example, you might want to label only those which are in the top quartile. One way to achieve this is to create a separate dataframe, which only includes the desired polygons, and pass this into the geom_sf_label() function.
#create new dataframe with only top counties
labs_df <- hu_dd %>% filter(total_breath_tests >= 15070)

#add to map
map +
  geom_sf_label(data = labs_df, # specify to use the labels df
                aes(label = name))
We are back to the map with the labels centred on the counties, but only five labels appear, with no overlap. They are in the northwest, southwest, and east of the country.
Figure 5.3.7. Label only polygons in top quartile of value on variable of interest
In another scenario, you might want to label only one region of interest. In this case, we might actually want to keep our annotation off the map, and draw an arrow onto the map pointing to where this annotation refers to. We can do this by using the nudge_x and nudge_y parameters of the geom_sf_label() function, to nudge the label position along the x and y axes respectively. In this case, let’s label only Budapest.
#create new labels dataframe
labs_df <- hu_dd %>% filter(name == "Budapest")

map +
  geom_sf_label(data = labs_df, aes(label = name), # label from name column
                nudge_y = 0.9,  # move label on y axis
                nudge_x = -0.1)  # move label on x axis
Still the same green shaded map of the counties of Hungary, now a label for ’Budapest’ floats north of the country, not covering any of it.
Figure 5.3.8. Label only one area of interest
But this floating label is a little ambiguous, and needs to be more explicitly connected to the map. To achieve this, we might want to use an arrow to point out Budapest on the map. To do this, we can use geom_curve() within ggplot2. We will need two sets of x and y values for this segment, the start point (x and y) and the end point (xend and yend). The end point will be the coordinates where we want the arrow pointing to. This would be some x,y pair within Budapest. We can use the st_coordinates() function once again to extract the centroid, this time of the Budapest polygon. Let’s extract the longitude of the centroid into an object called bp_x for our x value, and the latitude of the centroid into an object called bp_y for our y value.
# get x coordinate
bp_x <- labs_df %>%
  mutate(cent_lng = st_coordinates(st_centroid(.))[,1]) %>% pull(cent_lng)

# get y coordinate
bp_y <- labs_df %>%
  mutate(cent_lat = st_coordinates(st_centroid(.))[,2]) %>% pull(cent_lat)
Great, so we have the end point for our segment! But where should it start? Well we want it pointing from our label, so we can think back to how we adjusted this label with the nudge_x and nudge_y parameters inside the geom_sf_label() function earlier. We can add (or subtract) these values to our bp_x and bp_y objects to determine the start points for our curve. Finally, we can also specify some characteristics of the arrow head on our curve with the arrow = parameter. Here we specify that we want 2 millimeters.
map <- map +
  geom_curve(x = bp_x - 0.1,  # starting x coordinate (the label)
             y = bp_y + 0.9, # starting y coordinate (the label)
             xend = bp_x , # ending x coordinate (BP centroid)
             yend = bp_y,  # ending y coordinate (BP centroid)
             arrow = arrow(length = unit(2, "mm"))) +
  geom_sf_label(data = labs_df,
                aes(label = name),
                nudge_y = 0.9,
                nudge_x = -0.1)

map
The previous image, with a curved arrow from the label to the centre of Budapest itself.
Figure 5.3.9. Add arrow to clarify label off map
This is one way to include annotation while keeping the map clear, but still using the geographic information to reference. Annotations can be useful, but think carefully about whether you need them for your map, as they can also be distracting if not used appropriately.

Subsection 5.3.4 Production notes

Something that should be a key feature of all maps is the inclusion of production notes. This includes some information about who made it, as well as any attributions for data. Here we can string together a series of information we want to include, appended with a newline character (\n), in order to keep our notes nice and legible. We save this into a new object called caption_text.
caption_text <- paste("Map created by Réka Solymosi (@r_solymosi)",
                       "Contains data from Police Hungary",
                       "http://www.police.hu/hu/a-rendorsegrol/",
                       "statisztikak/kozrendvedelem",
                       "Map data copyrighted OpenStreetMap contributors",
                       "available from https://www.openstreetmap.org",
                       sep = "\n")
Then we can include this caption_text object as a caption in the function labs(), which stands for labels.
map <- map + labs(caption = caption_text) # include production notes here

map
The exact same map with the arrow, with attributions placed beneath the country in a right-aligned text block. This includes the creator on one line, and sources for data on the rest.
Figure 5.3.10. Include production notes in map
In this way we give credibility to our map, and we also make the proper attributions to where our data come from.