-2

I am trying to make a label that is made up of a book title and book author. I would like to underline the title, but not the author, in the label.

Here is the MWE data:

Title,Author,Pages,Date Started,Date Finished
underline('Time Travel'),'James Gleick',353,1/1/17,1/27/17
underline('The Road'),'Cormac McCarthy',324,1/28/17,3/10/17

This code works but does not allow for the title and author

library(ggplot2)
library(tidyverse)
library(ggrepel)
library(ggalt)

books.2017 <- read_csv('books_2017.csv')
books.2017$`Date Started` <- as.Date(books.2017$`Date Started`, "%m/%d/%y")
books.2017$`Date Finished` <- as.Date(books.2017$`Date Finished`, "%m/%d/%y")

ggplot(books.2017, aes(x=`Date Started`, xend=`Date Finished`)) +
  geom_dumbbell(aes(size=Pages),size_x=0, size_xend=0) +
  geom_text_repel(aes(label=paste(Title)), parse=TRUE)

When I try to change geom_text_repel to something like:

geom_text_repel(aes(label=paste(Title,Author)), parse=TRUE)

I get this error:

Error in parse(text = as.character(lab)) : 
  <text>:1:26: unexpected string constant
1: underline('Time Travel') 'James Gleick'
                             ^

EDIT The labels should look something like this

tesplot

Adam_G
  • 7,337
  • 20
  • 86
  • 148
  • 2
    you need to form a valid plotmath expression, `qplot(1,1,geom="blank") + annotate("text", x=1, y=1, label='underline("this")*" and that"', parse = TRUE)` – baptiste Dec 24 '17 at 20:57
  • Can you be more explicit? I'm not familiar with this. How does this for into the rest of the code? – Adam_G Dec 24 '17 at 21:00
  • 2
    the reason I constructed another example is because yours is far from being minimal and reproducible. Try running it in a fresh session as if you were answering the question to see what I mean. – baptiste Dec 24 '17 at 21:05
  • I'm sorry, I'm not following you – Adam_G Dec 24 '17 at 21:06
  • 4
    Explicitly: i) there should be no need to create a csv file if you had provided a `dput()` of your dataset or equivalent; ii) non-standard column names complicate everything; iii) tidyverse is a huge dependency that isn't needed here (it's a ggplot2 question); iv) geom_dumbell isn't part of those packages; v) the geom_text layer is missing a y aesthetic; vi) I don't know how to answer "How does this for into the rest of the code?". The code I provided instead was self-contained and illustrated a text label containing both underlined and normal text, and I suggested you read about ?plotmath. – baptiste Dec 24 '17 at 21:13
  • the "short answer" to your question is something like `label=paste(Title,Author,sep="~")` but to get there I had to install various packages and fix a number of things in your code, which is a real deterrent for a question that didn't require them. – baptiste Dec 24 '17 at 21:21
  • When I reformat the csv data using the format you mention about, it does not work. – Adam_G Dec 24 '17 at 21:22
  • Oh well, I'm sure someone else will come along and help. Good luck. – baptiste Dec 24 '17 at 21:23
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/161913/discussion-between-adam-g-and-baptiste). – Adam_G Dec 24 '17 at 21:29
  • Why not just have 2 `annonate` statements? One with the text, another with the underline portion? – InfiniteFlash Dec 24 '17 at 22:07
  • What would that look like? – Adam_G Dec 24 '17 at 22:08
  • I'll post an example below. – InfiniteFlash Dec 24 '17 at 22:11
  • Before I post, can you post what you want it to look like, just to make sure? – InfiniteFlash Dec 24 '17 at 22:18
  • All I need is an image for what you want it to look like. – InfiniteFlash Dec 24 '17 at 22:20
  • https://stackoverflow.com/a/30470446/5874001 Is this what youre asking for? – InfiniteFlash Dec 24 '17 at 22:28
  • No, these are labels for each point. That's what ggrepel does. They add labels to each point inside the plot. – Adam_G Dec 24 '17 at 22:38
  • Error msg `could not find function "geom_dumbbell"`. I think I know how to fix it but need to test it. You should be the one who knows where to get the missing function. – IRTFM Dec 24 '17 at 23:50
  • Oops, shoot, I deleted the ggalt library – Adam_G Dec 25 '17 at 00:35
  • Get a different error after loading ggalt: `arguments imply differing number of rows: 2, 100000.` I have no idea where the 100K element might be coming from. – IRTFM Dec 25 '17 at 00:39
  • @InfiniteFlashChess - I've added an image. Hopefully that helps. I've also added a bounty. :-) – Adam_G Dec 28 '17 at 04:06

3 Answers3

8

You need to form a valid plotmath expression, qplot(1,1,geom="blank") + annotate("text", x=1, y=1, label='underline("this")*" and that"', parse = TRUE)

enter image description here

Applied to your dataset this might look like label=paste(Title, Author, sep="~"), where ~ is a non-breaking space plotmath separator. After fixing your non-reproducible example, this gives

enter image description here

baptiste
  • 75,767
  • 19
  • 198
  • 294
  • I'm not annotating the plot, though. I'm annotating each label. – Adam_G Dec 28 '17 at 05:25
  • 7
    what's with the downvoting and ignoring comments given to you? We all agree that the question hasn't received enough attention; can you improve it with a truly minimal reproducible example? (by which I mean ggrepel, dumbbells, the entire tidyverse, dates, etc. are completely irrelevant, and the whole `read_csv('books_2017.csv')` is very answerer-unfriendly as I pointed out days ago). – baptiste Dec 28 '17 at 05:29
  • The ggrepel is not irrelevant. It creates annotations for each point. – Adam_G Dec 28 '17 at 05:45
  • @baptiste I created a sample dataset to expand upon your good work. My sense is that the difficulty here is understanding how `parse` works hence the example I provided. – Technophobe01 Dec 29 '17 at 00:58
  • 1
    @Technophobe01 until the OP cares to realise their code is not even reproducible I see really no point in contributing further. Chasing bounty rewards on this sort of half-arsed question has the long-term downside of eroding the motivation of some answerers. – baptiste Dec 29 '17 at 02:20
  • @babtiste No worries, my sense is your approach is correct. It is always tricky working without a clear dataset. Adam probably didn't want to share his reading habits. You both strike me as good people. I am not worried about the bounty my interest was around finding an interesting problem. Take care. – Technophobe01 Dec 29 '17 at 02:50
6

It looks like you are trying to pull down your goodreads data, and map out the number of books you read over the year, against start data, end data and book size.

