Skip to content

Commit 2e1076d

Browse files
committed
infinite row model
1 parent 5d17603 commit 2e1076d

3 files changed

Lines changed: 294 additions & 180 deletions

File tree

R/session/init.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ init_first <- function() {
1717
}
1818

1919
# check required packages
20-
required_packages <- c("jsonlite", "rlang")
20+
required_packages <- c("jsonlite", "rlang", "data.table")
2121
missing_packages <- required_packages[
2222
!vapply(required_packages, requireNamespace,
2323
logical(1L), quietly = TRUE

R/session/vsc.R

Lines changed: 135 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,130 @@ if (is.null(getOption("help_type"))) {
6767
}
6868

6969
use_webserver <- isTRUE(getOption("vsc.use_webserver", FALSE))
70+
71+
get_column_def <- function(name, field, value) {
72+
filter <- TRUE
73+
tooltip <- sprintf(
74+
"%s, class: [%s], type: %s",
75+
name,
76+
toString(class(value)),
77+
typeof(value)
78+
)
79+
if (is.numeric(value)) {
80+
type <- "numericColumn"
81+
if (is.null(attr(value, "class"))) {
82+
filter <- "agNumberColumnFilter"
83+
}
84+
} else if (inherits(value, "Date")) {
85+
type <- "dateColumn"
86+
filter <- "agDateColumnFilter"
87+
} else {
88+
type <- "textColumn"
89+
filter <- "agTextColumnFilter"
90+
}
91+
list(
92+
headerName = name,
93+
headerTooltip = tooltip,
94+
field = field,
95+
type = type,
96+
filter = filter
97+
)
98+
}
99+
100+
dataview_table <- function(data, start = 0, end = NULL, sortModel = NULL) {
101+
102+
if (is.matrix(data)) {
103+
data <- as.data.frame.matrix(data)
104+
}
105+
if (!is.data.frame(data)) {
106+
stop("data must be a data.frame or a matrix")
107+
}
108+
109+
data.table::setDT(data)
110+
111+
# number of rows & original column names
112+
.nrow <- nrow(data)
113+
.colnames <- colnames(data)
114+
if (is.null(.colnames)) {
115+
.colnames <- sprintf("V%d", seq_len(ncol(data)))
116+
} else {
117+
.colnames <- trimws(.colnames)
118+
}
119+
120+
# capture or generate rownames
121+
if (.row_names_info(data) > 0L) {
122+
rownames_ <- rownames(data)
123+
rownames(data) <- NULL
124+
} else {
125+
rownames_ <- seq_len(.nrow)
126+
}
127+
128+
.colnames <- c("(row)", .colnames)
129+
fields <- sprintf("x%d", seq_along(.colnames))
130+
131+
# map x1→"(row)", x2→first real col, …
132+
field_map <- setNames(.colnames, fields)
133+
134+
# ── SORT data before slicing ──
135+
if (!is.null(sortModel) && length(sortModel) > 0) {
136+
137+
sort <- sortModel[[1]]
138+
col_f <- sort$colId
139+
dir <- sort$sort
140+
real <- field_map[[col_f]]
141+
142+
# only sort if it's one of your real data columns
143+
if (!is.null(real) && real %in% .colnames[-1]) {
144+
145+
# attach rownames_ as a helper column
146+
data[, "__rownames__" := rownames_]
147+
148+
# sort in place: order=1L for asc, -1L for desc
149+
data.table::setorderv(
150+
data,
151+
cols = real,
152+
order = if (dir == "asc") 1L else -1L
153+
)
154+
155+
# pull the helper back out as rownames_
156+
rownames_ <- data[["__rownames__"]]
157+
data[, "__rownames__" := NULL]
158+
}
159+
}
160+
161+
if (is.null(end)) end <- .nrow
162+
s <- as.integer(start) + 1
163+
e <- min(.nrow, as.integer(end))
164+
165+
if (s > .nrow || e < 1 || s > e) {
166+
rows <- data[0, , drop = FALSE]
167+
rownums <- integer(0)
168+
} else {
169+
rows <- data[s:e, , drop = FALSE]
170+
rownums <- rownames_[s:e]
171+
}
172+
173+
rows <- c(list(" " = rownums), .subset(rows))
174+
names(rows) <- fields
175+
class(rows) <- "data.frame"
176+
attr(rows, "row.names") <- .set_row_names(length(rownums))
177+
178+
rows <- jsonlite::fromJSON(
179+
jsonlite::toJSON(rows, dataframe = "rows", na = "null", auto_unbox = TRUE)
180+
)
181+
columns <- .mapply(
182+
get_column_def,
183+
list(.colnames, fields, rows),
184+
NULL
185+
)
186+
187+
list(
188+
columns = columns,
189+
rows = rows,
190+
totalRows = .nrow
191+
)
192+
}
193+
70194
if (use_webserver) {
71195
if (requireNamespace("httpuv", quietly = TRUE)) {
72196
request_handlers <- list(
@@ -120,6 +244,12 @@ if (use_webserver) {
120244
})
121245
return(result)
122246
}
247+
},
248+
dataview_fetch_rows = function(varname, start, end, sortModel, ...) {
249+
obj <- get(varname, envir = .GlobalEnv)
250+
out <- dataview_table(obj, start, end, sortModel)
251+
out$columns <- NULL
252+
return(out)
123253
}
124254
)
125255

@@ -456,76 +586,8 @@ if (use_httpgd && "httpgd" %in% .packages(all.available = TRUE)) {
456586

457587
show_view <- !identical(getOption("vsc.view", "Two"), FALSE)
458588
if (show_view) {
459-
# Create registry to track dataview UUIDs by title
460589
dataview_registry <- new.env(parent = emptyenv())
461590

462-
get_column_def <- function(name, field, value) {
463-
filter <- TRUE
464-
tooltip <- sprintf(
465-
"%s, class: [%s], type: %s",
466-
name,
467-
toString(class(value)),
468-
typeof(value)
469-
)
470-
if (is.numeric(value)) {
471-
type <- "numericColumn"
472-
if (is.null(attr(value, "class"))) {
473-
filter <- "agNumberColumnFilter"
474-
}
475-
} else if (inherits(value, "Date")) {
476-
type <- "dateColumn"
477-
filter <- "agDateColumnFilter"
478-
} else {
479-
type <- "textColumn"
480-
filter <- "agTextColumnFilter"
481-
}
482-
list(
483-
headerName = name,
484-
headerTooltip = tooltip,
485-
field = field,
486-
type = type,
487-
filter = filter
488-
)
489-
}
490-
491-
dataview_table <- function(data) {
492-
if (is.matrix(data)) {
493-
data <- as.data.frame.matrix(data)
494-
}
495-
496-
if (is.data.frame(data)) {
497-
.nrow <- nrow(data)
498-
.colnames <- colnames(data)
499-
if (is.null(.colnames)) {
500-
.colnames <- sprintf("V%d", seq_len(ncol(data)))
501-
} else {
502-
.colnames <- trimws(.colnames)
503-
}
504-
if (.row_names_info(data) > 0L) {
505-
rownames <- rownames(data)
506-
rownames(data) <- NULL
507-
} else {
508-
rownames <- seq_len(.nrow)
509-
}
510-
.colnames <- c("(row)", .colnames)
511-
fields <- sprintf("x%d", seq_along(.colnames))
512-
data <- c(list(" " = rownames), .subset(data))
513-
names(data) <- fields
514-
class(data) <- "data.frame"
515-
attr(data, "row.names") <- .set_row_names(.nrow)
516-
columns <- .mapply(get_column_def,
517-
list(.colnames, fields, data),
518-
NULL
519-
)
520-
list(
521-
columns = columns,
522-
data = data
523-
)
524-
} else {
525-
stop("data must be a data.frame or a matrix")
526-
}
527-
}
528-
529591
show_dataview <- function(x, title, uuid = NULL,
530592
viewer = getOption("vsc.view", "Two"),
531593
row_limit = abs(getOption("vsc.row_limit", 0))) {
@@ -615,9 +677,11 @@ if (show_view) {
615677
}
616678
if (is.data.frame(x) || is.matrix(x)) {
617679
x <- as_truncated_data(x)
618-
data <- dataview_table(x)
680+
# Get initial chunk of data (first 100 rows)
681+
meta <- dataview_table(x, start = 0, end = 1)
682+
meta$rows <- list()
619683
file <- tempfile(tmpdir = tempdir, fileext = ".json")
620-
jsonlite::write_json(data, file, na = "string", null = "null", auto_unbox = TRUE, force = TRUE)
684+
jsonlite::write_json(meta, file, na = "string", null = "null", auto_unbox = TRUE, force = TRUE)
621685
request("dataview", source = "table", type = "json",
622686
title = title, file = file, viewer = viewer, uuid = uuid, dataview_uuid = dataview_uuid
623687
)
@@ -690,8 +754,6 @@ path_to_uri <- function(path) {
690754
}
691755

692756
request_browser <- function(url, title, ..., viewer) {
693-
# Printing URL with specific port triggers
694-
# auto port-forwarding under remote development
695757
message("Browsing ", url)
696758
request("browser", url = url, title = title, ..., viewer = viewer)
697759
}
@@ -820,7 +882,6 @@ options(
820882
page_viewer = show_page_viewer
821883
)
822884

823-
# rstudioapi
824885
rstudioapi_enabled <- function() {
825886
isTRUE(getOption("vsc.rstudioapi", TRUE))
826887
}
@@ -832,12 +893,11 @@ if (rstudioapi_enabled()) {
832893
file.create(response_lock_file, showWarnings = FALSE)
833894
file.create(response_file, showWarnings = FALSE)
834895
addin_registry <- file.path(dir_session, "addins.json")
835-
# This is created in attach()
836896

837897
get_response_timestamp <- function() {
838898
readLines(response_lock_file)
839899
}
840-
# initialise the reponse timestamp to empty string
900+
841901
response_time_stamp <- ""
842902

843903
get_response_lock <- function() {
@@ -876,10 +936,6 @@ if (rstudioapi_enabled()) {
876936
}
877937
)
878938
if ("rstudioapi" %in% loadedNamespaces()) {
879-
# if the rstudioapi is already loaded, for example via a call to
880-
# library(tidyverse) in the user's profile, we need to shim it now.
881-
# There's no harm in having also registered the hook in this case. It can
882-
# work in the event that the namespace is unloaded and reloaded.
883939
rstudioapi_util_env$rstudioapi_patch_hook(rstudioapi_env)
884940
}
885941

@@ -945,7 +1001,6 @@ print.hsearch <- function(x, ...) {
9451001
invisible(x)
9461002
}
9471003

948-
# a copy of .S3method(), since this function is new in R 4.0
9491004
.S3method <- function(generic, class, method) {
9501005
if (missing(method)) {
9511006
method <- paste(generic, class, sep = ".")

0 commit comments

Comments
 (0)