How can I get the date from the draggable vertical bar in timevis? - r

I found the timevis package to be very useful for displaying time periods, and it is working as expected to display my data.
I have used the addCustomTime() function to add a draggable vertical line, which also works great, however, I find that I can't get the current value of that line.
The underlying js widget exposes functions to interact with the elements, but I haven't been able to access them successfully. I have attempted to use shinyjs to catch the events emitted by the timeline, but I wasn't able to get that to work either.
I am fairly comfortable in R and shiny, but very much a newb in terms of the js, so I suspect I am just missing a trick on this.
In my ui.R, I create a timevisOutput object:
fluidPage(
timevisOutput("timeline")
)
I am trying to listen to the "timechanged" event: http://visjs.org/docs/timeline/#Events
When I add listeners with shinyjs in my server.R, I am able to see "mouseenter" events, but the "timechanged" handler doesn't seem to fire:
onevent("mouseenter", "timeline", print("timeline: mouseenter"))
onevent("timechanged", "timeline", print("Saw timechanged!"))
The visjs documentation has this snippet on "how to listen for a select event":
timeline.on('select', function (properties) {
alert('selected items: ' + properties.items);
});
I tried adding that in a shinyjs::extendShinyjs() call, but that doesn't work either. I added an example to listen for keydown events that did work:
$(document).keypress(function(e) { alert('Key pressed: ' + e.which); });
So that makes me think that I'm not referencing the timeline object correctly. The $(document) in there makes me think that I'm not aware of a way to properly get at the timeline element.
Since I can see mouseenter events for the timeline, but can't see its timechanged events, I think I need the .on() call, however, I don't think I'm referencing the timeline element properly/.
After #timelyportfolio's first response:
That was a great write up, thanks! I was able to confirm what the event looks; I have already been able to listen to the _selected event, but it's nice to see.
I am trying to get a custom time value, which uses a draggable line in the chart, it's added like this:
addCustomTime("mytimevis", Sys.Date(), "CustomTimeId")
The doc for the underlying javascript widget (http://visjs.org/docs/timeline), shows an event ("timechanged"), and a method ("getCustomTime()") to get access to its value, but I don't see the event in the trace, nor can I figure out how to make the method call work.
The "timechanged" event does not show up in the trace, which makes me think I need to make the .on() call from the widget's doc in order to enable that event:
timeline.on('select', function (properties) {
alert('selected items: ' + properties.items);
});
I have been working to get that method call to work, since ideally I'd like to capture its change, and also if I can get that working, I should be able to get the getCustomTime() method working as well!

assumption
I will answer assuming that you would like to get the data in R. If I assumed incorrectly, let me know and I will adjust.
message with shiny.trace
If you are not aware, one trick I often use is options(shiny.trace=TRUE) which will fill your screen with all the messages sent over the websocket. Here is what I see when I run the following code.
library(timevis)
data <- data.frame(
id = 1:4,
content = c("Item one" , "Item two" ,"Ranged item", "Item four"),
start = c("2016-01-10", "2016-01-11", "2016-01-20", "2016-02-14 15:00:00"),
end = c(NA , NA, "2016-02-04", NA)
)
tv <- timevis(data)
# now let's see what messages we get in Shiny
library(shiny)
options(shiny.trace=TRUE)
ui <- timevisOutput("mytimevis")
server <- function(input,output,session) {
output$mytimevis <- renderTimevis({tv})
}
shinyApp(ui,server)
I highlighted the messages pertaining to select. This tells us we can observe or react to mytimevis_selected.
observe select event
Now, let's turn off options(shiny.trace=FALSE) and print in the R console when we receive a selected message.
options(shiny.trace=FALSE)
ui <- timevisOutput("mytimevis")
server <- function(input,output,session) {
output$mytimevis <- renderTimevis({tv})
observeEvent(
input$mytimevis_selected,
{
print(input$mytimevis_selected)
}
)
}
shinyApp(ui,server)
add timechanged handler
Based on comments and re-reading your question, I now understand that you would like to add an event handler for timechanged. I think this code will help you over the hump.
library(timevis)
library(shiny)
library(htmltools)
library(htmlwidgets)
library(magrittr)
tv <- timevis() %>%
addCustomTime(Sys.Date() - 1, "yesterday") %>%
# add an event handler since this is not one
# timevis provides
htmlwidgets::onRender(
"
function(el,x) {
// this will be our instance
var tv = this.timeline;
tv.on('timechanged', function(id, time) {
//uncomment debugger if you want to stop here
//debugger;
Shiny.onInputChange(el.id + '_timechanged', {id:id, time:time})
});
}
"
)
shinyApp(
ui = fluidPage(
timevisOutput("timeline"),
actionButton("btn", "Add time bar 24 hours ago")
),
server = function(input, output) {
output$timeline <- renderTimevis(
tv
)
observeEvent(input$timeline_timechanged, {
str(input$timeline_timechanged)
})
}
)
follow up
Please let me know if I headed down the wrong path or if none of this makes any sense. I feel your pain, and troubleshooting this blend of technology can be very tricky.

(Disclaimer: I wrote the timevis package)
First of all: Kenton's answer is excellent, and OP's attempts were great as well.
This is more of an informational post.
Why what you tried didn't work
The reason onevent("timechanged", ...) doesn't work is because the onevent() function works for any standard javascript events, but it doesn't work for specific events that different plugins provide.
To bind a custom event such as that one, you'd have write some short custom javascript code. So looking at the visjs documentation and finding the timeline.on('select', ...) code is the correct path, but (as you suspected) you weren't referencing the timeline object correctly.
I made sure to export the actual underlying object just for this reason, so that people like you could manipulate it in its raw format. To access the timeline object from timevis, you use $(id)[0].widget.timeline. Since the id of your timeline is timeline and you wanted to call the on() function, then you can do something like $("#timeline")[0].widget.timeline.on('select', ...).
Why this functionality doesn't exist
When I looked at the documentation of visjs I decided to have 4 events that the widget would export to shiny automatically whenever they changed: the data, the selected items, the IDs of all items, and the currently visible time window. I decided not to use the on('timechanged') event because it behaved differently from all the rest: it would only return one item at a time.
What do I mean by that? For example, input$timeline_data always returns the entire data in this moment; input$timeline_select always tells you what are all the selected items at this moment; input$timeline_timechanged would get fired every time a different vertical bar gets dragged, and therefore will contain only the last dragged bar, not all of them, and it wouldn't give you a way to ask for the time in one specific bar. Does that make sense?
Of course it's possible to write some code that keeps track of all the bars, but that gets very messy very quickly (for example, it'd have to make sure to get updated when a bar is added/removed). So I chose to not go down that rabbithole.

