--- a
+++ b/matplotlib2tikz.py
@@ -0,0 +1,1614 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2010--2014 Nico Schlömer
+#
+# This file is part of matplotlib2tikz.
+#
+# matplotlib2tikz is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# matplotlib2tikz is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# matplotlib2tikz.  If not, see <http://www.gnu.org/licenses/>.
+#
+'''Script to convert Matplotlib generated figures into TikZ/Pgfplots figures.
+'''
+
+# imported modules
+import matplotlib as mpl
+import numpy as np
+import os
+import warnings
+
+# meta info
+__author__ = 'Nico Schlömer'
+__email__ = 'nico.schloemer@gmail.com'
+__copyright__ = 'Copyright (c) 2010--2014, %s <%s>' % (__author__, __email__)
+__credits__ = []
+__license__ = 'GNU Lesser General Public License (LGPL), Version 3'
+__version__ = '0.1.0'
+__maintainer__ = 'Nico Schlömer'
+__status__ = 'Development'
+
+
+def save(filepath,
+         figure='gcf',
+         encoding=None,
+         figurewidth=None,
+         figureheight=None,
+         textsize=10.0,
+         tex_relative_path_to_data=None,
+         strict=False,
+         draw_rectangles=False,
+         wrap=True,
+         extra=None,
+         show_info=True
+         ):
+    '''Main function. Here, the recursion into the image starts and the
+    contents are picked up. The actual file gets written in this routine.
+
+    :param figure: either a Figure object or 'gcf' (default).
+
+    :param filepath: The file to which the TikZ output will be written.
+    :type filepath: str
+
+    :param encoding: Which encoding to use for the file.
+
+    :param figurewidth: If not ``None``, this will be used as figure width
+                        within the TikZ/Pgfplot output. If ``figureheight``
+                        is not given, ``matplotlib2tikz`` will try to preserve
+                        the original width/height ratio.
+                        Note that ``figurewidth`` can be a string literal,
+                        such as ``'\\figurewidth'``.
+    :type figurewidth: str
+
+    :param figureheight: If not ``None``, this will be used as figure height
+                         within the TikZ/Pgfplot output. If ``figurewidth`` is
+                         not given, ``matplotlib2tikz`` will try to preserve
+                         the original width/height ratio.  Note that
+                         ``figurewidth`` can be a string literal, such as
+                         ``'\\figureheight'``.
+    :type figureheight: str
+
+    :param textsize: The text size (in pt) that the target latex document is
+                     using.  Default is 10.0.
+    :type textsize: float
+
+    :param tex_relative_path_to_data: In some cases, the TikZ file will have to
+                                      refer to another file, e.g., a PNG for
+                                      image plots. When ``\\input`` into a
+                                      regular LaTeX document, the additional
+                                      file is looked for in a folder relative
+                                      to the LaTeX file, not the TikZ file.
+                                      This arguments optionally sets the
+                                      relative path from the LaTeX file to the
+                                      data.
+    :type tex_relative_path_to_data: str
+
+    :param strict: Whether or not to strictly stick to matplotlib's appearance.
+                   This influences, for example, whether tick marks are set
+                   exactly as in the matplotlib plot, or if TikZ/Pgfplots
+                   can decide where to put the ticks.
+    :type strict: bool
+
+    :param draw_rectangles: Whether or not to draw Rectangle objects.
+                            You normally don't want that as legend, axes, and
+                            other entities which are natively taken care of by
+                            Pgfplots are represented as rectangles in
+                            matplotlib. Some plot types (such as bar plots)
+                            cannot otherwise be represented though.
+                            Don't expect working or clean output when using
+                            this option.
+    :type draw_rectangles: bool
+
+    :param wrap: Whether ``'\\begin{tikzpicture}'`` and
+                 ``'\\end{tikzpicture}'`` will be written. One might need to
+                 provide custom arguments to the environment (eg. scale= etc.).
+                 Default is ``True``.
+    :type wrap: bool
+
+    :param extra: Extra axis options to be passed (as a set) to pgfplots.
+                  Default is ``None``.
+    :type extra: a set of strings for the pfgplots axes.
+
+    :returns: None
+
+
+    The following optional attributes of matplotlib's objects are recognized
+    and handled:
+
+     - axes.Axes._matplotlib2tikz_anchors
+       This attribute can be set to a list of ((x,y), anchor_name) tuples.
+       Invisible nodes at the respective location will be created which  can be
+       referenced from outside the axis environment.
+    '''
+    # not as default value because gcf() would be evaluated at import time
+    if figure == 'gcf':
+        figure = mpl.pyplot.gcf()
+    data = {}
+    data['fwidth'] = figurewidth
+    data['fheight'] = figureheight
+    data['rel data path'] = tex_relative_path_to_data
+    data['output dir'] = os.path.dirname(filepath)
+    data['strict'] = strict
+    data['draw rectangles'] = draw_rectangles
+    data['tikz libs'] = set()
+    data['pgfplots libs'] = set()
+    data['font size'] = textsize
+    data['custom colors'] = {}
+    if extra:
+        data['extra axis options'] = extra.copy()
+    else:
+        data['extra axis options'] = set()
+
+    # open file
+    import codecs
+    file_handle = codecs.open(filepath, 'w', encoding)
+
+    if show_info:
+        print('file encoding: {0}'.format(file_handle.encoding))
+
+    # gather the file content
+    data, content = _handle_children(data, figure)
+
+    disclaimer = (
+        'This file was created by matplotlib v%s.\n'
+        '%s\n'
+        'All rights reserved.\n') \
+        % (__version__, __copyright__)
+
+    if show_info:
+        disclaimer += \
+            '\nThe lastest updates can be retrieved from\n\n' \
+            + 'https://github.com/nschloe/matplotlib2tikz\n\n' \
+            + 'where you can also submit bug reports and leave' \
+            + 'comments.\n'
+
+    # write disclaimer to the file header
+    file_handle.write(_tex_comment(disclaimer))
+
+    # write the contents
+    if wrap:
+        file_handle.write('\\begin{tikzpicture}\n\n')
+
+    coldefs = _get_color_definitions(data)
+    if coldefs:
+        file_handle.write('\n'.join(coldefs))
+        file_handle.write('\n\n')
+
+    file_handle.write(''.join(content))
+    if wrap:
+        file_handle.write('\\end{tikzpicture}')
+
+    # close file
+    file_handle.close()
+
+    # print message about necessary pgfplot libs to command line
+    if show_info:
+        _print_pgfplot_libs_message(data)
+    return
+
+
+def _tex_comment(comment):
+    '''Prepends each line in string with the LaTeX comment key, '%'.
+    '''
+    return '% ' + str.replace(comment, '\n', '\n% ') + "\n"
+
+
+def _print_tree(obj, indent=''):
+    '''Recursively prints the tree structure of the matplotlib object.
+    '''
+    print(indent, type(obj))
+    for child in obj.get_children():
+        _print_tree(child, indent + '   ')
+    return
+
+
+def _get_color_definitions(data):
+    '''Returns the list of custom color definitions for the TikZ file.
+    '''
+    definitions = []
+    for name, rgb in data['custom colors'].items():
+        definitions.append('\\definecolor{%s}{rgb}{%.15g,%.15g,%.15g}'
+                           % (name, rgb[0], rgb[1], rgb[2])
+                           )
+    return definitions
+
+
+#def _parse_text(text):
+    #'''Parses input text for LaTeX expressions and escaptes them if
+    #necessary.'''
+    #replace_list = ['_', '$', '\\', '%']
+    #return
+
+
+def _draw_axes(data, obj):
+    '''Returns the Pgfplots code for an axis environment.
+    '''
+    content = []
+
+    # Are we dealing with an axis that hosts a colorbar?
+    # Skip then.
+    # TODO instead of testing here, rather blacklist the colorbar axis
+    #      plots as soon as they have been found, e.g., by
+    #      _find_associated_colorbar()
+    if _extract_colorbar(obj):
+        return
+
+    # instantiation
+    nsubplots = 1
+    subplot_index = 0
+    is_subplot = False
+
+    if isinstance(obj, mpl.axes.Subplot):
+        geom = obj.get_geometry()
+        nsubplots = geom[0]*geom[1]
+        if nsubplots > 1:
+            is_subplot = True
+            subplot_index = geom[2]
+            if subplot_index == 1:
+                content.append(
+                    '\\begin{groupplot}[group style='
+                    '{group size=%.d by %.d}]\n' % (geom[1], geom[0])
+                    )
+                data['pgfplots libs'].add('groupplots')
+
+    axis_options = []
+
+    # check if axes need to be displayed at all
+    if not obj.axison:
+        axis_options.append('hide x axis')
+        axis_options.append('hide y axis')
+
+    # get plot title
+    title = obj.get_title()
+    if title:
+        axis_options.append('title={' + title + '}')
+
+    # get axes titles
+    xlabel = obj.get_xlabel()
+    if xlabel:
+        axis_options.append('xlabel={' + xlabel + '}')
+    ylabel = obj.get_ylabel()
+    if ylabel:
+        axis_options.append('ylabel={' + ylabel + '}')
+
+    # Axes limits.
+    # Sort the limits so make sure that the smaller of the two is actually
+    # *min.
+    xlim = sorted(list(obj.get_xlim()))
+    axis_options.append('xmin=%.15g' % xlim[0]
+                        + ', xmax=%.15g' % xlim[1]
+                        )
+    ylim = sorted(list(obj.get_ylim()))
+    axis_options.append('ymin=%.15g' % ylim[0]
+                        + ', ymax=%.15g' % ylim[1]
+                        )
+
+    # axes scaling
+    xscale = obj.get_xscale()
+    yscale = obj.get_yscale()
+    if xscale == 'log' and yscale == 'log':
+        env = 'loglogaxis'
+    elif xscale == 'log':
+        env = 'semilogxaxis'
+    elif yscale == 'log':
+        env = 'semilogyaxis'
+    else:
+        env = 'axis'
+
+    if not obj.get_axisbelow():
+        axis_options.append('axis on top')
+
+    # aspect ratio, plot width/height
+    aspect = obj.get_aspect()
+    if aspect == 'auto' or aspect == 'normal':
+        aspect_num = None  # just take the given width/height values
+    elif aspect == 'equal':
+        aspect_num = 1.0
+    else:
+        try:
+            aspect_num = float(aspect)
+        except ValueError:
+            print('Aspect ratio not a number?!')
+
+    if data['fwidth'] and data['fheight']:
+        # width and height overwrite aspect ratio
+        axis_options.append('width='+data['fwidth'])
+        axis_options.append('height='+data['fheight'])
+    elif data['fwidth']:
+        # only data['fwidth'] given. calculate height by the aspect ratio
+        axis_options.append('width='+data['fwidth'])
+        if aspect_num:
+            alpha = aspect_num * (ylim[1]-ylim[0])/(xlim[1]-xlim[0])
+            if alpha != 1.0:
+                # Concatenate the literals, as data['fwidth'] could as well be
+                # a LaTeX length variable such as \figurewidth.
+                data['fheight'] = str(alpha) + '*' + data['fwidth']
+            else:
+                data['fheight'] = data['fwidth']
+            axis_options.append('height='+data['fheight'])
+    elif data['fheight']:
+        # only data['fheight'] given. calculate width by the aspect ratio
+        axis_options.append('height='+data['fheight'])
+        if aspect_num:
+            alpha = aspect_num * (ylim[1]-ylim[0])/(xlim[1]-xlim[0])
+            if alpha != 1.0:
+                # Concatenate the literals, as data['fheight'] could as well be
+                # a LaTeX length variable such as \figureheight.
+                data['fwidth'] = str(1.0/alpha) + '*' + data['fheight']
+            else:
+                data['fwidth'] = data['fheight']
+            axis_options.append('width='+data['fwidth'])
+    else:
+        if aspect_num:
+            print('Non-automatic aspect ratio demanded, but neither height '
+                  'nor width of the plot are given. Discard aspect ratio.')
+
+    # get ticks
+    axis_options.extend(_get_ticks(data, 'x', obj.get_xticks(),
+                                         obj.get_xticklabels()))
+    axis_options.extend(_get_ticks(data, 'y', obj.get_yticks(),
+                                         obj.get_yticklabels()))
+
+    # Don't use get_{x,y}gridlines for gridlines; see discussion on
+    # <http://sourceforge.net/mailarchive/forum.php?thread_name=AANLkTima87pQkZmJhU2oNb8uxD2dfeV-Pa-uXWAFc2-v%40mail.gmail.com&forum_name=matplotlib-users>
+    # Coordinate of the lines are entirely meaningless, but styles (colors,...
+    # are respected.
+    if obj.xaxis._gridOnMajor:
+        axis_options.append('xmajorgrids')
+    elif obj.xaxis._gridOnMinor:
+        axis_options.append('xminorgrids')
+
+    if obj.yaxis._gridOnMajor:
+        axis_options.append('ymajorgrids')
+    elif obj.yaxis._gridOnMinor:
+        axis_options.append('yminorgrids')
+
+    # find color bar
+    colorbar = _find_associated_colorbar(obj)
+    if colorbar:
+        colorbar_styles = []
+
+        orientation = colorbar.orientation
+        limits = colorbar.get_clim()
+        if orientation == 'horizontal':
+            axis_options.append('colorbar horizontal')
+
+            colorbar_ticks = colorbar.ax.get_xticks()
+            axis_limits = colorbar.ax.get_xlim()
+
+            # In matplotlib, the colorbar color limits are determined by
+            # get_clim(), and the tick positions are as usual with respect to
+            # {x,y}lim. In Pgfplots, however, they are mixed together.
+            # Hence, scale the tick positions just like {x,y}lim are scaled
+            # to clim.
+            colorbar_ticks = (colorbar_ticks - axis_limits[0]) \
+                / (axis_limits[1] - axis_limits[0]) \
+                * (limits[1] - limits[0]) \
+                + limits[0]
+
+            # Getting the labels via get_* might not actually be suitable:
+            # they might not reflect the current state.
+            # http://sourceforge.net/mailarchive/message.php?msg_name=AANLkTikdNFwSAhMIlLjnd4Ai8-XIdJYGmrwq6PrHkbgi%40mail.gmail.com
+            colorbar_ticklabels = colorbar.ax.get_xticklabels()
+            colorbar_styles.extend(_get_ticks(data, 'x', colorbar_ticks,
+                                                    colorbar_ticklabels))
+
+        elif orientation == 'vertical':
+            axis_options.append('colorbar')
+            colorbar_ticks = colorbar.ax.get_yticks()
+            axis_limits = colorbar.ax.get_ylim()
+
+            # In matplotlib, the colorbar color limits are determined by
+            # get_clim(), and the tick positions are as usual with respect to
+            # {x,y}lim. In Pgfplots, however, they are mixed together.
+            # Hence, scale the tick positions just like {x,y}lim are scaled
+            # to clim.
+            colorbar_ticks = (colorbar_ticks - axis_limits[0]) \
+                / (axis_limits[1] - axis_limits[0]) \
+                * (limits[1] - limits[0]) \
+                + limits[0]
+
+            # Getting the labels via get_* might not actually be suitable:
+            # they might not reflect the current state.
+            # http://sourceforge.net/mailarchive/message.php?msg_name=AANLkTikdNFwSAhMIlLjnd4Ai8-XIdJYGmrwq6PrHkbgi%40mail.gmail.com
+            colorbar_ticklabels = colorbar.ax.get_yticklabels()
+            colorbar_styles.extend(_get_ticks(data, 'y', colorbar_ticks,
+                                                    colorbar_ticklabels))
+        else:
+            raise RuntimeError('Unknown color bar orientation ''%s''. Abort.' %
+                               orientation)
+
+        mycolormap, is_custom_cmap = _mpl_cmap2pgf_cmap(colorbar.get_cmap())
+        if is_custom_cmap:
+            axis_options.append('colormap=' + mycolormap)
+        else:
+            axis_options.append('colormap/' + mycolormap)
+
+        axis_options.append('point meta min=%.15g' % limits[0])
+        axis_options.append('point meta max=%.15g' % limits[1])
+
+        if colorbar_styles:
+            axis_options.append('colorbar style={%s}'
+                                % ','.join(colorbar_styles)
+                                )
+
+    # actually print the thing
+    if is_subplot:
+        content.append('\\nextgroupplot')
+    else:
+        content.append('\\begin{%s}' % env)
+
+    # Run through the children objects, gather the content, and give them the
+    # opportunity to contribute to data['extra axis options'].
+    data, children_content = _handle_children(data, obj)
+
+    if data['extra axis options']:
+        axis_options.extend(data['extra axis options'])
+
+    if axis_options:
+        options = ',\n'.join(axis_options)
+        content.append('[\n' + options + '\n]\n')
+
+    content.extend(children_content)
+
+    # anchors
+    if hasattr(obj, "_matplotlib2tikz_anchors"):
+        try:
+            for coord, anchor_name in obj._matplotlib2tikz_anchors:
+                content.append('\\node (%s) at (axis cs:%e,%e) {};\n'
+                               % (anchor_name, coord[0], coord[1])
+                               )
+        except:
+            print('Axes attribute _matplotlib2tikz_anchors wrongly set:'
+                  "Expected a list of ((x,y), anchor_name), got '%s'"
+                  % str(obj._matplotlib2tikz_anchors)
+                  )
+
+    if not is_subplot:
+        content.append('\\end{%s}\n\n' % env)
+    elif is_subplot and nsubplots == subplot_index:
+        content.append('\\end{groupplot}\n\n')
+
+    return data, content
+
+
+def _get_ticks(data, xy, ticks, ticklabels):
+    '''
+    Gets a {'x','y'}, a number of ticks and ticks labels, and returns the
+    necessary axis options for the given configuration.
+    '''
+    axis_options = []
+    pgfplots_ticks = []
+    pgfplots_ticklabels = []
+    is_label_necessary = False
+    for (tick, ticklabel) in zip(ticks, ticklabels):
+        pgfplots_ticks.append(tick)
+        # store the label anyway
+        label = ticklabel.get_text()
+        pgfplots_ticklabels.append(label)
+        # Check if the label is necessary.
+        # If *one* of the labels is, then all of them must
+        # appear in the TikZ plot.
+        is_label_necessary = (label and label != str(tick))
+        # TODO This seems not quite to be the test whether labels are
+        #      necessary.
+
+    # Leave the ticks to Pgfplots if not in STRICT mode and if there are no
+    # explicit labels.
+    if data['strict'] or is_label_necessary:
+        if pgfplots_ticks:
+            axis_options.append('%stick={%s}'
+                                % (xy,
+                                   ','.join(['%.15g' % el for el in pgfplots_ticks]))
+                                )
+        else:
+            axis_options.append('%stick=\\empty' % xy)
+
+        if is_label_necessary:
+            axis_options.append('%sticklabels={%s}'
+                                % (xy, ','.join(pgfplots_ticklabels))
+                                )
+    return axis_options
+
+
+def _mpl_cmap2pgf_cmap(cmap):
+    '''Converts a color map as given in matplotlib to a color map as
+    represented in Pgfplots.
+    '''
+    if not isinstance(cmap, mpl.colors.LinearSegmentedColormap):
+        print('Don''t know how to handle color map. Using ''blackwhite''.')
+        is_custom_colormap = False
+        return ('blackwhite', is_custom_colormap)
+
+    if cmap.is_gray():
+        is_custom_colormap = False
+        return ('blackwhite', is_custom_colormap)
+
+    # For an explanation of what _segmentdata contains, see
+    # http://matplotlib.sourceforge.net/examples/pylab_examples/custom_cmap.html
+    # A key sentence:
+    # If there are discontinuities, then it is a little more complicated.
+    # Label the 3 elements in each row in the cdict entry for a given color as
+    # (x, y0, y1).  Then for values of x between x[i] and x[i+1] the color
+    # value is interpolated between y1[i] and y0[i+1].
+    segdata = cmap._segmentdata
+    red = segdata['red']
+    green = segdata['green']
+    blue = segdata['blue']
+
+    # Loop over the data, stop at each spot where the linear interpolations is
+    # interrupted, and set a color mark there.
+    #
+    # Set initial color.
+    k_red = 0
+    k_green = 0
+    k_blue = 0
+    x = 0.0
+    colors = []
+    X = []
+    while True:
+        # find next x
+        x = min(red[k_red][0], green[k_green][0], blue[k_blue][0])
+
+        if red[k_red][0] == x:
+            red_comp = red[k_red][1]
+            k_red += 1
+        else:
+            red_comp = _linear_interpolation(x,
+                                             (red[k_red-1][0], red[k_red][0]),
+                                             (red[k_red-1][2], red[k_red][1])
+                                             )
+
+        if green[k_green][0] == x:
+            green_comp = green[k_green][1]
+            k_green += 1
+        else:
+            green_comp = _linear_interpolation(x,
+                                               (green[k_green-1][0],
+                                                green[k_green][0]),
+                                               (green[k_green-1][2],
+                                                green[k_green][1])
+                                               )
+
+        if blue[k_blue][0] == x:
+            blue_comp = blue[k_blue][1]
+            k_blue += 1
+        else:
+            blue_comp = _linear_interpolation(x,
+                                              (blue[k_blue-1][0],
+                                               blue[k_blue][0]),
+                                              (blue[k_blue-1][2],
+                                               blue[k_blue][1])
+                                              )
+
+        X.append(x)
+        colors.append((red_comp, green_comp, blue_comp))
+
+        if x >= 1.0:
+            break
+
+    # The Pgfplots color map has an actual physical scale, like (0cm,10cm), and
+    # the points where the colors change is also given in those units. As of
+    # now (2010-05-06) it is crucial for Pgfplots that the difference between
+    # two successive points is an integer multiple of a given unity (parameter
+    # to the colormap; e.g., 1cm).  At the same time, TeX suffers from
+    # significant round-off errors, so make sure that this unit is not too
+    # small such that the round- off errors don't play much of a role. A unit
+    # of 1pt, e.g., does most often not work.
+    unit = 'pt'
+
+    # Scale to integer
+    X = _scale_to_int(np.array(X))
+
+    color_changes = []
+    for (k, x) in enumerate(X):
+        color_changes.append('rgb(%d%s)=(%.15g,%.15g,%.15g)'
+                             % ((x, unit) + colors[k])
+                             )
+
+    colormap_string = '{mymap}{[1%s] %s}' % \
+                      (unit, '; '.join(color_changes))
+    is_custom_colormap = True
+    return (colormap_string, is_custom_colormap)
+
+
+def _scale_to_int(X):
+    '''
+    Scales the array X such that it contains only integers.
+    '''
+    X = X / _gcd_array(X)
+    return [int(entry) for entry in X]
+
+
+def _gcd_array(X):
+    '''
+    Return the largest real value h such that all elements in x are integer
+    multiples of h.
+    '''
+    greatest_common_divisor = 0.0
+    for x in X:
+        greatest_common_divisor = _gcd(greatest_common_divisor, x)
+
+    return greatest_common_divisor
+
+
+def _gcd(a, b):
+    '''Euclidean algorithm for calculating the GCD of two numbers a, b.
+    This algoritm also works for real numbers:
+    Find the greatest number h such that a and b are integer multiples of h.
+    '''
+    # Keep the tolerance somewhat significantly above machine precision as
+    # otherwise round-off errors will be accounted for, returning 1.0e-10
+    # instead of 1.0 for the values
+    #   [1.0, 2.0000000001, 3.0, 4.0].
+    while a > 1.0e-5:
+        a, b = b % a, a
+    return b
+
+
+def _linear_interpolation(x, X, Y):
+    '''Given two data points [X,Y], linearly interpolate those at x.
+    '''
+    return (Y[1]*(x-X[0]) + Y[0]*(X[1]-x)) / (X[1]-X[0])
+
+
+def _transform_to_data_coordinates(obj, xdata, ydata):
+    '''Transform to data coordinates
+    The coordinates might not be in data coordinates, but could be partly in
+    axes coordinates.  For example, the matplotlib command
+      axes.axvline(2)
+    will have the y coordinates set to 0 and 1, not to the limits. Therefore, a
+    two stage transform is to be applied, first transforming to display
+    coordinates, then from display to data.  In case of problems
+    (non-invertible, or whatever), print a warning and continue anyways.
+    '''
+    try:
+        import matplotlib.transforms
+        points = zip(xdata, ydata)
+        transform = matplotlib.transforms.composite_transform_factory(
+            obj.get_transform(),
+            obj.get_axes().transData.inverted()
+            )
+        points_data = transform.transform(points)
+        xdata, ydata = zip(*points_data)
+    except:
+        print("Problem during transformation, continuing with original data")
+    return (xdata, ydata)
+
+
+TIKZ_LINEWIDTHS = {0.1: 'ultra thin',
+                   0.2: 'very thin',
+                   0.4: 'thin',
+                   0.6: 'semithick',
+                   0.8: 'thick',
+                   1.2: 'very thick',
+                   1.6: 'ultra thick'
+                   }
+
+
+def _draw_line2d(data, obj):
+    '''Returns the Pgfplots code for an Line2D environment.
+    '''
+    content = []
+    addplot_options = []
+
+    # get the linewidth (in pt)
+    line_width = obj.get_linewidth()
+
+    if data['strict']:
+        # Takes the matplotlib linewidths, and just translate them
+        # into Pgfplots.
+        try:
+            addplot_options.append(TIKZ_LINEWIDTHS[line_width])
+        except KeyError:
+            # explicit line width
+            addplot_options.append('line width=%spt' % line_width)
+    else:
+        # The following is an alternative approach to line widths.
+        # The default line width in matplotlib is 1.0pt, in Pgfplots 0.4pt
+        # ('thin').
+        # Match the two defaults, and scale for the rest.
+        scaled_line_width = line_width / 1.0  # scale by default line width
+        if scaled_line_width == 0.25:
+            addplot_options.append('ultra thin')
+        elif scaled_line_width == 0.5:
+            addplot_options.append('very thin')
+        elif scaled_line_width == 1.0:
+            pass  # Pgfplots default line width, 'thin'
+        elif scaled_line_width == 1.5:
+            addplot_options.append('semithick')
+        elif scaled_line_width == 2:
+            addplot_options.append('thick')
+        elif scaled_line_width == 3:
+            addplot_options.append('very thick')
+        elif scaled_line_width == 4:
+            addplot_options.append('ultra thick')
+        else:
+            # explicit line width
+            addplot_options.append('line width=%rpt' % (0.4*line_width))
+
+    # get line color
+    color = obj.get_color()
+    data, line_xcolor, _ = _mpl_color2xcolor(data, color)
+    addplot_options.append(line_xcolor)
+
+    alpha = obj.get_alpha()
+    if alpha is not None:
+        addplot_options.append('opacity=%r' % alpha)
+
+    show_line, linestyle = _mpl_linestyle2pgfp_linestyle(obj.get_linestyle())
+    if show_line and linestyle:
+        addplot_options.append(linestyle)
+
+    marker_face_color = obj.get_markerfacecolor()
+    marker_edge_color = obj.get_markeredgecolor()
+    data, marker, extra_mark_options = \
+        _mpl_marker2pgfp_marker(data, obj.get_marker(), marker_face_color)
+    if marker:
+        addplot_options.append('mark=' + marker)
+
+        mark_size = obj.get_markersize()
+        if mark_size:
+            # setting half size because pgfplots counts the radius/half-width
+            pgf_size = int(mark_size/2)
+            # make sure we didn't round off to zero by accident
+            if pgf_size == 0 and mark_size != 0:
+                pgf_size = 1
+            addplot_options.append('mark size=%d' % pgf_size)
+
+        mark_options = []
+        if extra_mark_options:
+            mark_options.append(extra_mark_options)
+        if marker_face_color:
+            data, face_xcolor, _ = _mpl_color2xcolor(data, marker_face_color)
+            if face_xcolor != line_xcolor:
+                mark_options.append('fill=' + face_xcolor)
+        if marker_edge_color and marker_edge_color != marker_face_color:
+            data, draw_xcolor, _ = _mpl_color2xcolor(data, marker_edge_color)
+            if draw_xcolor != line_xcolor:
+                mark_options.append('draw=' + draw_xcolor)
+        if mark_options:
+            addplot_options.append('mark options={%s}' % ','.join(mark_options)
+                                   )
+
+    if marker and not show_line:
+        addplot_options.append('only marks')
+
+    # process options
+    content.append('\\addplot ')
+    if addplot_options:
+        options = ', '.join(addplot_options)
+        content.append('[' + options + ']\n')
+
+    content.append('coordinates {\n')
+
+    # print the hard numerical data
+    xdata, ydata = _transform_to_data_coordinates(obj, *obj.get_data())
+
+    try:
+        has_mask = ydata.mask.any()
+    except AttributeError:
+        has_mask = 0
+
+    if has_mask:
+        # matplotlib jumps at masked images, while Pgfplots by default
+        # interpolates. Hence, if we have a masked plot, make sure that
+        # Pgfplots jump as well.
+        data['extra axis options'].add('unbounded coords=jump')
+        for (x, y, is_masked) in zip(xdata, ydata, ydata.mask):
+            if is_masked:
+                content.append('(%.15g,nan) ' % x)
+            else:
+                content.append('(%.15g,%.15g) ' % (x, y))
+    else:
+        for (x, y) in zip(xdata, ydata):
+            content.append('(%.15g,%.15g)\n' % (x, y))
+    content.append('\n};\n')
+
+    return data, content
+
+
+# for matplotlib markers, see
+# http://matplotlib.sourceforge.net/api/artist_api.html#matplotlib.lines.Line2D.set_marker
+MP_MARKER2PGF_MARKER = {'.': '*',  # point
+                        'o': 'o',  # circle
+                        '+': '+',  # plus
+                        'x': 'x',  # x
+                        'None': None,
+                        ' ': None,
+                        '': None
+                        }
+
+# the following markers are only available with PGF's plotmarks library
+MP_MARKER2PLOTMARKS = {'v': ('triangle', 'rotate=180'),  # triangle down
+                       '1': ('triangle', 'rotate=180'),
+                       '^': ('triangle', None),  # triangle up
+                       '2': ('triangle', None),
+                       '<': ('triangle', 'rotate=270'),  # triangle left
+                       '3': ('triangle', 'rotate=270'),
+                       '>': ('triangle', 'rotate=90'),  # triangle right
+                       '4': ('triangle', 'rotate=90'),
+                       's': ('square', None),
+                       'p': ('pentagon', None),
+                       '*': ('asterisk', None),
+                       'h': ('star', None),  # hexagon 1
+                       'H': ('star', None),  # hexagon 2
+                       'd': ('diamond', None),  # diamond
+                       'D': ('diamond', None),  # thin diamond
+                       '|': ('|', None),  # vertical line
+                       '_': ('_', None)  # horizontal line
+                       }
+
+
+def _mpl_marker2pgfp_marker(data, mpl_marker, is_marker_face_color):
+    '''Translates a marker style of matplotlib to the corresponding style
+    in Pgfplots.
+    '''
+    # try default list
+    try:
+        pgfplots_marker = MP_MARKER2PGF_MARKER[mpl_marker]
+        if is_marker_face_color and pgfplots_marker == 'o':
+            pgfplots_marker = '*'
+            data['pgfplots libs'].add('plotmarks')
+        marker_options = None
+        return (data, pgfplots_marker, marker_options)
+    except KeyError:
+        pass
+    # try plotmarks list
+    try:
+        data['pgfplots libs'].add('plotmarks')
+        pgfplots_marker, marker_options = MP_MARKER2PLOTMARKS[mpl_marker]
+        if (is_marker_face_color
+            and is_marker_face_color.lower() != "none") \
+                and not pgfplots_marker in ['|', '_']:
+            pgfplots_marker += '*'
+        return (data, pgfplots_marker, marker_options)
+    except KeyError:
+        pass
+    if mpl_marker == ',':  # pixel
+        print('Unsupported marker ''%r''.' % mpl_marker)
+    else:
+        print('Unknown marker ''%r''.' % mpl_marker)
+    return (data, None, None)
+
+
+MPLLINESTYLE_2_PGFPLOTSLINESTYLE = {'None': None,
+                                    '-': None,
+                                    ':': 'dotted',
+                                    '--': 'dashed',
+                                    '-.': 'dash pattern=on 1pt off 3pt '
+                                          'on 3pt off 3pt'
+                                    }
+
+
+def _mpl_linestyle2pgfp_linestyle(line_style):
+    '''Translates a line style of matplotlib to the corresponding style
+    in Pgfplots.
+    '''
+    show_line = (line_style != 'None')
+    try:
+        style = MPLLINESTYLE_2_PGFPLOTSLINESTYLE[line_style]
+    except KeyError:
+        print('Unknown line style ''%r''. Using default.' % line_style)
+        style = None
+    return show_line, style
+
+
+def _draw_image(data, obj):
+    '''Returns the Pgfplots code for an image environment.
+    '''
+    content = []
+
+    if not 'img number' in data.keys():
+        data['img number'] = 0
+
+    # Make sure not to overwrite anything.
+    file_exists = True
+    while file_exists:
+        data['img number'] = data['img number'] + 1
+        filename = os.path.join(data['output dir'],
+                                'img' + str(data['img number']) + '.png'
+                                )
+        file_exists = os.path.isfile(filename)
+
+    # store the image as in a file
+    img_array = obj.get_array()
+
+    dims = img_array.shape
+    if len(dims) == 2:  # the values are given as one real number: look at cmap
+        clims = obj.get_clim()
+        mpl.pyplot.imsave(fname=filename,
+                          arr=img_array,
+                          cmap=obj.get_cmap(),
+                          vmin=clims[0],
+                          vmax=clims[1]
+                          )
+    elif len(dims) == 3 and dims[2] in [3, 4]:
+        # RGB (+alpha) information at each point
+        # convert to PIL image (after upside-down flip)
+        import Image
+        image = Image.fromarray(np.flipud(img_array))
+        image.save(filename)
+    else:
+        raise RuntimeError('Unable to store image array.')
+
+    # write the corresponding information to the TikZ file
+    extent = obj.get_extent()
+
+    # the format specification will only accept tuples, not lists
+    if isinstance(extent, list):  # convert to () list
+        extent = tuple(extent)
+
+    if data['rel data path']:
+        rel_filepath = os.path.join(data['rel data path'],
+                                    os.path.basename(filename)
+                                    )
+    else:
+        rel_filepath = os.path.basename(filename)
+
+    # Explicitly use \pgfimage as includegrapics command, as the default
+    # \includegraphics fails unexpectedly in some cases
+    content.append('\\addplot graphics [includegraphics cmd=\pgfimage,'
+                   'xmin=%.15g, xmax=%.15g, '
+                   'ymin=%.15g, ymax=%.15g] {%s};\n'
+                   % (extent + (rel_filepath,))
+                   )
+
+    data, cont = _handle_children(data, obj)
+    content.extend(cont)
+
+    return data, content
+
+
+def _find_associated_colorbar(obj):
+    '''Rather poor way of telling whether an axis has a colorbar associated:
+    Check the next axis environment, and see if it is de facto a color bar;
+    if yes, return the color bar object.
+    '''
+    for child in obj.get_children():
+        try:
+            cbar = child.colorbar
+        except AttributeError:
+            continue
+        if not cbar is None:  # really necessary?
+            # if fetch was successful, cbar contains
+            # (reference to colorbar,
+            #   reference to axis containing colorbar)
+            return cbar[0]
+    return None
+
+
+def _is_colorbar(obj):
+    '''Returns 'True' if 'obj' is a  color bar, and 'False' otherwise.
+    '''
+    # TODO Are the colorbars exactly the l.collections.PolyCollection's?
+    if isinstance(obj, mpl.collections.PolyCollection):
+        arr = obj.get_array()
+        dims = arr.shape
+        return len(dims) == 1  # o rly?
+    else:
+        return False
+
+
+def _extract_colorbar(obj):
+    '''Search for color bars as subobjects of obj, and return the first found.
+    If none is found, return None.
+    '''
+    colorbars = mpl.pyplot.findobj(obj, _is_colorbar)
+    if not colorbars:
+        return None
+    if not _equivalent(colorbars):
+        print('More than one color bar found. Use first one.')
+    return colorbars[0]
+
+
+def _equivalent(array):
+    '''Checks if the vectors consists of all the same objects.
+    '''
+    if not array:
+        return False
+    else:
+        for elem in array:
+            if elem != array[0]:
+                return False
+    return True
+
+
+def _draw_polycollection(data, obj):
+    '''Returns Pgfplots code for a number of polygons. Currently empty.
+    '''
+    print('matplotlib2tikz: Don''t know how to draw a PolyCollection.')
+    return data, ''
+
+
+def _draw_patchcollection(data, obj):
+    '''Returns Pgfplots code for a number of patch objects.
+    '''
+    content = []
+    # Gather the draw options.
+    data, draw_options = _get_draw_options(data,
+                                           obj.get_edgecolor()[0],
+                                           obj.get_facecolor()[0]
+                                           )
+    for path in obj.get_paths():
+        data, cont = _draw_path(obj, data, path,
+                                draw_options=draw_options
+                                )
+        content.append(cont)
+    return data, content
+
+
+def _get_draw_options(data, ec, fc):
+    '''Get the draw options for a given (patch) object.
+    '''
+    draw_options = []
+
+    if ec is not None:
+        data, col, ec_rgba = _mpl_color2xcolor(data, ec)
+        if ec_rgba[3] != 0.0:
+            # Don't draw if it's invisible anyways.
+            draw_options.append('draw=%s' % col)
+
+    if fc is not None:
+        data, col, fc_rgba = _mpl_color2xcolor(data, fc)
+        if fc_rgba[3] != 0.0:
+            # Don't draw if it's invisible anyways.
+            draw_options.append('fill=%s' % col)
+
+    # handle transparency
+    if ec is not None and fc is not None and \
+       ec_rgba[3] != 1.0 and ec_rgba[3] == fc_rgba[3]:
+        draw_options.append('opacity=%.15g' % ec[3])
+    else:
+        if ec is not None and ec_rgba[3] != 1.0:
+            draw_options.append('draw opacity=%.15g' % ec_rgba[3])
+        if fc is not None and fc_rgba[3] != 1.0:
+            draw_options.append('fill opacity=%.15g' % fc_rgba[3])
+    # TODO Use those properties
+    #linewidths = obj.get_linewidths()
+    return data, draw_options
+
+
+def _draw_patch(data, obj):
+    '''Return the Pgfplots code for patches.
+    '''
+
+    # Gather the draw options.
+    data, draw_options = _get_draw_options(data,
+                                           obj.get_edgecolor(),
+                                           obj.get_facecolor()
+                                           )
+
+    if (isinstance(obj, mpl.patches.Rectangle)):
+        # rectangle specialization
+        return _draw_rectangle(data, obj, draw_options)
+    elif (isinstance(obj, mpl.patches.Ellipse)):
+        # ellipse specialization
+        return _draw_ellipse(data, obj, draw_options)
+    else:
+        # regular patch
+        return _draw_path(obj, data, obj.get_path(),
+                          draw_options=draw_options
+                          )
+
+
+def _draw_rectangle(data, obj, draw_options):
+    '''Return the Pgfplots code for rectangles.
+    '''
+    if not data['draw rectangles']:
+        return data, []
+
+    left_lower_x = obj.get_x()
+    left_lower_y = obj.get_y()
+    cont = ('\draw[%s] (axis cs:%.15g,%.15g) '
+            'rectangle (axis cs:%.15g,%.15g);\n'
+            ) % (','.join(draw_options),
+                 left_lower_x,
+                 left_lower_y,
+                 left_lower_x + obj.get_width(),
+                 left_lower_y + obj.get_height()
+                 )
+    return data, cont
+
+
+def _draw_ellipse(data, obj, draw_options):
+    '''Return the Pgfplots code for ellipses.
+    '''
+    if (isinstance(obj, mpl.patches.Circle)):
+        # circle specialization
+        return _draw_circle(data, obj, draw_options)
+    x, y = obj.center
+    cont = '\draw[%s] (axis cs:%.15g,%.15g) ellipse (%.15g and %.15g);\n' % \
+        (','.join(draw_options),
+         x, y,
+         0.5 * obj.width, 0.5 * obj.height
+         )
+    return data, cont
+
+
+def _draw_circle(data, obj, draw_options):
+    '''Return the Pgfplots code for circles.
+    '''
+    x, y = obj.center
+    cont = '\draw[%s] (axis cs:%.15g,%.15g) circle (%.15g);\n' % \
+        (','.join(draw_options),
+         x, y,
+         obj.get_radius()
+         )
+    return data, cont
+
+
+def _draw_pathcollection(data, obj):
+    '''Returns Pgfplots code for a number of patch objects.
+    '''
+    content = []
+    # TODO Use those properties
+    #linewidths = obj.get_linewidths()
+    # gather the draw options
+    ec = obj.get_edgecolors()
+    fc = obj.get_facecolors()
+    paths = obj.get_paths()
+    # TODO always use [0]?
+    if ec is not None and len(ec) > 0:
+        ec = ec[0]
+    else:
+        ec = None
+    if fc is not None and len(fc) > 0:
+        fc = fc[0]
+    else:
+        fc = None
+    for path in paths:
+        data, do = _get_draw_options(data, ec, fc)
+        data, cont = _draw_path(obj, data, path,
+                                draw_options=do
+                                )
+        content.append(cont)
+    return data, content
+
+
+def _draw_path(obj, data, path,
+               draw_options=None
+               ):
+    '''Adds code for drawing an ordinary path in Pgfplots (TikZ).
+    '''
+    nodes = []
+    prev = None
+    for vert, code in path.iter_segments():
+        vert = np.asarray(_transform_to_data_coordinates(obj,
+                                                         [vert[0]],
+                                                         [vert[1]]
+                                                         ))
+        # For path codes see
+        # http://matplotlib.sourceforge.net/api/path_api.html#matplotlib.path.Path
+        if code == mpl.path.Path.STOP:
+            pass
+        elif code == mpl.path.Path.MOVETO:
+            nodes.append('(axis cs:%.15g,%.15g)' % tuple(vert))
+        elif code == mpl.path.Path.LINETO:
+            nodes.append('--(axis cs:%.15g,%.15g)' % tuple(vert))
+        elif code == mpl.path.Path.CURVE3:
+            # Quadratic Bezier curves aren't natively supported in TikZ, but
+            # can be emulated as cubic Beziers.
+            # From
+            # http://www.latex-community.org/forum/viewtopic.php?t=4424&f=45:
+            # If you really need a quadratic Bézier curve on the points P0, P1
+            # and P2, then a process called 'degree elevation' yields the cubic
+            # control points (Q0, Q1, Q2 and Q3) as follows:
+            #   CODE: SELECT ALL
+            #   Q0 = P0
+            #   Q1 = 1/3 P0 + 2/3 P1
+            #   Q2 = 2/3 P1 + 1/3 P2
+            #   Q3 = P2
+            #
+            # P0 is the point of the previous step which is needed to compute
+            # Q1.
+            if prev is None:
+                raise RuntimeError('Cannot draw quadratic Bezier curves '
+                                   'as the beginning of of a path.')
+            Q1 = 1./3. * prev + 2./3. * vert[0:2]
+            Q2 = 2./3. * vert[0:2] + 1./3. * vert[2:4]
+            Q3 = vert[2:4]
+            nodes.append(('.. controls (axis cs:%.15g,%.15g) '
+                          + 'and (axis cs:%.15g,%.15g) '
+                          + '.. (axis cs:%.15g,%.15g)')
+                         % tuple(Q1 + Q2 + Q3)
+                         )
+        elif code == mpl.path.Path.CURVE4:
+            # Cubic Bezier curves.
+            nodes.append(('.. controls (axis cs:%.15g,%.15g) '
+                          + 'and (axis cs:%.15g,%.15g) '
+                          + '.. (axis cs:%.15g,%.15g)')
+                         % tuple(vert)
+                         )
+        elif code == mpl.path.Path.CLOSEPOLY:
+            nodes.append('--cycle')
+        else:
+            raise RuntimeError('Unknown path code %d. Abort.' % code)
+        # Store the previous point for quadratic Beziers.
+        prev = vert[0:2]
+
+    nodes_string = ''.join(nodes)
+    if draw_options:
+        path_command = '\\path [%s] %s;\n\n' % \
+                       (', '.join(draw_options), nodes_string)
+    else:
+        path_command = '\\path %s;\n\n' % nodes_string
+
+    return data, path_command
+
+
+def _mpl_color2xcolor(data, matplotlib_color):
+    '''Translates a matplotlib color specification into a proper LaTeX xcolor.
+    '''
+    # Convert it to RGBA.
+    my_col = np.array(mpl.colors.ColorConverter().to_rgba(matplotlib_color))
+
+    xcol = None
+
+    # RGB values (as taken from xcolor.dtx):
+    available_colors = {
+        'red':  np.array([1, 0, 0]),
+        'green': np.array([0, 1, 0]),
+        'blue': np.array([0, 0, 1]),
+        'brown': np.array([0.75, 0.5,  0.25]),
+        'lime': np.array([0.75, 1, 0]),
+        'orange': np.array([1, 0.5, 0]),
+        'pink': np.array([1, 0.75, 0.75]),
+        'purple': np.array([0.75, 0, 0.25]),
+        'teal': np.array([0, 0.5, 0.5]),
+        'violet': np.array([0.5, 0, 0.5]),
+        'black': np.array([0, 0, 0]),
+        'darkgray': np.array([0.25, 0.25, 0.25]),
+        'gray': np.array([0.5, 0.5, 0.5]),
+        'lightgray': np.array([0.75, 0.75, 0.75]),
+        'white': np.array([1, 1, 1])
+        # The colors cyan, magenta, yellow, and olive are also
+        # predefined by xcolor, but their RGB approximation of the
+        # native CMYK values is not very good. Don't use them here.
+        }
+
+    available_colors.update(data['custom colors'])
+
+    # Check if it exactly matches any of the colors already available.
+    # This case is actually treated below (alpha==1), but that loop
+    # may pick up combinations with black before finding the exact
+    # match. Hence, first check all colors.
+    for name, rgb in available_colors.items():
+        if all(my_col[:3] == rgb):
+            xcol = name
+            break
+
+    if not xcol:
+        # Check if my_col is a multiple of a predefined color and 'black'.
+        for name, rgb in available_colors.items():
+            if rgb[0] != 0.0:
+                alpha = my_col[0] / rgb[0]
+            elif rgb[1] != 0.0:
+                alpha = my_col[1] / rgb[1]
+            elif rgb[2] != 0.0:
+                alpha = my_col[2] / rgb[2]
+            else:  # rgb=(0,0,0)
+                alpha = 0.0
+
+            if all(my_col[:3] == alpha * rgb):
+                if alpha == 1.0:
+                    xcol = name
+                    break
+                elif alpha == 0.0:
+                    xcol = 'black'
+                    break
+                elif 0.0 < alpha and alpha < 1.0:
+                    # ... and round(alpha*100) == alpha*100:
+                    # Is the last condition really necessary?
+                    xcol = name + ('!%r!black' % (alpha*100))
+
+    # Lookup failed, add it to the custom list.
+    if not xcol:
+        xcol = 'color' + str(len(data['custom colors']))
+        data['custom colors'][xcol] = my_col[:3]
+
+    return data, xcol, my_col
+
+
+def _draw_legend(data, obj):
+    '''Adds legend code to the EXTRA_AXIS_OPTIONS.
+    '''
+    texts = []
+    for text in obj.texts:
+        texts.append('%s' % text.get_text())
+
+    cont = 'legend entries={{%s}}' % '},{'.join(texts)
+    data['extra axis options'].add(cont)
+
+    # Get the location.
+    # http://matplotlib.org/api/legend_api.html
+    pad = 0.03
+    if obj._loc == 1:
+        # upper right
+        position = None
+        anchor = None
+    elif obj._loc == 2:
+        # upper left
+        position = [pad, 1.0-pad]
+        anchor = 'north west'
+    elif obj._loc == 3:
+        # lower left
+        position = [pad, pad]
+        anchor = 'south west'
+    elif obj._loc == 4:
+        # lower right
+        position = [1.0-pad, pad]
+        anchor = 'south east'
+    elif obj._loc == 5:
+        # right
+        position = [1.0-pad, 0.5]
+        anchor = 'west'
+    elif obj._loc == 6:
+        # center left
+        position = [3*pad, 0.5]
+        anchor = 'east'
+    elif obj._loc == 7:
+        # center right
+        position = [1.0-3*pad, 0.5]
+        anchor = 'west'
+    elif obj._loc == 8:
+        # lower center
+        position = [0.5, 3*pad]
+        anchor = 'south'
+    elif obj._loc == 9:
+        # upper center
+        position = [0.5, 1.0-3*pad]
+        anchor = 'north'
+    elif obj._loc == 10:
+        # center
+        position = [0.5, 0.5]
+        anchor = 'center'  # does this work?
+    else:
+        position = None
+        anchor = None
+        warnings.warn('Unknown legend location ''%r''. Using default.'
+                      % obj._loc)
+
+    legend_style = []
+    if position:
+        legend_style.append('at={(%.15g,%.15g)}' % (position[0], position[1]))
+    if anchor:
+        legend_style.append('anchor=%s' % anchor)
+
+    if legend_style:
+        style = 'legend style={%s}' % ', '.join(legend_style)
+        data['extra axis options'].add(style)
+
+    return data
+
+
+def _draw_text(data, obj):
+    '''Paints text on the graph.
+    '''
+    content = []
+    properties = []
+    style = []
+    if(isinstance(obj, mpl.text.Annotation)):
+        ann_xy = obj.xy
+        ann_xycoords = obj.xycoords
+        ann_xytext = obj.xytext
+        ann_textcoords = obj.textcoords
+        if ann_xycoords != 'data' or ann_textcoords != 'data':
+            print('Warning: Anything else except for explicit positioning '
+                  'is not supported for annotations yet :(')
+            return data, content
+        else:  # Create a basic tikz arrow
+            arrow_style = []
+            if obj.arrowprops is not None:
+                if obj.arrowprops['arrowstyle'] is not None:
+                    if obj.arrowprops['arrowstyle'] in ['-', '->',
+                                                        '<-', '<->']:
+                        arrow_style.append(obj.arrowprops['arrowstyle'])
+                        data, col, _ = _mpl_color2xcolor(
+                            data,
+                            obj.arrow_patch.get_ec()
+                            )
+                        arrow_style.append(col)
+                    elif obj.arrowprops['ec'] is not None:
+                        data, col, _ = _mpl_color2xcolor(
+                            data,
+                            obj.arrowprops['ec']
+                            )
+                        arrow_style.append(col)
+                    elif obj.arrowprops['edgecolor'] is not None:
+                        data, col, _ = _mpl_color2xcolor(
+                            data,
+                            obj.arrowprops['edgecolor']
+                            )
+                        arrow_style.append(col)
+                    else:
+                        pass
+
+            arrow_proto = '\\draw[%s] (axis cs:%.15g,%.15g) ' \
+                          '-- (axis cs:%.15g,%.15g);\n'
+            the_arrow = arrow_proto % (','.join(arrow_style),
+                                       ann_xytext[0], ann_xytext[1],
+                                       ann_xy[0], ann_xy[1]
+                                       )
+            content.append(the_arrow)
+
+    # 1: coordinates in axis system
+    # 2: properties (shapes, rotation, etc)
+    # 3: text style
+    # 4: the text
+    #                   -------1--------2---3--4--
+    proto = '\\node at (axis cs:%.15g,%.15g)[\n  %s\n]{%s %s};\n'
+    pos = obj.get_position()
+    text = obj.get_text()
+    size = obj.get_size()
+    bbox = obj.get_bbox_patch()
+    converter = mpl.colors.ColorConverter()
+    scaling = 0.5*size / data['font size']  # XXX: This is ugly
+    properties.append('scale=%.15g' % scaling)
+    if bbox is not None:
+        bbox_style = bbox.get_boxstyle()
+        if bbox.get_fill():
+            data, fc, _ = _mpl_color2xcolor(data, bbox.get_facecolor())
+            if fc:
+                properties.append('fill=%s' % fc)
+        data, ec, _ = _mpl_color2xcolor(data, bbox.get_edgecolor())
+        if ec:
+            properties.append('draw=%s' % ec)
+        # XXX: This is ugly, too
+        properties.append('line width=%.15gpt' % (bbox.get_lw()*0.4))
+        properties.append('inner sep=%.15gpt'
+                          % (bbox_style.pad * data['font size'])
+                          )
+        # Rounded boxes
+        if(isinstance(bbox_style, mpl.patches.BoxStyle.Round)):
+            properties.append('rounded corners')
+        elif(isinstance(bbox_style, mpl.patches.BoxStyle.RArrow)):
+            data['tikz libs'].add('shapes.arrows')
+            properties.append('single arrow')
+        elif(isinstance(bbox_style, mpl.patches.BoxStyle.LArrow)):
+            data['tikz libs'].add('shapes.arrows')
+            properties.append('single arrow')
+            properties.append('shape border rotate=180')
+        # Sawtooth, Roundtooth or Round4 not supported atm
+        # Round4 should be easy with 'rounded rectangle'
+        # directive though.
+        else:
+            pass  # Rectangle
+        # Line style
+        if bbox.get_ls() == 'dotted':
+            properties.append('dotted')
+        elif bbox.get_ls() == 'dashed':
+            properties.append('dashed')
+        # TODO Check if there is there any way to extract the dashdot
+        # pattern from matplotlib instead of hardcoding
+        # an approximation?
+        elif(bbox.get_ls() == 'dashdot'):
+            properties.append(('dash pattern=on %.3gpt off %.3gpt on '
+                               '%.3gpt off %.3gpt'
+                               ) % (1.0/scaling, 3.0/scaling,
+                                    6.0/scaling, 3.0/scaling)
+                              )
+        else:
+            pass  # solid
+
+    ha = obj.get_ha()
+    va = obj.get_va()
+    anchor = _transform_positioning(ha, va)
+    if anchor is not None:
+        properties.append(anchor)
+    data, col, _ = _mpl_color2xcolor(data, converter.to_rgb(obj.get_color()))
+    properties.append('text=%s' % col)
+    properties.append('rotate=%.1f' % obj.get_rotation())
+    if obj.get_style() != 'normal':
+        style.append('\\itshape')
+    try:
+        if int(obj.get_weight()) > 550:
+            style.append('\\bfseries')
+    except:  # Not numeric
+        vals = ['semibold',
+                'demibold',
+                'demi',
+                'bold',
+                'heavy',
+                'extra bold',
+                'black'
+                ]
+        if str(obj.get_weight()) in vals:
+            style.append('\\bfseries')
+    content.append(proto
+                   % (pos[0], pos[1],
+                      ',\n  '.join(properties), ' '.join(style), text
+                      )
+                   )
+    return data, content
+
+
+def _transform_positioning(ha, va):
+    '''Converts matplotlib positioning to pgf node positioning.
+    Not quite accurate but the results are equivalent more or less.'''
+    if ha == 'center' and va == 'center':
+        return None
+    else:
+        ha_mpl_to_tikz = {'right': 'east',
+                          'left': 'west',
+                          'center': ''
+                          }
+        va_mpl_to_tikz = {'top': 'north',
+                          'bottom': 'south',
+                          'center': '',
+                          'baseline': 'base'
+                          }
+        return ('anchor=%s %s' % (va_mpl_to_tikz[va], ha_mpl_to_tikz[ha])
+                ).strip()
+
+
+def _handle_children(data, obj):
+    '''Iterates over all children of the current object, gathers the contents
+    contributing to the resulting Pgfplots file, and returns those.
+    '''
+    content = []
+    for child in obj.get_children():
+        if (isinstance(child, mpl.axes.Axes)):
+            data, cont = _draw_axes(data, child)
+            content.extend(cont)
+        elif (isinstance(child, mpl.lines.Line2D)):
+            data, cont = _draw_line2d(data, child)
+            content.extend(cont)
+        elif (isinstance(child, mpl.image.AxesImage)):
+            data, cont = _draw_image(data, child)
+            content.extend(cont)
+        elif (isinstance(child, mpl.patches.Patch)):
+            data, cont = _draw_patch(data, child)
+            content.extend(cont)
+        elif (isinstance(child, mpl.collections.PolyCollection)):
+            data, cont = _draw_polycollection(data, child)
+            content.extend(cont)
+        elif (isinstance(child, mpl.collections.PatchCollection)):
+            data, cont = _draw_patchcollection(data, child)
+            content.extend(cont)
+        elif (isinstance(child, mpl.collections.PathCollection)):
+            data, cont = _draw_pathcollection(data, child)
+            content.extend(cont)
+        elif (isinstance(child, mpl.legend.Legend)):
+            data = _draw_legend(data, child)
+        elif (isinstance(child, mpl.axis.XAxis)
+              or isinstance(child, mpl.axis.YAxis)
+              or isinstance(child, mpl.spines.Spine)
+              or isinstance(child, mpl.text.Text)
+              or isinstance(child, mpl.collections.QuadMesh)
+              ):
+            pass
+        else:
+            print('matplotlib2tikz: Don''t know how to handle object ''%s''.' %
+                  type(child))
+    # XXX: This is ugly
+    if isinstance(obj, mpl.axes.Subplot) or isinstance(obj, mpl.figure.Figure):
+        for text in obj.texts:
+            data, cont = _draw_text(data, text)
+            content.extend(cont)
+    return data, content
+
+
+def _print_pgfplot_libs_message(data):
+    '''Prints message to screen indicating the use of Pgfplots and its
+    libraries.'''
+    pgfplotslibs = ','.join(list(data['pgfplots libs']))
+    tikzlibs = ','.join(list(data['tikz libs']))
+
+    print('=========================================================')
+    print('Please add the following line to your LaTeX preamble:\n')
+    print('\\usepackage{pgfplots}')
+    if tikzlibs:
+        print('\\usetikzlibrary{' + tikzlibs + '}')
+    if pgfplotslibs:
+        print('\\usepgfplotslibrary{' + pgfplotslibs + '}')
+    print('=========================================================')
+    return