我试图将网络可视化从D3.js绑定到Shiny中的自定义输出。出于某种原因,似乎我的渲染函数没有被调用。这是我的代码:
rbindings.js
var forceNetworkOB = new Shiny.OutputBinding();
forceNetworkOB.find = function(scope) {
return $(scope).find("svg.rio-force-network");
};
forceNetworkOB.renderValue = function(el, graph) {
alert('rendering')
//actual rendering code here...
};
Shiny.outputBindings.register(forceNetworkOB, "jumpy.forceNetworkOB");
CustomIO.R
renderForceNetwork <- function(expr, env=parent.frame(), quoted=FALSE) {
func <- exprToFunction(expr, env, quoted)
function() {
# Never called
browser()
graph <- func()
list(nodes = graph$nodes,
links = graph$edges
)
}
}
forceNetwork <- function(id, width = '400px', height = '400px') {
tag('svg', list(id = id, class = 'rio-force-network', width = width, height = height))
}
ui.R
library(shiny)
source('customIO.R')
shinyUI(fluidPage(
tags$script(src = 'js/d3.min.js'),
tags$script(src = 'js/rbindings.js'),
titlePanel('Network Visualization'),
tabsetPanel(
tabPanel('D3.js Force Layout',
forceNetwork('vis.force', width = '800px', height = '800px'),
)
)
))
和server.R
library(shiny)
source('cytoscape.R')
source('customIO.R')
shinyServer(function(session, input, output) {
# Load the network
network <- networkFromCytoscape('network.cyjs')
output$vis.force <- renderForceNetwork({
# Never called
print('rendering')
browser()
list(
nodes = data.frame(name = network$nodes.data$Label_for_display, group = rep(1, nrow(network$nodes.data))),
edges = data.frame(from = network$edges[,1], to = network$edges[,2])
)
})
})
从注释中可以看出,我的R渲染函数中的browser()行永远不会被调用,js渲染函数中的alert()也是如此。通过一些js调试,我可以看到我的自定义绑定正确地提供svg元素以及它的id。这可能很简单,但我无法弄清楚。
答案 0 :(得分:5)
嗯,虽然代码似乎都是合法的,但你真的需要深入了解Shiny的源代码才能找到罪魁祸首。
当Shiny初始化时,它会调用initShiny()
,然后调用bindOutputs
。
现在,这是bindOutputs
函数的样子:
function bindOutputs(scope) {
if (scope === undefined)
scope = document;
scope = $(scope);
var bindings = outputBindings.getBindings();
for (var i = 0; i < bindings.length; i++) {
var binding = bindings[i].binding;
var matches = binding.find(scope) || [];
for (var j = 0; j < matches.length; j++) {
var el = matches[j];
var id = binding.getId(el);
// Check if ID is falsy
if (!id)
continue;
var bindingAdapter = new OutputBindingAdapter(el, binding);
shinyapp.bindOutput(id, bindingAdapter);
$(el).data('shiny-output-binding', bindingAdapter);
$(el).addClass('shiny-bound-output'); // <- oops!
}
}
// Send later in case DOM layout isn't final yet.
setTimeout(sendImageSize, 0);
setTimeout(sendOutputHiddenState, 0);
}
我用<- oops
标记的行是导致所有问题的原因。
这真的不是Shiny 本身的错误:这行代码依赖于jQuery
将一个类添加到el
,即svg
您使用forceNetwork()
函数创建的DOM元素。
类shiny-bound-output
对实际绑定起作用非常重要。
问题是$.addClass
对<svg>
无效。
为了一个很好的参考,see this article或this question on stackoverflow。
因此,您的<svg>
元素缺少必需的shiny-bound-output
类,可使您的自定义OutputBinding
正常运行。
请勿使用<svg>
作为输出容器。请改用<div>
。
这意味着,您应该将forceNetwork
功能更改为:
forceNetwork <- function(id, width = '400px', height = '400px') {
tag('div', list(id = id, class = 'rio-force-network', width = width, height = height))
}
您可以使用<svg>
轻松附加d3.select(...).append('svg')
并在那里设置宽度和高度。
(请务必同时修改find()
中的rbindings.js
功能。
如果您以某种方式将d3.select(...).append('svg')
添加到您的javascript代码中,请记住在实际d3绘图之前clear()
输出绑定<div>
函数中的renderValue()
容器。否则,每次调用renderForceNetwork
时,都会向您的<svg>
容器添加新的<div>
元素。