"""
.. testsetup::
from thompson.drawing import *
"""
from .plot import plot as plot_svg
from .forest import forest_code
from .flow_graph import flow_graph_code
from subprocess import call, check_call
from tempfile import mkstemp, mkdtemp
from io import StringIO
import os
import shutil
import sys
__all__ = ["display_file", "forest", "forest_code", "plot", "flow"]
[docs]def display_file(filepath, format=None, scale=1.0, verbose=False):
"""Display the image at *filepath* to the user. This function behaves differently, depending on whether or not we execute it in a Jupyter notebook.
If we are **not** in a notebook, this function opens the given image using the operating system's default application for that file.
If we **are** in a notebook, this returns an IPython object corresponding to the given image.
If this object is the last expression of a notebook code block, the image will be displayed.
The image is handled differently depending on its *format*, which must be specified when the function is called in a notebook.
Only the formats `'svg'` and `'pdf'` are accepted.
.. note::
PDF files are displayed in a notebook as a rendered PNG. The conversion is made using the ``convert`` program provided by `ImageMagick <http://www.imagemagick.org/script/index.php>`_, which must be available on the `PATH <https://en.wikipedia.org/wiki/PATH_(variable)>`_ for this function to work with PDF files. The optional *scale* argument can be used to control how big the rendered PNG is.
.. todo:: use a Python binding to ImageMagick rather than just shelling out?
"""
if in_ipynb():
print(filepath)
from IPython.display import Image, SVG
format = format.lower()
if format == 'svg':
return SVG(filename=filepath)
elif format == 'png':
return Image(filename=filepath)
elif format == 'pdf':
png_file = filepath[:-4] + '.png'
check_call(['convert',
'-density', str(100*scale),
filepath,
'-quality','90',
png_file
])
return Image(filename=png_file)
else:
raise NotImplementedError( "Don't know how to display format {}".format(format) )
"""From http://stackoverflow.com/a/435669.
Opens the given file with the OS's default application."""
if sys.platform.startswith('darwin'):
call(('open', filepath))
elif os.name == 'nt':
os.startfile(filepath)
elif os.name == 'posix':
call(('xdg-open', filepath))
else:
raise NotImplementedError
return filepath
[docs]def plot(*auts, dest=None, display=True, diagonal=False, endpoints=False, scale=1):
r"""Plots the given :class:`automorphism <thompson.automorphism.Automorphism>` s as a function :math:`[0, 1] \to [0, 1]`. The image is rendered as an SVG using `svgwrite`.
:param str dest: the destination filepath to save the SVG to. If `None`, the SVG is saved to a temporary file location.
:param bool display: if True, automatically call :func:`display_file` to display the SVG to the user. Otherwise does nothing.
:param bool diagonal: if True, draws the diagonal to highlight fixed points of *aut*.
:param bool endpoints: if True, open and closed endpoints are drawn on the plot to emphasise any discontinuities that *aut* has.
:returns: the filepath where the SVG was saved. If *dest* is `None` this is a temporary file; otherwise the return value is simply *dest*.
"""
if dest is None:
#Write to a temporary file
dest = mkstemp()[1]
plot_svg(*auts, filename=dest, diagonal=diagonal, endpoints=endpoints, display_scale=scale)
if display:
return display_file(dest, format='svg')
return dest
[docs]def forest(aut, jobname=None, display=True, scale=1, **kwargs):
r"""Draws the given :class:`~thompson.automorphism.Automorphism` as a forest-pair diagram.
The image is rendered as a PDF using the `tikz` graph drawing libraries and `lualatex`.
:param str jobname: the destination filepath to save the PDF to. A file extension should **not** be provided. If `None`, the PDF is saved to a temporary file location.
:param bool display: if True, automatically call :func:`display_file` to display the PDF to the user. Otherwise does nothing.
:param float scale: In a Jupyter notebook, this controls the size of the rendered PNG image. See the note in :func:`display_file`.
:returns: the filepath where the PDF was saved. If *dest* is `None` this is a temporary file; otherwise the return value is simply `jobname + '.pdf'`.
.. note::
The graph drawing is done via a TikZ and LaTeX. The source file is compiled using `lualatex`, which must be available on the `PATH <https://en.wikipedia.org/wiki/PATH_(variable)>`_ for this function to work.
"""
outdir = mkdtemp()
specific_location = jobname is not None
if not specific_location:
jobname = 'forest_diagram'
else:
destdir = os.path.dirname(jobname)
jobname = os.path.basename(jobname)
tex = os.path.join(outdir, jobname + '.tex')
pdf = os.path.join(outdir, jobname + '.pdf')
with open(tex, 'wt') as texfile:
texfile.write(forest_code(aut, **kwargs))
check_call(['lualatex',
'-output-directory=' + outdir,
'-interaction=batchmode',
'--halt-on-error',
tex
])
if specific_location:
new_pdf = os.path.join(destdir, jobname + '.pdf')
shutil.copy(pdf, new_pdf)
pdf = new_pdf
if display:
return display_file(pdf, format='pdf', scale=scale)
return pdf
def forest_plain(aut, **overrides):
"""The same as :func:`forest_code`, but this is a custom version for my own use. It passes in arguments to produce minimal TikZ code only."""
options = dict(
include_styles = False,
standalone = False,
draw_revealing = False,
LTR = True
)
options.update(overrides)
return forest_code(aut, **options)
def in_ipynb():
"""From a comment on Stack overflow:
http://stackoverflow.com/questions/15411967/how-can-i-check-if-code-is-executed-in-the-ipython-notebook#comment53916407_24937408"""
try:
ipy = get_ipython()
except NameError:
return False
else:
return type(ipy).__name__ == 'ZMQInteractiveShell'
[docs]def flow(aut, jobname=None, display=True):
if jobname is None:
outdir = mkdtemp()
jobname = 'flow_graph'
else:
outdir = os.path.basename(jobname)
dot = os.path.join(outdir, jobname + ".dot")
png = os.path.join(outdir, jobname + ".png")
flow_graph_code(aut, dot)
check_call([
'dot',
'-T', 'png',
'-o', png,
dot
])
if display:
return display_file(png, format='png')
return png