library(shiny) library(shinydashboard) library(shinyWidgets) # Constants for calculations AVERAGE_BASE_PAIR_WEIGHT <- 650.0 SODIUM_COUNTERION_WEIGHT <- 23.0 WATER_MOLECULE_WEIGHT <- 18.0 BP_TO_NM <- 0.34 # 1 bp = 0.34 nm # Helper functions for calculations calculateMolecularWeight <- function(bpLength) { baseWeight <- bpLength * AVERAGE_BASE_PAIR_WEIGHT counterIonWeight <- bpLength * 2 * SODIUM_COUNTERION_WEIGHT waterWeight <- bpLength * WATER_MOLECULE_WEIGHT totalMolecularWeight <- baseWeight + counterIonWeight + waterWeight steps <- paste( "Molecular Weight Calculation:", paste0("Base Pair Weight = ", bpLength, " × ", AVERAGE_BASE_PAIR_WEIGHT, " = ", baseWeight, " g/mol"), paste0("Sodium Counterion Weight = ", bpLength, " × 2 × ", SODIUM_COUNTERION_WEIGHT, " = ", counterIonWeight, " g/mol"), paste0("Water Molecule Weight = ", bpLength, " × ", WATER_MOLECULE_WEIGHT, " = ", waterWeight, " g/mol"), paste0("Total Molecular Weight = ", baseWeight, " + ", counterIonWeight, " + ", waterWeight, " = ", totalMolecularWeight, " g/mol"), sep = "\n" ) return(list(mw = totalMolecularWeight, steps = steps)) } formatMoles <- function(moles) { if (moles >= 1) { return(paste0(round(moles), " mol")) } else if (moles >= 1e-3) { return(paste0(round(moles * 1e3), " mmol")) } else if (moles >= 1e-6) { return(paste0(round(moles * 1e6), " µmol")) } else if (moles >= 1e-9) { return(paste0(round(moles * 1e9), " nmol")) } else { return(paste0(round(moles * 1e12), " pmol")) } } formatWeight <- function(weight) { if (weight >= 1) { return(paste0(round(weight), " g")) } else if (weight >= 1e-3) { return(paste0(round(weight * 1e3), " mg")) } else if (weight >= 1e-6) { return(paste0(round(weight * 1e6), " µg")) } else if (weight >= 1e-9) { return(paste0(round(weight * 1e9), " ng")) } else { return(paste0(round(weight * 1e12), " pg")) } } # Conversion factors for units weightUnitFactors <- list( "g" = 1.0, "mg" = 1e-3, "µg" = 1e-6, "ng" = 1e-9, "pg" = 1e-12 ) moleUnitFactors <- list( "mol" = 1.0, "mmol" = 1e-3, "µmol" = 1e-6, "nmol" = 1e-9, "pmol" = 1e-12 ) volumeUnitFactors <- list( "L" = 1.0, "mL" = 1e-3, "µL" = 1e-6 ) molarityUnitFactors <- list( "M" = 1.0, "mM" = 1e-3, "µM" = 1e-6, "nM" = 1e-9, "pM" = 1e-12 ) # UI definition ui <- dashboardPage( dashboardHeader(title = "DNA Molarity Calculator"), dashboardSidebar( sidebarMenu( menuItem("Absorbance", tabName = "absorbance", icon = icon("flask")), menuItem("Weight ↔ Moles", tabName = "weightMoles", icon = icon("balance-scale")), menuItem("Weight ↔ Molarity", tabName = "weightMolarity", icon = icon("flask-vial")), menuItem("Length Conversion", tabName = "lengthConversion", icon = icon("ruler")) ), actionButton("resetAll", "Reset All", icon = icon("refresh"), style = "color: white; background-color: #3c8dbc; width: 80%; margin: 10px;") ), dashboardBody( tabItems( # Absorbance Tab tabItem(tabName = "absorbance", fluidRow( box( title = "DNA Length", status = "primary", solidHeader = TRUE, width = 12, numericInput("dnaLength", "Enter DNA length (bp)", value = NULL, min = 1) ) ), fluidRow( box( title = "Absorbance to Concentration", status = "primary", solidHeader = TRUE, width = 12, numericInput("absorbanceInput", "Enter A260", value = NULL, min = 0), numericInput("dilutionFactor", "Dilution factor", value = 1, min = 1), actionButton("calcAbsorbance", "Calculate", style = "color: white; background-color: #3c8dbc;"), conditionalPanel( condition = "output.absorbanceResult", tags$div( tags$h4("Result"), verbatimTextOutput("absorbanceResult"), tags$h4("Steps"), verbatimTextOutput("absorbanceSteps") ) ) ) ) ), # Weight ↔ Moles Tab tabItem(tabName = "weightMoles", fluidRow( box( title = "DNA Length", status = "primary", solidHeader = TRUE, width = 12, numericInput("dnaLengthWM", "Enter DNA length (bp)", value = NULL, min = 1) ) ), fluidRow( box( title = "Weight to Moles", status = "primary", solidHeader = TRUE, width = 6, numericInput("weightToMoleInput", "Enter weight", value = NULL, min = 0), selectInput("weightToMoleUnit", "Weight Unit", choices = c("g", "mg", "µg", "ng", "pg"), selected = "g"), actionButton("calcWeightToMole", "Calculate", style = "color: white; background-color: #3c8dbc;"), conditionalPanel( condition = "output.weightToMoleResult", tags$div( tags$h4("Result"), verbatimTextOutput("weightToMoleResult"), tags$h4("Steps"), verbatimTextOutput("weightToMoleSteps") ) ) ), box( title = "Moles to Weight", status = "primary", solidHeader = TRUE, width = 6, numericInput("moleToWeightInput", "Enter moles", value = NULL, min = 0), selectInput("moleToWeightUnit", "Mole Unit", choices = c("mol", "mmol", "µmol", "nmol", "pmol"), selected = "mol"), actionButton("calcMoleToWeight", "Calculate", style = "color: white; background-color: #3c8dbc;"), conditionalPanel( condition = "output.moleToWeightResult", tags$div( tags$h4("Result"), verbatimTextOutput("moleToWeightResult"), tags$h4("Steps"), verbatimTextOutput("moleToWeightSteps") ) ) ) ) ), # Weight ↔ Molarity Tab tabItem(tabName = "weightMolarity", fluidRow( box( title = "DNA Length", status = "primary", solidHeader = TRUE, width = 12, numericInput("dnaLengthWMol", "Enter DNA length (bp)", value = NULL, min = 1) ) ), fluidRow( box( title = "Weight to Molarity", status = "primary", solidHeader = TRUE, width = 6, numericInput("weightToMolarityWeight", "Enter weight", value = NULL, min = 0), selectInput("weightToMolarityWeightUnit", "Weight Unit", choices = c("g", "mg", "µg", "ng", "pg"), selected = "g"), numericInput("weightToMolarityVolume", "Enter volume", value = NULL, min = 0), selectInput("weightToMolarityVolumeUnit", "Volume Unit", choices = c("L", "mL", "µL"), selected = "L"), actionButton("calcWeightToMolarity", "Calculate", style = "color: white; background-color: #3c8dbc;"), conditionalPanel( condition = "output.weightToMolarityResult", tags$div( tags$h4("Result"), verbatimTextOutput("weightToMolarityResult"), tags$h4("Steps"), verbatimTextOutput("weightToMolaritySteps") ) ) ), box( title = "Molarity to Weight", status = "primary", solidHeader = TRUE, width = 6, numericInput("molarityToWeightMolarity", "Enter molarity", value = NULL, min = 0), selectInput("molarityToWeightMolarityUnit", "Molarity Unit", choices = c("M", "mM", "µM", "nM", "pM"), selected = "M"), numericInput("molarityToWeightVolume", "Enter volume", value = NULL, min = 0), selectInput("molarityToWeightVolumeUnit", "Volume Unit", choices = c("L", "mL", "µL"), selected = "L"), actionButton("calcMolarityToWeight", "Calculate", style = "color: white; background-color: #3c8dbc;"), conditionalPanel( condition = "output.molarityToWeightResult", tags$div( tags$h4("Result"), verbatimTextOutput("molarityToWeightResult"), tags$h4("Steps"), verbatimTextOutput("molarityToWeightSteps") ) ) ) ) ), # Length Conversion Tab tabItem(tabName = "lengthConversion", fluidRow( box( title = "DNA Length", status = "primary", solidHeader = TRUE, width = 12, numericInput("dnaLengthLC", "Enter DNA length (bp)", value = NULL, min = 1) ) ), fluidRow( box( title = "Base Pairs to Nanometers", status = "primary", solidHeader = TRUE, width = 6, numericInput("bpToNmInput", "Enter length in bp", value = NULL, min = 0), actionButton("calcBpToNm", "Calculate", style = "color: white; background-color: #3c8dbc;"), conditionalPanel( condition = "output.bpToNmResult", tags$div( tags$h4("Result"), verbatimTextOutput("bpToNmResult"), tags$h4("Steps"), verbatimTextOutput("bpToNmSteps") ) ) ), box( title = "Nanometers to Base Pairs", status = "primary", solidHeader = TRUE, width = 6, numericInput("nmToBpInput", "Enter length in nm", value = NULL, min = 0), actionButton("calcNmToBp", "Calculate", style = "color: white; background-color: #3c8dbc;"), conditionalPanel( condition = "output.nmToBpResult", tags$div( tags$h4("Result"), verbatimTextOutput("nmToBpResult"), tags$h4("Steps"), verbatimTextOutput("nmToBpSteps") ) ) ) ) ) ) ) ) # Server logic server <- function(input, output, session) { # Create a reactive value to store and share DNA length across tabs dnaLengthVal <- reactiveVal(NULL) # Synchronize DNA length across all tabs observeEvent(input$dnaLength, { if(!is.null(input$dnaLength) && !is.na(input$dnaLength)) { dnaLengthVal(input$dnaLength) updateNumericInput(session, "dnaLengthWM", value = input$dnaLength) updateNumericInput(session, "dnaLengthWMol", value = input$dnaLength) updateNumericInput(session, "dnaLengthLC", value = input$dnaLength) } }) observeEvent(input$dnaLengthWM, { if(!is.null(input$dnaLengthWM) && !is.na(input$dnaLengthWM)) { dnaLengthVal(input$dnaLengthWM) updateNumericInput(session, "dnaLength", value = input$dnaLengthWM) updateNumericInput(session, "dnaLengthWMol", value = input$dnaLengthWM) updateNumericInput(session, "dnaLengthLC", value = input$dnaLengthWM) } }) observeEvent(input$dnaLengthWMol, { if(!is.null(input$dnaLengthWMol) && !is.na(input$dnaLengthWMol)) { dnaLengthVal(input$dnaLengthWMol) updateNumericInput(session, "dnaLength", value = input$dnaLengthWMol) updateNumericInput(session, "dnaLengthWM", value = input$dnaLengthWMol) updateNumericInput(session, "dnaLengthLC", value = input$dnaLengthWMol) } }) observeEvent(input$dnaLengthLC, { if(!is.null(input$dnaLengthLC) && !is.na(input$dnaLengthLC)) { dnaLengthVal(input$dnaLengthLC) updateNumericInput(session, "dnaLength", value = input$dnaLengthLC) updateNumericInput(session, "dnaLengthWM", value = input$dnaLengthLC) updateNumericInput(session, "dnaLengthWMol", value = input$dnaLengthLC) } }) # Reset all inputs and outputs observeEvent(input$resetAll, { # Reset all DNA length inputs updateNumericInput(session, "dnaLength", value = NULL) updateNumericInput(session, "dnaLengthWM", value = NULL) updateNumericInput(session, "dnaLengthWMol", value = NULL) updateNumericInput(session, "dnaLengthLC", value = NULL) dnaLengthVal(NULL) updateNumericInput(session, "absorbanceInput", value = NULL) updateNumericInput(session, "dilutionFactor", value = 1) updateNumericInput(session, "weightToMoleInput", value = NULL) updateNumericInput(session, "moleToWeightInput", value = NULL) updateNumericInput(session, "weightToMolarityWeight", value = NULL) updateNumericInput(session, "weightToMolarityVolume", value = NULL) updateNumericInput(session, "molarityToWeightMolarity", value = NULL) updateNumericInput(session, "molarityToWeightVolume", value = NULL) updateNumericInput(session, "bpToNmInput", value = NULL) updateNumericInput(session, "nmToBpInput", value = NULL) # Clear all outputs output$absorbanceResult <- renderText(NULL) output$absorbanceSteps <- renderText(NULL) output$weightToMoleResult <- renderText(NULL) output$weightToMoleSteps <- renderText(NULL) output$moleToWeightResult <- renderText(NULL) output$moleToWeightSteps <- renderText(NULL) output$weightToMolarityResult <- renderText(NULL) output$weightToMolaritySteps <- renderText(NULL) output$molarityToWeightResult <- renderText(NULL) output$molarityToWeightSteps <- renderText(NULL) output$bpToNmResult <- renderText(NULL) output$bpToNmSteps <- renderText(NULL) output$nmToBpResult <- renderText(NULL) output$nmToBpSteps <- renderText(NULL) }) # Absorbance to Concentration calculation observeEvent(input$calcAbsorbance, { absorbance <- input$absorbanceInput dilution <- input$dilutionFactor # Get the length from the specific tab's input if needed # length <- input$dnaLength if (is.null(absorbance) || is.null(dilution) || absorbance < 0 || dilution < 0) { output$absorbanceResult <- renderText("Invalid input") output$absorbanceSteps <- renderText("Please enter valid numbers.") return() } # DNA concentration (μg/mL) = A260 × 50 μg/mL × dilution factor concentration <- absorbance * 50.0 * dilution output$absorbanceResult <- renderText(sprintf("%.2f μg/mL", concentration)) output$absorbanceSteps <- renderText(paste( "Step-by-Step:", "1. DNA concentration = A260 × 50 μg/mL × dilution factor", sprintf("2. %.2f × 50 × %.2f = %.2f μg/mL", absorbance, dilution, concentration), "", "Note: This calculation assumes pure DNA.", sep = "\n" )) }) # Weight to Moles calculation observeEvent(input$calcWeightToMole, { weight <- input$weightToMoleInput # Get the length from the specific tab's input length <- input$dnaLengthWM unit <- input$weightToMoleUnit if (is.null(weight) || is.null(length) || weight < 0 || length <= 0) { output$weightToMoleResult <- renderText("Invalid input") output$weightToMoleSteps <- renderText("Ensure inputs are valid numbers.") return() } molWeightResult <- calculateMolecularWeight(length) molecularWeight <- molWeightResult$mw molecularWeightSteps <- molWeightResult$steps weightInGrams <- weight * weightUnitFactors[[unit]] moles <- weightInGrams / molecularWeight formattedMoles <- formatMoles(moles) output$weightToMoleResult <- renderText(formattedMoles) output$weightToMoleSteps <- renderText(paste( molecularWeightSteps, "", "Step-by-Step:", sprintf("1. Weight (converted to grams): %.2f × %s = %.8f g", weight, weightUnitFactors[[unit]], weightInGrams), sprintf("2. Moles = Weight ÷ Molecular Weight = %.8f ÷ %.2f = %.8e", weightInGrams, molecularWeight, moles), sep = "\n" )) }) # Moles to Weight calculation observeEvent(input$calcMoleToWeight, { moles <- input$moleToWeightInput # Get the length from the specific tab's input length <- input$dnaLengthWM unit <- input$moleToWeightUnit if (is.null(moles) || is.null(length) || moles < 0 || length <= 0) { output$moleToWeightResult <- renderText("Invalid input") output$moleToWeightSteps <- renderText("Ensure inputs are valid numbers.") return() } molWeightResult <- calculateMolecularWeight(length) molecularWeight <- molWeightResult$mw molecularWeightSteps <- molWeightResult$steps molesInMol <- moles * moleUnitFactors[[unit]] weight <- molesInMol * molecularWeight formattedWeight <- formatWeight(weight) output$moleToWeightResult <- renderText(formattedWeight) output$moleToWeightSteps <- renderText(paste( molecularWeightSteps, "", "Step-by-Step:", sprintf("1. Moles (converted to mol): %.2f × %s = %.8e mol", moles, moleUnitFactors[[unit]], molesInMol), sprintf("2. Weight = Moles × Molecular Weight = %.8e × %.2f = %.8f g", molesInMol, molecularWeight, weight), sep = "\n" )) }) # Weight to Molarity calculation observeEvent(input$calcWeightToMolarity, { weight <- input$weightToMolarityWeight volume <- input$weightToMolarityVolume # Get the length from the specific tab's input length <- input$dnaLengthWMol weightUnit <- input$weightToMolarityWeightUnit volumeUnit <- input$weightToMolarityVolumeUnit if (is.null(weight) || is.null(volume) || is.null(length) || weight < 0 || volume <= 0 || length <= 0) { output$weightToMolarityResult <- renderText("Invalid input") output$weightToMolaritySteps <- renderText("Ensure inputs are valid numbers.") return() } molWeightResult <- calculateMolecularWeight(length) molecularWeight <- molWeightResult$mw molecularWeightSteps <- molWeightResult$steps weightInGrams <- weight * weightUnitFactors[[weightUnit]] volumeInLiters <- volume * volumeUnitFactors[[volumeUnit]] molarity <- (weightInGrams / molecularWeight) / volumeInLiters formattedMolarity <- formatMoles(molarity) output$weightToMolarityResult <- renderText(formattedMolarity) output$weightToMolaritySteps <- renderText(paste( molecularWeightSteps, "", "Step-by-Step:", sprintf("1. Weight (converted to grams): %.2f × %s = %.8f g", weight, weightUnitFactors[[weightUnit]], weightInGrams), sprintf("2. Volume (converted to liters): %.2f × %s = %.8f L", volume, volumeUnitFactors[[volumeUnit]], volumeInLiters), sprintf("3. Moles = Weight ÷ Molecular Weight = %.8f ÷ %.2f = %.8e", weightInGrams, molecularWeight, weightInGrams / molecularWeight), sprintf("4. Molarity = Moles ÷ Volume = %.8e ÷ %.8f = %.8e M", weightInGrams / molecularWeight, volumeInLiters, molarity), sep = "\n" )) }) # Molarity to Weight calculation observeEvent(input$calcMolarityToWeight, { molarity <- input$molarityToWeightMolarity volume <- input$molarityToWeightVolume # Get the length from the specific tab's input length <- input$dnaLengthWMol molarityUnit <- input$molarityToWeightMolarityUnit volumeUnit <- input$molarityToWeightVolumeUnit if (is.null(molarity) || is.null(volume) || is.null(length) || molarity < 0 || volume <= 0 || length <= 0) { output$molarityToWeightResult <- renderText("Invalid input") output$molarityToWeightSteps <- renderText("Ensure inputs are valid numbers.") return() } molWeightResult <- calculateMolecularWeight(length) molecularWeight <- molWeightResult$mw molecularWeightSteps <- molWeightResult$steps molarityInMol <- molarity * molarityUnitFactors[[molarityUnit]] volumeInLiters <- volume * volumeUnitFactors[[volumeUnit]] weight <- molarityInMol * volumeInLiters * molecularWeight formattedWeight <- formatWeight(weight) output$molarityToWeightResult <- renderText(formattedWeight) output$molarityToWeightSteps <- renderText(paste( molecularWeightSteps, "", "Step-by-Step:", sprintf("1. Molarity (converted to molar): %.2f × %s = %.8e M", molarity, molarityUnitFactors[[molarityUnit]], molarityInMol), sprintf("2. Volume (converted to liters): %.2f × %s = %.8f L", volume, volumeUnitFactors[[volumeUnit]], volumeInLiters), sprintf("3. Weight = Molarity × Volume × Molecular Weight = %.8e × %.8f × %.2f = %.8f g", molarityInMol, volumeInLiters, molecularWeight, weight), sep = "\n" )) }) # BP to NM conversion observeEvent(input$calcBpToNm, { bp <- input$bpToNmInput if (is.null(bp) || bp < 0) { output$bpToNmResult <- renderText("Invalid input") output$bpToNmSteps <- renderText("Please enter a valid number.") return() } nm <- bp * BP_TO_NM output$bpToNmResult <- renderText(sprintf("%.2f nm", nm)) output$bpToNmSteps <- renderText(paste( "Step-by-Step:", "1. Length in nm = bp × 0.34", sprintf("2. %.2f × 0.34 = %.2f nm", bp, nm), sep = "\n" )) }) # NM to BP conversion observeEvent(input$calcNmToBp, { nm <- input$nmToBpInput if (is.null(nm) || nm < 0) { output$nmToBpResult <- renderText("Invalid input") output$nmToBpSteps <- renderText("Please enter a valid number.") return() } bp <- nm / BP_TO_NM output$nmToBpResult <- renderText(sprintf("%.0f bp", bp)) output$nmToBpSteps <- renderText(paste( "Step-by-Step:", "1. Length in bp = nm ÷ 0.34", sprintf("2. %.2f ÷ 0.34 = %.2f bp", nm, bp), sep = "\n" )) }) } # Run the application shinyApp(ui = ui, server = server)