Work in progress

This commit is contained in:
Joe Cheng
2013-07-02 01:29:33 -07:00
parent 64a62d7aed
commit 0b23f30bb7
7 changed files with 698 additions and 11 deletions

View File

@@ -37,6 +37,7 @@ Collate:
'timer.R'
'tags.R'
'cache.R'
'graph.R'
'react.R'
'reactives.R'
'fileupload.R'

View File

@@ -126,6 +126,7 @@ export(validateCssUnit)
export(verbatimTextOutput)
export(wellPanel)
export(withTags)
export(writeReactLog)
import(RJSONIO)
import(caTools)
import(digest)

41
R/graph.R Normal file
View File

@@ -0,0 +1,41 @@
#' @export
writeReactLog <- function(file=stdout()) {
cat(RJSONIO::toJSON(.graphEnv$log, pretty=TRUE), file=file)
}
.graphAppend <- function(logEntry) {
.graphEnv$log <- c(.graphEnv$log, list(logEntry))
}
.graphDependsOn <- function(id, label) {
.graphAppend(list(action='dep', id=id, dependsOn=label))
}
.graphDependsOnId <- function(id, dependee) {
.graphAppend(list(action='depId', id=id, dependsOn=dependee))
}
.graphCreateContext <- function(id, label, type, prevId) {
.graphAppend(list(
action='ctx', id=id, label=paste(label, collapse='\n'), type=type, prevId=prevId
))
}
.graphEnterContext <- function(id) {
.graphAppend(list(action='enter', id=id))
}
.graphExitContext <- function(id) {
.graphAppend(list(action='exit', id=id))
}
.graphValueChange <- function(label) {
.graphAppend(list(action='valueChange', id=label))
}
.graphInvalidate <- function(id) {
.graphAppend(list(action='invalidate', id=id))
}
.graphEnv <- new.env()
.graphEnv$log <- list()

View File

