I am trying to make an input form with gWidgets2 to write your name, select a time period and select a car. These input values are the saved into a list. The data is following:
list.timeinterval = c("last month", "last year", "Specific Period")
list.vehicle = c("car1", "car2", "car3")
This is my code:
choose.specs <- function(list.timeinterval, list.vehicle){
library(gWidgets2)
library(gWidgets2tcltk)
options(guiToolkit = "tcltk")
reporter <- NULL
period <- NULL
vehicle <- NULL
w <- gwindow("Choose report specification")
g <- ggroup(horizontal = FALSE, cont = w)
tmp <- gframe("Reporter name", cont = g, expand = TRUE)
rep <- gedit("write your name",
cont = tmp,
handler = function(...)
reporter = svalue(rep))
tmp <- gframe("Choose time intervall", cont = g, expand = TRUE)
per <- gcombobox(list.timeinterval,
label = "Select time period",
editable = FALSE,
cont = tmp,
handler = function(...)
period = svalue(per))
tmp <- gframe("Choose car", cont = g, expand = TRUE)
car <- gcombobox(list.vehicle,
label = "Select car:",
editable = FALSE,
cont = tmp,
handler = function(...)
vehicle = svalue(car))
visible(tmp, set = TRUE)
btn <- gbutton("confirm", cont = g)
addHandlerClicked(btn, handler = function(h,...) {
dispose(w)
})
return(list(reporter,
period,
vehicle))
}
list.specs <- choose.specs(list.timeinterval, list.vehicle)
Somehow it returns an empty list. If i don't set reporter, period and fleet = NULL it gives an error "object reporter not found".
If i write this return(list(svalue(rep), svalue(per), svalue(car))) instead of return(list(reporter, period, vehicle)) it returns the default values
[[1]]
[1] "write your name"
[[2]]
[1] "last month"
[[3]]
[1] "car1"
how can i return this input values?
EDIT:
My interpretation of the suggested solution from #jverzani is following:
choose.specs <- function(list.timeinterval, list.vehicle){
library(gWidgets2)
library(gWidgets2tcltk)
options(guiToolkit = "tcltk")
reporter <- NULL
period <- NULL
fleets <- NULL
e <- new.env()
e$reporter <- reporter
e$period <- period
e$fleets <- fleets
w <- gwindow("Choose report specification")
g <- ggroup(horizontal = FALSE, cont = w)
tmp <- gframe("Reporter name", cont = g, expand = TRUE)
rep <- gedit("write your name",
cont = tmp,
handler = function(...)
e$reporter <- svalue(rep))
tmp <- gframe("Choose time intervall", cont = g, expand = TRUE)
per <- gcombobox(list.timeinterval,
label = "Select time period",
editable = FALSE,
cont = tmp,
handler = function(...)
e$period <- svalue(per))
tmp <- gframe("Choose car", cont = g, expand = TRUE)
car <- gcombobox(list.vehicle,
label = "Select car:",
editable = FALSE,
cont = tmp,
handler = function(...)
e$fleets <- svalue(car))
visible(tmp, set = TRUE)
btn <- gbutton("confirm", cont = g)
addHandlerClicked(btn, handler = function(h,...) {
dispose(w)
})
return(list(e$reporter,
e$period,
e$vehicle))
}
and/or:
choose.specs <- function(list.timeinterval, list.vehicle){
library(gWidgets2)
library(gWidgets2tcltk)
options(guiToolkit = "tcltk")
reporter <- NULL
period <- NULL
vehicle <- NULL
w <- gwindow("Choose report specification")
g <- ggroup(horizontal = FALSE, cont = w)
tmp <- gframe("Reporter name", cont = g, expand = TRUE)
rep <- gedit("write your name",
cont = tmp,
handler = function(...)
reporter <<- svalue(rep))
tmp <- gframe("Choose time intervall", cont = g, expand = TRUE)
per <- gcombobox(list.timeinterval,
label = "Select time period",
editable = FALSE,
cont = tmp,
handler = function(...)
period <<- svalue(per))
tmp <- gframe("Choose car", cont = g, expand = TRUE)
car <- gcombobox(list.vehicle,
label = "Select car:",
editable = FALSE,
cont = tmp,
handler = function(...)
vehicle <<- svalue(car))
visible(tmp, set = TRUE)
btn <- gbutton("confirm", cont = g)
addHandlerClicked(btn, handler = function(h,...) {
dispose(w)
})
return(list(reporter,
period,
vehicle))
}
Unfortunately still the same issues.
You can't return values from a handler (except for the modal dialogs). You need to assign them. Typically this is done with <<- or an environment, so that modifications within the function body happen outside the scope of the body. For example, in the list.timeinterval handler you might write e$period = ... where e is some environment you initialize before displaying the GUI.
Your return value happens instantly. Rather, you need to assign values in the handler to something persistent. Here is one pattern:
library(gWidgets2)
library(gWidgets2tcltk)
options(guiToolkit = "tcltk")
e = new.env()
choose.specs <- function(e, list.timeinterval, list.vehicle){
e$reporter <- NULL
e$period <- NULL
e$vehicle <- NULL
w <- gwindow("Choose report specification")
g <- ggroup(horizontal = FALSE, cont = w)
tmp <- gframe("Reporter name", cont = g, expand = TRUE)
rep <- gedit("write your name",
cont = tmp,
handler = function(...)
e$reporter <<- svalue(rep))
tmp <- gframe("Choose time intervall", cont = g, expand = TRUE)
per <- gcombobox(list.timeinterval,
label = "Select time period",
editable = FALSE,
cont = tmp,
handler = function(...)
e$period <<- svalue(per))
tmp <- gframe("Choose car", cont = g, expand = TRUE)
car <- gcombobox(list.vehicle,
label = "Select car:",
editable = FALSE,
cont = tmp,
handler = function(...)
e$vehicle <<- svalue(car))
visible(tmp, set = TRUE)
btn <- gbutton("confirm", cont = g)
addHandlerClicked(btn, handler = function(h,...) {
dispose(w)
})
# return(list(reporter,
# period,
# vehicle))
}
When you interact with the dialong, the e environment updates.
Related
I can change the line style and the item style, but I cannot seem to be able to pass arguments to areaStyle (see areaStyle).
For example:
library(echarts4r)
data.frame(x = seq.int(1, 5, 1),
y = 10) %>%
e_chart(x = x) %>%
e_area(
serie = y,
areaStyle = list(opacity = 0),
lineStyle = list(opacity = 0),
itemStyle = list(opacity = 0)
)
produces an area chart with no points and no line, but the area is still visible. How do I change the color, opacity, etc. of the area itself?
I had a look at the source code of e_area_ (which is called by e_area). and the issue is that e_area_ inits areaStyle as an empty list. See https://github.com/JohnCoene/echarts4r/blob/bf23891749cf42a40656fa87ff04ecb3627a9af5/R/add_.R#L263-L269 . And unfortunately this empty list doesn't gets updated when the user provides his own specs. Not sure whether this is a bug or whether this is intended. Perhaps you should file an issue.
As a possible quick workaround here is "fixed" e_area2_ which updates the default empty list via modifyList:
library(echarts4r)
library(dplyr)
e_area2_ <- function(e, serie, bind = NULL, name = NULL, legend = TRUE,
y_index = 0, x_index = 0, coord_system = "cartesian2d", ...) {
.default <- list(areaStyle = list())
args <- utils::modifyList(.default, list(...))
if (missing(e)) {
stop("must pass e", call. = FALSE)
}
if (missing(serie)) {
stop("must pass serie", call. = FALSE)
}
for (i in seq_along(e$x$data)) {
vector <- echarts4r:::.build_data2(
e$x$data[[i]], e$x$mapping$x,
serie
)
if (!is.null(bind)) {
vector <- echarts4r:::.add_bind2(e, vector, bind, i = i)
}
l <- list(data = vector)
if (coord_system == "cartesian2d") {
if (y_index != 0) {
e <- echarts4r:::.set_y_axis(e, serie, y_index, i)
}
if (x_index != 0) {
e <- echarts4r:::.set_x_axis(e, x_index, i)
}
l$yAxisIndex <- y_index
l$xAxisIndex <- x_index
} else if (coord_system == "polar") {
l$data <- as.list(unname(unlist(dplyr::select(
e$x$data[[i]],
serie
))))
}
if (!e$x$tl) {
nm <- echarts4r:::.name_it(e, serie, name, i)
args
opts <- c(
list(name = nm, type = "line", coordinateSystem = coord_system),
args
)
l <- append(l, opts)
if (isTRUE(legend)) {
e$x$opts$legend$data <- append(
e$x$opts$legend$data,
list(nm)
)
}
e$x$opts$series <- append(e$x$opts$series, list(l))
} else {
e$x$opts$options[[i]]$series <- append(
e$x$opts$options[[i]]$series,
list(l)
)
}
}
if (isTRUE(e$x$tl)) {
if (is.null(name)) {
name <- serie
}
series_opts <- c(
list(
name = name, type = "line", yAxisIndex = y_index,
xAxisIndex = x_index, coordinateSystem = coord_system
),
args
)
if (isTRUE(legend)) {
e$x$opts$baseOption$legend$data <- append(
e$x$opts$baseOption$legend$data,
list(name)
)
}
e$x$opts$baseOption$series <- append(
e$x$opts$baseOption$series,
list(series_opts)
)
}
e
}
data.frame(
x = seq.int(1, 5, 1),
y = 10
) %>%
e_chart(x = x) %>%
e_area2_(
serie = "y",
areaStyle = list(opacity = 0),
lineStyle = list(opacity = 0),
itemStyle = list(opacity = 0)
)
e_area() is going to be deprecated (see this GitHub Issue). Using e_line() and areaStyle (which follows from echarts.js) solves my issue.
library(echarts4r)
library(magrittr)
data.frame(x = seq.int(1, 5, 1),
y = 10) %>%
e_chart(x = x) %>%
e_line(
serie = y,
areaStyle = list(opacity = 0),
lineStyle = list(opacity = 0),
itemStyle = list(opacity = 0)
)
With area areaStyle opactiy = 1:
library(echarts4r)
library(magrittr)
data.frame(x = seq.int(1, 5, 1),
y = 10) %>%
e_chart(x = x) %>%
e_line(
serie = y,
areaStyle = list(opacity = 1),
lineStyle = list(opacity = 0),
itemStyle = list(opacity = 0)
)
Is there a way to generate the column defitions for color_tiles dynamically? Here is my current approach which does not work correctly, as the tiles are always colored by the last class-column that I pass.
library(reactable)
library(reactablefmtr)
library(dplyr)
library(purrr)
tile_column <- function(x, ref_column, name, show_text = FALSE) {
colDef(
show = TRUE,
name = name,
cell = color_tiles(
x,
color_by = ref_column,
colors = c("red", "orange", "yellow", "limegreen", "green"),
box_shadow = TRUE,
show_text = show_text,
),
na = ""
)
}
data <- tibble(
name = paste("name", 1:10),
val1 = paste("val1", 1:10),
val2 = paste("val2", 1:10),
class1 = sample(1:5, 10, replace = TRUE),
class2 = sample(1:5, 10, replace = TRUE),
)
col_definitions1 <- list(
name = colDef(show = TRUE, name = "Name")
)
col_definitions2 <- list()
for (i in 1:2) {
col_definitions2 <- c(
col_definitions2,
list(tile_column(data, ref_column = paste0("class", i), name = paste("Column", i), show_text = TRUE))
)
}
col_definitions3 <- col_definitions2 |> set_names(paste0("val", 1:2))
col_definitions <- c(col_definitions1, col_definitions3)
# browser()
data %>%
reactable(
defaultColDef = colDef(show = FALSE),
columns = col_definitions
)
Not sure what's the issue. But to make your code work you could switch to lapply instead of using a for loop. Just a guess, but as a similar issue is well known when creating ggplot2 inside a for loop the reason for the issue might be related to lazy evaluation.
library(reactable)
library(reactablefmtr)
library(dplyr)
set.seed(123)
col_definitions2 <- lapply(1:2, function(i) tile_column(data, ref_column = paste0("class", i), name = paste("Column", i), show_text = TRUE))
names(col_definitions2) <- paste0("val", 1:2)
col_definitions <- c(col_definitions1, col_definitions2)
data %>%
reactable(
defaultColDef = colDef(show = FALSE),
columns = col_definitions
)
I need to add a textbox and button for each of the iteration. User will enter the value in the textbox and button will write the value in textbpx to a file along with other information. This other informatin can be taken from current graph item . This needs to be done in for each iteration similar to the graphs as shown for each symbol as below
library('quantmod')
getSymbols("AAPL", from = "2020/01/01",to = Sys.Date(), periodicity = "daily")
getSymbols("MSFT", from = "2020/01/01",to = Sys.Date(), periodicity = "daily")
getSymbols("FB", from = "2020/01/01",to = Sys.Date(), periodicity = "daily")
getSymbols("ORCL", from = "2020/01/01",to = Sys.Date(), periodicity = "daily")
df_AAPL <- as.data.frame(AAPL)
df_AAPL$DATE <- index(AAPL)
rownames(df_AAPL) <- NULL
names(df_AAPL) <- c('OPEN','HIGH','LOW','CLOSE','VOLUME','ADJUSTED','DATE')
df_AAPL$SYMBOL <- 'AAPL'
df_MSFT <- as.data.frame(MSFT)
df_MSFT$DATE <- index(MSFT)
rownames(df_MSFT) <- NULL
names(df_MSFT) <- c('OPEN','HIGH','LOW','CLOSE','VOLUME','ADJUSTED','DATE')
df_MSFT$SYMBOL <- 'MSFT'
df_FB <- as.data.frame(FB)
df_FB$DATE <- index(FB)
rownames(df_FB) <- NULL
names(df_FB) <- c('OPEN','HIGH','LOW','CLOSE','VOLUME','ADJUSTED','DATE')
df_FB$SYMBOL <- 'FB'
df_ORCL <- as.data.frame(ORCL)
df_ORCL$DATE <- index(ORCL)
rownames(df_ORCL) <- NULL
names(df_ORCL) <- c('OPEN','HIGH','LOW','CLOSE','VOLUME','ADJUSTED','DATE')
df_ORCL$SYMBOL <- 'ORCL'
df_all <- rbind(df_AAPL, df_MSFT,df_FB,df_ORCL)
df_all[, c('SYMBOL','DATE','OPEN','HIGH','LOW','CLOSE','VOLUME','ADJUSTED')]
library(shiny)
#unique(df_all$DATE)
df_rep_date <- data.frame('RunDate'= character(),'ListStocks' = character(), stringsAsFactors=FALSE)
df_rep_date[1,] <- c("2020-01-06", 'AAPL,MSFT')
df_rep_date[2,] <- c("2021-01-04",'ORCL,AAPL')
df_rep_date[3,] <- c("2022-01-04", 'FB,ORCL')
df_rep_date$RunDate <- as.Date(df_rep_date$RunDate)
v_lst_sel_dates <-c(df_rep_date$RunDate)
func_1symb_plot <- function(p_symb){
#p_symb = 'AAPL'
df_tmp_hist_dat = df_all[df_all$SYMBOL == p_symb,c("DATE" ,"OPEN","HIGH","LOW" ,"CLOSE","VOLUME" )]
v_df_dly_dat_6mnth_xts <- xts(df_tmp_hist_dat[, -1], order.by = df_tmp_hist_dat[, 1])
v_grph_op <- candleChart( v_df_dly_dat_6mnth_xts,name = p_symb, type = "auto", up.col = "green", dn.col = "red",
theme = "white",plot = TRUE,TA = "addVo();addSMA(n = 1, on = 1, overlay = TRUE, col ='black');
addSMA(n = 7, on = 1, overlay = TRUE, col ='gold'); addSMA(n = 14, on = 1, overlay = TRUE, col ='brown');addMACD(); addBBands();addRSI();addOBV();")
return(v_grph_op)}
simpUI <- function(id) {
tagList(selectInput(NS(id, 'RunDate'), "Run Date", v_lst_sel_dates),
textOutput(NS(id,'date_output')),
textOutput(NS(id,'lst_symb_output')),
uiOutput(NS(id,"myplot"))
)
}
simpServer <- function(id) {
moduleServer(id, function(input, output, session) {
ns <- session$ns
string <- reactive(input$RunDate)
output$date_output <- renderText(string())
v_lst_symbol <- reactive(df_rep_date[df_rep_date$RunDate == input$RunDate,]$ListStocks)
output$lst_symb_output <- renderText(v_lst_symbol())
observeEvent(input$RunDate, {
print(v_lst_symbol())
symbs <- read.table(text = v_lst_symbol(), sep = ",", colClasses = "character")
print(symbs)
lapply(symbs[1,], function(v_symb){
v_symb_name = paste0(v_symb, '_name')
output[[paste0(v_symb, '_name')]] = renderText(v_symb_name)
output[[paste0(v_symb, '_plot')]] <- renderPlot(func_1symb_plot(v_symb))
})
})
output$myplot <- renderUI({
symbs <- read.table(text = v_lst_symbol(), sep = ",", colClasses = "character")
tagList(
lapply(symbs[1,], function(v_symb){
id1 <- paste0(v_symb, '_name')
id2 <- paste0(v_symb, '_plot')
textOutput(ns(id1))
plotOutput(ns(id2))
# add the textbox and the button.
# this will be for each of the element in the loop
})
)
})
})
}
ui <- fluidPage(fluidRow(simpUI("par1")))
server <- function(input, output, session) {
simpServer("par1")
}
shinyApp(ui = ui, server = server)
I am running a loop that assigns a plot to a variable. the code then pulls the variable names with the ls() pattern function
while (i > 0) {
assign(paste("fig", i, sep = ""), plot_ly(tot, y = ~gene, color = ~bx, type = "box", boxpoints = "all") %>% layout(annotations = list(x = 0 , y = 1, xanchor = "left",yanchor = "top",yshift = 20,showarrow = FALSE,font = list(size = 20), text = paste("Data", colnames(total)[i+1]))))
i = i-1
if (i == 0){
the code then pulls the variable names with the ls() pattern function and attempts to pass them as a list to subplot.
...
l <- as.list(ls(pattern = "fig"))
l <- do.call(paste, c(l,sep=", "))
fig <- subplot(l,margin = 0.06,nrows=2) %>% layout(showlegend = FALSE)
fig
}
}'
This is where i am experiencing some errors.
Error in [[: subscript out of bounds
Try the following :
library(plotly)
l <- mget(ls(pattern = "fig"))
fig <- subplot(l,margin = 0.06,nrows=2) %>% layout(showlegend = FALSE)
I use a function with a text loading bar (get_reddit()) in a Shiny app and I would like to display the progression not in the R console but in the app. Does anyone know how I could do that?
For now I have an empty progress bar in the app (not surprising since I don't have any incProgress() to go with the withProgress()) and an active text bar in my RStudio console.
library(shiny)
library(RedditExtractoR)
ui <- fluidPage(actionButton("go", "GO !"),
tableOutput("reddit"))
server <- function(input, output) {
get_data <- eventReactive(input$go, {
withProgress(message = 'Work in progress', value = 0, {
df <-
get_reddit(
search_terms = "Lyon",
regex_filter = "",
subreddit = "france",
cn_threshold = 1,
page_threshold = 1,
sort_by = "comments",
wait_time = 2
)
df
})
})
output$reddit <- renderTable({
df <- get_data()
df[1:5, 1:5]
})
}
shinyApp(ui = ui, server = server)
Thank you for your help!
A simple solution is to edit the function in the RedditExtractoR package responsible for the progress bar, which is reddit_content. This function is called from within the get_reddit function, so this function has to be updated too.
library(shiny)
library(RedditExtractoR)
source("get_reddit2.R") # source the new get_reddit2 function (see below)
source("reddit_content2.R") # source the new reddit_content2 function (see below)
ui <- fluidPage(actionButton("go", "GO !"),
tableOutput("reddit"))
server <- function(input, output) {
get_data <- eventReactive(input$go, {
df <- get_reddit2(
search_terms = "science",
subreddit = "science")
})
output$reddit <- renderTable({
df <- get_data()
df[1:5, 1:5]
})
}
shinyApp(ui = ui, server = server)
Put the following function in a separate file called get_reddit2.R which you source from the app (see above):
get_reddit2 <- function (
search_terms = NA,
regex_filter = "",
subreddit = NA,
cn_threshold = 1,
page_threshold = 1,
sort_by = "comments",
wait_time = 2)
{
URL = unique(as.character(
reddit_urls(
search_terms,
regex_filter,
subreddit,
cn_threshold,
page_threshold,
sort_by,
wait_time
)$URL
))
retrieved_data = reddit_content2(URL, wait_time)
return(retrieved_data)
}
Put also the following function in a separate file called reddit_content2.R (see above):
reddit_content2 <- function (URL, wait_time = 2)
{
if (is.null(URL) | length(URL) == 0 | !is.character(URL)) {
stop("invalid URL parameter")
}
GetAttribute = function(node, feature) {
Attribute = node$data[[feature]]
replies = node$data$replies
reply.nodes = if (is.list(replies))
replies$data$children
else
NULL
return(list(Attribute, lapply(reply.nodes, function(x) {
GetAttribute(x, feature)
})))
}
get.structure = function(node, depth = 0) {
if (is.null(node)) {
return(list())
}
filter = is.null(node$data$author)
replies = node$data$replies
reply.nodes = if (is.list(replies))
replies$data$children
else
NULL
return(list(
paste0(filter, " ", depth),
lapply(1:length(reply.nodes),
function(x)
get.structure(reply.nodes[[x]], paste0(depth,
"_", x)))
))
}
data_extract = data.frame(
id = numeric(),
structure = character(),
post_date = as.Date(character()),
comm_date = as.Date(character()),
num_comments = numeric(),
subreddit = character(),
upvote_prop = numeric(),
post_score = numeric(),
author = character(),
user = character(),
comment_score = numeric(),
controversiality = numeric(),
comment = character(),
title = character(),
post_text = character(),
link = character(),
domain = character(),
URL = character()
)
# pb = utils::txtProgressBar(min = 0,
# max = length(URL),
# style = 3)
withProgress(message = 'Work in progress', value = 0, {
for (i in seq(URL)) {
if (!grepl("^https?://(.*)", URL[i]))
URL[i] = paste0("https://www.", gsub("^.*(reddit\\..*$)",
"\\1", URL[i]))
if (!grepl("\\?ref=search_posts$", URL[i]))
URL[i] = paste0(gsub("/$", "", URL[i]), "/?ref=search_posts")
X = paste0(gsub("\\?ref=search_posts$", "", URL[i]),
".json?limit=500")
raw_data = tryCatch(
RJSONIO::fromJSON(readLines(X, warn = FALSE)),
error = function(e)
NULL
)
if (is.null(raw_data)) {
Sys.sleep(min(1, wait_time))
raw_data = tryCatch(
RJSONIO::fromJSON(readLines(X,
warn = FALSE)),
error = function(e)
NULL
)
}
if (is.null(raw_data) == FALSE) {
meta.node = raw_data[[1]]$data$children[[1]]$data
main.node = raw_data[[2]]$data$children
if (min(length(meta.node), length(main.node)) > 0) {
structure = unlist(lapply(1:length(main.node),
function(x)
get.structure(main.node[[x]], x)))
TEMP = data.frame(
id = NA,
structure = gsub("FALSE ",
"", structure[!grepl("TRUE", structure)]),
post_date = format(as.Date(
as.POSIXct(meta.node$created_utc,
origin = "1970-01-01")
), "%d-%m-%y"),
comm_date = format(as.Date(
as.POSIXct(unlist(lapply(main.node,
function(x) {
GetAttribute(x, "created_utc")
})), origin = "1970-01-01")
), "%d-%m-%y"),
num_comments = meta.node$num_comments,
subreddit = ifelse(
is.null(meta.node$subreddit),
"UNKNOWN",
meta.node$subreddit
),
upvote_prop = meta.node$upvote_ratio,
post_score = meta.node$score,
author = meta.node$author,
user = unlist(lapply(main.node, function(x) {
GetAttribute(x, "author")
})),
comment_score = unlist(lapply(main.node,
function(x) {
GetAttribute(x, "score")
})),
controversiality = unlist(lapply(main.node,
function(x) {
GetAttribute(x, "controversiality")
})),
comment = unlist(lapply(main.node, function(x) {
GetAttribute(x, "body")
})),
title = meta.node$title,
post_text = meta.node$selftext,
link = meta.node$url,
domain = meta.node$domain,
URL = URL[i],
stringsAsFactors = FALSE
)
TEMP$id = 1:nrow(TEMP)
if (dim(TEMP)[1] > 0 & dim(TEMP)[2] > 0)
data_extract = rbind(TEMP, data_extract)
else
print(paste("missed", i, ":", URL[i]))
}
}
# utils::setTxtProgressBar(pb, i)
incProgress()
Sys.sleep(min(2, wait_time))
}
# close(pb)
})
return(data_extract)
}
Now the loading bar is shown in Shiny instead of the console.