bslib: input_task_button 소개

bslib의 0.7.0에서 새롭게 추가된 input_task_button에 대해 소개합니다.

R
shiny
bslib
ux
Author
Published

March 30, 2024

bslib

bslib이란 bootstrap의 css를 R에서 사용할 수 있도록 만든 패키지입니다. 정확한 원문 설명은 Tools for theming Shiny and R Markdown via Bootstrap 3, 4, or 5. 으로, Shiny와 Rmarkdown (당연히 Quarto를 포함)에서 여러 테마를 활용할 수 있게 합니다.

이 글에서는 bslib의 활용 방법들중 Shiny에 집중하여 설명합니다.

사실 Shiny는 기본적으로 디자인을 위해 bootstrap을 사용합니다. 그런데 Shiny는 패키지를 이루고 있는 구성 요소들과, 관계가 너무 복잡하게 얽혀 있는 상당히 무거운 패키지가 되어버렸고 이로 인해 업데이트에 영향을 받는 부분이 많아, 기능 위주의 업데이트를 하는 것으로 알려져 있습니다.

즉, UI를 주로 다루는 bootstrap 부분은 5년 전의 버전인 3.4.1 버전을 사용하고 있고, 별도의 테마 설정을 하지 않는다면 특유의 파랑 / 회색 테마를 기본적으로 사용하게 됩니다.(최근 버전은 5.3.3)

그래서 shiny에서는 정체된 UI를 업데이트 하기 위해 UI를 다루는 별도의 R 패키지를 만들어 덮어 씌우듯 최근 bootstrap의 기능들을 제공하게 됩니다.

이 글에서는 bslib의 주요 사용법은 다루지 않습니다.

actionButton

Shiny에서 제공하는 기능들은 정말 다양하지만, 핵심 기능을 꼽으라면 actionButton을 꼽을 수 있습니다.

actionButton이란 사용자가 버튼을 누르면 server에서 미리 선언한 특정 동작을 수행하도록 하는 기능으로 보통은 사용자가 데이터를 업로드 하고 나면, 이 데이터를 활용해 계산 결과를 만들어내게 하는 것에 쓰입니다.

actionButton의 사용 예시로는 아래의 코드와 같이 (?shiny::actionButton으로 확인할 수 있습니다) 사용자의 선택한 관측수에 맞는 히스토그램을 그릴 수 있게 합니다.

library(shiny)

ui <- fluidPage(
  sliderInput("obs", "Number of observations", 0, 1000, 500),
  actionButton("goButton", "Go!", class = "btn-success"),
  plotOutput("distPlot")
)

server <- function(input, output) {
  output$distPlot <- renderPlot({
    input$goButton
    dist <- isolate(rnorm(input$obs))
    hist(dist)
  })
}

shinyApp(ui, server)

long actionButton

그런데 이 actionButton의 문제점 중 하나는 만약 연산에 시간이 오래걸린다면 사용자는 버튼을 누르고 결과를 기다리는 동안 아무것도 할 수 없다는 것입니다.

심지어 단순히 아무것도 할 수 없는 것을 넘어, 버튼이 클릭되지 않은 것으로 오해하고 버튼을 여러번 클릭하기도 합니다.

만약 대용량 유전체 데이터를 활용한 계산을 위한 Shiny라면 연산 한번에 분 단위 시간이 필요할 수도 있는데 이는 여러가지 문제점을 초래할 수 있습니다.

특히 여러번 클릭을 했다면 오랜 시간을 거쳐 연산을 마친 직후 다시 동일한 연산을 또 하고, 또 기다리고, … 의 악순환에 빠지기도 합니다.

shiny with loading

actionButton의 이 문제를 해결하기 위해 여러 방법들이 존재했습니다.

  1. progress indicator를 사용

이 방법은 shiny에서 기본적으로 제공하는 Progress Indicator UI를 활용하는 방법으로 연산의 과정 / 단계가 진행됨에 따라 진행 정도를 사용자에게 보여줄 수 있습니다.