@@ -8,16 +8,19 @@ Context <- setRefClass(
.flushCallbacks = 'list'
),
methods = list(
initialize = function(label='') {
initialize = function(label='', type='other', prevId='') {
id <<- .getReactiveEnvironment()$nextId()
.invalidated <<- FALSE
.invalidateCallbacks <<- list()
.flushCallbacks <<- list()
.label <<- label
.graphCreateContext(id, label, type, prevId)
},
run = function(func) {
"Run the provided function under this context."
env <- .getReactiveEnvironment()
.graphEnterContext(id)
on.exit(.graphExitContext(id))
env$runWith(.self, func)
},
invalidate = function() {
@@ -30,6 +33,7 @@ Context <- setRefClass(
lapply(.invalidateCallbacks, function(func) {
func()
})
.graphInvalidate(id)
NULL
},
onInvalidate = function(func) {
@@ -86,10 +90,11 @@ ReactiveEnvironment <- setRefClass(
return(as.character(.nextId))
},
currentContext = function() {
if (is.null(.currentContext))
if (is.null(.currentContext)) {
stop('Operation not allowed without an active reactive context. ',
'(You tried to do something that can only be done from inside a ',
'reactive function.)')
}
return(.currentContext)
},
runWith = function(ctx, func) {

View File

@@ -4,13 +4,18 @@ Dependents <- setRefClass(
.dependents = 'Map'
),
methods = list(
register = function() {
register = function(depId=NULL, depLabel=NULL) {
ctx <- .getReactiveEnvironment()$currentContext()
if (!.dependents$containsKey(ctx$id)) {
.dependents$set(ctx$id, ctx)
ctx$onInvalidate(function() {
.dependents$remove(ctx$id)
})
if (!is.null(depId) && nchar(depId) > 0)
.graphDependsOnId(ctx$id, depId)
if (!is.null(depLabel))
.graphDependsOn(ctx$id, depLabel)
}
},
invalidate = function() {
@@ -31,6 +36,8 @@ Dependents <- setRefClass(
ReactiveValues <- setRefClass(
'ReactiveValues',
fields = list(
# For debug purposes
.label = 'character',
.values = 'environment',
.dependents = 'environment',
# Dependents for the list of all names, including hidden
@@ -42,6 +49,8 @@ ReactiveValues <- setRefClass(
),
methods = list(
initialize = function() {
.label <<- paste('reactiveValues', runif(1, min=1000, max=9999),
sep="")
.values <<- new.env(parent=emptyenv())
.dependents <<- new.env(parent=emptyenv())
},
@@ -49,6 +58,7 @@ ReactiveValues <- setRefClass(
ctx <- .getReactiveEnvironment()$currentContext()
dep.key <- paste(key, ':', ctx$id, sep='')
if (!exists(dep.key, where=.dependents, inherits=FALSE)) {
.graphDependsOn(ctx$id, sprintf('%s$%s', .label, key))
assign(dep.key, ctx, pos=.dependents, inherits=FALSE)
ctx$onInvalidate(function() {
rm(list=dep.key, pos=.dependents, inherits=FALSE)
@@ -76,7 +86,11 @@ ReactiveValues <- setRefClass(
.allValuesDeps$invalidate()
else
.valuesDeps$invalidate()
.graphValueChange(sprintf('names(%s)', .label))
.graphValueChange(sprintf('%s (all)', .label))
.graphValueChange(sprintf('%s$%s', .label, key))
assign(key, value, pos=.values, inherits=FALSE)
dep.keys <- objects(
pos=.dependents,
@@ -99,16 +113,23 @@ ReactiveValues <- setRefClass(
})
},
names = function() {
.graphDependsOn(.getReactiveEnvironment()$currentContext()$id,
sprintf('names(%s)', .label))
.namesDeps$register()
return(ls(.values, all.names=TRUE))
},
toList = function(all.names=FALSE) {
.graphDependsOn(.getReactiveEnvironment()$currentContext()$id,
sprintf('%s (all)', .label))
if (all.names)
.allValuesDeps$register()
.valuesDeps$register()
return(as.list(.values, all.names=all.names))
},
.setLabel = function(label) {
.label <<- label
}
)
)
@@ -231,6 +252,11 @@ as.list.reactivevalues <- function(x, all.names=FALSE, ...) {
reactiveValuesToList(x, all.names)
}
# For debug purposes
.setLabel <- function(x, label) {
.subset2(x, 'impl')$.setLabel(label)
}
#' Convert a reactivevalues object to a list
#'
#' This function does something similar to what you might \code{\link{as.list}}
@@ -269,7 +295,8 @@ Observable <- setRefClass(
.running = 'logical',
.value = 'ANY',
.visible = 'logical',
.execCount = 'integer'
.execCount = 'integer',
.mostRecentCtxId = 'character'
),
methods = list(
initialize = function(func, label=deparse(substitute(func))) {
@@ -282,9 +309,10 @@ Observable <- setRefClass(
.running <<- FALSE
.label <<- label
.execCount <<- 0L
.mostRecentCtxId <<- ""
},
getValue = function() {
.dependents$register()
.dependents$register(.mostRecentCtxId)
if (.invalidated || .running) {
.self$.updateValue()
@@ -299,7 +327,8 @@ Observable <- setRefClass(
invisible(.value)
},
.updateValue = function() {
ctx <- Context$new(.label)
ctx <- Context$new(.label, type='observable', prevId=.mostRecentCtxId)
.mostRecentCtxId <<- ctx$id
ctx$onInvalidate(function() {
.invalidated <<- TRUE
.dependents$invalidate()
@@ -395,7 +424,8 @@ Observer <- setRefClass(
.invalidateCallbacks = 'list',
.execCount = 'integer',
.onResume = 'function',
.suspended = 'logical'
.suspended = 'logical',
.prevId = 'character'
),
methods = list(
initialize = function(func, label, suspended = FALSE, priority = 0) {
@@ -409,12 +439,14 @@ Observer <- setRefClass(
.execCount <<- 0L
.suspended <<- suspended
.onResume <<- function() NULL
.prevId <<- ''
# Defer the first running of this until flushReact is called
.createContext()$invalidate()
},
.createContext = function() {
ctx <- Context$new(.label)
ctx <- Context$new(.label, type='observer', prevId=.prevId)
.prevId <<- ctx$id
ctx$onInvalidate(function() {
lapply(.invalidateCallbacks, function(func) {
@@ -780,7 +812,7 @@ invalidateLater <- function(millis, session) {
#'
#' @export
isolate <- function(expr) {
ctx <- Context$new('[isolate]')
ctx <- Context$new('[isolate]', type='isolate')
ctx$run(function() {
expr
})

View File

@@ -64,7 +64,9 @@ ShinySession <- setRefClass(
.clientData <<- ReactiveValues$new()
input <<- .createReactiveValues(.input, readonly=TRUE)
.setLabel(input, 'input')
clientData <<- .createReactiveValues(.clientData, readonly=TRUE)
.setLabel(clientData, 'clientData')
output <<- .createOutputWriter(.self)
@@ -142,7 +144,7 @@ ShinySession <- setRefClass(
}
else
.invalidatedOutputValues$set(name, value)
}, label=label, suspended=.shouldSuspend(name))
}, label=sprintf('output$%s', name), suspended=.shouldSuspend(name))
obs$onInvalidate(function() {
showProgress(name)

View File

@@ -0,0 +1,605 @@
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style type="text/css">
#viz {
width: 600px;
height: 300px;
}
.node {
z-index: 1;
}
.node.invalidated {
opacity: 0.5;
}
.link {
fill: none;
stroke: #CCC;
stroke-width: 0.5px;
z-index: 0;
}
#description {
position: fixed;
width: 300px;
left: 12px;
top: 36px;
height: auto;
}
</style>
<script>
var log = [
{
"action" : "valueChange",
"id" : "names(input)"
},
{
"action" : "valueChange",
"id" : "input (all)"
},
{
"action" : "valueChange",
"id" : "input$dataset"
},
{
"action" : "valueChange",
"id" : "names(input)"
},
{
"action" : "valueChange",
"id" : "input (all)"
},
{
"action" : "valueChange",
"id" : "input$caption"
},
{
"action" : "valueChange",
"id" : "names(input)"
},
{
"action" : "valueChange",
"id" : "input (all)"
},
{
"action" : "valueChange",
"id" : "input$obs"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$output_caption_hidden"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$output_summary_hidden"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$output_view_hidden"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$pixelratio"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$url_protocol"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$url_hostname"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$url_port"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$url_pathname"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$url_search"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$url_hash_initial"
},
{
"action" : "valueChange",
"id" : "names(clientData)"
},
{
"action" : "valueChange",
"id" : "clientData (all)"
},
{
"action" : "valueChange",
"id" : "clientData$allowDataUriScheme"
},
{
"action" : "ctx",
"id" : "1",
"label" : "output$caption",
"type" : "observer",
"prevId" : ""
},
{
"action" : "invalidate",
"id" : "1"
},
{
"action" : "ctx",
"id" : "2",
"label" : "output$summary",
"type" : "observer",
"prevId" : ""
},
{
"action" : "invalidate",
"id" : "2"
},
{
"action" : "ctx",
"id" : "3",
"label" : "output$view",
"type" : "observer",
"prevId" : ""
},
{
"action" : "invalidate",
"id" : "3"
},
{
"action" : "ctx",
"id" : "4",
"label" : "output$caption",
"type" : "observer",
"prevId" : "1"
},
{
"action" : "enter",
"id" : "4"
},
{
"action" : "dep",
"id" : "4",
"dependsOn" : "input$caption"
},
{
"action" : "exit",
"id" : "4"
},
{
"action" : "ctx",
"id" : "5",
"label" : "output$summary",
"type" : "observer",
"prevId" : "2"
},
{
"action" : "enter",
"id" : "5"
},
{
"action" : "ctx",
"id" : "6",
"label" : "{\n switch(input$dataset, rock = rock, pressure = pressure, cars = cars)\n}",
"type" : "observable",
"prevId" : ""
},
{
"action" : "enter",
"id" : "6"
},
{
"action" : "dep",
"id" : "6",
"dependsOn" : "input$dataset"
},
{
"action" : "exit",
"id" : "6"
},
{
"action" : "exit",
"id" : "5"
},
{
"action" : "ctx",
"id" : "7",
"label" : "output$view",
"type" : "observer",
"prevId" : "3"
},
{
"action" : "enter",
"id" : "7"
},
{
"action" : "depId",
"id" : "7",
"dependsOn" : "6"
},
{
"action" : "dep",
"id" : "7",
"dependsOn" : "input$obs"
},
{
"action" : "exit",
"id" : "7"
},
{
"action" : "valueChange",
"id" : "names(input)"
},
{
"action" : "valueChange",
"id" : "input (all)"
},
{
"action" : "valueChange",
"id" : "input$caption"
},
{
"action" : "invalidate",
"id" : "4"
},
{
"action" : "ctx",
"id" : "8",
"label" : "output$caption",
"type" : "observer",
"prevId" : "4"
},
{
"action" : "enter",
"id" : "8"
},
{
"action" : "dep",
"id" : "8",
"dependsOn" : "input$caption"
},
{
"action" : "exit",
"id" : "8"
},
{
"action" : "valueChange",
"id" : "names(input)"
},
{
"action" : "valueChange",
"id" : "input (all)"
},
{
"action" : "valueChange",
"id" : "input$dataset"
},
{
"action" : "invalidate",
"id" : "5"
},
{
"action" : "invalidate",
"id" : "7"
},
{
"action" : "invalidate",
"id" : "6"
},
{
"action" : "ctx",
"id" : "9",
"label" : "output$summary",
"type" : "observer",
"prevId" : "5"
},
{
"action" : "enter",
"id" : "9"
},
{
"action" : "depId",
"id" : "9",
"dependsOn" : "6"
},
{
"action" : "ctx",
"id" : "10",
"label" : "{\n switch(input$dataset, rock = rock, pressure = pressure, cars = cars)\n}",
"type" : "observable",
"prevId" : "6"
},
{
"action" : "enter",
"id" : "10"
},
{
"action" : "dep",
"id" : "10",
"dependsOn" : "input$dataset"
},
{
"action" : "exit",
"id" : "10"
},
{
"action" : "exit",
"id" : "9"
},
{
"action" : "ctx",
"id" : "11",
"label" : "output$view",
"type" : "observer",
"prevId" : "7"
},
{
"action" : "enter",
"id" : "11"
},
{
"action" : "depId",
"id" : "11",
"dependsOn" : "10"
},
{
"action" : "dep",
"id" : "11",
"dependsOn" : "input$obs"
},
{
"action" : "exit",
"id" : "11"
}
];
var nodes = {};
var nodeList = [];
var nodeSelection = null;
var links = [];
var linkSelection = null;
var colors = {
observer: '#009',
observable: '#900',
value: '#999'
};
var force = d3.layout.force()
.nodes(nodeList)
.links(links)
.size([150, 75]);
force.on('tick', onTick);
function update() {
var viz = d3.select('#viz');
viz.select('#nodes').selectAll('.node').data(nodeList)
.enter().append('circle')
.attr('class', 'node')
.attr('title', function(n) {return n.label;})
.attr('fill', function(n) {return colors[n.type];})
.attr('r', 5)
.attr('onmouseover', '$("#description").text(this.getAttribute("title"));')
.attr('onmouseout', '$("#description").html("<br/>");');
viz.selectAll('.node').data(nodeList)
.exit().remove();
nodeSelection = viz.selectAll('.node');
nodeSelection.classed('invalidated', function(n) { return n.invalidated; })
viz.select('#links').selectAll('.link').data(links)
.enter().append('path')
.attr('class', 'link');
viz.selectAll('.link').data(links)
.exit().remove();
linkSelection = viz.selectAll('.link');
force.start();
}
function onTick() {
nodeSelection
.attr('cx', function(n) { return n.x; })
.attr('cy', function(n) { return n.y; })
;
linkSelection
.attr('d', function(link) {
return 'M' + link.source.x + ',' + link.source.y +
' L' + link.target.x + ',' + link.target.y;
});
}
function createNode(data, type) {
var node;
if (!data.prevId) {
node = {
label: data.label,
type: data.type,
index: nodeList.length
};
nodes[data.id] = node;
nodeList.push(node);
} else {
node = nodes[data.prevId];
delete nodes[data.prevId];
nodes[data.id] = node;
node.invalidated = false;
}
}
var callbacks = {
ctx: function(data) {
createNode(data);
},
dep: function(data) {
if (!nodes[data.dependsOn])
createNode({id: data.dependsOn, label: data.dependsOn, type: 'value'});
links.push({
source: nodes[data.id],
target: nodes[data.dependsOn]
});
},
depId: function(data) {
links.push({
source: nodes[data.id],
target: nodes[data.dependsOn]
});
},
invalidate: function(data) {
var node = nodes[data.id];
node.invalidated = true;
links = links.filter(function(link) {
return link.source !== node;
});
},
valueChange: function(data) {
return true;
},
enter: function(data) {
return true;
},
exit: function(data) {
return true;
}
};
function processMessage(data) {
console.log(JSON.stringify(data));
if (!callbacks.hasOwnProperty(data.action))
throw new Error('Unknown action ' + data.action);
var result = callbacks[data.action].call(callbacks, data);
update();
return result;
}
function doNext() {
while (log.length)
if (!processMessage(log.shift()))
break;
if (log.length === 0)
document.getElementById('processNext').setAttribute('disabled', 'disabled');
}
</script>
<body>
<button id="processNext" onclick="doNext();">Next</button>
<br/>
<svg>
<g id="viz" transform="scale(4)">
<g id="links"></g>
<g id="nodes"></g>
</g>
</svg>
<pre id="description"><br/></pre>
</body>
</html>
<!--
<svg style="border: 1px solid silver; width: 400px; height: 400px">
<g>
<path stroke="black" stroke-width="4" fill="white" d="m 58,2 c -75,0 -75,100 0,100 l 60,0 l 50,-50 l -50,-50 Z"/>
</g>
<g transform="translate(0 110)">
<path stroke="black" stroke-width="4" fill="white" d="m 58,2 c -75,0 -75,100 0,100 l 100,0 l 0,-100 Z"/>
</g>
<g transform="translate(0 220)">
<path stroke="black" stroke-width="4" fill="white" d="m 2,0 l 0,100 l 100,0 l 50,-50 l -50,-50 Z"/>
</g>
</svg>
-->