3
votes

I am trying to replicate this line chart from highcharts. The annotations are wrapped in speech/text bubbles. I have tried to do this in ggplot with geom_label. To get the points, I've added geom_point with a filled triangle shape up or down. Whilst this works for the dark and opaque fills and colours, it does not work for white semi transparent fill with black colour outline, as you can clearly see the triangle's base.

Does anybody know a way around this or a better approach?

Code:

library(ggplot)

ggplot(france, aes(Distance, Elevation))+
  geom_area(fill = "#90ed7d", color = "#434348", 
            size = 0.7, alpha = 0.5)+ # area chart
  # location labels
  geom_label(aes(x, y-200, label = text), data = plot_labels[-3,],
             label.padding = unit(.4, "lines"), label.size = unit(.4, "mm"),
             label.r = unit(.05, "lines"), alpha = 0.5,
             vjust = 0.5, size = 3, family = "Lucida Grande")+ 
  # location labels points
  geom_point(aes(x, y-105, fill = "white"), data = plot_labels[-3,], 
             shape = 24, fill = "#FFFFFF80", size = 3, color = "black")+
  # elevation labels
  geom_label(aes(x, y+200, label = text), data = text_labels[-3,],
             label.padding = unit(.4, "lines"), label.size = unit(.4, "mm"),
             label.r = unit(.05, "lines"), color = "#404040",
             fill = "#404040",
             vjust = 0.5, size = 3, family = "Lucida Grande")+
  # text in white color for elevation labels 
  geom_text(aes(x, y+200, label = text), data = text_labels,
            vjust = 0.5, size = 3, family = "Lucida Grande", 
            colour = "#ffffff")+
  # elevation labels points
  geom_point(aes(x, y+105), data = text_labels[-3,], 
             shape = 25, fill = "#404040", size = 3, color = "#404040")+
  scale_y_continuous(labels = function (x) paste(x, "m"),
                     limits = c(0, 1600),
                     expand = c(0,0),
                     breaks = seq(0, 1500, 500))+
  scale_x_continuous(labels = function(x) paste(x, "km"),
                     expand = c(0,1.5), breaks = seq(0, 200, 25),
                     limits = c(0, max(france$Distance)))+
  ggtitle("2017 Tour de France Stage 8: Dole - Station des Rousses")+
  theme_minimal()+
  theme(panel.grid.minor = element_blank(),
        panel.grid.major.x = element_blank(),
        axis.ticks.x = element_line("#d6d7e6"),
        axis.ticks.length.x = unit(.25, "cm"),
        text = element_text("Lucida Grande", size = 11))

Data:

france <- read.csv(file = "https://raw.githubusercontent.com/mrRlover/data/main/Stackoverflow/tour_de_france_stage_8.csv")

plot_labels <- read.csv("https://raw.githubusercontent.com/mrRlover/data/main/Stackoverflow/labels.csv")

text_labels <- read.csv("https://raw.githubusercontent.com/mrRlover/data/main/Stackoverflow/text_labels.csv", encoding = "UTF-8", sep = ",")

colnames(france) <- c("Distance", "Elevation")

colnames(plot_labels) <- gsub("point__", "", colnames(plot_labels))

colnames(text_labels) <- gsub("point__", "", colnames(text_labels))

text_labels$text <- gsub("<br>", "\n", text_labels$text)

Target plot: enter image description here

Current output:

enter image description here

1

1 Answers

4
votes

Here's a semi-manual approach that takes a specifications table with your label info and converts each coordinate into a series of 8 points, defining the outline of the bubble. Then those can be fed into geom_poly to draw the bubbles.

enter image description here

Fake data:

set.seed(42)
fake_data <- data.frame(x = 1:100,
                        y = cumsum(rnorm(100, 0.1)))

Define labels:

library(tidyverse)
tri_width = 3
tri_height = 1
specifications <- data.frame(
  id = ids,
  x = c(12, 47, 94),
  y = c(11, 1, 17),
  y_dir = c(1, -1, 1),
  label = c("label 1", "label 2 is very wide", "label 3\nhas a\nfew lines"),
  width = c(15, 40, 19),
  height = c(3, 3, 7)
)

Convert each label row to 8 rows, defining the outline of each bubble:

bubbles <- specifications %>%
  uncount(8, .id = "pos") %>%   # bubble will have 7 coords, last one repeated
  mutate(x = x + recode(pos, 
                    0,
                    tri_width/2,
                    width/2,
                    width/2,
                    -width/2,
                    -width/2,
                    -tri_width/2,
                    0),
         y = y + y_dir * recode(pos,
                    0,
                    tri_height,
                    tri_height,
                    tri_height + height,
                    tri_height + height,
                    tri_height,
                    tri_height,
                    0)
         )

Plot:

ggplot(fake_data, aes(x, y)) +
  geom_line() +
  geom_polygon(data = bubbles, 
               aes(x, y, group = id),
               fill = "white", color = "gray70", alpha = 0.85) +
  geom_text(data = specifications, 
            aes(label = label, y = y + y_dir*(tri_height + height/2)))