Skip to content

Commit 04630ff

Browse files
seismanweiji14Meghan Jones
authored
Improve Figure.show for displaying previews in Jupyter notebooks and external viewers (#529)
Figure.show() now is able to detect the current running environment, and display previews in Jupyter notebooks, or open previews using external viewers. This PR also provides `pygmt.set_display()` to change the display method globally. Setting environmental variable `PYGMT_USE_EXTERNAL_DISPLAY ` to `false` can disable external viewers, mostly for running tests and building the documentations. Figure.show() no longer returns the Image object. Co-authored-by: Wei Ji <[email protected]> Co-authored-by: Meghan Jones <[email protected]>
1 parent 4fc657c commit 04630ff

File tree

6 files changed

+113
-45
lines changed

6 files changed

+113
-45
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ test:
3636
@echo ""
3737
@cd $(TESTDIR); python -c "import $(PROJECT); $(PROJECT).show_versions()"
3838
@echo ""
39-
cd $(TESTDIR); pytest $(PYTEST_COV_ARGS) $(PROJECT)
39+
cd $(TESTDIR); PYGMT_USE_EXTERNAL_DISPLAY="false" pytest $(PYTEST_COV_ARGS) $(PROJECT)
4040
cp $(TESTDIR)/coverage.xml .
4141
cp -r $(TESTDIR)/htmlcov .
4242
rm -r $(TESTDIR)

doc/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ html: api
3131
@echo
3232
@echo "Building HTML files."
3333
@echo
34-
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
34+
# Set PYGMT_USE_EXTERNAL_DISPLAY to "false" to disable external display
35+
PYGMT_USE_EXTERNAL_DISPLAY="false" $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
3536
@echo
3637
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
3738

doc/api/index.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ Saving and displaying the figure:
6060
Figure.show
6161
Figure.psconvert
6262

63+
Configuring the display settings:
64+
65+
.. autosummary::
66+
:toctree: generated
67+
68+
set_display
69+
6370

6471
Data Processing
6572
---------------

pygmt/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# Import modules to make the high-level GMT Python API
2626
from pygmt import datasets
2727
from pygmt.accessors import GMTDataArrayAccessor
28-
from pygmt.figure import Figure
28+
from pygmt.figure import Figure, set_display
2929
from pygmt.session_management import begin as _begin
3030
from pygmt.session_management import end as _end
3131
from pygmt.src import (

pygmt/figure.py

Lines changed: 91 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
from tempfile import TemporaryDirectory
77

88
try:
9-
from IPython.display import Image
10-
except ImportError:
11-
Image = None
9+
import IPython
10+
except KeyError:
11+
IPython = None # pylint: disable=invalid-name
12+
1213

1314
from pygmt.clib import Session
1415
from pygmt.exceptions import GMTError, GMTInvalidInput
@@ -25,6 +26,23 @@
2526
# This is needed for the sphinx-gallery scraper in pygmt/sphinx_gallery.py
2627
SHOWED_FIGURES = []
2728

29+
# Configurations for figure display
30+
SHOW_CONFIG = {
31+
"method": "external", # Open in an external viewer by default
32+
}
33+
34+
# Show figures in Jupyter notebooks if available
35+
if IPython:
36+
get_ipython = IPython.get_ipython() # pylint: disable=invalid-name
37+
if get_ipython and "IPKernelApp" in get_ipython.config: # Jupyter Notebook enabled
38+
SHOW_CONFIG["method"] = "notebook"
39+
40+
# Set environment variable PYGMT_USE_EXTERNAL_DISPLAY to 'false' to disable
41+
# external display. Use it when running the tests and building the docs to
42+
# avoid popping up windows.
43+
if os.environ.get("PYGMT_USE_EXTERNAL_DISPLAY", "true").lower() == "false":
44+
SHOW_CONFIG["method"] = "none"
45+
2846

2947
class Figure:
3048
"""
@@ -235,62 +253,72 @@ def savefig(
235253
if show:
236254
launch_external_viewer(fname)
237255

238-
def show(self, dpi=300, width=500, method="static"):
256+
def show(self, dpi=300, width=500, method=None):
239257
"""
240258
Display a preview of the figure.
241259
242-
Inserts the preview in the Jupyter notebook output. You will need to
243-
have IPython installed for this to work. You should have it if you are
244-
using the notebook.
260+
Inserts the preview in the Jupyter notebook output if available,
261+
otherwise opens it in the default viewer for your operating system
262+
(falls back to the default web browser).
263+
264+
:func:`pygmt.set_display` can select the default display method
265+
(**notebook**, **external**, or **none**).
266+
267+
The ``method`` parameter can also override the default display method
268+
for the current figure. Parameters ``dpi`` and ``width`` can be used
269+
to control the resolution and dimension of the figure in the notebook.
270+
271+
Note: The external viewer can be disabled by setting the
272+
PYGMT_USE_EXTERNAL_DISPLAY environment variable to **false**.
273+
This is useful when running unit tests and building the documentation
274+
in consoles without a Graphical User Interface.
245275
246-
If ``method='external'``, makes PDF preview instead and opens it in the
247-
default viewer for your operating system (falls back to the default web
248-
browser). Note that the external viewer does not block the current
249-
process, so this won't work in a script.
276+
Note that the external viewer does not block the current process.
250277
251278
Parameters
252279
----------
253280
dpi : int
254-
The image resolution (dots per inch).
281+
The image resolution (dots per inch) in Jupyter notebooks.
255282
width : int
256-
Width of the figure shown in the notebook in pixels. Ignored if
257-
``method='external'``.
283+
The image width (in pixels) in Jupyter notebooks.
258284
method : str
259-
How the figure will be displayed. Options are (1) ``'static'``: PNG
260-
preview (default); (2) ``'external'``: PDF preview in an external
261-
program.
285+
How the current figure will be displayed. Options are
262286
263-
Returns
264-
-------
265-
img : IPython.display.Image
266-
Only if ``method != 'external'``.
287+
- **external**: PDF preview in an external program [default]
288+
- **notebook**: PNG preview [default in Jupyter notebooks]
289+
- **none**: Disable image preview
267290
"""
268291
# Module level variable to know which figures had their show method
269292
# called. Needed for the sphinx-gallery scraper.
270293
SHOWED_FIGURES.append(self)
271294

272-
if method not in ["static", "external"]:
273-
raise GMTInvalidInput("Invalid show method '{}'.".format(method))
274-
if method == "external":
275-
pdf = self._preview(fmt="pdf", dpi=dpi, anti_alias=False, as_bytes=False)
276-
launch_external_viewer(pdf)
277-
img = None
278-
elif method == "static":
279-
png = self._preview(
280-
fmt="png", dpi=dpi, anti_alias=True, as_bytes=True, transparent=True
295+
# Set the display method
296+
if method is None:
297+
method = SHOW_CONFIG["method"]
298+
299+
if method not in ["external", "notebook", "none"]:
300+
raise GMTInvalidInput(
301+
(
302+
f"Invalid display method '{method}', "
303+
"should be either 'notebook', 'external', or 'none'."
304+
)
281305
)
282-
if Image is None:
306+
307+
if method in ["notebook", "none"]:
308+
if IPython is None:
283309
raise GMTError(
284-
" ".join(
285-
[
286-
"Cannot find IPython.",
287-
"Make sure you have it installed",
288-
"or use 'method=\"external\"' to open in an external viewer.",
289-
]
310+
(
311+
"Notebook display is selected, but IPython is not available. "
312+
"Make sure you have IPython installed, "
313+
"or run the script in a Jupyter notebook."
290314
)
291315
)
292-
img = Image(data=png, width=width)
293-
return img
316+
png = self._preview(fmt="png", dpi=dpi, anti_alias=True, as_bytes=True)
317+
IPython.display.display(IPython.display.Image(data=png, width=width))
318+
319+
if method == "external":
320+
pdf = self._preview(fmt="pdf", dpi=dpi, anti_alias=False, as_bytes=False)
321+
launch_external_viewer(pdf)
294322

295323
def shift_origin(self, xshift=None, yshift=None):
296324
"""
@@ -396,3 +424,27 @@ def _repr_html_(self):
396424
subplot,
397425
text,
398426
)
427+
428+
429+
def set_display(method=None):
430+
"""
431+
Set the display method.
432+
433+
Parameters
434+
----------
435+
method : str or None
436+
The method to display an image. Choose from:
437+
438+
- **external**: PDF preview in an external program [default]
439+
- **notebook**: PNG preview [default in Jupyter notebooks]
440+
- **none**: Disable image preview
441+
"""
442+
if method in ["notebook", "external", "none"]:
443+
SHOW_CONFIG["method"] = method
444+
elif method is not None:
445+
raise GMTInvalidInput(
446+
(
447+
f"Invalid display mode '{method}', "
448+
"should be either 'notebook', 'external' or 'none'."
449+
)
450+
)

pygmt/tests/test_figure.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import numpy as np
99
import numpy.testing as npt
1010
import pytest
11-
from pygmt import Figure
11+
from pygmt import Figure, set_display
1212
from pygmt.exceptions import GMTInvalidInput
1313

1414

@@ -142,8 +142,7 @@ def test_figure_show():
142142
"""
143143
fig = Figure()
144144
fig.basemap(region="10/70/-300/800", projection="X3i/5i", frame="af")
145-
img = fig.show(width=800)
146-
assert img.width == 800
145+
fig.show()
147146

148147

149148
@pytest.mark.mpl_image_compare
@@ -175,3 +174,12 @@ def test_figure_show_invalid_method():
175174
fig.basemap(region="10/70/-300/800", projection="X3i/5i", frame="af")
176175
with pytest.raises(GMTInvalidInput):
177176
fig.show(method="test")
177+
178+
179+
def test_figure_set_display_invalid():
180+
"""
181+
Test to check if an error is raised when an invalid method is passed to
182+
set_display.
183+
"""
184+
with pytest.raises(GMTInvalidInput):
185+
set_display(method="invalid")

0 commit comments

Comments
 (0)