Skip to content

Commit

Permalink
Redesign the display mechanism
Browse files Browse the repository at this point in the history
This is a new design of the PyGMT display mechanism, following the
discussions in #269, with some changes.

**It's working now but not finished yet.**
  • Loading branch information
seisman committed Oct 1, 2020
1 parent c191d3e commit 5c5499f
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test:
@echo ""
@cd $(TESTDIR); python -c "import $(PROJECT); $(PROJECT).show_versions()"
@echo ""
cd $(TESTDIR); pytest $(PYTEST_ARGS) $(PROJECT)
cd $(TESTDIR); PYGMT_DISABLE_EXTERNAL_DISPLAY="true" pytest $(PYTEST_ARGS) $(PROJECT)
cp $(TESTDIR)/coverage.xml .
cp -r $(TESTDIR)/htmlcov .
rm -r $(TESTDIR)
Expand Down
3 changes: 1 addition & 2 deletions doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXBUILD = PYGMT_DISABLE_EXTERNAL_DISPLAY="true" sphinx-build
SPHINXAUTOGEN = sphinx-autogen
BUILDDIR = _build

Expand Down Expand Up @@ -45,7 +45,6 @@ api:
@echo
$(SPHINXAUTOGEN) -i -t _templates -o api/generated api/*.rst


linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
Expand Down
7 changes: 7 additions & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ Saving and displaying the figure:
Figure.show
Figure.psconvert

Utilities for setting display options when working in Jupyter notebooks:

.. autosummary::
:toctree: generated

set_display


Data Processing
---------------
Expand Down
2 changes: 1 addition & 1 deletion pygmt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

# Import modules to make the high-level GMT Python API
from .session_management import begin as _begin, end as _end
from .figure import Figure
from .figure import Figure, set_display
from .filtering import blockmedian
from .gridding import surface
from .sampling import grdtrack
Expand Down
129 changes: 77 additions & 52 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@
Define the Figure class that handles all plotting.
"""
import os
import sys
from tempfile import TemporaryDirectory
import base64

try:
from IPython.display import Image
except ImportError:
Image = None

from .clib import Session
from .base_plotting import BasePlotting
from .exceptions import GMTError, GMTInvalidInput
from .exceptions import GMTInvalidInput
from .helpers import (
build_arg_string,
fmt_docstring,
Expand All @@ -27,6 +24,31 @@
# This is needed for the sphinx-gallery scraper in pygmt/sphinx_gallery.py
SHOWED_FIGURES = []

# Configuration options for Jupyter notebook support
SHOW_CONFIG = {
"external": True, # Open in an external viewer
"notebook": True, # Notebook display
"dpi": 200, # default DPI
}

# Determine the default display mode
try:
IPython = sys.modules["IPython"]
if "IPKernelApp" in IPython.get_ipython().config: # Jupyter Notebook enabled
SHOW_CONFIG["notebook"] = True
SHOW_CONFIG["external"] = False
else:
SHOW_CONFIG["notebook"] = False
SHOW_CONFIG["external"] = True
except KeyError:
SHOW_CONFIG["notebook"] = False
SHOW_CONFIG["external"] = True

# If the environment variable is set to "true", disable the external viewer.
# Use this for running the tests and building the docs to avoid pop up windows.
if os.environ.get("PYGMT_DISABLE_EXTERNAL_DISPLAY", "default").lower() == "true":
SHOW_CONFIG["external"] = False


class Figure(BasePlotting):
"""
Expand Down Expand Up @@ -57,7 +79,7 @@ class Figure(BasePlotting):
>>> fig = Figure()
>>> fig.basemap(region='JP', projection="M3i", frame=True)
>>> # The fig.region attribute shows the WESN bounding box for the figure
>>> print(', '.join('{:.2f}'.format(i) for i in fig.region))
>>> print(', '.join('{:.2f}'.format(i) for i in fig.region))
122.94, 145.82, 20.53, 45.52
"""
Expand Down Expand Up @@ -235,63 +257,38 @@ def savefig(
if show:
launch_external_viewer(fname)

def show(self, dpi=300, width=500, method="static"):
def show(self):
"""
Display a preview of the figure.
Inserts the preview in the Jupyter notebook output. You will need to
have IPython installed for this to work. You should have it if you are
using the notebook.
If ``method='external'``, makes PDF preview instead and opens it in the
default viewer for your operating system (falls back to the default web
browser). Note that the external viewer does not block the current
process, so this won't work in a script.
Inserts the preview in the Jupyter notebook output, otherwise opens it
in the default viewer for your operating system (falls back to the
default web browser). Note that the external viewer does not block the
current process, so this won't work in a script.
Parameters
----------
dpi : int
The image resolution (dots per inch).
width : int
Width of the figure shown in the notebook in pixels. Ignored if
``method='external'``.
method : str
How the figure will be displayed. Options are (1) ``'static'``: PNG
preview (default); (2) ``'external'``: PDF preview in an external
program.
:func:`pygmt.set_display` can select the default display mode (either
"notebook" or "external").
Returns
-------
img : IPython.display.Image
Only if ``method != 'external'``.
The external viewer can also be disabled by setting the
``PYGMT_DISABLE_EXTERNAL_DISPLAY`` environment variable to ``true``.
This is mainly used for running our tests and building the
documentation.
"""
# Module level variable to know which figures had their show method
# called. Needed for the sphinx-gallery scraper.
SHOWED_FIGURES.append(self)

if method not in ["static", "external"]:
raise GMTInvalidInput("Invalid show method '{}'.".format(method))
if method == "external":
pdf = self._preview(fmt="pdf", dpi=dpi, anti_alias=False, as_bytes=False)
launch_external_viewer(pdf)
img = None
elif method == "static":
png = self._preview(
fmt="png", dpi=dpi, anti_alias=True, as_bytes=True, transparent=True
if SHOW_CONFIG["notebook"]:
png = self._repr_png_()
if IPython is not None:
IPython.display.display(IPython.display.Image(data=png))

if SHOW_CONFIG["external"]:
pdf = self._preview(
fmt="pdf", dpi=SHOW_CONFIG["dpi"], anti_alias=False, as_bytes=False
)
if Image is None:
raise GMTError(
" ".join(
[
"Cannot find IPython.",
"Make sure you have it installed",
"or use 'external=True' to open in an external viewer.",
]
)
)
img = Image(data=png, width=width)
return img
launch_external_viewer(pdf)

def shift_origin(self, xshift=None, yshift=None):
"""
Expand Down Expand Up @@ -362,7 +359,9 @@ def _repr_png_(self):
Show a PNG preview if the object is returned in an interactive shell.
For the Jupyter notebook or IPython Qt console.
"""
png = self._preview(fmt="png", dpi=70, anti_alias=True, as_bytes=True)
png = self._preview(
fmt="png", dpi=SHOW_CONFIG["dpi"], anti_alias=True, as_bytes=True
)
return png

def _repr_html_(self):
Expand All @@ -374,3 +373,29 @@ def _repr_html_(self):
base64_png = base64.encodebytes(raw_png)
html = '<img src="data:image/png;base64,{image}" width="{width}px">'
return html.format(image=base64_png.decode("utf-8"), width=500)


def set_display(mode, dpi=200):
"""Set the display mode.
Parameters
----------
mode : str
Choose from "notebook" (for inline display in Jupyter notebook)
or "external" (for displaying preview using the external viewer).
dpi : int
Set the default DPI (dots-per-inch) used for PNG image previews that
are inserted into the notebook.
"""
if mode == "notebook":
SHOW_CONFIG["notebook"] = True
SHOW_CONFIG["external"] = False
elif mode == "external":
SHOW_CONFIG["notebook"] = False
SHOW_CONFIG["external"] = True
else:
raise GMTInvalidInput(
f'Invalid display mode {mode}, should be either "notebook" or "external".'
)
SHOW_CONFIG["dpi"] = dpi
8 changes: 6 additions & 2 deletions pygmt/helpers/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Utilities and common tasks for wrapping the GMT modules.
"""
import os
import sys
import shutil
import subprocess
Expand Down Expand Up @@ -199,11 +200,14 @@ def launch_external_viewer(fname):
# with noise
run_args = dict(stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

platform = sys.platform
# Open the file with the default viewer.
# Fall back to the browser if can't recognize the operating system.
if sys.platform.startswith("linux") and shutil.which("xdg-open"):
if platform.startswith("linux") and shutil.which("xdg-open"):
subprocess.run(["xdg-open", fname], check=False, **run_args)
elif sys.platform == "darwin": # Darwin is macOS
elif platform == "darwin": # Darwin is macOS
subprocess.run(["open", fname], check=False, **run_args)
elif platform == "win32":
os.startfile(fname) # pylint: disable=no-member
else:
webbrowser.open_new_tab("file://{}".format(fname))
3 changes: 1 addition & 2 deletions pygmt/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ def test_figure_show():
"Test that show creates the correct file name and deletes the temp dir"
fig = Figure()
fig.basemap(region="10/70/-300/800", projection="X3i/5i", frame="af")
img = fig.show(width=800)
assert img.width == 800
fig.show()


@pytest.mark.mpl_image_compare
Expand Down

0 comments on commit 5c5499f

Please sign in to comment.