Skip to content

Try to create a dynamic UI web-based IOL power calculator using ShinyLive and Quarto  #153

@XinshenFD

Description

@XinshenFD

I tried to migrate the intraocular lens power calculator I previously created along with some logo images from Shiny Server to Shinylive, which does not require a server, and attempted to configure it on GitHub:

Here is my main code:

yml:

project:
type: website
output-dir: _site

format:
html:
grid:
body-width: 1300px
theme: lumen
filters:

  • shinylive


title: "Risk Stratification and IOL Power Calculation Tool for Marfan Syndrome Patients with Ectopia Lentis"
author: Xinshen
date: 2025/05/17
date-format: long

filters:

  • shinylive

Next comes the main part of the calculator.

#| standalone: true
#| viewerHeight: 2000
library(bslib)
library(shiny)
library(gt)
library(tidyverse)
library(magick)
library(shinyjs)
library(DT)
library(ggplot2)

#####IOL power calculation
****************
# Define UI for application that draws a histogram

gender_female_male <- c("Female", "Male")
eye_side <- c("Right", "Left")
procedures <- c("MCTR", "CTR-CH", "SF-IOL")

ui <- page_fixed(
  useShinyjs(),
  tags$style(    "
    html, body {
    max-width: 1300px; 
    margin: auto; 
    width: 100%; 
    }
    "),
  layout_columns(
    col_widths = c(4, 8),
    card(
     
      full_screen = TRUE,
      imageOutput("img1_ui",, inline = TRUE),
      style = "margin-top: 10px; display: flex; justify-content: center; align-items: center;"
    ),
    card_body(
     
      HTML(
        '<div style="text-align: justify; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 0 15px;">
            <p style="text-indent: 20px; font-size: 18px; line-height: 1.6;">
              *****************
            </p>
          </div>'
      )
    )
  )
  ,
  card(
    full_screen = TRUE,
    card_body(
      fluidRow(
        column(4, textInput("name", label = span("Patient's name", class = "label custom-input"), value = "")),
        column(4, dateInput("dob", label = span("Patient's birthday", class = "label custom-input"), value = "1993-12-22")),
        column(4, selectInput("Gender", label = span("Patient's gender", class = "label custom-input"), gender_female_male))
      ),
      fluidRow(
        column(4, selectInput("eyeside", label = span("Laterality",  class = "label custom-input"), eye_side)),
        column(4, textInput("surger", label = span("Doctor's name",  class = "label custom-input"), value = "")),
        column(4, dateInput("Sob", label = span("Surgery date",  class = "label custom-input"), value = "2025-5-15"))
      ),
      fluidRow(
        column(4, numericInput("AL", label = span("AL (mm)",  class = "label custom-input"), value = "24.03", step = 0.01)),
        column(4, numericInput("WTW", label = span("WTW (mm)",  class = "label custom-input"), value = "12.32", step = 0.01)),
        column(4, numericInput("APEX", label = span("APEX (µm)",  class = "label custom-input"), value = "553",step = 1))
      ),
      fluidRow(
        column(4, numericInput("K1", label = span("K1 (D)",  class = "label custom-input"), value = "41.21",step = 0.01)),
        column(4, numericInput("K2", label = span("K2 (D)",  class = "label custom-input"), value = "42.33",step = 0.01)),
        column(4, numericInput("ref", label = span("Target refraction (D)",  class = "label custom-input"), value = "-1",step = 0.01))
      ),
      fluidRow(
        column(4,offset = 2, selectInput("procedure",label = span( "Procedure",  class = "label custom-input"),choices = c("MCTR", "CTR-CH",
                                                                                                                           "SF-IOL"))),
        column(4, selectInput("IOL_type",label = span( "IOL",  class = "label custom-input"), 
                              choices = c("DCB00/ICB00/ZCB00", "SN60AT/SN60WF","Rayner 920H/970C"))
        )
      )))
  ,
  card(
    actionButton("cal", label = span("Extimate cluster and calculate IOL power",class = "label custom-input"), class = "btn-success",
                 icon = shiny::icon("calculator"))
  ),
  tags$div(
    id = "plot_div",
    style = "display:none;",
    
    layout_columns(
      col_widths = c(4, 8),
      card(
        div(
          style = "display: flex; justify-content: center; align-items: center; height: 100%;",
          uiOutput("words_cluster")
        )
      ),
      card(
        div(
          style = "margin-top: 10px; display: flex; justify-content: center; align-items: center;",  
          imageOutput("PCO_sur", inline = TRUE)
        )
      )
    )
  ),
  tags$div(
    style = "display: flex; justify-content: center; align-items: center; height: 100%;",
    uiOutput("conditional_layout")
  )
)

server <- function(input, output, session) {

  rdata_file1 <- tempfile(fileext = ".png")
  correct_url1 <- "https://raw.githubusercontent.com/XinshenFD/data-save/main/sup1.png"
  download.file(correct_url1, destfile = rdata_file1, mode = "wb")
  
  rdata_file2 <- tempfile(fileext = ".png")
  correct_url2 <- "https://raw.githubusercontent.com/XinshenFD/data-save/main/Logo-Department.png"
  download.file(correct_url2, destfile = rdata_file2, mode = "wb")
  
  rdata_file3 <- tempfile(fileext = ".png")
  correct_url3 <- "https://raw.githubusercontent.com/XinshenFD/data-save/main/sup2.png"
  download.file(correct_url3, destfile = rdata_file3, mode = "wb")
  
  
  observeEvent(input$procedure, {
    if (input$procedure %in% c("MCTR", "CTR-CH")) {
      # If the first input is A or B, update second input to D and E
      updateSelectInput(session, "IOL_type", choices = c("DCB00/ICB00/ZCB00", "SN60AT/SN60WF"))
    } else if (input$procedure == "SF-IOL") {
      # If the first input is C, update second input to F
      updateSelectInput(session, "IOL_type", choices = "Rayner 920H/970C")
    }
  })
  observeEvent(input$cal, {
    
    shinyjs::show(id = "plot_div")
  })
  
  
  calculateValues <- eventReactive(input$cal, {
    
    if(input$Gender == "Female") {
      gg <- 0
    } else {
      gg <- 1
    }
    today <- Sys.Date()
    dob <- as.Date(input$dob)
    surgery_date <- as.Date(input$Sob)
    aa <- as.numeric(difftime(surgery_date, dob, units = "days")) / 365.25
    ccrz <- 337.5/(2/(1/input$K1 + 1/input$K2))
    # calculate
    ***********
  ******************
  output$conditional_layout <- renderUI({
    values <- calculateValues()
    aa <- values$aa
    clus <- values$clus
    
    if (aa > 15 || clus %in% c("C", "D")) {
      div(
        style = "width: 100%; max-width: 1300px; margin: 0 auto;", # 外层容器最大1300px
        card(
          style = "width: 100%; max-width: 1300px; margin: 0 auto;", # card充满容器
          fluidRow(
            div(
              style = "width: 600px; margin: 0 auto;", # 固定内容宽度600px
              uiOutput("words")
            )
          ),
          fluidRow(
            div(
              style = "width: 600px; margin: 0 auto;", # 固定内容宽度600px
              DTOutput("table")
            )
          )
        )
      )
    } else {
      div(
        style = "width: 100%; max-width: 1300px; margin: 0 auto; margin-top: 20px;",
        card(
          fluidRow(
            div(
              style = "width: 600px; margin: 0 auto;",
              uiOutput("words")
            )
          ),
          fluidRow(
            column(
              width = 6,
              div(
                style = "width: 550px;", # 表格区域固定宽度
                DTOutput("table")
              )
            ),
            column(
              width = 6,
              imageOutput("cluster_image")  # 动态显示图片
            )
          )
        )
      )
    }
  })
  
  output$cluster_image <- renderImage({
    
    list(
      src = rdata_file3,
      contentType = "image/png", 
      style = "width:100%; height:auto;"
    )
  }, deleteFile = FALSE)
  
  output$words_cluster <- renderUI({
    clus <- calculateValues()$clus
    description <- switch(clus,
                         ************************    )
    HTML(description)
  })
  
  # PCO_sur 输出逻辑
  output$PCO_sur <- -renderImage({
    list(
      src = rdata_file1,
      contentType = "image/png",
      style = "width:100%; height:auto;"
    )
  }, deleteFile = FALSE)
  
  output$img1_ui <-renderImage({
    list(
      src = rdata_file2,
      contentType = "image/png",
      style = "width:100%; height:auto;"
    )
  }, deleteFile = FALSE)
  
  
}

shinyApp(ui = ui, server = server)


This Shiny app has successfully run and has been uploaded to the server (https://xinshen.shinyapps.io/MFS_EL_cluster/), so I believe this Shiny app is usable. However, when I rendered it, I found that the images could not load, and clicking the action button in Shinylive had no response.

Image Image

I thought the issue was with the way I was loading the images, so I added a Shinylive instance. I found that the way I was loading the images is indeed feasible.

#| standalone: true
#| viewerHeight: 200

library(shiny)
library(ggplot2)
library(shinyjs)

ui <- fluidPage(
  useShinyjs(),
  actionButton("cal", "Estimate IOL Power"),
  imageOutput("img1_ui"),
  imageOutput("img2_ui"),
  imageOutput("img3_ui"),
  uiOutput("words")
)

server <- function(input, output, session) {
  rdata_file1 <- tempfile(fileext = ".png")
  correct_url1 <- "https://raw.githubusercontent.com/XinshenFD/data-save/main/sup1.png"
  download.file(correct_url1, destfile = rdata_file1, mode = "wb")
  
  rdata_file2 <- tempfile(fileext = ".png")
  correct_url2 <- "https://raw.githubusercontent.com/XinshenFD/data-save/main/Logo-Department.png"
  download.file(correct_url2, destfile = rdata_file2, mode = "wb")
  
  rdata_file3 <- tempfile(fileext = ".png")
  correct_url3 <- "https://raw.githubusercontent.com/XinshenFD/data-save/main/sup2.png"
  download.file(correct_url3, destfile = rdata_file3, mode = "wb")
  

  output$img1_ui <- renderImage({
    list(
      src = rdata_file1,
      contentType = "image/png",
      style = "width:100%; height:auto;"
    )
  }, deleteFile = FALSE)
  output$img2_ui <- renderImage({
    list(
      src = rdata_file2,
      contentType = "image/png",
      style = "width:100%; height:auto;"
    )
  }, deleteFile = FALSE)
  output$img3_ui <- renderImage({
    list(
      src = rdata_file3,
      contentType = "image/png",
      style = "width:100%; height:auto;"
    )
  }, deleteFile = FALSE)
  
  observeEvent(input$cal, {
    shinyjs::show("img1_ui")
    output$words <- renderUI({
      HTML("<b>Calculation completed!</b>")
    })
  })
}

shinyApp(ui = ui, server = server)
Image

My complete qmd document is at: https/raw.githubusercontent.com/XinshenFD/website/refs/heads/main/index.qmd

I want to know whether it is Shinylive that cannot achieve my goal or if there is an issue with my code.

Thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions