How To Find Two Hop Neighbors In Shiny App
Photo by Clayton Cardinalli on Unsplash
Introduction
At PKGlobal, we had a manufacturing client that wanted a Shiny app for their engineers and plant workers. This Shiny app would allow their employees, none of whom are data scientists or machine learning experts, to use a machine learning algorithm with over 60 inputs to make a prediction. The prediction would allow them to use less material and significantly cut down on costs. (We're talking tens of thousands of dollars a week!)
This posed a challenge though. How do you democratize an ML model to manufacturing workers with so many parameters?
The solution was a Shiny app with an intuitive interface. One that presented the parameters in roughly the same way they would see them on the factory floor and allowed them multiple options for how they inputed those values.
This project wound up using Shiny's amazing strength and flexible functionality. Packages, such as shinyMatrix and plotly, provided the end-user the ability to determine their manufacturing inputs the way they wanted. Specific Shiny functions, such as observeEvent
and reactive
, ensured these methods could communicate with one another without the "wonkiness" that happens with less flexible tools, such as PowerBI or Tableau.
Please note that much of this code is based on online resources, such as Carson Sievert's Stack Overflow comment and Hadley Wickham's Mastering Shiny, as well as package-specific documentation. This article simply ties these resources together to teach you how to use them and why they work.
What Packages and Functions Did This App Use?
Delivering this functionality required the following packages:
install.packages("shiny") install.packages("tidyverse") install.packages("plotly") install.packages("shinyMatrix")
We made heavy use of the functions observeEvent
and reactive
. I suggest reviewing this article on the subject to better understand the concept. You don't need to be an expert on these functions, though. My article below will provide a good demo on how to use them.
Building the UI
Down below, you'll see the "skeleton" of the app we'll build. This will include the packages, data sets, and UI that you'll need. I also include three table outputs that will help illustrate how the app handles the values within the matrixInput
and reactiveValue
functions.
To get started, go into RStudio and create a new Shiny app. Paste the code below into the app.R file of your Shiny app:
app.R
### Load packages library(shiny) library(tidyverse) library(plotly) library(shinyMatrix) ### Define default matrix rateInputs_m <- matrix( c(0, 10, 15, 26, 29, 39, 70, 0.78, 1.05, 1.21, 0.67, 0.61, 0.67, 0.67), nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) ### Define UI ui <- fluidPage( titlePanel("Plotly and Shiny Matrix Input Demonstration"), column( 4, radioButtons( "toggleInputSelect", "Input Method:", choices = c("Drag-and-Drop" = "dragDrop", "Hand Typed" = "handTyped") ), br(), conditionalPanel(condition = "input.toggleInputSelect=='dragDrop'", plotlyOutput("speed_p", height = "250px")), conditionalPanel( condition = "input.toggleInputSelect=='handTyped'", matrixInput( "rateInputs_mi", value = rateInputs_m, class = "numeric", row = list(names = FALSE) ) ) ), column(8, tabsetPanel( id = "tabs", tabPanel( "Algorithm Tab", value = "algorithmOutput", column(3, br(), tags$h4("Original Values"), tableOutput("table1")), column(3, br(), tags$h4("Matix Inputs"), tableOutput("table2")), column(3, br(), tags$h4("Reactive Values"), tableOutput("table3")) ) )) ) ### Define server logic server <- function(input, output, session) { output$table1 <- renderTable({ rateInputs_m }) output$table2 <- renderTable({ input$rateInputs_mi }) output$table3 <- renderTable({ req(rv$time) data.frame(rv$time, rv$speed) }) # Creating Reactive Values rv <- reactiveValues() } ### Run the application shinyApp(ui = ui, server = server)
If you run the app, you may notice that altering the matrix input on the left-hand side only changes the table labeled "Matrix Inputs." The "original" table is the one defined at the beginning of the app.R script. shinyMatrix uses those inputs, but starts to function as its own object once the app runs.
You can see this demonstrated below. Notice that we change the speed in the third row and only the value updates on the "Matrix Input" table on the right.
This is an important distinction to make. We'll end up adding a third object that will operate in the background. Like the shinyMatrix, this will also use the original matrix defined at the beginning of our app, but become independent later.
One other thing I want you to notice are the conditional panels (conditionalPanel
) in the UI script.
conditionalPanel(condition = "input.toggleInputSelect=='dragDrop'", plotlyOutput("speed_p", height = "250px")), conditionalPanel( condition = "input.toggleInputSelect=='handTyped'", matrixInput( "rateInputs_mi", value = rateInputs_m, class = "numeric", row = list(names = FALSE) ) )),
These conditional panels make it possible to toggle back and forth between the two input methods we'll use later in the app.
Building the Plotly Drag-and-Drop Inputs
Now that we have our UI, we can add to our server function. Within this server function, we'll build a set of smaller functions that will coordinate with one another to produce the functionality we want. These functions include reactiveValues
, renderPlotly
, and observeEvent
.
-
reactiveValues
stores a list of values that we'll use as both a source and destination in the other two functions. -
renderPlotly
allows us to display a plotly graph within our application and will use the reactive values as its data. -
observeEvent
observes the end-user as they interact with the application. We will use this function to update the reactive value list, but only if the user updates the matrix input or moves a point on the plotly graph.
If you look below, you can see how these will interact. We store the original matrix inputs in the reactive value function. The user can update either the plotly graph or the matrix input. We'll have two event observation functions that will update the reactive values with the new inputs.
This graph is a somewhat simplistic representation of the steps we'll be programming. But overall, it highlights what we'll be working towards.
Creating the Reactive Values
The first thing we'll add is the reactive value function. I already have an empty reactive function in the app.R script shared earlier. Go ahead and adjust the rv
object to match what you see below.
# Creating Reactive Values rv <- reactiveValues( time=rateInputs_m[,1], speed=rateInputs_m[,2] )
In this function, we pulled the rateInputs_m
matrix defined at the top of our app.R script and assigned each column to its respective name. So column one (rateInputs_m[,1]
) is time and column two (rateInputs_m[,2]
) is speed. The functions we'll add later will call upon this reactive value list, rather than the original rateInputs_m
matrix.
If you run the app, you should see values populate in the "Reactive Values" table on the far right.
These values won't change yet, but it's important to note we now have three matrices in the background: the original matrix, the shinyMatrix, and now these reactive values.
There are a few other nuances I want to point out as well.
First, pay close attention to the parentheses (...)
surrounding the reactiveValues
function in the code above. If you read the documentation (?reactiveValues
), this function operates more like a list.
That means using the ({...})
notation will prevent this reactive value list from working. Instead, we'll need to use (...)
notation.
The second thing is the use of the =
sign rather than <-
to assign object names. Normally, you could assign object names with <-
, but since these are not individual objects per se, but items within a list-like function, we'll need to use the equal =
sign.
The third thing is the use of ,
between each list item. Unlike the reactive functions with a Shiny app, this must be comma-separated.
Creating the renderPlotly Function
Next we'll add our reactive function for plotly. We start with the renderPlotly
function:
# Speed 1's Plot and Table and Feedback output$speed_p <- renderPlotly({ })
We'll need to alter this later, but to start, let's just add the full plot_ly
function to see what it looks like:
# Speed 1's Plot and Table and Feedback output$speed_p <- renderPlotly({ plot_ly() %>% add_lines(x = rv$time, y = rv$speed, color = I("black")) %>% layout( xaxis = list(title = "Time"), yaxis = list(title = "Speed"), showlegend = FALSE ) })
This will plot the reactive values defined earlier. We can see that we add lines for the x-axis and y-axis, which are rv$time
and rv$speed
respectively. We also do some minor formatting items with the layout
function.
Now here's where things start to get tricky. If you remember, we want the end-user to drag-and-drop these plot points to determine how they feed into the algorithm. And we'll need to ensure the reactive value list updates with the new values after they move one of those points.
Let's first add the ability to move the plot points. We can do this with the config
function, as seen below:
# Speed 1's Plot and Table and Feedback output$speed_p <- renderPlotly({ plot_ly() %>% add_lines(x = rv$time, y = rv$speed, color = I("black")) %>% layout( xaxis = list(title = "Time"), yaxis = list(title = "Speed"), showlegend = FALSE ) %>% config(edits = list(shapePosition = TRUE), displayModeBar = FALSE) })
If we try to run the app now, we still wouldn't be able to move the lines on the plot. The reason is that we only allowed for "shapes" to be moved on the plot. We still need to create our shapes.
If you look at the code below, you can see I added a function called map2
. We can include these shapes by adding it to the layout
function near the bottom.
# Speed 1's Plot and Table and Feedback output$speed_p <- renderPlotly({ speed_c <- map2( rv$time, rv$speed, ~ list( type = "circle", xanchor = .x, yanchor = .y, x0 = -4, x1 = 4, y0 = -4, y1 = 4, xsizemode = "pixel", ysizemode = "pixel", fillcolor = "grey", line = list(color = "black") ) ) plot_ly() %>% add_lines(x = rv$time, y = rv$speed, color = I("black")) %>% layout( xaxis = list(title = "Time"), yaxis = list(title = "Speed"), showlegend = FALSE, shapes = speed_c ) %>% config(edits = list(shapePosition = TRUE), displayModeBar = FALSE) })
Within the map2
function, I defined a list of arguments that will create the circles we can move. This then maps those arguments to both rv$time
and rv$speed
, the values from our reactive value list.
By referencing this object in the layout
function near the bottom of this script, we will see those points appear on our Shiny application.
There's one last thing to add to the plot_ly
function before moving forward. We will want to observe interactions on this plot by the end-user. For that reason, we need to give it a "name" that we can reference later. So we'll add the argument source="speed_s"
and you can see that in the final code for this below:
# Speed 1's Plot and Table and Feedback output$speed_p <- renderPlotly({ speed_c <- map2( rv$time, rv$speed, ~ list( type = "circle", xanchor = .x, yanchor = .y, x0 = -4, x1 = 4, y0 = -4, y1 = 4, xsizemode = "pixel", ysizemode = "pixel", fillcolor = "grey", line = list(color = "black") ) ) plot_ly(source = "speed_s") %>% add_lines(x = rv$time, y = rv$speed, color = I("black")) %>% layout( shapes = speed_c, xaxis = list(title = "Time"), yaxis = list(title = "Speed"), showlegend = FALSE ) %>% config(edits = list(shapePosition = TRUE), displayModeBar = FALSE) })
Now let's run the app and move the dots around. The lines don't move with the dots, do they?
That's because the app doesn't know how to respond when we move a plot point. It's not updating the reactive list we made earlier. That's where the observeEvent
function comes in handy.
Creating the observeEvent for Plotly
Within Shiny, we can use either the observeEvent
or observe
functions to see how the end-user interacts with the app. Those functions can then make adjustments to the app, based on what code we add.
As you can imagine, they have a wide-range of uses and they are something I suggest you get very good at doing if you want to build more Shiny apps.
You can use both functions in relatively similar ways, but I like the observeEvent
function myself.
Let's create our observeEvent
function. Take the code below and add it to your server
function in the Shiny app.
observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { })
Now there's two things I want you to pay attention to here. The first is the event_data
function. This is a plotly function and allows us to specify which plotly event we want to observe.
In this example, we want to know whether a "plotly_relayout" event occurred and whether it came from the plotly graph called "speed_s". If you recall, we had named the plotly graph we created earlier as "speed_s".
The second thing I want you to notice is where I placed the event_data
function. I placed it between the first (
and the first {
. By placing the event_data
function at this location and using "speed_s" as the source, the observeEvent
function will proceed if there's an event associated with "speed_s". It will then execute the code enclosed within the {...}
brackets.
Now with that out of the way, we can start filling in the interior of this observeEvent
function. We'll add the same event_data()
as earlier and we'll then sub-select specific values from it.
observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { # Speed 1 Event Data speed_ed <- event_data("plotly_relayout", source = "speed_s") speed_sa <- speed_ed[grepl("^shapes.*anchor$", names(speed_ed))] })
There's two steps here. The first is determining the actual event data. I provide an example of what this looks like below:
The problem with this function is sometimes it will display events unrelated to the ones we want. For example, sometimes it'll record a user adjusting the range on the plot, like you see below:
speed_sa
helps us overcome this problem. It will only includes events with the word "shapes" in its column header. When it does include that word, the values will be the same as the speed_ed
object above it.
After we pull the new shape points, we need to do some additional data transformations. If you recall, we're updating the reactive values. However, there's seven rows we could update. How will Shiny know which row to update?
The good news is that our event data includes a numeric value associated with the shape the user moved. Unfortunately, that value does not correspond to the row number. If you have seven values, like our app does, it will begin the count at zero. So instead of 1 through 7, we have 0 through 6. We can account for this by merely extracting the number from the column name and adding a 1 to it.
observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { # Speed 1 Event Data speed_ed <- event_data("plotly_relayout", source = "speed_s") speed_sa <- speed_ed[grepl("^shapes.*anchor$", names(speed_ed))] speed_ri <- unique(readr::parse_number(names(speed_sa)) + 1) })
We'll also want to pull the new values associated with the new location of the plot points. That's what the speed_pts <- as.numeric(speed_sa)
code added below accomplishes.
observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { # Speed 1 Event Data speed_ed <- event_data("plotly_relayout", source = "speed_s") speed_sa <- speed_ed[grepl("^shapes.*anchor$", names(speed_ed))] speed_ri <- unique(readr::parse_number(names(speed_sa)) + 1) speed_pts <- as.numeric(speed_sa) })
Now we get to the fun part! We'll need to include some extra logic to take into account some of the rules we want in place.
The first rule is that we want to re-sort the dot plots. The second is to ensure that the x-axis value for the first and last point are always 0 and 70, respectively.
While there are probably several ways to implement these two rules, I found it easier to make a matrix to serve as a temporary home for our values. I could then alter this matrix the way I needed to ensure these rules stayed in place and limit the amount of "wonkiness" in the application.
To start this process, look at the additional lines of code I added below. You'll see the new matrix called "temp_matrix." All this does is record the values from the reactive values list and puts it in the temporary matrix.
observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { # Speed 1 Event Data speed_ed <- event_data("plotly_relayout", source = "speed_s") speed_sa <- speed_ed[grepl("^shapes.*anchor$", names(speed_ed))] speed_ri <- unique(readr::parse_number(names(speed_sa)) + 1) speed_pts <- as.numeric(speed_sa) # Speed 1 Point Updates temp_matrix <- matrix( c(round(rv$time, 2), round(rv$speed, 2)), nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[speed_ri, 1] <- round(speed_pts[1], 2) temp_matrix[speed_ri, 2] <- round(speed_pts[2], 2) })
We then update the temporary matrix with the new values. We use "speed_ri", which is the "row index," to determine the proper row to update. We then use speed_pts[1]
and speed_pts[2]
to update the temporary matrix values with their new values for the x and y axis.
Next, we want to re-sort the values. Remember, these are time values and we want them sorted in order of seconds. If we don't, the model inputs may be in the incorrect order. And we also want to ensure the first and last value are 0 and 70, respectively.
observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { # Speed 1 Event Data speed_ed <- event_data("plotly_relayout", source = "speed_s") speed_sa <- speed_ed[grepl("^shapes.*anchor$", names(speed_ed))] speed_ri <- unique(readr::parse_number(names(speed_sa)) + 1) speed_pts <- as.numeric(speed_sa) # Speed 1 Point Updates temp_matrix <- matrix( c(round(rv$time, 2), round(rv$speed, 2)), nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[speed_ri, 1] <- round(speed_pts[1], 2) temp_matrix[speed_ri, 2] <- round(speed_pts[2], 2) temp_matrix <- temp_matrix[order(temp_matrix[, 1], decreasing = FALSE), ] temp_matrix[1, 1] <- 0 temp_matrix[7, 1] <- 70 })
After we do that, we want to update the reactive value list with these new values found in the temporary matrix.
observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { # Speed 1 Event Data speed_ed <- event_data("plotly_relayout", source = "speed_s") speed_sa <- speed_ed[grepl("^shapes.*anchor$", names(speed_ed))] speed_ri <- unique(readr::parse_number(names(speed_sa)) + 1) speed_pts <- as.numeric(speed_sa) # Speed 1 Point Updates temp_matrix <- matrix( c(round(rv$time, 2), round(rv$speed, 2)), nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[speed_ri, 1] <- round(speed_pts[1], 2) temp_matrix[speed_ri, 2] <- round(speed_pts[2], 2) temp_matrix <- temp_matrix[order(temp_matrix[, 1], decreasing = FALSE), ] temp_matrix[1, 1] <- 0 temp_matrix[7, 1] <- 70 # Update reactive values rv$time <- round(temp_matrix[, 1], 2) rv$speed <- round(temp_matrix[, 2], 2) })
With this code in place, we should now be able to move the dots on the plotly graph. Go ahead and try on your app!
Pay close attention to which tables on the right are changing. If you'll notice, only the reactive value list is changing.
Building the shinyMatrix Inputs
If you recall, we wanted to provide our end-user the ability to toggle back and forth between the drag-and-drop plot and the ability to hand type their inputs.
So if they want to visualize how speed changes overtime, they can use the plotly graph. If they want to input precise values, they can hand type them.
Fortunately, there's a great package out there called shinyMatrix that allows this.
So how do we add this?
Well, if you look at the UI script, you'll notice that I had included an input called matrixInput
.
matrixInput( "rateInputs_mi", value = rateInputs_m, class = "numeric", row = list(names = FALSE) )
This function actually works pretty good on its own, but we need to do more. Currently, the new values you can type into the matrix only apply to itself. If we run the app, we'll notice that the reactive values (which is what the plotly graph uses) does not update. And rules, such as the first and last value must equal 0 and 70, do not apply either.
We'll need to add an additional observeEvent()
function to update the reactive value list, similar to how we did the plotly relayout.
observeEvent(req(input$rateInputs_mi & input$toggleInputSelect == "handTyped"), { })
We will add a Boolean statement to this event observation between the first (
and the first {
. Much like our other event observation function, this will only continue in certain instances. The user will need to select both the "Hand Type" option in the UI and change the matrix input for this function to continue.
It may not be clear now why this is important, but later on, we'll be adding to our earlier observeEvent
function for the "plotly_relayout" events. We want to prevent these two event observations from competing with one another. Adding these statements between the (
and {
ensures these functions only proceeds at the right moment.
Now let's actually add our code!
This one is a lot easier to write. The shinyMatrix package makes it a relatively straight forward process to make updates.
First, we'll take all the values in the rateInputs_mi
matrix (the shinyMatrix input in the UI) and assign it to a "temp matrix." Please note, it is important to include the full matrix code below for it to register with the plotly graph. This is done for the same reasons as we did with the plotly. We want to ensure the end-user follows the rules required for the algorithm input.
observeEvent(req(input$rateInputs_mi & input$toggleInputSelect == "handTyped"), { temp_matrix <- matrix( input$rateInputs_mi, nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[1, 1] <- 0 temp_matrix[7, 1] <- 70 temp_matrix <- temp_matrix[order(temp_matrix[, 1], decreasing = FALSE), ] })
As you can see in the code above, we force the first and last value to be 0 and 70, respectively. We also re-order the speed values from least to highest.
Next, we'll update the reactive value list.
observeEvent(req(input$rateInputs_mi & input$toggleInputSelect == "handTyped"), { temp_matrix <- matrix( input$rateInputs_mi, nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[1, 1] <- 0 temp_matrix[7, 1] <- 70 temp_matrix <- temp_matrix[order(temp_matrix[, 1], decreasing = FALSE), ] rv$time <- temp_matrix[, 1] rv$speed <- temp_matrix[, 2] })
But there's one more thing we need to do.
Normally with shinyMatrix, we don't need to worry about updating the shinyMatrix with new values. It usually does that itself. But in this case, the end-user may have entered 71 for the last time value. If you look at the script above, we changed that value back to 70. That changes only applies to the reactive value list though! We need to make sure the shinyMatrix updates too.
As seen in the GIF below, we are able to change the first and last value to something other than 0 and 70. I can also change the order of the values. While the reactive list on the far right changes, the table for the shinyMatrix does not. You can replicate this yourself in the app currently.
To fix this, we update the matrix input with the reactive values. You can see this with the updateMatrixInput
function below:
observeEvent(req(input$rateInputs_mi & input$toggleInputSelect == "handTyped"), { temp_matrix <- matrix( input$rateInputs_mi, nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[1, 1] <- 0 temp_matrix[7, 1] <- 70 temp_matrix <- temp_matrix[order(temp_matrix[, 1], decreasing = FALSE), ] rv$time <- temp_matrix[, 1] rv$speed <- temp_matrix[, 2] updateMatrixInput(session, "rateInputs_mi", temp_matrix) })
Now try the same experiment before! It works right? If you look below, you can see that we can no longer "break the rules". The matrix corrects itself.
But we're still not done! We still need to ensure the plotly graph talks back to the matrixInput and vice versa!
How to Make These Inputs Options Work Together
When running your earlier experiment, you may have noticed the plotly graph doesn't seem to change the matrix input.
We had programmed the second event observation function to update the plotly graph on the first tab. We did not program it to work the other way… yet!
This is an easy change to make. All you need to do is add the same updateMatrixInput
function to your observeEvent()
function for the plotly graph. Here's the full code below:
observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { # Speed 1 Event Data speed_ed <- event_data("plotly_relayout", source = "speed_s") speed_sa <- speed_ed[grepl("^shapes.*anchor$", names(speed_ed))] speed_ri <- unique(readr::parse_number(names(speed_sa)) + 1) speed_pts <- as.numeric(speed_sa) # Speed 1 Point Updates temp_matrix <- matrix( c(round(rv$time, 2), round(rv$speed, 2)), nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[speed_ri, 1] <- round(speed_pts[1], 2) temp_matrix[speed_ri, 2] <- round(speed_pts[2], 2) temp_matrix <- temp_matrix[order(temp_matrix[, 1], decreasing = FALSE), ] temp_matrix[1, 1] <- 0 temp_matrix[7, 1] <- 70 # Update reactive values rv$time <- round(temp_matrix[, 1], 2) rv$speed <- round(temp_matrix[, 2], 2) updateMatrixInput(session, "rateInputs_mi", temp_matrix) })
And with that last addition, these two input methods should work together. Give your app a preview and see if that's the case!
The complete app should look something like this:
Final app.R
### Load packages library(shiny) library(tidyverse) library(plotly) library(shinyMatrix) ### Define default matrix rateInputs_m <- matrix( c(0, 10, 15, 26, 29, 39, 70, 0.78, 1.05, 1.21, 0.67, 0.61, 0.67, 0.67), nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) ### Define UI ui <- fluidPage( titlePanel("Plotly and Shiny Matrix Input Demonstration"), column( 4, radioButtons( "toggleInputSelect", "Input Method:", choices = c("Drag-and-Drop" = "dragDrop", "Hand Typed" = "handTyped") ), br(), conditionalPanel(condition = "input.toggleInputSelect=='dragDrop'", plotlyOutput("speed_p", height = "250px")), conditionalPanel( condition = "input.toggleInputSelect=='handTyped'", matrixInput( "rateInputs_mi", value = rateInputs_m, class = "numeric", row = list(names = FALSE) ) ) ), column(8, tabsetPanel( id = "tabs", tabPanel( "Algorithm Tab", value = "algorithmOutput", column(3, br(), tags$h4("Original Values"), tableOutput("table1")), column(3, br(), tags$h4("Matix Inputs"), tableOutput("table2")), column(3, br(), tags$h4("Reactive Values"), tableOutput("table3")) ) )) ) ### Define server logic server <- function(input, output, session) { output$table1 <- renderTable({ rateInputs_m }) output$table2 <- renderTable({ input$rateInputs_mi }) output$table3 <- renderTable({ req(rv$time) data.frame(rv$time, rv$speed) }) # Creating Reactive Values rv <- reactiveValues(time = rateInputs_m[, 1], speed = rateInputs_m[, 2]) # Speed 1's Plot and Table and Feedback output$speed_p <- renderPlotly({ speed_c <- map2( rv$time, rv$speed, ~ list( type = "circle", xanchor = .x, yanchor = .y, x0 = -4, x1 = 4, y0 = -4, y1 = 4, xsizemode = "pixel", ysizemode = "pixel", fillcolor = "grey", line = list(color = "black") ) ) plot_ly(source = "speed_s") %>% add_lines(x = rv$time, y = rv$speed, color = I("black")) %>% layout( shapes = speed_c, xaxis = list(title = "Time"), yaxis = list(title = "Speed"), showlegend = FALSE ) %>% config(edits = list(shapePosition = TRUE), displayModeBar = FALSE) }) observeEvent(event_data(event = "plotly_relayout", source = "speed_s"), { # Speed 1 Event Data speed_ed <- event_data("plotly_relayout", source = "speed_s") speed_sa <- speed_ed[grepl("^shapes.*anchor$", names(speed_ed))] speed_ri <- unique(readr::parse_number(names(speed_sa)) + 1) speed_pts <- as.numeric(speed_sa) # Speed 1 Point Updates temp_matrix <- matrix( c(round(rv$time, 2), round(rv$speed, 2)), nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[speed_ri, 1] <- round(speed_pts[1], 2) temp_matrix[speed_ri, 2] <- round(speed_pts[2], 2) temp_matrix <- temp_matrix[order(temp_matrix[, 1], decreasing = FALSE), ] temp_matrix[1, 1] <- 0 temp_matrix[7, 1] <- 70 # Update reactive values rv$time <- round(temp_matrix[, 1], 2) rv$speed <- round(temp_matrix[, 2], 2) updateMatrixInput(session, "rateInputs_mi", temp_matrix) }) observeEvent(req(input$rateInputs_mi & input$toggleInputSelect == "handTyped"), { temp_matrix <- matrix( input$rateInputs_mi, nrow = 7, ncol = 2, dimnames = list(NULL, c("Time", "Speed")) ) temp_matrix[1, 1] <- 0 temp_matrix[7, 1] <- 70 temp_matrix <- temp_matrix[order(temp_matrix[, 1], decreasing = FALSE), ] rv$time <- temp_matrix[, 1] rv$speed <- temp_matrix[, 2] updateMatrixInput(session, "rateInputs_mi", temp_matrix) }) } ### Run the application shinyApp(ui = ui, server = server)
Things to Remember
If there's one thing I hope you learned from this tutorial, it's that Shiny apps are powerful tools. Data scientists can build applications that allow engineers, manufacturers, and plant workers to use machine learning to improve results and save money. Shiny provides endless ways to provide this service in a flexible and intuitive way for the end user. The only hard part is the programming. Learn to invest the time in learning observation and reactive functions. Find new and novel ways to use existing packages, such as plotly and shinyMatrix, to build something cool.
How To Find Two Hop Neighbors In Shiny App
Source: https://www.rstudio.com/blog/how-to-use-shinymatrix-and-plotly-graphs/
Posted by: clyburnnotle1973.blogspot.com
0 Response to "How To Find Two Hop Neighbors In Shiny App"
Post a Comment