How to make sure that the text title is inside the polygon object?

I am doing a cartographic plot where I want to place a small text label inside each state. My current problem is that the text is out of state , so it doesn't look very good: enter image description here

I tried using averages, medians, centroids and so on.

I want each text to be completely inside or outside the polygon , as here: enter image description here (image from http://www.businessinsider.com/map-what-100-is-actually-worth-in-your-state-2015-7?IR=T )

I use the following code to create my image:

library(maps) library(dplyr) library(ggplot2) #data mapbase <- map_data("state.vbm") data(state.vbm.center) df <- state.vbm.center %>% as.data.frame() %>% mutate(region = unique(mapbase$region) ) %>% full_join(mapbase) #actual plotting cnames <- aggregate(cbind(long, lat) ~ region, data=df, FUN=median) gmap<- ggplot()+ geom_polygon( data=df2, aes(long, lat, group = region, fill = somevalue,alpha=0.3)) + coord_fixed() + theme_void() + geom_text(data=cnames, aes( fontface=2 ,cnames$long, cnames$lat , label = "text" ), color= "black" ,size=3,check_overlap = T, position=position_jitter(width=3, height=3) ) + scale_fill_gradient(low="red",high="blue") 

Thanks so much for the tips!

+5
source share
1 answer

A few points to consider.

1 - Optimal location for annotation purposes in a polygon

In an ideal world, each polygon is like a circle, and its center is the best place to place a text label (for example, Texas). In fact, map areas have different shapes and cannot even be in one place (for example, in Michigan). The mathematical midpoint / median point can be on the edge or outside the polygon (e.g. Florida).

R will not be so big trying to figure out these complications. Instead, I would use GIS software.

However, if your use case is USA, the state.vbm.center already comes with a pretty good default coordinate set. His help file reads:

state.vbm.center are the coordinates of the state centers for annotating the target .

Let's see where these points are:

 #data mapbase <- map_data("state.vbm") data(state.vbm.center) cnames <- state.vbm.center %>% as.data.frame() %>% mutate(region = unique(mapbase$region)) #actual plotting ggplot()+ geom_polygon( data=mapbase, aes(long, lat, group = region, fill = region), alpha = 0.3) + coord_fixed() + theme_void() + geom_point(data = cnames, aes(x, y)) + scale_fill_discrete(guide = F) 

center location

It is not too shabby. If all you need to indicate is state names, this should be enough:

 cnames$abb <- state.abb ggplot()+ geom_polygon( data=mapbase, aes(long, lat, group = region, fill = region), alpha = 0.3) + coord_fixed() + theme_void() + geom_text(data=cnames, aes(x, y , label = abb), color= "black", size=3, fontface = 2, hjust = 0.5, vjust = 0.5) + #central alignment scale_fill_discrete(guide = F) 

abbreviated names

2 - Installation of long marks in bottlenecks

This is very suitable for short marks within the map polygons, but if you want to include additional information (each state’s full name, birth rate, crime rate, unemployment rate, education level, income range, population density, percentage of people who voted in recent elections , ...), in the end you will start to escape from space into smaller / stranger forms of polygons.

At this point, you can take a dual approach, storing information in large polygons and placing smaller polygons separately on one side, as a partial legend. For US states, the state area is part of the standard datasets package, which eliminates the need to calculate it:

 # incorporate area information & identify small area states cnames$area <- state.area ggplot(cnames %>% mutate(region = factor(region, levels = region[order(area)])), aes(x = region, y = area)) + geom_col() + theme_classic() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) # the first 7 states (up to Maryland) are noticeably smaller than the rest 

sorted by size of area

Select a small blank area on the map for small states. I decided to align them vertically in 1 column in longitude = 140 and latitude from 0 to 60:

 library(tidyr) legend.states <- cnames$region[which(cnames$area <= 10577)] legend.states <- as.data.frame(legend.states) legend.states$long1 <- 140 legend.states$lat1 <- seq(0, 60, length.out = nrow(legend.states)) legend.states <- legend.states %>% mutate(long2 = long1 + 5, lat2 = lat1) %>% mutate(long3 = long2, lat3 = lat2 - 5) %>% mutate(long4 = long1, lat4 = lat3) %>% mutate(long5 = long1, lat5 = lat1) %>% gather(k, v, -legend.states) %>% mutate(order = as.integer(substring(k, nchar(k))), k = gsub("[0-9]", "", k)) %>% spread(k, v) %>% rename(region = legend.states) %>% mutate(group = mapbase$group[match(region, mapbase$region)]) %>% select(long, lat, group, order, region) %>% mutate(subregion = NA) # add legend polygons to the original polygon dataset mapbase2 <- rbind(mapbase, legend.states) 

Change the coordinates of the annotations for these small states so that they are aligned with the positions of the legend window:

 cnames2 <- left_join(cnames, legend.states %>% filter(order %in% c(1, 4)) %>% group_by(region) %>% summarise(long = mean(long) + 7, lat = mean(lat))) %>% mutate(x = coalesce(long, x), y = coalesce(lat, y), hjust = ifelse(is.na(lat), 0.5, 0)) # left alignment (hjust=0) for small state text, central alignment (hjust=0.5) otherwise. 

Put it all together:

 ggplot()+ geom_polygon( data=mapbase2, aes(long, lat, group = region, fill = region), alpha = 0.3) + coord_fixed() + theme_void() + geom_text(data=cnames2, aes(x, y , label = abb, hjust = hjust), size=3, fontface = 2, vjust = 0.5) + scale_fill_discrete(guide = F) 

legend field

(Note: for longer text, you probably also need to increase the limits of the x axis and / or insert line breaks.)

+3
source

Source: https://habr.com/ru/post/1270190/


All Articles