server <- function(input, output) {
  output$plot <- renderPlot({
    input$goPlot 

    dat <- data.frame(x = numeric(0), y = numeric(0))

    withProgress(message = 'Making plot', value = 0, {
      n <- 10

      for (i in 1:n) {
        dat <- rbind(dat, data.frame(x = rnorm(1), y = rnorm(1)))
        incProgress(1/n, detail = paste("Doing part", i))
        Sys.sleep(0.1)
      }
    })

    plot(dat$x, dat$y)
  })
}

ui <- shinyUI(basicPage(
  plotOutput('plot', width = "300px", height = "300px"),
  actionButton('goPlot', 'Go plot')
))

shinyApp(ui = ui, server = server)

그러나 이는 withProgress, incProgress 또는 Progress등의 함수와 오브젝트를 시간이 오래걸리는 연산에 추가로 코드를 작성해야한다는 단점이 있습니다.

  1. 별도의 R 패키지 사용

R의 생태계에는 해결하려는 여러 문제가 있고, 그 문제마다의 R 패키지가 있다고 생각해도 과언이 아닌데요. actionButton과 연산 결과 사이의 긴 공백을 UI에 표기하기 위한 기능 또한 마찬가지입니다.

이전의 progress indicator와 유사하게 추가 코드를 작성하여 해결해야하며 조금 더 디자인이나 세부 설정을 할 수 있는 커스텀 기능이 있다고 생각하면 좋습니다.

아래는 몇가지 예시 패키지와 사례입니다 (알파벳순).

input_task_button

input_task_button은 위의 방법들과는 다르게 actionButton을 확장한 기능으로, actionButton을 누르면 연산이 진행중임을 알리며 버튼이 비활성화 되며, 연산이 끝나면 다시 버튼이 활성화되는 기능을 제공합니다.

무엇보다도 가장 큰 차이점은 actionButton을 대체할 수 있기에 추가 코드를 사용할 필요가 없다는 것입니다.

library(shiny)

ui <- fluidPage(
  sliderInput("obs", "Number of observations", 0, 1000, 500),
  # actionButton("goButton", "Go!", class = "btn-success"),
  input_task_button("goButton", "Go!", type = "success"),
  plotOutput("distPlot")
)

server <- function(input, output) {
  output$distPlot <- renderPlot({
    input$goButton
    Sys.sleep(5)
    dist <- isolate(rnorm(input$obs))
    hist(dist)
  })
}

shinyApp(ui, server)

input_task_button의 사용을 위해 5번째 줄의 actionButton을 6번째 줄의 input_task_button으로 대체하였고, 추가로 의도적으로 오래 걸리는 연산을 만들기 위해 13번째 줄의 Sys.sleep() 코드를 활용하여 5초를 지연시켰습니다. input_task_button 사용 방법은 다음과 같습니다.

input_task_buttonactionButton을 무리없이 대체할 수 있지만 약간의 parameter 수정이 필요합니다.

actionButton input_task_button 역할
inputId id 버튼 id
label label 버튼 라벨
icon icon 버튼 아이콘
label_busy 버튼 비활성화시 라벨
icon_busy 버튼 비활성화시 아이콘
class type 버튼 테마 / 색상

정리

이번 글에서는 bslib의 최신 기능인 input_task_button과 간단한 사용 사례를 소개했습니다.

이는 잠재적으로 사용자 경험을 향상시키는 기능이며, 기존의 shiny에서는 추가 코드를 작성해야하는 번거로움을 해결해주는 기능이라고 생각합니다.

특히 다른 웹 어플리케이션과 다르게 Shiny에서는 대용량 데이터 연산으로 인해 시간이 오래 걸리는 경우가 많지만, 상대적으로 빈약한 UI/UX를 가지고 있어 기존의 actionButtoninput_task_button으로 대체할 경우 이를 보완하는데 큰 도움이 될 것입니다.

이번 글이 도움이 되었길 바라며, 다음 글에서 또 만나요!

Reuse

Citation

BibTeX citation:
@online{kim2024,
  author = {Kim, Jinhwan},
  title = {Bslib: Input\_task\_button {소개}},
  date = {2024-03-30},
  url = {https://blog.zarathu.com/posts/2024-03-30-input-task-button},
  langid = {en}
}
For attribution, please cite this work as:
Kim, Jinhwan. 2024. “Bslib: Input_task_button 소개.” March 30, 2024. https://blog.zarathu.com/posts/2024-03-30-input-task-button.