Literate.jl icon indicating copy to clipboard operation
Literate.jl copied to clipboard

How would I capture PlotlyJS.jl figures as HTML?

Open MikaelSlevinsky opened this issue 4 years ago • 11 comments

I'm currently setup to convert a .jl script to markdown using Literate.markdown for inclusion as generated pages in Documenter. But the captured output gives the figures a random number name and thus Plots.jl defaults to saving the figure as a PNG instead of HTML. Is this avoidable?

My basic script is here https://github.com/JuliaApproximation/FastTransforms.jl/blob/master/examples/disk.jl

The docs build is here https://github.com/JuliaApproximation/FastTransforms.jl/blob/master/docs/make.jl

MikaelSlevinsky avatar Nov 25 '20 23:11 MikaelSlevinsky

Does Documenter handle this? In that case, perhaps try with execute=false here: https://github.com/JuliaApproximation/FastTransforms.jl/blob/04008f6e863c7879e03b2bf345f0b8ec4023c6c2/docs/make.jl#L20 instead?

Currently, markdown execution only handles image/png and image/jpeg, see https://github.com/fredrikekre/Literate.jl/blob/5db8c22fcfe74470d1ab836d566afbc8a9e86d9f/src/Literate.jl#L481.

fredrikekre avatar Nov 26 '20 00:11 fredrikekre

Ok so this is a feature request (for the whole package chain)?

MikaelSlevinsky avatar Nov 26 '20 02:11 MikaelSlevinsky

Probably, but I thought it worked in Documenter already, but maybe I misremember. I think https://github.com/JuliaDocs/Documenter.jl/issues/1247 is causing some trouble for including the necessary js library.

fredrikekre avatar Nov 26 '20 12:11 fredrikekre

Thanks for the link! I thought PloylyJS's docs used HTML plots a while back, but it appears they're PNG now. Perhaps this is related.

MikaelSlevinsky avatar Nov 26 '20 16:11 MikaelSlevinsky

Maybe Literate could produce a raw block for documenter?

https://github.com/JuliaDocs/Documenter.jl/issues/1149

MikaelSlevinsky avatar Nov 26 '20 16:11 MikaelSlevinsky

So my hack is to add commented raw html and use your post processing https://github.com/JuliaApproximation/FastTransforms.jl/commit/ecc0e592beabcdf32074468887c472c1d6263bb3 results in https://juliaapproximation.github.io/FastTransforms.jl/dev/generated/disk/

MikaelSlevinsky avatar Nov 26 '20 20:11 MikaelSlevinsky

Okay, using <object> is a nice idea. I developed that idea a bit further, and if you put this in docs/make.jl

import Plots
struct HTMLPlot
    p # :: Plots.Plot
end
const ROOT_DIR = joinpath(@__DIR__, "build")
const PLOT_DIR = joinpath(ROOT_DIR, "plots")
function Base.show(io::IO, ::MIME"text/html", p::HTMLPlot)
    mkpath(PLOT_DIR)
    path = joinpath(PLOT_DIR, string(hash(p) % UInt32, ".html"))
    Plots.savefig(p.p, path)
    print(io, "<object type=\"text/html\" data=\"../$(relpath(path, ROOT_DIR))\" style=\"width:100%;height:600px;\"></object>")
end

you can just return HTMLPlot(p) from your blocks, like this:

```@example test
import Plots, PlotlyJS
Plots.plotlyjs()
x = range(0, 2pi, length=100)
y = sin.(x)
p = Plots.plot(x, y)
Main.HTMLPlot(p)
```

fredrikekre avatar Nov 27 '20 09:11 fredrikekre

I thought the point of using Literate was that the .jl scripts for, e.g. a repository's examples, are still standalone. Wouldn't Main.HTMLPlot(p) throw an error unless one is building the docs (or loaded docs/make.jl)?

Perhaps a more seamless solution would be if Literate.markdown supported another keyword, say plot_extension = :html / :png / :svg / ... and then execute_markdown! would depend more specifically on the extension, with the guts of the show method above for :html.

Also, in the object type, the height seems to make an appreciable difference (I switched back to default size with height 400px for better mobile support), since it's a fixed pixel size. Given a plot p, the object height could be specified by taking the plot's height as size(p.o)[2] or p.attr[:size][2].

MikaelSlevinsky avatar Nov 27 '20 15:11 MikaelSlevinsky

Okay, using <object> is a nice idea. I developed that idea a bit further, and if you put this in docs/make.jl

import Plots
struct HTMLPlot
    p # :: Plots.Plot
end
const ROOT_DIR = joinpath(@__DIR__, "build")
const PLOT_DIR = joinpath(ROOT_DIR, "plots")
function Base.show(io::IO, ::MIME"text/html", p::HTMLPlot)
    mkpath(PLOT_DIR)
    path = joinpath(PLOT_DIR, string(hash(p) % UInt32, ".html"))
    Plots.savefig(p.p, path)
    print(io, "<object type=\"text/html\" data=\"../$(relpath(path, ROOT_DIR))\" style=\"width:100%;height:600px;\"></object>")
end

you can just return HTMLPlot(p) from your blocks, like this:

```@example test
import Plots, PlotlyJS
Plots.plotlyjs()
x = range(0, 2pi, length=100)
y = sin.(x)
p = Plots.plot(x, y)
Main.HTMLPlot(p)

Thanks @fredrikekre! Works like a charm! Just FYI, when using prettyurl in the CI framework, I had to make the following modification:

function Base.show(io::IO, ::MIME"text/html", p::HTMLPlot)
    mkpath(PLOT_DIR)
    path = joinpath(PLOT_DIR, string(hash(p) % UInt32, ".html"))
    Plots.savefig(p.p, path)
    if get(ENV, "CI", "false") == "true" # for prettyurl
        print(io, "<object type=\"text/html\" data=\"../../$(relpath(path, ROOT_DIR))\" style=\"width:100%;height:425px;\"></object>")
    else
        print(io, "<object type=\"text/html\" data=\"../$(relpath(path, ROOT_DIR))\" style=\"width:100%;height:425px;\"></object>")
    end
end

JakobAsslaender avatar Jul 15 '21 17:07 JakobAsslaender

I have another question regarding PlotlyJS though. Does anyone have an idea how to get them working in Jupyter notebooks?

On my own computer I got them to work with this extension, i.e. by executing this command

jupyter labextension install jupyterlab-plotly

But I am puzzled how to incorporate that in the make.jl or somewhere so that a) the precomputed notebooks (executed by the GitHub action) display the figures and b) binder knows how to handle these plots. Neither is working right now.

@fredrikekre, do you have yet another trick up your sleeve? Thank a ton for your help in advance!

JakobAsslaender avatar Jul 16 '21 13:07 JakobAsslaender

No idea, sorry.

fredrikekre avatar Jul 16 '21 13:07 fredrikekre