在 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 来完成。

简答

  1. 使用 RPython 或任何其他支持的语言创建图表。
  2. 将图表插入新的 HTML 页面并添加回调函数,如下例所示。如果你对 DOM 有很好的了解,你也可以将 JS 添加到原来的 HTML 而不是创建一个新的
  3. 在回调函数中绘制弹出图形,回调函数接受包含悬停数据点数据的参数。

详情

如@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 使用jsonliteR 对象转换为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