I am trying to create an application where a user selection determines the number of plots to be placed into a carousel. I have a MWE below, where a user "selects" anywhere between 1-10 of the lines in the parallel coordinate plot on the left. After doing so, on the right, 1-10 plots are created (one for each of the lines the user selected). This all seems to be working, and the dynamic number of plots are stored in a tagList() object.
With larger datasets, the number of lines the user can select can be large and the output plots can look crowded. Hence, I am trying to put the output plots into a carousel. Currently, I have all output plots in a carousel - but they are all shoved into the first page of the carousel.
I would be grateful to hear any advice on how I can tweak this MWE so that each page of the carousel only contains one of the output plots.
library(shiny)
library(plotly)
library(data.table)
library(dplyr)
library(tidyr)
library(bsplus)
ui <- shinyUI(pageWithSidebar(
headerPanel("Dynamic number of plots"),
sidebarPanel(
plotlyOutput("plot")
),
mainPanel(
# This is the dynamic UI for the plots
bs_carousel(id = "tabPrev", use_indicators = TRUE) %>%
bs_append(content = uiOutput("plots"))
)
)
)
server <- shinyServer(function(input, output) {
set.seed(1)
dat <- data.frame(ID = paste0("ID",1:10), A.1 = runif(10), A.2 = runif(10), A.3 = runif(10), B.1 = runif(10), B.2 = runif(10), B.3 = runif(10))
dat$ID <- as.character(dat$ID)
# Convert DF from scatterplot to PCP
datt <- data.frame(t(dat))
names(datt) <- as.matrix(datt[1, ])
datt <- datt[-1, ]
datt[] <- lapply(datt, function(x) type.convert(as.character(x)))
setDT(datt, keep.rownames = TRUE)[]
colnames(datt)[1] <- "x"
dat_long <- melt(datt, id.vars ="x" )
dat_long <- separate(dat_long, x, c("group", "rep"), remove=FALSE)
dat_long$group <- factor(dat_long$group)
output$plot <- renderPlotly({
plot_ly(dat_long, x= ~x, y= ~value, type = 'scatter', mode = 'lines+markers', color = ~variable) %>% layout(dragmode="box", showlegend = FALSE)
})
d <- reactive(event_data("plotly_selected"))
observeEvent(d(),{
# Insert the right number of plot output objects into the web page
output$plots <- renderUI({
lengthY <- reactive((length(unique(d()$curveNumber))))
if (lengthY()<1){
plot_output_list <- list()
}
else{
plot_output_list <- lapply(1:lengthY(), function(i) {
plotname <- paste("plot", i, sep="")
plotlyOutput(plotname, height = 280, width = 250)
})
}
# Convert the list to a tagList - this is necessary for the list of items
# to display properly.
do.call(tagList, plot_output_list)
})
})
# Call renderPlot for each one. Plots are only actually generated when they
# are visible on the web page.
observeEvent(d(),{
lengthY <- reactive(length(unique(d()$curveNumber)))
for (i in 1:lengthY()) {
# Need local so that each item gets its own number. Without it, the value
# of i in the renderPlot() will be the same across all instances, because
# of when the expression is evaluated.
local({
my_i <- i
curveY <- reactive(d()$curveNumber[my_i])
plotname <- paste("plot", my_i, sep="")
ax <- list(title = "", showticklabels = TRUE)
ay <- list(title = "Read Count")
indDat <- as.data.frame(dat_long[variable %in% dat[curveY()+1,]$ID])
g1 <- levels(indDat$group)[1]
g2 <- levels(indDat$group)[2]
g1m <- mean(filter(indDat, group==g1)$value)
g2m <- mean(filter(indDat, group==g2)$value)
output[[plotname]] <- renderPlotly({
indDat %>% plot_ly(x = ~group, y = ~value, type = "scatter", marker = list(size = 10), color = ~group, colors = "Set2", hoverinfo = "text", text = paste0("Read count = ", format(round(indDat$value, 2), nsmall = 2))) %>% layout(xaxis = ax, yaxis = ay, legend = list(x = 0.35, y = -0.26)) %>% add_segments(x = g1, xend = g2, y = g1m, yend = g2m, showlegend = FALSE, line = list(color='#000000')) %>% add_trace(x = g1, y= g1m, showlegend = FALSE, hoverinfo = "text", text = paste0("Mean Read Count = ", round(g1m, digits = 2)), marker = list(color='#000000')) %>% add_trace(x = g2, y= g2m, showlegend = FALSE, hoverinfo = "text", text = paste0("Mean Read Count = ", round(g2m, digits = 2)), marker = list(color='#000000'))
})
})
}
})
})
shinyApp(ui, server)
The way I would do this is embedding the bs_carousel inside the renderUI. It does work but I couldn't manage to remove the plots object totally, which sometimes plots... If I remove it, only the first plot appears in the carousel.
1- Change the ui to :
ui <- shinyUI(pageWithSidebar(
headerPanel("Dynamic number of plots"),
sidebarPanel(
plotlyOutput("plot")
),
mainPanel(
uiOutput("car_ui"),
uiOutput("plots")
)
)
)
2- Add this code in the first observeEvent, just above the output$plots
output$car_ui <- renderUI({
lengthY <- length(unique(d()$curveNumber))
if (lengthY<1){
plot_output_list <- list()
}
else{
plot_output_list <- lapply(1:lengthY, function(i) {
plotname <- paste("plot", i, sep="")
plotlyOutput(plotname, height = 280, width = 250)
})
}
car <- bs_carousel(id = "carousel", use_indicators = TRUE)
Reduce(bs_append, plot_output_list, init=car)
})
Also note you don't have to put all your calculations (lengthY...) in a reactive
Related
I want to build a shiny app that counts the number of clicks I make on any image, but I don't know how to make the counter increase, it just plots the number 1
I tried to create loops inside renderPlot but it doesn't work.
It is necessary to change the path of the files to a directory that contains .jpg images
library(shiny)
ui <- fluidPage(
titlePanel("Click Count"),
sidebarPanel(selectInput("IMAGE", "Sample image:",
list.files(path = "~",
pattern = ".jpg",
full.names = TRUE,
include.dirs = FALSE))),
fluidRow(
plotOutput("IMG", click = "countClick", "100%", "500px")
),
verbatimTextOutput("info")
)
server <- function(input, output, session){
# Creating a reactive variable that recognizes the selected image
img <- reactive({
f <- input$IMAGE
imager::load.image(f)
})
# Creating a spot where i can store reactive values
initX <- 1
initY <- 2
source_coords <- reactiveValues(xy = c(x=initX,y=initY))
# Coords
dest_coords <- reactiveValues(x=initX, y = initY)
observeEvent(plot_click(),{
dest_coords$x <- c(dest_coords$x, floor(plot_click()$x))
dest_coords$y <- c(dest_coords$y, floor(plot_click()$y))
})
plot_click <- debounce(reactive(input$countClick), 300)
output$IMG <- renderPlot({
plot(img(), axes = FALSE)
n <- 0
ex <- expression(n+1)
text(dest_coords$x, dest_coords$y,eval(ex),cex = 1 ,col = 'red')
})
output$info <- renderPrint({
req(input$countClick)
x <- round(input$countClick$x,2)
y <- round(input$countClick$y,2)
cat("[", x, ", ", y, "]", sep = "")
})
}
shinyApp(ui, server)
countClick is not a good name because input$countClick does not contain the numbers of clicks.
Not tested:
numberOfClicks <- reactiveVal(0)
dest_coords <- reactiveValues(x = initX, y = initY)
observeEvent(plot_click(),{
numberOfClicks(numberOfClicks() + 1)
dest_coords$x <- c(dest_coords$x, floor(plot_click()$x))
dest_coords$y <- c(dest_coords$y, floor(plot_click()$y))
})
plot_click <- debounce(reactive(input$countClick), 300)
output$IMG <- renderPlot({
plot(img(), axes = FALSE)
n <- numberOfClicks()
text(dest_coords$x, dest_coords$y, n, cex = 1 ,col = 'red')
})
In Code1 below I am trying to create a draggable plot using the plotly package. The user should be able to drag the points of the plot and capture the new points in the data frame rendered to the left called "Data1". When running the code I get the error "Warning: Error in <-: invalid (NULL) left side of assignment". What am I doing wrong?
As an FYI, Code2 below does just this but using a different data set, though both are structured the same. In running Code2, I compare the data frame that works in Code2 (called "Data") with the data frame that does not work in Code1 ("Data1") to show how similarly the two data frames are in structure. Drag the plotted data points in Code2 and see how nicely the "Data" table to the left updates. This is what I'm trying to get at in Code1, but instead by using Data1 data.
Solution spoiler: see ismirsehregal answer below. The difference between Code1 and Code2, where Code1 fails and Code2 doesn't, is due to the inappropriate use of reactive() in defining the data1() dataframe in Code1. Since data1() is modified from different places (sliderInput(), the drag feauture in plotly), reactiveVal() or reactiveValues() must be used and not reactive() in defining the dataframe. Also note the use of reactiveValuesToList() in rendering the modified dataframe after dragging a plot point.
Code1:
library(plotly)
library(purrr)
library(shiny)
ui <- fluidPage(
fluidRow(column(5,sliderInput('periods','Nbr of periods:',min=0,max=24,value=12))),
fluidRow(column(2,h5("Data1:"),tableOutput('data1')),
column(6, plotlyOutput("p")))
)
server <- function(input, output, session) {
data1 <- reactive({
data.frame(
x = c(1:input$periods),
y = c((0.15-0.70)*(exp(-50/100*(1:input$periods))-exp(-50/100*input$periods)*
(1:input$periods)/input$periods)) + 0.70
)
})
output$p <- renderPlotly({
circles <- map2(data1()$x, data1()$y,
~list(type = "circle",
xanchor = .x,
yanchor = .y,
x0 = -4, x1 = 4,
y0 = -4, y1 = 4,
xsizemode = "pixel",
ysizemode = "pixel",
fillcolor = "blue",
line = list(color = "transparent"))
)
plot_ly() %>%
add_lines(x = data1()$x, y = data1()$y, color = I("red")) %>%
layout(shapes = circles) %>%
config(edits = list(shapePosition = TRUE))
})
output$data1 <- renderTable(data1())
# update x/y reactive values in response to changes in shape anchors
observe({
ed <- event_data("plotly_relayout")
shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
if (length(shape_anchors) != 2) return()
row_index <- unique(readr::parse_number(names(shape_anchors)) + 1)
pts <- as.numeric(shape_anchors)
data1()$x[row_index] <- pts[1]
data1()$y[row_index] <- pts[2]
})
}
shinyApp(ui, server)
Code2:
library(plotly)
library(purrr)
library(shiny)
ui <- fluidPage(
fluidRow(column(5,sliderInput('periods','Nbr of periods:',min=0,max=24,value=12))),
fluidRow(
column(2,h5(strong(("Data:"))),tableOutput('data')),
column(2,h5(strong(("Data1:"))),tableOutput('data1')),
column(6,h5(strong(("Move the points and see how `Data` table to left updates:"))), plotlyOutput("p")),
),
fluidRow(h5(strong(("Data1 above shown for comparison purposes, would like to substitute Data with Data1 in the plot"))))
)
server <- function(input, output, session) {
rv <- reactiveValues( x = mtcars$mpg,y = mtcars$wt)
data <- reactive(data.frame(x=(rv$x_sub),y=(rv$y_sub)))
data1 <- reactive({
data.frame(
x = c(1:input$periods),
y = c((0.15-0.70) * (exp(-50/100*(1:input$periods))-
exp(-50/100*input$periods)*(1:input$periods)/input$periods)) + 0.70
)
})
observe({
rv$x_sub <- rv$x[1:input$periods]
rv$y_sub <- rv$y[1:input$periods]
})
output$p <- renderPlotly({
circles <- map2(rv$x_sub, rv$y_sub,
~list(
type = "circle",
xanchor = .x,
yanchor = .y,
x0 = -4, x1 = 4,
y0 = -4, y1 = 4,
xsizemode = "pixel",
ysizemode = "pixel",
fillcolor = "blue",
line = list(color = "transparent")
)
)
plot_ly() %>%
add_lines(x = rv$x_sub, y = rv$y_sub, color = I("red")) %>%
layout(shapes = circles) %>%
config(edits = list(shapePosition = TRUE))
})
output$data <- renderTable(data())
output$data1 <- renderTable(data1())
# update x/y reactive values in response to changes in shape anchors
observe({
ed <- event_data("plotly_relayout")
shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
if (length(shape_anchors) != 2) return()
row_index <- unique(readr::parse_number(names(shape_anchors)) + 1)
pts <- as.numeric(shape_anchors)
rv$x[row_index] <- pts[1]
rv$y[row_index] <- pts[2]
})
}
shinyApp(ui, server)
Unfortunately you can't modify a reactive in multiple places. For this use case reactiveVal or reactiveValues are intended.
Please check the following:
library(plotly)
library(purrr)
library(shiny)
ui <- fluidPage(
fluidRow(column(5,sliderInput('periods','Nbr of periods:',min=0,max=24,value=12))),
fluidRow(column(2,h5("Data1:"),tableOutput('data1table')),
column(6, plotlyOutput("p")))
)
server <- function(input, output, session) {
data1 <- reactiveValues(x = NULL, y = NULL)
observe({
data1$x <- c(1:input$periods)
data1$y <- c((0.15-0.70)*(exp(-50/100*(1:input$periods))-exp(-50/100*input$periods)*
(1:input$periods)/input$periods)) + 0.70
})
output$p <- renderPlotly({
circles <- map2(data1$x, data1$y,
~list(type = "circle",
xanchor = .x,
yanchor = .y,
x0 = -4, x1 = 4,
y0 = -4, y1 = 4,
xsizemode = "pixel",
ysizemode = "pixel",
fillcolor = "blue",
line = list(color = "transparent"))
)
plot_ly() %>%
add_lines(x = data1$x, y = data1$y, color = I("red")) %>%
layout(shapes = circles) %>%
config(edits = list(shapePosition = TRUE))
})
output$data1table <- renderTable({
as.data.frame(reactiveValuesToList(data1))
})
# update x/y reactive values in response to changes in shape anchors
observe({
ed <- event_data("plotly_relayout")
shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
if (length(shape_anchors) != 2) return()
row_index <- unique(readr::parse_number(names(shape_anchors)) + 1)
pts <- as.numeric(shape_anchors)
data1$x[row_index] <- pts[1]
data1$y[row_index] <- pts[2]
})
}
shinyApp(ui, server)
This shiny app has some radio buttons to see whether the plotly object needs to have a log scale. The textOutput verifies that the reactive function is following the changes in the input, yet the layout does not change.
Could anyone help?
library(tidyverse)
library(plotly)
c1 <- c(1,2,3,4,5)
c2 <- c(6,3,4,6,5)
c3 <- c(1,2,3,4,5)
df<- data.frame(c1,c2,c3)
cols <- names(df)
ui <- fluidPage(
titlePanel("Log Test"),
sidebarLayout(
sidebarPanel(
selectInput("x",
"x-axis",
cols),
selectInput("y",
"y-axis",
cols),
radioButtons("rb", "Log Axis", choiceNames = list("X", "Y", "Both", "None"), choiceValues = list("X", "Y", "Both", "None"))
),
mainPanel(
plotlyOutput("plot"),
textOutput("note")
)
)
)
server <- function(input, output, session) {
x <- reactive({
df[,input$x]
})
y <- reactive({
df[,input$y]
})
logsc <- reactive({
if (input$rb=='X'){
list('log','linear')
}else if (input$rb=='Y'){
list('linear','log')
}else if (input$rb=='Both'){
list('log','log')
}else{
list('linear','linear')
}
})
output$plot <- renderPlotly(
{
plot1 <- plot_ly(
x = x(),
y = y(),
type = 'scatter',
mode = 'markers',
)
plot1 <- layout(plot1, xaxis = list(type = logsc()[1], ticks ='inside'),yaxis = list(type = logsc()[2], ticks = 'inside'))
plot1
}
)
output$note <- renderText({
paste0("rb ", logsc()[1],"-", logsc()[2])
})
}
shinyApp(ui = ui, server = server)
As you checked it, reactive works fine. Your issue is with logsc() value (of type list) and being subset with single bracket (like a vector).
Single bracket subsetting of a list returns a list containing one item:
> list(1,2,3)[2]
[[1]]
[1] 2
Double bracket subsetting of a list returns a single item of the list
> list(1,2,3)[[2]]
[1] 2
You have been fooled by paste that unlisted you list
To fix your code you can write the call to layout() this way:
plot1 <- layout(plot1,
xaxis = list(type = logsc()[[1]],
ticks ='inside'),
yaxis = list(type = logsc()[[2]],
ticks = 'inside'))
Thanks for your help in advance as this one is really driving me mad. I am trying to create a plotly scatterplot where I can change the location of single plots by dragging them, thus changing the regression line. Importantly, I would like to filter the data through a pickerInput, to only run the analysis for a subset of the data.
Most things are working, however I am coming unstuck with my use of reactiveValues(). More, specifically, I believe reactiveValues() can't take a reactive dataframe...in this case a filtered version of mtcars. I have tried all sorts of things and am now getting a little desperate. Below is the code. I have also attached code of a simplified version of the code, which works just fine however doesn't have the all important filtering capability.
Please help!
library(plotly)
library(purrr)
library(shiny)
ui = navbarPage(windowTitle="Draggable Plot",
tabPanel(title = "Draggable Plot",
sidebarPanel(width = 2,
pickerInput("Cylinders","Select Cylinders",
choices = unique(mtcars$cyl), options = list(`actions-box` = TRUE),multiple = FALSE, selected = unique(mtcars$cyl))),
mainPanel(
plotlyOutput("p", height = "500px", width = "1000px"),verbatimTextOutput("summary"))))
server <- function(input, output, session) {
data = reactive({
data = mtcars
data <- data[data$cyl %in% input$Cylinders,]
return(data)
})
rv <- reactiveValues(
data = data()
x = data$mpg,
y = data$wt
)
grid <- reactive({
data.frame(x = seq(min(rv$x), max(rv$x), length = 10))
})
model <- reactive({
d <- data.frame(x = rv$x, y = rv$y)
lm(y ~ x, d)
})
output$p <- renderPlotly({
# creates a list of circle shapes from x/y data
circles <- map2(rv$x, rv$y,
~list(
type = "circle",
# anchor circles at (mpg, wt)
xanchor = .x,
yanchor = .y,
# give each circle a 2 pixel diameter
x0 = -4, x1 = 4,
y0 = -4, y1 = 4,
xsizemode = "pixel",
ysizemode = "pixel",
# other visual properties
fillcolor = "blue",
line = list(color = "transparent")
)
)
# plot the shapes and fitted line
plot_ly() %>%
add_lines(x = grid()$x, y = predict(model(), grid()), color = I("red")) %>%
layout(shapes = circles) %>%
config(edits = list(shapePosition = TRUE))
})
output$summary <- renderPrint({a
summary(model())
})
# update x/y reactive values in response to changes in shape anchors
observe({
ed <- event_data("plotly_relayout")
shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
if (length(shape_anchors) != 2) return()
row_index <- unique(readr::parse_number(names(shape_anchors)) + 1)
pts <- as.numeric(shape_anchors)
rv$x[row_index] <- pts[1]
rv$y[row_index] <- pts[2]
})
}
shinyApp(ui, server)
Just to add insult to injury, this version of the code without filtering works just fine.
library(plotly)
library(purrr)
library(shiny)
ui = navbarPage(windowTitle="Draggable Plot",
tabPanel(title = "Draggable Plot",
mainPanel(
plotlyOutput("p", height = "500px", width = "1000px"))))
server <- function(input, output, session) {
rv <- reactiveValues(
x = mtcars$mpg,
y = mtcars$wt
)
grid <- reactive({
data.frame(x = seq(min(rv$x), max(rv$x), length = 10))
})
model <- reactive({
d <- data.frame(x = rv$x, y = rv$y)
lm(y ~ x, d)
})
output$p <- renderPlotly({
# creates a list of circle shapes from x/y data
circles <- map2(rv$x, rv$y,
~list(
type = "circle",
# anchor circles at (mpg, wt)
xanchor = .x,
yanchor = .y,
# give each circle a 2 pixel diameter
x0 = -4, x1 = 4,
y0 = -4, y1 = 4,
xsizemode = "pixel",
ysizemode = "pixel",
# other visual properties
fillcolor = "blue",
line = list(color = "transparent")
)
)
# plot the shapes and fitted line
plot_ly() %>%
add_lines(x = grid()$x, y = predict(model(), grid()), color = I("red")) %>%
layout(shapes = circles) %>%
config(edits = list(shapePosition = TRUE))
})
output$summary <- renderPrint({a
summary(model())
})
# update x/y reactive values in response to changes in shape anchors
observe({
ed <- event_data("plotly_relayout")
shape_anchors <- ed[grepl("^shapes.*anchor$", names(ed))]
if (length(shape_anchors) != 2) return()
row_index <- unique(readr::parse_number(names(shape_anchors)) + 1)
pts <- as.numeric(shape_anchors)
rv$x[row_index] <- pts[1]
rv$y[row_index] <- pts[2]
})
}
shinyApp(ui, server)
The following should address your concerns.
rv <- reactiveValues()
observe({
rv$data = data()
rv$x = data()$mpg
rv$y = data()$wt
})
When I select options in my Shiny application, I expect it to produce a graph in plotly and it does. It look like this:
When I unselect the options, it SHOULD look like this:
However, the output is misbehaving in a way I don't understand why. This is output I get:
As you can see, the x and y axis still remains. If I hover over the plot with no options selected, the plot still shows the coordinates.
Here is my code:
# UI
box(width = NULL, uiOutput("plotTitle"),
plotlyOutput('plot1', height="730px"), collapsed = F, title = "Figure",
status = "warning", solidHeader = T, height = "830px")
# SERVER
output$plot1 <- renderPlotly({
validate(
need(input$crops, "INSTRUCTIONS: \n\n1) Select Crops Of Interest.\n2)
Use 'Farm' tab to refine results.\n3) Hover over graph to see values.")
)
crop.data <- reactive({
crop.list <- input$crops
z.df <- dat
c.df <- subset(z.df, Crop %in% crop.list)
c.df <- unique(c.df)
})
output$plotTitle <- renderUI({
if(length(crop.data()) > 0) {
div(style='background-color:green; color:white; text-align:center;
font-size:22px; font-family:"Open Sans",verdana,arial,sans-serif',
input$metric)
}
})
# s.df is the Dataframe
s.df <- subset(s.df, Crop %in% input$crops)
if(length(crop.data()) > 0) {
if(input$metric == "Percentage") {
p <- plot_ly(s.df, x = ~Farm, y = ~Percentage, color = ~Crop) %>%
layout(barmode = 'stack', xaxis = list(title = ''), margin = list(b = 140))
} else if(input$metric == "Acreage") {
p <- plot_ly(s.df, x = ~Farm, y = ~Acreage, color = ~Crop) %>%
layout(barmode = 'stack', xaxis = list(title = ''), margin = list(b = 140))
} else {
p <- plot_ly(s.df, x = ~Farm, y = ~Exposure_Costs, color = ~Crop) %>%
layout(barmode = 'stack', xaxis = list(title = ''),
yaxis = list(title = 'Exposure Costs'), margin = list(b = 140))
}
p
}
})
Is there a specific reason as to why this happens? Also, this error doesn't exist on my local instance but occurs on the shiny server and another's local instance. How do I fix it?
You're using length(crop.data()) to check whether or not to plot but this will return a value greater than 0 for an "empty" data.frame because it is counting the number of columns.
e.g.
> (x <- iris[0, ])
[1] Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<0 rows> (or 0-length row.names)
> length(x)
[1] 5
You really should untangle your mangling of the reactives, renderUI and renderPlotly but replacing length(crop.data()) with nrow(crop.data()) should solve your problem.
This reproducible example shows that your desired behavior does occur for a simple example:
library("shiny")
library("plotly")
set.seed(100)
d <- diamonds[sample(nrow(diamonds), 1000), ]
ui <- fluidPage(
titlePanel("Null Plotly Object"),
sidebarLayout(
sidebarPanel(
checkboxGroupInput("cuts", "Cut", choices = sort(unique(d$cut)))
),
mainPanel(
plotlyOutput("distPlot")
)
)
)
server <- function(input, output) {
plot_dat <- reactive({
shiny::validate(
need(input$cuts, "Select a cut")
)
d[d$cut %in% input$cuts, ]
})
output$distPlot <- renderPlotly({
plot_ly(plot_dat(), x = ~carat, y = ~price, color = ~carat, type = "scatter",
mode = "markers", size = ~carat, text = ~paste("Clarity: ", clarity))
})
}
shinyApp(ui = ui, server = server)