I have a xts object that has dates as values, I'm using ggplot2 and shiny app for show the result.
But I want to change the default names of the tooltip when the mouse is on the line.
From:
index: 2020-03-19
value: 70
To:
Date: 2020-03-19
Cantidad: 70
Code for XTS:
data<-rnorm(10)
dates <- seq(as.Date("2016-01-01"), length =10, by = "days")
xtsMyData <- xts(x = data, order.by = dates)
Plot:
r <- ggplot(tidy(xtsMyData), aes(x=index,y=value, color=series, type = 'scatter', mode = 'lines')
) + geom_line(size=2)
The result is:
I'm triyng the following code:
r <- ggplot(tidy(xtsMyData), aes(x=index,y=value, color=series, type = 'scatter', mode = 'lines')
) + geom_line(size=2)
return(ggplotly(r, tooltip = **c("x","y", "series" )**) %>% plotly::config(displayModeBar = T) %>%
layout(legend = list(orientation = "h", x = 0.4, y = -0.2)))
And the result is:
How can I change the tooltip? Can I add words? I tried with paste("Dates","x") but doesn't work.
Thanks for your help.
You can use text in style to change the hover text.
The plotly object will have values accessible through a list as below. The date values will need to be converted with as.Date.
Edit: The code includes a full shiny app as a demo.
library(xts)
library(shiny)
data<-rnorm(10)
dates <- seq(as.Date("2016-01-01"), length =10, by = "days")
xtsMyData <- xts(x = data, order.by = dates)
ui <- fluidPage(
plotlyOutput("myplot")
)
server <- function(input, output, session) {
output$myplot <- renderPlotly({
r <- ggplot(tidy(xtsMyData), aes(x=index,y=value, color=series, type = 'scatter', mode = 'lines')) +
geom_line(size=2)
r <- ggplotly(r) %>%
plotly::config(displayModeBar = T) %>%
layout(legend = list(orientation = "h", x = 0.4, y = -0.2))
r %>%
style(text = paste0("Date:", as.Date(r$x$data[[1]]$x),
"</br></br>",
"Cantidad:", r$x$data[[1]]$y))
})
}
shinyApp(ui, server)
Plot
The first answer gave me the idea to change manually all, because I had 2 different geom_lines and that didn't work for me , this labels are stored in r$x$data[[1]]$text (the following line plots are in r$x$data[[2]]$text,r$x$data[[3]]$text... ), so, if you use an gsub, you could change everything you want, it's very dumb but it works. (You can use the same philosophy to delete the last label, manipulating strings)
I put an example for your problem, despite you already solve it, other person could have more than one line plot.
r$x$data[[1]]$text<-gsub(r$x$data[[1]]$text,pattern='index', replacement='Fecha')
r$x$data[[1]]$text<-gsub(r$x$data[[1]]$text,pattern='value', replacement='Valor')
r$x$data[[1]]$text<-gsub(r$x$data[[1]]$text,pattern='series', replacement='Serie')
Related
A complete ggplot2/Shiny beginner here. I have been searching on Stack and Google for days and could not come up with a decent solution.
Task: to create an interactive leaflet map showing a user-selected column in a long data format (Covid vaccine doses - first, second, and third dose; need shiny to feed this into ggplot2's "data"), which are pre-filtered based on additional user choices (month of the year, age group, type of vaccine administered; these cannot be fed into ggplot2 directly so I need to filter out the data). I am therefore interested in subsetting selected columns (time, age_group, vaccine) based on the values the users select in the input.
I am importing a data frame in .csv which needs to be merged with a sf object later on to match the data with the sf coordinates (supplied by RCzechia).
# Load packages
library(shiny)
library(here)
library(tidyverse)
library(ggplot2)
library(RCzechia)
library(sf)
# Load data
df <- read.csv("data", encoding = "UTF-8")
# load geo-spatial sf data for ggplot
czrep <- republika()
regions <- kraje(resolution = "low")
# Defining UI for the ggplot application
ui <- fluidPage(
titlePanel(),
# Sidebar
sidebarLayout(
sidebarPanel(width = 3,
selectInput("box_time", label = "Month & Year",
choices = sort(unique(df$time)), selected = "",
width = "100%", selectize=FALSE),
selectInput("box_age", label = "Age group",
choices = sort(unique(df$age_group)), selected = "",
width = "100%", selectize=FALSE),
selectInput("box_vax", label = "Type of vaccine",
choices = sort(unique(df$vaccine)), selected = "",
width = "100%", selectize=FALSE),
radioButtons("button_dose", label = "Vaccine dose",
choices = c("First dose" = "first_dose",
"Second dose" = "second_dose",
"Booster" = "booster"))
),
# Displaying the user-defined ggplot
mainPanel(
plotOutput("map")
)))
# Server
server <- function(input, output) {
# select column for ggplot
r_button_dose <- reactive({input$button_dose})
### Subset based on user choices - this is where I tried to create a new data frame (new_df) as a result of subsetting by - see below. ###
# merge the df with the sf object
new_df <- merge(regions, new_df, by.x = "region_id", by.y="region_id")
# transform data set into an sf object (readable by ggplot)
new_df <- st_as_sf(new_df)
})
# Generating the plot based on user choices
output$map <- renderPlot({
ggplot(data = new_df) +
geom_sf(aes_string(fill = r_button_dose(), colour = NA, lwd = 2)) +
geom_sf(data = czrep, color = "grey27", fill = NA) +
scale_fill_viridis_c(trans = "log", labels = scales::comma) +
labs(fill = "log scale") +
theme_bw() +
theme(legend.text.align = 1,
legend.title.align = 0.5)
})
}
# Starting the Shiny application
shinyApp(ui = ui, server = server)
I cannot figure out how to subset the data - I have tried many different things that I found here and on the RStudio community forms.
Here are a couple of things I have already tried:
# used both filter() and subset(); also tried both '==' and '%in%'
new_df %>%
filter(time %in% box_time() &
age_group %in% input$box_age() &
vaccine %in% input$box_vax())
})
#OR#
new_df <- reactive({
df <- df %>%
filter(time %in% box_time() &
age_group %in% input$box_age() &
vaccine %in% input$box_vax())
})
#OR#
new_df <- df
new_df$time <- df[df$time==box_time(),]
new_df$age_group <- df[df$age_group==input$box_age(),]
new_df$vaccine <- df[df$vaccine ==input$box_vax(),]
# I also tried passing them the same way as this example:
r_button_dose <- reactive({input$button_dose})
#OR EVEN#
new_df <- reactive({
new_df <- df
new_df$time <- df[df$X.U.FEFF.year_mo==box_time(),]
new_df$age_group <- df[df$age_group==input$box_age(),]
new_df$vaccine <- df[df$vaccine ==input$box_vax(),]
})
With the latest option, I get the following error - even though they are similar:
Listening on http://127.0.0.1:4092
Warning: Error in $: object of type 'closure' is not subsettable
1: runApp
Warning: Error in $: object of type 'closure' is not subsettable
1: runApp
Warning: Error in as.data.frame.default: cannot coerce class ‘c("reactiveExpr", "reactive", "function")’ to a data.frame
176: stop
175: as.data.frame.default
172: merge.data.frame
168: renderPlot [C:/Users/xyz/Documents/R/example/gg_app.R#78]
166: func
126: drawPlot
112: <reactive:plotObj>
96: drawReactive
83: renderFunc
82: output$map
1: runApp
I don't know what to do - looking for more examples online has not worked. I know that I cannot pass a reactive value directly (even though I am not sure if it is because it returns a logical value). I would be extremely grateful for any tips regarding how to resolve this - thank you!
You can define your reactive dataframe as a reactiveVal:
df_filtered <- reactiveVal(df) ## df being your initial static dataframe
The tricky bit is to treat your reactive dataframe as a function, not an static object:
## works:
df_filtered(df %>% filter(age_group == input$box_age))
renderDataTable(df_filtered()) ## note the parentheses
instead of:
## won't work:
df_filtered <- df %>% filter(age_group %in% input$box_age)
renderDataTable(df_filtered)
finally, wrap it into a reactive expression:
observe({df_filtered(df %>% filter(age_group == input$box_age))
## note: function argument, not assignment operator
output$map <- renderPlot({
df_filtered() %>% ## again: note function (parentheses)
ggplot() # etc.
})
}) %>% bindEvent(input$box_age, input$some_other_picker)
I think you are almost there, slight syntax issue. Note I return the new_df as part of reactive block (essentially a function), and, in renderPlot, I tell 'data' is in essence invocation result of function r_button_dose. You need to modify the fill attribute as I'm not sure what you want it to be filled with
# select column for ggplot
r_button_dose <- reactive({input$button_dose})
### Subset based on user choices - this is where I tried to create a new data frame (new_df) as a result of subsetting by - see below. ###
# merge the df with the sf object
new_df <- merge(regions, new_df, by.x = "region_id", by.y="region_id")
# transform data set into an sf object (readable by ggplot)
new_df <- st_as_sf(new_df)
new_df
})
# Generating the plot based on user choices
output$map <- renderPlot({
ggplot(data = r_button_dose()) +
geom_sf(aes_string(fill = r_button_dose()$region_id, colour = NA, lwd = 2)) +
geom_sf(data = czrep, color = "grey27", fill = NA) +
scale_fill_viridis_c(trans = "log", labels = scales::comma) +
labs(fill = "log scale") +
theme_bw() +
theme(legend.text.align = 1,
legend.title.align = 0.5)
})
I’m trying to display error bars on a scatter plot with Shiny and plotly. Here’s my code in my server.R file:
data = reactiveVal()
observe({
results <- data.frame() # actually getting the data from here
# formatting output
final.results <- cbind(
"id" = paste(results$a,
results$b,
results$c,
sep = '-'),
"sigma" = sprintf("%.5g", results$s),
"c-e" = sprintf("%.3g",results$calc - results$exp)
)
data(final.results)
})
output$plot <- renderPlotly(
as.data.frame(data()[,c("id", "c-e", "sigma")]) %>% plot_ly(
x = ~`c-e`,
y = ~id,
height = 800,
type = 'scatter',
mode = 'markers',
marker = list(color = "#90AFD9"),
error_x = list(array = ~sigma, color = "#000000", type = "data")
)
)
The plot is ok except it’s not showing the error bars, what’s my mistake ?
EDIT: clarification for the origin of the data() function and what it’s return value is.
Thesprintf() function returns a character string, not a number, that is why it is not displaying the sigma values as error bars. If you want to keep 5 decimal places, use the round() function instead:
"sigma" = round(results$s, digits = 5)
The code blow generates a plotly graph with one data point. I design this plot to be able to display some text information when the users move the mouse cursor to the data point, but as the plot shows, this does not work.
library(dplyr)
library(lubridate)
library(plotly)
a1 <- data.frame(
DateTime = ymd_hms("2020-01-01 08:00:00"),
Value = 1
)
a1 <- a1 %>%
mutate(DateTimeText = as.character(DateTime))
p1 <- plot_ly(a1, x = ~DateTime, y = ~Value, type = "scatter", mode = "markers",
text = ~DateTimeText,
hovertemplate = paste(
"<br>Date Time: %{text} </br>",
"<br>Value: %{y} </br>",
"<extra></extra>"))
However, if I provided two data points. The code works. Here is an example. This is strange to me as I think both cases should work. Please give some advice.
a2 <- data.frame(
DateTime = ymd_hms(c("2020-01-01 08:00:00", "2020-01-02 08:00:00")),
Value = c(1, 2)
)
a2 <- a2 %>%
mutate(DateTimeText = as.character(DateTime))
p2 <- plot_ly(a2, x = ~DateTime, y = ~Value, type = "scatter", mode = "markers",
text = ~DateTimeText,
hovertemplate = paste(
"<br>Date Time: %{text} </br>",
"<br>Value: %{y} </br>",
"<extra></extra>"))
The issue is that your length 1 vector in R is not properly converted to a JSON array of length 1. This a known pitfall as there is some ambiguity when converting R objects to JSON, see https://plotly-r.com/json.html. This ambiguity does not arise when you have a vector of length > 1. That's why you code works in such cases.
To solve this make use of the asIs function or I, i.e. use text = ~I(DateTimeText). Try this:
library(dplyr)
library(lubridate)
library(plotly)
a1 <- data.frame(
DateTime = ymd_hms("2020-01-01 08:00:00"),
Value = 1
)
a1 <- a1 %>%
mutate(DateTimeText = as.character(DateTime))
p1 <- plot_ly(a1, x = ~DateTime, y = ~Value, type = "scatter", mode = "markers",
text = ~I(DateTimeText),
hovertemplate = paste(
"<br>Date Time: %{text} </br>",
"<br>Value: %{y} </br>",
"<extra></extra>"))
p1
In the following shiny app, the plotly package is used to create an interactive correlation heat map. When individual tiles are clicked, the corresponding scatter plot appears. One can then download the individual scatters by clicking download plot as png. But is there a way to download all the possible scatter plots at once without having to click each individual tile and save each individual one? Thank you
library(plotly)
library(shiny)
# compute a correlation matrix
correlation <- round(cor(mtcars), 3)
nms <- names(mtcars)
ui <- fluidPage(
mainPanel(
plotlyOutput("heat"),
plotlyOutput("scatterplot")
),
verbatimTextOutput("selection")
)
server <- function(input, output, session) {
output$heat <- renderPlotly({
plot_ly(x = nms, y = nms, z = correlation,
key = correlation, type = "heatmap", source = "heatplot") %>%
layout(xaxis = list(title = ""),
yaxis = list(title = ""))
})
output$selection <- renderPrint({
s <- event_data("plotly_click")
if (length(s) == 0) {
"Click on a cell in the heatmap to display a scatterplot"
} else {
cat("You selected: \n\n")
as.list(s)
}
})
output$scatterplot <- renderPlotly({
s <- event_data("plotly_click", source = "heatplot")
if (length(s)) {
vars <- c(s[["x"]], s[["y"]])
d <- setNames(mtcars[vars], c("x", "y"))
yhat <- fitted(lm(y ~ x, data = d))
plot_ly(d, x = ~x) %>%
add_markers(y = ~y) %>%
add_lines(y = ~yhat) %>%
layout(xaxis = list(title = s[["x"]]),
yaxis = list(title = s[["y"]]),
showlegend = FALSE)
} else {
plotly_empty()
}
})
}
shinyApp(ui, server)
You can use webshot to capture a static image of Plotly's HTML output using the instructions here: https://plot.ly/r/static-image-export/
An example for loop below generates random scatter plots from mtcars.
library(plotly)
library(webshot)
## You'll need to run the function the first time if you dont't have phantomjs installed
#webshot::install_phantomjs()
ColumnOptions <- colnames(mtcars)
for (i in seq_len(5)){
xCol <- sample(ColumnOptions,1)
yCol <- sample(ColumnOptions,1)
ThisFileName <- paste0("Scatter_",xCol,"_vs_",yCol,".png")
plot_ly(x = mtcars[[xCol]], y = mtcars[[yCol]], type = "scatter", mode = "markers") %>%
export(., file = ThisFileName)
}
However, if you're going to be potentially doing this dozens of times, the amount of computation required to go through the following steps really adds up.
Generate a JSON plotly object from R
Use htmlwidgets/htmltoolsto generate a self-contained HTML web page
Render that HTML as a browser would see it with an external program --webshot
Use webshot to render an image of that HTML and save it as a PNG
This isn't really a reflection of plotly being slow, but to make an analogy it's kind've like using an airplane to travel half a mile -- the plane gets you there, but if you need to make that trip more than a few times you should probably consider a car.
The plotly loop above takes 27 seconds to render 5 PNG images, but the alternative method below using ggplot2 takes 1.2 seconds.
library(ggplot2)
ColumnOptions <- colnames(mtcars)
for (i in seq_len(5)){
xCol <- sample(ColumnOptions,1)
yCol <- sample(ColumnOptions,1)
ThisFileName <- paste0("ggplot2_Scatter_",xCol,"_vs_",yCol,".png")
ggplot() +
geom_point(aes(x = mtcars[[xCol]], y = mtcars[[yCol]])) +
labs(x = xCol, y = yCol) -> ThisPlot
ggsave(plot = ThisPlot, filename = ThisFileName)
}
I am searching for a way to (auto-)scale the y-axis of a candlestick chart. If you take a look at the following example (from https://plot.ly/r/candlestick-charts/)
library(plotly)
library(quantmod)
getSymbols("AAPL",src='yahoo')
df <- data.frame(Date=index(AAPL),coredata(AAPL))
df <- tail(df, 365)
p <- df %>%
plot_ly(x = ~Date, type="candlestick",
open = ~AAPL.Open, close = ~AAPL.Close,
high = ~AAPL.High, low = ~AAPL.Low) %>%
add_lines(y = ~AAPL.Open, line = list(color = 'black', width = 0.75)) %>%
layout(showlegend = FALSE)
The y-axis has autoscal="normal", so it takes min and max from the dataset, but if you zoom, these values stay the same. It would be clearer for me to have the min and max of the current (zoomed/viewed) part of the graph
Until now i could not find a way to implement this feature, does anyone know a way to do so?
Amother solution for me would be just to get the "normal" zoom from charts like in this example:
library(plotly)
set.seed(100)
d <- diamonds[sample(nrow(diamonds), 1000), ]
plot_ly(d, x = ~carat, y = ~price, color = ~carat,
size = ~carat, text = ~paste("Clarity: ", clarity))
Moving the slider within the candlestick chart is not autoscaling the y-axis for me either. Plotly team may not have solved it yet for candlesticks charts. autorange = TRUE is working neither.
But if anyone is using this in a shiny application, the workaround which can work is to have a date slider reactively connected to Plotly graph. The steps are as following:
create a date slider with a start and end selected
ui.R
...
uiOutput("dateSlider")
...
server.R
output$dateSlider <- renderUI({
sliderInput(
"dtSlider",
"Select a date range:",
min = min_date,
max = max_date,
value = c(max_date - 30, max_date), # in this case last 30 defines start and end
timeFormat = "%Y-%m-%d",
width = '80%'
)
output$dateSlider <- renderUI({
In the RenderPlotly section of server.R calculate a data.frame with data filtered from start to end using input$dtSlider[1] and input$dtSlider[2] correspondingly and then use the new reactively filtered data.frame in the Plotly code.
ui.R
plotlyOutput("candleChart")
server.R
output$candleChart <- renderPlotly({
...
df <- as.data.frame(dbFetch(res)) # querying DB to pull data with new input$dtSlider[1] as start and input$dtSlider[2] as end
...
fig <- df %>% plot_ly(x = ~timestamp, type="candlestick",
open = ~open, close = ~close,
high = ~high, low = ~low )
...
})
So now if we change the date slider, y-axis range changes automatically as shown below from same data.frame object: