Skip to content

Instantly share code, notes, and snippets.

@qxcv
Last active August 29, 2015 14:18
Show Gist options
  • Save qxcv/cf4650c076c64d9dbd0c to your computer and use it in GitHub Desktop.
Save qxcv/cf4650c076c64d9dbd0c to your computer and use it in GitHub Desktop.
tikzmagic.py for Py3K
# -*- coding: utf-8 -*-
"""
=========
tikzmagic
=========
Magics for generating figures with TikZ.
.. note::
``TikZ`` and ``LaTeX`` need to be installed separately.
Downloaded from https://gist.github.com/DavidPowell/84655e9a87fdfdaf2717
Adapted for Py3K
Usage
=====
``%%tikz``
{TIKZ_DOC}
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2013 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
import sys
import tempfile
from glob import glob
from os import chdir, getcwd
from subprocess import call
from shutil import rmtree, copy
from xml.dom import minidom
from IPython.core.displaypub import publish_display_data
from IPython.core.magic import (Magics, magics_class, line_magic,
line_cell_magic, needs_local_scope)
from IPython.testing.skipdoctest import skip_doctest
from IPython.core.magic_arguments import (
argument, magic_arguments, parse_argstring
)
from IPython.utils.py3compat import unicode_to_str
_mimetypes = {'png' : 'image/png',
'svg' : 'image/svg+xml',
'jpg' : 'image/jpeg',
'jpeg': 'image/jpeg'}
@magics_class
class TikzMagics(Magics):
"""A set of magics useful for creating figures with TikZ.
"""
def __init__(self, shell):
"""
Parameters
----------
shell : IPython shell
"""
super(TikzMagics, self).__init__(shell)
self._plot_format = 'png'
# Allow publish_display_data to be overridden for
# testing purposes.
self._publish_display_data = publish_display_data
def _fix_gnuplot_svg_size(self, image, size=None):
"""
GnuPlot SVGs do not have height/width attributes. Set
these to be the same as the viewBox, so that the browser
scales the image correctly.
Parameters
----------
image : str
SVG data.
size : tuple of int
Image width, height.
"""
(svg,) = minidom.parseString(image).getElementsByTagName('svg')
viewbox = svg.getAttribute('viewBox').split(' ')
if size is not None:
width, height = size
else:
width, height = viewbox[2:]
svg.setAttribute('width', '%dpx' % width)
svg.setAttribute('height', '%dpx' % height)
return svg.toxml()
def _run_latex(self, code, dir):
f = open(dir + '/tikz.tex', 'w')
f.write(code)
f.close()
current_dir = getcwd()
chdir(dir)
ret_log = False
log = None
try:
retcode = call("pdflatex -shell-escape tikz.tex", shell=True)
if retcode != 0:
sys.stderr.write("LaTeX terminated with signal {}\n".format(-retcode))
ret_log = True
except OSError as e:
sys.stderr.write("LaTeX execution failed: {}\n".format(e))
ret_log = True
# in case of error return LaTeX log
if ret_log:
try:
f = open('tikz.log', 'r')
log = f.read()
f.close()
except IOError:
sys.stderr.write("No log file generated.\n")
chdir(current_dir)
return log
def _convert_pdf_to_svg(self, dir):
current_dir = getcwd()
chdir(dir)
try:
retcode = call("pdf2svg tikz.pdf tikz.svg", shell=True)
if retcode != 0:
sys.stderr.write("pdf2svg terminated with signal {}\n".format(-retcode))
except OSError as e:
sys.stderr.write("pdf2svg execution failed: {}\n".format(e))
chdir(current_dir)
def _convert_png_to_jpg(self, dir):
current_dir = getcwd()
chdir(dir)
try:
retcode = call("convert tikz.png -quality 100 -background white -flatten tikz.jpg", shell=True)
if retcode != 0:
sys.stderr.write("convert terminated with signal {}\n".format(-retcode))
except OSError as e:
sys.stderr.write("convert execution failed: {}\n".format(e))
chdir(current_dir)
@skip_doctest
@magic_arguments()
@argument(
'-sc', '--scale', action='store',
help='Scaling factor of plots. Default is "--scale 1".'
)
@argument(
'-s', '--size', action='store',
help='Pixel size of plots, "width,height". Default is "-s 400,240".'
)
@argument(
'-f', '--format', action='store',
help='Plot format (png, svg or jpg).'
)
@argument(
'-S', '--save', action='store',
help='Save a copy to "filename".'
)
@needs_local_scope
@argument(
'code',
nargs='*',
)
@line_cell_magic
def tikz(self, line, cell=None, local_ns=None):
'''
Run TikZ code in LaTeX and plot result.
In [9]: %tikz \draw (0,0) rectangle (1,1);
As a cell, this will run a block of TikZ code::
In [10]: %%tikz
....: \draw (0,0) rectangle (1,1);
In the notebook, plots are published as the output of the cell.
The size and format of output plots can be specified::
In [18]: %%tikz -s 600,800 -f svg --scale 2
...: \draw (0,0) rectangle (1,1);
...: \filldraw (0.5,0.5) circle (.1);
'''
args = parse_argstring(self.tikz, line)
# arguments 'code' in line are prepended to the cell lines
if cell is None:
code = ''
return_output = True
else:
code = cell
return_output = False
code = ' '.join(args.code) + code
# if there is no local namespace then default to an empty dict
if local_ns is None:
local_ns = {}
# generate plots in a temporary directory
plot_dir = tempfile.mkdtemp().replace('\\', '/')
#print(plot_dir)
if args.scale is not None:
scale = args.scale
else:
scale = '1'
if args.size is not None:
size = args.size
else:
size = '400,240'
width, height = size.split(',')
if args.format is not None:
plot_format = args.format
else:
plot_format = 'png'
add_params = ""
if plot_format == 'png' or plot_format == 'jpg' or plot_format == 'jpeg':
add_params += "density=300,"
#\\documentclass[convert={%(add_params)ssize=%(width)sx%(height)s,outext=.%(plot_format)s},border=0pt]{standalone}
pre_tex = '''
\\documentclass[convert={%(add_params)ssize=%(width)sx%(height)s,outext=.png},border=0pt]{standalone}
\\usepackage{tikz}
\\usetikzlibrary{intersections}
\\usetikzlibrary{calc}
\\begin{document}
\\begin{tikzpicture}[scale=%(scale)s]
''' % locals()
post_tex = '''
\\end{tikzpicture}
\\end{document}
'''
code = ' '.join((pre_tex, code, post_tex))
text_output = self._run_latex(code, plot_dir)
if plot_format == 'jpg' or plot_format == 'jpeg':
self._convert_png_to_jpg(plot_dir)
elif plot_format == 'svg':
self._convert_pdf_to_svg(plot_dir)
key = 'TikZMagic.Tikz'
display_data = []
# Publish text output
if text_output:
display_data.append((key, {'text/plain': text_output}))
image_filename = "%s/tikz.%s" % (plot_dir, plot_format)
# Publish image
try:
image = open(image_filename, 'rb').read()
plot_mime_type = _mimetypes.get(plot_format, 'image/%s' % (plot_format))
width, height = [int(s) for s in size.split(',')]
if plot_format == 'svg':
image = self._fix_gnuplot_svg_size(image, size=(width, height))
display_data.append((key, {plot_mime_type: image}))
except IOError:
sys.stderr.write("No image generated.")
# Copy output file if requested
if args.save is not None:
copy(image_filename, args.save)
rmtree(plot_dir)
for source, data in display_data:
# isolate data in an iframe, to prevent clashing glyph declarations in SVG
self._publish_display_data(source, data, metadata={'isolated' : 'true'})
__doc__ = __doc__.format(
TIKZ_DOC = ' '*8 + TikzMagics.tikz.__doc__,
)
def load_ipython_extension(ip):
"""Load the extension in IPython."""
ip.register_magics(TikzMagics)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment