Merge pull request #1829 from oschulz/faster-load

Reduce load time for Plots and GR backend
This commit is contained in:
Michael Krabbe Borregaard 2019-01-28 14:24:23 +01:00 committed by GitHub
commit f905cb9577
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 142 additions and 183 deletions

View File

@ -84,7 +84,6 @@ export
backends, backends,
backend_name, backend_name,
backend_object, backend_object,
add_backend,
aliases, aliases,
Shape, Shape,
@ -180,6 +179,8 @@ include("arg_desc.jl")
include("plotattr.jl") include("plotattr.jl")
include("backends.jl") include("backends.jl")
include("output.jl") include("output.jl")
include("ijulia.jl")
include("fileio.jl")
include("init.jl") include("init.jl")
include("backends/plotly.jl") include("backends/plotly.jl")

View File

@ -7,16 +7,20 @@ const _backendSymbol = Dict{DataType, Symbol}(NoBackend => :none)
const _backends = Symbol[] const _backends = Symbol[]
const _initialized_backends = Set{Symbol}() const _initialized_backends = Set{Symbol}()
const _default_backends = (:none, :gr, :plotly) const _default_backends = (:none, :gr, :plotly)
const _backendPackage = Dict{Symbol, Symbol}()
const _backend_packages = Dict{Symbol, Symbol}()
"Returns a list of supported backends" "Returns a list of supported backends"
backends() = _backends backends() = _backends
"Returns the name of the current backend" "Returns the name of the current backend"
backend_name() = CURRENT_BACKEND.sym 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]) function _backend_instance(sym::Symbol)::AbstractBackend
backend_package_name(sym::Symbol) = sym in _default_backends ? :Plots : _backendPackage[sym] haskey(_backendType, sym) ? _backendType[sym]() : error("Unsupported backend $sym")
end
backend_package_name(sym::Symbol) = _backend_packages[sym]
macro init_backend(s) macro init_backend(s)
package_str = string(s) package_str = string(s)
@ -26,13 +30,13 @@ macro init_backend(s)
esc(quote esc(quote
struct $T <: AbstractBackend end struct $T <: AbstractBackend end
export $sym export $sym
$sym(; kw...) = (default(; kw...); backend(Symbol($str))) $sym(; kw...) = (default(; kw...); backend($T()))
backend_name(::$T) = Symbol($str) backend_name(::$T) = Symbol($str)
backend_package_name(pkg::$T) = backend_package_name(Symbol($str)) backend_package_name(pkg::$T) = backend_package_name(Symbol($str))
push!(_backends, Symbol($str)) push!(_backends, Symbol($str))
_backendType[Symbol($str)] = $T _backendType[Symbol($str)] = $T
_backendSymbol[$T] = Symbol($str) _backendSymbol[$T] = Symbol($str)
_backendPackage[Symbol($str)] = Symbol($package_str) _backend_packages[Symbol($str)] = Symbol($package_str)
# include("backends/" * $str * ".jl") # include("backends/" * $str * ".jl")
end) end)
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 # don't do anything as a default
_create_backend_figure(plt::Plot) = nothing _create_backend_figure(plt::Plot) = nothing
_prepare_plot_object(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", "") env_default = get(ENV, "PLOTS_DEFAULT_BACKEND", "")
if env_default != "" if env_default != ""
sym = Symbol(lowercase(env_default)) sym = Symbol(lowercase(env_default))
if sym in _backends if sym in _backends
if sym in _initialized_backends if sym in _initialized_backends
return backend(sym) backend(sym)
else else
@warn("You have set `PLOTS_DEFAULT_BACKEND=$env_default` but `$(backend_package_name(sym))` is not loaded.") @warn("You have set `PLOTS_DEFAULT_BACKEND=$env_default` but `$(backend_package_name(sym))` is not loaded.")
_fallback_default_backend()
end end
else else
@warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not a valid backend package. Choose from:\n\t" * @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")) join(sort(_backends), "\n\t"))
_fallback_default_backend()
end end
else
_fallback_default_backend()
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 end
@ -174,10 +166,8 @@ end
Returns the current plotting package name. Initializes package on first call. Returns the current plotting package name. Initializes package on first call.
""" """
function backend() function backend()
global CURRENT_BACKEND
if CURRENT_BACKEND.sym == :none if CURRENT_BACKEND.sym == :none
pickDefaultBackend() _pick_default_backend()
end end
CURRENT_BACKEND.pkg CURRENT_BACKEND.pkg
@ -188,20 +178,13 @@ Set the plot backend.
""" """
function backend(pkg::AbstractBackend) function backend(pkg::AbstractBackend)
sym = backend_name(pkg) sym = backend_name(pkg)
if sym in _initialized_backends if !(sym in _initialized_backends)
CURRENT_BACKEND.sym = backend_name(pkg)
CURRENT_BACKEND.pkg = pkg
else
# try
_initialize_backend(pkg) _initialize_backend(pkg)
push!(_initialized_backends, sym) push!(_initialized_backends, sym)
CURRENT_BACKEND.sym = backend_name(pkg)
CURRENT_BACKEND.pkg = pkg
# catch
# add_backend(sym)
# end
end end
backend() CURRENT_BACKEND.sym = sym
CURRENT_BACKEND.pkg = pkg
pkg
end end
function backend(sym::Symbol) function backend(sym::Symbol)
@ -209,9 +192,9 @@ function backend(sym::Symbol)
backend(_backend_instance(sym)) backend(_backend_instance(sym))
else else
@warn("`:$sym` is not a supported backend.") @warn("`:$sym` is not a supported backend.")
end
backend() backend()
end end
end
const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize] const _deprecated_backends = [:qwt, :winston, :bokeh, :gadfly, :immerse, :glvisualize]
@ -316,13 +299,10 @@ function _initialize_backend(pkg::AbstractBackend)
end end
end end
function add_backend_string(pkg::AbstractBackend) _initialize_backend(pkg::GRBackend) = nothing
sym = backend_package_name(pkg)
""" _initialize_backend(pkg::PlotlyBackend) = nothing
using Pkg
Pkg.add("$sym")
"""
end
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# gr # gr
@ -379,13 +359,6 @@ const _gr_marker = _allMarkers
const _gr_scale = [:identity, :log10] const _gr_scale = [:identity, :log10]
is_marker_supported(::GRBackend, shape::Shape) = true is_marker_supported(::GRBackend, shape::Shape) = true
function add_backend_string(::GRBackend)
"""
Pkg.add("GR")
Pkg.build("GR")
"""
end
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# plotly # plotly
@ -447,14 +420,6 @@ const _plotly_scale = [:identity, :log10]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# pgfplots # pgfplots
function add_backend_string(::PGFPlotsBackend)
"""
using Pkg
Pkg.add("PGFPlots")
Pkg.build("PGFPlots")
"""
end
const _pgfplots_attr = merge_with_base_supported([ const _pgfplots_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_legend,
@ -499,22 +464,12 @@ const _pgfplots_scale = [:identity, :ln, :log2, :log10]
# plotlyjs # plotlyjs
function _initialize_backend(pkg::PlotlyJSBackend) function _initialize_backend(pkg::PlotlyJSBackend)
sym = backend_package_name(pkg)
@eval Main begin @eval Main begin
import PlotlyJS, ORCA import PlotlyJS, ORCA
export PlotlyJS export PlotlyJS
end end
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_attr = _plotly_attr
const _plotlyjs_seriestype = _plotly_seriestype const _plotlyjs_seriestype = _plotly_seriestype
const _plotlyjs_style = _plotly_style const _plotlyjs_style = _plotly_style
@ -535,16 +490,6 @@ function _initialize_backend(::PyPlotBackend)
end end
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([ const _pyplot_attr = merge_with_base_supported([
:annotations, :annotations,
:background_color_legend, :background_color_inside, :background_color_outside, :background_color_legend, :background_color_inside, :background_color_outside,
@ -598,14 +543,6 @@ const _pyplot_scale = [:identity, :ln, :log2, :log10]
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# unicodeplots # unicodeplots
function add_backend_string(::UnicodePlotsBackend)
"""
using Pkg
Pkg.add("UnicodePlots")
Pkg.build("UnicodePlots")
"""
end
const _unicodeplots_attr = merge_with_base_supported([ const _unicodeplots_attr = merge_with_base_supported([
:label, :label,
:legend, :legend,

View File

@ -6,6 +6,7 @@
import GR import GR
export GR export GR
# -------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------
const gr_linetype = KW( const gr_linetype = KW(

24
src/fileio.jl Normal file
View File

@ -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}

59
src/ijulia.jl Normal file
View File

@ -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

View File

@ -1,17 +1,23 @@
using REPL 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__() function __init__()
user_defaults = _plots_defaults()
if isdefined(Main, :PLOTS_DEFAULTS) if haskey(user_defaults, :theme)
if haskey(Main.PLOTS_DEFAULTS, :theme) theme(user_defaults[:theme])
theme(Main.PLOTS_DEFAULTS[:theme])
end end
for (k,v) in Main.PLOTS_DEFAULTS for (k,v) in user_defaults
k == :theme || default(k, v) k == :theme || default(k, v)
end end
end
insert!(Base.Multimedia.displays, findlast(x -> x isa Base.TextDisplay || x isa REPL.REPLDisplay, Base.Multimedia.displays) + 1, PlotsDisplay()) 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 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")) @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 @require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin
if IJulia.inited if IJulia.inited
# IJulia is more stable with local file _init_ijulia_plotting()
use_local = isfile(plotly_local_file_path)
"""
Add extra jupyter mimetypes to display_dict based on the plot backed.
The default is nothing, except for plotly based backends, where it IJulia.display_dict(plt::Plot) = _ijulia_display_dict(plt)
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"
end end
end end
if haskey(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL") if haskey(ENV, "PLOTS_HOST_DEPENDENCY_LOCAL")
use_local = ENV["PLOTS_HOST_DEPENDENCY_LOCAL"] == "true" use_local_plotlyjs[] = ENV["PLOTS_HOST_DEPENDENCY_LOCAL"] == "true"
use_local_dependencies[] = isfile(plotly_local_file_path) && use_local use_local_dependencies[] = isfile(plotly_local_file_path) && use_local_plotlyjs[]
if use_local && !isfile(plotly_local_file_path) 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") @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 end
else else
use_local_dependencies[] = use_local use_local_dependencies[] = use_local_plotlyjs[]
end 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 @require FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" begin
PDFBackends = Union{PGFPlotsBackend,PlotlyJSBackend,PyPlotBackend,InspectDRBackend,GRBackend} _show(io::IO, mime::MIME"image/png", plt::Plot{<:PDFBackends}) = _show_pdfbackends(io, mime, plt)
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
end end
end end

View File

@ -726,7 +726,7 @@ function with(f::Function, args...; kw...)
# save the backend # save the backend
if CURRENT_BACKEND.sym == :none if CURRENT_BACKEND.sym == :none
pickDefaultBackend() _pick_default_backend()
end end
oldbackend = CURRENT_BACKEND.sym oldbackend = CURRENT_BACKEND.sym