From 4dd3de1b580ac0d7dc53bcca396ba1bf25a8eea9 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 8 May 2012 16:35:14 -0400 Subject: [PATCH 1/3] Make hexbin much faster by creating a PathCollection that is a single polygon and a number of offsets. This allows the Agg backend to be dealing with far less data, and it allows the vector backends to write the polygon once and "use" it multiple times. --- examples/pylab_examples/hexbin_demo.py | 2 +- lib/matplotlib/axes.py | 44 ++++++++++--------- lib/matplotlib/backend_bases.py | 30 +++++++++---- lib/matplotlib/backends/backend_macosx.py | 3 +- lib/matplotlib/backends/backend_pdf.py | 53 +++++++++++++++++++++++ lib/matplotlib/backends/backend_ps.py | 8 ++-- lib/matplotlib/backends/backend_svg.py | 8 ++-- lib/matplotlib/collections.py | 31 +++++++++++-- src/_backend_agg.cpp | 20 ++++++--- src/_backend_agg.h | 4 +- 10 files changed, 157 insertions(+), 46 deletions(-) diff --git a/examples/pylab_examples/hexbin_demo.py b/examples/pylab_examples/hexbin_demo.py index e6436a941305..4446e2a944f7 100644 --- a/examples/pylab_examples/hexbin_demo.py +++ b/examples/pylab_examples/hexbin_demo.py @@ -9,6 +9,7 @@ import matplotlib.cm as cm import matplotlib.pyplot as plt +np.random.seed(0) n = 100000 x = np.random.standard_normal(n) y = 2.0 + 3.0 * x + 4.0 * np.random.standard_normal(n) @@ -33,4 +34,3 @@ cb.set_label('log10(N)') plt.show() - diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index df293fd44aaf..1a97aac6b211 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -6175,43 +6175,47 @@ def hexbin(self, x, y, C = None, gridsize = 100, bins = None, lattice1.astype(float).ravel(), lattice2.astype(float).ravel())) good_idxs = ~np.isnan(accum) - px = xmin + sx * np.array([ 0.5, 0.5, 0.0, -0.5, -0.5, 0.0]) - py = ymin + sy * np.array([-0.5, 0.5, 1.0, 0.5, -0.5, -1.0]) / 3.0 - - polygons = np.zeros((6, n, 2), float) - polygons[:,:nx1*ny1,0] = np.repeat(np.arange(nx1), ny1) - polygons[:,:nx1*ny1,1] = np.tile(np.arange(ny1), nx1) - polygons[:,nx1*ny1:,0] = np.repeat(np.arange(nx2) + 0.5, ny2) - polygons[:,nx1*ny1:,1] = np.tile(np.arange(ny2), nx2) + 0.5 - + px = sx * np.array([ 0.5, 0.5, 0.0, -0.5, -0.5, 0.0]) + py = sy * np.array([-0.5, 0.5, 1.0, 0.5, -0.5, -1.0]) / 3.0 + + offsets = np.zeros((n, 2), float) + offsets[:nx1*ny1,0] = np.repeat(np.arange(nx1), ny1) + offsets[:nx1*ny1,1] = np.tile(np.arange(ny1), nx1) + offsets[nx1*ny1:,0] = np.repeat(np.arange(nx2) + 0.5, ny2) + offsets[nx1*ny1:,1] = np.tile(np.arange(ny2), nx2) + 0.5 + offsets[:,0] *= sx + offsets[:,1] *= sy + offsets[:,0] += xmin + offsets[:,1] += ymin # remove accumulation bins with no data - polygons = polygons[:,good_idxs,:] + offsets = offsets[good_idxs,:] accum = accum[good_idxs] - polygons = np.transpose(polygons, axes=[1,0,2]) - polygons[:,:,0] *= sx - polygons[:,:,1] *= sy - polygons[:,:,0] += px - polygons[:,:,1] += py - if xscale=='log': - polygons[:,:,0] = 10**(polygons[:,:,0]) + offsets[:,0] = 10**(offsets[:,0]) xmin = 10**xmin xmax = 10**xmax self.set_xscale('log') if yscale=='log': - polygons[:,:,1] = 10**(polygons[:,:,1]) + offsets[:,1] = 10**(offsets[:,1]) ymin = 10**ymin ymax = 10**ymax self.set_yscale('log') + polygons = np.zeros((6, 2), float) + polygons[:,0] = px + polygons[:,1] = py + if edgecolors=='none': edgecolors = 'face' + collection = mcoll.PolyCollection( - polygons, + [polygons], edgecolors = edgecolors, linewidths = linewidths, - transOffset = self.transData, + offsets = offsets, + transOffset = mtransforms.IdentityTransform(), + offset_position = "data" ) if isinstance(norm, mcolors.LogNorm): diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 03cb7fe6badf..b31dfab9fef9 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -186,14 +186,16 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None) def draw_path_collection(self, gc, master_transform, paths, all_transforms, offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls): + linewidths, linestyles, antialiaseds, urls, + offset_position): """ Draws a collection of paths selecting drawing properties from the lists *facecolors*, *edgecolors*, *linewidths*, *linestyles* and *antialiaseds*. *offsets* is a list of offsets to apply to each of the paths. The offsets in *offsets* are first transformed by *offsetTrans* before being - applied. + applied. *offset_position* may be either "screen" or "data" + depending on the space that the offsets are in. This provides a fallback implementation of :meth:`draw_path_collection` that makes multiple calls to @@ -213,8 +215,9 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, path_ids.append((path, transform)) for xo, yo, path_id, gc0, rgbFace in self._iter_collection( - gc, path_ids, offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls): + gc, master_transform, all_transforms, path_ids, offsets, + offsetTrans, facecolors, edgecolors, linewidths, linestyles, + antialiaseds, urls, offset_position): path, transform = path_id transform = transforms.Affine2D(transform.get_matrix()).translate(xo, yo) self.draw_path(gc0, path, transform, rgbFace) @@ -240,7 +243,7 @@ def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight, return self.draw_path_collection( gc, master_transform, paths, [], offsets, offsetTrans, facecolors, - edgecolors, linewidths, [], [antialiased], [None]) + edgecolors, linewidths, [], [antialiased], [None], 'screen') def draw_gouraud_triangle(self, gc, points, colors, transform): """ @@ -302,9 +305,10 @@ def _iter_collection_raw_paths(self, master_transform, paths, transform = all_transforms[i % Ntransforms] yield path, transform + master_transform - def _iter_collection(self, gc, path_ids, offsets, offsetTrans, facecolors, - edgecolors, linewidths, linestyles, antialiaseds, - urls): + def _iter_collection(self, gc, master_transform, all_transforms, + path_ids, offsets, offsetTrans, facecolors, + edgecolors, linewidths, linestyles, + antialiaseds, urls, offset_position): """ This is a helper method (along with :meth:`_iter_collection_raw_paths`) to make it easier to write @@ -330,6 +334,7 @@ def _iter_collection(self, gc, path_ids, offsets, offsetTrans, facecolors, *path_ids*; *gc* is a graphics context and *rgbFace* is a color to use for filling the path. """ + Ntransforms = len(all_transforms) Npaths = len(path_ids) Noffsets = len(offsets) N = max(Npaths, Noffsets) @@ -359,6 +364,15 @@ def _iter_collection(self, gc, path_ids, offsets, offsetTrans, facecolors, path_id = path_ids[i % Npaths] if Noffsets: xo, yo = toffsets[i % Noffsets] + if offset_position == 'data': + if Ntransforms: + transform = all_transforms[i % Ntransforms] + master_transform + else: + transform = master_transform + xo, yo = transform.transform_point((xo, yo)) + xp, yp = transform.transform_point((0, 0)) + xo = -(xp - xo) + yo = -(yp - yo) if Nfacecolors: rgbFace = facecolors[i % Nfacecolors] if Nedgecolors: diff --git a/lib/matplotlib/backends/backend_macosx.py b/lib/matplotlib/backends/backend_macosx.py index 31e132c3dd3e..477ceb9c36f3 100644 --- a/lib/matplotlib/backends/backend_macosx.py +++ b/lib/matplotlib/backends/backend_macosx.py @@ -60,7 +60,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None) def draw_path_collection(self, gc, master_transform, paths, all_transforms, offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls): + linewidths, linestyles, antialiaseds, urls, + offset_position): cliprect = gc.get_clip_rectangle() clippath, clippath_transform = gc.get_clip_path() if all_transforms: diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index f443e2566703..74fb8f270ffe 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -452,6 +452,8 @@ def __init__(self, filename): self.markers = {} self.multi_byte_charprocs = {} + self.paths = [] + # The PDF spec recommends to include every procset procsets = [ Name(x) for x in "PDF Text ImageB ImageC ImageI".split() ] @@ -505,9 +507,12 @@ def close(self): xobjects[tup[0]] = tup[1] for name, value in self.multi_byte_charprocs.iteritems(): xobjects[name] = value + for name, path, trans, ob, join, cap, padding in self.paths: + xobjects[name] = ob self.writeObject(self.XObjectObject, xobjects) self.writeImages() self.writeMarkers() + self.writePathCollectionTemplates() self.writeObject(self.pagesObject, { 'Type': Name('Pages'), 'Kids': self.pageList, @@ -1259,6 +1264,28 @@ def writeMarkers(self): self.output(Op.paint_path(False, fillp, strokep)) self.endStream() + def pathCollectionObject(self, gc, path, trans, padding): + name = Name('P%d' % len(self.paths)) + ob = self.reserveObject('path %d' % len(self.paths)) + self.paths.append( + (name, path, trans, ob, gc.get_joinstyle(), gc.get_capstyle(), padding)) + return name + + def writePathCollectionTemplates(self): + for (name, path, trans, ob, joinstyle, capstyle, padding) in self.paths: + pathops = self.pathOperations(path, trans, simplify=False) + bbox = path.get_extents(trans) + bbox = bbox.padded(padding) + self.beginStream( + ob.id, None, + {'Type': Name('XObject'), 'Subtype': Name('Form'), + 'BBox': list(bbox.extents)}) + self.output(GraphicsContextPdf.joinstyles[joinstyle], Op.setlinejoin) + self.output(GraphicsContextPdf.capstyles[capstyle], Op.setlinecap) + self.output(*pathops) + self.output(Op.paint_path(False, True, True)) + self.endStream() + @staticmethod def pathOperations(path, transform, clip=None, simplify=None): cmds = [] @@ -1466,6 +1493,32 @@ def draw_path(self, gc, path, transform, rgbFace=None): rgbFace is None and gc.get_hatch_path() is None) self.file.output(self.gc.paint()) + def draw_path_collection(self, gc, master_transform, paths, all_transforms, + offsets, offsetTrans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds, urls, + offset_position): + + padding = np.max(linewidths) + path_codes = [] + for i, (path, transform) in enumerate(self._iter_collection_raw_paths( + master_transform, paths, all_transforms)): + name = self.file.pathCollectionObject(gc, path, transform, padding) + path_codes.append(name) + + output = self.file.output + output(Op.gsave) + lastx, lasty = 0, 0 + for xo, yo, path_id, gc0, rgbFace in self._iter_collection( + gc, master_transform, all_transforms, path_codes, offsets, + offsetTrans, facecolors, edgecolors, linewidths, linestyles, + antialiaseds, urls, offset_position): + + self.check_gc(gc0, rgbFace) + dx, dy = xo - lastx, yo - lasty + output(1, 0, 0, 1, dx, dy, Op.concat_matrix, path_id, Op.use_xobject) + lastx, lasty = xo, yo + output(Op.grestore) + def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): # For simple paths or small numbers of markers, don't bother # making an XObject diff --git a/lib/matplotlib/backends/backend_ps.py b/lib/matplotlib/backends/backend_ps.py index 709cb447c95d..2af23224f145 100644 --- a/lib/matplotlib/backends/backend_ps.py +++ b/lib/matplotlib/backends/backend_ps.py @@ -625,7 +625,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None) def draw_path_collection(self, gc, master_transform, paths, all_transforms, offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls): + linewidths, linestyles, antialiaseds, urls, + offset_position): write = self._pswriter.write path_codes = [] @@ -640,8 +641,9 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, path_codes.append(name) for xo, yo, path_id, gc0, rgbFace in self._iter_collection( - gc, path_codes, offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls): + gc, master_transform, all_transforms, path_codes, offsets, + offsetTrans, facecolors, edgecolors, linewidths, linestyles, + antialiaseds, urls, offset_position): ps = "%g %g %s" % (xo, yo, path_id) self._draw_ps(ps, gc0, rgbFace) diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py index 3a7e8d44cac2..49c39f883f58 100644 --- a/lib/matplotlib/backends/backend_svg.py +++ b/lib/matplotlib/backends/backend_svg.py @@ -577,7 +577,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None) def draw_path_collection(self, gc, master_transform, paths, all_transforms, offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls): + linewidths, linestyles, antialiaseds, urls, + offset_position): writer = self.writer path_codes = [] writer.start(u'defs') @@ -592,8 +593,9 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms, writer.end(u'defs') for xo, yo, path_id, gc0, rgbFace in self._iter_collection( - gc, path_codes, offsets, offsetTrans, facecolors, edgecolors, - linewidths, linestyles, antialiaseds, urls): + gc, master_transform, all_transforms, path_codes, offsets, + offsetTrans, facecolors, edgecolors, linewidths, linestyles, + antialiaseds, urls, offset_position): clipid = self._get_clip(gc0) url = gc0.get_url() if url is not None: diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index 81f800d48978..e4486d884908 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -42,6 +42,7 @@ class Collection(artist.Artist, cm.ScalarMappable): * *antialiaseds*: None * *offsets*: None * *transOffset*: transforms.IdentityTransform() + * *offset_position*: 'screen' (default) or 'data' * *norm*: None (optional for :class:`matplotlib.cm.ScalarMappable`) * *cmap*: None (optional for @@ -49,7 +50,11 @@ class Collection(artist.Artist, cm.ScalarMappable): * *hatch*: None *offsets* and *transOffset* are used to translate the patch after - rendering (default no offsets). + rendering (default no offsets). If offset_position is 'screen' + (default) the offset is applied after the master transform has + been applied, that is, the offsets are in screen coordinates. If + offset_position is 'data', the offset is applied before the master + transform, i.e., the offsets are in data coordinates. If any of *edgecolors*, *facecolors*, *linewidths*, *antialiaseds* are None, they default to their :data:`matplotlib.rcParams` patch @@ -80,6 +85,7 @@ def __init__(self, pickradius = 5.0, hatch=None, urls = None, + offset_position='screen', **kwargs ): """ @@ -98,7 +104,7 @@ def __init__(self, self.set_pickradius(pickradius) self.set_urls(urls) self.set_hatch(hatch) - + self.set_offset_position(offset_position) self._uniform_offsets = None self._offsets = np.array([], np.float_) @@ -242,7 +248,8 @@ def draw(self, renderer): renderer.draw_path_collection( gc, transform.frozen(), paths, self.get_transforms(), offsets, transOffset, self.get_facecolor(), self.get_edgecolor(), - self._linewidths, self._linestyles, self._antialiaseds, self._urls) + self._linewidths, self._linestyles, self._antialiaseds, self._urls, + self._offset_position) gc.restore() renderer.close_group(self.__class__.__name__) @@ -359,6 +366,24 @@ def get_offsets(self): else: return self._uniform_offsets + def set_offset_position(self, offset_position): + """ + The how offsets are applied. If *offset_position* is 'screen' + (default) the offset is applied after the master transform has + been applied, that is, the offsets are in screen coordinates. + If offset_position is 'data', the offset is applied before the + master transform, i.e., the offsets are in data coordinates. + """ + if offset_position not in ('screen', 'data'): + raise ValueError("offset_position must be 'screen' or 'data'") + self._offset_position = offset_position + + def get_offset_position(self): + """ + Returns the offset position of the collection. + """ + return self._offset_position + def set_linewidth(self, lw): """ Set the linewidth(s) for the collection. *lw* can be a scalar diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index 3f368547f66c..1ed1d9dab3d1 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -1401,7 +1401,8 @@ RendererAgg::_draw_path_collection_generic const Py::Object& edgecolors_obj, const Py::SeqBase& linewidths, const Py::SeqBase& linestyles_obj, - const Py::SeqBase& antialiaseds) + const Py::SeqBase& antialiaseds, + const bool data_offsets) { typedef agg::conv_transform transformed_path_t; typedef PathNanRemover nan_removed_t; @@ -1515,7 +1516,11 @@ RendererAgg::_draw_path_collection_generic double xo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 0); double yo = *(double*)PyArray_GETPTR2(offsets, i % Noffsets, 1); offset_trans.transform(&xo, &yo); - trans *= agg::trans_affine_translation(xo, yo); + if (data_offsets) { + trans = agg::trans_affine_translation(xo, yo) * trans; + } else { + trans *= agg::trans_affine_translation(xo, yo); + } } // These transformations must be done post-offsets @@ -1633,7 +1638,7 @@ Py::Object RendererAgg::draw_path_collection(const Py::Tuple& args) { _VERBOSE("RendererAgg::draw_path_collection"); - args.verify_length(12); + args.verify_length(13); Py::Object gc_obj = args[0]; GCAgg gc(gc_obj, dpi); @@ -1650,6 +1655,9 @@ RendererAgg::draw_path_collection(const Py::Tuple& args) Py::SeqBase antialiaseds = args[10]; // We don't actually care about urls for Agg, so just ignore it. // Py::SeqBase urls = args[11]; + std::string offset_position = Py::String(args[12]); + + bool data_offsets = (offset_position == "data"); try { @@ -1667,7 +1675,8 @@ RendererAgg::draw_path_collection(const Py::Tuple& args) edgecolors_obj, linewidths, linestyles_obj, - antialiaseds); + antialiaseds, + data_offsets); } catch (const char *e) { @@ -1843,7 +1852,8 @@ RendererAgg::draw_quad_mesh(const Py::Tuple& args) edgecolors_obj, linewidths, linestyles_obj, - antialiaseds); + antialiaseds, + false); } catch (const char* e) { diff --git a/src/_backend_agg.h b/src/_backend_agg.h index 187d1435332c..7d47ee397031 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -264,7 +264,8 @@ class RendererAgg: public Py::PythonExtension const Py::Object& edgecolors_obj, const Py::SeqBase& linewidths, const Py::SeqBase& linestyles_obj, - const Py::SeqBase& antialiaseds); + const Py::SeqBase& antialiaseds, + const bool data_offsets); void _draw_gouraud_triangle( @@ -300,4 +301,3 @@ class _backend_agg_module : public Py::ExtensionModule<_backend_agg_module> #endif - From bcea738ac52eeaf94dab80d4e2ba2c8ffaf52730 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Mon, 14 May 2012 11:52:23 -0400 Subject: [PATCH 2/3] Fixup some variable naming --- lib/matplotlib/axes.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes.py index 1a97aac6b211..0ba73a6dc1c7 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes.py @@ -2729,7 +2729,7 @@ def set_yticks(self, ticks, minor=False): def get_ymajorticklabels(self): """ - Get the major y tick labels as a list of + Get the major y tick labels as a list of :class:`~matplotlib.text.Text` instances. """ return cbook.silent_list('Text yticklabel', @@ -5207,7 +5207,7 @@ def errorbar(self, x, y, yerr=None, xerr=None, only upper/lower limits. In that case a caret symbol is used to indicate this. lims-arguments may be of the same type as *xerr* and *yerr*. - + *errorevery*: positive integer subsamples the errorbars. Eg if everyerror=5, errorbars for every 5-th datapoint will be plotted. The data plot itself still shows @@ -5361,7 +5361,7 @@ def xywhere(xs, ys, mask): leftlo, ylo = xywhere(left, y, xlolims & everymask) caplines.extend( self.plot(leftlo, ylo, 'k|', **plot_kw) ) else: - + leftlo, ylo = xywhere(left, y, everymask) caplines.extend( self.plot(leftlo, ylo, 'k|', **plot_kw) ) @@ -6175,9 +6175,6 @@ def hexbin(self, x, y, C = None, gridsize = 100, bins = None, lattice1.astype(float).ravel(), lattice2.astype(float).ravel())) good_idxs = ~np.isnan(accum) - px = sx * np.array([ 0.5, 0.5, 0.0, -0.5, -0.5, 0.0]) - py = sy * np.array([-0.5, 0.5, 1.0, 0.5, -0.5, -1.0]) / 3.0 - offsets = np.zeros((n, 2), float) offsets[:nx1*ny1,0] = np.repeat(np.arange(nx1), ny1) offsets[:nx1*ny1,1] = np.tile(np.arange(ny1), nx1) @@ -6202,15 +6199,15 @@ def hexbin(self, x, y, C = None, gridsize = 100, bins = None, ymax = 10**ymax self.set_yscale('log') - polygons = np.zeros((6, 2), float) - polygons[:,0] = px - polygons[:,1] = py + polygon = np.zeros((6, 2), float) + polygon[:,0] = sx * np.array([ 0.5, 0.5, 0.0, -0.5, -0.5, 0.0]) + polygon[:,1] = sy * np.array([-0.5, 0.5, 1.0, 0.5, -0.5, -1.0]) / 3.0 if edgecolors=='none': edgecolors = 'face' collection = mcoll.PolyCollection( - [polygons], + [polygon], edgecolors = edgecolors, linewidths = linewidths, offsets = offsets, From 0cfa8f9b170fa0e4843b55ab258a4df5127c5acf Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 22 May 2012 12:06:27 -0400 Subject: [PATCH 3/3] Improve docstring and add CHANGELOG message. --- CHANGELOG | 8 ++++++++ lib/matplotlib/collections.py | 9 +++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3745abbd9e86..265645e86680 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +2012-05-22 Collections now have a setting "offset_position" to select whether + the offsets are given in "screen" coordinates (default, + following the old behavior) or "data" coordinates. This is currently + used internally to improve the performance of hexbin. + + As a result, the "draw_path_collection" backend methods have grown + a new argument "offset_position". - MGD + 2012-05-03 symlog scale now obeys the logarithmic base. Previously, it was completely ignored and always treated as base e. - MGD diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py index e4486d884908..c78141739849 100644 --- a/lib/matplotlib/collections.py +++ b/lib/matplotlib/collections.py @@ -368,7 +368,7 @@ def get_offsets(self): def set_offset_position(self, offset_position): """ - The how offsets are applied. If *offset_position* is 'screen' + Set how offsets are applied. If *offset_position* is 'screen' (default) the offset is applied after the master transform has been applied, that is, the offsets are in screen coordinates. If offset_position is 'data', the offset is applied before the @@ -380,7 +380,12 @@ def set_offset_position(self, offset_position): def get_offset_position(self): """ - Returns the offset position of the collection. + Returns how offsets are applied for the collection. If + *offset_position* is 'screen', the offset is applied after the + master transform has been applied, that is, the offsets are in + screen coordinates. If offset_position is 'data', the offset + is applied before the master transform, i.e., the offsets are + in data coordinates. """ return self._offset_position