Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1f2a750

Browse files
committed
Merge pull request #859 from mdboom/hexbin-simplified
Make hexbin much faster
2 parents 85cfca2 + 0cfa8f9 commit 1f2a750

11 files changed

Lines changed: 170 additions & 49 deletions

File tree

CHANGELOG

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2012-05-22 Collections now have a setting "offset_position" to select whether
2+
the offsets are given in "screen" coordinates (default,
3+
following the old behavior) or "data" coordinates. This is currently
4+
used internally to improve the performance of hexbin.
5+
6+
As a result, the "draw_path_collection" backend methods have grown
7+
a new argument "offset_position". - MGD
8+
19
2012-05-03 symlog scale now obeys the logarithmic base. Previously, it was
210
completely ignored and always treated as base e. - MGD
311

examples/pylab_examples/hexbin_demo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import matplotlib.cm as cm
1010
import matplotlib.pyplot as plt
1111

12+
np.random.seed(0)
1213
n = 100000
1314
x = np.random.standard_normal(n)
1415
y = 2.0 + 3.0 * x + 4.0 * np.random.standard_normal(n)
@@ -33,4 +34,3 @@
3334
cb.set_label('log10(N)')
3435

3536
plt.show()
36-

lib/matplotlib/axes.py

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2729,7 +2729,7 @@ def set_yticks(self, ticks, minor=False):
27292729

27302730
def get_ymajorticklabels(self):
27312731
"""
2732-
Get the major y tick labels as a list of
2732+
Get the major y tick labels as a list of
27332733
:class:`~matplotlib.text.Text` instances.
27342734
"""
27352735
return cbook.silent_list('Text yticklabel',
@@ -5207,7 +5207,7 @@ def errorbar(self, x, y, yerr=None, xerr=None,
52075207
only upper/lower limits. In that case a caret symbol is
52085208
used to indicate this. lims-arguments may be of the same
52095209
type as *xerr* and *yerr*.
5210-
5210+
52115211
*errorevery*: positive integer
52125212
subsamples the errorbars. Eg if everyerror=5, errorbars for every
52135213
5-th datapoint will be plotted. The data plot itself still shows
@@ -5361,7 +5361,7 @@ def xywhere(xs, ys, mask):
53615361
leftlo, ylo = xywhere(left, y, xlolims & everymask)
53625362
caplines.extend( self.plot(leftlo, ylo, 'k|', **plot_kw) )
53635363
else:
5364-
5364+
53655365
leftlo, ylo = xywhere(left, y, everymask)
53665366
caplines.extend( self.plot(leftlo, ylo, 'k|', **plot_kw) )
53675367

@@ -6175,43 +6175,44 @@ def hexbin(self, x, y, C = None, gridsize = 100, bins = None,
61756175
lattice1.astype(float).ravel(), lattice2.astype(float).ravel()))
61766176
good_idxs = ~np.isnan(accum)
61776177

6178-
px = xmin + sx * np.array([ 0.5, 0.5, 0.0, -0.5, -0.5, 0.0])
6179-
py = ymin + sy * np.array([-0.5, 0.5, 1.0, 0.5, -0.5, -1.0]) / 3.0
6180-
6181-
polygons = np.zeros((6, n, 2), float)
6182-
polygons[:,:nx1*ny1,0] = np.repeat(np.arange(nx1), ny1)
6183-
polygons[:,:nx1*ny1,1] = np.tile(np.arange(ny1), nx1)
6184-
polygons[:,nx1*ny1:,0] = np.repeat(np.arange(nx2) + 0.5, ny2)
6185-
polygons[:,nx1*ny1:,1] = np.tile(np.arange(ny2), nx2) + 0.5
6186-
6178+
offsets = np.zeros((n, 2), float)
6179+
offsets[:nx1*ny1,0] = np.repeat(np.arange(nx1), ny1)
6180+
offsets[:nx1*ny1,1] = np.tile(np.arange(ny1), nx1)
6181+
offsets[nx1*ny1:,0] = np.repeat(np.arange(nx2) + 0.5, ny2)
6182+
offsets[nx1*ny1:,1] = np.tile(np.arange(ny2), nx2) + 0.5
6183+
offsets[:,0] *= sx
6184+
offsets[:,1] *= sy
6185+
offsets[:,0] += xmin
6186+
offsets[:,1] += ymin
61876187
# remove accumulation bins with no data
6188-
polygons = polygons[:,good_idxs,:]
6188+
offsets = offsets[good_idxs,:]
61896189
accum = accum[good_idxs]
61906190

6191-
polygons = np.transpose(polygons, axes=[1,0,2])
6192-
polygons[:,:,0] *= sx
6193-
polygons[:,:,1] *= sy
6194-
polygons[:,:,0] += px
6195-
polygons[:,:,1] += py
6196-
61976191
if xscale=='log':
6198-
polygons[:,:,0] = 10**(polygons[:,:,0])
6192+
offsets[:,0] = 10**(offsets[:,0])
61996193
xmin = 10**xmin
62006194
xmax = 10**xmax
62016195
self.set_xscale('log')
62026196
if yscale=='log':
6203-
polygons[:,:,1] = 10**(polygons[:,:,1])
6197+
offsets[:,1] = 10**(offsets[:,1])
62046198
ymin = 10**ymin
62056199
ymax = 10**ymax
62066200
self.set_yscale('log')
62076201

6202+
polygon = np.zeros((6, 2), float)
6203+
polygon[:,0] = sx * np.array([ 0.5, 0.5, 0.0, -0.5, -0.5, 0.0])
6204+
polygon[:,1] = sy * np.array([-0.5, 0.5, 1.0, 0.5, -0.5, -1.0]) / 3.0
6205+
62086206
if edgecolors=='none':
62096207
edgecolors = 'face'
6208+
62106209
collection = mcoll.PolyCollection(
6211-
polygons,
6210+
[polygon],
62126211
edgecolors = edgecolors,
62136212
linewidths = linewidths,
6214-
transOffset = self.transData,
6213+
offsets = offsets,
6214+
transOffset = mtransforms.IdentityTransform(),
6215+
offset_position = "data"
62156216
)
62166217

62176218
if isinstance(norm, mcolors.LogNorm):

lib/matplotlib/backend_bases.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,16 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None)
186186

