You can basically do almost anything in Shiny since you can create your own input and output bindings – so the answer to your question is yes, what you're asking is possible. Say you have a data frame that you send to a web page to be viewed by the user. As an example, you want to allow users to simply click a cell if it's an outlier that should be removed (replaced with NA
).
Let's say the data frame looks like this:
x <- data.frame(Age = c(10, 20, 1000), Weight = c(120, 131, 111))
x
# Age Weight
# 10 120
# 20 131
# 1000 111
From shiny you would construct a normal HTML table that might look something like this when displayed on the webpage:
<table class="outlier-finder" id="outliers">
<tr>
<td>Age</td>
<td>Weight</td>
</tr>
<tr>
<td>10</td>
<td>120</td>
</tr>
<tr>
<td>20</td>
<td>131</td>
</tr>
<tr>
<td>1000</td>
<td>111</td>
</tr>
</table>
Now break out the jQuery and bind a click event so that when a cell is clicked you can record the row and column number (see here) and then replace that cell with NA
in Shiny. Your input binding might look something like (see here for details of what's going on here):
$(document).on("click", ".outlier-finder td", function(evt) {
// Identify the clicked cell.
var el = $(evt.target);
// Raise an event to signal that the something has been selected.
el.trigger("change");
});
var cell_binding = new Shiny.InputBinding();
$.extend(cell_binding, {
find: function(scope) {
return $(scope).find(".outlier-finder td");
},
getValue: function(el) {
// Get the row and cell number of the selected td.
var col = el.parent().children().index(el);
var row = el.parent().parent().children().index(el.parent());
var result = [row, col];
return result;
},
setValue: function(el, value) {
$(el).text(value);
},
subscribe: function(el, callback) {
$(el).on("change.cell_binding", function(e) {
callback();
});
},
unsubscribe: function(el) {
$(el).off(".cell_binding");
}
});
Shiny.inputBindings.register(cell_binding);
There's a lot going on here, but generally these input bindings are fairly similar to each other. The most important thing is the setValue()
function. What should be happening there (this is untested) is the row and column number of the cell being clicked is recorded and posted back to the server.
Then from Shiny you would simply do something like:
updateData <- reactive({
# Get selection
remove_outlier <- as.integer(RJSONIO::fromJSON(input$outliers))
if (!is.null(remove_outlier)) {
# Remove that outlier.
x[remove_outlier[1], remove_outlier[2]] <- NA
}
return(x)
})
output$outliers <- renderText({
# Update x.
current_x <- updateData()
# Write code to output current_x to page.
# ...
# ...
})
You will probably need to make an output binding for output$outliers as well. This is simplified code here obviously, you would need to apply error checking etc.
This is just an example. In reality, you would probably not have Shiny updating your data frame every single time a user makes a change. You would want to have some sort of submit button so that once the user has made all of his/her changes they can be applied.
I haven't even remotely tested any of this so there are almost certainly some errors. But since you were just asking a theoretical question I didn't check it too much. The general tactic should work anyway. With input bindings you can get anything from a web page back to the server and vice versa with output bindings. Maybe saying "anything" is a stretch — but you can do a lot.