Skip to content

Commit 6d776e6

Browse files
authored
Merge pull request #2 from stevengj/modernize
modernize API somewhat
2 parents f0e6b19 + 8250b3d commit 6d776e6

File tree

5 files changed

+69
-58
lines changed

5 files changed

+69
-58
lines changed

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,20 @@ see the [matplotlib.pyplot documentation for more
5252
information](http://matplotlib.org/api/pyplot_api.html). The Matplotlib
5353
version number is returned by `PythonPlot.version`.
5454

55+
### Differences from PyPlot
56+
57+
Compared to the PyPlot package, there are a few differences in the API.
58+
59+
* 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.)
60+
* The `matplotlibl.pyplot` module is exported as `pyplot` rather than as `plt`.
61+
* 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.
62+
5563
### Exported functions
5664

5765
Only the currently documented `matplotlib.pyplot` API is exported. To use
5866
other functions in the module, you can also call `matplotlib.pyplot.foo(...)`
59-
as `plt.foo(...)`. For example, `plt.plot(x, y)` also works. (And
60-
the raw `Py` object for the `matplotlib` modules is also accessible
67+
as `pyplot.foo(...)`. For example, `pyplot.plot(x, y)` also works. (And
68+
the raw `Py` object for the `matplotlib` module itself is also accessible
6169
as `PythonPlot.matplotlib`.)
6270

6371
Matplotlib is somewhat inconsistent about capitalization: it has
@@ -71,8 +79,8 @@ must be used to access `matplotlib.pyplot.xcorr`
7179
etcetera.
7280

7381
If you wish to access *all* of the PyPlot functions exclusively
74-
through `plt.somefunction(...)`, as is conventional in Python, you can
75-
do `import PythonPlot as plt` instead of `using PythonPlot`.
82+
through `pyplot.somefunction(...)`, as is conventional in Python, you can
83+
do `import PythonPlot as pyplot` instead of `using PythonPlot`.
7684

7785
### Figure objects
7886

@@ -96,7 +104,7 @@ function (`plot` etc.) is evaluated.
96104
However, if you use PythonPlot from a Julia script that is run non-interactively
97105
(e.g. `julia myscript.jl`), then Matplotlib is executed in
98106
[non-interactive mode](http://matplotlib.org/faq/usage_faq.html#what-is-interactive-mode):
99-
a plot window is not opened until you run `show()` (equivalent to `plt.show()`
107+
a plot window is not opened until you run `pyshow()` (equivalent to `pyplot.show()`
100108
in the Python examples).
101109

102110
## Interactive versus Julia graphics

src/PythonPlot.jl

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
"""
33
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.
44
5-
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(...).
6-
For example, plt.plot(x, y) also works. (And the raw Py object for the matplotlib modules is also accessible as PythonPlot.matplotlib.)
7-
85
In general, all the arguments are the same as in Python.
96
107
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
1916
module PythonPlot
2017

2118
using PythonCall
22-
import Base: convert, ==, isequal, hash, getindex, setindex!, haskey, keys, show
23-
export Figure, plt, matplotlib, pygui, withfig
19+
export Figure, matplotlib, pyplot, pygui, withfig, pltshow, pltstep, pltclose
2420

2521
###########################################################################
2622
# Define a documentation object
@@ -46,11 +42,11 @@ function Base.show(io::IO, ::MIME"text/plain", h::LazyHelp)
4642
print(io, "no Python docstring found for ", o)
4743
end
4844
end
49-
Base.show(io::IO, h::LazyHelp) = show(io, "text/plain", h)
45+
Base.show(io::IO, h::LazyHelp) = Base.show(io, "text/plain", h)
5046
function Base.Docs.catdoc(hs::LazyHelp...)
5147
Base.Docs.Text() do io
5248
for h in hs
53-
show(io, MIME"text/plain"(), h)
49+
Base.show(io, MIME"text/plain"(), h)
5450
end
5551
end
5652
end
@@ -68,10 +64,9 @@ mutable struct Figure
6864
end
6965
PythonCall.Py(f::Figure) = getfield(f, :o)
7066
PythonCall.pyconvert(::Type{Figure}, o::Py) = Figure(o)
71-
==(f::Figure, g::Figure) = Py(f) == Py(g)
72-
==(f::Figure, g::Py) = Py(f) == g
73-
==(f::Py, g::Figure) = f == Py(g)
74-
hash(f::Figure) = hash(Py(f))
67+
Base.:(==)(f::Figure, g::Figure) = pyconvert(Bool, Py(f) == Py(g))
68+
Base.isequal(f::Figure, g::Figure) = isequal(Py(f), Py(g))
69+
Base.hash(f::Figure, h::UInt) = hash(Py(f), h)
7570
PythonCall.pycall(f::Figure, args...; kws...) = pycall(Py(f), args...; kws...)
7671
(f::Figure)(args...; kws...) = pycall(Py(f), PyAny, args...; kws...)
7772
Base.Docs.doc(f::Figure) = Base.Docs.Text(pyconvert(String, Py(f).__doc__))
@@ -88,7 +83,7 @@ for (mime,fmt) in aggformats
8883
@eval _showable(::MIME{Symbol($mime)}, f::Figure) = !isempty(f) && haskey(PyDict{Any,Any}(f.canvas.get_supported_filetypes()), $fmt)
8984
@eval function Base.show(io::IO, m::MIME{Symbol($mime)}, f::Figure)
9085
if !_showable(m, f)
91-
throw(MethodError(show, (io, m, f)))
86+
throw(MethodError(Base.show, (io, m, f)))
9287
end
9388
f.canvas.print_figure(io, format=$fmt, bbox_inches="tight")
9489
end
@@ -127,7 +122,7 @@ function display_figs() # called after IJulia cell executes
127122
if pyconvert(Int, f.number) withfig_fignums
128123
fig = Figure(f)
129124
isempty(fig) || display(fig)
130-
plt.close(f)
125+
pyplot.close(f)
131126
end
132127
end
133128
end
@@ -138,7 +133,7 @@ function close_figs() # called after error in IJulia cell
138133
for manager in Gcf.get_all_fig_managers()
139134
f = manager.canvas.figure
140135
if pyconvert(Int, f.number) withfig_fignums
141-
plt.close(f)
136+
pyplot.close(f)
142137
end
143138
end
144139
end
@@ -168,47 +163,56 @@ end
168163
###########################################################################
169164

170165
# export documented pyplot API (http://matplotlib.org/api/pyplot_api.html)
171-
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
166+
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
172167

173168
# The following pyplot functions must be handled specially since they
174169
# overlap with standard Julia functions:
175170
# close, fill, show, step
176-
#as in PyPlot.jl, we commit some type piracy here.
171+
#unlike PyPlot.jl, we'll avoid type piracy by renaming / not exporting.
177172

178173
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)
179174

180175
for f in plt_funcs
181176
sf = string(f)
182-
@eval @doc LazyHelp(plt,$sf) function $f(args...; kws...)
183-
if !hasproperty(plt, $(QuoteNode(f)))
177+
@eval @doc LazyHelp(pyplot,$sf) function $f(args...; kws...)
178+
if !hasproperty(pyplot, $(QuoteNode(f)))
184179
error("matplotlib ", version, " does not have pyplot.", $sf)
185180
end
186-
return pycall(plt.$f, args...; kws...)
181+
return pycall(pyplot.$f, args...; kws...)
187182
end
188183
end
189184

190-
# type piracy as in PyPlot.jl:
191-
@doc LazyHelp(plt,"step") Base.step(x, y; kws...) = pycall(plt.step, x, y; kws...)
185+
# rename to avoid type piracy:
186+
@doc LazyHelp(pyplot,"step") pltstep(x, y; kws...) = pycall(pyplot.step, x, y; kws...)
192187

193-
# type piracy as in PyPlot.jl:
194-
Base.show(; kws...) = begin pycall(plt.show; kws...); nothing; end
188+
# rename to avoid type piracy:
189+
pltshow(; kws...) = begin pycall(pyplot.show; kws...); nothing; end
195190

196-
Base.close(f::Figure) = close(f.number)
191+
Base.close(f::Figure) = pltclose(f)
197192

198-
# type piracy as in PyPlot.jl:
199-
function Base.close(f::Integer)
193+
# rename to avoid type piracy:
194+
@doc LazyHelp(pyplot,"close") pltclose() = pyplot.close()
195+
pltclose(f::Figure) = pyconvert(Int, pltclose(f.number))
196+
function pltclose(f::Integer)
200197
pop!(withfig_fignums, f, f)
201-
plt.close(f)
198+
pyplot.close(f)
202199
end
203-
Base.close(f::Union{AbstractString,Symbol}) = plt.close(f)
204-
@doc LazyHelp(plt,"close") Base.close() = plt.close()
200+
pltclose(f::AbstractString) = pyplot.close(f)
205201

206-
# type piracy as in PyPlot.jl:
207-
@doc LazyHelp(plt,"fill") Base.fill(x::AbstractArray,y::AbstractArray, args...; kws...) =
208-
pycall(plt."fill", PyAny, x, y, args...; kws...)
202+
# rename to avoid type piracy:
203+
@doc LazyHelp(pyplot,"fill") pltfill(x::AbstractArray,y::AbstractArray, args...; kws...) =
204+
pycall(pyplot.fill, PyAny, x, y, args...; kws...)
209205

210206
# consistent capitalization with mplot3d
211-
@doc LazyHelp(plt,"hist2d") hist2D(args...; kws...) = pycall(plt.hist2d, args...; kws...)
207+
@doc LazyHelp(pyplot,"hist2d") hist2D(args...; kws...) = pycall(pyplot.hist2d, args...; kws...)
208+
209+
# allow them to be accessed via their original names foo
210+
# as PythonPlot.foo … this also means that we must be careful
211+
# to use them as Base.foo in this module as needed!
212+
const close = pltclose
213+
const fill = pltfill
214+
const show = pltshow
215+
const step = pltstep
212216

213217
include("colormaps.jl")
214218

@@ -287,7 +291,7 @@ function withfig(actions::Function, f::Figure; clear=true)
287291
ax_save = gca()
288292
push!(withfig_fignums, f.number)
289293
figure(f.number)
290-
finalizer(close, f)
294+
finalizer(pltclose, f)
291295
try
292296
if clear && !isempty(f)
293297
clf()

src/colormaps.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ end
1313

1414
PythonCall.Py(c::ColorMap) = getfield(c, :o)
1515
PythonCall.pyconvert(::Type{ColorMap}, o::Py) = ColorMap(o)
16-
==(c::ColorMap, g::ColorMap) = Py(c) == Py(g)
17-
==(c::Py, g::ColorMap) = c == Py(g)
18-
==(c::ColorMap, g::Py) = Py(c) == g
19-
hash(c::ColorMap) = hash(Py(c))
16+
Base.:(==)(c::ColorMap, g::ColorMap) = pyconvert(Bool, Py(c) == Py(g))
17+
Base.isequal(c::ColorMap, g::ColorMap) = isequal(Py(c), Py(g))
18+
Base.hash(c::ColorMap, h::UInt) = hash(Py(c), h)
2019
PythonCall.pycall(c::ColorMap, args...; kws...) = pycall(Py(c), args...; kws...)
2120
(c::ColorMap)(args...; kws...) = pycall(Py(c), args...; kws...)
2221
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
2928
Base.propertynames(c::ColorMap) = propertynames(Py(c))
3029
Base.hasproperty(c::ColorMap, s::Union{Symbol,AbstractString}) = hasproperty(Py(c), s)
3130

32-
function show(io::IO, c::ColorMap)
31+
function Base.show(io::IO, c::ColorMap)
3332
print(io, "ColorMap \"$(pyconvert(String, c.name))\"")
3433
end
3534

@@ -187,7 +186,7 @@ function Base.show(io::IO, ::MIME"image/svg+xml", cs::AbstractVector{ColorMap})
187186
end
188187

189188
function Base.show(io::IO, m::MIME"image/svg+xml", c::ColorMap)
190-
show(io, m, [c])
189+
Base.show(io, m, [c])
191190
end
192191

193192
########################################################################

src/init.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ using VersionParsing
88
# so that their type is known at compile-time.
99

1010
const matplotlib = PythonCall.pynew()
11-
const plt = PythonCall.pynew()
11+
const pyplot = PythonCall.pynew()
1212
const Gcf = PythonCall.pynew()
1313
const orig_draw = PythonCall.pynew()
1414
const orig_gcf = PythonCall.pynew()
@@ -158,12 +158,12 @@ function __init__()
158158
global backend = backend_gui[1]
159159
global gui = backend_gui[2]
160160

161-
PythonCall.pycopy!(plt, pyimport("matplotlib.pyplot")) # raw Python module
161+
PythonCall.pycopy!(pyplot, pyimport("matplotlib.pyplot")) # raw Python module
162162
PythonCall.pycopy!(Gcf, pyimport("matplotlib._pylab_helpers").Gcf)
163-
PythonCall.pycopy!(orig_gcf, plt.gcf)
164-
PythonCall.pycopy!(orig_figure, plt.figure)
165-
plt.gcf = gcf
166-
plt.figure = figure
163+
PythonCall.pycopy!(orig_gcf, pyplot.gcf)
164+
PythonCall.pycopy!(orig_figure, pyplot.figure)
165+
pyplot.gcf = gcf
166+
pyplot.figure = figure
167167

168168
if isdefined(Main, :IJulia) && Main.IJulia.inited
169169
Main.IJulia.push_preexecute_hook(force_new_fig)
@@ -172,8 +172,8 @@ function __init__()
172172
end
173173

174174
if isjulia_display[] && gui != :gr && backend != "Agg"
175-
plt.switch_backend("Agg")
176-
plt.ioff()
175+
pyplot.switch_backend("Agg")
176+
pyplot.ioff()
177177
end
178178

179179
init_colormaps()
@@ -182,12 +182,12 @@ end
182182
function pygui(b::Bool)
183183
if !b != isjulia_display[]
184184
if backend != "Agg"
185-
plt.switch_backend(b ? backend : "Agg")
185+
pyplot.switch_backend(b ? backend : "Agg")
186186
if b
187187
pygui_start(gui) # make sure event loop is started
188-
Base.isinteractive() && plt.ion()
188+
Base.isinteractive() && pyplot.ion()
189189
else
190-
plt.ioff()
190+
pyplot.ioff()
191191
end
192192
elseif b
193193
error("No working GUI backend found for matplotlib.")

src/plot3d.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ for f in mplot3d_funcs
4646
fs = string(f)
4747
@eval @doc LazyHelp(axes3D,"Axes3D", $fs) function $f(args...; kws...)
4848
using3D() # make sure mplot3d is loaded
49-
ax = version <= v"3.4" ? gca(projection="3d") : plt.subplot(projection="3d")
49+
ax = version <= v"3.4" ? gca(projection="3d") : pyplot.subplot(projection="3d")
5050
pycall(ax.$fs, args...; kws...)
5151
end
5252
end
@@ -62,7 +62,7 @@ for f in zlabel_funcs
6262
fs = string("set_", f)
6363
@eval @doc LazyHelp(axes3D,"Axes3D", $fs) function $f(args...; kws...)
6464
using3D() # make sure mplot3d is loaded
65-
ax = version <= v"3.4" ? gca(projection="3d") : plt.subplot(projection="3d")
65+
ax = version <= v"3.4" ? gca(projection="3d") : pyplot.subplot(projection="3d")
6666
pycall(ax.$fs, args...; kws...)
6767
end
6868
end

0 commit comments

Comments
 (0)