Open jdamiba opened 4 years ago
Hi @jdamiba thanks for filling out the issue template -- very helpful
I haven't tried this exact use case before...
Let's call in some webIO experts and see if they can help us sort this out
cc @shashi @travigd
Thanks @sglyon ! If this cannot be made to work, is there some other known way of going from PlotlyJS.jl-plot-in-a-notebook to HTML?
WebIO doesn't have a way to export to static HTML. This is something we've talked about a bit, but it's hard because WebIO is really designed around interactive use cases (think widgets) which require JS to work.
Looking around at the codebase, it makes pretty heavy use of WebIO, so I'm not sure that it's possible.
What we might be able to do in WebIO would be to have a static_html
function that tries to strip out all of the fancier stuff, but I'm not even sure if that would work since PlotlyJS.jl uses WebIO's import system to bootstrap the plot with JS.
@sglyon Does the application/vnd.plotly.v1+json
MIME work?
So the way this works in our Python integration is that our equivalent of WebIO inlines all of the Javascript in the resulting HTML, so this isn't totally "static" HTML :) This isn't something you've tried tackling in WebIO.jl/PlotlyJS.jl yet?
Hey @nicolaskruchten thanks for stepping in
We do have a "static" html functionality built into the package. I haven't ever tried to get it set up for use with nbconvert. We can make it happen though!!
Can you describe how your python integration works with both active jupyter sessions and static html exports via nbconvert? Perhaps you utilize the plotly mimetype for jupyterlab (notebook too?) AND include a payload for text/html
mimetype that nbconvert uses?
How do you guarantee that the html has plotly.js loaded (perhaps via script tag that points to cdn, or inline minified js?)? How do you ensure you only have one plotly.js load attempt if you have multiple plots in the exported notebook?
Sorry lots of questions, but I want to make sure we get to the bottom of this!
I'm going to tag in @jonmmease here to answer the questions above :)
Hi @sglyon, these are definitely the right questions! In version 4 of plotly.py, we introduced a renderers framework that lets us support a bunch of different ways to display figures in a variety of contexts. The documentation for this is at https://plotly.com/python/renderers/. Here's the current set of renderers (with a few aliases removed): ['plotly_mimetype', 'notebook', 'notebook_connected', 'colab', 'cocalc', 'databricks', 'json', 'png', 'jpeg', 'jpg', 'svg', 'pdf', 'browser', 'firefox', 'chrome', 'chromium', 'iframe', 'iframe_connected', 'sphinx_gallery']
Renderers can be combined so that multiple representations are produced when a figure is displayed. For example, the default renderer (when no specialized environment is detected) is 'plotly_mimetype+notebook'
.
plotly_mimetype
The plotly_mimetype
renderer produces the application/vnd.plotly.v1+json
bundle and works in environments that include support for rendering the mimetype. These include JupyterLab (with the jupyterlab-plotly
extension), nteract, and vscode. In this case, we don't provide the plotly.js bundle as that is the responsibility of the front-end.
notebook
and notebook_connected
The notebook
renderer is designed to work in the classic Jupyter notebook and the output of nbconvert. The first time the renderer is used to display a figure, it adds an HTML snippet to the notebook that configures require.js to load the plotly.js bundle. notebook
does this by inserting the full bundle and so it works offline but adds a few MB to the notebook size. notebook_connected
points to a plotly.js CDN so it requires internet access but doesn't increase notebook size.
This require.js initialization is done in the HtmlRenderer.activate
method https://github.com/plotly/plotly.py/blob/5b0e8d3ccab55fe1a6e4ba123cfc9d718a9ffc5a/packages/python/plotly/plotly/io/_base_renderers.py#L267-L324.
Then each time a figure is displayed, it accesses plotly.js using requirejs (assuming that the init code was run to register plotly.js with require.js). This is done in the plotly.io.to_html
function https://github.com/plotly/plotly.py/blob/5b0e8d3ccab55fe1a6e4ba123cfc9d718a9ffc5a/packages/python/plotly/plotly/io/_html.py#L265-L267.
require.js is available in the classic notebook and in the default output of nbconvert. People do run into problems sometimes though when they include the nbconvert results in a custom template that doesn't include, or isn't compatible with, require.js.
colab
and databricks
Some Jupyter-like environments don't support the plotly mimetype or require.js. In these cases we have custom renderers tailored for the environemnt. colab
, for example, displays the results of each notebook cell in a self-contained html document in an iframe. So in that case we output a full html document and use a script tag to pull plotly.js from a CDN. Databricks provides their own custom displayHTML function that needs to be used instead of the IPython.display
mechanism, and so we have a custom renderer for that environment.
Hope that's helpful! It's not a simple problem unfortunately, and there may be simpler solutions than what we've figured out over the years.. Let me know if you have any other questions on what we're doing.
Thanks all!
That is very helpful info. I will not be able to work on this too much in the short run, but would be happy to support anyone who is brave enough to take it on.
Thanks
I think Plots.jl can already basically do this with the basic plotly
backend, I'm gonna try and strip it out and create a MWE over PlotlyJS.
Cool so @sglyon I have basic functionality working with only the PlotlyBase package.
import UUIDs
function plotly_html_body(plt)
uuid = UUIDs.uuid4()
html = """
<div id=\"$(uuid)\"></div>
<script>
PLOT = document.getElementById('$(uuid)');
require(['https://cdn.plot.ly/plotly-latest.min.js'], function(plotly) {
plotly.plot(PLOT, $(string(plt.data)), $(string(plt.layout)));
}, function(err) { console.error(err); PLOT.innerText = err; PLOT.classList.add("error", "output_stderr") } )
</script>
"""
html
end
Base.show(io::IO, ::MIME"text/html", p::PlotlyBase.Plot) = write(io, plotly_html_body(p))
This nearly works. Unfortunately, it looks like you are overriding IJulia.display_dict in PlotlyBase, and preventing if from detecting that it can now render text/html. If I add in a sneaky
Base.delete_method(@which IJulia.display_dict(plot))
Then this results in working plots in jupyter notebooks, that continue to work on static html exports, with none of PlotlyJS required at all (which is great in my case because I don't care for WebIO, Blink etc.).
Is there any chance you could consider removing the display_dict
override in PlotlyBase.jl?
Sidenote: it's neat to see the layering work being done here, and it mirrors certain aspects of our Python system, where we have plotly.io.show( <bare dict representation of figure> )
as well as fig.show()
which internally calls the former :)
Is there any chance you could consider removing the display_dict override in PlotlyBase.jl?
@sglyon ping on this :) I'm happy to submit a PR, just want to check that it would be accepted as it makes PlotlyBase.jl possible to use directly in jupyter notebooks, which might be a change in project direction. (imo a useful one, but I'm not the author of these packages!)
HI @akdor1154 sorry for delay -- haven't had time to look at this one yet.
I will hopefully find time soon and will ping you when I do
What's the verdict on how to get plotly plots to render both in jupyter lab and exported HTML, when using PlotlyJS
directly? (rather than using Plots; plotlyjs()
)
Naively attempting to follow @akdor1154's suggestion, I get an error:
MethodError: no method matching display_dict(::PlotlyJS.SyncPlot)
Closest candidates are:
display_dict(::Plot) at /Users/alex/.julia/packages/PlotlyBase/GDbp9/src/PlotlyBase.jl:110
Which results from deleting that method.
@sglyon ?
@sglyon
Working Solution:
using PlotlyJS
p = plot(scatter(x=1:10, y=1:10));
io = PipeBuffer()
PlotlyBase.to_html(io, p.plot, full_html=false)
HTML(read(io, String))
This shows the plot in Jupyter lab itself properly. After using nbconvert, make sure to add <script src="https://cdn.plot.ly/plotly-2.3.0.min.js"></script>
in <head>
of html file produced by nbconvert (can be automatized with a template file). This is to ensure that plotly-2.3.0.min.js script is loaded before the plot. Otherwise you will see a blank page. Another alternative is to make everything local with include_plotlyjs I presume.
The produced plot is fully interactive.
using PlotlyJS p = plot(scatter(x=1:10, y=1:10)); io = PipeBuffer() PlotlyBase.to_html(io, p.plot, full_html=false) HTML(read(io, String))
This does not work for me. Nothing is shown in Jupyterlab and nothing after converting with nbconvert and copying the mentioned line in the html file. Edit: just revisited and I see the plot in Jupyterlab, but the page is blank in the converted html.
I'm on julia v1.10.3, and:
[7073ff75] IJulia v1.24.2
[f0f68f2c] PlotlyJS v0.18.13
@PrParadox Do you have any idea, what could be the issue? (I included the plotly script in the final html.)
@cserteGT3
I had the same issue. Make sure you add the script tag at the top of the head section
<head><meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>My Notebook</title>
<script src="https://cdn.plot.ly/plotly-2.9.0.min.js"></script>
I have had the same issue when trying to export a notebook to html using quarto (see code below). All the plots using PlotlyJS do show in the notebook but not in the rendered html. However, plots created using Plots with the plotly() or plotlyjs() backend show both in the notebook and in the html. Plots using StatsPlots are sometimes shown, sometimes not (can't get a consistent behaviour here and it is a bit weird since StatsPlots is just a recipe based on Plots as far as I understand). Plots using PyCall.pyimport("plotly") show in the html but not in the notebook. Saving the PlotlyJS plot as html and importing its in an iframe works.
Hope this helps!
###
import PlotlyJS
PlotlyJS.set_default_renderer(PlotlyJS.DOCS)
# to get the different redenrers
# Base.Enums.namemap(PlotlyJS.RENDERERS)
import IJulia
# Generate some sample data
z = rand(100)
y = rand(100)
# Create a scatter plot with y and z using PlotlyJS
trace = PlotlyJS.scatter(x=y, y=z, mode="markers")
layout = PlotlyJS.Layout(title="Scatter Plot of Y vs Z", xaxis_title="Y values", yaxis_title="Z values")
fig = PlotlyJS.Plot(trace, layout)
fig
PlotlyJS.savefig(fig,"fig/random_scatter.html")
# Embed the generated scatter plot HTML file using an iframe in markdown
display("text/html", """
<iframe src="fig/random_scatter.html" width="100%" height="400"></iframe>
""")
###
using StatsPlots
# Set the backend to plotly
plotly()
# Create a scatter plot with y and z using StatsPlots with plotly backend
StatsPlots.scatter(y, z, color=:blue, markershape=:circle, legend=false, title="Scatter Plot of Y vs Z", xlabel="Y values", ylabel="Z values")
using DataFrames
df = DataFrame(a = 1:10, b = 10 .* rand(10), c = 10 .* rand(10))
@df df StatsPlots.plot(:a, [:b :c], colour = [:red :blue])
###
import Plots
# Set the backend to Plotly
Plots.plotly()
# Create a scatter plot with y and z using Plots with Plotly backend
Plots.scatter(y, z, color=:blue, markershape=:circle, legend=false, title="Scatter Plot of Y vs Z", xlabel="Y values", ylabel="Z values")
###
import PyCall
plotly_py=PyCall.pyimport("plotly")
plotly_py.io.renderers.default = "plotly_mimetype+notebook_connected"
# Create a scatter plot with y and z using plotly_py
trace = plotly_py.graph_objs.Scatter(x=y, y=z, mode="markers")
layout = plotly_py.graph_objs.Layout(title="Scatter Plot of Y vs Z", xaxis=Dict("title"=>"Y values"), yaxis=Dict("title"=>"Z values"))
fig = plotly_py.graph_objs.Figure(data=[trace], layout=layout)
fig
Julia Version 1.11.1
Commit 8f5b7ca12a (2024-10-16 10:53 UTC)
Build Info:
Official https://julialang.org/ release
Platform Info:
OS: Windows (x86_64-w64-mingw32)
CPU: 8 × Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz
WORD_SIZE: 64
LLVM: libLLVM-16.0.6 (ORCJIT, skylake)
Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)
Environment:
JULIA_EDITOR = code
JULIA_NUM_THREADS =
EDIT: Inspection of the html file leads the following error: which is link to this line (in the cell where PlotlyJS is used)
Describe the bug
Background: I work at Plotly, and I'm trying to figure out how to better document the
plotlyjs.jl
library alongside theplotly.jl
library.Issue: Figures I create using
plotlyjs.jl
in a Jupyter Notebook do not render when exported as.html
files usingnbconvert
becauseWebIO
is not present in the runtime.My question Is: how can I get the figures I create with
plotlyjs.jl
in a jupyter notebook to render when exported to.html
?Screenshot:
Steps to Reproduce: Create a figure using PlotlyJS.jl in a Jupyter Notebook with a Julia 1.4.1 kernel, then use
jupyter nbconvert
to transform the notebook into an .html file.Version info