187187
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
188188
offsets, offsetTrans, facecolors, edgecolors,
189-
linewidths, linestyles, antialiaseds, urls):
189+
linewidths, linestyles, antialiaseds, urls,
190+
offset_position):
190191
"""
191192
Draws a collection of paths selecting drawing properties from
192193
the lists *facecolors*, *edgecolors*, *linewidths*,
193194
*linestyles* and *antialiaseds*. *offsets* is a list of
194195
offsets to apply to each of the paths. The offsets in
195196
*offsets* are first transformed by *offsetTrans* before being
196-
applied.
197+
applied. *offset_position* may be either "screen" or "data"
198+
depending on the space that the offsets are in.
197199
198200
This provides a fallback implementation of
199201
:meth:`draw_path_collection` that makes multiple calls to
@@ -213,8 +215,9 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
213215
path_ids.append((path, transform))
214216

215217
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
216-
gc, path_ids, offsets, offsetTrans, facecolors, edgecolors,
217-
linewidths, linestyles, antialiaseds, urls):
218+
gc, master_transform, all_transforms, path_ids, offsets,
219+
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
220+
antialiaseds, urls, offset_position):
218221
path, transform = path_id
219222
transform = transforms.Affine2D(transform.get_matrix()).translate(xo, yo)
220223
self.draw_path(gc0, path, transform, rgbFace)
@@ -240,7 +243,7 @@ def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
240243

241244
return self.draw_path_collection(
242245
gc, master_transform, paths, [], offsets, offsetTrans, facecolors,
243-
edgecolors, linewidths, [], [antialiased], [None])
246+
edgecolors, linewidths, [], [antialiased], [None], 'screen')
244247

245248
def draw_gouraud_triangle(self, gc, points, colors, transform):
246249
"""
@@ -302,9 +305,10 @@ def _iter_collection_raw_paths(self, master_transform, paths,
302305
transform = all_transforms[i % Ntransforms]
303306
yield path, transform + master_transform
304307

305-
def _iter_collection(self, gc, path_ids, offsets, offsetTrans, facecolors,
306-
edgecolors, linewidths, linestyles, antialiaseds,
307-
urls):
308+
def _iter_collection(self, gc, master_transform, all_transforms,
309+
path_ids, offsets, offsetTrans, facecolors,
310+
edgecolors, linewidths, linestyles,
311+
antialiaseds, urls, offset_position):
308312
"""
309313
This is a helper method (along with
310314
: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,
330334
*path_ids*; *gc* is a graphics context and *rgbFace* is a color to
331335
use for filling the path.
332336
"""
337+
Ntransforms = len(all_transforms)
333338
Npaths = len(path_ids)
334339
Noffsets = len(offsets)
335340
N = max(Npaths, Noffsets)
@@ -359,6 +364,15 @@ def _iter_collection(self, gc, path_ids, offsets, offsetTrans, facecolors,
359364
path_id = path_ids[i % Npaths]
360365
if Noffsets:
361366
xo, yo = toffsets[i % Noffsets]
367+
if offset_position == 'data':
368+
if Ntransforms:
369+
transform = all_transforms[i % Ntransforms] + master_transform
370+
else:
371+
transform = master_transform
372+
xo, yo = transform.transform_point((xo, yo))
373+
xp, yp = transform.transform_point((0, 0))
374+
xo = -(xp - xo)
375+
yo = -(yp - yo)
362376
if Nfacecolors:
363377
rgbFace = facecolors[i % Nfacecolors]
364378
if Nedgecolors:

lib/matplotlib/backends/backend_macosx.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None)
6060

6161
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
6262
offsets, offsetTrans, facecolors, edgecolors,
63-
linewidths, linestyles, antialiaseds, urls):
63+
linewidths, linestyles, antialiaseds, urls,
64+
offset_position):
6465
cliprect = gc.get_clip_rectangle()
6566
clippath, clippath_transform = gc.get_clip_path()
6667
if all_transforms:

lib/matplotlib/backends/backend_pdf.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ def __init__(self, filename):
452452
self.markers = {}
453453
self.multi_byte_charprocs = {}
454454

455+
self.paths = []
456+
455457
# The PDF spec recommends to include every procset
456458
procsets = [ Name(x)
457459
for x in "PDF Text ImageB ImageC ImageI".split() ]
@@ -505,9 +507,12 @@ def close(self):
505507
xobjects[tup[0]] = tup[1]
506508
for name, value in self.multi_byte_charprocs.iteritems():
507509
xobjects[name] = value
510+
for name, path, trans, ob, join, cap, padding in self.paths:
511+
xobjects[name] = ob
508512
self.writeObject(self.XObjectObject, xobjects)
509513
self.writeImages()
510514
self.writeMarkers()
515+
self.writePathCollectionTemplates()
511516
self.writeObject(self.pagesObject,
512517
{ 'Type': Name('Pages'),
513518
'Kids': self.pageList,
@@ -1259,6 +1264,28 @@ def writeMarkers(self):
12591264
self.output(Op.paint_path(False, fillp, strokep))
12601265
self.endStream()
12611266

1267+
def pathCollectionObject(self, gc, path, trans, padding):
1268+
name = Name('P%d' % len(self.paths))
1269+
ob = self.reserveObject('path %d' % len(self.paths))
1270+
self.paths.append(
1271+
(name, path, trans, ob, gc.get_joinstyle(), gc.get_capstyle(), padding))
1272+
return name
1273+
1274+
def writePathCollectionTemplates(self):
1275+
for (name, path, trans, ob, joinstyle, capstyle, padding) in self.paths:
1276+
pathops = self.pathOperations(path, trans, simplify=False)
1277+
bbox = path.get_extents(trans)
1278+
bbox = bbox.padded(padding)
1279+
self.beginStream(
1280+
ob.id, None,
1281+
{'Type': Name('XObject'), 'Subtype': Name('Form'),
1282+
'BBox': list(bbox.extents)})
1283+
self.output(GraphicsContextPdf.joinstyles[joinstyle], Op.setlinejoin)
1284+
self.output(GraphicsContextPdf.capstyles[capstyle], Op.setlinecap)
1285+
self.output(*pathops)
1286+
self.output(Op.paint_path(False, True, True))
1287+
self.endStream()
1288+
12621289
@staticmethod
12631290
def pathOperations(path, transform, clip=None, simplify=None):
12641291
cmds = []
@@ -1466,6 +1493,32 @@ def draw_path(self, gc, path, transform, rgbFace=None):
14661493
rgbFace is None and gc.get_hatch_path() is None)
14671494
self.file.output(self.gc.paint())
14681495

1496+
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
1497+
offsets, offsetTrans, facecolors, edgecolors,
1498+
linewidths, linestyles, antialiaseds, urls,
1499+
offset_position):
1500+
1501+
padding = np.max(linewidths)
1502+
path_codes = []
1503+
for i, (path, transform) in enumerate(self._iter_collection_raw_paths(
1504+
master_transform, paths, all_transforms)):
1505+
name = self.file.pathCollectionObject(gc, path, transform, padding)
1506+
path_codes.append(name)
1507+
1508+
output = self.file.output
1509+
output(Op.gsave)
1510+
lastx, lasty = 0, 0
1511+
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
1512+
gc, master_transform, all_transforms, path_codes, offsets,
1513+
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
1514+
antialiaseds, urls, offset_position):
1515+
1516+
self.check_gc(gc0, rgbFace)
1517+
dx, dy = xo - lastx, yo - lasty
1518+
output(1, 0, 0, 1, dx, dy, Op.concat_matrix, path_id, Op.use_xobject)
1519+
lastx, lasty = xo, yo
1520+
output(Op.grestore)
1521+
14691522
def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None):
14701523
# For simple paths or small numbers of markers, don't bother
14711524
# making an XObject