Related

Shiny: conditionally build UI

I'm building a Shiny dashboard to show a large amount of data. People access the dashboard through a separate login page (non-Shiny) that sits in front, at which point a JWT is generated and put in a cookie. I've managed to read in the cookie in Shiny and parse the data, saving the token in a variable called userPermissions. Now, however, I'd like to show/hide tabs in the dashboard depending on the user permissions.
For example: I have data on both managers and assistants. I want to show the manager data to any user with userPermissions == 'manager', and the assistant data to anyone with userPermissions == assistant.
I thought the best way would be to use conditionalPanel() – here's a (simplified) reproducible example:
library(shiny)
# UI file
ui <- fluidPage(
# JS to read cookie -- simplified to just return value!
tags$head(tags$script(
HTML('
Shiny.addCustomMessageHandler("goReadTheCookie", function (message) {
Shiny.onInputChange("cookie", "manager");
})
')
)
# Title
,titlePanel('Test')
# Navbar
,navbarPage(
id="navbar"
,conditionalPanel(condition = "userPermissions() == 'manager'",
mainPanel(textOutput('Manager data')))
,conditionalPanel(condition = "userPermissions() == 'assistant'",
mainPanel(textOutput('Assistant data')))
)
))
# Server file
server <- shinyServer(function(input, output,session){
## Grab permissions from Cookie
# Prepare output
userPermissions <- reactiveVal("")
# Tell JS to return cookie
session$sendCustomMessage(type="goReadTheCookie", message=list(name="cookie_name"))
# Listen for cookie
observeEvent(input$cookie,{
## -- Bunch of code omitted for sake of brevity -- ##
userPermissions("manager")
})
})
# Run app
shinyApp(ui=ui, server=server)
The problem with this code is that, in a browser console, I get the error Can't find variable: userPermissions.
I guess what's going on here is that the entire ui is executed, before JS can grab and return the cookie. What would be the best way to solve this?
Or, maybe I'm going about this the wrong way. I obviously need to verify the cookie server-side (i.e., in R) not to divulge the secret; and preferably this check, and the hiding/showing is completed at the very start of the Shiny application (userPermissions won't change during the session). Maybe there's a different (& better) solution to get to that point?
Any help would be very much appreciated!
In the end I found that the function appendTab does exactly what I was looking for. This needs to be run in server.R though, within the function to look for the cookie (otherwise userPermissions indeed doesn't exist). I could then do:
appendTab("navbar" # the id of the navigation bar created in ui.R
,tabPanel("tab name"
,mainPanel(...))
)
where tabPanel(...) could be anything you'd normally put in ui.R.
The added benefit here is that hidden tabs are also not available in the HTML source, as they're never even passed from the server to the client!

shiny DT datatable - reset filters

probably easy but somehow cannot get it done, what's the best way to reset filters in shiny DT? I have a very wide table and I do not want someone to do some filtering forgetting they had filtered another column outside of the screen width - I would rather have a button to clear all filters. Is a change of dom/turning filter on/off the only way? Sorry if it is a duplicate - I checked the archive and cannot see it
dummy app to discuss it
library("shiny")
library("DT")
data(iris)
shinyApp(
ui = fluidPage( DT::dataTableOutput("tabel")
),
server = function(input,output,session){
output$tabel <- DT::renderDataTable({datatable(iris, filter="top")})
}
)
this one has just 5 columns so you can click x on each one and reset, if there are 50 columns that's not so easy - and without scrolling to the bottom of the screen you do not know if you are in filter or not
so what? flip filter='none', filter = 'top'? Or any other better way?
Ok, the answer was not overly difficult,
set the proxy
proxy <-dataTableProxy('tabel')
and tie to a button
clearSearch(proxy)
it still leaves the nasty x in the search fields if you have CLEAR=TRUE in filter definition, but clears filters and refreshes the content without reloading it

How to add time widget/picker?

New appmaker user here.
I'm trying to port the work permits approval "app" I made with G Suite form+spreadsheet+GAS; users should enter the day, the start and end time of the permit.
I can see from the Forum Sample that the Date field type is a DateTime field type, so I can use it in my model.
The problem is I cannot find the time picker in the widgets, and the date box has no option to also enter the time.
Am I missing something?
Time Pickers for App Maker
I read your question and thought I'd try to role one of my own and this is what I came up with. I put all of the buttons on a pageFragment and call it with app.showDialog(app.pageFragments.timePicker2);
I only use clientside script.
function updateOutput(){
var h=app.pageFragments.TimePicker2.properties.hour||'00';
var m=app.pageFragments.TimePicker2.properties.minute||'00';
var s=app.pageFragments.TimePicker2.properties.second||'00';
var t=h + ':' + m + ':' + s;
app.pageFragments.TimePicker2.descendants.timeLBL.text=t;
return t;
}
function updateHour(v){
app.pageFragments.TimePicker2.properties.hour=v;
updateOutput();
}
function updateMinute(v){
app.pageFragments.TimePicker2.properties.minute=v;
updateOutput();
}
function updateSecond(v){
app.pageFragments.TimePicker2.properties.second=v;
updateOutput();
}
Here's what my time picker looks like:
Yes. Adding all of the buttons is a nuisance but there are a few features about AppMaker that make it more tolerable.
First you can assign the TimePicker form properties which I use as global properties. I had three hour,minute and second.
Then after you add all of the hour buttons you can grab all of them at one time by clicking each one while holding down control on a windows machine and click on the onClick event and pick custom action and type this in updateHour(widget.text); the code completion won't give you text as an option but type it any way.
I just figured out how to grab the buttons all a one time by pushing shift and selecting with the mouse
Do the same thing with the minute and second buttons using updateMinute(widget.text) and updateSecond(widget.text); This saves you a lot of time typing all of the functions into each widget control panel. Also you don't have to bother giving all of the buttons special names like I did.
But you might like to format them with the following css.
And again you can grab all of the buttons at one time and change the following setting:
That way you can style all of the buttons at one time.
My save button just copies the final string into a label on the main panel.
app.pages.Testing.descendants.timeLBL2.text=app.pageFragments.TimePicker2.descendants.timeLBL.text;
app.closeDialog();
You will probably want to do something more elegant.
Here's a demo: in preview mode. Sorry about the 24 hour clock. I always use this for my own stuff because it's so much easier and I like it. You'll probably want AM & PM. I'll probably go back and do that too.
For an AM/PM Picker I used these functions:
function updateOutputAP(){
var h=app.pageFragments.TimePicker3.properties.hour||'00';
var m=app.pageFragments.TimePicker3.properties.minute||'00';
var s=app.pageFragments.TimePicker3.properties.second||'00';
var ap=app.pageFragments.TimePicker3.properties.ap||' ';
var t=h + ':' + m + ':' + s + ' ' + ap;
app.pageFragments.TimePicker3.descendants.timeLBL.text=t;
return t;
}
function updateHourPM(v){
app.pageFragments.TimePicker3.properties.hour=v;
app.pageFragments.TimePicker3.properties.ap='PM';
updateOutputAP();
}
function updateHourAM(v){
app.pageFragments.TimePicker3.properties.hour=v;
app.pageFragments.TimePicker3.properties.ap='AM';
updateOutputAP();
}
function updateMinuteAP(v){
app.pageFragments.TimePicker3.properties.minute=v;
updateOutputAP();
}
function updateSecondAP(v){
app.pageFragments.TimePicker3.properties.second=v;
updateOutputAP();
}
And this is what my picker looks like:
Now that I know how to pick the components easily with the mouse it was a break to make this change.
Three AppMaker Time Pickers:
At this time App Maker doesn't provide out of the box Time or Date/Time picker widgets, it means that you need to implement one by yourself. There are at least two ways to accomplish this task:
App Maker way
Wait and hope when App Maker will introduce Time or Date/Time picker widget or use existing App Maker widgets to emulate Time Picker. Calendar Sample can be a good starting point:
Hack into DOM/JS
If you have no concerns about cross-browser compatibility and you are OK to get you hands dirty with DOM manipulation by javascript, creating events listeners and other cool stuff, then you can play with HTML widget and native date/time or time input, or even some third party library.
One simple option would be to simply use a textbox and set the validation in the model field.
You can update your Date object on save or just use as is depending on your application. You get the benefit of auto validation errors in the UI to guide your user and it takes only seconds to set up.
Regex:
\b((1[0-2]|0?[1-9]):([0-5][0-9]) ([AaPp][Mm]))

R Shiny display app is busy in a widget

I am developing a R Shiny application involving Twitter data fetching. As the process could last some time I would like to indicate that the application is busy doing something, so the user doesn't thing the page is frozen.
In my case, I store some reactive values this way:
rv <- reactiveValues()
rv$analysisStarted <- FALSE
rv$analysisAvailable <- FALSE
Then, in UI, the user must press an actionButton in order to start processing. Then, I would like to indicate somewhere the Server is working...
observeEvent(input$analysisButton, {
rv$analysisStarted <- TRUE
rv$analysisAvailable <- FALSE
#Processing Twitter info...
rv$followersAnalysisStarted <- FALSE
rv$followersAnalysisAvailable <- TRUE
})
If, in UI.r, I place a textOutput and create the corresponding output method this way, it does NOT work:
output$text <- renderText({
if (rv$analysisStarted) {
"Server is working..."
} else if (rv$analysisAvailable) {
"Your report is ready :) "
} else {
"Enter the data to search and press analysisButton"
}
})
What I have noticed is when the analysis begins, the label changes to a gray color, but it doesn't update the text until the process is over.
What should be the proper coding of this feature?
Is it possible to redraw the text output within observeEvent?
Is it possible with the raw shiny library or requires shinyjs, which I am also using?
Any help would be grateful.
In your case, it would be helpful a progressbar to check the state. This element would be appreciate by your users to indicate the state. Unfortunately, the progress bar would have to code before the functionality. It is a trick, you see a progress bar but this object stops when the function starts.
Here is the tutorial: http://shiny.rstudio.com/gallery/progress-bar-example.html
From my point of view, the progress bar is the best way to inform the web users of the website state but it is not completely perfect. Further you can change the style of the bar with CSS to customize and select your own colors, size...

Execute a function in background when clicking a button with no output being changed (Shiny)

I want to be able to click a button within my Shiny App. So far so easy.
But when doing so, a function that loads, writes and then saves an excel sheet should be executed "behind the scenes" with no output changed in the App itself.
How can I do this?
Kind regards,
Martin
Yes. Try to use an Observer.
in server.R:
shinyServer(function(input, output, session) {
# other code ...
observe({
# do_something_button is your actionButton id.
if (input$do_something_button > 0) {
# perform behind the scenes analysis here.
# if you want to send a message to the client side, you can try
# something like:
message <- list(type="completed", excel_file=saved_file)
session$sendCustomMesage("background_task", message)
}
})
# other code ...
})
then write some custom javascript snippet and load it via ui.R:
Shiny.addCustomMessageHandler("background_task", function(message) {
console.log("Finished processing: " + message.excel_file);
});
In this way when the server completes the background processing, or should an error occur, you can get some feedback on the client side. Maybe you want to notify the user about an error or something.
The reference for using observe is here

Resources