To do what you propose, you can use the parse option on geom_text*(, to do this you have to create a parse string with sprintf() and pass that to geom_text*( as the label input where parse = TRUE.

To add a newline you might consider using plotmath::over()

parseLabel <- sprintf("over(%s,%s)",
                 gsub(" ", "~", books.2007$Title, fixed = TRUE),
                 gsub(" ", "~", books.2007$Author, fixed = TRUE))
parseLabel

alternatively, you can use underline, however adding a newline is tricky as plotmath() does not directly support the use of newline in a parse formula.

parseLabel <- sprintf("underline(%s)~\n~%s",
                      gsub(" ", "~", books.2007$Title, fixed = TRUE),
                      gsub(" ", "~", books.2007$Author, fixed = TRUE))
parseLabel

Note: Baptiste correctly hilights this in his answer I am just expanding upon his work here using an example dataset I created.

OK, here is a quick example based on the above assumptions. I hope this points you in the right direction.

Note: I have appended an example dataset for people to use.

Adding an Underline

In order to add an underline to the text, you can harness plotmath by setting parse=true in the geom_label*() call.

Simple example using plotmath wih geom_label

library(tidyverse) # Loads ggplot2
library(graphics)
library(ggrepel)
library(gtable)
library(ggalt)

# load test dataset
# ... See example data set
# books.2007 <- structure...

gp <- ggplot(books.2007)
gp <- gp + geom_dumbbell( aes(x = `Date Started`, 
                              xend = `Date Finished`, 
                              y = ISBN, 
                              size = as.numeric(Pages)), 
                          size_x = 0, size_xend = 0)

# Construct parseLabel using sprintf
parseLabel <- sprintf("underline(%s)~\n~%s",
                  gsub(" ", "~", books.2007$Title, fixed = TRUE),
                  gsub(" ", "~", books.2007$Author, fixed = TRUE))

gp <- gp + geom_label(aes(x = `Date Started`,
                          y = ISBN), 
                      label = parseLabel,
                      vjust = 1.5, hjust = "inward", parse = TRUE)
gp <- gp + labs(size = "Book Size")
gp

Example Plot Output

enter image description here

Simple example using plotmath with geom_label_repel

nb. My personal sense would be geom_text is easier to use as geom_label_repel requires computation overhead to calculate the positioning of the labels.

## Construct parse string
##
##
parseLabel <- sprintf("underline(%s)~\n~%s",
                      gsub(" ", "~", books.2007$Title, fixed = TRUE),
                      gsub(" ", "~", books.2007$Author, fixed = TRUE))
parseLabel

rm(gp)
gp <- ggplot(books.2007)
gp <- gp + geom_dumbbell( aes(x = `Date Started`,
                              xend = `Date Finished`,
                              y = ISBN,
                              size = as.numeric(Pages)),
                          size_x = 0, size_xend = 0)
gp <- gp + geom_label_repel(aes(x = `Date Started`,
                                y = ISBN),
                            label = parseLabel,
                            # max.iter = 100,
                            parse = TRUE)
gp <- gp + labs(size = "Book Size")
gp

Example Plot Output with geom_text_repel

enter image description here

Example Data Set:

books.2007 <- structure(list(Title = c("memoirs of a geisha", "Blink: The Power of Thinking Without Thinking", 
"Power of One", "Harry Potter and the Half-Blood Prince (Book 6)", 
"Dune (Dune Chronicles Book 1)"), Author = c("arthur golden", 
"Malcolm Gladwell", "Bryce Courtenay", "J.K. Rowling", "Frank Herbert"
), ISBN = c("0099498189", "0316172324", "034541005X", "0439785960", 
"0441172717"), `My Rating` = c(4L, 3L, 5L, 4L, 5L), `Average Rating` = c(4, 
4.17, 5, 4.38, 4.55), Publisher = c("vintage", "Little Brown and Company", 
"Ballantine Books", "Scholastic Paperbacks", "Ace"), Binding = c("paperback", 
"Hardcover", "Paperback", "Paperback", "Paperback"), `Year Published` = c(2005L, 
2005L, 1996L, 2006L, 1990L), `Original Publication Year` = c(2005L, 
2005L, 1996L, 2006L, 1977L), `Date Read` = c(NA_character_, NA_character_, 
NA_character_, NA_character_, NA_character_), `Date Added` = structure(c(13558, 
13558, 13558, 13558, 13558), class = "Date"), Bookshelves = c("fiction", 
"nonfiction marketing", "fiction", "fiction fantasy", "fiction scifi"
), `My Review` = c(NA_character_, NA_character_, NA_character_, 
NA_character_, NA_character_), `Date Started` = structure(c(13577, 
13610, 13634, 13684, 13722), class = "Date"), `Date Finished` = structure(c(13623, 
13647, 13660, 13689, 13784), class = "Date"), Pages = c("522", 
"700", "300", "145", "700")), .Names = c("Title", "Author", "ISBN", 
"My Rating", "Average Rating", "Publisher", "Binding", "Year Published", 
"Original Publication Year", "Date Read", "Date Added", "Bookshelves", 
"My Review", "Date Started", "Date Finished", "Pages"), row.names = c(NA, 
-5L), spec = structure(list(cols = structure(list(Title = structure(list(), class = c("collector_character", 
"collector")), Author = structure(list(), class = c("collector_character", 
"collector")), ISBN = structure(list(), class = c("collector_character", 
"collector")), `My Rating` = structure(list(), class = c("collector_integer", 
"collector")), `Average Rating` = structure(list(), class = c("collector_double", 
"collector")), Publisher = structure(list(), class = c("collector_character", 
"collector")), Binding = structure(list(), class = c("collector_character", 
"collector")), `Year Published` = structure(list(), class = c("collector_integer", 
"collector")), `Original Publication Year` = structure(list(), class = c("collector_integer", 
"collector")), `Date Read` = structure(list(), class = c("collector_character", 
"collector")), `Date Added` = structure(list(), class = c("collector_character", 
"collector")), Bookshelves = structure(list(), class = c("collector_character", 
"collector")), `My Review` = structure(list(), class = c("collector_character", 
"collector"))), .Names = c("Title", "Author", "ISBN", "My Rating", 
"Average Rating", "Publisher", "Binding", "Year Published", "Original Publication Year", 
"Date Read", "Date Added", "Bookshelves", "My Review")), default = structure(list(), class = c("collector_guess", 
"collector"))), .Names = c("cols", "default"), class = "col_spec"), class = c("tbl_df", 
"tbl", "data.frame"))

Simple Example - no formatting

For completeness here is how I would approach the problem avoiding the formula construction problems.

gp <- ggplot(books.2007)
gp <- gp + geom_dumbbell( aes(x = `Date Started`, 
                              xend = `Date Finished`, 
                              y = ISBN, 
                              size = as.numeric(Pages)), 
                          size_x = 0, size_xend = 0)
t <- paste(books.2007$Title, "\n", books.2007$Author)
gp <- gp + geom_label(aes(x = `Date Started`,
                               y = ISBN),
                      label = t,
                      vjust = 1.5, hjust = "inward", parse = FALSE)
gp <- gp + labs(size = "Book Size")
gp

Plot Output

enter image description here


Technophobe01
  • 8,212
  • 3
  • 32
  • 59
2

This problem could be made a lot simpler if italics sufficed instead of underlines, as grid::gpar() does not support an underline fontface. Here's an example of using italics instead:

library(tibble)
library(ggplot2)

books.2017 <- 
  tribble(~Title,~Author,~Pages,~`Date Started`,~`Date Finished`,
       'Time Travel','James Gleick',353,'1/1/17','1/27/17',
       'The Road','Cormac McCarthy',324,'1/28/17','3/10/17')

ggplot(books.2017, aes(x = `Date Started`,
                       xend = `Date Finished`,
                       y = Title,
                       yend = Title)) +
  geom_segment(aes(size = Pages), 
               lineend = 'round') +
  geom_text(aes(label = Title),
            fontface = 'italic',
            vjust = -3.5) +
  geom_text(aes(label = Author),
            vjust = -2)

zlipp
  • 790
  • 7
  • 16
  • Thanks, but I'm not looking to add another geom. ggrepel automatically plots the annotations best the point and ensures they don't overlap. – Adam_G Dec 28 '17 at 21:07