use Requires.jl for backend dependencies

This commit is contained in:
Daniel Schwabeneder 2018-07-19 22:33:36 +02:00
parent 4860bf3f19
commit f60aac93ab
15 changed files with 251 additions and 358 deletions

View File

@ -171,6 +171,7 @@ include("arg_desc.jl")
include("plotattr.jl")
include("backends.jl")
include("output.jl")
include("init.jl")
# ---------------------------------------------------------

View File

@ -6,6 +6,8 @@ const _backendType = Dict{Symbol, DataType}(:none => NoBackend)
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}()
"Returns a list of supported backends"
backends() = _backends
@ -13,9 +15,12 @@ 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]
macro init_backend(s)
str = lowercase(string(s))
package_str = string(s)
str = lowercase(package_str)
sym = Symbol(str)
T = Symbol(string(s) * "Backend")
esc(quote
@ -23,14 +28,16 @@ macro init_backend(s)
export $sym
$sym(; kw...) = (default(; kw...); backend(Symbol($str)))
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)
include("backends/" * $str * ".jl")
_backendPackage[Symbol($str)] = Symbol($package_str)
# include("backends/" * $str * ".jl")
end)
end
include("backends/web.jl")
# include("backends/web.jl")
# include("backends/supported.jl")
# ---------------------------------------------------------
@ -40,7 +47,6 @@ function add_backend(pkg::Symbol)
println(add_backend_string(_backend_instance(pkg)))
println()
end
add_backend_string(b::AbstractBackend) = warn("No custom install defined for $(backend_name(b))")
# don't do anything as a default
_create_backend_figure(plt::Plot) = nothing
@ -135,30 +141,30 @@ CurrentBackend(sym::Symbol) = CurrentBackend(sym, _backend_instance(sym))
function pickDefaultBackend()
env_default = get(ENV, "PLOTS_DEFAULT_BACKEND", "")
if env_default != ""
if env_default in keys(Pkg.installed())
sym = Symbol(lowercase(env_default))
if haskey(_backendType, sym)
if sym in _backends
if sym in _initialized_backends
return backend(sym)
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"))
@warn("You have set `PLOTS_DEFAULT_BACKEND=$env_default` but `$(backend_package_name(sym))` is not loaded.")
end
else
warn("You have set PLOTS_DEFAULT_BACKEND=$env_default but it is not installed.")
@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"))
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", "GLVisualize")
if pkgstr in keys(Pkg.installed())
return backend(Symbol(lowercase(pkgstr)))
end
end
# for pkgstr in ("GR", "PyPlot", "PlotlyJS", "PGFPlots", "UnicodePlots", "InspectDR", "GLVisualize")
# if pkgstr in keys(Pkg.installed())
# return backend(Symbol(lowercase(pkgstr)))
# end
# end
# the default if nothing else is installed
backend(:plotly)
backend(:gr)
end
@ -174,24 +180,6 @@ function backend()
pickDefaultBackend()
end
sym = CURRENT_BACKEND.sym
if !(sym in _initialized_backends)
# # initialize
# println("[Plots.jl] Initializing backend: ", sym)
inst = _backend_instance(sym)
try
_initialize_backend(inst)
catch err
warn("Couldn't initialize $sym. (might need to install it?)")
add_backend(sym)
rethrow(err)
end
push!(_initialized_backends, sym)
end
CURRENT_BACKEND.pkg
end
@ -199,16 +187,29 @@ end
Set the plot backend.
"""
function backend(pkg::AbstractBackend)
sym = backend_name(pkg)
if sym in _initialized_backends
CURRENT_BACKEND.sym = backend_name(pkg)
warn_on_deprecated_backend(CURRENT_BACKEND.sym)
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
end
backend()
end
function backend(modname::Symbol)
warn_on_deprecated_backend(modname)
CURRENT_BACKEND.sym = modname
CURRENT_BACKEND.pkg = _backend_instance(modname)
function backend(sym::Symbol)
if sym in _backends
backend(_backend_instance(sym))
else
@warn("`:$sym` is not a supported backend.")
end
backend()
end
@ -308,3 +309,109 @@ end
# is_subplot_supported(::AbstractBackend) = false
# is_subplot_supported() = is_subplot_supported(backend())
################################################################################
# initialize the backends
function _initialize_backend(pkg::AbstractBackend)
sym = backend_package_name(pkg)
@eval Main begin
import $sym
export $sym
end
end
function add_backend_string(pkg::AbstractBackend)
sym = backend_package_name(pkg)
"""
using Pkg
Pkg.add("$sym")
"""
end
# ------------------------------------------------------------------------------
# glvisualize
function _initialize_backend(::GLVisualizeBackend; kw...)
@eval Main begin
import GLVisualize, GeometryTypes, Reactive, GLAbstraction, GLWindow, Contour
import GeometryTypes: Point2f0, Point3f0, Vec2f0, Vec3f0, GLNormalMesh, SimpleRectangle, Point, Vec
import FileIO, Images
export GLVisualize
import Reactive: Signal
import GLAbstraction: Style
import GLVisualize: visualize
import Plots.GL
import UnicodeFun
end
end
# ------------------------------------------------------------------------------
# hdf5
function _initialize_backend(::HDF5Backend)
@eval Main begin
import HDF5
export HDF5
end
end
# ------------------------------------------------------------------------------
# PGFPLOTS
function add_backend_string(::PGFPlotsBackend)
"""
using Pkg
Pkg.add("PGFPlots")
Pkg.build("PGFPlots")
"""
end
# ------------------------------------------------------------------------------
# plotlyjs
function add_backend_string(::PlotlyJSBackend)
"""
using Pkg
Pkg.add("PlotlyJS")
Pkg.add("Rsvg")
import Blink
Blink.AtomShell.install()
"""
end
# ------------------------------------------------------------------------------
# pyplot
function _initialize_backend(::PyPlotBackend)
@eval Main begin
import PyPlot, PyCall
import LaTeXStrings: latexstring
export PyPlot
# we don't want every command to update the figure
PyPlot.ioff()
end
end
function add_backend_string(::PyPlotBackend)
"""
using Pkg
Pkg.add("PyPlot")
withenv("PYTHON" => "") do
Pkg.build("PyPlot")
end
"""
end
# ------------------------------------------------------------------------------
# unicodeplots
function add_backend_string(::UnicodePlotsBackend)
"""
using Pkg
Pkg.add("UnicodePlots")
Pkg.build("UnicodePlots")
"""
end

View File

@ -61,36 +61,11 @@ const _glvisualize_style = [:auto, :solid, :dash, :dot, :dashdot]
const _glvisualize_marker = _allMarkers
const _glvisualize_scale = [:identity, :ln, :log2, :log10]
# --------------------------------------------------------------------------------------
function _initialize_backend(::GLVisualizeBackend; kw...)
@eval begin
import GLVisualize, GeometryTypes, Reactive, GLAbstraction, GLWindow, Contour
import GeometryTypes: Point2f0, Point3f0, Vec2f0, Vec3f0, GLNormalMesh, SimpleRectangle, Point, Vec
import FileIO, Images
export GLVisualize
import Reactive: Signal
import GLAbstraction: Style
import GLVisualize: visualize
import Plots.GL
import UnicodeFun
Plots.slice_arg(img::Matrix{C}, idx::Int) where {C<:Colorant} = img
slice_arg(img::Matrix{C}, idx::Int) where {C<:Colorant} = img
is_marker_supported(::GLVisualizeBackend, shape::GLVisualize.AllPrimitives) = true
is_marker_supported(::GLVisualizeBackend, shape::Union{Vector{Matrix{C}}, Matrix{C}}) where {C<:Colorant} = true
is_marker_supported(::GLVisualizeBackend, shape::Shape) = true
GL = Plots
end
end
function add_backend_string(b::GLVisualizeBackend)
"""
if !Plots.is_installed("GLVisualize")
Pkg.add("GLVisualize")
end
"""
end
# ---------------------------------------------------------------------------

View File

@ -65,12 +65,8 @@ function add_backend_string(::GRBackend)
"""
end
function _initialize_backend(::GRBackend; kw...)
@eval begin
import GR
export GR
end
end
# --------------------------------------------------------------------------------------

