From ac01d392e974789bc447dc97050ad42dfbdbc38f Mon Sep 17 00:00:00 2001 From: Alexey Stukalov Date: Thu, 12 Dec 2024 13:16:35 -0800 Subject: [PATCH 1/6] cleanup savefig() logic and docs --- src/kaleido.jl | 110 +++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 68 deletions(-) diff --git a/src/kaleido.jl b/src/kaleido.jl index 49da826..147f61f 100644 --- a/src/kaleido.jl +++ b/src/kaleido.jl @@ -2,26 +2,43 @@ using PlotlyKaleido: kill, is_running, start, restart, ALL_FORMATS, TEXT_FORMATS savefig(p::SyncPlot; kwargs...) = savefig(p.plot; kwargs...) +""" + savefig( + [io::IO], p::Plot, [fn:AbstractString]; + width::Union{Nothing,Integer}=700, + height::Union{Nothing,Integer}=500, + scale::Union{Nothing,Real}=nothing, + format::String="png", + ) + +Save a plot `p` to the IO stream `io` or to the file named `fn`. + +If both `io` and `fn` are not specified, returns the image as a vector of bytes. + +## Keyword Arguments + + - `format`: the image format, must be one of $(join(string.("*", ALL_FORMATS, "*"), ", ")), or *html*. + - `scale`: the image scale. + - `width` and `height`: the image dimensions, in pixels. +""" function savefig( p::Plot; - width::Union{Nothing,Int}=nothing, - height::Union{Nothing,Int}=nothing, + width::Union{Nothing,Integer}=700, + height::Union{Nothing,Integer}=500, scale::Union{Nothing,Real}=nothing, - format::String="png" - )::Vector{UInt8} - if !(format in ALL_FORMATS) - error("Unknown format $format. Expected one of $ALL_FORMATS") - end + format::String="png", + ) + in(format, ALL_FORMATS) || + throw(ArgumentError("Unknown format: $format. Expected one of: $(join(ALL_FORMATS, ", "))")) # construct payload - _get(x, def) = x === nothing ? def : x payload = Dict( - :width => _get(width, 700), - :height => _get(height, 500), - :scale => _get(scale, 1), :format => format, :data => p ) + isnothing(width) || (payload[:width] = width) + isnothing(height) || (payload[:height] = height) + isnothing(scale) || (payload[:scale] = scale) _ensure_kaleido_running() P = PlotlyKaleido.P @@ -57,66 +74,27 @@ end @inline _get_Plot(p::Plot) = p @inline _get_Plot(p::SyncPlot) = p.plot -""" - savefig( - io::IO, - p::Plot; - width::Union{Nothing,Int}=nothing, - height::Union{Nothing,Int}=nothing, - scale::Union{Nothing,Real}=nothing, - format::String="png" - ) - -Save a plot `p` to the io stream `io`. They keyword argument `format` -determines the type of data written to the figure and must be one of -$(join(ALL_FORMATS, ", ")), or html. `scale` sets the -image scale. `width` and `height` set the dimensions, in pixels. Defaults -are taken from `p.layout`, or supplied by plotly -""" -function savefig(io::IO, - p::Union{SyncPlot,Plot}; - width::Union{Nothing,Int}=nothing, - height::Union{Nothing,Int}=nothing, - scale::Union{Nothing,Real}=nothing, - format::String="png") +function savefig(io::IO, p::Union{SyncPlot,Plot}; + format::AbstractString="png", + kwargs...) if format == "html" return show(io, MIME("text/html"), _get_Plot(p), include_mathjax="cdn", include_plotlyjs="cdn", full_html=true) end - bytes = savefig(p, width=width, height=height, scale=scale, format=format) + bytes = savefig(p; format, kwargs...) write(io, bytes) end - -""" - savefig( - p::Plot, fn::AbstractString; - format::Union{Nothing,String}=nothing, - width::Union{Nothing,Int}=nothing, - height::Union{Nothing,Int}=nothing, - scale::Union{Nothing,Real}=nothing, - ) - -Save a plot `p` to a file named `fn`. If `format` is given and is one of -$(join(ALL_FORMATS, ", ")), or html; it will be the format of the file. By -default the format is guessed from the extension of `fn`. `scale` sets the -image scale. `width` and `height` set the dimensions, in pixels. Defaults -are taken from `p.layout`, or supplied by plotly -""" function savefig( p::Union{SyncPlot,Plot}, fn::AbstractString; - format::Union{Nothing,String}=nothing, - width::Union{Nothing,Int}=nothing, - height::Union{Nothing,Int}=nothing, - scale::Union{Nothing,Real}=nothing, + format::Union{Nothing,AbstractString}=nothing, + kwargs... ) ext = split(fn, ".")[end] - if format === nothing - format = String(ext) - end + format = something(format, String(ext)) open(fn, "w") do f - savefig(f, p; format=format, scale=scale, width=width, height=height) + savefig(f, p; format, kwargs...) end return fn end @@ -136,20 +114,16 @@ const _KALEIDO_MIMES = Dict( for (mime, fmt) in _KALEIDO_MIMES @eval function Base.show( - io::IO, ::MIME{Symbol($mime)}, plt::Plot, - width::Union{Nothing,Int}=nothing, - height::Union{Nothing,Int}=nothing, - scale::Union{Nothing,Real}=nothing, + io::IO, ::MIME{Symbol($mime)}, plt::Plot; + kwargs... ) - savefig(io, plt, format=$fmt) + savefig(io, plt; format=$fmt, kwargs...) end @eval function Base.show( - io::IO, ::MIME{Symbol($mime)}, plt::SyncPlot, - width::Union{Nothing,Int}=nothing, - height::Union{Nothing,Int}=nothing, - scale::Union{Nothing,Real}=nothing, + io::IO, ::MIME{Symbol($mime)}, plt::SyncPlot; + kwargs... ) - savefig(io, plt.plot, format=$fmt) + savefig(io, plt.plot; format=$fmt, kwargs...) end end From 58d63a9f0692e04c1b735243d977dd2a0f944cf8 Mon Sep 17 00:00:00 2001 From: Alexey Stukalov Date: Thu, 12 Dec 2024 13:17:45 -0800 Subject: [PATCH 2/6] savefig(): allow specifying js library and version --- src/kaleido.jl | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/kaleido.jl b/src/kaleido.jl index 147f61f..9dfb373 100644 --- a/src/kaleido.jl +++ b/src/kaleido.jl @@ -9,6 +9,8 @@ savefig(p::SyncPlot; kwargs...) = savefig(p.plot; kwargs...) height::Union{Nothing,Integer}=500, scale::Union{Nothing,Real}=nothing, format::String="png", + plotlyjs::Union{AbstractString, Nothing}=nothing, + plotly_version::Union{AbstractString, Nothing}=nothing ) Save a plot `p` to the IO stream `io` or to the file named `fn`. @@ -20,6 +22,9 @@ If both `io` and `fn` are not specified, returns the image as a vector of bytes. - `format`: the image format, must be one of $(join(string.("*", ALL_FORMATS, "*"), ", ")), or *html*. - `scale`: the image scale. - `width` and `height`: the image dimensions, in pixels. + - `plotly_version`, `plotly_js`: the version of *Plotly* JavaScript library to use for rendering. + These arguments are mutually exclusive. + Defaults to using the Plotly library bundled with *PlotlyJS.jl*. """ function savefig( p::Plot; @@ -27,6 +32,8 @@ function savefig( height::Union{Nothing,Integer}=500, scale::Union{Nothing,Real}=nothing, format::String="png", + plotlyjs::Union{AbstractString, Nothing}=nothing, + plotly_version::Union{AbstractString, Nothing}=nothing ) in(format, ALL_FORMATS) || throw(ArgumentError("Unknown format: $format. Expected one of: $(join(ALL_FORMATS, ", "))")) @@ -40,7 +47,7 @@ function savefig( isnothing(height) || (payload[:height] = height) isnothing(scale) || (payload[:scale] = scale) - _ensure_kaleido_running() + _ensure_kaleido_running(; plotlyjs, plotly_version) P = PlotlyKaleido.P # convert payload to vector of bytes bytes = transcode(UInt8, JSON.json(payload)) @@ -99,7 +106,25 @@ function savefig( return fn end -_ensure_kaleido_running(; kwargs...) = !is_running() && restart(; plotlyjs=_js_path, kwargs...) +# If kaleido is not running, starts it using the specified plotly library. +# The plotly library is specified either as the `plotlyjs` path to the javascript library, +# or as a `plotly_version` (in the latter case the library is taken from `https://cdn.plot.ly/`). +# If none are specified, the plotly library from the `Artifacts.toml` is used. +function _ensure_kaleido_running(; + plotlyjs::Union{AbstractString, Nothing} = nothing, + plotly_version::Union{AbstractString, Nothing} = nothing, + kwargs... +) + if !is_running() + !isnothing(plotly_version) && !isnothing(plotlyjs) && + throw(ArgumentError("Cannot specify both `plotly_version` and `plotlyjs`")) + if !isnothing(plotly_version) + restart(; plotly_version, kwargs...) + else + resstart(; plotlyjs=something(plotlyjs, _js_path), kwargs...) + end + end +end const _KALEIDO_MIMES = Dict( "application/pdf" => "pdf", From 9e951ee00bdc1d954c1cc1442e50ab0b3d58e3db Mon Sep 17 00:00:00 2001 From: Alexey Stukalov Date: Thu, 12 Dec 2024 14:29:58 -0800 Subject: [PATCH 3/6] fixup typo --- src/kaleido.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kaleido.jl b/src/kaleido.jl index 9dfb373..c0ca82d 100644 --- a/src/kaleido.jl +++ b/src/kaleido.jl @@ -121,7 +121,7 @@ function _ensure_kaleido_running(; if !isnothing(plotly_version) restart(; plotly_version, kwargs...) else - resstart(; plotlyjs=something(plotlyjs, _js_path), kwargs...) + restart(; plotlyjs=something(plotlyjs, _js_path), kwargs...) end end end From 340119e5ead41df091fc644671226e4b23ea5294 Mon Sep 17 00:00:00 2001 From: Alexey Stukalov Date: Thu, 12 Dec 2024 14:59:53 -0800 Subject: [PATCH 4/6] update savefig docs --- docs/src/manipulating_plots.md | 82 ++++++++++++++-------------------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/docs/src/manipulating_plots.md b/docs/src/manipulating_plots.md index e85fa64..27f6ef1 100644 --- a/docs/src/manipulating_plots.md +++ b/docs/src/manipulating_plots.md @@ -138,70 +138,56 @@ More examples are being worked on at this time (2021-07-14), but for now you can ## Saving figures - -Figures can be saved in a variety of formats using the `savefig` function. +Figures can be saved in a variety of formats using the [`savefig`](@ref) function. !!! note - Note that the docs below are shown for the `PlotlyBase.Plot` type, but are also defined for `PlotlyJS.SyncPlot`. Thus, you can use these methods after calling either `plot` or `Plot`. - -This function has a few methods: + Note that the docs below are shown for the `PlotlyBase.Plot` type, but are also defined for `PlotlyJS.SyncPlot`. + Thus, you can use these methods after calling either `plot` or `Plot`. -**1** +The `savefig` function can be called in a few ways: -```@docs -savefig(::Union{PlotlyBase.Plot}, ::String) -``` - -When using this method the format of the file is inferred based on the extension -of the second argument. The examples below show the possible export formats: - -```julia -savefig(p::Union{Plot,SyncPlot}, "output_filename.pdf") -savefig(p::Union{Plot,SyncPlot}, "output_filename.html") -savefig(p::Union{Plot,SyncPlot}, "output_filename.json") -savefig(p::Union{Plot,SyncPlot}, "output_filename.png") -savefig(p::Union{Plot,SyncPlot}, "output_filename.svg") -savefig(p::Union{Plot,SyncPlot}, "output_filename.jpeg") -savefig(p::Union{Plot,SyncPlot}, "output_filename.webp") -``` +**Save into a file** -**2** +`savefig(p, filename)` saves the plot `p` into `filename`. +Unless explicitly specified, the format of the file is inferred from the `filename` extension. +The examples demonstrate the supported formats: ```julia -savefig( - io::IO, - p::Plot; - width::Union{Nothing,Int}=nothing, - height::Union{Nothing,Int}=nothing, - scale::Union{Nothing,Real}=nothing, - format::String="png" -) +savefig(p, "output_filename.pdf") +savefig(p, "output_filename.html") +savefig(p, "output_filename.json") +savefig(p, "output_filename.png") +savefig(p, "output_filename.svg") +savefig(p, "output_filename.jpeg") +savefig(p, "output_filename.webp") ``` -This method allows you to save a plot directly to an open IO stream. +**Save into a stream** -See the [`savefig(::IO, ::PlotlyBase.Plot)`](@ref) API docs for more information. +`savefig(io, p)` saves the plot `p` into the open `io` stream. +The figure format could be specified with the `format` keyword, the default format is *PNG*. -**3** +**Display on the screen** +*PlotlyJS.jl* overloads the `Base.show` method to hook into Julia's rich display system: ```julia -Base.show(::IO, ::MIME, ::Union{PlotlyBase.Plot}) +Base.show(io::IO, ::MIME, p::Union{PlotlyBase.Plot}) ``` +Internally, this `Base.show` implementation calls `savefig(io, p)`, +and the `MIME` argument allows to specify the output format. + +The following MIME formats are supported: + * `::MIME"application/pdf` + * `::MIME"image/png` + * `::MIME"image/svg+xml` + * `::MIME"image/eps` + * `::MIME"image/jpeg` + * `::MIME"application/json"` + * `::MIME"application/json; charset=UTF-8"` -This method hooks into Julia's rich display system. - -Possible arguments for the second argument are shown in the examples below: - -```julia -savefig(io::IO, ::MIME"application/pdf", p::Union{Plot,SyncPlot}) -savefig(io::IO, ::MIME"image/png", p::Union{Plot,SyncPlot}) -savefig(io::IO, ::MIME"image/svg+xml", p::Union{Plot,SyncPlot}) -savefig(io::IO, ::MIME"image/eps", p::Union{Plot,SyncPlot}) -savefig(io::IO, ::MIME"image/jpeg", p::Union{Plot,SyncPlot}) -savefig(io::IO, ::MIME"application/json", p::Union{Plot,SyncPlot}) -savefig(io::IO, ::MIME"application/json; charset=UTF-8", p::Union{Plot,SyncPlot}) +```@docs +savefig ``` - !!! note You can also save the json for a figure by calling `savejson(p::Union{Plot,SyncPlot}, filename::String)`. From 4c2f60458ae2f7b399e709b01f82e52dad521e34 Mon Sep 17 00:00:00 2001 From: Alexey Stukalov Date: Thu, 12 Dec 2024 21:30:16 -0800 Subject: [PATCH 5/6] fixup savefig --- src/kaleido.jl | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/kaleido.jl b/src/kaleido.jl index c0ca82d..0186257 100644 --- a/src/kaleido.jl +++ b/src/kaleido.jl @@ -1,7 +1,5 @@ using PlotlyKaleido: kill, is_running, start, restart, ALL_FORMATS, TEXT_FORMATS -savefig(p::SyncPlot; kwargs...) = savefig(p.plot; kwargs...) - """ savefig( [io::IO], p::Plot, [fn:AbstractString]; @@ -77,6 +75,7 @@ function savefig( end end +savefig(p::SyncPlot; kwargs...) = savefig(p.plot; kwargs...) @inline _get_Plot(p::Plot) = p @inline _get_Plot(p::SyncPlot) = p.plot @@ -138,17 +137,13 @@ const _KALEIDO_MIMES = Dict( ) for (mime, fmt) in _KALEIDO_MIMES - @eval function Base.show( + @eval Base.show( io::IO, ::MIME{Symbol($mime)}, plt::Plot; kwargs... - ) - savefig(io, plt; format=$fmt, kwargs...) - end + ) = savefig(io, plt; format=$fmt, kwargs...) - @eval function Base.show( + @eval Base.show( io::IO, ::MIME{Symbol($mime)}, plt::SyncPlot; kwargs... - ) - savefig(io, plt.plot; format=$fmt, kwargs...) - end + ) = savefig(io, plt.plot; format=$fmt, kwargs...) end From 9acc5346ae569d2df1402be9707d246e1421ee37 Mon Sep 17 00:00:00 2001 From: Alexey Stukalov Date: Thu, 12 Dec 2024 21:31:54 -0800 Subject: [PATCH 6/6] bring all show() methods together --- src/display.jl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/display.jl b/src/display.jl index 98f1a0b..e674759 100644 --- a/src/display.jl +++ b/src/display.jl @@ -22,6 +22,11 @@ function Base.show(io::IO, mm::MIME"text/html", p::SyncPlot) end Base.show(io::IO, mm::MIME"application/prs.juno.plotpane+html", p::SyncPlot) = show(io, mm, p.scope) +# using @eval instead of Union{} to avoid ambiguity with other methods +for mime in [MIME"text/plain", MIME"application/vnd.plotly.v1+json", MIME"application/prs.juno.plotpane+html"] + @eval Base.show(io::IO, mm::$mime, p::SyncPlot, args...; kwargs...) = show(io, mm, p.plot, args...; kwargs...) +end + function SyncPlot( p::Plot; kwargs... @@ -343,11 +348,4 @@ for f in (:extendtraces!, :prependtraces!) end end - -for mime in ["text/plain", "application/vnd.plotly.v1+json", "application/prs.juno.plotpane+html"] - function Base.show(io::IO, m::MIME{Symbol(mime)}, p::SyncPlot, args...) - show(io, m, p.plot, args...) - end -end - PlotlyBase.savejson(sp::SyncPlot, fn::String) = PlotlyBase.savejson(sp.plot, fn)