diff --git a/src/Plots.jl b/src/Plots.jl index fb93c45f..10c599ff 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -84,7 +84,6 @@ export backends, backend_name, backend_object, - add_backend, aliases, Shape, @@ -180,6 +179,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/backends.jl b/src/backends.jl index eae321f8..11d0a414 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -7,16 +7,20 @@ 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] + +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) package_str = string(s) @@ -26,13 +30,13 @@ 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)) _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 @@ -42,12 +46,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 @@ -138,33 +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 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 @@ -174,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 """ @@ -188,20 +178,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) @@ -209,8 +192,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] @@ -316,13 +299,10 @@ function _initialize_backend(pkg::AbstractBackend) end end -function add_backend_string(pkg::AbstractBackend) - sym = backend_package_name(pkg) - """ - using Pkg - Pkg.add("$sym") - """ -end +_initialize_backend(pkg::GRBackend) = nothing + +_initialize_backend(pkg::PlotlyBackend) = nothing + # ------------------------------------------------------------------------------ # gr @@ -379,13 +359,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 @@ -447,14 +420,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, @@ -499,22 +464,12 @@ 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 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 @@ -535,16 +490,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, @@ -598,14 +543,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, 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( 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 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