View File

@ -108,42 +108,6 @@ const _hdf5_marker = vcat(_allMarkers, :pixel)
const _hdf5_scale = [:identity, :ln, :log2, :log10]
is_marker_supported(::HDF5Backend, shape::Shape) = true
function add_backend_string(::HDF5Backend)
"""
if !Plots.is_installed("HDF5")
Pkg.add("HDF5")
end
"""
end
#==Helper functions
===============================================================================#
_hdf5_plotelempath(subpath::String) = "$_hdf5_plotroot/$subpath"
_hdf5_datapath(subpath::String) = "$_hdf5_dataroot/$subpath"
_hdf5_map_str2telem(k::String) = HDF5PLOT_MAP_STR2TELEM[k]
_hdf5_map_str2telem(v::Vector) = HDF5PLOT_MAP_STR2TELEM[v[1]]
function _hdf5_merge!(dest::Dict, src::Dict)
for (k, v) in src
if isa(v, Axis)
_hdf5_merge!(dest[k].d, v.d)
else
dest[k] = v
end
end
return
end
#==
===============================================================================#
function _initialize_backend(::HDF5Backend)
@eval begin
import HDF5
export HDF5
if length(HDF5PLOT_MAP_TELEM2STR) < 1
#Possible element types of high-level data types:
telem2str = Dict{String, Type}(
@ -174,10 +138,31 @@ function _initialize_backend(::HDF5Backend)
merge!(HDF5PLOT_MAP_STR2TELEM, telem2str)
merge!(HDF5PLOT_MAP_TELEM2STR, Dict{Type, String}(v=>k for (k,v) in HDF5PLOT_MAP_STR2TELEM))
end
#==Helper functions
===============================================================================#
_hdf5_plotelempath(subpath::String) = "$_hdf5_plotroot/$subpath"
_hdf5_datapath(subpath::String) = "$_hdf5_dataroot/$subpath"
_hdf5_map_str2telem(k::String) = HDF5PLOT_MAP_STR2TELEM[k]
_hdf5_map_str2telem(v::Vector) = HDF5PLOT_MAP_STR2TELEM[v[1]]
function _hdf5_merge!(dest::Dict, src::Dict)
for (k, v) in src
if isa(v, Axis)
_hdf5_merge!(dest[k].d, v.d)
else
dest[k] = v
end
end
return
end
#==
===============================================================================#
# ---------------------------------------------------------------------------
# Create the window/figure for this backend.
function _create_backend_figure(plt::Plot{HDF5Backend})

View File

@ -152,19 +152,6 @@ end
# ---------------------------------------------------------------------------
function add_backend_string(::InspectDRBackend)
"""
if !Plots.is_installed("InspectDR")
Pkg.add("InspectDR")
end
"""
end
function _initialize_backend(::InspectDRBackend; kw...)
@eval begin
import InspectDR
export InspectDR
#Glyph used when plotting "Shape"s:
INSPECTDR_GLYPH_SHAPE = InspectDR.GlyphPolyline(
2*InspectDR.GLYPH_SQUARE.x, InspectDR.GLYPH_SQUARE.y
@ -181,8 +168,7 @@ function _initialize_backend(::InspectDRBackend; kw...)
_inspectdr_getgui(::Any) = nothing
_inspectdr_getgui(gplot::InspectDR.GtkPlot) = (gplot.destroyed ? nothing : gplot)
_inspectdr_getgui(r::InspecDRPlotRef) = _inspectdr_getgui(r.gui)
end
end
push!(_initialized_backends, :inspectdr)
# ---------------------------------------------------------------------------

View File

@ -47,23 +47,6 @@ const _pgfplots_marker = [:none, :auto, :circle, :rect, :diamond, :utriangle, :d
const _pgfplots_scale = [:identity, :ln, :log2, :log10]
# --------------------------------------------------------------------------------------
function add_backend_string(::PGFPlotsBackend)
"""
Pkg.add("PGFPlots")
Pkg.build("PGFPlots")
"""
end
function _initialize_backend(::PGFPlotsBackend; kw...)
@eval begin
import PGFPlots
export PGFPlots
end
end
# --------------------------------------------------------------------------------------
const _pgfplots_linestyles = KW(

View File

@ -76,18 +76,10 @@ end
# --------------------------------------------------------------------------------------
function add_backend_string(::PlotlyBackend)
"""
Pkg.build("Plots")
"""
end
const _plotly_js_path = joinpath(dirname(@__FILE__), "..", "..", "deps", "plotly-latest.min.js")
const _plotly_js_path_remote = "https://cdn.plot.ly/plotly-latest.min.js"
function _initialize_backend(::PlotlyBackend; kw...)
@eval begin
_js_code = open(readstring, _plotly_js_path, "r")
# borrowed from https://github.com/plotly/plotly.py/blob/2594076e29584ede2d09f2aa40a8a195b3f3fc66/plotly/offline/offline.py#L64-L71 c/o @spencerlyon2
@ -111,10 +103,9 @@ function _initialize_backend(::PlotlyBackend; kw...)
# import Atom
# Atom.@msg evaljs(_js_code)
# end
using UUIDs
end
# TODO: other initialization
end
push!(_initialized_backends, :plotly)
# ----------------------------------------------------------------
@ -906,7 +897,7 @@ function html_body(plt::Plot{PlotlyBackend}, style = nothing)
w, h = plt[:size]
style = "width:$(w)px;height:$(h)px;"
end
uuid = Base.Random.uuid4()
uuid = UUIDs.uuid4()
html = """
<div id=\"$(uuid)\" style=\"$(style)\"></div>
<script>

View File

@ -13,35 +13,6 @@ const _plotlyjs_scale = _plotly_scale
# --------------------------------------------------------------------------------------
function add_backend_string(::PlotlyJSBackend)
"""
if !Plots.is_installed("PlotlyJS")
Pkg.add("PlotlyJS")
end
if !Plots.is_installed("Rsvg")
Pkg.add("Rsvg")
end
import Blink
Blink.AtomShell.install()
"""
end
function _initialize_backend(::PlotlyJSBackend; kw...)
@eval begin
import PlotlyJS
export PlotlyJS
end
# # override IJulia inline display
# if isijulia()
# IJulia.display_dict(plt::AbstractPlot{PlotlyJSBackend}) = IJulia.display_dict(plt.o)
# end
end
# ---------------------------------------------------------------------------
function _create_backend_figure(plt::Plot{PlotlyJSBackend})
if !isplotnull() && plt[:overwrite_figure] && isa(current().o, PlotlyJS.SyncPlot)
PlotlyJS.SyncPlot(PlotlyJS.Plot(), current().o.view)

View File

@ -59,29 +59,10 @@ is_marker_supported(::PyPlotBackend, shape::Shape) = true
# --------------------------------------------------------------------------------------
function add_backend_string(::PyPlotBackend)
"""
if !Plots.is_installed("PyPlot")
Pkg.add("PyPlot")
end
withenv("PYTHON" => "") do
Pkg.build("PyPlot")
end
# now restart julia!
"""
end
function _initialize_backend(::PyPlotBackend)
@eval begin
# problem: https://github.com/tbreloff/Plots.jl/issues/308
# solution: hack from @stevengj: https://github.com/stevengj/PyPlot.jl/pull/223#issuecomment-229747768
otherdisplays = splice!(Base.Multimedia.displays, 2:length(Base.Multimedia.displays))
import PyPlot, PyCall
import LaTeXStrings: latexstring
append!(Base.Multimedia.displays, otherdisplays)
export PyPlot
pycolors = PyPlot.pyimport("matplotlib.colors")
pypath = PyPlot.pyimport("matplotlib.path")
mplot3d = PyPlot.pyimport("mpl_toolkits.mplot3d")
@ -103,13 +84,6 @@ function _initialize_backend(::PyPlotBackend)
:set_facecolor
end
# we don't want every command to update the figure
PyPlot.ioff()
end
end
# --------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------
# # convert colorant to 4-tuple RGBA
# py_color(c::Colorant, α=nothing) = map(f->float(f(convertColor(c,α))), (red, green, blue, alpha))

View File

@ -3,14 +3,9 @@
# [ADD BACKEND WEBSITE]
function _initialize_backend(::[PkgName]Backend; kw...)
@eval begin
import [PkgName]
export [PkgName]
# todo: other initialization that needs to be eval-ed
end
# todo: other initialization
end
push!(_initialized_backends, [pgkname]::Symbol)
# ---------------------------------------------------------------------------

View File

@ -33,22 +33,6 @@ warnOnUnsupported_args(::UnicodePlotsBackend, d::KW) = nothing
# --------------------------------------------------------------------------------------
function add_backend_string(::UnicodePlotsBackend)
"""
Pkg.add("UnicodePlots")
Pkg.build("UnicodePlots")
"""
end
function _initialize_backend(::UnicodePlotsBackend; kw...)
@eval begin
import UnicodePlots
export UnicodePlots
end
end
# -------------------------------
const _canvas_type = Ref(:auto)
function _canvas_map()

View File

@ -255,62 +255,6 @@ end
#
# html_output_format("svg")
# ---------------------------------------------------------
# IJulia
# ---------------------------------------------------------
@require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin
if IJulia.inited
"""
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.
"""
_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
# ---------------------------------------------------------
# Atom PlotPane

View File

@ -412,8 +412,8 @@ function fakedata(sz...)
y
end
isijulia() = isdefined(Main, :IJulia) && Main.IJulia.inited
isatom() = isdefined(Main, :Atom) && Main.Atom.isconnected()
isijulia() = :IJulia in nameof.(collect(values(Base.loaded_modules)))
isatom() = :Atom in nameof.(collect(values(Base.loaded_modules)))
function is_installed(pkgstr::AbstractString)
try

View File

@ -6,3 +6,4 @@ GR 0.31.0
RDatasets
VisualRegressionTests
UnicodePlots
LaTeXStrings