lib/matplotlib/backends/backend_ps.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None)
625625

626626
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
627627
offsets, offsetTrans, facecolors, edgecolors,
628-
linewidths, linestyles, antialiaseds, urls):
628+
linewidths, linestyles, antialiaseds, urls,
629+
offset_position):
629630
write = self._pswriter.write
630631

631632
path_codes = []
@@ -640,8 +641,9 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
640641
path_codes.append(name)
641642

642643
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
643-
gc, path_codes, offsets, offsetTrans, facecolors, edgecolors,
644-
linewidths, linestyles, antialiaseds, urls):
644+
gc, master_transform, all_transforms, path_codes, offsets,
645+
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
646+
antialiaseds, urls, offset_position):
645647
ps = "%g %g %s" % (xo, yo, path_id)
646648
self._draw_ps(ps, gc0, rgbFace)
647649

lib/matplotlib/backends/backend_svg.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,8 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None)
577577

578578
def draw_path_collection(self, gc, master_transform, paths, all_transforms,
579579
offsets, offsetTrans, facecolors, edgecolors,
580-
linewidths, linestyles, antialiaseds, urls):
580+
linewidths, linestyles, antialiaseds, urls,
581+
offset_position):
581582
writer = self.writer
582583
path_codes = []
583584
writer.start(u'defs')
@@ -592,8 +593,9 @@ def draw_path_collection(self, gc, master_transform, paths, all_transforms,
592593
writer.end(u'defs')
593594

594595
for xo, yo, path_id, gc0, rgbFace in self._iter_collection(
595-
gc, path_codes, offsets, offsetTrans, facecolors, edgecolors,
596-
linewidths, linestyles, antialiaseds, urls):
596+
gc, master_transform, all_transforms, path_codes, offsets,
597+
offsetTrans, facecolors, edgecolors, linewidths, linestyles,
598+
antialiaseds, urls, offset_position):
597599
clipid = self._get_clip(gc0)
598600
url = gc0.get_url()
599601
if url is not None:

0 commit comments

Comments
 (0)