diff --git a/README.md b/README.md index ebd4f82..4c8f3e6 100644 --- a/README.md +++ b/README.md @@ -52,12 +52,20 @@ see the [matplotlib.pyplot documentation for more information](http://matplotlib.org/api/pyplot_api.html). The Matplotlib version number is returned by `PythonPlot.version`. +### Differences from PyPlot + +Compared to the PyPlot package, there are a few differences in the API. + +* To avoid type piracy, the functions `show`, `close`, `step`, and `fill` are renamed to `pltshow`, `pltclose`, `pltstep`, and `pltfill`, respectively. (You can also access them as `PythonPlot.show` etcetera.) +* The `matplotlibl.pyplot` module is exported as `pyplot` rather than as `plt`. +* The PythonCall package performs many fewer automatic conversions from Python types to Julia types (in comparison to PyCall). If you need to convert Matplotlib return values to native Julia objects, you'll need to do `using PythonCall` and call its `pyconvert(T, o)` or other conversion functions. + ### Exported functions Only the currently documented `matplotlib.pyplot` API is exported. To use other functions in the module, you can also call `matplotlib.pyplot.foo(...)` -as `plt.foo(...)`. For example, `plt.plot(x, y)` also works. (And -the raw `Py` object for the `matplotlib` modules is also accessible +as `pyplot.foo(...)`. For example, `pyplot.plot(x, y)` also works. (And +the raw `Py` object for the `matplotlib` module itself is also accessible as `PythonPlot.matplotlib`.) Matplotlib is somewhat inconsistent about capitalization: it has @@ -71,8 +79,8 @@ must be used to access `matplotlib.pyplot.xcorr` etcetera. If you wish to access *all* of the PyPlot functions exclusively -through `plt.somefunction(...)`, as is conventional in Python, you can -do `import PythonPlot as plt` instead of `using PythonPlot`. +through `pyplot.somefunction(...)`, as is conventional in Python, you can +do `import PythonPlot as pyplot` instead of `using PythonPlot`. ### Figure objects @@ -96,7 +104,7 @@ function (`plot` etc.) is evaluated. However, if you use PythonPlot from a Julia script that is run non-interactively (e.g. `julia myscript.jl`), then Matplotlib is executed in [non-interactive mode](http://matplotlib.org/faq/usage_faq.html#what-is-interactive-mode): -a plot window is not opened until you run `show()` (equivalent to `plt.show()` +a plot window is not opened until you run `pyshow()` (equivalent to `pyplot.show()` in the Python examples). ## Interactive versus Julia graphics diff --git a/src/PythonPlot.jl b/src/PythonPlot.jl index 9894c74..da857e1 100755 --- a/src/PythonPlot.jl +++ b/src/PythonPlot.jl @@ -2,9 +2,6 @@ """ PythonPlot allows Julia to interface with the Matplotlib library in Python, specifically the matplotlib.pyplot module, so you can create beautiful plots in Julia with your favorite Python package. -Only the currently documented matplotlib.pyplot API is exported. To use other functions in the module, you can also call matplotlib.pyplot.foo(...) as plt.foo(...). -For example, plt.plot(x, y) also works. (And the raw Py object for the matplotlib modules is also accessible as PythonPlot.matplotlib.) - In general, all the arguments are the same as in Python. Here's a brief demo of a simple plot in Julia: @@ -19,8 +16,7 @@ For more information on API, see the matplotlib.pyplot documentation and the Pyt module PythonPlot using PythonCall -import Base: convert, ==, isequal, hash, getindex, setindex!, haskey, keys, show -export Figure, plt, matplotlib, pygui, withfig +export Figure, matplotlib, pyplot, pygui, withfig, pltshow, pltstep, pltclose ########################################################################### # Define a documentation object @@ -46,11 +42,11 @@ function Base.show(io::IO, ::MIME"text/plain", h::LazyHelp) print(io, "no Python docstring found for ", o) end end -Base.show(io::IO, h::LazyHelp) = show(io, "text/plain", h) +Base.show(io::IO, h::LazyHelp) = Base.show(io, "text/plain", h) function Base.Docs.catdoc(hs::LazyHelp...) Base.Docs.Text() do io for h in hs - show(io, MIME"text/plain"(), h) + Base.show(io, MIME"text/plain"(), h) end end end @@ -68,10 +64,9 @@ mutable struct Figure end PythonCall.Py(f::Figure) = getfield(f, :o) PythonCall.pyconvert(::Type{Figure}, o::Py) = Figure(o) -==(f::Figure, g::Figure) = Py(f) == Py(g) -==(f::Figure, g::Py) = Py(f) == g -==(f::Py, g::Figure) = f == Py(g) -hash(f::Figure) = hash(Py(f)) +Base.:(==)(f::Figure, g::Figure) = pyconvert(Bool, Py(f) == Py(g)) +Base.isequal(f::Figure, g::Figure) = isequal(Py(f), Py(g)) +Base.hash(f::Figure, h::UInt) = hash(Py(f), h) PythonCall.pycall(f::Figure, args...; kws...) = pycall(Py(f), args...; kws...) (f::Figure)(args...; kws...) = pycall(Py(f), PyAny, args...; kws...) Base.Docs.doc(f::Figure) = Base.Docs.Text(pyconvert(String, Py(f).__doc__)) @@ -88,7 +83,7 @@ for (mime,fmt) in aggformats @eval _showable(::MIME{Symbol($mime)}, f::Figure) = !isempty(f) && haskey(PyDict{Any,Any}(f.canvas.get_supported_filetypes()), $fmt) @eval function Base.show(io::IO, m::MIME{Symbol($mime)}, f::Figure) if !_showable(m, f) - throw(MethodError(show, (io, m, f))) + throw(MethodError(Base.show, (io, m, f))) end f.canvas.print_figure(io, format=$fmt, bbox_inches="tight") end @@ -127,7 +122,7 @@ function display_figs() # called after IJulia cell executes if pyconvert(Int, f.number) ∉ withfig_fignums fig = Figure(f) isempty(fig) || display(fig) - plt.close(f) + pyplot.close(f) end end end @@ -138,7 +133,7 @@ function close_figs() # called after error in IJulia cell for manager in Gcf.get_all_fig_managers() f = manager.canvas.figure if pyconvert(Int, f.number) ∉ withfig_fignums - plt.close(f) + pyplot.close(f) end end end @@ -168,47 +163,56 @@ end ########################################################################### # export documented pyplot API (http://matplotlib.org/api/pyplot_api.html) -export acorr,annotate,arrow,autoscale,autumn,axhline,axhspan,axis,axline,axvline,axvspan,bar,barbs,barh,bone,box,boxplot,broken_barh,cla,clabel,clf,clim,cohere,colorbar,colors,contour,contourf,cool,copper,csd,delaxes,disconnect,draw,errorbar,eventplot,figaspect,figimage,figlegend,figtext,figure,fill_between,fill_betweenx,findobj,flag,gca,gcf,gci,get_current_fig_manager,get_figlabels,get_fignums,get_plot_commands,ginput,gray,grid,hexbin,hist2D,hlines,hold,hot,hsv,imread,imsave,imshow,ioff,ion,ishold,jet,legend,locator_params,loglog,margins,matshow,minorticks_off,minorticks_on,over,pause,pcolor,pcolormesh,pie,pink,plot,plot_date,plotfile,polar,prism,psd,quiver,quiverkey,rc,rc_context,rcdefaults,rgrids,savefig,sca,scatter,sci,semilogx,semilogy,set_cmap,setp,show,specgram,spectral,spring,spy,stackplot,stem,step,streamplot,subplot,subplot2grid,subplot_tool,subplots,subplots_adjust,summer,suptitle,table,text,thetagrids,tick_params,ticklabel_format,tight_layout,title,tricontour,tricontourf,tripcolor,triplot,twinx,twiny,vlines,waitforbuttonpress,winter,xkcd,xlabel,xlim,xscale,xticks,ylabel,ylim,yscale,yticks,hist +export acorr,annotate,arrow,autoscale,autumn,axhline,axhspan,axis,axline,axvline,axvspan,bar,barbs,barh,bone,box,boxplot,broken_barh,cla,clabel,clf,clim,cohere,colorbar,colors,contour,contourf,cool,copper,csd,delaxes,disconnect,draw,errorbar,eventplot,figaspect,figimage,figlegend,figtext,figure,fill_between,fill_betweenx,findobj,flag,gca,gcf,gci,get_current_fig_manager,get_figlabels,get_fignums,get_plot_commands,ginput,gray,grid,hexbin,hist2D,hlines,hold,hot,hsv,imread,imsave,imshow,ioff,ion,ishold,jet,legend,locator_params,loglog,margins,matshow,minorticks_off,minorticks_on,over,pause,pcolor,pcolormesh,pie,pink,plot,plot_date,plotfile,polar,prism,psd,quiver,quiverkey,rc,rc_context,rcdefaults,rgrids,savefig,sca,scatter,sci,semilogx,semilogy,set_cmap,setp,specgram,spectral,spring,spy,stackplot,stem,step,streamplot,subplot,subplot2grid,subplot_tool,subplots,subplots_adjust,summer,suptitle,table,text,thetagrids,tick_params,ticklabel_format,tight_layout,title,tricontour,tricontourf,tripcolor,triplot,twinx,twiny,vlines,waitforbuttonpress,winter,xkcd,xlabel,xlim,xscale,xticks,ylabel,ylim,yscale,yticks,hist # The following pyplot functions must be handled specially since they # overlap with standard Julia functions: # close, fill, show, step -# … as in PyPlot.jl, we commit some type piracy here. +# … unlike PyPlot.jl, we'll avoid type piracy by renaming / not exporting. const plt_funcs = (:acorr,:annotate,:arrow,:autoscale,:autumn,:axes,:axhline,:axhspan,:axis,:axline,:axvline,:axvspan,:bar,:barbs,:barh,:bone,:box,:boxplot,:broken_barh,:cla,:clabel,:clf,:clim,:cohere,:colorbar,:colors,:connect,:contour,:contourf,:cool,:copper,:csd,:delaxes,:disconnect,:draw,:errorbar,:eventplot,:figaspect,:figimage,:figlegend,:figtext,:fill_between,:fill_betweenx,:findobj,:flag,:gca,:gci,:get_current_fig_manager,:get_figlabels,:get_fignums,:get_plot_commands,:ginput,:gray,:grid,:hexbin,:hlines,:hold,:hot,:hsv,:imread,:imsave,:imshow,:ioff,:ion,:ishold,:jet,:legend,:locator_params,:loglog,:margins,:matshow,:minorticks_off,:minorticks_on,:over,:pause,:pcolor,:pcolormesh,:pie,:pink,:plot,:plot_date,:plotfile,:polar,:prism,:psd,:quiver,:quiverkey,:rc,:rc_context,:rcdefaults,:rgrids,:savefig,:sca,:scatter,:sci,:semilogx,:semilogy,:set_cmap,:setp,:specgram,:spectral,:spring,:spy,:stackplot,:stem,:streamplot,:subplot,:subplot2grid,:subplot_tool,:subplots,:subplots_adjust,:summer,:suptitle,:table,:text,:thetagrids,:tick_params,:ticklabel_format,:tight_layout,:title,:tricontour,:tricontourf,:tripcolor,:triplot,:twinx,:twiny,:vlines,:waitforbuttonpress,:winter,:xkcd,:xlabel,:xlim,:xscale,:xticks,:ylabel,:ylim,:yscale,:yticks,:hist,:xcorr,:isinteractive) for f in plt_funcs sf = string(f) - @eval @doc LazyHelp(plt,$sf) function $f(args...; kws...) - if !hasproperty(plt, $(QuoteNode(f))) + @eval @doc LazyHelp(pyplot,$sf) function $f(args...; kws...) + if !hasproperty(pyplot, $(QuoteNode(f))) error("matplotlib ", version, " does not have pyplot.", $sf) end - return pycall(plt.$f, args...; kws...) + return pycall(pyplot.$f, args...; kws...) end end -# type piracy as in PyPlot.jl: -@doc LazyHelp(plt,"step") Base.step(x, y; kws...) = pycall(plt.step, x, y; kws...) +# rename to avoid type piracy: +@doc LazyHelp(pyplot,"step") pltstep(x, y; kws...) = pycall(pyplot.step, x, y; kws...) -# type piracy as in PyPlot.jl: -Base.show(; kws...) = begin pycall(plt.show; kws...); nothing; end +# rename to avoid type piracy: +pltshow(; kws...) = begin pycall(pyplot.show; kws...); nothing; end -Base.close(f::Figure) = close(f.number) +Base.close(f::Figure) = pltclose(f) -# type piracy as in PyPlot.jl: -function Base.close(f::Integer) +# rename to avoid type piracy: +@doc LazyHelp(pyplot,"close") pltclose() = pyplot.close() +pltclose(f::Figure) = pyconvert(Int, pltclose(f.number)) +function pltclose(f::Integer) pop!(withfig_fignums, f, f) - plt.close(f) + pyplot.close(f) end -Base.close(f::Union{AbstractString,Symbol}) = plt.close(f) -@doc LazyHelp(plt,"close") Base.close() = plt.close() +pltclose(f::AbstractString) = pyplot.close(f) -# type piracy as in PyPlot.jl: -@doc LazyHelp(plt,"fill") Base.fill(x::AbstractArray,y::AbstractArray, args...; kws...) = - pycall(plt."fill", PyAny, x, y, args...; kws...) +# rename to avoid type piracy: +@doc LazyHelp(pyplot,"fill") pltfill(x::AbstractArray,y::AbstractArray, args...; kws...) = + pycall(pyplot.fill, PyAny, x, y, args...; kws...) # consistent capitalization with mplot3d -@doc LazyHelp(plt,"hist2d") hist2D(args...; kws...) = pycall(plt.hist2d, args...; kws...) +@doc LazyHelp(pyplot,"hist2d") hist2D(args...; kws...) = pycall(pyplot.hist2d, args...; kws...) + +# allow them to be accessed via their original names foo +# as PythonPlot.foo … this also means that we must be careful +# to use them as Base.foo in this module as needed! +const close = pltclose +const fill = pltfill +const show = pltshow +const step = pltstep include("colormaps.jl") @@ -287,7 +291,7 @@ function withfig(actions::Function, f::Figure; clear=true) ax_save = gca() push!(withfig_fignums, f.number) figure(f.number) - finalizer(close, f) + finalizer(pltclose, f) try if clear && !isempty(f) clf() diff --git a/src/colormaps.jl b/src/colormaps.jl index 4b3a8c5..39f9d90 100644 --- a/src/colormaps.jl +++ b/src/colormaps.jl @@ -13,10 +13,9 @@ end PythonCall.Py(c::ColorMap) = getfield(c, :o) PythonCall.pyconvert(::Type{ColorMap}, o::Py) = ColorMap(o) -==(c::ColorMap, g::ColorMap) = Py(c) == Py(g) -==(c::Py, g::ColorMap) = c == Py(g) -==(c::ColorMap, g::Py) = Py(c) == g -hash(c::ColorMap) = hash(Py(c)) +Base.:(==)(c::ColorMap, g::ColorMap) = pyconvert(Bool, Py(c) == Py(g)) +Base.isequal(c::ColorMap, g::ColorMap) = isequal(Py(c), Py(g)) +Base.hash(c::ColorMap, h::UInt) = hash(Py(c), h) PythonCall.pycall(c::ColorMap, args...; kws...) = pycall(Py(c), args...; kws...) (c::ColorMap)(args...; kws...) = pycall(Py(c), args...; kws...) Base.Docs.doc(c::ColorMap) = Base.Docs.Text(pyconvert(String, Py(c).__doc__)) @@ -29,7 +28,7 @@ Base.setproperty!(c::ColorMap, s::AbstractString, x) = setproperty!(Py(c), Symbo Base.propertynames(c::ColorMap) = propertynames(Py(c)) Base.hasproperty(c::ColorMap, s::Union{Symbol,AbstractString}) = hasproperty(Py(c), s) -function show(io::IO, c::ColorMap) +function Base.show(io::IO, c::ColorMap) print(io, "ColorMap \"$(pyconvert(String, c.name))\"") end @@ -187,7 +186,7 @@ function Base.show(io::IO, ::MIME"image/svg+xml", cs::AbstractVector{ColorMap}) end function Base.show(io::IO, m::MIME"image/svg+xml", c::ColorMap) - show(io, m, [c]) + Base.show(io, m, [c]) end ######################################################################## diff --git a/src/init.jl b/src/init.jl index 445e94d..af488f1 100644 --- a/src/init.jl +++ b/src/init.jl @@ -8,7 +8,7 @@ using VersionParsing # so that their type is known at compile-time. const matplotlib = PythonCall.pynew() -const plt = PythonCall.pynew() +const pyplot = PythonCall.pynew() const Gcf = PythonCall.pynew() const orig_draw = PythonCall.pynew() const orig_gcf = PythonCall.pynew() @@ -158,12 +158,12 @@ function __init__() global backend = backend_gui[1] global gui = backend_gui[2] - PythonCall.pycopy!(plt, pyimport("matplotlib.pyplot")) # raw Python module + PythonCall.pycopy!(pyplot, pyimport("matplotlib.pyplot")) # raw Python module PythonCall.pycopy!(Gcf, pyimport("matplotlib._pylab_helpers").Gcf) - PythonCall.pycopy!(orig_gcf, plt.gcf) - PythonCall.pycopy!(orig_figure, plt.figure) - plt.gcf = gcf - plt.figure = figure + PythonCall.pycopy!(orig_gcf, pyplot.gcf) + PythonCall.pycopy!(orig_figure, pyplot.figure) + pyplot.gcf = gcf + pyplot.figure = figure if isdefined(Main, :IJulia) && Main.IJulia.inited Main.IJulia.push_preexecute_hook(force_new_fig) @@ -172,8 +172,8 @@ function __init__() end if isjulia_display[] && gui != :gr && backend != "Agg" - plt.switch_backend("Agg") - plt.ioff() + pyplot.switch_backend("Agg") + pyplot.ioff() end init_colormaps() @@ -182,12 +182,12 @@ end function pygui(b::Bool) if !b != isjulia_display[] if backend != "Agg" - plt.switch_backend(b ? backend : "Agg") + pyplot.switch_backend(b ? backend : "Agg") if b pygui_start(gui) # make sure event loop is started - Base.isinteractive() && plt.ion() + Base.isinteractive() && pyplot.ion() else - plt.ioff() + pyplot.ioff() end elseif b error("No working GUI backend found for matplotlib.") diff --git a/src/plot3d.jl b/src/plot3d.jl index 00a6cb8..9d623d2 100644 --- a/src/plot3d.jl +++ b/src/plot3d.jl @@ -46,7 +46,7 @@ for f in mplot3d_funcs fs = string(f) @eval @doc LazyHelp(axes3D,"Axes3D", $fs) function $f(args...; kws...) using3D() # make sure mplot3d is loaded - ax = version <= v"3.4" ? gca(projection="3d") : plt.subplot(projection="3d") + ax = version <= v"3.4" ? gca(projection="3d") : pyplot.subplot(projection="3d") pycall(ax.$fs, args...; kws...) end end @@ -62,7 +62,7 @@ for f in zlabel_funcs fs = string("set_", f) @eval @doc LazyHelp(axes3D,"Axes3D", $fs) function $f(args...; kws...) using3D() # make sure mplot3d is loaded - ax = version <= v"3.4" ? gca(projection="3d") : plt.subplot(projection="3d") + ax = version <= v"3.4" ? gca(projection="3d") : pyplot.subplot(projection="3d") pycall(ax.$fs, args...; kws...) end end