[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