Skip to content

Commit 98da46b

Browse files
author
Christopher Doris
committed
change display of matplotlib figures
1 parent 1a8a2f8 commit 98da46b

File tree

7 files changed

+66
-146
lines changed

7 files changed

+66
-146
lines changed

docs/src/compat.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,24 @@ In the other direction, the following functions can be used to convert any `Tabl
2020
pytable
2121
```
2222

23-
## MatPlotLib / PyPlot
23+
## MatPlotLib / PyPlot / Seaborn
2424

25-
```@docs
26-
pyplot_show
27-
```
25+
MatPlotLib figures can be shown with Julia's display mechanism,
26+
like `display(fig)` or `display(mime, fig)`.
27+
28+
This means that if you return a figure from a Jupyter or Pluto notebook cell,
29+
it will be shown. You can call `display(plt.gcf())` to display the current figure.
2830

29-
If Julia is running an IJulia kernel, `pyplot_show()` is automatically called after executing a cell, so that plots generated in a cell are always shown (similar to IPython). It can be disabled by setting `PythonCall.CONFIG.auto_pyplot_show = false`.
31+
We also provide a simple MatPlotLib backend: `mpl.use("module://juliacall.matplotlib")`.
32+
Now you can call `plt.show()` to display the figure with Julia's display mechanism.
33+
You can specify the format like `plt.show(format="png")`.
3034

3135
## GUIs (including MatPlotLib)
3236

3337
### Event loops
3438

35-
If for example you wish to use PyPlot in interactive mode (`matplotlib.pyplot.ion()`) then activating the correct event loop will allow it to work.
39+
If for example you wish to use PyPlot in interactive mode (`matplotlib.pyplot.ion()`)
40+
then activating the correct event loop will allow it to work.
3641

