Search code examples
rggplot2plotlegend

How to avoid overlapping legends for multiple geom_point layers in ggplot2


I'm trying to create a chart with 2 geom_points and a geom_text_repel layer. When I display my legend, the geom_points overlap. The main data points should be represented as empty circles, while the "region"-specific points should be filled circles. Additionally, the points corresponding to regions are not displayed correctly in the legend.

enter image description here

library(ggplot2)
library(ggrepel)
library(stringr)

set.seed(123)
data_filtered <- data.frame(
    evolution = runif(15, -10, 10),
    tauxpourmille = runif(15, 0, 100),
    faits = sample(1:100, 15, replace = TRUE),
    Code.région = sample(letters[1:3], 15, replace = TRUE),
    Code.département = sample(LETTERS[1:5], 15, replace = TRUE)
)

evol_tpm_reg_filtered <- data.frame(
    evolution_reg = runif(3, -10, 10),
    tauxpourmille = runif(3, 0, 100),
    Code.région = letters[1:3]
)

x_label <- "Évolution"
y_label <- "Taux pour mille"

region_colors <- c("red", "blue", "green")

ggplot(data_filtered, aes(x = evolution, y = tauxpourmille, size = faits, color = factor(Code.région))) +
    geom_point(shape = 1, stroke = 1.3, alpha = 0.6, show.legend = TRUE) +
    geom_text_repel(aes(label = Code.département),
                    family = "sans",
                    box.padding = 3,
                    size = 5,
                    min.segment.length = 0,
                    force = 20,
                    max.overlaps = 50,
                    segment.size = 0.5,
                    show.legend = FALSE) +
    geom_point(data = evol_tpm_reg_filtered, aes(x = evolution_reg, y = tauxpourmille, color = factor(Code.région)),
               size = 5, shape = 16, show.legend = TRUE) +
    labs(x = str_wrap(x_label, width = 90),
         y = str_wrap(y_label, width = 35),
         color = "Region",
         size = "Faits") +
    theme_minimal() +
    scale_color_manual(values = region_colors) +
    scale_size_area(max_size = 30,
                    breaks = c(min(data_filtered$faits), median(data_filtered$faits), max(data_filtered$faits)),
                    labels = scales::number_format(accuracy = 1)) +
    theme(axis.title.x = element_text(family = "sans", margin = margin(t = 15)),
          axis.title.y = element_text(family = "sans", margin = margin(r = 15)),
          axis.text.x = element_text(face = "bold", family = "sans"),
          axis.text.y = element_text(face = "bold", family = "sans"),
          axis.line.x = element_line(size = 1, color = "lightgray"),
          axis.line.y = element_line(size = 1, color = "lightgray"),
          legend.position = "right") +
    expand_limits(x = range(data_filtered$evolution) + c(-0.5, 0.5) * diff(range(data_filtered$evolution)),
                  y = range(data_filtered$tauxpourmille) + c(-0.5, 0.5) * diff(range(data_filtered$tauxpourmille)))

How can I make sure that the geom_point of “Faits” is an empty circle?


Solution

  • Simply drop show.legend=TRUE from your geom_points:

    library(ggplot2)
    library(ggrepel)
    library(stringr)
    
    set.seed(123)
    data_filtered <- data.frame(
      evolution = runif(15, -10, 10),
      tauxpourmille = runif(15, 0, 100),
      faits = sample(1:100, 15, replace = TRUE),
      Code.région = sample(letters[1:3], 15, replace = TRUE),
      Code.département = sample(LETTERS[1:5], 15, replace = TRUE)
    )
    
    evol_tpm_reg_filtered <- data.frame(
      evolution_reg = runif(3, -10, 10),
      tauxpourmille = runif(3, 0, 100),
      Code.région = letters[1:3]
    )
    
    x_label <- "Évolution"
    y_label <- "Taux pour mille"
    
    region_colors <- c("red", "blue", "green")
    
    ggplot(data_filtered, aes(
      x = evolution, y = tauxpourmille,
      size = faits, color = factor(Code.région)
    )) +
      geom_point(shape = 1, stroke = 1.3, alpha = 0.6) +
      geom_text_repel(
        aes(label = Code.département),
        family = "sans",
        box.padding = 3,
        size = 5,
        min.segment.length = 0,
        force = 20,
        max.overlaps = 50,
        segment.size = 0.5,
        show.legend = FALSE
      ) +
      geom_point(
        data = evol_tpm_reg_filtered,
        aes(x = evolution_reg),
        size = 5, shape = 16
      ) +
      labs(
        x = str_wrap(x_label, width = 90),
        y = str_wrap(y_label, width = 35),
        color = "Region",
        size = "Faits"
      ) +
      theme_minimal() +
      scale_color_manual(values = region_colors) +
      scale_size_area(
        max_size = 30,
        breaks = c(
          min(data_filtered$faits),
          median(data_filtered$faits),
          max(data_filtered$faits)
        ),
        labels = scales::number_format(accuracy = 1)
      ) +
      theme(
        axis.title.x = element_text(family = "sans", margin = margin(t = 15)),
        axis.title.y = element_text(family = "sans", margin = margin(r = 15)),
        axis.text.x = element_text(face = "bold", family = "sans"),
        axis.text.y = element_text(face = "bold", family = "sans"),
        axis.line.x = element_line(linewidth = 1, color = "lightgray"),
        axis.line.y = element_line(linewidth = 1, color = "lightgray"),
        legend.position = "right"
      ) +
      expand_limits(
        x = range(data_filtered$evolution) + c(-0.5, 0.5) * diff(range(data_filtered$evolution)),
        y = range(data_filtered$tauxpourmille) + c(-0.5, 0.5) * diff(range(data_filtered$tauxpourmille))
      )