I use ggplot2 to create a point plot and then I add the correlation coefficient to that chart. Next, I use plotly to see info about each data point. However, there is a mistake in font style in the plot as shown here.
I need R = 0.87 and P = 2.2e-16, not "italic(R)" or "italic(P)", while keeping mapping part in stat_cor. I guess, plotly cannot understand italic(p) part as a code. The solution should not fix that adding a text manually, I need the calculation of "R" and "P".
Here is the code:
p1 <- ggplot(iris) +
geom_point(aes(Sepal.Length, Petal.Length)) +
stat_cor(mapping = aes(Sepal.Length, Petal.Length))
p2 <- ggplotly(p1)
p2
You can add anotations to the plotly chart - any sort of R functions and html code are going to work as part of the text.
Plotly only solution
A possible solution is using plotly directly, not using ggplot then converting.
The code would be:
p2 <- plot_ly(data = iris, x=~Sepal.Length, y = ~Petal.Length) |> #base R pipe operator
add_annotations(
xref = "paper", yref = "paper",
x = 0.1, y = 0.9,
text = paste0("<i>R</i> = ", round(cor(iris$Sepal.Length, iris$Petal.Length),2), "<br>",
"<i>P</i> = ", formatC(cor.test(iris$Sepal.Length, iris$Petal.Length)$p.value,
format="e", digits=2)),
showarrow = F, # Does not show an arrow indicating the text position
align = "left") #align the text left
p2
"paper" defines how x and y applies (relative to the axis (paper) or on specific value)
x = 0.1, y = 0.9 says that the text will be placed at the 10% of the x-axis and 90% of the y-axis.
text is the text itself. I am using basic functions to calculate R and p-value, and html symbols to edit the text.
Alternative using ggplotly
As you prefer to use ggplotly, the exact same annotation can be used on it. In this case, the code is:
p1 <- ggplot(iris) + geom_point(aes(Sepal.Length, Petal.Length))
p2 <- ggplotly(p1) |>
add_annotations(
xref = "paper", yref = "paper",
x = 0.01, y = 0.95,
text = paste0("<i>R</i> = ", round(cor(iris$Sepal.Length, iris$Petal.Length),2),
"<br>",
"<i>P</i> = ", formatC(cor.test(iris$Sepal.Length, iris$Petal.Length)$p.value,
format="e", digits=2)),
showarrow = F, # Does not show an arrow indicating the text position
align = "left") #align the text left
p2
Related
I am trying to make my beautiful ggplot map interactive with a tooltip using ggplotly. But the map rendered with ggploty is not beautiful.
Here is a picture of my map with only ggplot:
Here is a picture of my map when using ggplotly. It removes the legend and make the map ugly:
Is there another way of making my ggplot map interactive with a tooltip? And also ggplotly takes some time to render the interactive map:
Here is my sample code for my ggplot:
ggplot(data = sdpf_f, aes( fill = n,x = long, y = lat, group = group, text = tooltip)) +
geom_polygon(color = "white") +
theme_void() +
scale_fill_continuous(low="#c3ffff", high="#0291da",
guide = guide_legend(title.position = "top", label.position = "bottom", keywidth = 2,
keyheight = 0.5,
title = "Number of agreements"),na.value="lightgrey"
) +
theme(legend.position="bottom") +
coord_map()
Thanks & kind regards,
Akshay
I don't have your data and this isn't exactly the same, but it's fairly close to what I think you're expecting.
The libraries:
I called tidyverse for the plotting and piping. I called maps for the data I used and plotly for the Plotly graph.
I used a function that is derived from one of the ways ggplot sets the aspect ratio. I know I found this function on SO, but I don't remember who wrote it.
library(tidyverse)
library(maps)
library(plotly)
map_aspect = function(x, y) {
x.center <- sum(range(x)) / 2
y.center <- sum(range(y)) / 2
x.dist <- ggplot2:::dist_central_angle(x.center + c(-0.5, 0.5), rep(y.center, 2))
y.dist <- ggplot2:::dist_central_angle(rep(x.center, 2), y.center + c(-0.5, 0.5))
y.dist / x.dist
}
I had to create data as your question is not reproducible. I figured I would include it, so that my answer was reproducible.
ms <- map_data("state") %>%
mutate(n = ifelse(str_detect(region, "^a"), 1.0,
ifelse(str_detect(region, "^o"), 1.5,
ifelse(str_detect(region, "^t"), 2.0,
ifelse(str_detect(region, "^s"), 2.5,
ifelse(str_detect(region, "^w"),
3.0,
NA))))))
I modified your ggplot call. The only change is coord_fixed instead of coord_map. I did this so that Plotly could interpret the aspect ratio correctly. Within coord_fixed, I used the function map_aspect.
gp <- ggplot(data = ms, aes(fill = n, x = long, y = lat,
group = group, text = tooltip)) +
geom_polygon(color = "white") +
theme_void() +
scale_fill_continuous(low="#c3ffff", high="#0291da",
guide = guide_legend(title.position = "top",
label.position = "bottom",
keywidth = 2,
keyheight = 0.5,
title = "Number of agreements"),
na.value="lightgrey"
) +
theme(legend.position="bottom") +
coord_fixed(with(ms, map_aspect(long, lat)))
Then I created a Plotly object. I set some requirements for the layout, as well (horizontal legend at the bottom, with the legend title above the elements—similar to the legend in your ggplot call).
pp <- ggplotly(gp) %>%
layout(legend = list(orientation = "h", valign = "bottom",
title = list(side = "top"),
x = .02),
xaxis = list(showgrid = F), yaxis = list(showgrid = F),
showlegend = T)
Next, I needed to add the information for the legend. I chose to name the traces (which are split by color). I started by creating a vector of the names of the traces (which is what you see in the legend). I added a "" at the end, so that the NA-colored states wouldn't have a named trace (so they won't show in the legend).
This is likely something you'll need to change for your data.**
# the color values and the last one, which is for NAs, no legend
nm <- seq(1, 3, by = .5) %>% sprintf(fmt = "%.1f") %>% append(., "")
Then I wanted to ensure that showlegend was true for all but the NA (light gray) states. I created a vector of T and F.
legs <- c(rep(TRUE, 5), FALSE)
Then I added these to the plot.
invisible(lapply(1:length(pp$x$data),
function(i){
pp$x$data[[i]]$name <<- nm[i]
pp$x$data[[i]]$showlegend <<- legs[i]
}))
I'm wondering if there's a 'best' way to define the position of an annotation such that it doesn't overlap with existing legend text. Below is some sample code that generates 2 charts: 1 with a legend that only takes up one line and the second with a legend that take up 2 lines due to the length of the text. I can easily define the position of an annotation such that it works for the first chart, but how would I programmatically define the position of the annotation "if the legend takes up 2 lines instead of just one"? Is there a way to "get" the attributes of the existing legend so I can write an if statement based on the existing positioning or size of the legend? Or is there a way I can define the yanchor of the annotation to be attached to the bottom of the legend? Thanks!
library(plotly)
library(dplyr)
pnames1 <- paste0("Short Name ", 1:4)
pnames2 <- paste0("A Really Really Long Legend Name ", 1:4)
fn_makeChart <- function(pnames){
p <- plot_ly(height = 400, width = 800)
for (n in 1:length(pnames)){
p <- p %>%
add_trace(x = 1:5,
y = runif(5,0,10),
name = pnames[n],
color = pnames[n],
type = 'scatter',
mode = 'lines')
}
p <- p %>%
layout(legend = list(orientation = "h",
xanchor = "center",
x = 0.5, y = -0.1),
showlegend = T)
p
}
anno <- list(x = 0.5, y = -0.23,
xref = 'paper', yref = 'paper',
text = 'My Annotation',
xanchor = 'center', showarrow = F)
#the annotation looks fine on this chart:
fn_makeChart(pnames1) %>% layout(annotations = list(anno))
#but it overlaps the legend on this chart:
fn_makeChart(pnames2) %>% layout(annotations = list(anno))
I can't seem to get the text to rotate in Plotly, in a scatter plot, using add_text().
I'm just trying to get the same result that the angle argument yields in ggplot. In plotly, the output needs to have the hovertext if that's of consequence.
Example -
library(dplyr)
library(plotly)
data <- data.frame(
x = 1:10,
y = runif(10,0,10),
lab = LETTERS[1:10]
)
# base output needed in ggplot
p <- data %>%
ggplot(aes(x,y)) +
geom_text(aes(label = lab, angle = 90))
# doesn't respect angle arg - not that I'm looking to use ggplotly
ggplotly(p)
# plotly version
plot_ly(data) %>%
add_text(
x = ~x,
y = ~y,
text = ~lab,
hovertext = ~paste0("Label is ", lab),
# things I've tried (generally one at a time..)
textfont = list(angle = 90, textangle = 90, orientation = 90, rotate = 90)
)
I'm sure I'm missing something obvious, but I can't track it down.. Help pls!
It appears the solution is to use add_annotations() rather than add_text(). A textangle arg is then accpeted.
Edit - turns out you need two traces - annotations to achieve the text rotation, then markers for the hovertext. Setting opacity = 0 for the markers seems OK.
I'm trying to combine ggplot and plotly together to make a timeline.
It's working great, but have an issue using the legend. The code below replicates what I'm trying to do.
library(ggplot2)
library(plotly)
x=1:10
df2 = data.frame(x,y = 2*x+rnorm(length(x)),lab = as.factor(c("col1","col2")))
status_colors <- c("#0070C0", "#00B050", "#FFC000", "#C00000","darkgreen","purple","darkgrey","blue","salmon","darkorange","black","navy","darkblue")
status_levels <- c(sort(unique(df2$lab)))
p= ggplot(df2,aes(x=x, y=y, col = lab)) + geom_point() + labs(col="labtest") +
scale_color_manual(values=status_colors,
labels=status_levels, drop = FALSE)
fig = ggplotly(p, tooltip = NULL)
fig %>%
add_text(
x = df2$x,
y = ifelse(df2$y>0,df2$y+0.05,df2$y-0.05),
text = df2$lab,
hovertext = df2$lab,
hoverinfo = 'text',
mode ="text",
textfont = list(color=status_colors[df2$lab], size =10),
marker = list(color=status_colors[df2$lab], size = 0.00001),
showlegend = T,
textposition = ifelse(df2$y>0,"top center","bottom center")
)
Basically, as you can see in the image, the label of each point is the same colour as the point that it is attached to. But whenever I add the legend of the label text from plotly, there is a new legend that appears that controls all the points regardless of their colour.
Thus, is there a way to combine the ggplot legend with the plotly legend so that it's only written col1 and col2 with the right colour and that whenever I interact with the points of a certain colour, the label attached to it stays there?
In other words, is there a way to remove the "trace 2" legend and make the "add_text" know that there is a legend already created in ggplot?
If I got you right, besides getting rid of the second legend (which can be simply achievd by setting showlegend = FALSE) you want one legend to control both the points and the labels. This can be achieved via legendgroups. Instead of adding labels with one add_text you could (or have to? Sorry. Still a plotly newbie so perhaps there is a simpler approach) add the labels via two add_text calls one for each col. Instead of copy and paste (which is probably okay for just two cols, but with more cols ...) you can add these via the magic of purrr::reduce to the ggplotly object. Try this:
library(ggplot2)
library(plotly)
library(purrr)
x=1:10
df2 = data.frame(x,y = 2*x+rnorm(length(x)),lab = as.factor(c("col1","col2")))
status_colors <- c("#0070C0", "#00B050", "#FFC000", "#C00000","darkgreen","purple","darkgrey","blue","salmon","darkorange","black","navy","darkblue")
status_levels <- c(sort(unique(df2$lab)))
p= ggplot(df2,aes(x=x, y=y, col = lab)) + geom_point() +
labs(col="labtest") +
scale_color_manual(values=status_colors,
labels=status_levels, drop = FALSE)
fig = ggplotly(p, tooltip = NULL)
purrr::reduce(c("col1", "col2"), ~ .x %>% add_text(
data = filter(df2, lab == .y),
x = ~x,
y = ~ifelse(y > 0, y + 0.05, y-0.05),
text = ~lab,
hovertext = ~lab,
hoverinfo = 'text',
mode ="text",
textfont = list(color= ~status_colors[lab], size =10),
marker = list(color= ~status_colors[lab], size = 0.00001),
showlegend = FALSE,
textposition = ~ifelse(y>0, "top center","bottom center"),
legendgroup = .y
), .init = fig)
BTW: I also simplified the code a little bit. You don't need df2$... because (gg)plotly already knows the data.
Is there a way to add a data source / caption to a chart in Plotly, similar with what can be done in ggplot with the caption argument:
labs(caption = "source: data i found somewhere")
i.e., so we can display the data source at the bottom right of the graph, in a smaller font.
annotation offers a simple way to add a caption to a chart in plotly:
library(plotly)
plot_ly(x=~hp, y=~mpg, data=mtcars, type="scatter", mode="marker") %>%
layout(annotations =
list(x = 1, y = -0.1, text = "Source: data I found somewhere.",
showarrow = F, xref='paper', yref='paper',
xanchor='right', yanchor='auto', xshift=0, yshift=0,
font=list(size=15, color="red"))
)
.
More details are given here and here.
If you want to use a ggplot graph, you can convert it to plotly using ggplotly and still add a caption using the same layout function like this:
library(plotly)
library(ggplot2)
p <- ggplot(mtcars, aes(x = hp, y = mpg)) +
geom_point()
ggplotly(p) %>%
layout(margin = list(l = 50, r = 50, b = 100, t = 50),
annotations = list(x = 1, y = -0.3, text = "Source: data I found somewhere.",
xref='paper', yref='paper', showarrow = F,
xanchor='right', yanchor='auto', xshift=0, yshift=0,
font = list(size = 10)))
Output:
So this makes it possible to use ggplot as a plotly graph.
This was what I was looking for, but it don't produce the same result as expected when I run it - the text didn't show. I edited and tested and got the text to show. But I don't understand all the arguments within the list, could you explain?
Here are some of them I think got figured out.
xref and yref: "paper" - suggest some ref. to the plot-area (but from where, i.e. what point I don't know)
x and y - moving the text in decimal %(?) from some point, increasing x - right, increasing y - up
showarrow - is self-explanitory
Thanks!