Сначала нужно решить, как добавить к data.frame
. Я предвосхищу это заранее, что растущие объекты (data.frame
s) в R неэффективны с точки зрения памяти: каждый раз, когда вы добавляете одну строку, весь кадр копируется в память. На мгновение (пока R не выполнит некоторое управление памятью и сборку мусора), в памяти будет две полные копии фрейма, имеет ли он 2 строки или 20 миллионов строк. Для небольших номеров это хорошо, но плохо масштабируется. Я предлагаю это решение несмотря на это, так как я верю, что сильный текст при вашем использовании будет в небольшом масштабе и, вероятно, не будет проблемой. Однако, если вы планируете применить это к чему-то немного большему, имейте это в виду; он не только плохо работает с памятью, но каждая копия (с большим количеством строк) будет работать медленнее, чем предыдущая.
Существует два способа увеличить data.frame
на одну или несколько строк: rbind
( который имеет data.frame
S3 метод) или просто перестраивает его. Две функции mylog
, приведенные ниже, демонстрируют оба из них: учитывая предыдущие данные (или NULL
в первый раз) и запись, она возвращает расширенный кадр.
mylog1 <- function(dat, entry) {
if (is.null(dat)) dat <- data.frame(timestamp = character(0), entry = integer(0))
rbind.data.frame(dat, data.frame(
timestamp = rep(format(Sys.time(), format = "%H:%M:%S"), length(entry)),
entry = entry,
stringsAsFactors = FALSE
), stringsAsFactors = FALSE)
}
mylog2 <- function(dat, entry) {
if (is.null(dat)) dat <- data.frame(timestamp = character(0), entry = integer(0))
data.frame(
timestamp = c(dat$timestamp, rep(format(Sys.time(), format = "%H:%M:%S"), length(entry))),
entry = c(dat$entry, entry),
stringsAsFactors = FALSE
)
}
library(shiny)
shinyApp(
ui = fluidPage(
sidebarLayout(
sidebarPanel(
numericInput(inputId = "num", label = h3("Enter value:"), value = "")
),
mainPanel(
tableOutput("table")
)
)
),
server = function(input, output) {
mydata <- reactiveVal( mylog1(NULL, integer(0)) )
num_debounced <- debounce(reactive(input$num), 3000)
observeEvent(num_debounced(), {
req(input$num)
dat <- mylog1(mydata(), num_debounced())
mydata(dat)
})
output$table <- renderTable({
req(mydata())
},
bordered = TRUE)
}
)
Примечания:
Всякий раз, когда у меня есть «журнал» или подобная таблица, которая всегда должна иметь одинаковую структуру (количество и имена столбцов), я лучше всего определить его в одном месте , обычно это вспомогательная функция. Это абсолютно не обязательно, но если / когда вы когда-нибудь измените формат таблицы и пропустите одно место, которое вставляете в нее, вы поймете, что я имею в виду. Когда его формат увеличивается в нескольких местах, его легко пропустить при форматировании. В моем случае единственное место, где можно изменить расположение таблицы, - это сама функция.
Поле ввода num
может быть слишком "отзывчивым" в этом, как только кто-то замедляет набор текста, его значение читается и используется. Я считаю, что это может быть проблематично c в пользовательских интерфейсах, поэтому я добавил shiny::debounce
: поле ввода не считается пригодным для использования, пока пользователь не перестал вводить данные в поле в течение некоторого времени (3 секунды здесь). Я думаю, что 3 секунды - это слишком много для этой демонстрации, но я думаю, что это понятно. Если вы хотите 1 секунду, измените 3000 на 1000. Если вы хотите удалить ее, измените все num_debounced()
на input$num
и удалите строку кода debounce
.
It почти всегда (на самом деле, я не могу придумать жизнеспособного исключения) для отделения реактивов данных от реактивов рендеринга. То есть я думаю, что вы не должны пытаться увеличивать данные внутри чего-то вроде renderTable
. Лучше сформулировать данные в другом месте (в своем собственном реактивном блоке), а затем использовать эту таблицу в функции рендеринга. Это по нескольким причинам, ведущая (для меня) в том, что я обычно хочу использовать эти данные в нескольких местах, будь то для рендеринга или просто для справки (эта причина не очевидна в этом приложении, но я делаю это в любом случае). Другая причина в том, что это немного упрощает реактивную блокировку рендеринга.
Я использую req(...)
до req
uire, значения которых будут "правдивыми" (не NULL
, et c). Без этого, например, таблица журнала будет начинаться со строки, в которой есть пустая запись, потому что она сработала при первом выстреле. Кроме того, если пользователь вводит что-то (добавляя строку), а затем удаляет запись, это предотвратит добавление пустой строки. Чтобы увидеть, как это работает, удалите req(input$num)
, затем добавьте запись, затем очистите поле ввода.