[Python-modules-commits] [python-mplexporter] 02/135: Commit initial Renderer/Exporter framework

Wolfgang Borgert debacle at moszumanska.debian.org
Tue Sep 23 21:18:57 UTC 2014


This is an automated email from the git hooks/post-receive script.

debacle pushed a commit to branch master
in repository python-mplexporter.

commit e597c05458ed8756c5f42a0f95e4a56a2ece13bf
Author: Jake Vanderplas <vanderplas at astro.washington.edu>
Date:   Sat Feb 15 09:25:27 2014 -0800

    Commit initial Renderer/Exporter framework
---
 .gitignore                      |  4 ++
 mplexporter/__init__.py         |  2 +
 mplexporter/exporter.py         | 74 ++++++++++++++++++++++++++++++++
 mplexporter/renderer.py         | 75 ++++++++++++++++++++++++++++++++
 mplexporter/tests/__init__.py   |  0
 mplexporter/tests/test_basic.py | 53 +++++++++++++++++++++++
 mplexporter/utils.py            | 95 +++++++++++++++++++++++++++++++++++++++++
 renderer_example.py             | 48 +++++++++++++++++++++
 8 files changed, 351 insertions(+)

diff --git a/.gitignore b/.gitignore
index ded6067..43ac30c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,3 +34,7 @@ nosetests.xml
 .mr.developer.cfg
 .project
 .pydevproject
+
+
+# emacs backup files
+*~
\ No newline at end of file
diff --git a/mplexporter/__init__.py b/mplexporter/__init__.py
new file mode 100644
index 0000000..daa22df
--- /dev/null
+++ b/mplexporter/__init__.py
@@ -0,0 +1,2 @@
+from renderer import Renderer
+from exporter import Exporter
diff --git a/mplexporter/exporter.py b/mplexporter/exporter.py
new file mode 100644
index 0000000..05fa21f
--- /dev/null
+++ b/mplexporter/exporter.py
@@ -0,0 +1,74 @@
+"""
+Matplotlib Exporter
+===================
+This submodule contains tools for crawling a matplotlib figure and exporting
+relevant pieces to a renderer.
+"""
+from . import utils
+
+
+class Exporter(object):
+    def __init__(self, renderer):
+        self.renderer = renderer
+
+    def run(self, fig):
+        self._crawl_fig(fig)
+
+    def _process_transform(self, transform, ax=None, data=None):
+        """Process the transform and convert data to figure or data coordinates
+
+        Parameters
+        ----------
+        transform : matplotlib Transform object
+            The transform applied to the data
+
+        ax : matplotlib Axes object (optional)
+            The axes the data is associated with
+
+        data : ndarray (optional)
+            The array of data to be transformed.
+
+        Returns
+        -------
+        code : string
+            Code is either "data" or "figure", indicating data coordinates
+            or figure coordinates.
+        new_data : ndarray
+            Data transformed to either "data" or "figure" coordinates.
+            Returned only if data is specified
+        """
+        if ax is not None and transform.contains_branch(ax.transData):
+            code = "data"
+            transform = (transform - ax.transData)
+        else:
+            code = "figure"
+
+        if data is not None:
+            return code, transform.transform(data)
+        else:
+            return code            
+
+    def _crawl_fig(self, fig):
+        with self.renderer.draw_figure(fig):
+            for ax in fig.axes:
+                self._crawl_ax(ax)
+
+    def _crawl_ax(self, ax):
+        with self.renderer.draw_axes(ax):
+            self._extract_lines(ax)
+
+    def _extract_lines(self, ax):
+        for line in ax.lines:
+            code, data = self._process_transform(line.get_transform(),
+                                                 ax, line.get_xydata())
+            linestyle = utils.get_line_style(line)
+            if linestyle['dasharray'] not in ['None', 'none', None]:
+                self.renderer.draw_line(data,
+                                        coordinates=code,
+                                        style=linestyle)
+
+            markerstyle = utils.get_marker_style(line)
+            if markerstyle['marker'] not in ['None', 'none', None]:
+                self.renderer.draw_markers(data,
+                                           coordinates=code,
+                                           style=markerstyle)
diff --git a/mplexporter/renderer.py b/mplexporter/renderer.py
new file mode 100644
index 0000000..119ceb9
--- /dev/null
+++ b/mplexporter/renderer.py
@@ -0,0 +1,75 @@
+"""
+Matplotlib Renderer
+===================
+This submodule contains renderer objects which define renderer behavior used
+within the Exporter class.
+"""
+import warnings
+from contextlib import contextmanager
+
+
+class Renderer(object):
+    @staticmethod
+    def ax_zoomable(ax):
+        return bool(ax and ax.get_navigate())
+
+    @staticmethod
+    def ax_has_xgrid(ax):
+        return bool(ax and ax.xaxis._gridOnMajor and ax.yaxis.get_gridlines())
+
+    @staticmethod
+    def ax_has_ygrid(ax):
+        return bool(ax and ax.yaxis._gridOnMajor and ax.yaxis.get_gridlines())
+
+    @property
+    def current_ax_zoomable(self):
+        return self.ax_zoomable(self._current_ax)
+
+    @property
+    def current_ax_has_xgrid(self):
+        return self.ax_has_xgrid(self._current_ax)
+
+    @property
+    def current_ax_has_ygrid(self):
+        return self.ax_has_ygrid(self._current_ax)
+
+    @contextmanager
+    def draw_figure(self, fig):
+        if hasattr(self, "_current_fig") and self._current_fig is not None:
+            warnings.warn("figure embedded in figure: something is wrong")
+        self._current_fig = fig
+        self.open_figure(fig)
+        yield
+        self.close_figure(fig)
+        self._current_fig = None
+
+    @contextmanager
+    def draw_axes(self, ax):
+        if hasattr(self, "_current_ax") and self._current_ax is not None:
+            warnings.warn("axes embedded in axes: something is wrong")
+        self._current_ax = ax
+        self.open_axes(ax)
+        yield
+        self.close_axes(ax)
+        self._current_ax = None
+
+    # Following are the functions which should be overloaded in subclasses
+
+    def open_figure(self, fig):
+        pass
+
+    def close_figure(self, fig):
+        pass
+
+    def open_axes(self, ax):
+        pass
+
+    def close_axes(self, ax):
+        pass
+
+    def draw_line(self, data, coordinates, style):
+        raise NotImplementedError()
+
+    def draw_markers(self, data, coordinates, style):
+        raise NotImplementedError()
+
diff --git a/mplexporter/tests/__init__.py b/mplexporter/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/mplexporter/tests/test_basic.py b/mplexporter/tests/test_basic.py
new file mode 100644
index 0000000..01bcc04
--- /dev/null
+++ b/mplexporter/tests/test_basic.py
@@ -0,0 +1,53 @@
+from ..exporter import Exporter
+from ..renderer import Renderer
+
+import matplotlib
+matplotlib.use('Agg')
+import matplotlib.pyplot as plt
+
+
+FAKE_OUTPUT = """
+opening figure
+  opening axes
+    draw line with 20 points
+    draw 10 markers
+  closing axes
+closing figure
+"""
+
+
+class FakeRenderer(Renderer):
+    def __init__(self):
+        self.output = ""
+
+    def open_figure(self, fig):
+        self.output += "opening figure\n"
+
+    def close_figure(self, fig):
+        self.output += "closing figure\n"
+
+    def open_axes(self, ax):
+        self.output += "  opening axes\n"
+
+    def close_axes(self, ax):
+        self.output += "  closing axes\n"
+
+    def draw_line(self, data, coordinates, style):
+        self.output += "    draw line with {0} points\n".format(data.shape[0])
+
+    def draw_markers(self, data, coordinates, style):
+        self.output += "    draw {0} markers\n".format(data.shape[0])
+
+
+def test_fakerenderer():
+    fig, ax = plt.subplots()
+    ax.plot(range(20), '-k')
+    ax.plot(range(10), '.k')
+
+    renderer = FakeRenderer()
+    exporter = Exporter(renderer)
+    exporter.run(fig)
+
+    for line1, line2 in zip(renderer.output.strip().split(),
+                            FAKE_OUTPUT.strip().split()):
+        assert line1 == line2
diff --git a/mplexporter/utils.py b/mplexporter/utils.py
new file mode 100644
index 0000000..73d74fe
--- /dev/null
+++ b/mplexporter/utils.py
@@ -0,0 +1,95 @@
+"""
+Utility Routines for Working with Matplotlib Objects
+====================================================
+"""
+from matplotlib.colors import colorConverter
+from matplotlib.path import Path
+from matplotlib.markers import MarkerStyle
+from matplotlib.transforms import Affine2D
+
+
+def color_to_hex(color):
+    """Convert matplotlib color code to hex color code"""
+    rgb = colorConverter.to_rgb(color)
+    return '#{0:02X}{1:02X}{2:02X}'.format(*(int(255 * c) for c in rgb))
+
+
+
+def many_to_one(input_dict):
+    """Convert a many-to-one mapping to a one-to-one mapping"""
+    return dict((key, val)
+                for keys, val in input_dict.items()
+                for key in keys)
+
+LINESTYLES = many_to_one({('solid', '-', (None, None)): "10,0",
+                          ('dashed', '--'): "6,6",
+                          ('dotted', ':'): "2,2",
+                          ('dashdot', '-.'): "4,4,2,4",
+                          ('', ' ', 'None', 'none'): "none"})
+
+
+def get_dasharray(obj, i=None):
+    """Get an SVG dash array for the given matplotlib linestyle"""
+    if obj.__dict__.get('_dashSeq', None) is not None:
+        return ','.join(map(str, obj._dashSeq))
+    else:
+        ls = obj.get_linestyle()
+        if i is not None:
+            ls = ls[i]
+
+        dasharray = LINESTYLES.get(ls, None)
+        if dasharray is None:
+            warnings.warn("dash style '{0}' not understood: "
+                          "defaulting to solid.".format(ls))
+            dasharray = LINESTYLES['-']
+        return dasharray
+
+
+PATH_DICT = {Path.LINETO: 'L',
+             Path.MOVETO: 'M',
+             Path.STOP: 'STOP',
+             Path.CURVE3: 'S',
+             Path.CURVE4: 'C',
+             Path.CLOSEPOLY: 'Z'}
+
+
+def SVG_path(path, transform=None):
+    """Return a list of SVG path tuples of a (transformed) path"""
+    if transform is not None:
+        path = path.transformed(transform)
+
+    return [(PATH_DICT[path_code], vertices.tolist())
+            for vertices, path_code in path.iter_segments(simplify=False)]
+        
+
+def get_line_style(line):
+    """Return the line style dict for the line"""
+    style = {}
+    style['alpha'] = line.get_alpha()
+    if style['alpha'] is None:
+        style['alpha'] = 1
+    style['color'] = color_to_hex(line.get_color())
+    style['width'] = line.get_linewidth()
+    style['dasharray'] = get_dasharray(line)
+    return style
+
+
+def get_marker_style(line):
+    """Return the marker style dict for the line"""
+    style = {}
+    style['alpha'] = line.get_alpha()
+    if style['alpha'] is None:
+        style['alpha'] = 1
+
+    style['facecolor'] = color_to_hex(line.get_markerfacecolor())
+    style['edgecolor'] = color_to_hex(line.get_markeredgecolor())
+    style['edgewidth'] = line.get_markeredgewidth()
+
+    style['marker'] = line.get_marker()
+    markerstyle = MarkerStyle(line.get_marker())
+    markersize = line.get_markersize()
+    markertransform = (markerstyle.get_transform()
+                       + Affine2D().scale(markersize, -markersize))
+    style['markerpath'] = SVG_path(markerstyle.get_path(),
+                                   markertransform)
+    return style
diff --git a/renderer_example.py b/renderer_example.py
new file mode 100644
index 0000000..7a390a0
--- /dev/null
+++ b/renderer_example.py
@@ -0,0 +1,48 @@
+"""
+Example Renderer
+================
+This shows an example of a do-nothing renderer, along with how to use it.
+"""
+import matplotlib.pyplot as plt
+
+from mplexporter.renderer import Renderer
+from mplexporter.exporter import Exporter
+
+
+class ExampleRenderer(Renderer):
+    def __init__(self):
+        self.output = ""
+
+    def open_figure(self, fig):
+        self.output += "opening figure\n"
+
+    def close_figure(self, fig):
+        self.output += "closing figure\n"
+
+    def open_axes(self, ax):
+        self.output += "  opening axes\n"
+
+    def close_axes(self, ax):
+        self.output += "  closing axes\n"
+
+    def draw_line(self, data, coordinates, style):
+        self.output += "    draw line with {0} points\n".format(data.shape[0])
+
+    def draw_markers(self, data, coordinates, style):
+        self.output += "    draw {0} markers\n".format(data.shape[0])
+
+
+def run_example():
+    fig, ax = plt.subplots()
+    ax.plot(range(20), '-b')
+    ax.plot(range(10), '.k')
+
+    renderer = ExampleRenderer()
+    exporter = Exporter(renderer)
+
+    exporter.run(fig)
+    print renderer.output
+
+
+if __name__ == '__main__':
+    run_example()

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-mplexporter.git



More information about the Python-modules-commits mailing list