3742
```@docs
3843
PythonCall.event_loop_on

examples/seaborn.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ using Markdown
55
using InteractiveUtils
66

77
# ╔═╡ e9dcc3e9-7c0f-40aa-b633-1242aacca66c
8-
using Pkg; Pkg.activate("tmp", shared=true)
8+
using Pkg; Pkg.activate("tmp", shared=true);
99

1010
# ╔═╡ 545b2750-1bc5-11ec-1d6a-275133eec22d
11-
using PythonCall, RDatasets
11+
using PythonCall, RDatasets;
1212

1313
# ╔═╡ dbf0b8fc-d9d1-492a-892e-79f1ea86c345
1414
iris = dataset("datasets", "iris");

juliacall/matplotlib.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
Minimal matplotlib backend which shows plots using Julia's display mechanism.
3+
"""
4+
5+
from matplotlib.backend_bases import Gcf, FigureManagerBase
6+
from matplotlib.backends.backend_agg import FigureCanvasAgg
7+
from juliacall import Base as jl
8+
9+
FigureCanvas = FigureCanvasAgg
10+
11+
def show(format=None):
12+
for figmanager in Gcf.get_all_fig_managers():
13+
figmanager.show(format=format)
14+
15+
FORMAT_TO_MIME = {
16+
'png': 'image/png',
17+
'jpg': 'image/jpeg',
18+
'jpeg': 'image/jpeg',
19+
'tif': 'image/tiff',
20+
'tiff': 'image/tiff',
21+
'svg': 'image/svg+xml',
22+
'pdf': 'application/pdf',
23+
}
24+
25+
class FigureManager(FigureManagerBase):
26+
def show(self, format=None):
27+
fig = self.canvas.figure
28+
mime = FORMAT_TO_MIME.get(format)
29+
if format is None:
30+
jl.display(fig)
31+
elif mime is None:
32+
raise ValueError('invalid format {}, expecting one of: {}'.format(format, ', '.join(FORMAT_TO_MIME.keys())))
33+
else:
34+
jl.display(mime, fig)

src/PythonCall.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ include("compat/with.jl")
7575
include("compat/multimedia.jl")
7676
include("compat/serialization.jl")
7777
include("compat/gui.jl")
78-
include("compat/matplotlib.jl")
7978
include("compat/ipython.jl")
8079
include("compat/tables.jl")
8180

@@ -84,7 +83,6 @@ function __init__()
8483
init_consts()
8584
init_pyconvert()
8685
init_datetime()
87-
init_pyshow()
8886
# juliacall/jlwrap
8987
init_juliacall()
9088
init_jlwrap_base()
@@ -103,8 +101,8 @@ function __init__()
103101
init_juliacall_2()
104102
# compat
105103
init_stdlib()
104+
init_pyshow()
106105
init_gui()
107-
init_matplotlib()
108106
init_ipython()
109107
init_tables()
110108
end

src/compat/matplotlib.jl

Lines changed: 0 additions & 123 deletions
This file was deleted.

src/compat/multimedia.jl

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,14 @@ end
2525

2626
### Particular rules
2727

28+
# x._repr_mimebundle_()
2829
function pyshow_rule_mimebundle(io::IO, mime::String, x::Py)
2930
try
3031
ans = x._repr_mimebundle_(include=pylist((mime,)))
3132
if pyisinstance(ans, pybuiltins.tuple)
3233
data = ans[0][mime]
33-
meta = ans[1].get(mime)
3434
else
3535
data = ans[mime]
36-
meta = pybuiltins.None
3736
end
3837
write(io, pyconvert(Union{String,Vector{UInt8}}, data))
3938
return true
@@ -59,17 +58,16 @@ const MIME_TO_REPR_METHOD = Dict(
5958
"image/svg+xml" => "_repr_svg_",
6059
)
6160

61+
# x._repr_FORMAT_()
6262
function pyshow_rule_repr(io::IO, mime::String, x::Py)
6363
method = get(MIME_TO_REPR_METHOD, mime, "")
6464
isempty(method) && return false
6565
try
6666
ans = pygetattr(x, method)()
6767
if pyisinstance(ans, pybuiltins.tuple)
6868
data = ans[0]
69-
meta = ans[1]
7069
else
7170
data = ans
72-
meta = pybuiltins.None
7371
end
7472
write(io, pyconvert(Union{String,Vector{UInt8}}, data))
7573
return true
@@ -90,16 +88,25 @@ const MIME_TO_MATPLOTLIB_FORMAT = Dict(
9088
"application/pdf" => "pdf",
9189
)
9290

91+
# x.savefig()
92+
# Requires x to be a matplotlib.pyplot.Figure, or x.figure to be one.
93+
# Closes the underlying figure.
9394
function pyshow_rule_savefig(io::IO, mime::String, x::Py)
94-
# TODO: restrict to types or modules which are known to have a savefig method like this?
9595
format = get(MIME_TO_MATPLOTLIB_FORMAT, mime, "")
9696
isempty(format) && return false
97+
pyhasattr(x, "savefig") || return false
9798
try
98-
# buf = pyimport("io").BytesIO()
99-
# x.savefig(buf, format=format)
100-
# data = pyconvert(Vector{UInt8}, buf.getvalue())
101-
# write(io, data)
102-
x.savefig(io, format=format)
99+
plt = pysysmodule.modules["matplotlib.pyplot"]
100+
Figure = plt.Figure
101+
fig = x
102+
while !pyisinstance(fig, Figure)
103+
fig = fig.figure
104+
end
105+
buf = pyimport("io").BytesIO()
106+
x.savefig(buf, format=format)
107+
data = pyconvert(Vector{UInt8}, buf.getvalue())
108+
write(io, data)
109+
plt.close(fig)
103110
return true
104111
catch exc
105112
if exc isa PyException
@@ -113,5 +120,5 @@ end
113120
function init_pyshow()
114121
pyshow_add_rule(pyshow_rule_mimebundle)
115122
pyshow_add_rule(pyshow_rule_repr)
116-
# pyshow_add_rule(pyshow_rule_savefig)
123+
pyshow_add_rule(pyshow_rule_savefig)
117124
end

src/config.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ Base.@kwdef mutable struct Config
22
meta :: String = ""
33
auto_sys_last_traceback :: Bool = true
44
auto_fix_qt_plugin_path :: Bool = true
5-
auto_pyplot_show :: Bool = true
65
auto_ipython_display :: Bool = true
76
end
87

0 commit comments

Comments
 (0)