Skip to content

Commit

Permalink
Merge pull request #155 from PumasAI/jk/plotly
Browse files Browse the repository at this point in the history
Fix Plotly require.js config mechanism
  • Loading branch information
jkrumbiegel authored Jun 19, 2024
2 parents 6248b6a + 24db3d8 commit a56f555
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 23 deletions.
53 changes: 48 additions & 5 deletions src/QuartoNotebookWorker/ext/QuartoNotebookWorkerPlotlyBaseExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,60 @@ module QuartoNotebookWorkerPlotlyBaseExt
import QuartoNotebookWorker
import PlotlyBase

QuartoNotebookWorker._mimetype_wrapper(p::PlotlyBase.Plot) = PlotlyBasePlot(p)
struct PlotlyBasePlotWithoutRequireJS
plot::PlotlyBase.Plot
end

struct PlotlyRequireJSConfig end

const FIRST_PLOT_DISPLAYED = Ref(false)

function QuartoNotebookWorker.expand(p::PlotlyBase.Plot)

plotcell = QuartoNotebookWorker.Cell(PlotlyBasePlotWithoutRequireJS(p))

# Quarto expects that the require.js preamble which Plotly needs to function
# comes in its own cell, which will then be hoisted into the HTML page header.
# So we cannot have that preamble concatenated with every plot's HTML content.
# Instead, e keep track whether a Plotly plot is the first per notebook, and in that
# case have it expand into the preamble cell and the plot cell. If it's not the
# first time, we expand only into the plot cell.
cells = if !FIRST_PLOT_DISPLAYED[]
[QuartoNotebookWorker.Cell(PlotlyRequireJSConfig()), plotcell]
else
[plotcell]
end

FIRST_PLOT_DISPLAYED[] = true

struct PlotlyBasePlot <: QuartoNotebookWorker.WrapperType
value::PlotlyBase.Plot
return cells
end

function Base.show(io::IO, ::MIME"text/html", wrapper::PlotlyBasePlot)
function Base.show(io::IO, ::MIME"text/html", p::PlotlyBasePlotWithoutRequireJS)
# We want to embed only the minimum markup needed to render the
# plotlyjs plots, otherwise a full HTML page is generated for every
# plot which does not render correctly in our context.
PlotlyBase.to_html(io, wrapper.value; include_plotlyjs = "require", full_html = false)
# "require-loaded" means that we pass the require.js preamble ourselves.
PlotlyBase.to_html(io, p.plot; include_plotlyjs = "require-loaded", full_html = false)
end

Base.show(io::IO, M::MIME, p::PlotlyBasePlotWithoutRequireJS) = show(io, M, p.plot)
Base.show(io::IO, m::MIME"text/plain", p::PlotlyBasePlotWithoutRequireJS) =
show(io, m, p.plot)
Base.showable(M::MIME, p::PlotlyBasePlotWithoutRequireJS) = showable(M, p.plot)

function Base.show(io::IO, ::MIME"text/html", ::PlotlyRequireJSConfig)
print(io, PlotlyBase._requirejs_config())
end

function reset_first_plot_displayed_flag!()
FIRST_PLOT_DISPLAYED[] = false
end

function __init__()
if ccall(:jl_generating_output, Cint, ()) == 0
QuartoNotebookWorker.add_package_refresh_hook!(reset_first_plot_displayed_flag!)
end
end

end
18 changes: 1 addition & 17 deletions src/QuartoNotebookWorker/ext/QuartoNotebookWorkerPlotlyJSExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,6 @@ module QuartoNotebookWorkerPlotlyJSExt
import QuartoNotebookWorker
import PlotlyJS

QuartoNotebookWorker._mimetype_wrapper(p::PlotlyJS.SyncPlot) = PlotlyJSSyncPlot(p)

struct PlotlyJSSyncPlot <: QuartoNotebookWorker.WrapperType
value::PlotlyJS.SyncPlot
end

function Base.show(io::IO, ::MIME"text/html", wrapper::PlotlyJSSyncPlot)
# We want to embed only the minimum markup needed to render the
# plotlyjs plots, otherwise a full HTML page is generated for every
# plot which does not render correctly in our context.
PlotlyJS.PlotlyBase.to_html(
io,
wrapper.value.plot;
include_plotlyjs = "require",
full_html = false,
)
end
QuartoNotebookWorker.expand(p::PlotlyJS.SyncPlot) = QuartoNotebookWorker.expand(p.plot)

end
9 changes: 8 additions & 1 deletion test/testsets/integrations/PlotlyJS.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ if Sys.iswindows()
else
test_example(joinpath(@__DIR__, "../../examples/integrations/PlotlyJS.qmd")) do json
cells = json["cells"]
for nth in (4, 6)
preamble_cell = cells[5]
outputs = preamble_cell["outputs"]
@test length(outputs) == 1
data = outputs[1]["data"]
@test keys(data) == Set(["text/html", "text/plain"])
@test startswith(data["text/html"], "<script type=\"text/javascript\">")
@test occursin("require.undef(\"plotly\")", data["text/html"])
for nth in (6, 9)
cell = cells[nth]
outputs = cell["outputs"]
@test length(outputs) == 1
Expand Down

0 comments on commit a56f555

Please sign in to comment.