From: <md...@us...> - 2007-10-04 17:21:31
|
Revision: 3912 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3912&view=rev Author: mdboom Date: 2007-10-04 10:21:26 -0700 (Thu, 04 Oct 2007) Log Message: ----------- Lots of new docstrings. Reasonably good state for polar plots. r-axis labels can be dragged on polar plots. r-scale can be zoomed on polar plot. Lots of other minor changes too numerous to mention. Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/backends/backend_agg.py branches/transforms/lib/matplotlib/legend.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/patches.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/projections/polar.py branches/transforms/lib/matplotlib/pyplot.py branches/transforms/lib/matplotlib/ticker.py branches/transforms/lib/matplotlib/transforms.py branches/transforms/src/_backend_agg.cpp branches/transforms/src/_backend_agg.h Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/axes.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -478,13 +478,12 @@ """ % {'scale': ' | '.join([repr(x) for x in mscale.get_scale_names()])} martist.Artist.__init__(self) self._position = mtransforms.Bbox.from_lbwh(*rect) - self._originalPosition = copy.deepcopy(self._position) + self._originalPosition = self._position.frozen() self.set_axes(self) self.set_aspect('auto') self.set_adjustable('box') self.set_anchor('C') - # MGDTODO: Check that the axes being shared are scalable self._sharex = sharex self._sharey = sharey if sharex is not None: @@ -540,69 +539,6 @@ self.yaxis = maxis.YAxis(self) self._update_transScale() - def sharex_foreign(self, axforeign): - """ - You can share your x-axis view limits with another Axes in the - same Figure by using the sharex and sharey property of the - Axes. But this doesn't work for Axes in a different figure. - This function sets of the callbacks so that when the xaxis of - this Axes or the Axes in a foreign figure are changed, both - will be synchronized. - - The connection ids for the self.callbacks and - axforeign.callbacks cbook.CallbackRegistry instances are - returned in case you want to disconnect the coupling - """ - - def follow_foreign_xlim(ax): - xmin, xmax = axforeign.get_xlim() - # do not emit here or we'll get a ping png effect - self.set_xlim(xmin, xmax, emit=False) - self.figure.canvas.draw_idle() - - def follow_self_xlim(ax): - xmin, xmax = self.get_xlim() - # do not emit here or we'll get a ping png effect - axforeign.set_xlim(xmin, xmax, emit=False) - axforeign.figure.canvas.draw_idle() - - - cidForeign = axforeign.callbacks.connect('xlim_changed', follow_foreign_xlim) - cidSelf = self.callbacks.connect('xlim_changed', follow_self_xlim) - return cidSelf, cidForeign - - - def sharey_foreign(self, axforeign): - """ - You can share your y-axis view limits with another Axes in the - same Figure by using the sharey and sharey property of the - Axes. But this doesn't work for Axes in a different figure. - This function sets of the callbacks so that when the yaxis of - this Axes or the Axes in a foreign figure are changed, both - will be synchronized. - - The connection ids for the self.callbacks and - axforeign.callbacks cbook.CallbackRegistry instances are - returned in case you want to disconnect the coupling - """ - - def follow_foreign_ylim(ax): - ymin, ymax = axforeign.get_ylim() - # do not emit here or we'll get a ping pong effect - self.set_ylim(ymin, ymax, emit=False) - self.figure.canvas.draw_idle() - - def follow_self_ylim(ax): - ymin, ymax = self.get_ylim() - # do not emit here or we'll get a ping pong effect - axforeign.set_ylim(ymin, ymax, emit=False) - axforeign.figure.canvas.draw_idle() - - - cidForeign = axforeign.callbacks.connect('ylim_changed', follow_foreign_ylim) - cidSelf = self.callbacks.connect('ylim_changed', follow_self_ylim) - return cidSelf, cidForeign - def set_figure(self, fig): """ Set the Axes figure @@ -629,10 +565,6 @@ # It is assumed that this part will have non-linear components self.transScale = mtransforms.TransformWrapper(mtransforms.IdentityTransform()) - self._set_transData() - - # MGDTODO: Rename this method - def _set_transData(self): # A (possibly non-linear) projection on the (already scaled) data self.transProjection = mtransforms.IdentityTransform() @@ -648,34 +580,38 @@ self._yaxis_transform = mtransforms.blended_transform_factory( self.axes.transAxes, self.axes.transData) + self.transData.write_graphviz(open("trans.dot", "w")) + def get_xaxis_transform(self): return self._xaxis_transform def get_xaxis_text1_transform(self, pad_pixels): return (self._xaxis_transform + - mtransforms.Affine2D().translate(0, -1 * pad_pixels)) + mtransforms.Affine2D().translate(0, -1 * pad_pixels), + "top", "center") def get_xaxis_text2_transform(self, pad_pixels): return (self._xaxis_transform + - mtransforms.Affine2D().translate(0, pad_pixels)) + mtransforms.Affine2D().translate(0, pad_pixels), + "top", "center") def get_yaxis_transform(self): return self._yaxis_transform def get_yaxis_text1_transform(self, pad_pixels): return (self._yaxis_transform + - mtransforms.Affine2D().translate(-1 * pad_pixels, 0)) + mtransforms.Affine2D().translate(-1 * pad_pixels, 0), + "center", "right") def get_yaxis_text2_transform(self, pad_pixels): return (self._yaxis_transform + - mtransforms.Affine2D().translate(pad_pixels, 0)) + mtransforms.Affine2D().translate(pad_pixels, 0), + "center", "right") def _update_transScale(self): self.transScale.set( mtransforms.blended_transform_factory( self.xaxis.get_transform(), self.yaxis.get_transform())) - - self.transData.make_graphviz(open("trans.dot", "w")) def get_position(self, original=False): 'Return the axes rectangle left, bottom, width, height' @@ -1164,7 +1100,8 @@ # limits and set the bound to be the bounds of the xydata. # Otherwise, it will compute the bounds of it's current data # and the data in xydata - xys = npy.asarray(xys) + if not ma.isMaskedArray(xys): + xys = npy.asarray(xys) self.update_datalim_numerix(xys[:, 0], xys[:, 1]) def update_datalim_numerix(self, x, y): @@ -1535,7 +1472,9 @@ for other in self._shared_x_axes.get_siblings(self): if other is not self: other.set_xlim(self.viewLim.intervalx, emit=False) - + + self.figure.canvas.draw_idle() + return xmin, xmax def get_xscale(self): @@ -1641,7 +1580,9 @@ for other in self._shared_y_axes.get_siblings(self): if other is not self: other.set_ylim(self.viewLim.ymin, self.viewLim.ymax, emit=False) - + + self.figure.canvas.draw_idle() + return ymin, ymax def get_yscale(self): @@ -1795,8 +1736,19 @@ """ self._navigate_mode = b - def drag_pan(self, button, key, startx, starty, dx, dy, - start_lim, start_trans): + def start_pan(self, x, y, button): + self._pan_start = cbook.Bunch( + lim = self.viewLim.frozen(), + trans = self.transData.frozen(), + trans_inverse = self.transData.inverted().frozen(), + x = x, + y = y + ) + + def end_pan(self): + del self._pan_start + + def drag_pan(self, button, key, x, y): def format_deltas(key, dx, dy): if key=='control': if(abs(dx)>abs(dy)): @@ -1818,22 +1770,24 @@ dx=dx/abs(dx)*abs(dy) return (dx,dy) + p = self._pan_start + dx = x - p.x + dy = y - p.y if button == 1: - inverse = start_trans.inverted() dx, dy = format_deltas(key, dx, dy) - result = self.bbox.frozen().translated(-dx, -dy).transformed(inverse) + result = self.bbox.frozen().translated(-dx, -dy) \ + .transformed(p.trans_inverse) elif button == 3: try: # MGDTODO: This is broken with log scales - inverse = start_trans.inverted() dx, dy = format_deltas(key, dx, dy) dx = -dx / float(self.bbox.width) dy = -dy / float(self.bbox.height) - xmin, ymin, xmax, ymax = start_lim.lbrt + xmin, ymin, xmax, ymax = p.lim.lbrt alpha = npy.power(10.0, (dx, dy)) - start = inverse.transform_point((startx, starty)) - lim_points = start_lim.get_points() + start = p.trans_inverse.transform_point((p.x, p.y)) + lim_points = p.lim.get_points() result = start + alpha * (lim_points - start) result = mtransforms.Bbox(result) except OverflowError: @@ -5214,485 +5168,16 @@ label.set_visible(firstcol) def subplot_class_factory(axes_class=None): - # MGDTODO: This is a little bit strange to make a new class on the - # fly like this, but it keeps things relatively similar to how they - # were before + # This makes a new class that inherits from SubclassBase and the + # given axes_class (which is assumed to be a subclass of Axes). + + # This is perhaps a little bit roundabout to make a new class on + # the fly like this, but it means that a new Subplot class does + # not have to be created for every type of Axes. new_class = new.classobj("%sSubplot" % (axes_class.__name__), (SubplotBase, axes_class), {'_axes_class': axes_class}) return new_class - - -class PolarAxes(Axes): - """ - Make a PolarAxes. The rectangular bounding box of the axes is given by - - - PolarAxes(position=[left, bottom, width, height]) - - where all the arguments are fractions in [0,1] which specify the - fraction of the total figure window. - - axisbg is the color of the axis background - - Attributes: - thetagridlines : a list of Line2D for the theta grids - rgridlines : a list of Line2D for the radial grids - thetagridlabels : a list of Text for the theta grid labels - rgridlabels : a list of Text for the theta grid labels - - """ - - RESOLUTION = 100 - - def __init__(self, *args, **kwarg): - """ - See Axes base class for args and kwargs documentation - """ - Axes.__init__(self, *args, **kwarg) - self.set_aspect('equal', adjustable='box', anchor='C') - self.cla() - def _init_axis(self): - "nuthin to do" - self.xaxis = None - self.yaxis = None - - - def _set_lim_and_transforms(self): - """ - set the dataLim and viewLim BBox attributes and the - transData and transAxes Transformation attributes - """ - - # the lim are theta, r - - # MGDTODO -# Bbox = mtrans.Bbox -# Value = mtrans.Value -# Point = mtrans.Point -# self.dataLim = Bbox( Point( Value(5/4.*math.pi), Value(math.sqrt(2))), -# Point( Value(1/4.*math.pi), Value(math.sqrt(2)))) -# self.viewLim = Bbox( Point( Value(5/4.*math.pi), Value(math.sqrt(2))), -# Point( Value(1/4.*math.pi), Value(math.sqrt(2)))) - -# self.transData = mtrans.NonseparableTransformation( -# self.viewLim, self.bbox, -# mtrans.FuncXY(mtrans.POLAR)) -# self.transAxes = mtrans.get_bbox_transform( -# mtrans.unit_bbox(), self.bbox) - pass - - def contains(self,mouseevent): - """Test whether the mouse event occured in the axes. - - Returns T/F, {} - """ - if callable(self._contains): return self._contains(self,mouseevent) - - x,y = self.axes.transAxes.inverse_xy_tup((mouseevent.x,mouseevent.y)) - #print "Polar: x,y = ",x,y - inside = (x-0.5)**2 + (y-0.5)**2 <= 0.25 - return inside,{} - - def cla(self): - 'Clear the current axes' - - # init these w/ some arbitrary numbers - they'll be updated as - # data is added to the axes - - self._get_lines = _process_plot_var_args(self) - self._get_patches_for_fill = _process_plot_var_args(self, 'fill') - - self._gridOn = rcParams['polaraxes.grid'] - self.thetagridlabels = [] - self.thetagridlines = [] - self.rgridlabels = [] - self.rgridlines = [] - - self.lines = [] - self.images = [] - self.patches = [] - self.artists = [] - self.collections = [] - self.texts = [] # text in axis coords - self.legend_ = None - - self.grid(self._gridOn) - props = font_manager.FontProperties(size=rcParams['axes.titlesize']) - self.title = mtext.Text( - x=0.5, y=1.05, text='', - fontproperties=props, - verticalalignment='bottom', - horizontalalignment='center', - ) - self.title.set_transform(self.transAxes) - - self._set_artist_props(self.title) - - - self.thetas = npy.linspace(0, 2*math.pi, self.RESOLUTION) - - verts = zip(self.thetas, npy.ones(self.RESOLUTION)) - self.axesPatch = mpatches.Polygon( - verts, - facecolor=self._axisbg, - edgecolor=rcParams['axes.edgecolor'], - ) - - - - self.axesPatch.set_figure(self.figure) - self.axesPatch.set_transform(self.transData) - self.axesPatch.set_linewidth(rcParams['axes.linewidth']) - self.axison = True - - # we need to set a view and data interval from 0->rmax to make - # the formatter and locator work correctly - # MGDTODO - Value = mtrans.Value - Interval = mtrans.Interval - self.rintv = Interval(Value(0), Value(1)) - self.rintd = Interval(Value(0), Value(1)) - - self.rformatter = mticker.ScalarFormatter() - self.rformatter.set_view_interval(self.rintv) - self.rformatter.set_data_interval(self.rintd) - - class RadialLocator(mticker.AutoLocator): - 'enforce strictly positive radial ticks' - - def __call__(self): - ticks = mticker.AutoLocator.__call__(self) - return [t for t in ticks if t>0] - - self.rlocator = RadialLocator() - self.rlocator.set_view_interval(self.rintv) - self.rlocator.set_data_interval(self.rintd) - - - angles = npy.arange(0, 360, 45) - radii = npy.arange(0.2, 1.1, 0.2) - self.set_thetagrids(angles) - self.set_rgrids(radii) - - def get_children(self): - 'return a list of child artists' - children = [] - children.extend(self.rgridlines) - children.extend(self.rgridlabels) - children.extend(self.thetagridlines) - children.extend(self.thetagridlabels) - children.extend(self.lines) - children.extend(self.patches) - children.extend(self.texts) - children.extend(self.artists) - children.extend(self.images) - if self.legend_ is not None: - children.append(self.legend_) - children.extend(self.collections) - children.append(self.title) - children.append(self.axesPatch) - return children - - - def set_rmax(self, rmax): - self.rintv.set_bounds(0, rmax) - self.regrid(rmax) - - def grid(self, b): - 'Set the axes grids on or off; b is a boolean' - self._gridOn = b - - def regrid(self, rmax): - rmax = float(rmax) - self.axesPatch.xy = zip(self.thetas, rmax*npy.ones(self.RESOLUTION)) - - val = rmax*math.sqrt(2) - self.viewLim.intervaly().set_bounds(val, val) - - ticks = self.rlocator() - self.set_rgrids(ticks) - self.rformatter.set_locs(ticks) - - for t in self.thetagridlabels: - t.set_y(1.05*rmax) - - r = npy.linspace(0, rmax, self.RESOLUTION) - for l in self.thetagridlines: - l.set_ydata(r) - - def autoscale_view(self, scalex=True, scaley=True): - 'set the view limits to include all the data in the axes' - self.rintd.set_bounds(0, self.get_rmax()) - rmin, rmax = self.rlocator.autoscale() - self.rintv.set_bounds(rmin, rmax) - self.regrid(rmax) - - def set_rgrids(self, radii, labels=None, angle=22.5, rpad=0.05, **kwargs): - """ - set the radial locations and labels of the r grids - - The labels will appear at radial distances radii at angle - - labels, if not None, is a len(radii) list of strings of the - labels to use at each angle. - - if labels is None, the self.rformatter will be used - - rpad is a fraction of the max of radii which will pad each of - the radial labels in the radial direction. - - Return value is a list of lines, labels where the lines are - lines.Line2D instances and the labels are text.Text - instances - - kwargs control the rgrid Text label properties: - %(Text)s - - ACCEPTS: sequence of floats - """ - - - radii = npy.asarray(radii) - rmin = radii.min() - if rmin<=0: - raise ValueError('radial grids must be strictly positive') - - rpad = rpad * max(radii) - cbook.popall(self.rgridlines) - - theta = npy.linspace(0., 2*math.pi, self.RESOLUTION) - ls = rcParams['grid.linestyle'] - color = rcParams['grid.color'] - lw = rcParams['grid.linewidth'] - - rmax = self.get_rmax() - for r in radii: - r = npy.ones(self.RESOLUTION)*r - line = mlines.Line2D(theta, r, linestyle=ls, color=color, linewidth=lw, - figure=self.figure) - line.set_transform(self.transData) - self.rgridlines.append(line) - - cbook.popall(self.rgridlabels) - - - color = rcParams['xtick.color'] - - - props = font_manager.FontProperties(size=rcParams['xtick.labelsize']) - if labels is None: - labels = [self.rformatter(r,0) for r in radii] - for r,l in zip(radii, labels): - t = mtext.Text(angle/180.*math.pi, r+rpad, l, - fontproperties=props, color=color, - horizontalalignment='center', verticalalignment='center') - t.set_transform(self.transData) - t.update(kwargs) - self._set_artist_props(t) - t.set_clip_on(False) - self.rgridlabels.append(t) - - return self.rgridlines, self.rgridlabels - set_rgrids.__doc__ = cbook.dedent(set_rgrids.__doc__) % martist.kwdocd - - def set_thetagrids(self, angles, labels=None, fmt='%d', frac = 1.1, - **kwargs): - """ - set the angles at which to place the theta grids (these - gridlines are equal along the theta dimension). angles is in - degrees - - labels, if not None, is a len(angles) list of strings of the - labels to use at each angle. - - if labels is None, the labels with be fmt%%angle - - frac is the fraction of the polar axes radius at which to - place the label (1 is the edge).Eg 1.05 isd outside the axes - and 0.95 is inside the axes - - Return value is a list of lines, labels where the lines are - lines.Line2D instances and the labels are Text - instances: - - kwargs are optional text properties for the labels - %(Text)s - ACCEPTS: sequence of floats - """ - cbook.popall(self.thetagridlines) - ox, oy = 0,0 - ls = rcParams['grid.linestyle'] - color = rcParams['grid.color'] - lw = rcParams['grid.linewidth'] - - rmax = self.get_rmax() - r = npy.linspace(0., rmax, self.RESOLUTION) - for a in angles: - theta = npy.ones(self.RESOLUTION)*a/180.*math.pi - line = mlines.Line2D( - theta, r, linestyle=ls, color=color, linewidth=lw, - figure=self.figure) - line.set_transform(self.transData) - self.thetagridlines.append(line) - - cbook.popall(self.thetagridlabels) - - color = rcParams['xtick.color'] - - props = font_manager.FontProperties(size=rcParams['xtick.labelsize']) - r = frac*rmax - if labels is None: - labels = [fmt%a for a in angles] - for a,l in zip(angles, labels): - t = mtext.Text(a/180.*math.pi, r, l, fontproperties=props, color=color, - horizontalalignment='center', verticalalignment='center') - t.set_transform(self.transData) - t.update(kwargs) - self._set_artist_props(t) - t.set_clip_on(False) - self.thetagridlabels.append(t) - return self.thetagridlines, self.thetagridlabels - set_thetagrids.__doc__ = cbook.dedent(set_thetagrids.__doc__) % martist.kwdocd - - def get_rmax(self): - 'get the maximum radius in the view limits dimension' - vmin, vmax = self.dataLim.intervaly().get_bounds() - return max(vmin, vmax) - - def draw(self, renderer): - if not self.get_visible(): return - renderer.open_group('polar_axes') - self.apply_aspect(1) - self.transData.freeze() # eval the lazy objects - self.transAxes.freeze() # eval the lazy objects - - verts = self.axesPatch.get_verts() - tverts = self.transData.seq_xy_tups(verts) - - #for i,v,t in zip(range(len(verts)), verts, tverts): - # print i,v,t - - - - l,b,w,h = self.figure.bbox.get_bounds() - clippath = agg.path_storage() - for i, xy in enumerate(tverts): - x,y = xy - y = h-y - if i==0: clippath.move_to(x, y) - else: clippath.line_to(x, y) - clippath.close_polygon() - - #self._update_axes() - if self.axison: - if self._frameon: self.axesPatch.draw(renderer) - - if self._gridOn: - for l in self.rgridlines: - l.set_clip_path(clippath) - l.draw(renderer) - - for l in self.thetagridlines: - l.set_clip_path(clippath) - l.draw(renderer) - - for a in self.lines:# + self.patches: - a.set_clip_path(clippath) - - artists = [] - artists.extend(self.lines) - artists.extend(self.texts) - artists.extend(self.collections) - artists.extend(self.patches) - artists.extend(self.artists) - - dsu = [ (a.zorder, a) for a in artists] - dsu.sort() - - for zorder, a in dsu: - a.draw(renderer) - - - for t in self.thetagridlabels+self.rgridlabels: - t.draw(renderer) - - if self.legend_ is not None: - self.legend_.draw(renderer) - - self.title.draw(renderer) - - - - self.transData.thaw() # release the lazy objects - self.transAxes.thaw() # release the lazy objects - renderer.close_group('polar_axes') - - - def format_coord(self, theta, r): - 'return a format string formatting the coordinate' - theta /= math.pi - return 'theta=%1.2fpi, r=%1.3f'%(theta, r) - - - def has_data(self): - 'return true if any artists have been added to axes' - return len(self.lines)+len(self.collections) - - def set_xlabel(self, xlabel, fontdict=None, **kwargs): - 'xlabel not implemented' - raise NotImplementedError('xlabel not defined for polar axes (yet)') - - def set_ylabel(self, ylabel, fontdict=None, **kwargs): - 'ylabel not implemented' - raise NotImplementedError('ylabel not defined for polar axes (yet)') - - def set_xlim(self, xmin=None, xmax=None, emit=True, **kwargs): - 'xlim not implemented' - raise NotImplementedError('xlim not meaningful for polar axes') - - def set_ylim(self, ymin=None, ymax=None, emit=True, **kwargs): - 'ylim not implemented' - raise NotImplementedError('ylim not meaningful for polar axes') - - def get_xscale(self): - 'return the xaxis scale string' - return 'polar' - - def get_yscale(self): - 'return the yaxis scale string' - return 'polar' - - def table(self, *args, **kwargs): - """ - TABLE(*args, **kwargs) - Not implemented for polar axes - """ - raise NotImplementedError('table not implemented for polar axes') - - - -class PolarSubplot(SubplotBase, PolarAxes): - """ - Create a polar subplot with - - PolarSubplot(numRows, numCols, plotNum) - - where plotNum=1 is the first plot number and increasing plotNums - fill rows first. max(plotNum)==numRows*numCols - - You can leave out the commas if numRows<=numCols<=plotNum<10, as - in - - Subplot(211) # 2 rows, 1 column, first (upper) plot - """ - def __str__(self): - return "PolarSubplot(%gx%g)"%(self.figW,self.figH) - def __init__(self, fig, *args, **kwargs): - SubplotBase.__init__(self, fig, *args) - PolarAxes.__init__( - self, fig, - [self.figLeft, self.figBottom, self.figW, self.figH], **kwargs) martist.kwdocd['Axes'] = martist.kwdocd['Subplot'] = martist.kwdoc(Axes) Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/axis.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -166,7 +166,7 @@ def draw(self, renderer): if not self.get_visible(): return renderer.open_group(self.__name__) - midPoint = interval_contains_open(self.get_view_interval(), self.get_loc() ) + midPoint = interval_contains(self.get_view_interval(), self.get_loc()) if midPoint: if self.gridOn: @@ -176,8 +176,10 @@ if self.tick2On: self.tick2line.draw(renderer) - if self.label1On: self.label1.draw(renderer) - if self.label2On: self.label2.draw(renderer) + if self.label1On: + self.label1.draw(renderer) + if self.label2On: + self.label2.draw(renderer) renderer.close_group(self.__name__) @@ -237,17 +239,19 @@ # get the affine as an a,b,c,d,tx,ty list # x in data coords, y in axes coords #t = Text( + trans, vert, horiz = self.axes.get_xaxis_text1_transform(self._padPixels) + t = TextWithDash( x=loc, y=0, fontproperties=FontProperties(size=rcParams['xtick.labelsize']), color=rcParams['xtick.color'], - verticalalignment='top', - horizontalalignment='center', + verticalalignment=vert, + horizontalalignment=horiz, dashdirection=0, xaxis=True, ) - t.set_transform(self.axes.get_xaxis_text1_transform(self._padPixels)) + t.set_transform(trans) self._set_artist_props(t) return t @@ -257,17 +261,19 @@ 'Get the default Text 2 instance' # x in data coords, y in axes coords #t = Text( + trans, vert, horiz = self.axes.get_xaxis_text2_transform(self._padPixels) + t = TextWithDash( x=loc, y=1, fontproperties=FontProperties(size=rcParams['xtick.labelsize']), color=rcParams['xtick.color'], - verticalalignment='bottom', + verticalalignment=vert, dashdirection=1, xaxis=True, - horizontalalignment='center', + horizontalalignment=horiz, ) - t.set_transform(self.axes.get_xaxis_text2_transform(self._padPixels)) + t.set_transform(trans) self._set_artist_props(t) return t @@ -277,7 +283,6 @@ l = Line2D( xdata=(loc,), ydata=(0,), color='k', linestyle = 'None', - antialiased=False, marker = self._xtickmarkers[0], markersize=self._size, ) @@ -291,7 +296,6 @@ l = Line2D( xdata=(loc,), ydata=(1,), color='k', linestyle = 'None', - antialiased=False, marker = self._xtickmarkers[1], markersize=self._size, ) @@ -307,7 +311,6 @@ color=rcParams['grid.color'], linestyle=rcParams['grid.linestyle'], linewidth=rcParams['grid.linewidth'], - antialiased=False, ) l.set_transform(self.axes.get_xaxis_transform()) self._set_artist_props(l) @@ -356,16 +359,18 @@ 'Get the default Text instance' # x in axes coords, y in data coords #t = Text( - t = TextWithDash( + trans, vert, horiz = self.axes.get_yaxis_text1_transform(self._padPixels) + + t = TextWithDash( x=0, y=loc, fontproperties=FontProperties(size=rcParams['ytick.labelsize']), color=rcParams['ytick.color'], - verticalalignment='center', - horizontalalignment='right', + verticalalignment=vert, + horizontalalignment=horiz, dashdirection=0, xaxis=False, ) - t.set_transform(self.axes.get_yaxis_text1_transform(self._padPixels)) + t.set_transform(trans) #t.set_transform( self.axes.transData ) self._set_artist_props(t) return t @@ -374,16 +379,18 @@ 'Get the default Text instance' # x in axes coords, y in data coords #t = Text( + trans, vert, horiz = self.axes.get_yaxis_text2_transform(self._padPixels) + t = TextWithDash( x=1, y=loc, fontproperties=FontProperties(size=rcParams['ytick.labelsize']), color=rcParams['ytick.color'], - verticalalignment='center', + verticalalignment=vert, dashdirection=1, xaxis=False, - horizontalalignment='left', + horizontalalignment=horiz, ) - t.set_transform(self.axes.get_yaxis_text2_transform(self._padPixels)) + t.set_transform(trans) self._set_artist_props(t) return t @@ -392,7 +399,6 @@ # x in axes coords, y in data coords l = Line2D( (0,), (loc,), color='k', - antialiased=False, marker = self._ytickmarkers[0], linestyle = 'None', markersize=self._size, @@ -405,7 +411,6 @@ 'Get the default line2D instance' # x in axes coords, y in data coords l = Line2D( (1,), (0,), color='k', - antialiased=False, marker = self._ytickmarkers[1], linestyle = 'None', markersize=self._size, @@ -422,7 +427,6 @@ color=rcParams['grid.color'], linestyle=rcParams['grid.linestyle'], linewidth=rcParams['grid.linewidth'], - antialiased=False, ) l.set_transform(self.axes.get_yaxis_transform()) @@ -523,8 +527,8 @@ def get_children(self): children = [self.label] - majorticks = self.get_major_ticks(len(self.major.locator())) - minorticks = self.get_minor_ticks(len(self.minor.locator())) + majorticks = self.get_major_ticks() + minorticks = self.get_minor_ticks() children.extend(majorticks) children.extend(minorticks) @@ -560,8 +564,8 @@ def set_clip_path(self, clippath, transform=None): Artist.set_clip_path(self, clippath, transform) - majorticks = self.get_major_ticks(len(self.major.locator())) - minorticks = self.get_minor_ticks(len(self.minor.locator())) + majorticks = self.get_major_ticks() + minorticks = self.get_minor_ticks() for child in self.majorTicks + self.minorTicks: child.set_clip_path(clippath, transform) @@ -729,9 +733,11 @@ 'Get the formatter of the minor ticker' return self.minor.formatter - def get_major_ticks(self, numticks): + def get_major_ticks(self, numticks=None): 'get the tick instances; grow as necessary' - + if numticks is None: + numticks = len(self.get_major_locator()()) + if len(self.majorTicks)<numticks: # update the new tick label properties from the old protoTick = self.majorTicks[0] @@ -746,8 +752,11 @@ return ticks - def get_minor_ticks(self, numticks): + def get_minor_ticks(self, numticks=None): 'get the minor tick instances; grow as necessary' + if numticks is None: + numticks = len(self.get_minor_locator()()) + if len(self.minorTicks)<numticks: protoTick = self.minorTicks[0] for i in range(numticks-len(self.minorTicks)): Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -34,40 +34,22 @@ def draw_path(self, gc, path, transform, rgbFace=None): """ - Handles the caching of the native path associated with the - given path and calls the underlying backend's _draw_path to - actually do the drawing. + Draws a Path instance using the given affine transform. """ - # MGDTODO: Update docstring raise NotImplementedError def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): """ - This method is currently underscore hidden because the - draw_markers method is being used as a sentinel for newstyle - backend drawing + Draws a marker at each of the vertices in path. This includes + all vertices, including control points on curves. To avoid + that behavior, those vertices should be removed before calling + this function. - path - a matplotlib.agg.path_storage instance - - Draw the marker specified in path with graphics context gc at - each of the locations in arrays x and y. trans is a - matplotlib.transforms.Transformation instance used to - transform x and y to display coords. It consists of an - optional nonlinear component and an affine. You can access - these two components as - - if transform.need_nonlinear(): - x,y = transform.nonlinear_only_numerix(x, y) - # the a,b,c,d,tx,ty affine which transforms x and y - vec6 = transform.as_vec6_val() - ...backend dependent affine... + marker_trans is an affine transform applied to the marker. + trans is an affine transform applied to the path. """ - # MGDTODO: Update docstring raise NotImplementedError - def _draw_native_markers(self, gc, native_marker_path, marker_trans, path, trans, rgbFace=None): - raise NotImplementedError - def get_image_magnification(self): """ Get the factor by which to magnify images passed to draw_image. @@ -1560,7 +1542,8 @@ self._xypress=[] for i, a in enumerate(self.canvas.figure.get_axes()): if x is not None and y is not None and a.in_axes(event) and a.get_navigate(): - self._xypress.append((x, y, a, i, a.viewLim.frozen(), a.transData.frozen())) + a.start_pan(x, y, event.button) + self._xypress.append((a, i)) self.canvas.mpl_disconnect(self._idDrag) self._idDrag=self.canvas.mpl_connect('motion_notify_event', self.drag_pan) @@ -1597,14 +1580,12 @@ lims.append( (xmin, xmax, ymin, ymax) ) # Store both the original and modified positions pos.append( ( - copy.copy(a.get_position(True)), - copy.copy(a.get_position() )) ) + a.get_position(True).frozen(), + a.get_position().frozen() ) ) self._views.push(lims) self._positions.push(pos) self.set_history_buttons() - - def release(self, event): 'this will be called whenever mouse button is released' pass @@ -1613,6 +1594,8 @@ 'the release mouse button callback in pan/zoom mode' self.canvas.mpl_disconnect(self._idDrag) self._idDrag=self.canvas.mpl_connect('motion_notify_event', self.mouse_move) + for a, ind in self._xypress: + a.end_pan() if not self._xypress: return self._xypress = None self._button_pressed=None @@ -1623,12 +1606,10 @@ def drag_pan(self, event): 'the drag callback in pan/zoom mode' - for lastx, lasty, a, ind, old_lim, old_trans in self._xypress: + for a, ind in self._xypress: #safer to use the recorded button at the press than current button: #multiple button can get pressed during motion... - dx, dy = event.x - lastx, event.y - lasty - a.drag_pan(self._button_pressed, event.key, lastx, lasty, dx, dy, - old_lim, old_trans) + a.drag_pan(self._button_pressed, event.key, event.x, event.y) self.dynamic_update() def release_zoom(self, event): Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -96,15 +96,6 @@ The renderer handles all the drawing primitives using a graphics context instance that controls the colors/styles """ - # MGDTODO: Renderers seem to get created and destroyed fairly - # often so the paths are cached at the class (not instance) level. - # However, this dictionary is only directly used by RendererBase, - # so it seems funny to define it here. However, if we didn't, the - # native paths would be shared across renderers, which is - # obviously bad. Seems like a good use of metaclasses, but that - # also seems like a heavy solution for a minor problem. - _native_paths = weakref.WeakKeyDictionary() - debug=1 texd = {} # a cache of tex image rasters def __init__(self, width, height, dpi): @@ -130,12 +121,10 @@ if __debug__: verbose.report('RendererAgg.__init__ done', 'debug-annoying') - # MGDTODO: Just adding helpful asserts. This can be removed in the future def draw_path(self, gc, path, trans, rgbFace=None): assert trans.is_affine self._renderer.draw_path(gc, path, trans.frozen(), rgbFace) - # MGDTODO: Just adding helpful asserts. This can be removed in the future def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): assert marker_trans.is_affine assert trans.is_affine Modified: branches/transforms/lib/matplotlib/legend.py =================================================================== --- branches/transforms/lib/matplotlib/legend.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/legend.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -256,10 +256,10 @@ bboxesText = [t.get_window_extent(renderer) for t in self.texts] bboxesHandles = [h.get_window_extent(renderer) for h in self.legendHandles if h is not None] - bboxesAll = bboxesText bboxesAll.extend(bboxesHandles) bbox = Bbox.union(bboxesAll) + self.save = bbox ibox = bbox.inverse_transformed(self.get_transform()) @@ -558,7 +558,7 @@ handle.set_height(h/2) # Set the data for the legend patch - bbox = copy.copy(self._get_handle_text_bbox(renderer)) + bbox = self._get_handle_text_bbox(renderer).frozen() bbox = bbox.expanded(1 + self.pad, 1 + self.pad) l, b, w, h = bbox.bounds Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/lines.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -393,7 +393,6 @@ def recache(self): #if self.axes is None: print 'recache no axes' #else: print 'recache units', self.axes.xaxis.units, self.axes.yaxis.units - # MGDTODO: Deal with units x = ma.asarray(self.convert_xunits(self._xorig), float) y = ma.asarray(self.convert_yunits(self._yorig), float) @@ -414,12 +413,12 @@ self._x = self._xy[:, 0] # just a view self._y = self._xy[:, 1] # just a view self._logcache = None + # Masked arrays are now handled by the Path class itself self._path = Path(self._xy, closed=False) self._transformed_path = TransformedPath(self._path, self.get_transform()) - # MGDTODO: If _draw_steps is removed, remove the following line also - self._step_path = None + def set_transform(self, t): """ set the Transformation instance used by this artist @@ -434,47 +433,8 @@ if len(x)<2: return 1 return npy.alltrue(x[1:]-x[0:-1]>=0) - # MGDTODO: Remove me (seems to be used for old-style interface only) - def _get_plottable(self): - # If log scale is set, only pos data will be returned - - x, y = self._x, self._y - - # MGDTODO: (log-scaling) - -# try: logx = self.get_transform().get_funcx().get_type()==LOG10 -# except RuntimeError: logx = False # non-separable - -# try: logy = self.get_transform().get_funcy().get_type()==LOG10 -# except RuntimeError: logy = False # non-separable - - if True: - return x, y - - if self._logcache is not None: - waslogx, waslogy, xcache, ycache = self._logcache - if logx==waslogx and waslogy==logy: - return xcache, ycache - - Nx = len(x) - Ny = len(y) - - if logx: indx = npy.greater(x, 0) - else: indx = npy.ones(len(x)) - - if logy: indy = npy.greater(y, 0) - else: indy = npy.ones(len(y)) - - ind, = npy.nonzero(npy.logical_and(indx, indy)) - x = npy.take(x, ind) - y = npy.take(y, ind) - - self._logcache = logx, logy, x, y - return x, y - - def draw(self, renderer): - #renderer.open_group('line2d') + renderer.open_group('line2d') if not self._visible: return self._newstyle = hasattr(renderer, 'draw_markers') @@ -498,7 +458,6 @@ lineFunc = getattr(self, funcname) lineFunc(renderer, gc, *self._transformed_path.get_transformed_path_and_affine()) - # MGDTODO: Deal with markers if self._marker is not None: gc = renderer.new_gc() self._set_gc_clip(gc) @@ -509,7 +468,7 @@ markerFunc = getattr(self, funcname) markerFunc(renderer, gc, *self._transformed_path.get_transformed_path_and_affine()) - #renderer.close_group('line2d') + renderer.close_group('line2d') def get_antialiased(self): return self._antialiased def get_color(self): return self._color @@ -694,29 +653,7 @@ def _draw_nothing(self, *args, **kwargs): pass - - def _draw_steps(self, renderer, gc, path): - # We generate the step function path on-the-fly, and then cache it. - # The cache may be later invalidated when the data changes - # (in self.recache()) - - # MGDTODO: Untested -- using pylab.step doesn't actually trigger - # this code -- the path is "stepped" before even getting to this - # class. Perhaps this should be removed here, since it is not as - # powerful as what is in axes.step() anyway. - if self._step_path is None: - vertices = self._path.vertices - codes = self._path.codes - siz = len(vertices) - if siz<2: return - new_vertices = npy.zeros((2*siz, 2), vertices.dtype) - new_vertices[0:-1:2, 0], new_vertices[1:-1:2, 0], newvertices[-1, 0] = vertices[:, 0], vertices[1:, 0], vertices[-1, 0] - new_vertices[0:-1:2, 1], new_vertices[1::2, 1] = vertices[:, 1], vertices[:, 1] - self._step_path = Path(new_vertices, closed=False) - gc.set_linestyle('solid') - renderer.draw_path(gc, self._step_path, self.get_transform()) - - + def _draw_solid(self, renderer, gc, path, trans): gc.set_linestyle('solid') renderer.draw_path(gc, path, trans) Modified: branches/transforms/lib/matplotlib/patches.py =================================================================== --- branches/transforms/lib/matplotlib/patches.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/patches.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -12,8 +12,6 @@ import matplotlib.mlab as mlab import matplotlib.artist as artist from matplotlib.path import Path -# MGDTODO: Maybe this belongs elsewhere -from matplotlib.backends._backend_agg import point_in_path # these are not available for the object inspector until after the # class is build so we define an initial set here for the init @@ -93,8 +91,8 @@ # method. if callable(self._contains): return self._contains(self,mouseevent) - inside = point_in_path(mouseevent.x, mouseevent.y, self.get_path(), - self.get_transform().frozen()) + inside = self.get_path().contains_point( + (mouseevent.x, mouseevent.y), self.get_transform()) return inside, {} def update_from(self, other): @@ -236,8 +234,8 @@ def get_window_extent(self, renderer=None): - trans_path = self.get_path().transformed(self.get_path_transform()) - return Bbox.unit().update_from_data(trans_path.vertices) + return Bbox.from_lbrt( + get_path_extents(self.get_path(), self.get_patch_transform())) def set_lw(self, val): @@ -341,7 +339,9 @@ Patch.__init__(self, **kwargs) - self._bbox = transforms.Bbox.from_lbwh(xy[0], xy[1], width, height) + left, right = self.convert_xunits((xy[0], xy[0] + width)) + bottom, top = self.convert_yunits((xy[1], xy[1] + height)) + self._bbox = transforms.Bbox.from_lbrt(left, bottom, right, top) self._rect_transform = transforms.BboxTransform( transforms.Bbox.unit(), self._bbox) __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd @@ -352,10 +352,6 @@ """ return Path.unit_rectangle() - # MGDTODO: Convert units -# left, right = self.convert_xunits((x, x + self.width)) -# bottom, top = self.convert_yunits((y, y + self.height)) - def get_patch_transform(self): return self._rect_transform Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/path.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -1,3 +1,9 @@ +""" +Contains a class for managing paths (polylines). + +October 2007 Michael Droettboom +""" + import math import numpy as npy @@ -3,7 +9,47 @@ from numpy import ma as ma +from matplotlib.backends._backend_agg import point_in_path, get_path_extents +from matplotlib.cbook import simple_linear_interpolation + KAPPA = 4.0 * (npy.sqrt(2) - 1) / 3.0 class Path(object): + """ + Path represents a series of possibly disconnected, possibly + closed, line and curve segments. + + The underlying storage is made up of two parallel numpy arrays: + vertices: an Nx2 float array of vertices + codes: an N-length uint8 array of vertex types + + These two arrays always have the same length in the first + dimension. Therefore, to represent a cubic curve, you must + provide three vertices as well as three codes "CURVE3". + + The code types are: + + STOP : 1 vertex (ignored) + A marker for the end of the entire path (currently not + required and ignored) + + MOVETO : 1 vertex + Pick up the pen and move to the given vertex. + + LINETO : 1 vertex + Draw a line from the current position to the given vertex. + + CURVE3 : 1 control point, 1 endpoint + Draw a quadratic Bezier curve from the current position, + with the given control point, to the given end point. + + CURVE4 : 2 control points, 1 endpoint + Draw a cubic Bezier curve from the current position, with + the given control points, to the given end point. + + CLOSEPOLY : 1 vertex (ignored) + Draw a line segment to the start point of the current + polyline. + """ + # Path codes STOP = 0 # 1 vertex @@ -13,19 +59,31 @@ CURVE3 = 3 # 2 vertices CURVE4 = 4 # 3 vertices CLOSEPOLY = 5 # 1 vertex - ### - # MGDTODO: I'm not sure these are supported by PS/PDF/SVG, - # so if they don't, we probably shouldn't - CURVEN = 6 - CATROM = 7 - UBSPLINE = 8 - #### NUM_VERTICES = [1, 1, 1, 2, 3, 1] code_type = npy.uint8 def __init__(self, vertices, codes=None, closed=True): + """ + Create a new path with the given vertices and codes. + + vertices is an Nx2 numpy float array. + + codes is an N-length numpy array of type Path.code_type. + + See the docstring of Path for a description of the various + codes. + + These two arrays must have the same length in the first + dimension. + + If codes is None, vertices will be treated as a series of line + segments. Additionally, if closed is also True, the polyline + will closed. If vertices contains masked values, the + resulting path will be compressed, with MOVETO codes inserted + in the correct places to jump over the masked regions. + """ vertices = ma.asarray(vertices, npy.float_) if codes is None: @@ -82,6 +140,11 @@ vertices = property(_get_vertices) def iter_endpoints(self): + """ + Iterates over all of the endpoints in the path. Unlike + iterating directly over the vertices array, curve control + points are skipped over. + """ i = 0 NUM_VERTICES = self.NUM_VERTICES vertices = self.vertices @@ -95,11 +158,57 @@ i += 1 def transformed(self, transform): + """ + Return a transformed copy of the path. + + See transforms.TransformedPath for a path that will cache the + transformed result and automatically update when the transform + changes. + """ return Path(transform.transform(self.vertices), self.codes) - + + def contains_point(self, point, transform=None): + """ + Returns True if the path contains the given point. + + If transform is not None, the path will be transformed before + performing the test. + """ + if transform is None: + from transforms import IdentityTransform + transform = IdentityTransform + return point_in_path(point[0], point[1], self, transform.frozen()) + + def get_extents(self, transform=None): + """ + Returns the extents (xmin, ymin, xmax, ymax) of the path. + + Unlike computing the extents on the vertices alone, this + algorithm will take into account the curves and deal with + control points appropriately. + """ + from transforms import Bbox, IdentityTransform + if transform is None: + transform = IdentityTransform + return Bbox.from_lbrt(*get_path_extents(self, transform)) + + def interpolated(self, steps): + """ + Returns a new path resampled to length N x steps. + Does not currently handle interpolating curves. + """ + vertices = simple_linear_interpolation(self.vertices, steps) + codes = self.codes + new_codes = Path.LINETO * npy.ones(((len(codes) - 1) * steps + 1, )) + new_codes[0::steps] = codes + return Path(vertices, new_codes) + _unit_rectangle = None #@classmethod def unit_rectangle(cls): + """ + Returns a Path of the unit rectangle from (0, 0) to (1, 1). + """ if cls._unit_rectangle is None: cls._unit_rectangle = \ Path([[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]) @@ -109,9 +218,14 @@ _unit_regular_polygons = {} #@classmethod def unit_regular_polygon(cls, numVertices): + """ + Returns a Path for a unit regular polygon with the given + numVertices and radius of 1.0, centered at (0, 0). + """ path = cls._unit_regular_polygons.get(numVertices) if path is None: - theta = 2*npy.pi/numVertices * npy.arange(numVertices).reshape((numVertices, 1)) + theta = (2*npy.pi/numVertices * + npy.arange(numVertices).reshape((numVertices, 1))) # This initial rotation is to make sure the polygon always # "points-up" theta += npy.pi / 2.0 @@ -124,6 +238,10 @@ _unit_circle = None #@classmethod def unit_circle(cls): + """ + Returns a Path of the unit circle. The circle is approximated + using cubic Bezier curves. + """ if cls._unit_circle is None: offset = KAPPA vertices = npy.array( @@ -158,6 +276,10 @@ #@classmethod def arc(cls, theta1, theta2, is_wedge=False): + """ + Returns an arc on the unit circle from angle theta1 to angle + theta2 (in degrees). + """ # From Masionobe, L. 2003. "Drawing an elliptical arc using # polylines, quadratic or cubic Bezier curves". # @@ -234,5 +356,9 @@ arc = classmethod(arc) def wedge(cls, theta1, theta2): + """ + Returns a wedge of the unit circle from angle theta1 to angle + theta2 (in degrees). + """ return cls.arc(theta1, theta2, True) wedge = classmethod(wedge) Modified: branches/transforms/lib/matplotlib/projections/polar.py =================================================================== --- branches/transforms/lib/matplotlib/projections/polar.py 2007-10-03 23:05:30 UTC (rev 3911) +++ branches/transforms/lib/matplotlib/projections/polar.py 2007-10-04 17:21:26 UTC (rev 3912) @@ -2,9 +2,12 @@ import numpy as npy +from matplotlib.artist import kwdocd from matplotlib.axes import Axes +from matplotlib import cbook from matplotlib.patches import Circle -from matplotlib.ticker import Locator +from matplotlib.path import Path +from matplotlib.ticker import Formatter, Locator from matplotlib.transforms import Affine2D, Affine2DBase, Bbox, BboxTransform, \ IdentityTransform, Transform, TransformWrapper @@ -16,6 +19,10 @@ output_dims = 2 is_separable = False + def __init__(self, resolution): + Transform.__init__(self) + self._resolution = resolution + def transform(self, tr): xy = npy.zeros(tr.shape, npy.float_) t = tr[:, 0:1] @@ -27,6 +34,12 @@ return xy transform_non_affine = transform + def transform_path(self, path): + if len(path.vertices) == 2: + path = path.interpolated(self._resolution) + return Path(self.transform(path.vertices), path.codes) + transform_path_non_affine = transform_path + def inverted(self): return PolarAxes.InvertedPolarTransform() @@ -64,10 +77,35 @@ def inverted(self): return PolarAxes.PolarTransform() - class ThetaLocator(Locator): - pass + class ThetaFormatter(Formatter): + def __call__(self, x, pos=None): + return u"%d\u00b0" % ((x / npy.pi) * 180.0) + + class RadialLocator(Locator): + def __init__(self, base): + self.base = base + + def __call__(self): + ticks = self.base() + # MGDTODO: Use numpy + return [x for x in ticks if x > 0] + + def autoscale(self): + return self.base.autoscale() + + def pan(self, numsteps): + return self.base.pan(numsteps) + + def zoom(self, direction): + return self.base.zoom(direction) + + def refresh(self): + return self.base.refresh() + + RESOLUTION = 100 def __init__(self, *args, **kwargs): + self._rpad = 0.05 Axes.__init__(self, *args, **kwargs) self.set_aspect('equal', adjustable='box', anchor='C') self.cla() @@ -76,55 +114,105 @@ def cla(self): Axes.cla(self) - self.xaxis.set_major_locator(PolarAxes.ThetaLocator()) - - def _set_transData(self): + self.xaxis.set_major_formatter(self.ThetaFormatter()) + angles = npy.arange(0.0, 360.0, 45.0) + self.set_thetagrids(angles) + self.yaxis.set_major_locator(self.RadialLocator(self.yaxis.get_major_locator())) + + def _set_lim_and_transforms(self): + self.dataLim = Bbox([[0.0, 0.0], [npy.pi * 2.0, 1.0]]) + self.viewLim = Bbox.unit() + self.transAxes = BboxTransform(Bbox.unit(), self.bbox) + + # Transforms the x and y axis separately by a scale factor + # It is assumed that this part will have non-linear components + self.transScale = TransformWrapper(IdentityTransform()) + # A (possibly non-linear) projection on the (already scaled) data - self.transProjection = self.PolarTransform() + self.transProjection = self.PolarTransform(self.RESOLUTION) # An affine transformation on the data, generally to limit the # range of the axes self.transProjectionAffine = self.PolarAffine(self.viewLim) self.transData = self.transScale + self.transProjection + \ - self.transProjectionAffine + self.transAxes + (self.transProjectionAffine + self.transAxes) self._xaxis_transform = ( - self.PolarTransform() + + self.transProjection + self.PolarAffine(Bbox.unit()) + self.transAxes) + self._theta_label1_position = Affine2D().translate(0.0, 1.1) + self._xaxis_text1_transform = ( + self._theta_label1_positi... [truncated message content] |