I'm working on a little shiny app that requests stock data from Yahoo. If one clicks on the plot, the respective price / datetime row from the dataset shall be shown. But unfortunately it seems that input$plot_click does not return the correct x,y - values.
Here's the mwe:
library(shiny)
library(htmlwidgets)
library(ggplot2)
library(scales)
library(dplyr)
library(RCurl)
library(XML)
library(rvest)
server <- function(input, output, session) {
Sys.setlocale("LC_TIME", "C")
dataset <- data.frame()
xml.url <- "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.quote%20where%20symbol%20in%20(%22YHOO%22%2C%22AAPL%22%2C%22GOOG%22%2C%22MSFT%22)&diagnostics=true&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"
YahooObs <- function(xml.url){
script <- getURL(xml.url)
doc <- xmlParse(script)
results <- doc %>% xml_nodes("results")
dataset <- lapply(results, FUN=function(x){xmlToDataFrame(x, stringsAsFactors = F)})[[1]]
dataset$LastTradePriceOnly <- as.numeric(dataset$LastTradePriceOnly)
created <- doc %>% xml_node("query") %>% xml_attr("created")
dataset$created <- as.POSIXct(strptime(created, format="%Y-%m-%dT%H:%M:%SZ", tz="UTC")+3600)
return(dataset)
}
output$newsplot <- renderPlot({
invalidateLater(10000, session)
dataset <<- rbind(dataset, YahooObs(xml.url))
p <- ggplot(data = dataset)
p <- p + layer(mapping=aes(x=created, y=LastTradePriceOnly, color= Symbol),
geom="point", stat="identity", position="identity")
limit_down <- as.POSIXct(Sys.time()-input$timeslider*60)
attributes(limit_down)$tzone <- input$timezone
limit_up <- as.POSIXct(Sys.time())
attributes(limit_up)$tzone <- input$timezone
p <- p + scale_x_datetime(breaks = date_breaks("200 sec"), labels = date_format("%H:%M:%S"),
limits=c(min(dataset$created-1800), max(dataset$created))) +
theme(axis.text.x = element_text(angle = 90), panel.grid.major=element_blank(), panel.grid.minor=element_blank(),
panel.background = element_blank()) + coord_cartesian()
print(p)
})
output$plot_click <- renderPrint({
paste(str(input$plot_click))
})
output$newstable <- renderDataTable({
##Transforming the created column to numeric was a hint on stackoverflow but it didn't work out
#dataset$created <- as.numeric(dataset$created)
nearPoints(dataset, input$plot_click, xvar="created",yvar="LastTradeDatePrice", threshold = 100, maxpoints = 10,
addDist = TRUE)
})} #the server
ui_2 <- shinyUI(fluidPage(
#header
titlePanel(tags$h1("Share Prices")),
#horizontal line
sidebarLayout(
sidebarPanel(
sliderInput("timeslider", label = "Choose Timespan in minutes", min = 1, max = 60, value = 30, step = 1),
width=3
),
mainPanel(
tabsetPanel(type="tabs",
tabPanel("News Plot", plotOutput("newsplot", click="plot_click"),dataTableOutput("newstable"), textOutput("plot_click")),
tabPanel("Settings", selectInput("timezone", label="Choose your Timezone", choices=c("UTC")))
)
)
)))# the user interface
shinyApp(ui = ui_2, server = server) # this launches your app
Any idea?
It seems that print(p) manipulates something. Using only p solves the problem and shows the POSIXct values correctly as numeric.
p <- ggplot(data = dataset)
p <- p + layer(mapping=aes(x=created, y=LastTradePriceOnly, color= Symbol),
geom="point", stat="identity", position="identity")limit_down <- as.POSIXct(Sys.time()-input$timeslider*60)
attributes(limit_down)$tzone <- input$timezone
limit_up <- as.POSIXct(Sys.time())
attributes(limit_up)$tzone <- input$timezone
p <- p + scale_x_datetime(breaks = date_breaks("200 sec"), labels = date_format("%H:%M:%S"),
limits=c(min(dataset$created-1800), max(dataset$created))) +
theme(axis.text.x = element_text(angle = 90), panel.grid.major=element_blank(), panel.grid.minor=element_blank(),
panel.background = element_blank()) + coord_cartesian()
p
works.
Related
When I run this code with renderPlotly. It gives me error but without renderplotly it is working fine. Can you help me in fixing this code with renderPlotly? Thanks in advance.
output$tot_finalized_claims1 <- renderPlotly({
req(input$yearSelectInput)
#filter df to be used in graph
claims1 <- newly_formatted_logResults %>% filter(YEAR == input$yearSelectInput) %>% filter(PEND == "CMI") %>% select(YEAR,MONTH_NUM,PEND, TOTAL_FINALIZE,TOTAL)
data_pcode <- summarize(group_by(claims1,MONTH_NUM), actual_auto = round(sum(as.numeric(TOTAL_FINALIZE),na.rm = TRUE)/sum(as.numeric(TOTAL),na.rm = TRUE),digits = 2))
data_pcode <- data.frame(data_pcode)
ggplot(data = data_pcode,aes(x = MONTH_NUM, y = actual_auto )) +
geom_point() + geom_line() + # add the points and lines
stat_QC(method = "XmR" # specify QC charting method
auto.label = T, # Use Autolabels
label.digits = 2, # Use two digit in the label
show.1n2.sigma = T # Show 1 and two sigma lines
)+
labs(x = "Months",y = "Automation Rate",title = paste("Actual automations by CMI Pend code"))+
geom_text(aes(label=paste(actual_auto ,"%")), position=position_dodge(width=0.95), vjust=-0.5)+
scale_x_continuous(breaks = 1:12,labels = c("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"))+
scale_y_continuous(breaks = seq(0.0, 1.0, 0.1))
}) #end tot finalized plot summary
Apart from the fact that you didn't even use the plotly function to create the plot, if you want to generate plotly output you must remember two things:
In server section renderPlotly instead of renderPlot
In UI section plotlyOutput instead of plotOutput
You can try this code to see how it works:
library(shiny)
library(ggplot2)
library(ggthemes)
library(plotly)
ui <- fluidPage(
titlePanel("Plotly"),
sidebarLayout(
sidebarPanel(),
mainPanel(
plotlyOutput("plot2"))
))
server <- function(input, output) {
output$plot2 <- renderPlotly({
ggplotly(
ggplot(data = mtcars, aes(x = disp, y = cyl)) +
geom_smooth(method = lm, formula = y~x) +
geom_point() +
theme_gdocs())
})
}
shinyApp(ui, server)
I'm building a shiny app where I can manipulate a histogram of the diamonds dataset by changing the input to be displayed which works fine. I now want to be able to change the colours of the plot to reflect the cut, clarity and color of the diamonds data.
Code to build app
#Data preparation
library(shiny)
library(tidyverse)
library(ggplot2)
diamonds_data <- as_tibble(diamonds) %>%
rename_all(stringr::str_to_title)
#App design
ui <- fluidPage(
#App title
titlePanel("Histogram version"),
#Sidebar layout for main and sidebar panel
sidebarLayout(
# Sidebar panel for inputs
sidebarPanel(
#Selecting histogram colour
selectInput(inputId="color1",label="Choose Color",choices = c("Color"="Color","Carat"="Carat","Clarity"="Clarity"),
selected = "Cut",multiple = F),
#Selecting Histogram input
selectInput(inputId="channel1",label="Distribution of data",choices = c("Carat"="Carat",
"Depth"="Depth",
"Table"="Table",
"Price"="Price",
"X"="X",
"Y"="Y",
"Z"="Z"),
selected = "Carat",multiple = F),
#Selecting number of histogram bind
sliderInput(inputId = "NOofBins",
label = "Number of bins:",
min = 1,
max = 50,
value = 30),
),
#Main panel for outputs
mainPanel(
#Histogram
plotOutput(outputId = "distPlot")
)
)
)
server <- function(input, output){
output$distPlot <- renderPlot({
if(input$color1=="Clarity"){
Color = "Clarity"
}else if(input$color1=="Cut"){
Color = "Cut"
}else if(input$color1=="Color"){
Color = "Color"
}
my_plot <- diamonds_data %>% ggplot()
if(input$channel1 == "Carat"){
my_plot <- my_plot + geom_histogram(aes(x=Carat),bins = input$NOofBins,fill=Color)
}else if(input$channel1 == "Depth"){
my_plot <- my_plot + geom_histogram(aes(x=Depth),bins = input$NOofBins,fill=Color)
}else if(input$channel1 == "Table"){
my_plot <- my_plot + geom_histogram(aes(x=Table),bins = input$NOofBins,fill=Color)
}else if(input$channel1 == "Price"){
my_plot <- my_plot + geom_histogram(aes(x=Price),bins = input$NOofBins,fill=Color)
}else if(input$channel1 == "X"){
my_plot <- my_plot + geom_histogram(aes(x=X),bins = input$NOofBins,fill=Color)
}else if(input$channel1 == "Y"){
my_plot <- my_plot + geom_histogram(aes(x=Y),bins = input$NOofBins,fill=Color)
}else if(input$channel1 == "Z"){
my_plot <- my_plot + geom_histogram(aes(x=Z),bins = input$NOofBins,fill=Color)
}
my_plot <- my_plot + theme_bw()+
theme(axis.title = element_text(size=26,color="Grey",face="bold"),
axis.text = element_text(size=12,color="Grey",face="bold"))+
labs(x="Diamonds Element",y="Count",title=paste("Distribution of diamonds data",input$channel1,sep = " "))
my_plot
})
}
shinyApp(ui = ui, server = server)
As mentioned I want to be able to alter the input of the histogram where I can change the colours based on the cut, clarity or color as can be seen with the code below:
ggplot(data = diamonds_data, aes(x = Price)) +
geom_histogram(aes(fill = Cut))
When I use my app script I get the warning >Unknown colour name: Color
You can use the .data argument of ggplot which accepts strings as inputs and so significantly simplify your code:
my_plot <- diamonds_data %>%
ggplot() +
geom_histogram(aes(x = .data[[input$channel1]], fill = .data[[input$color1]]), bins = input$NOofBins) +
theme_bw()+
theme(axis.title = element_text(size=26,color="Grey",face="bold"),
axis.text = element_text(size=12,color="Grey",face="bold"))+
labs(x="Diamonds Element",y="Count",title=paste("Distribution of diamonds data",input$channel1,sep = " "))
I am following a solution given in R shiny Aesthetics must be either length 1 or the same as the data (8): y for that annoying problem which I have happily fixed.
The next issue I want to solve is that I want my plot to have a reactive legend - I only want the legend to display what's actually chosen and on the plot
I also want to set the colours of the lines to the ones I want. And finally I want to make sure the legend is always in the order that I specify
Here is a reproducible example (the commented out code is my attempt at solving my own issues.
As you can see, the commented out section is how I've tried to get the legend and colours I want:
library(shiny)
library(tidyverse)
library(reshape2)
library(scales)
time <- seq(-9, 60, 1)
var1 <- rnorm(70, 35, 2)
var2 <- rnorm(70, 50, 2)
var3 <- rnorm(70, 24, 2)
var4 <- rnorm(70, 17, 2)
data <- data.frame(time = time,
var1 = var1,
var2 = var2,
var3 = var3,
var4 = var4)
datamelt <- melt(data, "time")
p <- ggplot(datamelt, aes(x = time, y = value, color = variable)) +
# scale_color_manual(values = c(
# 'first' = 'red',
# 'second' = 'blue',
# 'third' = 'green',
# 'fourth' = 'orange'
# ),
# breaks = c("first", "second", "third", "fourth")) +
# labs(color = 'Legend') +
theme_classic() +
theme(axis.ticks = element_blank()) +
labs(title = 'it means nothing',
subtitle = 'these are made up data') +
theme(plot.title = element_text(hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5)) +
scale_x_continuous(name ="a y variable", breaks = seq(-9, 60, 1)) +
scale_y_continuous(name = "yep an x variable",
breaks = seq(0, 60, 5), labels = comma) + geom_blank()
ui <- fluidPage(
titlePanel("trying to make this work"),
sidebarLayout(
sidebarPanel(
checkboxGroupInput("whichone", "Choose something:",
choiceNames = c("first",
"second",
"third",
"fourth"),
choiceValues = c("var1",
"var2",
"var3",
"var4"))
),
###the plot
mainPanel(
plotOutput("plot")
)
)
)
server <- function(input, output) {
output$plot <- renderPlot({
data_filtered <- datamelt %>% filter(variable %in% input$whichone)
p + geom_line(data = data_filtered)
})
}
shinyApp(ui, server)
The problem occurs because ggplot uses all the factor levels, which stay in place when you filter. So you need to drop these levels first.
Secondly, you are generating the plot statically with all the levels. So you need to update the data in teh plto as well to let ggplot know which levels to show in the legend. Putting that together you can use the following:
server <- function(input, output) {
output$plot <- renderPlot({
## 1. drop unused levels from teh filtered database
data_filtered <- datamelt %>% filter(variable %in% input$whichone) %>%
droplevels()
## 2. tell ggplot to update the data
p %+% data_filtered + geom_line()
})
}
Note. This approach has the nasty (?) side effect, that if you do not select any data to show you see only an empty canvas. This can be also remedied, but would require some change in your code logic (basically moving the construction of the ggplot inside the renderPlot)
Screenshots
I have the following shiny app:
library(shiny)
library(ggplot2)
library(dplyr)
library(networkD3)
library(ggmosaic)
#Loading data
Category <- c("Bankpass", "Bankpass", "Bankpass", "Moving", "Moving")
Subcategory <- c("Stolen", "Lost", "Login", "Address", "New contract")
Weight <- c(10,20,13,40,20)
Duration <- as.character(c(0.2,0.4,0.5,0.44,0.66))
Silence <- as.character(c(0.1,0.3,0.25,0.74,0.26))
df <- data.frame(Category, Subcategory, Weight, Duration, Silence)
ui <- fluidPage(
tags$div(class="header",
selectInput("measure", "", c("Duration", "Silence"))
),
mainPanel(
tags$div(class = "dashboard_main",
tags$div(class="dashboard_main_left", plotOutput("secondPlot"))
)
)
)
server <- function(input, output){
output$secondPlot <- renderPlot({
ggplot(data = df) +
geom_mosaic(aes(weight = Weight, x = product(Category), fill=Duration),
offset = 0, na.rm=TRUE) +
theme(axis.text.x=element_text(angle=-25, hjust= .1)) +
theme(axis.title.x=element_blank()) +
scale_fill_manual(values=c("#e8f5e9", "#c8e6c9", "#a5d6a7", "#81c784", "#66bb6a"))
})
}
shinyApp(ui = ui, server= server)
I would like to make the second plot interactive now. So if you select the Duration the fill in the plot "secondPlot" should be Duration and if you you select "Silence" the fill should be "Silence".
However when I change the relevante code of the graph to:
ggplot(data = df) +
geom_mosaic(aes(weight = Weight, x = product(Category), fill=input$measure),
offset = 0, na.rm=TRUE) +
theme(axis.text.x=element_text(angle=-25, hjust= .1)) +
theme(axis.title.x=element_blank())
I dont see the colour gradients anymore. Any thoughts on what goes wrong here?
You should use aes_string inside geom_mosaic. Try this:
server <- function(input, output){
df$prodcat <- product(df$Category)
output$secondPlot <- renderPlot({
ggplot(data = df) +
geom_mosaic(aes_string(weight = "Weight", x = "prodcat", fill=input$measure),
offset = 0, na.rm=TRUE) +
theme(axis.text.x=element_text(angle=-25, hjust= .1)) +
theme(axis.title.x=element_blank()) +
scale_fill_manual(values=c("#e8f5e9", "#c8e6c9", "#a5d6a7", "#81c784", "#66bb6a"))
})
}
I want to be able to select observations from a box plot with jittered points over top. I have been somewhat successful by having the point click find the category, look at the y-value and select the observation. The following code shows my progress so far:
# ------------------------------Load Libraries---------------------------------
library(shiny)
library(ggplot2)
library(dplyr)
# -------------------------Print Boxplot to Screen-----------------------------
ui <- fluidPage(plotOutput('irisPlot', click = 'irisClick'))
server <- function(input, output){
# --------------------------Store Clicked Points-------------------------------
clicked <- reactiveValues(rows = rep(TRUE,nrow(iris)))
# ---------------------------Modify the Dataset--------------------------------
IRIS <- reactive({iris %>% mutate(index = clicked$rows)})
# ---------------------Select Points Through Plot Click------------------------
observeEvent(
input$irisClick,{
nS <- iris %>% mutate(selected = rep(FALSE,nrow(iris)))
lvls <- levels(iris$Species)
plant <- lvls[round(input$irisClick$x)]
pxl <- which(
sqrt((iris$Sepal.Width-input$irisClick$y)^2) %in%
min(sqrt((iris$Sepal.Width-input$irisClick$y)^2))
)
point <- iris[pxl,'Sepal.Width']
nS[nS$Species == plant & nS$Sepal.Width %in% point,'selected'] <- TRUE
clicked$rows <- xor(clicked$rows, nS$selected)
})
# --------------------------Generate the Boxplot-------------------------------
output$irisPlot <- renderPlot({
set.seed(1)
ggplot(IRIS(), aes(x = Species, y = Sepal.Width))+
geom_boxplot(na.rm = TRUE,outlier.shape = NA)+
geom_jitter(
na.rm = TRUE,
width = .8,
aes(shape = index, size = index, colour = index)
)+
theme(
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank(),
panel.border = element_rect(colour = 'black', fill = NA),
legend.position = "none"
)+
scale_shape_manual(values = c('FALSE'= 1,'TRUE'= 19))+
scale_size_manual(values = c('FALSE' = 4, 'TRUE'= 2))+
scale_colour_manual(values = c('TRUE' = "#428BCA", 'FALSE' = '#FAA634'))
})
}
shinyApp(ui, server)
As I said the code mostly works but it can be inconsistent. Sometimes it can't find a point, other times it selects a large group of points or selects a point on the opposite side of the box plot. I figure the best way to solve this is to have both an x and y coordinate to select the point however, since the x values are randomly generated I need geom_jitter() to tell me what x-values it is using for a given plot but I have not been able to find any way to access this. Any help finding this information would be greatly appreciated.
My thanks to aosmith for telling me about the layer_data() function and to Peter Ellis for suggesting that I use geom_point() instead of geom_jitter() both comments were instrumental in helping me solve my problem.
What I had to do was create a new plot object in the global environment to jitter the points. Then use the layer_data() function to return the newly created x-values.
Finally, using those x-values, I created a new plot object and layered the points over top using geom_point(). Here is the completed code for anyone interested.
# ------------------------------Load Libraries---------------------------------
library(shiny)
library(ggplot2)
library(dplyr)
# ----------------------------Generate X Coords--------------------------------
set.seed(1)
g1 <- ggplot(iris, aes(x = Species, y = Sepal.Width))+
geom_boxplot(na.rm = TRUE,outlier.shape = NA)+
geom_jitter(na.rm = TRUE,width = .8)
xPoints <- layer_data(g1, i = 2)$x
# -------------------------Print Boxplot to Screen-----------------------------
ui <- fluidPage(
plotOutput('irisPlot', click = 'irisClick')
)
server <- function(input, output){
# --------------------------Store Clicked Points-------------------------------
clicked <- reactiveValues(rows = rep(TRUE,nrow(iris)))
rand <- reactiveValues(x = rep(NA,nrow(iris)))
# ---------------------------Modify the Dataset--------------------------------
IRIS <- reactive({iris %>% mutate(index = clicked$rows)})
# ---------------------Select Points Through Plot Click------------------------
observeEvent(
input$irisClick,{
nS <-data.frame( iris, x = xPoints)
point <- nearPoints(
df = nS,
coordinfo = input$irisClick,
xvar = 'x',
yvar = 'Sepal.Width',
allRows = TRUE
)
clicked$rows <- xor(clicked$rows, point$selected_)
})
# --------------------------Generate the Boxplot-------------------------------
output$irisPlot <- renderPlot({
ggplot(IRIS(), aes(x = Species, y = Sepal.Width))+
geom_boxplot(na.rm = TRUE,outlier.shape = NA)+
geom_point(
aes(
x = xPoints,
y = iris$Sepal.Width,
shape = index,
size = index,
colour = index
),
inherit.aes = FALSE
)+
theme(
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank(),
panel.border = element_rect(colour = 'black', fill = NA),
legend.position = "none"
)+
scale_shape_manual(values = c('FALSE'= 1,'TRUE'= 19))+
scale_size_manual(values = c('FALSE' = 4, 'TRUE'= 2))+
scale_colour_manual(values = c('TRUE' = "#428BCA", 'FALSE' = '#FAA634'))
})
output$x <- renderPlot({
})
}
shinyApp(ui, server)
Just for the benefit of people like me who might be googling this problem I solved it very easily using Peter Ellis's suggestion of jittering the points myself using jitter().
I'm making it into an answer because I thought it should be more visible, I nearly missed it when I was looking at this page.