在 plotly 弹出窗口中放置图表
Place a chart in plotly popup
我正在为 R 使用 plotly,尽管我也愿意使用 Python 版本。当我将鼠标悬停在数据点上时,有没有办法让弹出窗口包含另一个图表?理想情况下,图表将根据数据创建,尽管我可以使用静态图像作为后备。
我不确定从哪里开始,并为没有 MWE 提前道歉。
解决方案 1:坚持使用 R
感谢@MLavoie。以下示例使用纯 R
创建两个图,"mainplot" 和 "hover" 对第一个的悬停事件做出反应。
library(shiny)
library(plotly)
ui <- fluidPage(
plotlyOutput("mainplot"),
plotlyOutput("hover")
)
server <- function(input, output) {
output$mainplot <- renderPlotly({
# https://plot.ly/r/
d <- diamonds[sample(nrow(diamonds), 1000), ]
plot_ly(d, x = carat, y = price, text = paste("Clarity: ", clarity), mode = "markers", color = carat, size = carat, source="main")
})
output$hover <- renderPlotly({
eventdat <- event_data('plotly_hover', source="main") # get event data from source main
if(is.null(eventdat) == T) return(NULL) # If NULL dont do anything
point <- as.numeric(eventdat[['pointNumber']]) # Index of the data point being charted
# draw plot according to the point number on hover
plot_ly( x = c(1,2,3), y = c(point, point*2, point*3), mode = "scatter")
})
}
shinyApp(ui, server)
这个例子使用了shiny binds for plotly
。 对于每个悬停事件,都会向服务器发送一个 POST
请求,然后服务器将更新弹出图表。它非常低效,因此在慢速连接上可能无法正常工作。
以上代码只是demo,还没有经过测试。查看一个有效且复杂得多的示例 here (with source).
解决方案 2:Javascript
是的,您可以使用 plotly Javascript API 来完成。
简答
- 使用
R
或 Python
或任何其他支持的语言创建图表。
- 将图表插入新的 HTML 页面并添加回调函数,如下例所示。如果你对 DOM 有很好的了解,你也可以将 JS 添加到原来的 HTML 而不是创建一个新的
- 在回调函数中绘制弹出图形,回调函数接受包含悬停数据点数据的参数。
详情
如@MLavoie 所述,plotly.hover-events
中显示了一个很好的示例
让我们深入研究代码。在JS文件中,有一个简单的回调函数附加到Plot
:
Plot.onHover = function(message) {
var artist = message.points[0].x.toLowerCase().replace(/ /g, '-');
var imgSrc = blankImg;
if(artistToUrl[artist] !== undefined) imgSrc = artistToUrl[artist];
Plot.hoverImg.src = imgSrc;
};
上面,artistToUrl
是一个充满base64字符串的巨大对象,我不会粘贴在这里溢出post。但是您可以在示例页面的 JS 选项卡下看到它。它有这样的结构:
var artistToUrl = { 'bob-dylan': 'data:image/jpeg;base64,/...',...}
工作示例:
为了演示,我准备了一个简单的例子here(点击试用):
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<iframe id="plot" style="width: 900px; height: 600px;" src="https://plot.ly/~jackp/10816.embed" seamless></iframe>
<div id="myDiv"></div>
<script>
(function main() {
var Plot = { id: 'plot', domain: 'https://plot.ly' };
Plot.onHover = function(message) {
var y = message.points[0].y; /*** y value of the data point(bar) under hover ***/
var line1 = {
x: [0.25,0.5,1], /*** dummy x array in popup-chart ***/
y: [1/y, 2, y], /*** dummy y array in popup-chart ***/
mode: 'lines+markers'
};
var layout = {
title:'Popup graph on hover',
height: 400,
width: 480
};
Plotly.newPlot('myDiv', [ line1 ], layout); // this finally draws your popup-chart
};
Plot.init = function init() {
var pinger = setInterval(function() {
Plot.post({task: 'ping'});
}, 500);
function messageListener(e) {
var message = e.data;
if(message.pong) {
console.log('Initial pong, frame is ready to receive');
clearInterval(pinger);
Plot.post({
'task': 'listen',
'events': ['hover']
});
}
else if(message.type === 'hover') {
Plot.onHover(message);
}
}
window.removeEventListener('message', messageListener);
window.addEventListener('message', messageListener);
};
Plot.post = function post(o) {
document.getElementById(Plot.id).contentWindow.postMessage(o, Plot.domain);
};
Plot.init();
})();
</script>
</body>
</html>
这是根据 python 的 poltly.hover-events 示例修改而来的。我没有弹出图像,而是更改 onhover
回调以根据每个柱的 y
值绘制曲线。
主图表由python生成并插入此处作为iframe
。您可以使用任何语言制作您自己的语言,包括 R
。在此页面中,我们添加一个 <div id="myDiv"></div>
并使用 plotly.js 绘制其中的弹出图表。
将 R 数据框导出到 JS 环境
Shiny 使用jsonlite 将R
对象转换为json
并将它们发送给客户端。我们可以使用相同的机制来打包和发送我们的数据帧,以便 JS 回调可以使用数据来呈现弹出图表。
server.r
output$json <- reactive({
paste('<script>data =', RJSONIO::toJSON(your_data_frame, byrow=T, colNames=T),'</script>')
ui.r
fluidPage(..., htmlOutput("json"), ...)
在JS回调函数中,您可以像使用任何其他JS对象一样使用data
。
更多详细信息 here and here。
如果您想坚持使用 R
,您可以使用 Shiny
来获得几乎您想要的结果。当您悬停每个点时,将在主图下渲染图像。对于下面的示例,我使用了 mtcars
数据集的前三行。 运行代码只需要3个logos/images对应前三行的名字(下mtcars$name
,Mazda RX4
,Mazda RX4 Wag
,Datsun 710
在这个例子中)。
library(shiny)
library(plotly)
datatest <- diamonds %>% count(cut)
datatest$ImageNumber <- c(0, 1, 2, 3, 4)
datatest$name <- c("Image0", "Image1", "Image2", "Image3", "Image4")
ui <- fluidPage(
plotlyOutput("plot"),
# verbatimTextOutput("hover2"),
#imageOutput("hover"),
plotlyOutput("hover3")
)
server <- function(input, output, session) {
output$plot <- renderPlotly({
plot_ly(datatest, x = cut, y = n, type = "bar", marker = list(color = toRGB("black")))
})
selected_image <- reactive({
eventdat <- event_data('plotly_hover', source = 'A')
ImagePick <- as.numeric(eventdat[['pointNumber']])
sub <- datatest[datatest$ImageNumber %in% ImagePick, ]
return(sub)
})
# output$hover2 <- renderPrint({
#d <- event_data("plotly_hover")
#if (is.null(d)) "Hover events appear here (unhover to clear)" else d
#})
# output$hover <- renderImage({
# datag <- selected_image()
#filename <- normalizePath(file.path('/Users/drisk/Desktop/temp',
# paste(datag$name, '.png', sep='')))
# Return a list containing the filename and alt text
# list(src = filename,
# alt = paste("Image number", datag$name))
# }, deleteFile = FALSE)
output$hover3 <- renderPlotly({
datag <- selected_image()
# draw plot according to the point number on hover
plot_ly(data=datag, x = ImageNumber, y = n, mode = "scatter")
})
}
shinyApp(ui, server)
似乎张贴的答案不适合你@Adam_G。我一直在为自己的工作探索类似的库,并确定 Plot.ly
在您需要高级功能时并不总是正确的路径。你见过bokeh
吗?它基本上是为这类任务设计的,并且更容易实现(也是一个像 Plot.ly
这样的 D3.js 库)。这是他们发布的示例的副本,您可以在其中移动滑块来更改数据图(类似于 @gdlmx 为 Plot.ly 发布的示例,但您可以在不将其托管在网站上的情况下使用它)。我添加了 flexx
包,这样你就可以使用纯 Python (没有 JavaScript - 它可以将 Python 函数转换为 JavaScript (CustomJS.from_py_func(callback)
) https://github.com/zoofIO/flexx-notebooks/blob/master/flexx_tutorial_pyscript.ipynb):
from bokeh.io import vform
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import figure, output_file, show
import flexx
output_file("callback.html")
x = [x*0.005 for x in range(0, 200)]
y = x
source = ColumnDataSource(data=dict(x=x, y=y))
plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
def callback(source=source):
data = source.get('data')
f = cb_obj.get('value') #this is the bokeh callback object, linked to the slider below
x, y = data['x'], data['y']
for i in range(len(x)):
y[i] = x[i]**f #the slider value passed to this function as f will alter chart as a function of x and y
source.trigger('change') #as the slider moves, the chart will change
slider = Slider(start=0.1, end=4, value=1, step=.1, title="power", callback=CustomJS.from_py_func(callback))
layout = vform(slider, plot)
show(layout)
请参阅此处了解实际操作示例:http://docs.bokeh.org/en/0.10.0/docs/user_guide/interaction.html#customjs-for-widgets
要与悬停事件集成,请参见此处(from bokeh.models import HoverTool
):
http://docs.bokeh.org/en/0.10.0/docs/user_guide/interaction.html#customjs-for-hover
悬停示例:
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models import HoverTool
output_file("toolbar.html")
source = ColumnDataSource(
data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
desc=['A', 'b', 'C', 'd', 'E'],
)
)
hover = HoverTool(
tooltips=[
("index", "$index"),
("(x,y)", "($x, $y)"),
("desc", "@desc"),
]
)
p = figure(plot_width=400, plot_height=400, tools=[hover], title="Mouse over the dots")
p.circle('x', 'y', size=20, source=source)
show(p)
查看第一个代码,您可以在 def callback
函数下放置您想要的任何公式 - 需要进行一些尝试。您可以将鼠标悬停在其旁边(hform(leftchart, rightchart)
或上方/下方 (vform(topchart, bottomchart)
)。这作为 CustomJS
传递,bokeh
用于允许可扩展性flexx
允许您将其写入 Python。
另一种方法是使用 HTML 将您想要自定义的任何内容放在悬停 tooltips
上(尽管此示例将图像放在字典中而不是基础数据中的新图):http://docs.bokeh.org/en/0.10.0/docs/user_guide/tools.html#custom-tooltip
我正在为 R 使用 plotly,尽管我也愿意使用 Python 版本。当我将鼠标悬停在数据点上时,有没有办法让弹出窗口包含另一个图表?理想情况下,图表将根据数据创建,尽管我可以使用静态图像作为后备。
我不确定从哪里开始,并为没有 MWE 提前道歉。
解决方案 1:坚持使用 R
感谢@MLavoie。以下示例使用纯 R
创建两个图,"mainplot" 和 "hover" 对第一个的悬停事件做出反应。
library(shiny)
library(plotly)
ui <- fluidPage(
plotlyOutput("mainplot"),
plotlyOutput("hover")
)
server <- function(input, output) {
output$mainplot <- renderPlotly({
# https://plot.ly/r/
d <- diamonds[sample(nrow(diamonds), 1000), ]
plot_ly(d, x = carat, y = price, text = paste("Clarity: ", clarity), mode = "markers", color = carat, size = carat, source="main")
})
output$hover <- renderPlotly({
eventdat <- event_data('plotly_hover', source="main") # get event data from source main
if(is.null(eventdat) == T) return(NULL) # If NULL dont do anything
point <- as.numeric(eventdat[['pointNumber']]) # Index of the data point being charted
# draw plot according to the point number on hover
plot_ly( x = c(1,2,3), y = c(point, point*2, point*3), mode = "scatter")
})
}
shinyApp(ui, server)
这个例子使用了shiny binds for plotly
。 对于每个悬停事件,都会向服务器发送一个 POST
请求,然后服务器将更新弹出图表。它非常低效,因此在慢速连接上可能无法正常工作。
以上代码只是demo,还没有经过测试。查看一个有效且复杂得多的示例 here (with source).
解决方案 2:Javascript
是的,您可以使用 plotly Javascript API 来完成。
简答
- 使用
R
或Python
或任何其他支持的语言创建图表。 - 将图表插入新的 HTML 页面并添加回调函数,如下例所示。如果你对 DOM 有很好的了解,你也可以将 JS 添加到原来的 HTML 而不是创建一个新的
- 在回调函数中绘制弹出图形,回调函数接受包含悬停数据点数据的参数。
详情
如@MLavoie 所述,plotly.hover-events
中显示了一个很好的示例让我们深入研究代码。在JS文件中,有一个简单的回调函数附加到Plot
:
Plot.onHover = function(message) {
var artist = message.points[0].x.toLowerCase().replace(/ /g, '-');
var imgSrc = blankImg;
if(artistToUrl[artist] !== undefined) imgSrc = artistToUrl[artist];
Plot.hoverImg.src = imgSrc;
};
上面,artistToUrl
是一个充满base64字符串的巨大对象,我不会粘贴在这里溢出post。但是您可以在示例页面的 JS 选项卡下看到它。它有这样的结构:
var artistToUrl = { 'bob-dylan': 'data:image/jpeg;base64,/...',...}
工作示例:
为了演示,我准备了一个简单的例子here(点击试用):
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<iframe id="plot" style="width: 900px; height: 600px;" src="https://plot.ly/~jackp/10816.embed" seamless></iframe>
<div id="myDiv"></div>
<script>
(function main() {
var Plot = { id: 'plot', domain: 'https://plot.ly' };
Plot.onHover = function(message) {
var y = message.points[0].y; /*** y value of the data point(bar) under hover ***/
var line1 = {
x: [0.25,0.5,1], /*** dummy x array in popup-chart ***/
y: [1/y, 2, y], /*** dummy y array in popup-chart ***/
mode: 'lines+markers'
};
var layout = {
title:'Popup graph on hover',
height: 400,
width: 480
};
Plotly.newPlot('myDiv', [ line1 ], layout); // this finally draws your popup-chart
};
Plot.init = function init() {
var pinger = setInterval(function() {
Plot.post({task: 'ping'});
}, 500);
function messageListener(e) {
var message = e.data;
if(message.pong) {
console.log('Initial pong, frame is ready to receive');
clearInterval(pinger);
Plot.post({
'task': 'listen',
'events': ['hover']
});
}
else if(message.type === 'hover') {
Plot.onHover(message);
}
}
window.removeEventListener('message', messageListener);
window.addEventListener('message', messageListener);
};
Plot.post = function post(o) {
document.getElementById(Plot.id).contentWindow.postMessage(o, Plot.domain);
};
Plot.init();
})();
</script>
</body>
</html>
这是根据 python 的 poltly.hover-events 示例修改而来的。我没有弹出图像,而是更改 onhover
回调以根据每个柱的 y
值绘制曲线。
主图表由python生成并插入此处作为iframe
。您可以使用任何语言制作您自己的语言,包括 R
。在此页面中,我们添加一个 <div id="myDiv"></div>
并使用 plotly.js 绘制其中的弹出图表。
将 R 数据框导出到 JS 环境
Shiny 使用jsonlite 将R
对象转换为json
并将它们发送给客户端。我们可以使用相同的机制来打包和发送我们的数据帧,以便 JS 回调可以使用数据来呈现弹出图表。
server.r
output$json <- reactive({
paste('<script>data =', RJSONIO::toJSON(your_data_frame, byrow=T, colNames=T),'</script>')
ui.r
fluidPage(..., htmlOutput("json"), ...)
在JS回调函数中,您可以像使用任何其他JS对象一样使用data
。
更多详细信息 here and here。
如果您想坚持使用 R
,您可以使用 Shiny
来获得几乎您想要的结果。当您悬停每个点时,将在主图下渲染图像。对于下面的示例,我使用了 mtcars
数据集的前三行。 运行代码只需要3个logos/images对应前三行的名字(下mtcars$name
,Mazda RX4
,Mazda RX4 Wag
,Datsun 710
在这个例子中)。
library(shiny)
library(plotly)
datatest <- diamonds %>% count(cut)
datatest$ImageNumber <- c(0, 1, 2, 3, 4)
datatest$name <- c("Image0", "Image1", "Image2", "Image3", "Image4")
ui <- fluidPage(
plotlyOutput("plot"),
# verbatimTextOutput("hover2"),
#imageOutput("hover"),
plotlyOutput("hover3")
)
server <- function(input, output, session) {
output$plot <- renderPlotly({
plot_ly(datatest, x = cut, y = n, type = "bar", marker = list(color = toRGB("black")))
})
selected_image <- reactive({
eventdat <- event_data('plotly_hover', source = 'A')
ImagePick <- as.numeric(eventdat[['pointNumber']])
sub <- datatest[datatest$ImageNumber %in% ImagePick, ]
return(sub)
})
# output$hover2 <- renderPrint({
#d <- event_data("plotly_hover")
#if (is.null(d)) "Hover events appear here (unhover to clear)" else d
#})
# output$hover <- renderImage({
# datag <- selected_image()
#filename <- normalizePath(file.path('/Users/drisk/Desktop/temp',
# paste(datag$name, '.png', sep='')))
# Return a list containing the filename and alt text
# list(src = filename,
# alt = paste("Image number", datag$name))
# }, deleteFile = FALSE)
output$hover3 <- renderPlotly({
datag <- selected_image()
# draw plot according to the point number on hover
plot_ly(data=datag, x = ImageNumber, y = n, mode = "scatter")
})
}
shinyApp(ui, server)
似乎张贴的答案不适合你@Adam_G。我一直在为自己的工作探索类似的库,并确定 Plot.ly
在您需要高级功能时并不总是正确的路径。你见过bokeh
吗?它基本上是为这类任务设计的,并且更容易实现(也是一个像 Plot.ly
这样的 D3.js 库)。这是他们发布的示例的副本,您可以在其中移动滑块来更改数据图(类似于 @gdlmx 为 Plot.ly 发布的示例,但您可以在不将其托管在网站上的情况下使用它)。我添加了 flexx
包,这样你就可以使用纯 Python (没有 JavaScript - 它可以将 Python 函数转换为 JavaScript (CustomJS.from_py_func(callback)
) https://github.com/zoofIO/flexx-notebooks/blob/master/flexx_tutorial_pyscript.ipynb):
from bokeh.io import vform
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import figure, output_file, show
import flexx
output_file("callback.html")
x = [x*0.005 for x in range(0, 200)]
y = x
source = ColumnDataSource(data=dict(x=x, y=y))
plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)
def callback(source=source):
data = source.get('data')
f = cb_obj.get('value') #this is the bokeh callback object, linked to the slider below
x, y = data['x'], data['y']
for i in range(len(x)):
y[i] = x[i]**f #the slider value passed to this function as f will alter chart as a function of x and y
source.trigger('change') #as the slider moves, the chart will change
slider = Slider(start=0.1, end=4, value=1, step=.1, title="power", callback=CustomJS.from_py_func(callback))
layout = vform(slider, plot)
show(layout)
请参阅此处了解实际操作示例:http://docs.bokeh.org/en/0.10.0/docs/user_guide/interaction.html#customjs-for-widgets
要与悬停事件集成,请参见此处(from bokeh.models import HoverTool
):
http://docs.bokeh.org/en/0.10.0/docs/user_guide/interaction.html#customjs-for-hover
悬停示例:
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.models import HoverTool
output_file("toolbar.html")
source = ColumnDataSource(
data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
desc=['A', 'b', 'C', 'd', 'E'],
)
)
hover = HoverTool(
tooltips=[
("index", "$index"),
("(x,y)", "($x, $y)"),
("desc", "@desc"),
]
)
p = figure(plot_width=400, plot_height=400, tools=[hover], title="Mouse over the dots")
p.circle('x', 'y', size=20, source=source)
show(p)
查看第一个代码,您可以在 def callback
函数下放置您想要的任何公式 - 需要进行一些尝试。您可以将鼠标悬停在其旁边(hform(leftchart, rightchart)
或上方/下方 (vform(topchart, bottomchart)
)。这作为 CustomJS
传递,bokeh
用于允许可扩展性flexx
允许您将其写入 Python。
另一种方法是使用 HTML 将您想要自定义的任何内容放在悬停 tooltips
上(尽管此示例将图像放在字典中而不是基础数据中的新图):http://docs.bokeh.org/en/0.10.0/docs/user_guide/tools.html#custom-tooltip