From 182b937143b7c1444cd1d79593b1d435f6ccf358 Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Wed, 31 Oct 2018 16:55:57 +0100 Subject: [PATCH 1/9] Refactor __init__() to make Plots load faster --- src/Plots.jl | 2 + src/fileio.jl | 24 +++++++++++ src/ijulia.jl | 59 ++++++++++++++++++++++++++++ src/init.jl | 107 +++++++++++--------------------------------------- 4 files changed, 107 insertions(+), 85 deletions(-) create mode 100644 src/fileio.jl create mode 100644 src/ijulia.jl diff --git a/src/Plots.jl b/src/Plots.jl index fb93c45f..d387275e 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -180,6 +180,8 @@ include("arg_desc.jl") include("plotattr.jl") include("backends.jl") include("output.jl") +include("ijulia.jl") +include("fileio.jl") include("init.jl") include("backends/plotly.jl") diff --git a/src/fileio.jl b/src/fileio.jl new file mode 100644 index 00000000..dbb0469e --- /dev/null +++ b/src/fileio.jl @@ -0,0 +1,24 @@ +# --------------------------------------------------------- +# A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert + +_fileio_load(@nospecialize(filename::AbstractString)) = FileIO.load(filename::AbstractString) +_fileio_save(@nospecialize(filename::AbstractString), @nospecialize(x)) = FileIO.save(filename::AbstractString, x) + +function _show_pdfbackends(io::IO, ::MIME"image/png", plt::Plot) + fn = tempname() + + # first save a pdf file + pdf(plt, fn) + + # load that pdf into a FileIO Stream + s = _fileio_load(fn * ".pdf") + + # save a png + pngfn = fn * ".png" + _fileio_save(pngfn, s) + + # now write from the file + write(io, read(open(pngfn), String)) +end + +const PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend} diff --git a/src/ijulia.jl b/src/ijulia.jl new file mode 100644 index 00000000..a54c0ca9 --- /dev/null +++ b/src/ijulia.jl @@ -0,0 +1,59 @@ +const use_local_dependencies = Ref(false) +const use_local_plotlyjs = Ref(false) + + +function _init_ijulia_plotting() + # IJulia is more stable with local file + use_local_plotlyjs[] = isfile(plotly_local_file_path) + + ENV["MPLBACKEND"] = "Agg" +end + + +""" +Add extra jupyter mimetypes to display_dict based on the plot backed. + +The default is nothing, except for plotly based backends, where it +adds data for `application/vnd.plotly.v1+json` that is used in +frontends like jupyterlab and nteract. +""" +_ijulia__extra_mime_info!(plt::Plot, out::Dict) = out + +function _ijulia__extra_mime_info!(plt::Plot{PlotlyJSBackend}, out::Dict) + out["application/vnd.plotly.v1+json"] = JSON.lower(plt.o) + out +end + +function _ijulia__extra_mime_info!(plt::Plot{PlotlyBackend}, out::Dict) + out["application/vnd.plotly.v1+json"] = Dict( + :data => plotly_series(plt), + :layout => plotly_layout(plt) + ) + out +end + + +function _ijulia_display_dict(plt::Plot) + output_type = Symbol(plt.attr[:html_output_format]) + if output_type == :auto + output_type = get(_best_html_output_type, backend_name(plt.backend), :svg) + end + out = Dict() + if output_type == :txt + mime = "text/plain" + out[mime] = sprint(show, MIME(mime), plt) + elseif output_type == :png + mime = "image/png" + out[mime] = base64encode(show, MIME(mime), plt) + elseif output_type == :svg + mime = "image/svg+xml" + out[mime] = sprint(show, MIME(mime), plt) + elseif output_type == :html + mime = "text/html" + out[mime] = sprint(show, MIME(mime), plt) + else + error("Unsupported output type $output_type") + end + _ijulia__extra_mime_info!(plt, out) + out +end diff --git a/src/init.jl b/src/init.jl index 257ba4b8..1cd1fcd2 100644 --- a/src/init.jl +++ b/src/init.jl @@ -1,16 +1,22 @@ using REPL -const use_local_dependencies = Ref(false) + +function _plots_defaults() + if isdefined(Main, :PLOTS_DEFAULTS) + Main.PLOTS_DEFAULTS::Dict{Symbol,Any} + else + Dict{Symbol,Any}() + end +end + function __init__() - - if isdefined(Main, :PLOTS_DEFAULTS) - if haskey(Main.PLOTS_DEFAULTS, :theme) - theme(Main.PLOTS_DEFAULTS[:theme]) - end - for (k,v) in Main.PLOTS_DEFAULTS - k == :theme || default(k, v) - end + user_defaults = _plots_defaults() + if haskey(user_defaults, :theme) + theme(user_defaults[:theme]) + end + for (k,v) in user_defaults + k == :theme || default(k, v) end insert!(Base.Multimedia.displays, findlast(x -> x isa Base.TextDisplay || x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay()) @@ -29,95 +35,26 @@ function __init__() @require PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee" include(joinpath(@__DIR__, "backends", "pyplot.jl")) @require UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" include(joinpath(@__DIR__, "backends", "unicodeplots.jl")) - # --------------------------------------------------------- - # IJulia - # --------------------------------------------------------- - use_local = false @require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin if IJulia.inited - # IJulia is more stable with local file - use_local = isfile(plotly_local_file_path) - """ - Add extra jupyter mimetypes to display_dict based on the plot backed. + _init_ijulia_plotting() - The default is nothing, except for plotly based backends, where it - adds data for `application/vnd.plotly.v1+json` that is used in - frontends like jupyterlab and nteract. - """ - _extra_mime_info!(plt::Plot, out::Dict) = out - function _extra_mime_info!(plt::Plot{PlotlyJSBackend}, out::Dict) - out["application/vnd.plotly.v1+json"] = JSON.lower(plt.o) - out - end - - function _extra_mime_info!(plt::Plot{PlotlyBackend}, out::Dict) - out["application/vnd.plotly.v1+json"] = Dict( - :data => plotly_series(plt), - :layout => plotly_layout(plt) - ) - out - end - - function IJulia.display_dict(plt::Plot) - output_type = Symbol(plt.attr[:html_output_format]) - if output_type == :auto - output_type = get(_best_html_output_type, backend_name(plt.backend), :svg) - end - out = Dict() - if output_type == :txt - mime = "text/plain" - out[mime] = sprint(show, MIME(mime), plt) - elseif output_type == :png - mime = "image/png" - out[mime] = base64encode(show, MIME(mime), plt) - elseif output_type == :svg - mime = "image/svg+xml" - out[mime] = sprint(show, MIME(mime), plt) - elseif output_type == :html - mime = "text/html" - out[mime] = sprint(show, MIME(mime), plt) - else - error("Unsupported output type $output_type") - end - _extra_mime_info!(plt, out) - out - end - - ENV["MPLBACKEND"] = "Agg" + IJulia.display_dict(plt::Plot) = _ijulia_display_dict(plt) end end if haskey(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL") - use_local = ENV["PLOTS_HOST_DEPENDENCY_LOCAL"] == "true" - use_local_dependencies[] = isfile(plotly_local_file_path) && use_local - if use_local && !isfile(plotly_local_file_path) + use_local_plotlyjs[] = ENV["PLOTS_HOST_DEPENDENCY_LOCAL"] == "true" + use_local_dependencies[] = isfile(plotly_local_file_path) && use_local_plotlyjs[] + if use_local_plotlyjs[] && !isfile(plotly_local_file_path) @warn("PLOTS_HOST_DEPENDENCY_LOCAL is set to true, but no local plotly file found. run Pkg.build(\"Plots\") and make sure PLOTS_HOST_DEPENDENCY_LOCAL is set to true") end else - use_local_dependencies[] = use_local + use_local_dependencies[] = use_local_plotlyjs[] end - - # --------------------------------------------------------- - # A backup, if no PNG generation is defined, is to try to make a PDF and use FileIO to convert @require FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" begin - PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend} - function _show(io::IO, ::MIME"image/png", plt::Plot{<:PDFBackends}) - fn = tempname() - - # first save a pdf file - pdf(plt, fn) - - # load that pdf into a FileIO Stream - s = FileIO.load(fn * ".pdf") - - # save a png - pngfn = fn * ".png" - FileIO.save(pngfn, s) - - # now write from the file - write(io, read(open(pngfn), String)) - end + _show(io::IO, mime::MIME"image/png", plt::Plot{<:PDFBackends}) = _show_pdfbackends(io, mime, plt) end end From 7867df6079908b7d425ff1566b35b194f86241ce Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Thu, 1 Nov 2018 18:15:50 +0100 Subject: [PATCH 2/9] _initialize_backend doesn't need to do anything for GRBackend --- src/backends.jl | 3 +++ src/backends/gr.jl | 1 + 2 files changed, 4 insertions(+) diff --git a/src/backends.jl b/src/backends.jl index eae321f8..4183cf3b 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -316,6 +316,9 @@ function _initialize_backend(pkg::AbstractBackend) end end +_initialize_backend(pkg::GRBackend) = nothing + + function add_backend_string(pkg::AbstractBackend) sym = backend_package_name(pkg) """ diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 1d4fbca6..7b51fff6 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -6,6 +6,7 @@ import GR export GR + # -------------------------------------------------------------------------------------- const gr_linetype = KW( From 141845aa407bcb93aee4a9f8230fbb73a80c7c8f Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Fri, 2 Nov 2018 00:13:49 +0100 Subject: [PATCH 3/9] _initialize_backend doesn't need to do anything for PlotlyBackend --- src/backends.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backends.jl b/src/backends.jl index 4183cf3b..52231ae1 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -318,6 +318,8 @@ end _initialize_backend(pkg::GRBackend) = nothing +_initialize_backend(pkg::PlotlyBackend) = nothing + function add_backend_string(pkg::AbstractBackend) sym = backend_package_name(pkg) From 9aa1fd5c003cc6453743e60ef49456cc2f1b265b Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Thu, 1 Nov 2018 18:18:04 +0100 Subject: [PATCH 4/9] macro init_backend can call backend() with backend instance --- src/backends.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends.jl b/src/backends.jl index 52231ae1..0ba8d5fc 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -26,7 +26,7 @@ macro init_backend(s) esc(quote struct $T <: AbstractBackend end export $sym - $sym(; kw...) = (default(; kw...); backend(Symbol($str))) + $sym(; kw...) = (default(; kw...); backend($T())) backend_name(::$T) = Symbol($str) backend_package_name(pkg::$T) = backend_package_name(Symbol($str)) push!(_backends, Symbol($str)) From f1719d6e9102e2cd77b9b2b3ffe96d7e5fc78afa Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Thu, 1 Nov 2018 18:26:31 +0100 Subject: [PATCH 5/9] Improve implementation of backend(pkg::AbstractBackend) --- src/backends.jl | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/backends.jl b/src/backends.jl index 0ba8d5fc..e008283b 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -188,20 +188,13 @@ Set the plot backend. """ function backend(pkg::AbstractBackend) sym = backend_name(pkg) - if sym in _initialized_backends - CURRENT_BACKEND.sym = backend_name(pkg) - CURRENT_BACKEND.pkg = pkg - else - # try - _initialize_backend(pkg) - push!(_initialized_backends, sym) - CURRENT_BACKEND.sym = backend_name(pkg) - CURRENT_BACKEND.pkg = pkg - # catch - # add_backend(sym) - # end + if !(sym in _initialized_backends) + _initialize_backend(pkg) + push!(_initialized_backends, sym) end - backend() + CURRENT_BACKEND.sym = sym + CURRENT_BACKEND.pkg = pkg + pkg end function backend(sym::Symbol) From 420228c67b24c8e131eb34ee179b1ab2ba0f5de3 Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Thu, 1 Nov 2018 21:49:34 +0100 Subject: [PATCH 6/9] Remove function add_backend No longer in use. --- src/Plots.jl | 1 - src/backends.jl | 56 ------------------------------------------------- 2 files changed, 57 deletions(-) diff --git a/src/Plots.jl b/src/Plots.jl index d387275e..10c599ff 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -84,7 +84,6 @@ export backends, backend_name, backend_object, - add_backend, aliases, Shape, diff --git a/src/backends.jl b/src/backends.jl index e008283b..f0445ff6 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -42,12 +42,6 @@ end # --------------------------------------------------------- -function add_backend(pkg::Symbol) - @info("To do a standard install of $pkg, copy and run this:\n\n") - println(add_backend_string(_backend_instance(pkg))) - println() -end - # don't do anything as a default _create_backend_figure(plt::Plot) = nothing _prepare_plot_object(plt::Plot) = nothing @@ -314,14 +308,6 @@ _initialize_backend(pkg::GRBackend) = nothing _initialize_backend(pkg::PlotlyBackend) = nothing -function add_backend_string(pkg::AbstractBackend) - sym = backend_package_name(pkg) - """ - using Pkg - Pkg.add("$sym") - """ -end - # ------------------------------------------------------------------------------ # gr @@ -377,13 +363,6 @@ const _gr_marker = _allMarkers const _gr_scale = [:identity, :log10] is_marker_supported(::GRBackend, shape::Shape) = true -function add_backend_string(::GRBackend) - """ - Pkg.add("GR") - Pkg.build("GR") - """ -end - # ------------------------------------------------------------------------------ # plotly @@ -445,14 +424,6 @@ const _plotly_scale = [:identity, :log10] # ------------------------------------------------------------------------------ # pgfplots -function add_backend_string(::PGFPlotsBackend) - """ - using Pkg - Pkg.add("PGFPlots") - Pkg.build("PGFPlots") - """ -end - const _pgfplots_attr = merge_with_base_supported([ :annotations, :background_color_legend, @@ -504,15 +475,6 @@ function _initialize_backend(pkg::PlotlyJSBackend) end end -function add_backend_string(::PlotlyJSBackend) - """ - using Pkg - Pkg.add(["PlotlyJS", "Blink", "ORCA"]) - import Blink - Blink.AtomShell.install() - """ -end - const _plotlyjs_attr = _plotly_attr const _plotlyjs_seriestype = _plotly_seriestype const _plotlyjs_style = _plotly_style @@ -533,16 +495,6 @@ function _initialize_backend(::PyPlotBackend) end end -function add_backend_string(::PyPlotBackend) - """ - using Pkg - withenv("PYTHON" => "") do - Pkg.add("PyPlot") - Pkg.build("PyPlot") - end - """ -end - const _pyplot_attr = merge_with_base_supported([ :annotations, :background_color_legend, :background_color_inside, :background_color_outside, @@ -596,14 +548,6 @@ const _pyplot_scale = [:identity, :ln, :log2, :log10] # ------------------------------------------------------------------------------ # unicodeplots -function add_backend_string(::UnicodePlotsBackend) - """ - using Pkg - Pkg.add("UnicodePlots") - Pkg.build("UnicodePlots") - """ -end - const _unicodeplots_attr = merge_with_base_supported([ :label, :legend, From 43245b5eb5f4a242c50d90cfd41d1188bc575043 Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Fri, 2 Nov 2018 00:14:52 +0100 Subject: [PATCH 7/9] Clean up backend-related global vars and related functions --- src/backends.jl | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/backends.jl b/src/backends.jl index f0445ff6..4106914d 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -7,16 +7,16 @@ const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none) const _backends = Symbol[] const _initialized_backends = Set{Symbol}() const _default_backends = (:none, :gr, :plotly) -const _backendPackage = Dict{Symbol, Symbol}() + +const _backend_packages = Dict{Symbol, Symbol}() "Returns a list of supported backends" backends() = _backends "Returns the name of the current backend" backend_name() = CURRENT_BACKEND.sym -_backend_instance(sym::Symbol) = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym") -backend_package(pkg::Symbol) = pkg in _default_backends ? :Plots : Symbol("Plots", _backendPackage[pkg]) -backend_package_name(sym::Symbol) = sym in _default_backends ? :Plots : _backendPackage[sym] +_backend_instance(sym::Symbol)::AbstractBackend = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym") +backend_package_name(sym::Symbol) = _backend_packages[sym] macro init_backend(s) package_str = string(s) @@ -32,7 +32,7 @@ macro init_backend(s) push!(_backends, Symbol($str)) _backendType[Symbol($str)] = $T _backendSymbol[$T] = Symbol($str) - _backendPackage[Symbol($str)] = Symbol($package_str) + _backend_packages[Symbol($str)] = Symbol($package_str) # include("backends/" * $str * ".jl") end) end @@ -148,15 +148,6 @@ function pickDefaultBackend() end end - # the ordering/inclusion of this package list is my semi-arbitrary guess at - # which one someone will want to use if they have the package installed...accounting for - # features, speed, and robustness - # for pkgstr in ("GR", "PyPlot", "PlotlyJS", "PGFPlots", "UnicodePlots", "InspectDR") - # if pkgstr in keys(Pkg.installed()) - # return backend(Symbol(lowercase(pkgstr))) - # end - # end - # the default if nothing else is installed backend(:gr) end @@ -196,8 +187,8 @@ function backend(sym::Symbol) backend(_backend_instance(sym)) else @warn("`:$sym` is not a supported backend.") + backend() end - backend() end const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize] @@ -468,7 +459,6 @@ const _pgfplots_scale = [:identity, :ln, :log2, :log10] # plotlyjs function _initialize_backend(pkg::PlotlyJSBackend) - sym = backend_package_name(pkg) @eval Main begin import PlotlyJS, ORCA export PlotlyJS From ffcdf986a3261be6f140fbcfb3dd451cd6b9cb92 Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Thu, 1 Nov 2018 23:07:58 +0100 Subject: [PATCH 8/9] Set explicit return type for function _backend_instance Inferred return type is Any, otherwise, not AbstractBackend. --- src/backends.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/backends.jl b/src/backends.jl index 4106914d..13eada09 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -15,7 +15,11 @@ backends() = _backends "Returns the name of the current backend" backend_name() = CURRENT_BACKEND.sym -_backend_instance(sym::Symbol)::AbstractBackend = haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym") + +function _backend_instance(sym::Symbol)::AbstractBackend + haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym") +end + backend_package_name(sym::Symbol) = _backend_packages[sym] macro init_backend(s) From b9b6439f472aba832008aad019bbba0debab759d Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Thu, 1 Nov 2018 23:08:52 +0100 Subject: [PATCH 9/9] Clean up backend default handling --- src/backends.jl | 23 ++++++++++++----------- src/utils.jl | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/backends.jl b/src/backends.jl index 13eada09..11d0a414 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -136,24 +136,27 @@ CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym)) # --------------------------------------------------------- -function pickDefaultBackend() +_fallback_default_backend() = backend(GRBackend()) + +function _pick_default_backend() env_default = get(ENV, "PLOTS_DEFAULT_BACKEND", "") if env_default != "" sym = Symbol(lowercase(env_default)) if sym in _backends if sym in _initialized_backends - return backend(sym) + backend(sym) else @warn("You have set `PLOTS_DEFAULT_BACKEND=$env_default` but `$(backend_package_name(sym))` is not loaded.") + _fallback_default_backend() end else @warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not a valid backend package. Choose from:\n\t" * join(sort(_backends), "\n\t")) + _fallback_default_backend() end + else + _fallback_default_backend() end - - # the default if nothing else is installed - backend(:gr) end @@ -163,13 +166,11 @@ end Returns the current plotting package name. Initializes package on first call. """ function backend() + if CURRENT_BACKEND.sym == :none + _pick_default_backend() + end - global CURRENT_BACKEND - if CURRENT_BACKEND.sym == :none - pickDefaultBackend() - end - - CURRENT_BACKEND.pkg + CURRENT_BACKEND.pkg end """ diff --git a/src/utils.jl b/src/utils.jl index 7f582e81..bdb3f330 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -726,7 +726,7 @@ function with(f::Function, args...; kw...) # save the backend if CURRENT_BACKEND.sym == :none - pickDefaultBackend() + _pick_default_backend() end oldbackend = CURRENT_BACKEND.sym