ggedit v0.2.1 : interaction in Shiny web app' ?
Opened this issue · 12 comments
Hi everyone,
I am interested in implementing an interactive ggplot2 editor, such as the one proposed by Yoni's package ‘ggedit’, in a Shiny web application.
Do you know if there is a way to pass to callModule(ggEdit, …) an object that is already in a reactive context ?
In practice, I would like to use as the default ggplot for ggEdit, the graphics which has been created/edited by the user via one or several input parameters (e.g: station, pollutants, time period, ...)
In the present example: ui side numericInput(“alpha”) --> input$alpha --> server side p1.react <- reactive({ggplot(…) + geom_point(alpha = input$alpha)}).
Observation : because p1.react() is not a ggplot2 object, it cannot be edited by ggEdit as expected…
Please find in attachment, the Shiny app’ (actually, I ran the source code from Yoni R-bloggers’ post “ggedit 0.1.1: Shiny module to interactvely edit ggplots within Shiny applications”
and added few lines of code) used to test this behavior.
Any clue is welcome.
With kind regards,
Laurent
app.txt
the module is expecting a reactive object, so you don't need to wrap the reactive plot you made with a reactive call again. I fixed your script below to have it work the way you intended.
look in the line with outp2
i also wrapped the plot in p1.react
in a list just to give it a name to see that it is being picked up ok (it can also be just a plot the way you had it)
ui = fluidPage(
numericInput(inputId = "alpha", label = "Alpha", value = 0.5, min = 0, max = 1),
conditionalPanel("input.tbPanel == 'tab2'",
sidebarPanel(uiOutput('x1'), uiOutput('x2'))),
mainPanel(
tabsetPanel(id = 'tbPanel',
tabPanel('renderPlot/plotOutput',value = 'tab1',plotOutput('p')),
tabPanel('ggEdit/ggEditUI',value = 'tab2',ggEditUI('pOut1')),
tabPanel('ggEdit/ggEditUI with lists of plots',value = 'tab3',ggEditUI('pOut2'))
)))
server = function(input, output, session) {
p1.react <- reactive({
p <- ggplot(iris,aes(x = Sepal.Length,y = Sepal.Width, colour = Species)) +
geom_point(alpha = input$alpha) +
scale_x_continuous(limits = c(-5, 5)) +
labs(title = "test", x = "x-axis", y = "y-axis") +
theme(panel.background = element_rect(fill = 'lightblue'),
legend.key = element_rect(colour = "pink", size = 1.5),
legend.background = element_rect(colour = "green"))
list(reactive_plot=p)
})
p1 <- ggplot(iris,aes(x = Sepal.Length, y = Sepal.Width, colour = Species)) +
geom_point(alpha = 0.5) +
scale_x_continuous(limits = c(-5, 5)) +
labs(title = "test", x = "x-axis", y = "y-axis") +
theme(panel.background = element_rect(fill = 'lightblue'),
legend.key = element_rect(colour = "pink", size = 1.5),
legend.background = element_rect(colour = "green"))
p2 <- ggplot(iris,aes(x = Sepal.Length, y = Sepal.Width, colour = Species)) +
geom_line(color = 'black') +
geom_point() +
scale_x_continuous(limits = c(-5, 5))
p3 <- list(p1 = p1, p2 = p2)
# reactive plot
observeEvent(p1.react(),{
output$p <- renderPlot({
p1.react()
})
})
outp1 <- callModule(ggEdit,'pOut1', obj = reactive(list(p1 = p1)))
outp2 <- callModule(ggEdit,'pOut2', obj = p1.react) #<======
output$x1 <- renderUI({
layerTxt = outp1()$UpdatedLayerCalls$p1[[1]]
aceEditor(outputId = 'layerAce', value=layerTxt,
mode = 'r', theme = 'chrome',
height = '100px', fontSize = 12, wordWrap = T)
})
output$x2 <- renderUI({
themeTxt = outp1()$UpdatedThemeCalls$p1
aceEditor(outputId = 'themeAce', value = themeTxt,
mode = 'r', theme = 'chrome',
height = '100px', fontSize = 12, wordWrap = T)
})
}
shinyApp(ui, server)
Hi Yoni,
Thank you for the quick reply.
Ok, understood what was wrong in the example that I provided. Thank you for fixing it.
But there's still a behaviour that seems "not as expected" to me:
when you launch the application, both 'renderPlot/plotOutput' and 'ggEdit/ggEditUI with lists of plots' display the plot which alpha parameter is set to 0.5 (default value) --> OK, normal behaviour.
However, when once change input$alpha's value, only 'renderPlot/plotOutput' is updated (as expected of course) but 'ggEdit/ggEditUI with lists of plots' is not. Yet, in my opinion, it should be since 'pOut2' has p1.react as input object.
To update this plot ('ggEdit/ggEditUI with lists of plots') , once have to click on "Update Plot Layer" or "Update Plot Theme" or "Update Grid Theme".
Indeed after you do this, 'renderPlot/plotOutput' and 'ggEdit/ggEditUI with lists of plots' are the same.
Note that the alpha value displayed in widget "Update Plot Layer" is not the actual value (cfr input$alpha).
Observation made with this app' :
ggedit.txt
Please find a similar app' that implements additional ui inputs: actionButton() and textInput().
In this app, 'pOut1' is also p1.react() thus, 'ggEdit/ggEditUI' should be reactive as well.
ggedit_action.txt
What do you think about this ?
Thanks for helping me out.
Laurent
The modules need to be wrapped in a observer that is conditional on input$alpha
.
See solution below
library(shiny)
library(ggedit)
library(ggplot2)
library(shinyAce)
ui = fluidPage(
numericInput(inputId = "alpha", label = "Alpha", value = 0.5, min = 0, max = 1),
conditionalPanel("input.tbPanel == 'tab2'",
sidebarPanel(uiOutput('x1'), uiOutput('x2'))),
mainPanel(
tabsetPanel(id = 'tbPanel',
tabPanel('renderPlot/plotOutput',value = 'tab1',plotOutput('p')),
tabPanel('ggEdit/ggEditUI',value = 'tab2',shiny::uiOutput('gg1')),
tabPanel('ggEdit/ggEditUI with lists of plots',value = 'tab3',
shiny::uiOutput('gg2'))
)))
server = function(input, output, session) {
p1 <- ggplot(iris,aes(x = Sepal.Length, y = Sepal.Width, colour = Species)) +
geom_point(alpha = 0.5) +
scale_x_continuous(limits = c(-5, 5)) +
labs(title = "test", x = "x-axis", y = "y-axis") +
theme(panel.background = element_rect(fill = 'lightblue'),
legend.key = element_rect(colour = "pink", size = 1.5),
legend.background = element_rect(colour = "green"))
p1.react <- eventReactive(input$alpha,{
ggplot(iris,aes(x = Sepal.Length,y = Sepal.Width, colour = Species)) +
geom_point(alpha = input$alpha) +
scale_x_continuous(limits = c(-5, 5)) +
labs(title = "test", x = "x-axis", y = "y-axis") +
theme(panel.background = element_rect(fill = 'lightblue'),
legend.key = element_rect(colour = "pink", size = 1.5),
legend.background = element_rect(colour = "green"))
})
output$p <- renderPlot({
p1.react()
})
outp1 <- callModule(ggEdit,'pOut1', obj = reactive(list(p1 = p1)))
output$gg1 <- renderUI({
ggEditUI('pOut1')
})
observeEvent(input$alpha,{
outp2 <- callModule(ggEdit,'pOut2', obj = reactive(list(rp =p1.react())))
output$gg2 <- renderUI({
ggEditUI('pOut2')
})
})
output$x1 <- renderUI({
layerTxt = outp1()$UpdatedLayerCalls$p1[[1]]
aceEditor(outputId = 'layerAce', value=layerTxt,
mode = 'r', theme = 'chrome',
height = '100px', fontSize = 12, wordWrap = T)
})
output$x2 <- renderUI({
themeTxt = outp1()$UpdatedThemeCalls$p1
aceEditor(outputId = 'themeAce', value = themeTxt,
mode = 'r', theme = 'chrome',
height = '100px', fontSize = 12, wordWrap = T)
})
}
shinyApp(ui, server)
Yoni,
Again thank you for your answer.
I think I understood the way (your way) in which this "issue" could be resolved.
But, when I try to apply this scheme to a more "realistic" shiny app', I face with a weird bug:
when the "ggEditable plot" is created
`
#trigger : actionButton("up")
observeEvent(eventExpr = input$up, handlerExpr = {
ggp <- plot.react()
outp1 <- callModule(ggEdit,'pOut1', obj = reactive(ggp))
output$gg1 <- renderUI({
ggEditUI('pOut1')
})
})
`
an error message (Error: replacement has 4 rows, data has 3) occurs (cfr tab "ggEdit/ggEditUI") but the app' still runs.
I don't understand the origin of this error. Do you have any idea ?
Note that "ggEdit/ggEditUI" plot is as reactive as "renderPlot/plotOutput" plot BUT in addition to this error message when one clicks on "Update Plot Layer" a blocking error appears but it does not happen when "Update Plot Theme" is used (indeed, I can even change the background color of the plot via Panel).
To be honest, I don't understand this phenomenon (that is : error message but reactive plot "ggEdit/ggEditUI", "Update Plot Layer" is NOK and "Update Plot Theme" is nearly OK).
In attachment, please find the new app' and a csv file to load.
May I ask you to set up several inputs as follow (the other one can keep their default condition) :
- Choose csv file : load ggedit_data.CSV supplied.
- Specify the full path of the CSV file (mandatory) : none (useless input in the present case study).
- Date or date/time ? : Date and Time
- Choose appropriate date time format : dd/mm/yyyy %H:%M
- Choose a variable for the x-axis (date/time): date
- Choose a variable for the y-axis (e.g.: pollutants): pm10, no
Then press the actionButton...
Many thanks for your help.
App' + csv file
ggedit_demo.zip
Laurent
library(reshape2)
library(ggplot2)
# Loading of the data
tmp <- read.csv2("ggedit_data.CSV", header = T, dec = ",")
# Reshaping data
## date time format: POSIXct
tmp$date <- as.POSIXct(strptime(as.character(levels(tmp$date)[tmp$date]), format = "%d/%m/%Y %H:%M", tz = "GMT"), tz = "GMT")
# for a more "readable" plot
tmp[, !names(tmp)%in%"date"][which(tmp[, !names(tmp)%in%"date"] > 125, arr.ind = T)] <- NA
## wide to long format
tmp.l <- melt(tmp, id.vars = "date")
# GGPLOT
p1 <- ggplot(tmp.l, aes_string(x = "date", y = "value")) +
geom_point(aes(colour = variable)) +
theme(panel.background = element_rect(fill = 'lightblue'),
legend.key = element_rect(colour = "pink", size = 1.5),
legend.background = element_rect(colour = "green"))
p1.list <- layersList(p1)
# Is this error related to column "date" ? output value of class(tmp$date) is length == 2
# > sapply(tmp,class)$date
# [1] "POSIXct" "POSIXt"
tmp.l.1 <- tmp.l
# POSIXct, POSIXt to integer
tmp.l.1$date <- as.integer(tmp.l.1$date)
p1.bis <- ggplot(tmp.l.1 , aes_string(x = "date", y = "value")) +
geom_point(aes(colour = variable)) +
theme(panel.background = element_rect(fill = 'lightblue'),
legend.key = element_rect(colour = "pink", size = 1.5),
legend.background = element_rect(colour = "green"))
p1.bis.list <- layersList(p1.bis)
## no more error message ...
Indeed, when I withdraw "date" variable from the plot (in the shiny app') and plot e.g. pm10 vs pm10, everything works fine.
Again thank you for your assistance.
Laurent
that is great! thank you for testing the app with real data, it is great to see that it is holding up. If it is ok with you, I will add these examples to the gitbook so others can learn how to use the modules in a real workflow.
Thanks for this comment !
Sorry but could you please do something to solve this issue ?
Because to my point of view, the question remains open as I cannot display POSIXct variable in ggEdit plot.
Note that no error occurs with Date class:
# In addition to the previous R code chunk
# POSIXct, POSIXt to Date
tmp.l.2 <- tmp.l
tmp.l.2$date <- as.Date(tmp.l.2$date)
p2.bis <- ggplot(tmp.l.2 , aes_string(x = "date", y = "value")) +
geom_point(aes(colour = variable)) +
theme(panel.background = element_rect(fill = 'lightblue'),
legend.key = element_rect(colour = "pink", size = 1.5),
legend.background = element_rect(colour = "green"))
p2.bis.list <- layersList(p2.bis)
With POSIXct POSIXt class :
p1 <- ggplot(tmp.l, aes_string(x = "date", y = "value")) +
geom_point(aes(colour = variable)) +
theme(panel.background = element_rect(fill = 'lightblue'),
legend.key = element_rect(colour = "pink", size = 1.5),
legend.background = element_rect(colour = "green"))
p1.list <- layersList(p1)
Error in `$<-.data.frame`(`*tmp*`, "class", value = c("POSIXct", "POSIXt", :
replacement has 3 rows, data has 2
In light of this, it seems that an operation (in relation with classes of the variables) done in ggedit::layersList() expects a third element for the class "POSIXct","POSXIt"...
function (obj)
{
if (is.ggplot(obj))
obj = list(obj)
rmNullObs(lapply(obj, layersListFull))
}
<environment: namespace:ggedit>
thank you for raising this issue. It is a bit more complicated because this object must have both class types to work. unfortunately i do not see a quick fix. I am currently finishing up the next release and will find a solution for this issue in that release.
Ok it's great ! I'm sure you will fix this issue :-)
Looking forward to hearing from you !
this seems to be working ok now. (first re-install from ggedit github)
timestamp <- c(1441229131, 1441229132, 1441229133, 1441229134, 1441229135)
response.time <- c(22, 48, 48, 59, 52)
lt1 <- data.frame(timestamp, response.time)
lt1$datetime <- as.POSIXct(lt1$timestamp, origin="1970-01-01", tz="GMT" format="%H:%M:%S")
library(scales)
p <- ggplot(lt1, aes(datetime, response.time)) +
geom_point() +
theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
scale_x_datetime(labels = date_format("%H:%M:%S"))
ggedit(p)
Hi Yoni,
Thank you and great work ! Indeed, POSIXt class is supported by ggedit :-)
While waiting for this new release, I found a temporary workaround in the sense that I added an extra variable in the data frame which consists of the POSIX var converted into integer (like your timestamp var). Since that, I was able to handle this dataframe with ggedit. Of course, I changed the labels of the x-axis ticks (var dateTime.int) and put appropriate labels (e.g. 2017-10-18 23:30:00) obtained from var dateTime.posix.
Well, now it's no longer useful ^^
Thanks again.
Regards,
Laurent