diff --git a/.gitignore b/.gitignore index f40fbd8..45c9db7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ _site .jekyll-cache .jekyll-metadata vendor +.Rhistory diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..40db850 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1 @@ +info.R diff --git a/arbinimport.R b/arbinimport.R deleted file mode 100644 index c26acf0..0000000 --- a/arbinimport.R +++ /dev/null @@ -1,1315 +0,0 @@ -# ###### -# -# Welcome to the Battery Analyzer Utility! -# -# This script aims to make it quick and efficient to analyze data exported by an Arbin battery cycler -# -# ###### - -# ###### -# -# These are all the required packages to aid in several of the processes, ranging from data analysis to plotting -# -# ###### - -require(readxl) -require(dplyr) -require(shiny) -require(tcltk) -require(DT) -require(shinyjs) -require(shinyalert) -require(pracma) -require(purrr) -require(zoo) -require(plotrix) -require(tools) -require(shinyWidgets) - -require(gifski) - -# This line tests if the current R environment is interactive, RStudio makes an interactive environment by default -if (interactive()) { - - # ###### - # - # All the global variables within the script, aka variables that need to be accessed by more than one - # function or session - # - #"Reactive Values" are ones that need to be readily changed, such as user inputs and variables to be displayed - # - # ###### - data <- reactiveValues(data = data.frame()) - final <- data.frame() - dirLocation <- reactiveVal("") - numCycles <- data.frame() - dQdVData <- data.frame() - total <- data.frame() - cycle_facts <- data.frame() - tmp_data <- data.frame() - dirName <<- "" - tmp_cycles <- vector() - titleLabel <-"" - xlabel <-"" - ylabel <-"" - addParams <- FALSE - catMetric <<- vector() - legTitle <<-"" - sheetName <<-"" - bounds <<- vector() - compCycleFacts <<- data.frame() - - # ###### - # - # This is the UI function for Shiny, it defines how the layout of what the user sees - # - # ###### - ui <- fluidPage( - - # Utilizing javascript within Shiny allows for addd features such as enable/disable of inputs - useShinyjs(), - - # Shinyalert is a package that makes interactive"pop-ups" (modals) easy to generate - useShinyalert(), - - setBackgroundColor( - color = c("ghostwhite", "lightgrey"), - gradient = "linear", - direction = "bottom", - shinydashboard = FALSE - ), - - fluidRow(headerPanel("Electrochemical Data Processing Tool (EcDPT)")), - - # This first column is where most user inputs are, with the exception of the directory name - column(4, - - # This generates the optional block in which the user can import a previous R environment - fluidRow( - strong("Files to be Analyzed*"), tags$br(), - "Import all Arbin files of interest.", tags$br(), tags$br(), - fileInput("files", NULL, multiple = TRUE), - style = "border: 1px solid black; padding: 5%; margin:5%" - ), - - fluidRow( - fileInput("rerun", "Optional: Import Previous R Environment", multiple = FALSE, accept = ".RData"), - actionButton("load", "Load"), - style = "border: 1px dashed black; margin: 5%; padding: 5%" - ), - - # These are the"optional" parameters that need to be filled out if select graphs are selected - fluidRow( - strong("Optional Parameters"), tags$br(), - "Parameters responsible for certain graphs.", tags$br(), tags$br(), - - # Used for dishcharge areal capacity graphs - numericInput("area","Limiting Electrode Area (cm^2)", 2.74, min = 0), - - # Used for C-Rate calculations - # numericInput("perActive","Active Loading of Limiting Electrode (wt%)", 96, min = 0, max = 100), - # numericInput("capActive","Capacity of Limiting Active Material (mAh/g)", 155, min = 0, max = 100), - style ="border: 1px dashed black; padding: 5%; margin:5%" - ), - ), - - # The second column is where selection of graphs and further features are selected - column(4, align ="left", - fluidRow( - # Presents options for graphs to be generated - "Choose graphs to be generated:", - actionButton("whatGraph","What's this?", class ="btn-link"), - checkboxGroupInput("gGraphs", NULL, choices = c("Discharge Capacity","Discharge Areal Capacity", - "Total Discharge Capacity","Average Voltage","Delta Voltage","Capacity Loss"), inline = FALSE), - "Choose graphs to animate:", - checkboxGroupInput("gAnim", NULL, choices = c("dQdV Plots", "Voltage Profiles"), inline = FALSE), - "Advanced Analysis", - #radioButtons("advCalc", NULL, choices = c("No", "Yes"), inline = TRUE), - #helpText(HTML("Advanced Analysis includes:")), - style ="margin: 5%; border: 1px solid black; padding: 5%" - ), - ), - - # The final column is where all the"action" items are, aka clicking any of these buttons will trigger a process - column(4, align ="center", - fluidRow( - textInput("dirName", "Analysis Name*"), tags$br(), - "Current Location: ", textOutput("currDir", inline = TRUE), tags$br(), - actionButton("chooseDir", "Change Output Location*", class = "btn-secondary", style = "width:80%; margin:5%; font-size:100%"), tags$br(), - helpText("The analysis will create a folder within the selected folder."), - actionButton("submit", "Begin Analysis", class = 'btn-success', style = "width:80%; height:100px; margin:5%; font-size:100%"), - style = "border: 4px double black; padding: 5%; margin:5%" - ), - - # This final block enables a button after data becomes available, which trigger the modal to build custom graphs - fluidRow( - strong("Custom Graph Builder"), tags$br(), - "Customize Graphs Once Data is Available", - disabled(actionButton("graphBuilder","Launch", width = '80%', class ="btn-primary", style ="height:50px; margin:5%; font-size:100%")), tags$br(), - style ="border: 1px solid black; padding: 5%; margin:5%" - ), - - fluidRow( - strong("Optional: Import Active Material Masses from Excel"), tags$br(), - "Running Analysis without Masses Will Render Raw Capacities (Ah)", - textAreaInput("masses", NULL, height = "100px", resize = "vertical"), - helpText("Enter the masses separated by a new line and in the order they appear in the data table below."), - actionButton("excelImport", "Import"), - style = "border: 1px dashed black; padding: 5%; margin:5%" - ), - ), - - # This renders the summary datatable at the bottom of the interface once data is imported - fluidRow( - dataTableOutput("channels") - ) - ) - - # ###### - # - # This is the server functon of Shiny. It defines all the"processing" of the data that the user initiated through the interface - # - # ###### - server <- function(input, output, session) { - - # This sets the maximum file size Shiny will import, the default of 5Mb is not large enough to handle Arbin files - options(shiny.maxRequestSize=100*1024^2) - - split_path <- function(x) if (dirname(x)==x) x else c(basename(x),split_path(dirname(x))) - - se <- function(x) {sd(x) / length(x)} - - export_to_origin <- function() { - if (!("reticulate" %in% installed.packages()[, "Package"])) { - install.packages("reticulate") - } - require(reticulate) - - py_location <- py_config() - - py_install("OriginExt", pip = TRUE) - py_install("pandas", pip = TRUE) - - filtered_location <- shQuote(paste(dirLocation(), "/", input$dirName, sep = '')) - - system(paste(py_location$python, " rPyO.py ", filtered_location, sep='')) - } - - # Defines the modal in which the cell masses can be exported from Excel - graphModal <- modalDialog({ - fluidPage(style ="font-size:15pt;", - tags$head(tags$style(".modal-dialog{min-width:60%}")), - - fluidRow(align ="center", - HTML(' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
GraphX AxisY AxisPlot Frequency
Description
dQdV GraphVoltage (V)dQdV (Ah/V)per cycleThe differential capacity plot for each cycle
Voltage ProfileContinuous Capacity (mAh/g or Ah)Voltage (V)per cycleVoltage vs. capacity plot for each cycle. Units depend if the masses are specified.
Voltage vs. TimeTime (min)Voltage (V)per cycleThe voltage as a function of time, including all steps
Discharge CapacityCycleDischarge Capacity (mAh/g or Ah)per cellDischarge capacity for each individual cell per cycle. Coulombic efficiency is also plotted on a secondary axis. Units depend if the masses are specified.
Discharge Areal CapacityCycleDischarge Capacity (Ah/cm2)per cellDischarge areal capacity for each individual cell per cycle. Coulombic efficiency is also plotted on a secondary axis.
Total Discharge CapacityCycleDischarge Capacity (mAh/g or Ah)per analysisDischarge capacity summarized for all cells in the analysis. Coulombic efficiency is also plotted on a secondary axis. Mean is plotted as a point with error bars presenting the standard error between the cells. Units depend if the masses are specified.
Average VoltageCycleVoltage (V)per cellThe average voltage vs capacity for each cycle. The charge voltage (Vcharge) and discharge voltage (Vdischarge) were calculated using the average value theorem. The average voltage is then (Vcharge + Vdischarge)/2. Charge and discharge voltages are plotted alongside the average.
Delta VoltageCycleVoltage (V)per cellThe delta voltage vs capacity for each cycle. The charge voltage (Vcharge) and discharge voltage (Vdischarge) were calculated using the average value theorem. The delta voltage is then Vcharge - Vdischarge). Charge and discharge voltages are plotted alongside the average.
Capacity LossCycleCapacity (mAh/g or Ah)per cellThe discharge capacity minus the charge capacity for each cycle. Units depend if the masses are specified.
- ') - ), - )}, title ="Graph Types", easyClose = TRUE) - - # Ensures the files imported for analysis are Excel files - observeEvent(input$files, { - validFile <- FALSE - - for (file in input$files) { - if (file_ext(file) =="xlsx" | file_ext(file) =="xls") { - validFile <- TRUE - } - } - - if (validFile) { - renderTable() - } else { - shinyalert("That isn't right...","Please upload an Excel file.","error") - } - }) - - observeEvent(input$chooseDir, { - chosenDir = tk_choose.dir() - dirLocation(chosenDir) - - if (!is.na(dirLocation())) { - output$currDir <- renderText({paste(split_path(dirLocation())[1], "(", split_path(dirLocation())[2], ")")}) - } - }) - - graphbuilder <- modalDialog({ - fluidPage( - useShinyjs(), - useShinyalert(), - - tags$head(tags$style(".modal-dialog{width:80%}")), - tags$head(tags$style(".modal-body{ min-height:1000px}")), - - sidebarLayout( - - sidebarPanel( - fluidRow( - headerPanel("Graph Options"), - ), - - fluidRow(style = "padding:5%; border: 1px solid black;", - strong("Basis for Plot Types"), tags$br(), - helpText("Between Analysis requires a second dataset to be imported"), tags$br(), - radioButtons("perType", NULL, choices = c("Within Analysis", "Between Analyses"), inline = TRUE), - hidden(fileInput("compAnalysis", "Data to Compare")) - ), - - fluidRow(style ="padding:5%; margin:5%;", - radioButtons("typeGraph","Graph Type:", choices = c("dQdV Graphs","Voltage Profiles", "Voltage vs. Time", - "Charge Voltage", "Discharge Voltage", - "Average Voltage", "Delta Voltage", "Discharge Capacity", "Charge Capacity" ), inline = FALSE), - radioButtons("plotStyle","Plot Style:", choiceNames = c("Point","Line","Both"), choiceValues = c("p","l","o"), inline = TRUE), - checkboxGroupInput("cells","Cell to Analyze:", choices = 1, inline = FALSE), - selectInput("renderCycles","Cycles of Interest:", choices = 1, multiple = TRUE), - ), - - fluidRow( - strong("Click to show coordinates:"), tags$br(), tags$br(), - "X: ", - textOutput("hoverCoordx", inline = TRUE), tags$br(), - "Y: ", - textOutput("hoverCoordy", inline = TRUE), - style ="border: 1px solid black; padding: 5%; margin: 5%;" - ), - - fluidRow( - textInput("fileName","Name of graph file:"), - actionButton("saveGraph","Save Graph", width = '100%', class = 'btn-primary'), - style ="border: 4px double black; padding: 5%; margin: 5%;" - ), - ), - - mainPanel( - fluidRow( - plotOutput("outputPlot", height ="800px", click = "plot_click"), - style = "padding: 5%;", - ), - fluidRow( - h3("Graph Formatting"), - helpText("*If left blank, they will be calculated using the min and max of the data to be plotted."), - column(2, - numericInput("xMin", "X Min", value = NULL), - sliderInput("textSize", "Text Size", min = 0.1, max = 5, value = 1, ticks = FALSE), - ), - column(2, - numericInput("xMax", "X Max", value = NULL), - sliderInput("pointSize", "Point/Line Size", min = 0.1, max = 5, value = 1, ticks = FALSE), - ), - column(2, - numericInput("yMin", "Y Min", value = NULL), - ), - column(2, - numericInput("yMax", "Y Max", value = NULL), - ), - column(4, - textInput("originalData", "Original Data Name", value = "Original Data"), - textInput("compareData", "Comparison Data Name", value = "Comparison Data"), - ), - style ="border: 1px dashed black; padding: 2%;", - ) - ) - ) - ) - }, size ="l", title ="Post-Processing Graph Builder") - - # Method for importing the previous R environment - observeEvent(input$load, { - if (is.null(input$rerun)) { - shinyalert("Uh oh!", "It appears you haven't selected a .RData file to import.", "error") - } else { - load(input$rerun$datapath[[1]]) - - validFile <- FALSE - - if (file_ext(input$rerun$datapath) =="RData") { - validFile <- TRUE - } - - if (validFile) { - data <<- filter(data, grepl('Channel', sheet)) - dirLocation(dirLocation()) - numCycles <<- numCycles - dQdVData <<- dQdVData - total <<- total - cycle_facts <<- cycle_facts - - output$channels <- renderDataTable(data, editable = FALSE, options=list(columnDefs = list(list(visible=FALSE, targets=c(4)))), - colnames = c("File","Sheet","Mass (g)","Filepath","Limiting Electrode Area (cm^2)")) - - enable("graphBuilder") - } else { - shinyalert("That isn't right...","Please upload an RData file.","error") - } - } - }) - - # After the validation of the Arbin files, they macros (file name, and sheets) are taken and rendered in to a datatable - renderTable <- function() { - output$channels <- renderDataTable({ - files <- input$files - - if (is.null(files)) { - return(NULL) - } - - file_sheet <- data.frame() - for (i in 1:nrow(files)) { - sheets <- excel_sheets(files[i, 4]) - file_sheet <- rbind(file_sheet, data.frame(name = rep(files[["name"]][i], length(sheets)),"sheet" = sheets,"Mass" = rep(0, length(sheets)), - datapath = rep(files[["datapath"]][i], length(sheets)), area = rep(input$area, length(sheets)))) - } - - data <<- filter(file_sheet, grepl('Channel', sheet) & !grepl('Chart', sheet)) - - data - - }, editable = FALSE, options=list(columnDefs = list(list(visible=FALSE, targets=c(4)))), - colnames = c("File","Sheet","Mass (g)","Filepath","Limiting Electrode Area (cm^2)")) - } - - observeEvent(input$whatGraph, { - showModal(graphModal) - }) - - # Data validation the masses imported from Excel, if valid they are placed into the datatable - observeEvent(input$excelImport, { - if (length(names(data)) <= 1) { - shinyalert("Uh oh!","You need to import cells first!","error") - removeModal() - } else { - tryCatch({ - masses <- lapply(strsplit(strRep(input$masses, "\n", ","), ",", fixed = TRUE), as.double) - names(masses)[names(masses) =="V1"] <-"Mass" - data$Mass <<- masses[[1]] - }, error = function(cond) { - print(cond) - shinyalert("Something isn't right...","The number of masses imported did not match the amount of cells present or the text contained some special characters. Please try again.","error") - removeModal() - }, finally = { - proxy = dataTableProxy("channels") - replaceData(proxy, data) - - renderDataTable(data) - - removeModal() - }) - } - }) - - # After some data validation, the main analysis is run on click of the"Run Analysis" button - observeEvent(input$submit, { - if (length(names(data)) <= 1) { - shinyalert("Uh oh!", "You need to import cells first!", "error") - } else if (is.na(dirLocation()) | dirLocation() == "") { - shinyalert("Uh oh!", "You need to enter a directory name first!", "error") - } else if (input$dirName == "") { - shinyalert("Uh oh!", "You need to enter an analysis name first!", "error") - } else if (sum(data$Mass) == 0) { - shinyalert("Uh oh!", "You have not entered any masses. Do you wish to continue?", - type ="warning", showConfirmButton = TRUE, showCancelButton = TRUE, confirmButtonText = "Continue", cancelButtonText = "Abort", - callbackR = function(x) { - if (x) { - runscript() - } - } - ) - } else { - runscript() - } - }) - - # This function responsible for analysis of the - runscript <- function() { - - # Sets up a progress bar in which to estimate how long the execution of the code will take - progress <- Progress$new(session, min = 0, max = nrow(data)) - progress$set(message ="Plugging and chugging...\n", detail ="Starting up...") - - # Closes all graphics devices that may be lingering (prevents an excess from opening and slowing down the analysis) - while (dev.cur() != 1) { - dev.off() - } - - # Resets the variables for the graph builder so new results are concatenated to old ones - numCycles <<- data.frame() - dQdVData <<- data.frame() - total <<- data.frame() - cycle_facts <<- data.frame() - - # Disable all input fields to prevent errors occurring from changing values - disable("files") - disable("lowV") - disable("highV") - disable("dirLocation()") - disable("submit") - disable("excelImport") - disable("gGraphs") - disable("peakFit") - disable("area") - disable("perActive") - disable("capActive") - - # Creates the directory in which all data will be stored - dir.create(paste(dirLocation(), input$dirName, sep = "/")) - - # Update the status once all set-up functions are complete - progress$set(detail ="Starting first cell...") - - # ###### - # - # The bulk of the analysis occurs within the loop. Each iteratin of the loop corresponds to a cell. - # - # ###### - for (row in 1:nrow(data)) { - - # ###### - # - # This is where all code that should be executed on a"per cell" basis, to prepare for analysis - # - # ###### - - # Import the excel sheet corresponding to cell of interest - tmp_excel <- read_excel(toString(data$datapath[row]), toString(data$sheet[row])) - - # Create an nested directory for all the data and, if applicable, then further folders for graphs of interest - dir.create(paste(dirLocation(), "/", input$dirName, data$sheet[row], sep ="/")) - if (is.element("dQdV Graphs", input$gGraphs)) dir.create(paste(dirLocation(), input$dirName, data$sheet[row],"dQdV Plots", sep ="/")) - if (is.element("Voltage Profiles", input$gGraphs)) dir.create(paste(dirLocation(), input$dirName, data$sheet[row],"Voltage Profiles", sep ="/")) - if (is.element("Voltage vs. Time", input$gGraphs)) dir.create(paste(dirLocation(), input$dirName, data$sheet[row],"Voltage v Time", sep ="/")) - - # Check if masses have been imported, if they have not then all future calculations will be done on a raw capacity basis - if (sum(data$Mass) != 0) { - ylabel <-"Capacity (mAh/g)" - - tmp_excel$Q.d <- as.numeric(tmp_excel$`Discharge_Capacity(Ah)` * (1000 / data$Mass[row])) - tmp_excel$Q.c <- as.numeric(tmp_excel$`Charge_Capacity(Ah)`* (1000 / data$Mass[row])) - - tmp_excel$CC <- tmp_excel$Q.d - tmp_excel$Q.c - tmp_excel$CE <- (tmp_excel$Q.d / tmp_excel$Q.c) * 100 - } else { - ylabel <-"Capacity (Ah)" - - tmp_excel$CC <- tmp_excel$`Discharge_Capacity(Ah)` - tmp_excel$`Charge_Capacity(Ah)` - tmp_excel$CE <- (tmp_excel$`Discharge_Capacity(Ah)` / tmp_excel$`Charge_Capacity(Ah)`) * 100 - } - tmp_excel$Cell <- row - tmp_excel$CE[is.infinite(tmp_excel$CE)|is.nan(tmp_excel$CE)|tmp_excel$CE > 200] <- 0; - - # ###### - # - # This loop iterates through each cycle of the cell. - # - # ###### - cycles <- split(tmp_excel, tmp_excel$Cycle_Index) - prev_c <- 0 - lastCC <- 0 - ch_dch <- TRUE - durations <- vector(length = 4) - caps <- vector(length = 4) - prev <- TRUE - dchV <- 0 - chV <- 0 - i <- 1 - for (cycle in cycles) { - - # ###### - # - # Within each cycle, take out the individual steps. These include the charge, discharge, and others. - # - # ###### - - progress$set(detail = paste("Analyzing cell", row,", cycle", i)) - - steps <- split(cycle, cycle$Step_Index) - n <- 1 - - for (step in steps) { - - # ###### - # - # Isolation of the charge and discharge cycles. The algorithm is as follows: - # - # If the change in voltage for the step is greater than 0.5V, it is a charge or dicharge cycle. Then, the direction (sign) of the current determines - # if it is charge or discharge (positive current = charge step, negative current is a discharge cycle). - # - # ###### - if (abs(tail(step$'Voltage(V)',1) - step$'Voltage(V)'[[1]]) > 0.5) { - - # ###### - # - # All code that should be executed for every charge/discharge cycles should be written here. - # - # ###### - lastCC <- n - if (step$'Current(A)'[[1]] > 0) { - chV <- (1 / (tail(step$`Charge_Capacity(Ah)`,1) - step$`Charge_Capacity(Ah)`[[1]])) * trapz(step$`Charge_Capacity(Ah)`, step$`Voltage(V)`) - dQCdV <- diff(step$`Charge_Capacity(Ah)`)/diff(step$`Voltage(V)`) - dQdVData <<- rbind(dQdVData, data.frame(cycle=rep(i, length(dQCdV)+1), cell = rep(row, length(dQCdV)+1), c_d=rep(0, length(dQCdV)+1), voltage=step$`Voltage(V)`, dQdV=c(0, dQCdV), F_L=rep(0,length(dQCdV)+1))) - - durations[1] <- tail(step$'Test_Time(s)', 1) - step$'Test_Time(s)'[[1]] - caps[1] <- tail(step$'Charge_Capacity(Ah)', 1) - step$'Charge_Capacity(Ah)'[[1]] - ch_dch <- TRUE - } else { - dchV <- (1 / (tail(step$`Discharge_Capacity(Ah)`,1) - step$`Discharge_Capacity(Ah)`[[1]])) * trapz(step$`Discharge_Capacity(Ah)`, step$`Voltage(V)`) - dQDdV <- diff(step$`Discharge_Capacity(Ah)`)/diff(step$`Voltage(V)`) - - durations[3] <- tail(step$'Test_Time(s)', 1) - step$'Test_Time(s)'[[1]] - caps[3] <- tail(step$'Discharge_Capacity(Ah)', 1) - step$'Discharge_Capacity(Ah)'[[1]] - ch_dch <- FALSE - if (abs(prev_c - step$`Current(A)`[[1]]) > 0.0005) { - dQdVData <<- rbind(dQdVData, data.frame(cycle=rep(i, length(dQDdV)+1), cell = rep(row, length(dQDdV)+1), c_d=rep(1, length(dQDdV)+1), voltage=step$`Voltage(V)`, dQdV=c(0, dQDdV), F_L=rep(1,length(dQDdV)+1))) - prev_c = step$`Current(A)`[[1]] - } else { - dQdVData <<- rbind(dQdVData, data.frame(cycle=rep(i, length(dQDdV)+1), cell = rep(row, length(dQDdV)+1), c_d=rep(1, length(dQDdV)+1), voltage=step$`Voltage(V)`, dQdV=c(0, dQDdV), F_L=rep(0, length(dQDdV)+1))) - } - } - } else if (n - lastCC == 1 & abs(tail(step$'Voltage(V)',1) - step$'Voltage(V)'[[1]]) < 0.001) { - if (step$'Current(A)'[[1]] > 0) { - durations[2] <- tail(step$'Test_Time(s)', 1) - step$'Test_Time(s)'[[1]] - caps[2] <- tail(step$'Charge_Capacity(Ah)', 1) - step$'Charge_Capacity(Ah)'[[1]] - } else { - durations[4] <- tail(step$'Test_Time(s)', 1) - step$'Test_Time(s)'[[1]] - caps[4] <- tail(step$'Discharge_Capacity(Ah)', 1) - step$'Discharge_Capacity(Ah)'[[1]] - } - } - ch_dch <- FALSE - n <- n + 1 - } - - # ###### - # - # Code meant to be run on data"per cycle" should be written here - # - # ###### - - dQdVData <<- dQdVData[is.finite(dQdVData$voltage),] - dQdVData <<- dQdVData[is.finite(dQdVData$dQdV),] - - if (sum(data$Mass) != 0) { - DCap <- tail(cycle$Q.d, 1) - CCap <- tail(cycle$Q.c, 1) - } else { - DCap <- tail(cycle$`Discharge_Capacity(Ah)`, 1) - CCap <- tail(cycle$`Charge_Capacity(Ah)`, 1) - } - - timeCVFracCh <- durations[1] / (durations[1] + durations[2]) - timeCVFracDch <- durations[3] / (durations[3] + durations[4]) - capCVFracCh <- caps[1] / (caps[1] + caps[2]) - capCVFracDch <- caps[3] / (caps[3] + caps[4]) - - # Record charge and discharge voltage, then calculate the delta and average voltage - cycle_facts <<- rbind(cycle_facts, data.frame(cycle=i, cell=row, chV=chV, dchV=dchV, avgV=(dchV + chV) / 2, - dV=chV-dchV, DCap = DCap, CCap = CCap, CE = (CCap / DCap) * 100, lostCap = CCap - DCap, cellFade = if (i == 1) 0 else {DCap - tail(cycle_facts$DCap, 1)}, - cycleTime = tail(cycle$`Test_Time(s)`, 1) - cycle$`Test_Time(s)`[[1]], timeCVFracCh = timeCVFracCh, timeCVFracDch = timeCVFracDch, - capCVFracCh = capCVFracCh, capCVRatioDch = capCVFracDch)) - - i <- i + 1 - } - - # ###### - # - # Code meant to be run on data"per cell" should be written here - # - # ###### - - cell_data <- cycle_facts[cycle_facts$cell == row,] - - # Discharge capacity plotting, with coulombic efficiency being plotted alongside - if (is.element("Discharge Capacity", input$gGraphs)) { - png(paste(dirLocation(), "/", input$dirName,"/", data$sheet[row],"/", data$sheet[row]," Discharge Capacity Plot.png", sep ="")) - eol <- cell_data$`DCap`[[1]] * 0.8 - plot(cell_data$cycle, cell_data$DCap, type ="p", main=paste("Discharge Capacity for", input$dirName), xlab=NA, ylab=paste("Discharge", ylabel), mai=c(1,1,1,1)) - abline(h=eol, lty ="dotted") - par(new = T) - plot(cell_data$cycle, cell_data$CE, type ="p", axes=F, col ="red", ylab=NA, xlab="Cycle", ylim = c(0, 105)) - mtext(side = 4, line = 3,"Coulombic Efficiency (%)", col = "red") - axis(side = 4, col ="red", col.axis = "red") - dev.off() - } - - # Discharge areal capacity plotting, with coulombic efficiency being plotted alongside - if (is.element("Discharge Areal Capacity", input$gGraphs)) { - png(paste(dirLocation(), "/", input$dirName,"/", data$sheet[row],"/", data$sheet[row]," Discharge Areal Capacity Plot.png", sep ="")) - new_par <- old_par <- par("mar") - new_par[4] <- old_par[2] - par(mar = new_par) - eol <- ((cell_data$DCap[[1]] * 1000) / data$area[row]) * 0.8 - plot(cell_data$cycle, ((cell_data$DCap * 1000) / data$area[row]), type ="p", main=paste("Discharge Areal Capacity for", input$dirName), xlab=NA, ylab="Discharge Capacity (mAh/cm^2)", mai = c(1,1,1,2)) - abline(h=eol, lty ="dotted") - par(new = T) - plot(cell_data$cycle, cell_data$CE, type ="p", axes=F, col ="red", ylab=NA, xlab="Cycle", ylim = c(0, 105)) - mtext(side = 4, line = 3,"Coulombic Efficiency (%)", col = "red") - axis(side = 4, col ="red", col.axis = "red") - dev.off() - } - - - # Average voltage plotting - if (is.element("Average Voltage", input$gGraphs)) { - png(paste(dirLocation(), "/", input$dirName,"/", data$sheet[row],"/", data$sheet[row]," Average Voltage Plot.png", sep ="")) - plot(cell_data$cycle, cell_data$chV, col="blue", main=paste("Average Voltage Plot for", input$dirname, data$sheet[row]), xlab="Cycle", ylab="Voltage (V)", ylim=c(min(cell_data[,2:4]), max(cell_data[,2:4]))) - points(cell_data$cycle, cell_data$dchV, col="red", main=paste("Average Voltage Plot for", input$dirName, data$sheet[row]), xlab="Cycle", ylab="Voltage (V)") - points(cell_data$cycle, cell_data$avgV, col="black", main=paste("Average Voltage Plot for", input$dirName, data$sheet[row]), xlab="Cycle", ylab="Voltage (V)") - legend("bottomright", c("Charge Voltage","Discharge Voltage","Average Voltage"), col=c("blue","red","black"), pch=19) - dev.off() - } - - # Delta voltage plotting - if (is.element("Delta Voltage", input$gGraphs)) { - png(paste(dirLocation(), "/", input$dirName,"/", data$sheet[row],"/", data$sheet[row]," Delta Voltage Plot.png", sep ="")) - plot(cell_data$cycle, cell_data$dV, main=paste("Delta Voltage Plot for", input$dirName, data$sheet[row]), xlab="Cycle", ylab="Voltage (V)", ylim =c(0, 0.5)) - dev.off() - } - - # Capacity Loss plotting - if (is.element("Capacity Loss", input$gGraphs)) { - png(paste(dirLocation(), "/", input$dirName,"/", data$sheet[row],"/", data$sheet[row]," Capacity Loss Plot.png", sep ="")) - plot(cell_data$cycle, cell_data$lostCap, main=paste("Capacity Loss Plot for", input$dirName, data$sheet[row]), xlab="Cycle", ylab= ylabel, ylim = c(mean(cell_data$lostCap) + (2* sd(cell_data$lostCap)), mean(cell_data$lostCap) - (1.5* sd(cell_data$lostCap)))) - abline(h=median(cell_data$lostCap), lty="dotted") - dev.off() - } - - if (is.element("dQdV Plots", input$gAnim)) { - dQdVplot <- function(){ - tmp_data <- dQdVData[dQdVData$cell == row,] - first_cycle <- dQdVData[dQdVData$cell == row & dQdVData$cycle == 2,] - datalist <- split(tmp_data, tmp_data$cycle) - lapply(datalist, function(plotData){ - p <- plot(plotData$voltage, plotData$dQdV, main=paste("dQdV Plot for", input$dirName, data$sheet[row], "Cycle", plotData$cycle[[1]]), xlab="Voltage (V)", ylab= "dQdV (Ah/V)", - xlim = c(min(tmp_data$voltage), max(tmp_data$voltage)), ylim = c(min(tmp_data$dQdV), max(tmp_data$dQdV))) + - points(first_cycle$voltage, first_cycle$dQdV, col = rgb(red = 1, green = 0, blue = 0, alpha = 0.5)) - }) - } - save_gif(dQdVplot(), paste(dirLocation(), input$dirName, data$sheet[row], "dQdV Animation.gif", sep = "/"), delay = 0.2) - } - - if (is.element("Voltage Profiles", input$gAnim)) { - vpPlot <- function(){ - first_cycle <- tmp_excel[tmp_excel$`Cycle_Index` == 2,] - datalist <- split(tmp_excel, tmp_excel$`Cycle_Index`) - lapply(datalist, function(plotData){ - p <- plot(plotData$CC, plotData$`Voltage(V)`, main=paste("Voltage Profile for", input$dirName, data$sheet[row], "Cycle", plotData$`Cycle_Index`[[1]]), xlab=ylabel, ylab= "Voltage (V)", - xlim = c(min(tmp_excel$CC), max(tmp_excel$CC)), ylim = c(min(tmp_excel$`Voltage(V)`), max(tmp_excel$`Voltage(V)`))) + - points(first_cycle$CC, first_cycle$`Voltage(V)`, col = rgb(red = 1, green = 0, blue = 0, alpha = 0.5)) - }) - } - save_gif(vpPlot(), paste(dirLocation(), input$dirName, data$sheet[row], "Voltage Profile Animation.gif", sep = "/"), delay = 0.2) - } - - # Save all data within the cell's directory - write.csv(tmp_excel, file = paste(dirLocation(), "/", input$dirName,"/", data$sheet[row],"/", data$sheet[row],".csv", sep ="")) - - # Append summation data to the larger datasets to be worked with later - final <- rbind(final, tmp_excel) - numCycles <<- rbind(numCycles, data.frame(sheet=data$sheet[row], cycles=nrow(cell_data))) - - # Update progress bar - progress$set(value = row, detail = paste("Finished", row," of", nrow(data)," cells.")) - } - - # ###### - # - # Code meant to be run on all data of all cells should be written here. - # - # ###### - - # With iterations complete, final calculations are being worked - progress$set(detail ="Wrapping up...") - - # Get the last status of each cycle for each cell (namely capacity) - DCap <- cycle_facts[c("cycle","DCap")] %>% group_by(cycle) %>% summarise_each(mean) - CE <- cycle_facts[c("cycle","CE")] %>% group_by(cycle) %>% summarise_each(mean) - capSEs <- cycle_facts[c("cycle","DCap")] %>% group_by(cycle) %>% summarise_each(se) - ceSEs <- cycle_facts[c("cycle","CE")] %>% group_by(cycle) %>% summarise_each(se) - stats <- data.frame(cbind(cycle = capSEs$cycle, DCap = DCap$DCap, CE = CE$CE, capSE = capSEs$DCap, ceSE = ceSEs$CE)) - - # Send all the data to a global variable to be used elsewhere - total <<- final - - tryCatch({ - # Total dishcharge capacity plotting - if (is.element("Total Discharge Capacity", input$gGraphs)) { - png(paste(dirLocation(), "/", input$dirName,"/", "Total Discharge Capacity Plot.png", sep ="")) - eol <- max(stats$DCap) * 0.8 - plot(stats$cycle, stats$DCap, type ="p", main=paste("Discharge Capacity for", input$dirName), xlab=NA, ylab=paste("Discharge", ylabel), mai=c(1,1,1,1)) - arrows(stats$cycle, stats$DCap - stats$capSE, stats$cycle, stats$DCap + stats$capSE, length=0.05, angle=90, code=3) - abline(h=eol, lty ="dotted") - par(new = T) - plot(stats$cycle, stats$CE, type ="p", axes=F, col ="red", ylab=NA, xlab="Cycle", ylim = c(0, 105)) - arrows(stats$cycle, stats$CE - stats$ceSE, stats$cycle, stats$CE + stats$ceSE, length=0.05, angle=90, code=3, col ="red") - axis(side = 4, col ="red") - mtext(side = 4, line = 2,"Coulombic Efficiency (%)") - dev.off() - } - }, error = function(cond) { - print(cond) - }) - - # Save total data and stats - write.csv(stats, file = paste(dirLocation(), "/", input$dirName,"/", basename(dirLocation())," Summary.csv", sep ="")) - write.csv(final, file = paste(dirLocation(), "/", input$dirName,"/", basename(dirLocation())," Total.csv", sep ="")) - write.csv(dQdVData, file = paste(dirLocation(), "/", input$dirName,"/", basename(dirLocation())," dQdV Data.csv", sep ="")) - write.csv(cycle_facts, file = paste(dirLocation(), "/", input$dirName,"/", basename(dirLocation())," Cycle Facts.csv", sep ="")) - - # If a histor directory does not exist, create it. Save all the data revelant to plotting to a RData file. - if (!dir.exists(paste(dirLocation(), "history", sep = "/"))) { - dir.create(paste(dirLocation(), "history", sep = "/")) - } - - dirName <<- input$dirName - - save(dirLocation, dirName, data, dQdVData, total, cycle_facts, numCycles, file = paste(dirLocation(), "/history/", input$dirName, ".RData", sep = "")) - - # Modal for completed analysis - shinyalert("Analysis Complete!", paste("All your data are now in ", dirLocation(), "/", input$dirName, sep = ""), - type ="success", showConfirmButton = TRUE, showCancelButton = TRUE, confirmButtonText = "Generate Origin File", cancelButtonText = "Continue", - callbackR = function(x) { - if (x) { - export_to_origin() - } - } - ) - - # Finish progress bar - progress$set(value = nrow(data)) - - # Re-enable all input fields, including graph builder - enable("files") - enable("lowV") - enable("highV") - enable("dirLocation()") - enable("submit") - enable("excelImport") - enable("gGraphs") - enable("peakFit") - enable("area") - enable("perActive") - enable("capActive") - enable("graphBuilder") - - # Close progress bar - progress$close() - } - - # Enable/Disable input field based on desired grph selection - observeEvent(input$gGraphs, { - disable("area") - disable("perActive") - disable("capActive") - - choices <- c("dQdV Graphs","Voltage Profiles","Voltage vs. Time","Discharge Capacity","Discharge Areal Capacity", - "Total Discharge Capacity","Average Voltage","Delta Voltage") - - if (is.element("Discharge Areal Capacity", input$gGraphs)) { - enable("area") - } - }) - - # ###### - # - # Graph Builder Processing - # - # ###### - output$outputPlot <- renderPlot({ - - tmp_data <<- data.frame() - normalTime <<- data.frame() - - bounds <<- c(input$xMin, input$xMax, input$yMin, input$yMax) - - # Define function to normalize Voltage vs. Time plots - normalizeTime <- function(x) { - return(x - x[[1]]) - } - - # ###### - # - # Switch statements defining the bulk of the processing, depending on the desired graph - # - # ###### - if (input$perType =="Within Analysis") { - sheetName <<- TRUE - - # Get the indicies in which the desired cells are in the data frame containing the number of cycles - cellIndex <- match(input$cells, numCycles$sheet) - - switch(input$typeGraph, - "dQdV Graphs" = { - tmp_data <<- data.frame(x=dQdVData[dQdVData$cell %in% cellIndex,]$voltage, y=dQdVData[dQdVData$cell %in% cellIndex,]$dQdV, cycle=dQdVData[dQdVData$cell %in% cellIndex,]$cycle, cell=dQdVData[dQdVData$cell %in% cellIndex,]$cell) - tmp_data <<- tmp_data[tmp_data$cycle == sort(as.numeric(input$renderCycles)),] - - titleLabel <<-"dQdV Plot" - xlabel <<-"Voltage (V)" - ylabel <<-"dQdV (mAh/V)" - }, - "Voltage Profiles" = { - tmp_data <<- data.frame(x=(-1) * total[total$Cell %in% cellIndex,]$CC, y=total[total$Cell %in% cellIndex,]$`Voltage(V)`, cycle=total[total$Cell %in% cellIndex,]$`Cycle_Index`, cell=total[total$Cell %in% cellIndex,]$Cell) - tmp_data <<- tmp_data[tmp_data$cycle == sort(as.numeric(input$renderCycles)),] - - titleLabel <<-"Voltage Profile" - if (sum(data$Mass) != 0) { - xlabel <-"Continuous Capacity (mAh/g)" - } else { - xlabel <-"Continuous Capacity (Ah)" - } - ylabel <<-"Voltage (V)" - }, - "Voltage vs. Time" = { - tmp_data <<- data.frame(x=(total[total$Cell %in% cellIndex,]$`Test_Time(s)` / 60), y=total[total$Cell %in% cellIndex,]$`Voltage(V)`, cycle=total[total$Cell %in% cellIndex,]$`Cycle_Index`, cell=total[total$Cell %in% cellIndex,]$Cell) - tmp_data <<- tmp_data[tmp_data$cycle %in% input$renderCycles,] - - x <- 0 - - for (cell in cellIndex) { - normalTime <<- c(normalTime, t(aggregate(tmp_data[tmp_data$cell == cell,]$x, by=list(tmp_data[tmp_data$cell == cell,]$cycle), normalizeTime)[,2])) - } - - tmp_data <<- data.frame(x=unlist(normalTime), y=tmp_data$y, cycle=tmp_data$cycle, cell=tmp_data$cell) - tmp_data <<- tmp_data[tmp_data$y >= 0.01,] - - titleLabel <<-"Voltge vs. Time Plot" - xlabel <<-"Time (min)" - ylabel <<-"Voltage (V)" - }, - "Charge Voltage" = { - tmp_data <<- data.frame(x=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle, y=cycle_facts[cycle_facts$cell %in% cellIndex,]$chV, cycle=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle, cell = cycle_facts[cycle_facts$cell %in% cellIndex,]$cell) - - titleLabel <<- "Charge Voltage Plot " - xlabel <<- "Cycle" - ylabel <<- "Voltage (V)" - }, - "Discharge Voltage" = { - tmp_data <<- data.frame(x=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle, y=cycle_facts[cycle_facts$cell %in% cellIndex,]$dchV, cell=cycle_facts[cycle_facts$cell %in% cellIndex,]$cell, cycle=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle) - - titleLabel <<- "Discharge Voltage Plot " - xlabel <<- "Cycle" - ylabel <<- "Voltage (V)" - }, - "Average Voltage" = { - tmp_data <<- data.frame(x=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle, y=cycle_facts[cycle_facts$cell %in% cellIndex,]$avgV, cell=cycle_facts[cycle_facts$cell %in% cellIndex,]$cell, cycle=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle) - - titleLabel <<- "Average Voltage Plot " - xlabel <<- "Cycle" - ylabel <<- "Voltage (V)" - }, - "Delta Voltage" = { - tmp_data <<- data.frame(x=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle, y=cycle_facts[cycle_facts$cell %in% cellIndex,]$dV, cell=cycle_facts[cycle_facts$cell %in% cellIndex,]$cell, cycle=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle) - - titleLabel <<- "Delta Voltage Plot " - xlabel <<- "Cycle" - ylabel <<- "Voltage (V)" - }, - "Discharge Capacity" = { - tmp_data <<- data.frame(x=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle, y=cycle_facts[cycle_facts$cell %in% cellIndex,]$DCap, cell=cycle_facts[cycle_facts$cell %in% cellIndex,]$cell, cycle=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle) - - titleLabel <<- "Discharge Capacity Plot " - xlabel <<- "Cycle" - if (sum(data$Mass) != 0) { - ylabel <<- "Discharge Capacity (mAh/g)" - } else { - ylabel <<- "Discharge Capacity (Ah)" - } - }, - "Charge Capacity" = { - tmp_data <<- data.frame(x=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle, y=cycle_facts[cycle_facts$cell %in% cellIndex,]$CCap, cell=cycle_facts[cycle_facts$cell %in% cellIndex,]$cell, cycle=cycle_facts[cycle_facts$cell %in% cellIndex,]$cycle) - - titleLabel <<- "Charge Capacity Plot " - xlabel <<- "Cycle" - if (sum(data$Mass) != 0) { - ylabel <<- "Charge Capacity (mAh/g)" - } else { - ylabel <<- "Charge Capacity (Ah)" - } - } - ) - - tmp_data$color <<- sapply(tmp_data$cycle, function(x) {match(x, input$renderCycles, nomatch = 1)}) - tmp_data$symbol <<- sapply(tmp_data$cell, function(x) {match(x, cellIndex)}) - - } else if (input$perType == "Between Analyses") { - - switch(input$typeGraph, - "Charge Voltage" = { - x<-0 - chV <- cycle_facts[c("cycle","chV")] %>% group_by(cycle) %>% summarise_each(mean) - chVSE <- cycle_facts[c("cycle","chV")] %>% group_by(cycle) %>% summarise_each(se) - - tryCatch({ - comp_chV <- compCycleFacts[c("cycle","chV")] %>% group_by(cycle) %>% summarise_each(mean) - comp_chVSE <- compCycleFacts[c("cycle","chV")] %>% group_by(cycle) %>% summarise_each(se) - - tmp_data <<- data.frame(x=c(chV$cycle, comp_chV$cycle), y=c(chV$chV, comp_chV$chV), se=c(chVSE$chV, comp_chVSE$chV), cell=c(rep(1, length(chV$cycle)), rep(2, length(comp_chV$cycle))), cycle=c(chV$cycle, comp_chV$cycle)) - tmp_data$symbol <<- rep(1, nrow(tmp_data)) - tmp_data$color <<- sapply(tmp_data$cell, function(x) {match(x, c(1,2))}) - - titleLabel <<- "Charge Voltage Plot " - xlabel <<- "Cycle" - ylabel <<- "Voltage (V)" - }, - error = function(x) { - print(x) - }) - }, - "Discharge Voltage" = { - x<-0 - dchV <- cycle_facts[c("cycle","dchV")] %>% group_by(cycle) %>% summarise_each(mean) - dchVSE <- cycle_facts[c("cycle","dchV")] %>% group_by(cycle) %>% summarise_each(se) - - tryCatch({ - comp_dchV <- compCycleFacts[c("cycle","dchV")] %>% group_by(cycle) %>% summarise_each(mean) - comp_dchVSE <- compCycleFacts[c("cycle","dchV")] %>% group_by(cycle) %>% summarise_each(se) - - tmp_data <<- data.frame(x=c(dchV$cycle, comp_dchV$cycle), y=c(dchV$dchV, comp_dchV$dchV), se=c(dchVSE$dchV, comp_dchVSE$dchV), cell=c(rep(1, length(dchV$cycle)), rep(2, length(comp_dchV$cycle))), cycle=c(dchV$cycle, comp_dchV$cycle)) - tmp_data$symbol <<- rep(1, nrow(tmp_data)) - tmp_data$color <<- sapply(tmp_data$cell, function(x) {match(x, c(1,2))}) - - titleLabel <<- "Disharge Voltage Plot " - xlabel <<- "Cycle" - ylabel <<- "Voltage (V)" - }, - error = function(x) { - print(x) - }) - }, - "Average Voltage" = { - avgV <- cycle_facts[c("cycle","avgV")] %>% group_by(cycle) %>% summarise_each(mean) - avgVSE <- cycle_facts[c("cycle","avgV")] %>% group_by(cycle) %>% summarise_each(se) - - tryCatch({ - comp_avgV <- compCycleFacts[c("cycle","avgV")] %>% group_by(cycle) %>% summarise_each(mean) - comp_avgVSE <- compCycleFacts[c("cycle","avgV")] %>% group_by(cycle) %>% summarise_each(se) - - tmp_data <<- data.frame(x=c(avgV$cycle, comp_avgV$cycle), y=c(avgV$avgV, comp_avgV$avgV), se=c(avgVSE$avgV, comp_avgVSE$avgV), cell=c(rep(1, length(avgV$cycle)), rep(2, length(comp_avgV$cycle))), cycle=c(avgV$cycle, comp_avgV$cycle)) - tmp_data$symbol <<- rep(1, nrow(tmp_data)) - tmp_data$color <<- sapply(tmp_data$cell, function(x) {match(x, c(1,2))}) - - titleLabel <<- "Average Voltage Plot " - xlabel <<- "Cycle" - ylabel <<- "Voltage (V)" - }, - error = function(x) { - print(x) - }) - }, - "Delta Voltage" = { - dV <- cycle_facts[c("cycle","dV")] %>% group_by(cycle) %>% summarise_each(mean) - dVSE <- cycle_facts[c("cycle","dV")] %>% group_by(cycle) %>% summarise_each(se) - - tryCatch({ - comp_dV <- compCycleFacts[c("cycle","dV")] %>% group_by(cycle) %>% summarise_each(mean) - comp_dVSE <- compCycleFacts[c("cycle","dV")] %>% group_by(cycle) %>% summarise_each(se) - - tmp_data <<- data.frame(x=c(dV$cycle, comp_dV$cycle), y=c(dV$dV, comp_dV$dV), se=c(dVSE$dV, comp_dVSE$dV), cell=c(rep(1, length(dV$cycle)), rep(2, length(comp_dV$cycle))), cycle=c(dV$cycle, comp_dV$cycle)) - tmp_data$symbol <<- rep(1, nrow(tmp_data)) - tmp_data$color <<- sapply(tmp_data$cell, function(x) {match(x, c(1,2))}) - - titleLabel <<- "Delta Voltage Plot " - xlabel <<- "Cycle" - ylabel <<- "Voltage (V)" - }, - error = function(x) { - print(x) - }) - }, - "Discharge Capacity" = { - DCap <- cycle_facts[c("cycle","DCap")] %>% group_by(cycle) %>% summarise_each(mean) - DCapSE <- cycle_facts[c("cycle","DCap")] %>% group_by(cycle) %>% summarise_each(se) - - tryCatch({ - comp_DCap <- compCycleFacts[c("cycle","DCap")] %>% group_by(cycle) %>% summarise_each(mean) - comp_DCapSE <- compCycleFacts[c("cycle","DCap")] %>% group_by(cycle) %>% summarise_each(se) - - tmp_data <<- data.frame(x=c(DCap$cycle, comp_DCap$cycle), y=c(DCap$DCap, comp_DCap$DCap), se=c(DCapSE$DCap, comp_DCapSE$DCap), cell=c(rep(1, length(DCap$cycle)), rep(2, length(comp_DCap$cycle))), cycle=c(DCap$cycle, comp_DCap$cycle)) - tmp_data$symbol <<- rep(1, nrow(tmp_data)) - tmp_data$color <<- sapply(tmp_data$cell, function(x) {match(x, c(1,2))}) - - titleLabel <<- "Discharge Capacity Plot " - xlabel <<- "Cycle" - if (sum(data$Mass) != 0) { - ylabel <<- "Discharge Capacity (mAh/g)" - } else { - ylabel <<- "Discharge Capacity (Ah)" - } - }, - error = function(x) { - print(x) - }) - }, - "Charge Capacity" = { - CCap <- cycle_facts[c("cycle","CCap")] %>% group_by(cycle) %>% summarise_each(mean) - CCapSE <- cycle_facts[c("cycle","CCap")] %>% group_by(cycle) %>% summarise_each(se) - - tryCatch({ - comp_CCap <- compCycleFacts[c("cycle","CCap")] %>% group_by(cycle) %>% summarise_each(mean) - comp_CCapSE <- compCycleFacts[c("cycle","CCap")] %>% group_by(cycle) %>% summarise_each(se) - - tmp_data <<- data.frame(x=c(CCap$cycle, comp_CCap$cycle), y=c(CCap$CCap, comp_CCap$CCap), se=c(CCapSE$CCap, comp_CCapSE$CCap), cell=c(rep(1, length(CCap$cycle)), rep(2, length(comp_CCap$cycle))), cycle=c(CCap$cycle, comp_CCap$cycle)) - tmp_data$symbol <<- rep(1, nrow(tmp_data)) - tmp_data$color <<- sapply(tmp_data$cell, function(x) {match(x, c(1,2))}) - - titleLabel <<- "Charge Capacity Plot " - xlabel <<- "Cycle" - if (sum(data$Mass) != 0) { - ylabel <<- "Charge Capacity (mAh/g)" - } else { - ylabel <<- "Charge Capacity (Ah)" - } - }, - error = function(x) { - print(x) - }) - }, - "Capacity Loss" = { - lostCap <- cycle_facts[c("cycle","lostCap")] %>% group_by(cycle) %>% summarise_each(mean) - lostCapSE <- cycle_facts[c("cycle","lostCap")] %>% group_by(cycle) %>% summarise_each(se) - - tryCatch({ - comp_lostCap <- compCycleFacts[c("cycle","lostCap")] %>% group_by(cycle) %>% summarise_each(mean) - comp_lostCapSE <- compCycleFacts[c("cycle","lostCap")] %>% group_by(cycle) %>% summarise_each(se) - - tmp_data <<- data.frame(x=c(lostCap$cycle, comp_lostCap$cycle), y=c(lostCap$lostCap, comp_lostCap$lostCap), se=c(lostCapSE$lostCap, comp_lostCapSE$lostCap), cell=c(rep(1, length(lostCap$cycle)), rep(2, length(comp_lostCap$cycle))), cycle=c(lostCap$cycle, comp_lostCap$cycle)) - tmp_data$symbol <<- rep(1, nrow(tmp_data)) - tmp_data$color <<- sapply(tmp_data$cell, function(x) {match(x, c(1,2))}) - - titleLabel <<- "Charge Capacity Plot " - xlabel <<- "Cycle" - if (sum(data$Mass) != 0) { - ylabel <<- "Capacity (mAh/g)" - } else { - ylabel <<- "Capacity (Ah)" - } - }, - error = function(x) { - print(x) - }) - }, - ) - } - - tmp_data <<- tmp_data[is.finite(tmp_data$x),] - tmp_data <<- tmp_data[is.finite(tmp_data$y),] - tmp_data <<- tmp_data[is.finite(tmp_data$cycle),] - tmp_data <<- tmp_data[is.finite(tmp_data$cell),] - - if (any(sapply(bounds, is.na))) { - if (is.na(bounds[1])) bounds[1] <<- min(tmp_data$x) - if (is.na(bounds[2])) bounds[2] <<- max(tmp_data$x) - if (is.na(bounds[3])) bounds[3] <<- min(tmp_data$y) - if (is.na(bounds[4])) bounds[4] <<- max(tmp_data$y) - } - - tryCatch({ - if (input$plotStyle =="o" | input$plotStyle =="p" | "se" %in% colnames(tmp_data)) { - par(mar=c(5.1, 6.1, 4.1, 2.1)) - - plot(tmp_data$x, tmp_data$y, type = input$plotStyle, col = tmp_data$color, pch = tmp_data$symbol, main=titleLabel, xlim = c(bounds[1], bounds[2]), ylim = c(bounds[3], bounds[4]), xlab=xlabel, ylab=ylabel, cex = input$pointSize, cex.axis = input$textSize, cex.lab = input$textSize, cex.main = input$textSize) - if ("se" %in% colnames(tmp_data)) { - arrows(tmp_data$x, tmp_data$y - tmp_data$se, tmp_data$x, tmp_data$y + tmp_data$se, col = tmp_data$color, length=0.05, angle=90, code=3) - legend("bottomright", legend = c(input$originalData, input$compareData), col = c(1,2), pch = 19) - } else { - if (input$typeGraph %in% c("dQdV Graphs", "Voltage Profiles", "Voltage vs. Time")) { - legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells), col = c(unique(tmp_data$color), rep("black", length(input$cells))), pch = c(rep(19, length(unique(tmp_data$color))), 1:length(input$cells)), title ="Cycle", ncol=2) - } else { - legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells), col = c(unique(tmp_data$color), rep("black", length(input$cells))), pch = c(1:length(input$cells)), title ="Cycle", ncol=2) - } - } - } else if (input$plotStyle =="l") { - newLine <- subset(tmp_data, tmp_data$color == 1 & tmp_data$symbol == 1) - plot(newLine$x, newLine$y, type ="l", col = newLine$color, lty = newLine$symbol, main=titleLabel, xlim = c(bounds[1], bounds[2]), ylim = c(bounds[3], bounds[4]), xlab=xlabel, ylab=ylabel, lwd = input$pointSize, cex.axis = input$textSize, cex.lab = input$textSize, cex.main = input$textSize) - - for (i in 1:length(input$renderCycles)) { - for (n in 1:length(cellIndex)) { - newLine <- subset(tmp_data, tmp_data$color == i & tmp_data$symbol == n) - lines(newLine$x, newLine$y, col = newLine$color, lty = newLine$symbol, lwd = input$pointSize) - } - } - if (input$typeGraph %in% c("dQdV Graphs", "Voltage Profiles", "Voltage vs. Time")) { - legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells, if ("se" %in% colnames(tmp_data)) {c(input$dirName, compName)}), col = c(unique(tmp_data$color), rep("black", length(input$cells))), lty = c(rep(19, length(unique(tmp_data$color))), 1:length(input$cells)), title ="Cycle", ncol=2) - } else { - legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells, if ("se" %in% colnames(tmp_data)) {c(input$dirName, compName)}), col = c(unique(tmp_data$color), rep("black", length(input$cells))), lty = c(1:length(input$cells)), title ="Cycle", ncol=2) - } - } - }, - error=function(cond) { - if (length(input$cells) != 0) { - text(0.5, 0.5, labels ="This graph requires you to select a cycle!", cex = 2) - } else { - text(0.5, 0.5, labels ="You don messed up A-aron!\n (no data to plot)", cex = 2) - } - print(cond) - return(NA) - }) - }, res = 125) - - # Method for handling changes in cell selection - observeEvent(input$cells, { - tmp_cycles <<- input$renderCycles - updateSelectInput(session,"renderCycles", choices = 1:max(numCycles$cycles), selected = tmp_cycles) - }) - - observeEvent(input$compAnalysis, { - load(input$compAnalysis$datapath[[1]]) - - compName <<- basename(input$compAnalysis$datapath[[1]]) - compCycleFacts <<- cycle_facts - - sheetName <<- !sheetName - }) - - # Error handling for graphBuilder and then showing modal - observeEvent(input$graphBuilder, { - if(dim(numCycles)[1] == 0 | dim(dQdVData)[1] == 0 | dim(cycle_facts)[1] == 0 | dim(total) == 0) { - shinyalert("No Data!","Please run the analysis first or load a previous environment.","error") - } else { - updateCheckboxGroupInput(session,"cells", choices = data$sheet) - showModal(graphbuilder) - } - }) - - observeEvent(input$plot_click, { - output$hoverCoordx <- renderText({input$plot_click$x}) - output$hoverCoordy <- renderText({input$plot_click$y}) - }) - - observeEvent(input$perType, { - if (input$perType == "Within Analysis") { - updateRadioButtons(session, "typeGraph", choices = c("dQdV Graphs","Voltage Profiles", "Voltage vs. Time", - "Charge Voltage", "Discharge Voltage", - "Average Voltage", "Delta Voltage", "Discharge Capacity", "Charge Capacity" )) - - show("cells") - show("renderCycles") - show("plotStyle") - hide("originalData") - hide("compareData") - hide("compAnalysis") - hide("analysis") - } else if (input$perType == "Between Analyses") { - updateRadioButtons(session, "typeGraph", choices = c("Charge Voltage", "Discharge Voltage", - "Average Voltage", "Delta Voltage", "Discharge Capacity", "Charge Capacity", - "Capacity Loss")) - - sheetName <<- !sheetName - - hide("cells") - hide("renderCycles") - show("originalData") - show("compareData") - hide("plotStyle") - show("compAnalysis") - show("analysis") - } - }) - - # Method for saving graph generated by graphBuilder - observeEvent(input$saveGraph, { - png(paste(input$fileName,".png"), res = 125) - - if (input$plotStyle =="o" | input$plotStyle =="p") { - plot(tmp_data$x, tmp_data$y, type = input$plotStyle, col = tmp_data$color, pch = tmp_data$symbol, main=titleLabel, xlim = c(bounds[1], bounds[2]), ylim = c(bounds[3], bounds[4]), xlab=xlabel, ylab=ylabel, cex = input$pointSize, cex.axis = input$textSize, cex.lab = input$textSize, cex.main = input$textSize) - legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells), col = c(unique(tmp_data$color), rep("black", length(input$cells))), pch = c(rep(19, length(unique(tmp_data$color))), 1:length(input$cells)), title ="Cycle", ncol=2) - } else if (input$plotStyle =="l") { - newLine <- subset(tmp_data, tmp_data$color == 1 & tmp_data$symbol == 1) - plot(newLine$x, newLine$y, type ="l", col = newLine$color, lty = newLine$symbol, main=titleLabel, xlim = c(bounds[1], bounds[2]), ylim = c(bounds[3], bounds[4]), xlab=xlabel, ylab=ylabel, lwd = input$pointSize, cex.axis = input$textSize, cex.lab = input$textSize, cex.main = input$textSize) - - for (i in 1:length(input$renderCycles)) { - for (n in 1:length(cellIndex)) { - newLine <- subset(tmp_data, tmp_data$color == i & tmp_data$symbol == n) - lines(newLine$x, newLine$y, col = newLine$color, lty = newLine$symbol, lwd = input$pointSize) - } - } - - legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells), col = c(unique(tmp_data$color), rep("black", length(input$cells))), lty = c(rep(19, length(unique(tmp_data$color))), 1:length(input$cells)), title ="Cycle", ncol=2) - } - - dev.off() - - shinyalert("Success!", paste("Plot saved in working directory:\n", getwd()),"success") - }) - } -} -shinyApp(ui, server) diff --git a/global.R b/global.R new file mode 100644 index 0000000..f762231 --- /dev/null +++ b/global.R @@ -0,0 +1,59 @@ +# ###### +# +# Welcome to the Battery Analyzer Utility! +# +# This script aims to make it quick and efficient to analyze data exported by an Arbin battery cycler +# +# ###### + +# ###### +# +# These are all the required packages to aid in several of the processes, ranging from data analysis to plotting +# +# ###### + +# options(warn = 0) + +list.of.packages <- c("readxl", "dplyr", "shiny", "tcltk", "DT", "shinyjs", "shinyalert", "pracma", "purrr", "zoo", "plotrix", "tools", "shinyWidgets", "gifski") +new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[, "Package"])] +if (length(new.packages)) install.packages(new.packages) +lapply(list.of.packages, require, character.only = TRUE) + +source("ui.R", local = TRUE) +source("server.R") +source("info.R") + +# This line tests if the current R environment is interactive, RStudio makes an interactive environment by default +if (interactive()) { + + # ###### + # + # All the global variables within the script, aka variables that need to be accessed by more than one + # function or session + # + # "Reactive Values" are ones that need to be readily changed, such as user inputs and variables to be displayed + # + # ###### + data <- reactiveValues(data = data.frame()) + final <- data.frame() + dirLocation <- vector() + numCycles <- data.frame() + dQdVData <- data.frame() + total <- data.frame() + cycle_facts <- data.frame() + tmp_data <- data.frame() + dirName <<- "" + tmp_cycles <- vector() + titleLabel <- "" + xlabel <- "" + ylabel <- "" + addParams <- FALSE + arbinCM <- FALSE + catMetric <<- vector() + legTitle <<- "" + sheetName <<- "" + bounds <<- vector() + compCycleFacts <<- data.frame() +} + +shinyApp(ui, server) diff --git a/info.R b/info.R new file mode 100644 index 0000000..a0f2664 --- /dev/null +++ b/info.R @@ -0,0 +1,86 @@ + +graphInfoTable <- HTML(' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GraphX AxisY AxisPlot Frequency
Description
dQdV GraphVoltage (V)dQdV (Ah/V)per cycleThe differential capacity plot for each cycle
Voltage ProfileContinuous Capacity (mAh/g or Ah)Voltage (V)per cycleVoltage vs. capacity plot for each cycle. Units depend if the masses are specified.
Voltage vs. TimeTime (min)Voltage (V)per cycleThe voltage as a function of time, including all steps
Discharge CapacityCycleDischarge Capacity (mAh/g or Ah)per cellDischarge capacity for each individual cell per cycle. Coulombic efficiency is also plotted on a secondary axis. Units depend if the masses are specified.
Discharge Areal CapacityCycleDischarge Capacity (Ah/cm2)per cellDischarge areal capacity for each individual cell per cycle. Coulombic efficiency is also plotted on a secondary axis.
Total Discharge CapacityCycleDischarge Capacity (mAh/g or Ah)per analysisDischarge capacity summarized for all cells in the analysis. Coulombic efficiency is also plotted on a secondary axis. Mean is plotted as a point with error bars presenting the standard error between the cells. Units depend if the masses are specified.
Average VoltageCycleVoltage (V)per cellThe average voltage vs capacity for each cycle. The charge voltage (Vcharge) and discharge voltage (Vdischarge) were calculated using the average value theorem. The average voltage is then (Vcharge + Vdischarge)/2. Charge and discharge voltages are plotted alongside the average.
Delta VoltageCycleVoltage (V)per cellThe delta voltage vs capacity for each cycle. The charge voltage (Vcharge) and discharge voltage (Vdischarge) were calculated using the average value theorem. The delta voltage is then Vcharge - Vdischarge). Charge and discharge voltages are plotted alongside the average.
Capacity LossCycleCapacity (mAh/g or Ah)per cellThe discharge capacity minus the charge capacity for each cycle. Units depend if the masses are specified.
+ ') diff --git a/modal.R b/modal.R new file mode 100644 index 0000000..07cc9a3 --- /dev/null +++ b/modal.R @@ -0,0 +1,97 @@ +# Defines the modal in which the cell masses can be exported from Excel +graphModal <- modalDialog( + { + fluidPage( + style = "font-size:15pt;", + tags$head(tags$style(".modal-dialog{min-width:60%}")), + fluidRow(align = "center", graphInfoTable), + ) + }, + title = "Graph Types", + easyClose = TRUE +) + +graphbuilder <- modalDialog( + { + fluidPage( + useShinyjs(), + useShinyalert(), + tags$head(tags$style(".modal-dialog{width:80%}")), + tags$head(tags$style(".modal-body{ min-height:1000px}")), + sidebarLayout( + sidebarPanel( + fluidRow( + headerPanel("Graph Options"), + ), + fluidRow( + style = "padding:5%; border: 1px solid black;", + strong("Basis for Plot Types"), tags$br(), + helpText("Between Analysis requires a second dataset to be imported"), tags$br(), + radioButtons("perType", NULL, choices = c("Within Analysis", "Between Analyses"), inline = TRUE), + hidden(fileInput("compAnalysis", "Data to Compare")) + ), + fluidRow( + style = "padding:5%; margin:5%;", + radioButtons("typeGraph", "Graph Type:", choices = c( + "dQdV Graphs", "Voltage Profiles", "Voltage vs. Time", + "Charge Voltage", "Discharge Voltage", + "Average Voltage", "Delta Voltage", "Discharge Capacity", "Charge Capacity" + ), inline = FALSE), + radioButtons("plotStyle", "Plot Style:", choiceNames = c("Point", "Line", "Both"), choiceValues = c("p", "l", "o"), inline = TRUE), + checkboxGroupInput("cells", "Cell to Analyze:", choices = 1, inline = FALSE), + selectInput("renderCycles", "Cycles of Interest:", choices = 1, multiple = TRUE), + ), + fluidRow( + strong("Click to show coordinates:"), tags$br(), tags$br(), + "X: ", + textOutput("hoverCoordx", inline = TRUE), tags$br(), + "Y: ", + textOutput("hoverCoordy", inline = TRUE), + style = "border: 1px solid black; padding: 5%; margin: 5%;" + ), + fluidRow( + textInput("fileName", "Name of graph file:"), + actionButton("saveGraph", "Save Graph", width = "100%", class = "btn-primary"), + style = "border: 4px double black; padding: 5%; margin: 5%;" + ), + ), + mainPanel( + fluidRow( + plotOutput("outputPlot", height = "800px", click = "plot_click"), + style = "padding: 5%;", + ), + fluidRow( + h3("Graph Formatting"), + helpText("*If left blank, they will be calculated using the min and max of the data to be plotted."), + column( + 2, + numericInput("xMin", "X Min", value = NULL), + sliderInput("textSize", "Text Size", min = 0.1, max = 5, value = 1, ticks = FALSE), + ), + column( + 2, + numericInput("xMax", "X Max", value = NULL), + sliderInput("pointSize", "Point/Line Size", min = 0.1, max = 5, value = 1, ticks = FALSE), + ), + column( + 2, + numericInput("yMin", "Y Min", value = NULL), + ), + column( + 2, + numericInput("yMax", "Y Max", value = NULL), + ), + column( + 4, + textInput("originalData", "Original Data Name", value = "Original Data"), + textInput("compareData", "Comparison Data Name", value = "Comparison Data"), + ), + style = "border: 1px dashed black; padding: 2%;", + ) + ) + ) + ) + }, + size = "l", + title = "Post-Processing Graph Builder" +) diff --git a/server.R b/server.R new file mode 100644 index 0000000..daebb77 --- /dev/null +++ b/server.R @@ -0,0 +1,1236 @@ +library(shiny) + +server <- function(input, output, session) { + utils <- c("modal.R") + lapply(utils, source) + + # This sets the maximum file size Shiny will import, the default of 5Mb is not large enough to handle Arbin files + options(shiny.maxRequestSize = 1000 * 1024^2) + + split_path <- function(x) if (dirname(x) == x) x else c(basename(x), split_path(dirname(x))) + + se <- function(x) { + sd(x) / length(x) + } + + proxy <- dataTableProxy("channels") + + batch.elements <- c("rerun", "load", "area", "whatGraph", "gGraphs", "gAnim", "graphBuilder") + + deleteButtonCol <- function(df, id, ...) { + f <- function(i) { + as.character( + actionButton( + paste(id, i, sep = "_"), + label = icon("trash", lib = "glyphicon"), + onclick = 'Shiny.setInputValue(\"deletePressed\", this.id, {priority: "event"})' + ) + ) + } + + deleteCol <- unlist(lapply(seq_len(nrow(df)), f)) + + cbind(data, delete = deleteCol) + } + + generateDataTable <- function(df) { + DT::datatable(df, + escape = FALSE, editable = FALSE, options = list(columnDefs = list(list(visible = FALSE, targets = c(5, 6, 7)))), + colnames = c("Group", "Program", "File", "Sheet", "Mass (g)", "Filepath", "Group Path", "Limiting Electrode Area (cm^2)", "Delete"), rownames = FALSE + ) + } + + parseDeleteEvent <- function(idstr) { + res <- as.integer(sub(".*_([0-9]+)", "\\1", idstr)) + if (!is.na(res)) { + res + } + } + + observeEvent(input$deletePressed, { + rowNum <- parseDeleteEvent(input$deletePressed) + + data <<- data[-rowNum, ] + + output$channels <- renderDataTable(generateDataTable(deleteButtonCol(data, "delete_button"))) + }) + + observeEvent(input$batchProcessing, { + if (input$batchProcessing) { + lapply(batch.elements, hide) + } else { + lapply(batch.elements, show) + } + }) + + observeEvent(input$clearTable, { + shinyalert("Warning!", "Are you sure you wish to clear all cells?", + type = "warning", showConfirmButton = TRUE, showCancelButton = TRUE, confirmButtonText = "Continue", cancelButtonText = "Abort", + callbackR = function(x) { + if (x) { + data <<- data[0, ] + output$channels <- renderDataTable(generateDataTable(deleteButtonCol(data, "delete_button"))) + } + } + ) + }) + + export_to_origin <- function() { + if (!("reticulate" %in% installed.packages()[, "Package"])) { + install.packages("reticulate") + } + require(reticulate) + + py_location <- py_config() + + py_install("OriginExt", pip = TRUE) + py_install("pandas", pip = TRUE) + + filtered_location <- shQuote(paste(dirLocation, "/", input$dirName, sep = "")) + + system(paste(py_location$python, " rPyO.py ", filtered_location, sep = "")) + } + + # Ensures the files imported for analysis are Excel files + observeEvent(input$files, { + validFile <- FALSE + + for (file in 1:nrow(input$files)) { + if (file_ext(input$files[file, "name"]) == "xlsx" | file_ext(input$files[file, "name"]) == "xls") { + validFile <- TRUE + arbinCM <<- FALSE + } else if (file_ext(file) == "csv") { + arbinCM <<- TRUE + validFile <- TRUE + } + } + + if (validFile) { + renderTable() + } else { + shinyalert("That isn't right...", "Please upload an Excel or CSV file.", "error") + } + }) + + observeEvent(input$chooseDir, { + dirLocation <<- tk_choose.dir() + + internal_folders <- list.dirs(path = dirLocation, recursive = FALSE) + base_names <- vector() + for (folder in internal_folders) { + base_names <- append(base_names, split_path(folder)[1]) + } + + if ("history" %in% base_names) { + past <- new.env() + + if (file.exists(paste(dirLocation, "/history/", "Formation.RData", sep = ""))) { + load(paste(dirLocation, "/history/", "Formation.RData", sep = ""), envir = past) + } else if (file.exists(paste(dirLocation, "/history/", "RateCap.RData", sep = ""))) { + load(paste(dirLocation, "/history/", "RateCap.RData", sep = ""), envir = past) + } else { + return(NA) + } + + if (input$batchProcessing) { + data <<- rbind(data, past$data) + } else { + data <<- past$dataSubset + } + + output$channels <- renderDataTable(generateDataTable(deleteButtonCol(data, "delete_button"))) + } + + if (!is.na(dirLocation)) { + output$currDir <- renderText({ + paste(split_path(dirLocation)[1], "(", split_path(dirLocation)[2], ")") + }) + } + }) + + # Method for importing the previous R environment + observeEvent(input$load, { + if (is.null(input$rerun)) { + shinyalert("Uh oh!", "It appears you haven't selected a .RData file to import.", "error") + } else { + load(input$rerun$datapath[[1]]) + + + if (file_ext(input$rerun$datapath) == "RData") { + validFile <- TRUE + } + + if (validFile) { + data <<- dataSubset + dirLocation <<- dirLocation + numCycles <<- numCycles + dQdVData <<- dQdVData + total <<- total + cycle_facts <<- cycle_facts + + output$channels <- renderDataTable(generateDataTable(deleteButtonCol(data, "delete_button"))) + + enable("graphBuilder") + } else { + shinyalert("That isn't right...", "Please upload an RData file.", "error") + } + } + }) + + observeEvent(input$whatGraph, { + showModal(graphModal) + }) + + # Data validation the masses imported from Excel, if valid they are placed into the datatable + observeEvent(input$excelImport, { + if (length(names(data)) <= 1) { + shinyalert("Uh oh!", "You need to import cells first!", "error") + removeModal() + } else { + tryCatch( + { + masses <- lapply(strsplit(strRep(input$masses, "\n", ","), ",", fixed = TRUE), as.double) + data$Mass <<- masses[[1]] + }, + error = function(cond) { + print(cond) + shinyalert("Something isn't right...", "The number of masses imported did not match the amount of cells present or the text contained some special characters. Please try again.", "error") + removeModal() + }, + finally = { + output$channels <- renderDataTable(generateDataTable(deleteButtonCol(data, "delete_button"))) + } + ) + } + }) + + # After the validation of the Arbin files, they macros (file name, and sheets) are taken and rendered in to a datatable + renderTable <- function() { + files <- input$files + + if (is.null(files)) { + return(NULL) + } + + file_sheet <- data.frame() + if (arbinCM) { + raw_data <- data.frame(name = files[["name"]], "sheet" = 1:nrow(files), datapath = files[["datapath"]]) + } else { + for (i in 1:nrow(files)) { + sheets <- excel_sheets(files[i, 4]) + file_sheet <- rbind(file_sheet, data.frame( + group = basename(dirLocation), program = rep(input$dirName, length(sheets)), name = rep(files[["name"]][i], length(sheets)), "sheet" = sheets, "Mass" = rep(0, length(sheets)), + datapath = rep(files[["datapath"]][i], length(sheets)), grouppath = rep(dirLocation, length(sheets)), area = rep(input$area, length(sheets)) + )) + } + + raw_data <- filter(file_sheet, grepl("Channel", sheet) & !grepl("Chart", sheet)) + } + + if (!is.null(dim(data)[1]) & !is.null(size(raw_data))) { + new_rows <- intersect(raw_data$sheet, data[data["group"] == basename(dirLocation)]$sheet) + data <<- data[which(data$sheet %in% new_rows), ] + data$datapath <<- raw_data$datapath + } else { + if (input$batchProcessing) { + tryCatch( + { + data <<- rbind(data, raw_data) + }, + error = function(cond) { + data <<- raw_data + } + ) + } else { + data <<- raw_data + } + } + + if (arbinCM) { + output$channels <- renderDataTable(data, + editable = TRUE, options = list(columnDefs = list(list(visible = FALSE, targets = c(3)))), + colnames = c("File", "Sheet", "Filepath") + ) + } else { + output$channels <- renderDataTable(generateDataTable(deleteButtonCol(data, "delete_button"))) + } + } + + # After some data validation, the main analysis is run on click of the"Run Analysis" button + observeEvent(input$submit, { + if (length(names(data)) <= 1) { + shinyalert("Uh oh!", "You need to import cells first!", "error") + } else if (is.na(dirLocation) | dirLocation == "") { + shinyalert("Uh oh!", "You need to enter a directory name first!", "error") + } else if (input$dirName == "") { + shinyalert("Uh oh!", "You need to enter an analysis name first!", "error") + } else if (sum(data$Mass) == 0) { + shinyalert("Uh oh!", "You have not entered any masses. Do you wish to continue?", + type = "warning", showConfirmButton = TRUE, showCancelButton = TRUE, confirmButtonText = "Continue", cancelButtonText = "Abort", + callbackR = function(x) { + if (x) { + runscript() + } + } + ) + } else { + runscript() + } + }) + + # This function responsible for analysis of the + runscript <- function() { + groupList <- unique(data[["group"]]) + + for (group in groupList) { + dataSubset <- data[data[["group"]] == group, ] + dirLocation <- dataSubset[["grouppath"]][1] + programName <- dataSubset[["program"]][1] + + # Sets up a progress bar in which to estimate how long the execution of the code will take + progress <- Progress$new(session, min = 0, max = nrow(dataSubset)) + progress$set(message = paste(group, ": Plugging and chugging...\n"), detail = "Starting up...") + + # Closes all graphics devices that may be lingering (prevents an excess from opening and slowing down the analysis) + while (dev.cur() != 1) { + dev.off() + } + + # Resets the variables for the graph builder so new results are concatenated to old ones + numCycles <<- data.frame() + dQdVData <<- data.frame() + total <<- data.frame() + cycle_facts <<- data.frame() + + # Disable all input fields to prevent errors occurring from changing values + disable("files") + disable("lowV") + disable("highV") + disable("dirLocation") + disable("submit") + disable("excelImport") + disable("gGraphs") + disable("peakFit") + disable("area") + disable("perActive") + disable("capActive") + + # Creates the directory in which all dataSubset will be stored + dir.create(paste(dirLocation, programName, sep = "/")) + + # Update the status once all set-up functions are complete + progress$set(detail = "Starting first cell...") + + # ###### + # + # The bulk of the analysis occurs within the loop. Each iteratin of the loop corresponds to a cell. + # + # ###### + for (row in 1:nrow(dataSubset)) { + + # ###### + # + # This is where all code that should be executed on a"per cell" basis, to prepare for analysis + # + # ###### + + # Import the excel sheet corresponding to cell of interest + if (arbinCM) { + tmp_excel <- read.csv(toString(dataSubset$dataSubsetpath[row])) + names(tmp_excel) <- c("Index", "Test_Time(s)", "Date_Time", "Step_Time(s)", "Step_Index", "Cycle_Index", "Current(A)", "Voltage(V)", "Charge_Capacity(Ah)", "Discharge_Capacity(Ah)", "Charge_Energy(Wh)", "Discharge_Energy(Wh)") + } else { + tmp_excel <- read_excel(toString(dataSubset$datapath[row]), toString(dataSubset$sheet[row])) + } + + # Create an nested directory for all the dataSubset and, if applicable, then further folders for graphs of interest + dir.create(paste(dirLocation, "/", programName, dataSubset$sheet[row], sep = "/")) + if (is.element("dQdV Graphs", input$gGraphs)) dir.create(paste(dirLocation, programName, dataSubset$sheet[row], "dQdV Plots", sep = "/")) + if (is.element("Voltage Profiles", input$gGraphs)) dir.create(paste(dirLocation, programName, dataSubset$sheet[row], "Voltage Profiles", sep = "/")) + if (is.element("Voltage vs. Time", input$gGraphs)) dir.create(paste(dirLocation, programName, dataSubset$sheet[row], "Voltage v Time", sep = "/")) + + # Check if masses have been imported, if they have not then all future calculations will be done on a raw capacity basis + if (sum(dataSubset$Mass) != 0) { + ylabel <- "Capacity (mAh/g)" + + tmp_excel$Q.d <- as.numeric(tmp_excel$`Discharge_Capacity(Ah)` * (1000 / dataSubset$Mass[row])) + tmp_excel$Q.c <- as.numeric(tmp_excel$`Charge_Capacity(Ah)` * (1000 / dataSubset$Mass[row])) + + tmp_excel$CC <- tmp_excel$Q.d - tmp_excel$Q.c + tmp_excel$CE <- (tmp_excel$Q.d / tmp_excel$Q.c) * 100 + } else { + ylabel <- "Capacity (Ah)" + + tmp_excel$CC <- tmp_excel$`Discharge_Capacity(Ah)` - tmp_excel$`Charge_Capacity(Ah)` + tmp_excel$CE <- (tmp_excel$`Discharge_Capacity(Ah)` / tmp_excel$`Charge_Capacity(Ah)`) * 100 + } + tmp_excel$Cell <- row + tmp_excel$CE[is.infinite(tmp_excel$CE) | is.nan(tmp_excel$CE) | tmp_excel$CE > 200] <- 0 + + # ###### + # + # This loop iterates through each cycle of the cell. + # + # ###### + cycles <- split(tmp_excel, tmp_excel$Cycle_Index) + prev_c <- 0 + lastCC <- 0 + ch_dch <- TRUE + durations <- vector(length = 4) + caps <- vector(length = 4) + prev <- TRUE + dchV <- 0 + chV <- 0 + i <- 1 + for (cycle in cycles) { + + # ###### + # + # Within each cycle, take out the individual steps. These include the charge, discharge, and others. + # + # ###### + + progress$set(detail = paste("Analyzing cell", row, ", cycle", i)) + + steps <- split(cycle, cycle$Step_Index) + n <- 1 + + for (step in steps) { + + # ###### + # + # Isolation of the charge and discharge cycles. The algorithm is as follows: + # + # If the change in voltage for the step is greater than 0.5V, it is a charge or dicharge cycle. Then, the direction (sign) of the current determines + # if it is charge or discharge (positive current = charge step, negative current is a discharge cycle). + # + # ###### + if (abs(tail(step$"Voltage(V)", 1) - step$"Voltage(V)"[[1]]) > 0.5) { + + # ###### + # + # All code that should be executed for every charge/discharge cycles should be written here. + # + # ###### + lastCC <- n + if (step$"Current(A)"[[1]] > 0) { + chV <- (1 / (tail(step$`Charge_Capacity(Ah)`, 1) - step$`Charge_Capacity(Ah)`[[1]])) * trapz(step$`Charge_Capacity(Ah)`, step$`Voltage(V)`) + dQCdV <- diff(step$`Charge_Capacity(Ah)`) / diff(step$`Voltage(V)`) + dQdVData <<- rbind(dQdVData, data.frame(cycle = rep(i, length(dQCdV) + 1), cell = rep(row, length(dQCdV) + 1), c_d = rep(0, length(dQCdV) + 1), voltage = step$`Voltage(V)`, dQdV = c(0, dQCdV), F_L = rep(0, length(dQCdV) + 1), CC = step[["CC"]][1:nrow(step)])) + + durations[1] <- tail(step$"Test_Time(s)", 1) - step$"Test_Time(s)"[[1]] + caps[1] <- tail(step$"Charge_Capacity(Ah)", 1) - step$"Charge_Capacity(Ah)"[[1]] + ch_dch <- TRUE + } else { + dchV <- (1 / (tail(step$`Discharge_Capacity(Ah)`, 1) - step$`Discharge_Capacity(Ah)`[[1]])) * trapz(step$`Discharge_Capacity(Ah)`, step$`Voltage(V)`) + dQDdV <- diff(step$`Discharge_Capacity(Ah)`) / diff(step$`Voltage(V)`) + + durations[3] <- tail(step$"Test_Time(s)", 1) - step$"Test_Time(s)"[[1]] + caps[3] <- tail(step$"Discharge_Capacity(Ah)", 1) - step$"Discharge_Capacity(Ah)"[[1]] + ch_dch <- FALSE + if (abs(prev_c - step$`Current(A)`[[1]]) > 0.0005) { + if (!arbinCM) dQdVData <<- rbind(dQdVData, data.frame(cycle = rep(i, length(dQDdV) + 1), cell = rep(row, length(dQDdV) + 1), c_d = rep(1, length(dQDdV) + 1), voltage = step$`Voltage(V)`, dQdV = c(0, dQDdV), F_L = rep(1, length(dQDdV) + 1), CC = step[["CC"]][1:nrow(step)])) + prev_c <- step$`Current(A)`[[1]] + } else { + if (!arbinCM) dQdVData <<- rbind(dQdVData, data.frame(cycle = rep(i, length(dQDdV) + 1), cell = rep(row, length(dQDdV) + 1), c_d = rep(1, length(dQDdV) + 1), voltage = step$`Voltage(V)`, dQdV = c(0, dQDdV), F_L = rep(0, length(dQDdV) + 1), CC = step[["CC"]][1:nrow(step)])) + } + } + } else if (n - lastCC == 1 & abs(tail(step$"Voltage(V)", 1) - step$"Voltage(V)"[[1]]) < 0.001) { + if (step$"Current(A)"[[1]] > 0) { + durations[2] <- tail(step$"Test_Time(s)", 1) - step$"Test_Time(s)"[[1]] + caps[2] <- tail(step$"Charge_Capacity(Ah)", 1) - step$"Charge_Capacity(Ah)"[[1]] + } else { + durations[4] <- tail(step$"Test_Time(s)", 1) - step$"Test_Time(s)"[[1]] + caps[4] <- tail(step$"Discharge_Capacity(Ah)", 1) - step$"Discharge_Capacity(Ah)"[[1]] + } + } + ch_dch <- FALSE + n <- n + 1 + } + + # ###### + # + # Code meant to be run on dataSubset"per cycle" should be written here + # + # ###### + + dQdVData <<- dQdVData[is.finite(dQdVData$voltage), ] + dQdVData <<- dQdVData[is.finite(dQdVData$dQdV), ] + + if (sum(dataSubset$Mass) != 0) { + DCap <- tail(cycle$Q.d, 1) + CCap <- tail(cycle$Q.c, 1) + } else { + DCap <- tail(cycle$`Discharge_Capacity(Ah)`, 1) + CCap <- tail(cycle$`Charge_Capacity(Ah)`, 1) + } + + timeCVFracCh <- durations[1] / (durations[1] + durations[2]) + timeCVFracDch <- durations[3] / (durations[3] + durations[4]) + capCVFracCh <- caps[1] / (caps[1] + caps[2]) + capCVFracDch <- caps[3] / (caps[3] + caps[4]) + + if (max(tmp_excel$voltage) > 2) { + CE <- DCap / CCap + } else { + CE <- CCap / DCap + } + + # Record charge and discharge voltage, then calculate the delta and average voltage + cycle_facts <<- rbind(cycle_facts, data.frame( + cycle = i, cell = row, chV = chV, dchV = dchV, avgV = (dchV + chV) / 2, + dV = chV - dchV, DCap = DCap, raw_DCap = tail(cycle$"Discharge_Capacity(Ah)", 1), raw_CCap = tail(cycle$"Charge_Capacity(Ah)", 1), CCap = CCap, CE = CE * 100, lostCap = CCap - DCap, cellFade = if (i == 1) { + 0 + } else { + DCap - tail(cycle_facts$DCap, 1) + }, + cycleTime = tail(cycle$`Test_Time(s)`, 1) - cycle$`Test_Time(s)`[[1]], timeCVFracCh = timeCVFracCh, timeCVFracDch = timeCVFracDch, + capCVFracCh = capCVFracCh, capCVRatioDch = capCVFracDch + )) + + i <- i + 1 + } + + # ###### + # + # Code meant to be run on dataSubset"per cell" should be written here + # + # ###### + + cell_dataSubset <- cycle_facts[cycle_facts$cell == row, ] + + # Discharge capacity plotting, with coulombic efficiency being plotted alongside + if (is.element("Discharge Capacity", input$gGraphs)) { + png(paste(dirLocation, "/", programName, "/", dataSubset$sheet[row], "/", dataSubset$sheet[row], " Discharge Capacity Plot.png", sep = "")) + eol <- cell_dataSubset$`DCap`[[1]] * 0.8 + plot(cell_dataSubset$cycle, cell_dataSubset$DCap, type = "p", main = paste("Discharge Capacity for", programName), xlab = NA, ylab = paste("Discharge", ylabel), mai = c(1, 1, 1, 1)) + abline(h = eol, lty = "dotted") + par(new = T) + plot(cell_dataSubset$cycle, cell_dataSubset$CE, type = "p", axes = F, col = "red", ylab = NA, xlab = "Cycle", ylim = c(0, 105)) + mtext(side = 4, line = 3, "Coulombic Efficiency (%)", col = "red") + axis(side = 4, col = "red", col.axis = "red") + dev.off() + } + + # Discharge areal capacity plotting, with coulombic efficiency being plotted alongside + if (is.element("Discharge Areal Capacity", input$gGraphs)) { + png(paste(dirLocation, "/", programName, "/", dataSubset$sheet[row], "/", dataSubset$sheet[row], " Discharge Areal Capacity Plot.png", sep = "")) + new_par <- old_par <- par("mar") + new_par[4] <- old_par[2] + par(mar = new_par) + eol <- ((cell_dataSubset$DCap[[1]] * 1000) / dataSubset$area[row]) * 0.8 + plot(cell_dataSubset$cycle, ((cell_dataSubset$DCap * 1000) / dataSubset$area[row]), type = "p", main = paste("Discharge Areal Capacity for", programName), xlab = NA, ylab = "Discharge Capacity (mAh/cm^2)", mai = c(1, 1, 1, 2)) + abline(h = eol, lty = "dotted") + par(new = T) + plot(cell_dataSubset$cycle, cell_dataSubset$CE, type = "p", axes = F, col = "red", ylab = NA, xlab = "Cycle", ylim = c(0, 105)) + mtext(side = 4, line = 3, "Coulombic Efficiency (%)", col = "red") + axis(side = 4, col = "red", col.axis = "red") + dev.off() + } + + + # Average voltage plotting + if (is.element("Average Voltage", input$gGraphs)) { + png(paste(dirLocation, "/", programName, "/", dataSubset$sheet[row], "/", dataSubset$sheet[row], " Average Voltage Plot.png", sep = "")) + plot(cell_dataSubset$cycle, cell_dataSubset$chV, col = "blue", main = paste("Average Voltage Plot for", programName, dataSubset$sheet[row]), xlab = "Cycle", ylab = "Voltage (V)", ylim = c(min(cell_dataSubset[, 2:4]), max(cell_dataSubset[, 2:4]))) + points(cell_dataSubset$cycle, cell_dataSubset$dchV, col = "red", main = paste("Average Voltage Plot for", programName, dataSubset$sheet[row]), xlab = "Cycle", ylab = "Voltage (V)") + points(cell_dataSubset$cycle, cell_dataSubset$avgV, col = "black", main = paste("Average Voltage Plot for", programName, dataSubset$sheet[row]), xlab = "Cycle", ylab = "Voltage (V)") + legend("bottomright", c("Charge Voltage", "Discharge Voltage", "Average Voltage"), col = c("blue", "red", "black"), pch = 19) + dev.off() + } + + # Delta voltage plotting + if (is.element("Delta Voltage", input$gGraphs)) { + png(paste(dirLocation, "/", programName, "/", dataSubset$sheet[row], "/", dataSubset$sheet[row], " Delta Voltage Plot.png", sep = "")) + plot(cell_dataSubset$cycle, cell_dataSubset$dV, main = paste("Delta Voltage Plot for", programName, dataSubset$sheet[row]), xlab = "Cycle", ylab = "Voltage (V)", ylim = c(0, 0.5)) + dev.off() + } + + # Capacity Loss plotting + if (is.element("Capacity Loss", input$gGraphs)) { + png(paste(dirLocation, "/", programName, "/", dataSubset$sheet[row], "/", dataSubset$sheet[row], " Capacity Loss Plot.png", sep = "")) + plot(cell_dataSubset$cycle, cell_dataSubset$lostCap, main = paste("Capacity Loss Plot for", programName, dataSubset$sheet[row]), xlab = "Cycle", ylab = ylabel, ylim = c(mean(cell_dataSubset$lostCap) + (2 * sd(cell_dataSubset$lostCap)), mean(cell_dataSubset$lostCap) - (1.5 * sd(cell_dataSubset$lostCap)))) + abline(h = median(cell_dataSubset$lostCap), lty = "dotted") + dev.off() + } + + if (is.element("dQdV Plots", input$gAnim)) { + dQdVplot <- function() { + tmp_dataSubset <- dQdVData[dQdVData$cell == row, ] + first_cycle <- dQdVData[dQdVData$cell == row & dQdVData$cycle == 2, ] + dataSubsetlist <- split(tmp_dataSubset, tmp_dataSubset$cycle) + lapply(dataSubsetlist, function(plotdataSubset) { + p <- plot(plotdataSubset$voltage, plotdataSubset$dQdV, + main = paste("dQdV Plot for", programName, dataSubset$sheet[row], "Cycle", plotdataSubset$cycle[[1]]), xlab = "Voltage (V)", ylab = "dQdV (Ah/V)", + xlim = c(min(tmp_dataSubset$voltage), max(tmp_dataSubset$voltage)), ylim = c(min(tmp_dataSubset$dQdV), max(tmp_dataSubset$dQdV)) + ) + + points(first_cycle$voltage, first_cycle$dQdV, col = rgb(red = 1, green = 0, blue = 0, alpha = 0.5)) + }) + } + save_gif(dQdVplot(), paste(dirLocation, programName, dataSubset$sheet[row], "dQdV Animation.gif", sep = "/"), delay = 0.2) + } + + if (is.element("Voltage Profiles", input$gAnim)) { + vpPlot <- function() { + first_cycle <- tmp_excel[tmp_excel$`Cycle_Index` == 2, ] + dataSubsetlist <- split(tmp_excel, tmp_excel$`Cycle_Index`) + lapply(dataSubsetlist, function(plotdataSubset) { + p <- plot(plotdataSubset$CC, plotdataSubset$`Voltage(V)`, + main = paste("Voltage Profile for", programName, dataSubset$sheet[row], "Cycle", plotdataSubset$`Cycle_Index`[[1]]), xlab = ylabel, ylab = "Voltage (V)", + xlim = c(min(tmp_excel$CC), max(tmp_excel$CC)), ylim = c(min(tmp_excel$`Voltage(V)`), max(tmp_excel$`Voltage(V)`)) + ) + + points(first_cycle$CC, first_cycle$`Voltage(V)`, col = rgb(red = 1, green = 0, blue = 0, alpha = 0.5)) + }) + } + save_gif(vpPlot(), paste(dirLocation, programName, dataSubset$sheet[row], "Voltage Profile Animation.gif", sep = "/"), delay = 0.2) + } + + # Save all dataSubset within the cell's directory + write.csv(tmp_excel, file = paste(dirLocation, "/", programName, "/", dataSubset$sheet[row], "/", dataSubset$sheet[row], ".csv", sep = "")) + + # Append summation dataSubset to the larger dataSubsetsets to be worked with later + final <- rbind(final, tmp_excel) + numCycles <<- rbind(numCycles, data.frame(sheet = dataSubset$sheet[row], cycles = nrow(cell_dataSubset))) + + # Update progress bar + progress$set(value = row, detail = paste("Finished", row, " of", nrow(dataSubset), " cells.")) + } + + # ###### + # + # Code meant to be run on all dataSubset of all cells should be written here. + # + # ###### + + # With iterations complete, final calculations are being worked + progress$set(detail = "Wrapping up...") + + # Get the last status of each cycle for each cell (namely capacity) + DCap <- cycle_facts[c("cycle", "DCap")] %>% + group_by(cycle) %>% + summarise_each(mean) + CE <- cycle_facts[c("cycle", "CE")] %>% + group_by(cycle) %>% + summarise_each(mean) + capSEs <- cycle_facts[c("cycle", "DCap")] %>% + group_by(cycle) %>% + summarise_each(se) + ceSEs <- cycle_facts[c("cycle", "CE")] %>% + group_by(cycle) %>% + summarise_each(se) + stats <- data.frame(cbind(cycle = capSEs$cycle, DCap = DCap$DCap, CE = CE$CE, capSE = capSEs$DCap, ceSE = ceSEs$CE)) + + # Send all the dataSubset to a global variable to be used elsewhere + total <<- final + + tryCatch( + { + # Total dishcharge capacity plotting + if (is.element("Total Discharge Capacity", input$gGraphs)) { + png(paste(dirLocation, "/", programName, "/", "Total Discharge Capacity Plot.png", sep = "")) + eol <- max(stats$DCap) * 0.8 + plot(stats$cycle, stats$DCap, type = "p", main = paste("Discharge Capacity for", programName), xlab = NA, ylab = paste("Discharge", ylabel), mai = c(1, 1, 1, 1)) + arrows(stats$cycle, stats$DCap - stats$capSE, stats$cycle, stats$DCap + stats$capSE, length = 0.05, angle = 90, code = 3) + abline(h = eol, lty = "dotted") + par(new = T) + plot(stats$cycle, stats$CE, type = "p", axes = F, col = "red", ylab = NA, xlab = "Cycle", ylim = c(0, 105)) + arrows(stats$cycle, stats$CE - stats$ceSE, stats$cycle, stats$CE + stats$ceSE, length = 0.05, angle = 90, code = 3, col = "red") + axis(side = 4, col = "red") + mtext(side = 4, line = 2, "Coulombic Efficiency (%)") + dev.off() + } + }, + error = function(cond) { + print(cond) + } + ) + + # Save total dataSubset and stats + write.csv(stats, file = paste(dirLocation, "/", programName, "/", basename(dirLocation), " Summary.csv", sep = "")) + if (!arbinCM) write.csv(dQdVData, file = paste(dirLocation, "/", programName, "/", basename(dirLocation), " dQdV data.csv", sep = "")) + write.csv(cycle_facts, file = paste(dirLocation, "/", programName, "/", basename(dirLocation), " Cycle Facts.csv", sep = "")) + + # If a histor directory does not exist, create it. Save all the dataSubset revelant to plotting to a RdataSubset file. + if (!dir.exists(paste(dirLocation, "history", sep = "/"))) { + dir.create(paste(dirLocation, "history", sep = "/")) + } + + dirName <<- programName + data <- dataSubset + + save(dirLocation, dirName, data, dQdVData, total, cycle_facts, numCycles, file = paste(dirLocation, "/history/", programName, ".Rdata", sep = "")) + } + + # Modal for completed analysis + shinyalert("Analysis Complete!", paste("All your data are now in ", dirLocation, "/", programName, sep = ""), + type = "success", showConfirmButton = TRUE, showCancelButton = TRUE, confirmButtonText = "Generate Origin File", cancelButtonText = "Continue", + callbackR = function(x) { + if (x) { + export_to_origin() + } + } + ) + + # Finish progress bar + progress$set(value = nrow(data)) + + # Re-enable all input fields, including graph builder + enable("files") + enable("lowV") + enable("highV") + enable("dirLocation") + enable("submit") + enable("excelImport") + enable("gGraphs") + enable("peakFit") + enable("area") + enable("perActive") + enable("capActive") + enable("graphBuilder") + + + for (num in 1:size(groupList)[2]) { + # Close progress bar + progress$close() + } + } + + # ###### + # + # Graph Builder Processing + # + # ###### + output$outputPlot <- renderPlot( + { + tmp_data <<- data.frame() + normalTime <<- data.frame() + + bounds <<- c(input$xMin, input$xMax, input$yMin, input$yMax) + + # Define function to normalize Voltage vs. Time plots + normalizeTime <- function(x) { + return(x - x[[1]]) + } + + # ###### + # + # Switch statements defining the bulk of the processing, depending on the desired graph + # + # ###### + if (input$perType == "Within Analysis") { + sheetName <<- TRUE + + # Get the indicies in which the desired cells are in the data frame containing the number of cycles + cellIndex <- match(input$cells, numCycles$sheet) + + switch(input$typeGraph, + "dQdV Graphs" = { + tmp_data <<- data.frame(x = dQdVData[dQdVData$cell %in% cellIndex, ]$voltage, y = dQdVData[dQdVData$cell %in% cellIndex, ]$dQdV, cycle = dQdVData[dQdVData$cell %in% cellIndex, ]$cycle, cell = dQdVData[dQdVData$cell %in% cellIndex, ]$cell) + tmp_data <<- tmp_data[tmp_data$cycle == sort(as.numeric(input$renderCycles)), ] + + titleLabel <<- "dQdV Plot" + xlabel <<- "Voltage (V)" + ylabel <<- "dQdV (mAh/V)" + }, + "Voltage Profiles" = { + tmp_data <<- data.frame(x = total[total$Cell %in% cellIndex, ]$CC, y = total[total$Cell %in% cellIndex, ]$`Voltage(V)`, cycle = total[total$Cell %in% cellIndex, ]$`Cycle_Index`, cell = total[total$Cell %in% cellIndex, ]$Cell) + tmp_data <<- tmp_data[tmp_data$cycle == sort(as.numeric(input$renderCycles)), ] + + titleLabel <<- "Voltage Profile" + if (sum(data$Mass) != 0) { + xlabel <- "Continuous Capacity (mAh/g)" + } else { + xlabel <- "Continuous Capacity (Ah)" + } + ylabel <<- "Voltage (V)" + }, + "Voltage vs. Time" = { + tmp_data <<- data.frame(x = (total[total$Cell %in% cellIndex, ]$`Test_Time(s)` / 60), y = total[total$Cell %in% cellIndex, ]$`Voltage(V)`, cycle = total[total$Cell %in% cellIndex, ]$`Cycle_Index`, cell = total[total$Cell %in% cellIndex, ]$Cell) + tmp_data <<- tmp_data[tmp_data$cycle %in% input$renderCycles, ] + + x <- 0 + + for (cell in cellIndex) { + normalTime <<- c(normalTime, t(aggregate(tmp_data[tmp_data$cell == cell, ]$x, by = list(tmp_data[tmp_data$cell == cell, ]$cycle), normalizeTime)[, 2])) + } + + tmp_data <<- data.frame(x = unlist(normalTime), y = tmp_data$y, cycle = tmp_data$cycle, cell = tmp_data$cell) + tmp_data <<- tmp_data[tmp_data$y >= 0.01, ] + + titleLabel <<- "Voltge vs. Time Plot" + xlabel <<- "Time (min)" + ylabel <<- "Voltage (V)" + }, + "Charge Voltage" = { + tmp_data <<- data.frame(x = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle, y = cycle_facts[cycle_facts$cell %in% cellIndex, ]$chV, cycle = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle, cell = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cell) + + titleLabel <<- "Charge Voltage Plot " + xlabel <<- "Cycle" + ylabel <<- "Voltage (V)" + }, + "Discharge Voltage" = { + tmp_data <<- data.frame(x = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle, y = cycle_facts[cycle_facts$cell %in% cellIndex, ]$dchV, cell = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cell, cycle = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle) + + titleLabel <<- "Discharge Voltage Plot " + xlabel <<- "Cycle" + ylabel <<- "Voltage (V)" + }, + "Average Voltage" = { + tmp_data <<- data.frame(x = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle, y = cycle_facts[cycle_facts$cell %in% cellIndex, ]$avgV, cell = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cell, cycle = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle) + + titleLabel <<- "Average Voltage Plot " + xlabel <<- "Cycle" + ylabel <<- "Voltage (V)" + }, + "Delta Voltage" = { + tmp_data <<- data.frame(x = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle, y = cycle_facts[cycle_facts$cell %in% cellIndex, ]$dV, cell = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cell, cycle = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle) + + titleLabel <<- "Delta Voltage Plot " + xlabel <<- "Cycle" + ylabel <<- "Voltage (V)" + }, + "Discharge Capacity" = { + tmp_data <<- data.frame(x = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle, y = cycle_facts[cycle_facts$cell %in% cellIndex, ]$DCap, cell = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cell, cycle = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle) + + titleLabel <<- "Discharge Capacity Plot " + xlabel <<- "Cycle" + if (sum(data$Mass) != 0) { + ylabel <<- "Discharge Capacity (mAh/g)" + } else { + ylabel <<- "Discharge Capacity (Ah)" + } + }, + "Charge Capacity" = { + tmp_data <<- data.frame(x = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle, y = cycle_facts[cycle_facts$cell %in% cellIndex, ]$CCap, cell = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cell, cycle = cycle_facts[cycle_facts$cell %in% cellIndex, ]$cycle) + + titleLabel <<- "Charge Capacity Plot " + xlabel <<- "Cycle" + if (sum(data$Mass) != 0) { + ylabel <<- "Charge Capacity (mAh/g)" + } else { + ylabel <<- "Charge Capacity (Ah)" + } + } + ) + + tmp_data$color <<- sapply(tmp_data$cycle, function(x) { + match(x, input$renderCycles, nomatch = 1) + }) + tmp_data$symbol <<- sapply(tmp_data$cell, function(x) { + match(x, cellIndex) + }) + } else if (input$perType == "Between Analyses") { + switch(input$typeGraph, + "Charge Voltage" = { + x <- 0 + chV <- cycle_facts[c("cycle", "chV")] %>% + group_by(cycle) %>% + summarise_each(mean) + chVSE <- cycle_facts[c("cycle", "chV")] %>% + group_by(cycle) %>% + summarise_each(se) + + tryCatch( + { + comp_chV <- compCycleFacts[c("cycle", "chV")] %>% + group_by(cycle) %>% + summarise_each(mean) + comp_chVSE <- compCycleFacts[c("cycle", "chV")] %>% + group_by(cycle) %>% + summarise_each(se) + + tmp_data <<- data.frame(x = c(chV$cycle, comp_chV$cycle), y = c(chV$chV, comp_chV$chV), se = c(chVSE$chV, comp_chVSE$chV), cell = c(rep(1, length(chV$cycle)), rep(2, length(comp_chV$cycle))), cycle = c(chV$cycle, comp_chV$cycle)) + tmp_data$symbol <<- rep(1, nrow(tmp_data)) + tmp_data$color <<- sapply(tmp_data$cell, function(x) { + match(x, c(1, 2)) + }) + + titleLabel <<- "Charge Voltage Plot " + xlabel <<- "Cycle" + ylabel <<- "Voltage (V)" + }, + error = function(x) { + print(x) + } + ) + }, + "Discharge Voltage" = { + x <- 0 + dchV <- cycle_facts[c("cycle", "dchV")] %>% + group_by(cycle) %>% + summarise_each(mean) + dchVSE <- cycle_facts[c("cycle", "dchV")] %>% + group_by(cycle) %>% + summarise_each(se) + + tryCatch( + { + comp_dchV <- compCycleFacts[c("cycle", "dchV")] %>% + group_by(cycle) %>% + summarise_each(mean) + comp_dchVSE <- compCycleFacts[c("cycle", "dchV")] %>% + group_by(cycle) %>% + summarise_each(se) + + tmp_data <<- data.frame(x = c(dchV$cycle, comp_dchV$cycle), y = c(dchV$dchV, comp_dchV$dchV), se = c(dchVSE$dchV, comp_dchVSE$dchV), cell = c(rep(1, length(dchV$cycle)), rep(2, length(comp_dchV$cycle))), cycle = c(dchV$cycle, comp_dchV$cycle)) + tmp_data$symbol <<- rep(1, nrow(tmp_data)) + tmp_data$color <<- sapply(tmp_data$cell, function(x) { + match(x, c(1, 2)) + }) + + titleLabel <<- "Disharge Voltage Plot " + xlabel <<- "Cycle" + ylabel <<- "Voltage (V)" + }, + error = function(x) { + print(x) + } + ) + }, + "Average Voltage" = { + avgV <- cycle_facts[c("cycle", "avgV")] %>% + group_by(cycle) %>% + summarise_each(mean) + avgVSE <- cycle_facts[c("cycle", "avgV")] %>% + group_by(cycle) %>% + summarise_each(se) + + tryCatch( + { + comp_avgV <- compCycleFacts[c("cycle", "avgV")] %>% + group_by(cycle) %>% + summarise_each(mean) + comp_avgVSE <- compCycleFacts[c("cycle", "avgV")] %>% + group_by(cycle) %>% + summarise_each(se) + + tmp_data <<- data.frame(x = c(avgV$cycle, comp_avgV$cycle), y = c(avgV$avgV, comp_avgV$avgV), se = c(avgVSE$avgV, comp_avgVSE$avgV), cell = c(rep(1, length(avgV$cycle)), rep(2, length(comp_avgV$cycle))), cycle = c(avgV$cycle, comp_avgV$cycle)) + tmp_data$symbol <<- rep(1, nrow(tmp_data)) + tmp_data$color <<- sapply(tmp_data$cell, function(x) { + match(x, c(1, 2)) + }) + + titleLabel <<- "Average Voltage Plot " + xlabel <<- "Cycle" + ylabel <<- "Voltage (V)" + }, + error = function(x) { + print(x) + } + ) + }, + "Delta Voltage" = { + dV <- cycle_facts[c("cycle", "dV")] %>% + group_by(cycle) %>% + summarise_each(mean) + dVSE <- cycle_facts[c("cycle", "dV")] %>% + group_by(cycle) %>% + summarise_each(se) + + tryCatch( + { + comp_dV <- compCycleFacts[c("cycle", "dV")] %>% + group_by(cycle) %>% + summarise_each(mean) + comp_dVSE <- compCycleFacts[c("cycle", "dV")] %>% + group_by(cycle) %>% + summarise_each(se) + + tmp_data <<- data.frame(x = c(dV$cycle, comp_dV$cycle), y = c(dV$dV, comp_dV$dV), se = c(dVSE$dV, comp_dVSE$dV), cell = c(rep(1, length(dV$cycle)), rep(2, length(comp_dV$cycle))), cycle = c(dV$cycle, comp_dV$cycle)) + tmp_data$symbol <<- rep(1, nrow(tmp_data)) + tmp_data$color <<- sapply(tmp_data$cell, function(x) { + match(x, c(1, 2)) + }) + + titleLabel <<- "Delta Voltage Plot " + xlabel <<- "Cycle" + ylabel <<- "Voltage (V)" + }, + error = function(x) { + print(x) + } + ) + }, + "Discharge Capacity" = { + DCap <- cycle_facts[c("cycle", "DCap")] %>% + group_by(cycle) %>% + summarise_each(mean) + DCapSE <- cycle_facts[c("cycle", "DCap")] %>% + group_by(cycle) %>% + summarise_each(se) + + tryCatch( + { + comp_DCap <- compCycleFacts[c("cycle", "DCap")] %>% + group_by(cycle) %>% + summarise_each(mean) + comp_DCapSE <- compCycleFacts[c("cycle", "DCap")] %>% + group_by(cycle) %>% + summarise_each(se) + + tmp_data <<- data.frame(x = c(DCap$cycle, comp_DCap$cycle), y = c(DCap$DCap, comp_DCap$DCap), se = c(DCapSE$DCap, comp_DCapSE$DCap), cell = c(rep(1, length(DCap$cycle)), rep(2, length(comp_DCap$cycle))), cycle = c(DCap$cycle, comp_DCap$cycle)) + tmp_data$symbol <<- rep(1, nrow(tmp_data)) + tmp_data$color <<- sapply(tmp_data$cell, function(x) { + match(x, c(1, 2)) + }) + + titleLabel <<- "Discharge Capacity Plot " + xlabel <<- "Cycle" + if (sum(data$Mass) != 0) { + ylabel <<- "Discharge Capacity (mAh/g)" + } else { + ylabel <<- "Discharge Capacity (Ah)" + } + }, + error = function(x) { + print(x) + } + ) + }, + "Charge Capacity" = { + CCap <- cycle_facts[c("cycle", "CCap")] %>% + group_by(cycle) %>% + summarise_each(mean) + CCapSE <- cycle_facts[c("cycle", "CCap")] %>% + group_by(cycle) %>% + summarise_each(se) + + tryCatch( + { + comp_CCap <- compCycleFacts[c("cycle", "CCap")] %>% + group_by(cycle) %>% + summarise_each(mean) + comp_CCapSE <- compCycleFacts[c("cycle", "CCap")] %>% + group_by(cycle) %>% + summarise_each(se) + + tmp_data <<- data.frame(x = c(CCap$cycle, comp_CCap$cycle), y = c(CCap$CCap, comp_CCap$CCap), se = c(CCapSE$CCap, comp_CCapSE$CCap), cell = c(rep(1, length(CCap$cycle)), rep(2, length(comp_CCap$cycle))), cycle = c(CCap$cycle, comp_CCap$cycle)) + tmp_data$symbol <<- rep(1, nrow(tmp_data)) + tmp_data$color <<- sapply(tmp_data$cell, function(x) { + match(x, c(1, 2)) + }) + + titleLabel <<- "Charge Capacity Plot " + xlabel <<- "Cycle" + if (sum(data$Mass) != 0) { + ylabel <<- "Charge Capacity (mAh/g)" + } else { + ylabel <<- "Charge Capacity (Ah)" + } + }, + error = function(x) { + print(x) + } + ) + }, + "Capacity Loss" = { + lostCap <- cycle_facts[c("cycle", "lostCap")] %>% + group_by(cycle) %>% + summarise_each(mean) + lostCapSE <- cycle_facts[c("cycle", "lostCap")] %>% + group_by(cycle) %>% + summarise_each(se) + + tryCatch( + { + comp_lostCap <- compCycleFacts[c("cycle", "lostCap")] %>% + group_by(cycle) %>% + summarise_each(mean) + comp_lostCapSE <- compCycleFacts[c("cycle", "lostCap")] %>% + group_by(cycle) %>% + summarise_each(se) + + tmp_data <<- data.frame(x = c(lostCap$cycle, comp_lostCap$cycle), y = c(lostCap$lostCap, comp_lostCap$lostCap), se = c(lostCapSE$lostCap, comp_lostCapSE$lostCap), cell = c(rep(1, length(lostCap$cycle)), rep(2, length(comp_lostCap$cycle))), cycle = c(lostCap$cycle, comp_lostCap$cycle)) + tmp_data$symbol <<- rep(1, nrow(tmp_data)) + tmp_data$color <<- sapply(tmp_data$cell, function(x) { + match(x, c(1, 2)) + }) + + titleLabel <<- "Charge Capacity Plot " + xlabel <<- "Cycle" + if (sum(data$Mass) != 0) { + ylabel <<- "Capacity (mAh/g)" + } else { + ylabel <<- "Capacity (Ah)" + } + }, + error = function(x) { + print(x) + } + ) + }, + ) + } + + tmp_data <<- tmp_data[is.finite(tmp_data$x), ] + tmp_data <<- tmp_data[is.finite(tmp_data$y), ] + tmp_data <<- tmp_data[is.finite(tmp_data$cycle), ] + tmp_data <<- tmp_data[is.finite(tmp_data$cell), ] + + if (any(sapply(bounds, is.na))) { + if (is.na(bounds[1])) bounds[1] <<- min(tmp_data$x) + if (is.na(bounds[2])) bounds[2] <<- max(tmp_data$x) + if (is.na(bounds[3])) bounds[3] <<- min(tmp_data$y) + if (is.na(bounds[4])) bounds[4] <<- max(tmp_data$y) + } + + tryCatch( + { + if (input$plotStyle == "o" | input$plotStyle == "p" | "se" %in% colnames(tmp_data)) { + par(mar = c(5.1, 6.1, 4.1, 2.1)) + + plot(tmp_data$x, tmp_data$y, type = input$plotStyle, col = tmp_data$color, pch = tmp_data$symbol, main = titleLabel, xlim = c(bounds[1], bounds[2]), ylim = c(bounds[3], bounds[4]), xlab = xlabel, ylab = ylabel, cex = input$pointSize, cex.axis = input$textSize, cex.lab = input$textSize, cex.main = input$textSize) + if ("se" %in% colnames(tmp_data)) { + arrows(tmp_data$x, tmp_data$y - tmp_data$se, tmp_data$x, tmp_data$y + tmp_data$se, col = tmp_data$color, length = 0.05, angle = 90, code = 3) + # legend("bottomright", legend = c(input$originalData, input$compareData), col = c(1,2), pch = 19) + } else { + if (input$typeGraph %in% c("dQdV Graphs", "Voltage Profiles", "Voltage vs. Time")) { + # legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells), col = c(unique(tmp_data$color), rep("black", length(input$cells))), pch = c(rep(19, length(unique(tmp_data$color))), 1:length(input$cells)), title ="Cycle", ncol=2) + } else { + # legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells), col = c(unique(tmp_data$color), rep("black", length(input$cells))), pch = c(1:length(input$cells)), title ="Cycle", ncol=2) + } + } + } else if (input$plotStyle == "l") { + newLine <- subset(tmp_data, tmp_data$color == 1 & tmp_data$symbol == 1) + plot(newLine$x, newLine$y, type = "l", col = newLine$color, lty = newLine$symbol, main = titleLabel, xlim = c(bounds[1], bounds[2]), ylim = c(bounds[3], bounds[4]), xlab = xlabel, ylab = ylabel, lwd = input$pointSize, cex.axis = input$textSize, cex.lab = input$textSize, cex.main = input$textSize) + + for (i in 1:length(input$renderCycles)) { + for (n in 1:length(cellIndex)) { + newLine <- subset(tmp_data, tmp_data$color == i & tmp_data$symbol == n) + lines(newLine$x, newLine$y, col = newLine$color, lty = newLine$symbol, lwd = input$pointSize) + } + } + if (input$typeGraph %in% c("dQdV Graphs", "Voltage Profiles", "Voltage vs. Time")) { + legend("topright", legend = c(sort(as.numeric(input$renderCycles)), if ("se" %in% colnames(tmp_data)) { + c(input$dirName, compName) + }), col = c(unique(tmp_data$color), rep("black", length(input$cells))), lty = c(rep(19, length(unique(tmp_data$color))), 1:length(input$cells)), title = "Cycle", ncol = 2) + } else { + legend("topright", legend = c(sort(as.numeric(input$renderCycles)), input$cells, if ("se" %in% colnames(tmp_data)) { + c(input$dirName, compName) + }), col = c(unique(tmp_data$color), rep("black", length(input$cells))), lty = c(1:length(input$cells)), title = "Cycle", ncol = 2) + } + } + }, + error = function(cond) { + if (length(input$cells) != 0) { + text(0.5, 0.5, labels = "This graph requires you to select a cycle!", cex = 2) + } else { + text(0.5, 0.5, labels = "You don messed up A-aron!\n (no data to plot)", cex = 2) + } + print(cond) + return(NA) + } + ) + }, + res = 125 + ) + + # Enable/Disable input field based on desired grph selection + observeEvent(input$gGraphs, { + disable("area") + disable("perActive") + disable("capActive") + + choices <- c( + "dQdV Graphs", "Voltage Profiles", "Voltage vs. Time", "Discharge Capacity", "Discharge Areal Capacity", + "Total Discharge Capacity", "Average Voltage", "Delta Voltage" + ) + + if (is.element("Discharge Areal Capacity", input$gGraphs)) { + enable("area") + } + }) + + # Method for handling changes in cell selection + observeEvent(input$cells, { + tmp_cycles <<- input$renderCycles + updateSelectInput(session, "renderCycles", choices = 1:max(numCycles$cycles), selected = tmp_cycles) + }) + + observeEvent(input$compAnalysis, { + load(input$compAnalysis$datapath[[1]]) + + compName <<- basename(input$compAnalysis$datapath[[1]]) + compCycleFacts <<- cycle_facts + + sheetName <<- !sheetName + }) + + # Error handling for graphBuilder and then showing modal + observeEvent(input$graphBuilder, { + if (dim(numCycles)[1] == 0 | dim(dQdVData)[1] == 0 | dim(cycle_facts)[1] == 0) { + shinyalert("No Data!", "Please run the analysis first or load a previous environment.", "error") + } else { + updateCheckboxGroupInput(session, "cells", choices = data$sheet) + showModal(graphbuilder) + } + }) + + observeEvent(input$plot_click, { + output$hoverCoordx <- renderText({ + input$plot_click$x + }) + output$hoverCoordy <- renderText({ + input$plot_click$y + }) + }) + + observeEvent(input$perType, { + if (input$perType == "Within Analysis") { + updateRadioButtons(session, "typeGraph", choices = c( + "dQdV Graphs", "Voltage Profiles", "Voltage vs. Time", + "Charge Voltage", "Discharge Voltage", + "Average Voltage", "Delta Voltage", "Discharge Capacity", "Charge Capacity" + )) + + show("cells") + show("renderCycles") + show("plotStyle") + hide("originalData") + hide("compareData") + hide("compAnalysis") + hide("analysis") + } else if (input$perType == "Between Analyses") { + updateRadioButtons(session, "typeGraph", choices = c( + "Charge Voltage", "Discharge Voltage", + "Average Voltage", "Delta Voltage", "Discharge Capacity", "Charge Capacity", + "Capacity Loss" + )) + + sheetName <<- !sheetName + + hide("cells") + hide("renderCycles") + show("originalData") + show("compareData") + hide("plotStyle") + show("compAnalysis") + show("analysis") + } + }) + + # Method for saving graph generated by graphBuilder + observeEvent(input$saveGraph, { + png(paste(input$fileName, ".png"), res = 125) + + if (input$plotStyle == "o" | input$plotStyle == "p") { + plot(tmp_data$x, tmp_data$y, type = input$plotStyle, col = tmp_data$color, pch = tmp_data$symbol, main = titleLabel, xlim = c(bounds[1], bounds[2]), ylim = c(bounds[3], bounds[4]), xlab = xlabel, ylab = ylabel, cex = input$pointSize, cex.axis = input$textSize, cex.lab = input$textSize, cex.main = input$textSize) + # legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells), col = c(unique(tmp_data$color), rep("black", length(input$cells))), pch = c(rep(19, length(unique(tmp_data$color))), 1:length(input$cells)), title ="Cycle", ncol=2) + } else if (input$plotStyle == "l") { + newLine <- subset(tmp_data, tmp_data$color == 1 & tmp_data$symbol == 1) + plot(newLine$x, newLine$y, type = "l", col = newLine$color, lty = newLine$symbol, main = titleLabel, xlim = c(bounds[1], bounds[2]), ylim = c(bounds[3], bounds[4]), xlab = xlabel, ylab = ylabel, lwd = input$pointSize, cex.axis = input$textSize, cex.lab = input$textSize, cex.main = input$textSize) + + for (i in 1:length(input$renderCycles)) { + for (n in 1:length(cellIndex)) { + newLine <- subset(tmp_data, tmp_data$color == i & tmp_data$symbol == n) + lines(newLine$x, newLine$y, col = newLine$color, lty = newLine$symbol, lwd = input$pointSize) + } + } + + # legend("bottomright", legend = c(sort(as.numeric(input$renderCycles)), input$cells), col = c(unique(tmp_data$color), rep("black", length(input$cells))), lty = c(rep(19, length(unique(tmp_data$color))), 1:length(input$cells)), title ="Cycle", ncol=2) + } + + dev.off() + + shinyalert("Success!", paste("Plot saved in working directory:\n", getwd()), "success") + }) +} diff --git a/ui.R b/ui.R new file mode 100644 index 0000000..11fa0d4 --- /dev/null +++ b/ui.R @@ -0,0 +1,111 @@ +library(shiny) + +ui <- shinyUI({ + fluidPage( + + # Utilizing javascript within Shiny allows for added features such as enable/disable of inputs + useShinyjs(), + + # Shinyalert is a package that makes interactive"pop-ups" (modals) easy to generate + useShinyalert(), + setBackgroundColor( + color = c("ghostwhite", "lightgrey"), + gradient = "linear", + direction = "bottom", + shinydashboard = FALSE + ), + fluidRow(headerPanel("Electrochemical Data Processing Tool (EcDPT)")), + fluidRow("Analysis Mode", + switchInput("batchProcessing", onLabel = "Batch Processing", offLabel = "Single Analysis", handleWidth = "200px"), + align = "center", style = "margin: 0%;" + ), + + # This first column is where most user inputs are, with the exception of the directory name + column(4, + align = "center", + + # This generates the optional block in which the user can import a previous R environment + fluidRow( + strong("Start Here"), tags$br(), + "Current Cell Group: ", textOutput("currDir", inline = TRUE), tags$br(), + actionButton("chooseDir", "Cell Group Location*", class = "btn-secondary", style = "width: 80%; margin: 5%; font-size: 100%;"), tags$br(), + helpText("The analysis will create a folder within the selected folder."), + style = "border: 1px solid black; padding: 5%; margin: 5%;" + ), + fluidRow( + fileInput("rerun", "Optional: Import Previous R Environment", multiple = FALSE, accept = ".RData"), + actionButton("load", "Load"), + style = "border: 1px dashed black; margin: 5%; padding: 5%;" + ), + + # These are the"optional" parameters that need to be filled out if select graphs are selected + fluidRow( + strong("Optional Parameters"), tags$br(), + "Parameters responsible for certain graphs.", tags$br(), tags$br(), + + # Used for dishcharge areal capacity graphs + numericInput("area", "Limiting Electrode Area (cm^2)", 2.74, min = 0), + + # Used for C-Rate calculations + # numericInput("perActive","Active Loading of Limiting Electrode (wt%)", 96, min = 0, max = 100), + # numericInput("capActive","Capacity of Limiting Active Material (mAh/g)", 155, min = 0, max = 100), + style = "border: 1px dashed black; padding: 5%; margin: 5%;" + ), + ), + + # The second column is where selection of graphs and further features are selected + column(4, + align = "left", + fluidRow( + # Presents options for graphs to be generated + "Choose graphs to be generated: ", + actionButton("whatGraph", "What's this?", class = "btn-link"), + checkboxGroupInput("gGraphs", NULL, choices = c( + "Discharge Capacity", "Discharge Areal Capacity", + "Total Discharge Capacity", "Average Voltage", "Delta Voltage", "Capacity Loss" + ), inline = FALSE), + "Choose graphs to animate: ", + checkboxGroupInput("gAnim", NULL, choices = c("dQdV Plots", "Voltage Profiles"), inline = FALSE), + # "Advanced Analysis", + # radioButtons("advCalc", NULL, choices = c("No", "Yes"), inline = TRUE), + # helpText(HTML("Advanced Analysis includes: ")), + style = "margin: 5%; border: 1px solid black; padding: 5%;" + ), + ), + + # The final column is where all the"action" items are, aka clicking any of these buttons will trigger a process + column(4, + align = "center", + fluidRow( + selectInput("dirName", "Analysis Name*", c("Formation", "RateCap", "HighRate", "Constant Current", "CC-CV"), selected = "RateCap"), tags$br(), + strong("Files to be Analyzed*"), tags$br(), + "Import all Arbin files of interest.", tags$br(), tags$br(), + fileInput("files", NULL, multiple = TRUE), + actionButton("submit", "Begin Analysis", class = "btn-success", style = "width: 80%; height: 100px; margin: 5%; font-size: 100%;"), + actionButton("clearTable", "Clear Cells", class = "btn-danger", style = "width: 60%; margin: 5%; font-size: 100%;"), + style = "border: 4px double black; padding: 5%; margin: 5%;" + ), + + # This final block enables a button after data becomes available, which trigger the modal to build custom graphs + fluidRow( + strong("Custom Graph Builder"), tags$br(), + "Customize Graphs Once Data is Available", + disabled(actionButton("graphBuilder", "Launch", width = "80%", class = "btn-primary", style = "height: 50px; margin: 5%; font-size: 100%;")), tags$br(), + style = "border: 1px solid black; padding: 5%; margin: 5%" + ), + fluidRow( + strong("Optional: Import Active Material Masses from Excel"), tags$br(), + "Running Analysis without Masses Will Render Raw Capacities (Ah)", + textAreaInput("masses", NULL, height = "100px", resize = "vertical"), + helpText("Enter the masses separated by a new line and in the order they appear in the data table below."), + actionButton("excelImport", "Import"), + style = "border: 1px dashed black; padding: 5%; margin: 5%;" + ), + ), + + # This renders the summary datatable at the bottom of the interface once data is imported + fluidRow( + dataTableOutput("channels") + ) + ) +})