From: <md...@us...> - 2007-09-10 17:39:47
|
Revision: 3822 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3822&view=rev Author: mdboom Date: 2007-09-10 10:39:37 -0700 (Mon, 10 Sep 2007) Log Message: ----------- Baby steps and horrible breakage on transforms branch. Modified Paths: -------------- branches/transforms/lib/matplotlib/artist.py branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/backends/backend_agg.py branches/transforms/lib/matplotlib/figure.py branches/transforms/lib/matplotlib/legend.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/pylab.py branches/transforms/lib/matplotlib/table.py branches/transforms/lib/matplotlib/text.py branches/transforms/lib/matplotlib/transforms.py branches/transforms/lib/matplotlib/widgets.py Modified: branches/transforms/lib/matplotlib/artist.py =================================================================== --- branches/transforms/lib/matplotlib/artist.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/artist.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -1,7 +1,7 @@ from __future__ import division import sys, re from cbook import iterable, flatten -from transforms import identity_transform +from affine import Affine2D import matplotlib.units as units ## Note, matplotlib artists use the doc strings for set and get @@ -145,7 +145,7 @@ def get_transform(self): 'return the Transformation instance used by this artist' if self._transform is None: - self._transform = identity_transform() + self._transform = Affine2D() return self._transform def hitlist(self,event): Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -9,6 +9,8 @@ rcParams = matplotlib.rcParams from matplotlib import artist as martist +from matplotlib import affine as maffine +from matplotlib import bbox as mbbox from matplotlib import agg from matplotlib import axis as maxis from matplotlib import cbook @@ -23,11 +25,11 @@ from matplotlib import mlab from matplotlib import cm from matplotlib import patches as mpatches +from matplotlib import pbox as mpbox from matplotlib import quiver as mquiver from matplotlib import table as mtable from matplotlib import text as mtext from matplotlib import ticker as mticker -from matplotlib import transforms as mtrans iterable = cbook.iterable is_string_like = cbook.is_string_like @@ -413,14 +415,7 @@ yield seg remaining=remaining[2:] -ValueType=type(mtrans.zero()) -def makeValue(v): - if type(v) == ValueType: - return v - else: - return mtrans.Value(v) - class Axes(martist.Artist): """ The Axes contains most of the figure elements: Axis, Tick, Line2D, @@ -434,10 +429,15 @@ """ - scaled = {mtrans.IDENTITY : 'linear', - mtrans.LOG10 : 'log', + # MGDTODO +# scaled = {mtrans.IDENTITY : 'linear', +# mtrans.LOG10 : 'log', +# } + scaled = {0 : 'linear', + 1 : 'log', } + def __str__(self): return "Axes(%g,%g;%gx%g)"%(self._position[0].get(),self._position[1].get(), self._position[2].get(),self._position[3].get()) @@ -485,7 +485,7 @@ """ martist.Artist.__init__(self) - self._position = map(makeValue, rect) + self._position = rect self._originalPosition = rect self.set_axes(self) self.set_aspect('auto') @@ -629,12 +629,9 @@ self.right = (l+w)*figw self.top = (b+h)*figh - - Bbox = mtrans.Bbox - Point = mtrans.Point - self.bbox = Bbox( - Point(self.left, self.bottom), - Point(self.right, self.top ), + self.bbox = mbbox.Bbox.from_lbrt( + self.left, self.bottom, + self.right, self.top, ) #these will be updated later as data is added self._set_lim_and_transforms() @@ -644,40 +641,36 @@ set the dataLim and viewLim BBox attributes and the transData and transAxes Transformation attributes """ - - - one = mtrans.one - zero = mtrans.zero - Point = mtrans.Point - Bbox = mtrans.Bbox + Bbox = mbbox.Bbox if self._sharex is not None: - left=self._sharex.viewLim.ll().x() - right=self._sharex.viewLim.ur().x() + left = self._sharex.viewLim.xmin() + right = self._sharex.viewLim.xmax() else: - left=zero() - right=one() + left = 0.0 + right = 1.0 if self._sharey is not None: - bottom=self._sharey.viewLim.ll().y() - top=self._sharey.viewLim.ur().y() + bottom = self._sharey.viewLim.ymin() + top = self._sharey.viewLim.ymax() else: - bottom=zero() - top=one() + bottom = 0.0 + top = 1.0 - self.viewLim = Bbox(Point(left, bottom), Point(right, top)) - self.dataLim = mtrans.unit_bbox() + self.viewLim = Bbox.from_lbrt(left, bottom, right, top) + self.dataLim = Bbox.unit() - self.transData = mtrans.get_bbox_transform( + self.transData = maffine.get_bbox_transform( self.viewLim, self.bbox) - self.transAxes = mtrans.get_bbox_transform( - mtrans.unit_bbox(), self.bbox) + self.transAxes = maffine.get_bbox_transform( + Bbox.unit(), self.bbox) - if self._sharex: - self.transData.set_funcx(self._sharex.transData.get_funcx()) + # MGDTODO +# if self._sharex: +# self.transData.set_funcx(self._sharex.transData.get_funcx()) - if self._sharey: - self.transData.set_funcy(self._sharey.transData.get_funcy()) +# if self._sharey: +# self.transData.set_funcy(self._sharey.transData.get_funcy()) def get_position(self, original=False): 'Return the axes rectangle left, bottom, width, height' @@ -861,7 +854,7 @@ """ ACCEPTS: ['C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'] """ - if anchor in mtrans.PBox.coefs.keys() or len(anchor) == 2: + if anchor in mpbox.PBox.coefs.keys() or len(anchor) == 2: self._anchor = anchor else: raise ValueError('argument must be among %s' % @@ -901,7 +894,7 @@ if data_ratio is None: data_ratio = ysize/xsize box_aspect = A * data_ratio - pb = mtrans.PBox(self._originalPosition) + pb = mpbox.PBox(self._originalPosition) pb1 = pb.shrink_to_aspect(box_aspect, fig_aspect) self.set_position(pb1.anchor(self._anchor), 'active') return @@ -1538,11 +1531,12 @@ if xmin is None: xmin = old_xmin if xmax is None: xmax = old_xmax - if (self.transData.get_funcx().get_type()==mtrans.LOG10 - and min(xmin, xmax)<=0): - raise ValueError('Cannot set nonpositive limits with log transform') + # MGDTODO +# if (self.transData.get_funcx().get_type()==mtrans.LOG10 +# and min(xmin, xmax)<=0): +# raise ValueError('Cannot set nonpositive limits with log transform') - xmin, xmax = mtrans.nonsingular(xmin, xmax, increasing=False) + xmin, xmax = mbbox.nonsingular(xmin, xmax, increasing=False) self.viewLim.intervalx().set_bounds(xmin, xmax) if emit: self.callbacks.process('xlim_changed', self) @@ -1574,19 +1568,22 @@ #if subsx is None: subsx = range(2, basex) assert(value.lower() in ('log', 'linear', )) if value == 'log': - self.xaxis.set_major_locator(mticker.LogLocator(basex)) - self.xaxis.set_major_formatter(mticker.LogFormatterMathtext(basex)) - self.xaxis.set_minor_locator(mticker.LogLocator(basex,subsx)) - self.transData.get_funcx().set_type(mtrans.LOG10) - minx, maxx = self.get_xlim() - if min(minx, maxx)<=0: - self.autoscale_view() + # MGDTODO +# self.xaxis.set_major_locator(mticker.LogLocator(basex)) +# self.xaxis.set_major_formatter(mticker.LogFormatterMathtext(basex)) +# self.xaxis.set_minor_locator(mticker.LogLocator(basex,subsx)) +# self.transData.get_funcx().set_type(mtrans.LOG10) +# minx, maxx = self.get_xlim() +# if min(minx, maxx)<=0: +# self.autoscale_view() + pass elif value == 'linear': self.xaxis.set_major_locator(mticker.AutoLocator()) self.xaxis.set_major_formatter(mticker.ScalarFormatter()) self.xaxis.set_minor_locator(mticker.NullLocator()) self.xaxis.set_minor_formatter(mticker.NullFormatter()) - self.transData.get_funcx().set_type( mtrans.IDENTITY ) + # self.transData.get_funcx().set_type( mtrans.IDENTITY ) + self.transData.get_funcx().set_type( 0 ) # MGDTODO def get_xticks(self): 'Return the x ticks as a list of locations' @@ -1659,11 +1656,12 @@ if ymin is None: ymin = old_ymin if ymax is None: ymax = old_ymax - if (self.transData.get_funcy().get_type()==mtrans.LOG10 - and min(ymin, ymax)<=0): - raise ValueError('Cannot set nonpositive limits with log transform') + # MGDTODO +# if (self.transData.get_funcy().get_type()==mtrans.LOG10 +# and min(ymin, ymax)<=0): +# raise ValueError('Cannot set nonpositive limits with log transform') - ymin, ymax = mtrans.nonsingular(ymin, ymax, increasing=False) + ymin, ymax = mbbox.nonsingular(ymin, ymax, increasing=False) self.viewLim.intervaly().set_bounds(ymin, ymax) if emit: self.callbacks.process('ylim_changed', self) @@ -1696,21 +1694,24 @@ assert(value.lower() in ('log', 'linear', )) if value == 'log': - self.yaxis.set_major_locator(mticker.LogLocator(basey)) - self.yaxis.set_major_formatter(mticker.LogFormatterMathtext(basey)) - self.yaxis.set_minor_locator(mticker.LogLocator(basey,subsy)) - self.transData.get_funcy().set_type(mtrans.LOG10) - miny, maxy = self.get_ylim() - if min(miny, maxy)<=0: - self.autoscale_view() - + # MGDTODO +# self.yaxis.set_major_locator(mticker.LogLocator(basey)) +# self.yaxis.set_major_formatter(mticker.LogFormatterMathtext(basey)) +# self.yaxis.set_minor_locator(mticker.LogLocator(basey,subsy)) +# self.transData.get_funcy().set_type(mtrans.LOG10) +# miny, maxy = self.get_ylim() +# if min(miny, maxy)<=0: +# self.autoscale_view() + pass + elif value == 'linear': self.yaxis.set_major_locator(mticker.AutoLocator()) self.yaxis.set_major_formatter(mticker.ScalarFormatter()) self.yaxis.set_minor_locator(mticker.NullLocator()) self.yaxis.set_minor_formatter(mticker.NullFormatter()) - self.transData.get_funcy().set_type( mtrans.IDENTITY ) - + # self.transData.get_funcy().set_type( mtrans.IDENTITY ) MGDTODO + self.transData.get_funcy().set_type( 0 ) + def get_yticks(self): 'Return the y ticks as a list of locations' return self.yaxis.get_ticklocs() @@ -1744,9 +1745,11 @@ def toggle_log_lineary(self): 'toggle between log and linear on the y axis' - funcy = self.transData.get_funcy().get_type() - if funcy==mtrans.LOG10: self.set_yscale('linear') - elif funcy==mtrans.IDENTITY: self.set_yscale('log') + # MGDTODO +# funcy = self.transData.get_funcy().get_type() +# if funcy==mtrans.LOG10: self.set_yscale('linear') +# elif funcy==mtrans.IDENTITY: self.set_yscale('log') + pass def xaxis_date(self, tz=None): """Sets up x-axis ticks and labels that treat the x data as dates. @@ -2172,7 +2175,7 @@ %(Annotation)s """ a = mtext.Annotation(*args, **kwargs) - a.set_transform(mtrans.identity_transform()) + a.set_transform(maffine.Affine2D.identity()) self._set_artist_props(a) if kwargs.has_key('clip_on'): a.set_clip_box(self.bbox) self.texts.append(a) @@ -2211,7 +2214,7 @@ %(Line2D)s """ - trans = mtrans.blend_xy_sep_transform( self.transAxes, self.transData) + trans = maffine.blend_xy_sep_transform( self.transAxes, self.transData) l, = self.plot([xmin,xmax], [y,y], transform=trans, scalex=False, **kwargs) return l @@ -2247,7 +2250,7 @@ %(Line2D)s """ - trans = mtrans.blend_xy_sep_transform( self.transData, self.transAxes ) + trans = maffine.blend_xy_sep_transform( self.transData, self.transAxes ) l, = self.plot([x,x], [ymin,ymax] , transform=trans, scaley=False, **kwargs) return l @@ -2286,7 +2289,7 @@ %(Polygon)s """ # convert y axis units - trans = mtrans.blend_xy_sep_transform( self.transAxes, self.transData) + trans = maffine.blend_xy_sep_transform( self.transAxes, self.transData) verts = (xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin) p = mpatches.Polygon(verts, **kwargs) p.set_transform(trans) @@ -2326,7 +2329,7 @@ %(Polygon)s """ # convert x axis units - trans = mtrans.blend_xy_sep_transform(self.transData, self.transAxes) + trans = maffine.blend_xy_sep_transform(self.transData, self.transAxes) verts = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)] p = mpatches.Polygon(verts, **kwargs) p.set_transform(trans) @@ -4105,7 +4108,7 @@ offsets = zip(x,y), transOffset = self.transData, ) - collection.set_transform(mtrans.identity_transform()) + collection.set_transform(maffine.Affine2D()) collection.set_alpha(alpha) collection.update(kwargs) @@ -5299,21 +5302,22 @@ # the lim are theta, r - 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)))) + # 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) +# 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. @@ -5380,6 +5384,7 @@ # 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)) Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -15,8 +15,8 @@ from ticker import NullFormatter, FixedFormatter, ScalarFormatter, LogFormatter from ticker import NullLocator, FixedLocator, LinearLocator, LogLocator, AutoLocator -from transforms import Value, blend_xy_sep_transform,\ - translation_transform, bbox_all, identity_transform +from affine import Affine2D, blend_xy_sep_transform +from bbox import bbox_union from font_manager import FontProperties from text import Text, TextWithDash, _process_text_args from patches import bbox_artist @@ -81,16 +81,16 @@ if self._tickdir == 'in': self._xtickmarkers = (TICKUP, TICKDOWN) self._ytickmarkers = (TICKRIGHT, TICKLEFT) - self._pad = Value(pad) + self._pad = pad else: self._xtickmarkers = (TICKDOWN, TICKUP) self._ytickmarkers = (TICKLEFT, TICKRIGHT) - self._pad = Value(pad + size) + self._pad = pad + size self._loc = loc self._size = size - self._padPixels = self.figure.dpi*self._pad*Value(1/72.0) + self._padPixels = self.figure.dpi * self._pad * (1/72.0) self.tick1line = self._get_tick1line(loc) @@ -236,14 +236,11 @@ xaxis=True, ) - - trans = blend_xy_sep_transform( self.axes.transData, - self.axes.transAxes) + trans = blend_xy_sep_transform( + self.axes.transData, self.axes.transAxes) #offset the text downward with a post transformation - transOffset = translation_transform( - Value(0), Value(-1)*self._padPixels) - trans.set_offset( (0,0), transOffset) - t.set_transform( trans) + trans = trans + Affine2D().translated(0, -1 * self._padPixels) + t.set_transform(trans) self._set_artist_props(t) return t @@ -264,12 +261,10 @@ horizontalalignment='center', ) - trans = blend_xy_sep_transform( self.axes.transData, - self.axes.transAxes) + trans = blend_xy_sep_transformation( + self.axes.transData, self.axes.transAxes) # offset the text upward with a post transformation - transOffset = translation_transform( - Value(0), self._padPixels) - trans.set_offset( (0,0), transOffset) + trans = trans + Affine2D().translated(0, self._padPixels) t.set_transform( trans ) self._set_artist_props(t) return t @@ -284,8 +279,8 @@ marker = self._xtickmarkers[0], markersize=self._size, ) - l.set_transform( blend_xy_sep_transform( self.axes.transData, - self.axes.transAxes) ) + l.set_transform(blend_xy_sep_transform( + self.axes.transData, self.axes.transAxes) ) self._set_artist_props(l) return l @@ -300,8 +295,8 @@ markersize=self._size, ) - l.set_transform( blend_xy_sep_transform( self.axes.transData, - self.axes.transAxes) ) + l.set_transform(blend_xy_sep_transform( + self.axes.transData, self.axes.transAxes) ) self._set_artist_props(l) return l @@ -314,8 +309,9 @@ linewidth=rcParams['grid.linewidth'], antialiased=False, ) - l.set_transform( blend_xy_sep_transform( self.axes.transData, - self.axes.transAxes) ) + l.set_transform( + blend_xy_sep_transform( + self.axes.transData, self.axes.transAxes)) l.set_clip_box(self.axes.bbox) self._set_artist_props(l) @@ -363,13 +359,11 @@ dashdirection=0, xaxis=False, ) - trans = blend_xy_sep_transform( self.axes.transAxes, - self.axes.transData) + trans = blend_xy_sep_transform( + self.axes.transAxes, self.axes.transData) # offset the text leftward with a post transformation + trans = trans + Affine2D().translated(-1 * self._padPixels, 0) - transOffset = translation_transform( - Value(-1)*self._padPixels, Value(0)) - trans.set_offset( (0,0), transOffset) t.set_transform( trans ) #t.set_transform( self.axes.transData ) self._set_artist_props(t) @@ -388,13 +382,10 @@ xaxis=False, horizontalalignment='left', ) - trans = blend_xy_sep_transform( self.axes.transAxes, - self.axes.transData) + trans = blend_xy_sep_transform( + self.axes.transAxes, self.axes.transData) # offset the text rightward with a post transformation - - transOffset = translation_transform( - self._padPixels, Value(0)) - trans.set_offset( (0,0), transOffset) + trans = trans + Affine2D().translated(self._padPixels, 0) t.set_transform( trans ) self._set_artist_props(t) return t @@ -409,8 +400,9 @@ linestyle = 'None', markersize=self._size, ) - l.set_transform( blend_xy_sep_transform( self.axes.transAxes, - self.axes.transData) ) + l.set_transform( + blend_xy_sep_transform( + self.axes.transAxes, self.axes.transData)) self._set_artist_props(l) return l @@ -424,8 +416,9 @@ markersize=self._size, ) - l.set_transform( blend_xy_sep_transform( self.axes.transAxes, - self.axes.transData) ) + l.set_transform( + blend_xy_sep_transform( + self.axes.transAxes, self.axes.transData)) self._set_artist_props(l) return l @@ -439,8 +432,8 @@ antialiased=False, ) - l.set_transform( blend_xy_sep_transform( self.axes.transAxes, - self.axes.transData) ) + l.set_transform( blend_xy_sep_transform( + self.axes.transAxes, self.axes.transData) ) l.set_clip_box(self.axes.bbox) self._set_artist_props(l) @@ -989,8 +982,8 @@ verticalalignment='top', horizontalalignment='center', ) - label.set_transform( blend_xy_sep_transform( self.axes.transAxes, - identity_transform() )) + label.set_transform( blend_xy_sep_transform( + self.axes.transAxes, Affine2D() )) self._set_artist_props(label) self.label_position='bottom' @@ -1004,8 +997,8 @@ verticalalignment='top', horizontalalignment='right', ) - offsetText.set_transform( blend_xy_sep_transform( self.axes.transAxes, - identity_transform() )) + offsetText.set_transform( blend_xy_sep_transform( + self.axes.transAxes, Affine2D() )) self._set_artist_props(offsetText) self.offset_text_position='bottom' return offsetText @@ -1041,7 +1034,7 @@ bottom = self.axes.bbox.ymin() else: - bbox = bbox_all(bboxes) + bbox = bbox_union(bboxes) bottom = bbox.ymin() self.label.set_position( (x, bottom-self.LABELPAD*self.figure.dpi.get()/72.0)) @@ -1051,7 +1044,7 @@ top = self.axes.bbox.ymax() else: - bbox = bbox_all(bboxes2) + bbox = bbox_union(bboxes2) top = bbox.ymax() self.label.set_position( (x, top+self.LABELPAD*self.figure.dpi.get()/72.0)) @@ -1065,7 +1058,7 @@ if not len(bboxes): bottom = self.axes.bbox.ymin() else: - bbox = bbox_all(bboxes) + bbox = bbox_union(bboxes) bottom = bbox.ymin() self.offsetText.set_position((x, bottom-self.OFFSETTEXTPAD*self.figure.dpi.get()/72.0)) @@ -1179,8 +1172,8 @@ horizontalalignment='right', rotation='vertical', ) - label.set_transform( blend_xy_sep_transform( identity_transform(), - self.axes.transAxes) ) + label.set_transform( blend_xy_sep_transform( + Affine2D(), self.axes.transAxes) ) self._set_artist_props(label) self.label_position='left' @@ -1194,8 +1187,8 @@ verticalalignment = 'bottom', horizontalalignment = 'left', ) - offsetText.set_transform(blend_xy_sep_transform(self.axes.transAxes, - identity_transform()) ) + offsetText.set_transform(blend_xy_sep_transform( + self.axes.transAxes, Affine2D()) ) self._set_artist_props(offsetText) self.offset_text_position='left' return offsetText @@ -1231,7 +1224,7 @@ left = self.axes.bbox.xmin() else: - bbox = bbox_all(bboxes) + bbox = bbox_union(bboxes) left = bbox.xmin() self.label.set_position( (left-self.LABELPAD*self.figure.dpi.get()/72.0, y)) @@ -1241,7 +1234,7 @@ right = self.axes.bbox.xmax() else: - bbox = bbox_all(bboxes2) + bbox = bbox_union(bboxes2) right = bbox.xmax() self.label.set_position( (right+self.LABELPAD*self.figure.dpi.get()/72.0, y)) Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -84,7 +84,7 @@ from matplotlib.font_manager import findfont from matplotlib.ft2font import FT2Font, LOAD_DEFAULT from matplotlib.mathtext import MathTextParser -from matplotlib.transforms import lbwh_to_bbox +from matplotlib.bbox import Bbox from _backend_agg import RendererAgg as _RendererAgg @@ -126,7 +126,7 @@ self.mathtext_parser = MathTextParser('Agg') self._fontd = {} - self.bbox = lbwh_to_bbox(0,0, self.width, self.height) + self.bbox = Bbox.from_lbwh(0,0, self.width, self.height) if __debug__: verbose.report('RendererAgg.__init__ done', 'debug-annoying') @@ -277,7 +277,7 @@ cliprect = gc.get_clip_rectangle() if cliprect is None: bbox = None - else: bbox = lbwh_to_bbox(*cliprect) + else: bbox = Bbox.from_lbwh(*cliprect) self.draw_image(x, self.height-y, im, bbox) def get_canvas_width_height(self): Modified: branches/transforms/lib/matplotlib/figure.py =================================================================== --- branches/transforms/lib/matplotlib/figure.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/figure.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -18,7 +18,8 @@ from text import Text, _process_text_args from legend import Legend -from transforms import Bbox, Value, Point, get_bbox_transform, unit_bbox +from affine import get_bbox_transform +from bbox import Bbox from ticker import FormatStrFormatter from cm import ScalarMappable from contour import ContourSet @@ -127,17 +128,14 @@ if facecolor is None: facecolor = rcParams['figure.facecolor'] if edgecolor is None: edgecolor = rcParams['figure.edgecolor'] - self.dpi = Value(dpi) - self.figwidth = Value(figsize[0]) - self.figheight = Value(figsize[1]) - self.ll = Point( Value(0), Value(0) ) - self.ur = Point( self.figwidth*self.dpi, - self.figheight*self.dpi ) - self.bbox = Bbox(self.ll, self.ur) - + self.dpi = dpi + self.bbox = Bbox.from_lbwh(0, 0, + self.figsize[0] * dpi, + self.figsize[1] * dpi) + self.frameon = frameon - self.transFigure = get_bbox_transform( unit_bbox(), self.bbox) + self.transFigure = get_bbox_transform( Bbox.unit(), self.bbox) Modified: branches/transforms/lib/matplotlib/legend.py =================================================================== --- branches/transforms/lib/matplotlib/legend.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/legend.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -34,12 +34,9 @@ from patches import Patch, Rectangle, RegularPolygon, Shadow, bbox_artist, draw_bbox from collections import LineCollection, RegularPolyCollection, PatchCollection from text import Text -from transforms import Bbox, Point, Value, get_bbox_transform, bbox_all,\ - unit_bbox, inverse_transform_bbox, lbwh_to_bbox +from affine import get_bbox_transform +from bbox import Bbox, bbox_union - - - def line_cuts_bbox(line, bbox): """ Return True if and only if line cuts bbox. """ minx, miny, width, height = bbox.get_bounds() @@ -168,7 +165,7 @@ else: raise TypeError("Legend needs either Axes or Figure as parent") self.parent = parent - self.set_transform( get_bbox_transform( unit_bbox(), parent.bbox) ) + self.set_transform( get_bbox_transform( Bbox.unit(), parent.bbox) ) if loc is None: loc = rcParams["legend.loc"] @@ -263,10 +260,10 @@ bboxesAll = bboxesText bboxesAll.extend(bboxesHandles) - bbox = bbox_all(bboxesAll) + bbox = bbox_union(bboxesAll) self.save = bbox - ibox = inverse_transform_bbox(self.get_transform(), bbox) + ibox = bbox.inverse_transform(self.get_transform()) self.ibox = ibox return ibox @@ -468,7 +465,7 @@ candidates = [] for l, b in consider: - legendBox = lbwh_to_bbox(l, b, width, height) + legendBox = Bbox.from_lbwh(l, b, width, height) badness = 0 badness = legendBox.count_contains(verts) ox, oy = l-tx, b-ty Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -18,7 +18,7 @@ from cbook import iterable, is_string_like, is_numlike from colors import colorConverter -from transforms import lbwh_to_bbox, LOG10 +from bbox import lbwh_to_bbox from matplotlib import rcParams # special-purpose marker identifiers: @@ -378,7 +378,7 @@ bottom -= ms/2 width += ms height += ms - return lbwh_to_bbox( left, bottom, width, height) + return lbwh_to_bbox(left, bottom, width, height) def set_axes(self, ax): @@ -447,13 +447,15 @@ x, y = self._x, self._y - try: logx = self.get_transform().get_funcx().get_type()==LOG10 - except RuntimeError: logx = False # non-separable + # MGDTODO: Deal with the log scale here + +# 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 +# try: logy = self.get_transform().get_funcy().get_type()==LOG10 +# except RuntimeError: logy = False # non-separable - if not logx and not logy: + if True: return x, y if self._logcache is not None: Modified: branches/transforms/lib/matplotlib/pylab.py =================================================================== --- branches/transforms/lib/matplotlib/pylab.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/pylab.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -234,7 +234,6 @@ from lines import Line2D from text import Text, Annotation from patches import Polygon, Rectangle, Circle, Arrow -from transforms import blend_xy_sep_transform from widgets import SubplotTool, Button, Slider, Widget import numerix as nx Modified: branches/transforms/lib/matplotlib/table.py =================================================================== --- branches/transforms/lib/matplotlib/table.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/table.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -29,12 +29,10 @@ from patches import Rectangle from cbook import enumerate, is_string_like, flatten from text import Text -from transforms import Bbox, inverse_transform_bbox, bbox_all, unit_bbox, \ - get_bbox_transform +from bbox import Bbox, bbox_union - class Cell(Rectangle): """ A cell is a Rectangle with some associated text. @@ -132,7 +130,7 @@ def get_text_bounds(self, renderer): """ Get text bounds in axes co-ordinates. """ bbox = self._text.get_window_extent(renderer) - bboxa = inverse_transform_bbox(self.get_transform(), bbox) + bbox.inverse_transform(self.get_transform()) return bboxa.get_bounds() def get_required_width(self, renderer): Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/text.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -15,7 +15,7 @@ from cbook import enumerate, is_string_like, maxdict, is_numlike from font_manager import FontProperties from patches import bbox_artist, YAArrow -from transforms import lbwh_to_bbox, bbox_all, identity_transform +from bbox import lbwh_to_bbox, bbox_union from lines import Line2D import matplotlib.nxutils as nxutils Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-10 06:55:10 UTC (rev 3821) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-10 17:39:37 UTC (rev 3822) @@ -1,652 +1,652 @@ -""" -The transforms module is broken into two parts, a collection of -classes written in the extension module _transforms to handle -efficient transformation of data, and some helper functions in -transforms to make it easy to instantiate and use those objects. -Hence the core of this module lives in _transforms. +# """ +# The transforms module is broken into two parts, a collection of +# classes written in the extension module _transforms to handle +# efficient transformation of data, and some helper functions in +# transforms to make it easy to instantiate and use those objects. +# Hence the core of this module lives in _transforms. -The transforms class is built around the idea of a LazyValue. A -LazyValue is a base class that defines a method get that returns the -value. The concrete derived class Value wraps a float, and simply -returns the value of that float. The concrete derived class BinOp -allows binary operations on LazyValues, so you can add them, multiply -them, etc. When you do something like +# The transforms class is built around the idea of a LazyValue. A +# LazyValue is a base class that defines a method get that returns the +# value. The concrete derived class Value wraps a float, and simply +# returns the value of that float. The concrete derived class BinOp +# allows binary operations on LazyValues, so you can add them, multiply +# them, etc. When you do something like - inches = Value(8) - dpi = Value(72) - width = inches * dpi +# inches = Value(8) +# dpi = Value(72) +# width = inches * dpi -width is a BinOp instance (that tells you the width of the figure in -pixels). Later, if the figure size in changed, ie we call +# width is a BinOp instance (that tells you the width of the figure in +# pixels). Later, if the figure size in changed, ie we call - inches.set(10) +# inches.set(10) -The width variable is automatically updated because it stores a -pointer to the inches variable, not the value. Since a BinOp is also -a lazy value, you can define binary operations on BinOps as well, such -as +# The width variable is automatically updated because it stores a +# pointer to the inches variable, not the value. Since a BinOp is also +# a lazy value, you can define binary operations on BinOps as well, such +# as - middle = Value(0.5) * width +# middle = Value(0.5) * width -Pairs of LazyValue instances can occur as instances of two classes: +# Pairs of LazyValue instances can occur as instances of two classes: - pt = Point( Value(x), Value(y)) # where x, y are numbers - pt.x(), pt.y() return Value(x), Value(y)) +# pt = Point( Value(x), Value(y)) # where x, y are numbers +# pt.x(), pt.y() return Value(x), Value(y)) - iv = Interval( Value(x), Value(y)) - iv.contains(z) returns True if z is in the closed interval - iv.contains_open(z): same for open interval - iv.span() returns y-x as a float - iv.get_bounds() returns (x,y) as a tuple of floats - iv.set_bounds(x, y) allows input of new floats - iv.update(seq) updates the bounds to include all elements - in a sequence of floats - iv.shift(s) shifts the interval by s, a float +# iv = Interval( Value(x), Value(y)) +# iv.contains(z) returns True if z is in the closed interval +# iv.contains_open(z): same for open interval +# iv.span() returns y-x as a float +# iv.get_bounds() returns (x,y) as a tuple of floats +# iv.set_bounds(x, y) allows input of new floats +# iv.update(seq) updates the bounds to include all elements +# in a sequence of floats +# iv.shift(s) shifts the interval by s, a float -The bounding box class Bbox is also heavily used, and is defined by a -lower left point ll and an upper right point ur. The points ll and ur -are given by Point(x, y) instances, where x and y are LazyValues. So -you can represent a point such as +# The bounding box class Bbox is also heavily used, and is defined by a +# lower left point ll and an upper right point ur. The points ll and ur +# are given by Point(x, y) instances, where x and y are LazyValues. So +# you can represent a point such as - ll = Point( Value(0), Value(0) ) # the origin - ur = Point( width, height ) # the upper right of the figure +# ll = Point( Value(0), Value(0) ) # the origin +# ur = Point( width, height ) # the upper right of the figure -where width and height are defined as above, using the product of the -figure width in inches and the dpi. This is, in face, how the Figure -bbox is defined +# where width and height are defined as above, using the product of the +# figure width in inches and the dpi. This is, in face, how the Figure +# bbox is defined - bbox = Bbox(ll, ur) +# bbox = Bbox(ll, ur) -A bbox basically defines an x,y coordinate system, with ll giving the -lower left of the coordinate system and ur giving the upper right. +# A bbox basically defines an x,y coordinate system, with ll giving the +# lower left of the coordinate system and ur giving the upper right. -The bbox methods are +# The bbox methods are - ll() - return the lower left Point - ur() - return the upper right Point - contains(x,y) - return True if self contains point - overlaps(bbox) - return True if self overlaps bbox - overlapsx(bbox) - return True if self overlaps bbox in the x interval - overlapsy(bbox) - return True if self overlaps bbox in the y interval - intervalx() - return the x Interval instance - intervaly() - return the y interval instance - get_bounds() - get the left, bottom, width, height bounding tuple - update(xys, ignore) - update the bbox to bound all the xy tuples in - xys; if ignore is true ignore the current contents of bbox and - just bound the tuples. If ignore is false, bound self + tuples - width() - return the width of the bbox - height() - return the height of the bbox - xmax() - return the x coord of upper right - ymax() - return the y coord of upper right - xmin() - return the x coord of lower left - ymin() - return the y coord of lower left - scale(sx,sy) - scale the bbox by sx, sy - deepcopy() - return a deep copy of self (pointers are lost) +# ll() - return the lower left Point +# ur() - return the upper right Point +# contains(x,y) - return True if self contains point +# overlaps(bbox) - return True if self overlaps bbox +# overlapsx(bbox) - return True if self overlaps bbox in the x interval +# overlapsy(bbox) - return True if self overlaps bbox in the y interval +# intervalx() - return the x Interval instance +# intervaly() - return the y interval instance +# get_bounds() - get the left, bottom, width, height bounding tuple +# update(xys, ignore) - update the bbox to bound all the xy tuples in +# xys; if ignore is true ignore the current contents of bbox and +# just bound the tuples. If ignore is false, bound self + tuples +# width() - return the width of the bbox +# height() - return the height of the bbox +# xmax() - return the x coord of upper right +# ymax() - return the y coord of upper right +# xmin() - return the x coord of lower left +# ymin() - return the y coord of lower left +# scale(sx,sy) - scale the bbox by sx, sy +# deepcopy() - return a deep copy of self (pointers are lost) -The basic transformation maps one bbox to another, with an optional -nonlinear transformation of one of coordinates (eg log scaling). +# The basic transformation maps one bbox to another, with an optional +# nonlinear transformation of one of coordinates (eg log scaling). -The base class for transformations is Transformation, and the concrete -derived classes are SeparableTransformation and Affine. Earlier -versions of matplotlib handled transformation of x and y separately -(ie we assumed all transformations were separable) but this makes it -difficult to do rotations or polar transformations, for example. All -artists contain their own transformation, defaulting to the identity -transform. +# The base class for transformations is Transformation, and the concrete +# derived classes are SeparableTransformation and Affine. Earlier +# versions of matplotlib handled transformation of x and y separately +# (ie we assumed all transformations were separable) but this makes it +# difficult to do rotations or polar transformations, for example. All +# artists contain their own transformation, defaulting to the identity +# transform. -The signature of a separable transformation instance is +# The signature of a separable transformation instance is - trans = SeparableTransformation(bbox1, bbox2, funcx, funcy) +# trans = SeparableTransformation(bbox1, bbox2, funcx, funcy) -where funcx and funcy operate on x and y. The typical linear -coordinate transformation maps one bounding box to another, with funcx -and funcy both identity. Eg, +# where funcx and funcy operate on x and y. The typical linear +# coordinate transformation maps one bounding box to another, with funcx +# and funcy both identity. Eg, - transData = Transformation(viewLim, displayLim, - Func(IDENTITY), Func(IDENTITY)) +# transData = Transformation(viewLim, displayLim, +# Func(IDENTITY), Func(IDENTITY)) -maps the axes view limits to display limits. If the xaxis scaling is -changed to log, one simply calls +# maps the axes view limits to display limits. If the xaxis scaling is +# changed to log, one simply calls - transData.get_funcx().set_type(LOG10) +# transData.get_funcx().set_type(LOG10) -For more general transformations including rotation, the Affine class -is provided, which is constructed with 6 LazyValue instances: -a, b, c, d, tx, ty. These give the values of the matrix transformation +# For more general transformations including rotation, the Affine class +# is provided, which is constructed with 6 LazyValue instances: +# a, b, c, d, tx, ty. These give the values of the matrix transformation - [xo = |a c| [xi + [tx - yo] |b d| yi] ty] +# [xo = |a c| [xi + [tx +# yo] |b d| yi] ty] -where if sx, sy are the scaling components, tx, y are the translation -components, and alpha is the rotation +# where if sx, sy are the scaling components, tx, y are the translation +# components, and alpha is the rotation - a = sx*cos(alpha); - b = -sx*sin(alpha); - c = sy*sin(alpha); - d = sy*cos(alpha); +# a = sx*cos(alpha); +# b = -sx*sin(alpha); +# c = sy*sin(alpha); +# d = sy*cos(alpha); -The affine transformation can be accomplished for row vectors with a -single matrix multiplication - X_new = X_old * M -where - M = [ a b 0 - c d 0 - tx ty 1] -and each X is the row vector [x, y, 1]. Hence M is -the transpose of the matrix representation given in -http://en.wikipedia.org/wiki/Affine_transformation, -which is for the more usual column-vector representation -of the position.) +# The affine transformation can be accomplished for row vectors with a +# single matrix multiplication +# X_new = X_old * M +# where +# M = [ a b 0 +# c d 0 +# tx ty 1] +# and each X is the row vector [x, y, 1]. Hence M is +# the transpose of the matrix representation given in +# http://en.wikipedia.org/wiki/Affine_transformation, +# which is for the more usual column-vector representation +# of the position.) -From a user perspective, the most important Tranformation methods are +# From a user perspective, the most important Tranformation methods are -All transformations -------------------- - freeze() - eval and freeze the lazy objects - thaw() - release the lazy objects +# All transformations +# ------------------- +# freeze() - eval and freeze the lazy objects +# thaw() - release the lazy objects - xy_tup(xy) - transform the tuple (x,y) - seq_x_y(x, y) - transform the python sequences x and y - numerix_x_y(x, y) - x and y are numerix 1D arrays - numerix_xy(xy) - xy is a numerix array of shape (N,2) - inverse_numerix_xy(xy)- inverse of the above - seq_xy_tups(seq) - seq is a sequence of xy tuples or a (N,2) array - inverse_xy_tup(xy) - apply the inverse transformation to tuple xy +# xy_tup(xy) - transform the tuple (x,y) +# seq_x_y(x, y) - transform the python sequences x and y +# numerix_x_y(x, y) - x and y are numerix 1D arrays +# numerix_xy(xy) - xy is a numerix array of shape (N,2) +# inverse_numerix_xy(xy)- inverse of the above +# seq_xy_tups(seq) - seq is a sequence of xy tuples or a (N,2) array +# inverse_xy_tup(xy) - apply the inverse transformation to tuple xy - set_offset(xy, trans) - xy is an x,y tuple and trans is a - Transformation instance. This will apply a post transformational - offset of all future transformations by xt,yt = trans.xy_tup(xy[0], xy[1]) +# set_offset(xy, trans) - xy is an x,y tuple and trans is a +# Transformation instance. This will apply a post transformational +# offset of all future transformations by xt,yt = trans.xy_tup(xy[0], xy[1]) - deepcopy() - returns a deep copy; references are lost - shallowcopy() - returns a shallow copy excluding the offset +# deepcopy() - returns a deep copy; references are lost +# shallowcopy() - returns a shallow copy excluding the offset -Separable transformations -------------------------- +# Separable transformations +# ------------------------- - get_bbox1() - return the input bbox - get_bbox2() - return the output bbox - set_bbox1() - set the input bbox - set_bbox2() - set the output bbox - get_funcx() - return the Func instance on x - get_funcy() - return the Func instance on y - set_funcx() - set the Func instance on x - set_funcy() - set the Func instance on y +# get_bbox1() - return the input bbox +# get_bbox2() - return the output bbox +# set_bbox1() - set the input bbox +# set_bbox2() - set the output bbox +# get_funcx() - return the Func instance on x +# get_funcy() - return the Func instance on y +# set_funcx() - set the Func instance on x +# set_funcy() - set the Func instance on y -Affine transformations ----------------------- +# Affine transformations +# ---------------------- - as_vec6() - return the affine as length 6 list of Values +# as_vec6() - return the affine as length 6 list of Values -In general, you shouldn't need to construct your own transformations, -but should use the helper functions defined in this module. +# In general, you shouldn't need to construct your own transformations, +# but should use the helper functions defined in this module. - zero - return Value(0) - one - return Value(1) - origin - return Point(zero(), zero()) - unit_bbox - return the 0,0 to 1,1 bounding box - identity_affine - An affine identity transformation - identity_transform - An identity separable transformation - translation_transform - a pure translational affine - scale_transform - a pure scale affine - scale_sep_transform - a pure scale separable transformation - scale_translation_transform - a scale and translate affine - bound_vertices - return the bbox that bounds all the xy tuples - bbox_all - return the bbox that bounds all the bboxes - lbwh_to_bbox - build a bbox from tuple - left, bottom, width, height tuple +# zero - return Value(0) +# one - return Value(1) +# origin - return Point(zero(), zero()) +# unit_bbox - return the 0,0 to 1,1 bounding box +# identity_affine - An affine identity transformation +# identity_transform - An identity separable transformation +# translation_transform - a pure translational affine +# scale_transform - a pure scale affine +# scale_sep_transform - a pure scale separable transformation +# scale_translation_transform - a scale and translate affine +# bound_vertices - return the bbox that bounds all the xy tuples +# bbox_all - return the bbox that bounds all the bboxes +# lbwh_to_bbox - build a bbox from tuple +# left, bottom, width, height tuple - multiply_affines - return the affine that is the matrix product of - the two affines +# multiply_affines - return the affine that is the matrix product of +# the two affines - get_bbox_transform - return a SeparableTransformation instance that - transforms one bbox to another +# get_bbox_transform - return a SeparableTransformation instance that +# transforms one bbox to another - blend_xy_sep_transform - mix the x and y components of two separable - transformations into a new transformation. - This allows you to specify x and y in - different coordinate systems +# blend_xy_sep_transform - mix the x and y components of two separable +# transformations into a new transformation. +# This allows you to specify x and y in +# different coordinate systems - transform_bbox - apply a transformation to a bbox and return the - transformed bbox +# transform_bbox - apply a transformation to a bbox and return the +# transformed bbox - inverse_transform_bbox - apply the inverse transformation of a bbox - and return the inverse transformed bbox +# inverse_transform_bbox - apply the inverse transformation of a bbox +# and return the inverse transformed bbox - offset_copy - make a copy with an offset +# offset_copy - make a copy with an offset -The units/transform_unit.py code has many examples. +# The units/transform_unit.py code has many examples. -A related and partly overlapping class, PBox, has been added to the -original transforms module to facilitate Axes repositioning and resizing. -At present, the differences between Bbox and PBox include: +# A related and partly overlapping class, PBox, has been added to the +# original transforms module to facilitate Axes repositioning and resizing. +# At present, the differences between Bbox and PBox include: - Bbox works with the bounding box, the coordinates of the lower-left - and upper-right corners; PBox works with the lower-left coordinates - and the width, height pair (left, bottom, width, height, or 'lbwh'). - Obviously, these are equivalent, but lbwh is what is used by - Axes._position, and it is the natural specification for the types of - manipulations for which the PBox class was made. +# Bbox works with the bounding box, the coordinates of the lower-left +# and upper-right corners; PBox works with the lower-left coordinates +# and the width, height pair (left, bottom, width, height, or 'lbwh'). +# Obviously, these are equivalent, but lbwh is what is used by +# Axes._position, and it is the natural specification for the types of +# manipulations for which the PBox class was made. - Bbox uses LazyValues grouped in pairs as 'll' and 'ur' Point objects; - PBox uses a 4-element list, subclassed from the python list. +# Bbox uses LazyValues grouped in pairs as 'll' and 'ur' Point objects; +# PBox uses a 4-element list, subclassed from the python list. - Bbox and PBox methods are mostly quite different, reflecting their - different original purposes. Similarly, the CXX implementation of - Bbox is good for methods such as update and for lazy evaluation, but - for PBox intended uses, involving very little calculation, pure - python probably is adequate. +# Bbox and PBox methods are mostly quite different, reflecting their +# different original purposes. Similarly, the CXX implementation of +# Bbox is good for methods such as update and for lazy evaluation, but +# for PBox intended uses, involving very little calculation, pure +# python probably is adequate. -In the future we may reimplement the PBox using Bbox -and transforms, or eliminate it entirely by adding its methods -and attributes to Bbox and/or putting them elsewhere in this module. -""" -from __future__ import division -import math -import numpy as npy +# In the future we may reimplement the PBox using Bbox +# and transforms, or eliminate it entirely by adding its methods +# and attributes to Bbox and/or putting them elsewhere in this module. +# """ +# from __future__ import division +# import math +# import numpy as npy -from matplotlib._transforms import Value, Point, Interval, Bbox, Affine -from matplotlib._transforms import IDENTITY, LOG10, POLAR, Func, FuncXY -from matplotlib._transforms import SeparableTransformation -from matplotlib._transforms import NonseparableTransformation +# from matplotlib._transforms import Value, Point, Interval, Bbox, Affine +# from matplotlib._transforms import IDENTITY, LOG10, POLAR, Func, FuncXY +# from matplotlib._transforms import SeparableTransformation +# from matplotlib._transforms import NonseparableTransformation -def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): - ''' - Ensure the endpoints of a range are not too close together. +# def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): +# ''' +# Ensure the endpoints of a range are not too close together. - "too close" means the interval is smaller than 'tiny' times - the maximum absolute value. +# "too close" means the interval is smaller than 'tiny' times +# the maximum absolute value. - If they are too close, each will be moved by the 'expander'. - If 'increasing' is True and vmin > vmax, they will be swapped, - regardless of whether they are too close. - ''' - swapped = False - if vmax < vmin: - vmin, vmax = vmax, vmin - swapped = True - if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny: - if vmin==0.0: - vmin = -expander - vmax = expander - else: - vmin -= expander*abs(vmin) - vmax += expander*abs(vmax) - if swapped and not increasing: - vmin, vmax = vmax, vmin - return vmin, vmax +# If they are too close, each will be moved by the 'expander'. +# If 'increasing' is True and vmin > vmax, they will be swapped, +# regardless of whether they are too close. +# ''' +# swapped = False +# if vmax < vmin: +# vmin, vmax = vmax, vmin +# swapped = True +# if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny: +# if vmin==0.0: +# vmin = -expander +# vmax = expander +# else: +# vmin -= expander*abs(vmin) +# vmax += expander*abs(vmax) +# if swapped and not increasing: +# vmin, vmax = vmax, vmin +# return vmin, vmax -def zero(): return Value(0) +# def zero(): return Value(0) -def one() : return Value(1) +# def one() : return Value(1) -def origin(): - return Point( zero(), zero() ) +# def origin(): +# return Point( zero(), zero() ) -def unit_bbox(): - """ - Get a 0,0 -> 1,1 Bbox instance - """ - return Bbox( origin(), Point( one(), one() ) ) +# def unit_bbox(): +# """ +# Get a 0,0 -> 1,1 Bbox instance +# """ +# return Bbox( origin(), Point( one(), one() ) ) -def identity_affine(): - """ - Get an affine transformation that maps x,y -> x,y - """ +# def identity_affine(): +# """ +# Get an affine transformation that maps x,y -> x,y +# """ - return Affine(one(), zero(), zero(), one(), zero(), zero()) +# return Affine(one(), zero(), zero(), one(), zero(), zero()) -def identity_transform(): - """ - Get an affine transformation that maps x,y -> x,y - """ - return SeparableTransformation(unit_bbox(), unit_bbox(), - Func(IDENTITY), - Func(IDENTITY)) +# def identity_transform(): +# """ +# Get an affine transformation that maps x,y -> x,y +# """ +# return SeparableTransformation(unit_bbox(), unit_bbox(), +# Func(IDENTITY), +# Func(IDENTITY)) -def translation_transform(tx, ty): - """ - return a pure tranlational transformation... [truncated message content] |
From: <md...@us...> - 2007-09-10 17:40:51
|
Revision: 3823 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3823&view=rev Author: mdboom Date: 2007-09-10 10:40:47 -0700 (Mon, 10 Sep 2007) Log Message: ----------- Adding new files that will eventually replace transforms.py/cpp Added Paths: ----------- branches/transforms/lib/matplotlib/affine.py branches/transforms/lib/matplotlib/bbox.py Added: branches/transforms/lib/matplotlib/affine.py =================================================================== --- branches/transforms/lib/matplotlib/affine.py (rev 0) +++ branches/transforms/lib/matplotlib/affine.py 2007-09-10 17:40:47 UTC (rev 3823) @@ -0,0 +1,207 @@ +""" +A set of classes to handle transformations. + +2007 Michael Droettboom +""" + +import numpy as N +from numpy.linalg import inv + +class Transform(object): + def __call__(self, points): + raise NotImplementedError() + + def __add__(self, other): + if isinstance(other, Transform): + return CompositeTransform(self, other) + raise TypeError("Can not add Transform to object of type '%s'" % type(other)) + + def __radd__(self, other): + if isinstance(other, Transform): + return CompositeTransform(other, self) + raise TypeError("Can not add Transform to object of type '%s'" % type(other)) + + def has_inverse(self): + raise NotImplementedError() + + def inverted(self): + raise NotImplementedError() + + def is_separable(self): + return False + +class CompositeTransform(Transform): + def __init__(self, a, b): + assert a.output_dims == b.input_dims + self.a = a + self.b = b + + def __call__(self, points): + return self.b(self.a(points)) + +class Affine2D(Transform): + input_dims = 2 + output_dims = 2 + + def __init__(self, matrix = None): + """ + Initialize an Affine transform from a 3x3 numpy float array. + + a c e + b d f + 0 0 1 + """ + if matrix is None: + matrix = N.identity(3) + else: + assert matrix.shape == (3, 3) + self.mtx = matrix + + def __repr__(self): + return repr(self.mtx) + + def __str__(self): + return str(self.mtx) + + #@staticmethod + def from_values(a, b, c, d, e, f): + return Affine2D(Affine2D.matrix_from_values(a, b, c, d, e, f)) + from_values = staticmethod(from_values) + + #@staticmethod + def matrix_from_values(a, b, c, d, e, f): + affine = N.zeros((3,3), N.float_) + affine[0,] = a, c, e + affine[1,] = b, d, f + affine[2,2] = 1 + return affine + matrix_from_values = staticmethod(matrix_from_values) + + def __call__(self, points): + """ + Applies the transformation to a set of 2D points and + returns the result. + + points must be a numpy array of shape (N, 2), where N is the + number of points. + """ + # MGDTODO: This involves a copy. We may need to do something like + # http://neuroimaging.scipy.org/svn/ni/ni/trunk/neuroimaging/core/reference/mapping.py + # to separate the matrix out into the translation and scale components + # and apply each separately (which is still sub-optimal) + + # This is nicer for now, however, since we can just keep a + # regular affine matrix around + new_points = points.swapaxes(0, 1) + new_points = N.vstack((new_points, N.ones((1, points.shape[0])))) + result = N.dot(self.mtx, new_points)[:2] + result.swapaxes(0, 1) + return result + + #@staticmethod + def _concat(a, b): + return N.dot(b, a) + _concat = staticmethod(_concat) + + def concat(a, b): + return Affine2D(Affine2D._concat(a.mtx, b.mtx)) + concat = staticmethod(concat) + + #@staticmethod + def identity(): + return Affine2D(N.identity(3)) + identity = staticmethod(identity) + + def __add__(self, other): + if isinstance(other, Affine2D): + return Affine2D.concat(self, other) + return Transform.__add__(self, other) + + def __radd__(self, other): + if isinstance(other, Affine2D): + return Affine2D.concat(other, self) + return Transform.__radd__(self, other) + + def rotated(self, theta): + a = N.cos(theta) + b = N.sin(theta) + rotate_mtx = self.matrix_from_values(a, b, -b, a, 0, 0) + return Affine2D(self._concat(self.mtx, rotate_mtx)) + + def rotated_deg(self, degrees): + return self.rotated(degrees*N.pi/180.) + + def translated(self, tx, ty): + translate_mtx = self.matrix_from_values(1., 0., 0., 1., tx, ty) + return Affine2D(self._concat(self.mtx, translate_mtx)) + + def scaled(self, sx, sy=None): + if sy is None: + sy = sx + scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.) + return Affine2D(self._concat(self.mtx, scale_mtx)) + + def inverted(self): + # MGDTODO: We may want to optimize by storing the inverse + # of the transform with every transform + return Affine2D(inv(self.mtx)) + + def is_separable(self): + mtx = self.mtx + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + +class BlendedAffine2D(Affine2D): + def __init__(self, x_transform, y_transform): + assert isinstance(x_transform, Affine2D) + assert isinstance(y_transform, Affine2D) + assert x_transform.is_separable() + assert y_transform.is_separable() + x_mtx = x_transform.mtx + y_mtx = y_transform.mtx + self.mtx = self.matrix_from_values( + x_mtx[0,0], 0.0, 0.0, y_mtx[1,1], x_mtx[0,2], y_mtx[1,2]) + +# This is a placeholder since eventually we may need to handle the +# more general case of two transforms that aren't affines +BlendedTransform = BlendedAffine2D + +def blend_xy_sep_transform(x_transform, y_transform): + return BlendedAffine2D(x_transform, y_transform) + +def get_bbox_transform(boxin, boxout): + x_scale = boxout.width() / boxin.width() + y_scale = boxout.height() / boxin.height() + + # MGDTODO: Optimize + return Affine2D() \ + .translated(-boxin.xmin(), -boxin.ymin()) \ + .scaled(x_scale, y_scale) \ + .translated(boxout.xmin(), boxout.ymin()) + +if __name__ == '__main__': + print Affine2D.from_values(1., 0, 0, 1, 0, 0) + + print "translated", Affine2D.identity().translated(5, 4) + print "rotated", Affine2D.identity().rotated_deg(30) + print "scaled", Affine2D.identity().scaled(5, 4) + + transform = Affine2D.identity().rotated_deg(30).translated(5, 4) + + points = N.array([[1, 2], [3, 4], [5, 6]]) + + print inv(transform.mtx) + + print transform(points) + + transform = Affine2D.identity().scaled(5., 1.).translated(10, 0) + print transform + print transform.inverted() + + from bbox import Bbox + boxin = Bbox([[10, 10], [320, 240]]) + boxout = Bbox([[25, 25], [640, 400]]) + trans = bbox_transform(boxin, boxout) + print trans + print trans(N.array([[10, 10], [320, 240]])) + +__all__ = ['Transform', 'Affine2D'] Added: branches/transforms/lib/matplotlib/bbox.py =================================================================== --- branches/transforms/lib/matplotlib/bbox.py (rev 0) +++ branches/transforms/lib/matplotlib/bbox.py 2007-09-10 17:40:47 UTC (rev 3823) @@ -0,0 +1,108 @@ +""" +A convenience class for handling bounding boxes + +2007 Michael Droettboom +""" + +import numpy as N + +class Bbox: + def __init__(self, points): + self._points = N.array(points) + + #@staticmethod + def unit(): + return Bbox([[0,0], [1,1]]) + unit = staticmethod(unit) + + #@staticmethod + def from_lbwh(left, bottom, width, height): + return Bbox([[left, bottom], [left + width, bottom + height]]) + from_lbwh = staticmethod(from_lbwh) + + #@staticmethod + def from_lbrt(left, bottom, right, top): + return Bbox([[left, bottom], [right, top]]) + from_lbwh = staticmethod(from_lbwh) + + # MGDTODO: Probably a more efficient ways to do this... + def xmin(self): + return self._points[0,0] + + def ymin(self): + return self._points[0,1] + + def xmax(self): + return self._points[1,0] + + def ymax(self): + return self._points[1,1] + + def width(self): + return self.xmax() - self.xmin() + + def height(self): + return self.ymax() - self.ymin() + + def transform(self, transform): + return Bbox(transform(points)) + + def inverse_transform(self, transform): + return Bbox(transform.inverted()(points)) + + def get_bounds(self): + return (self.xmin(), self.ymin(), + self.xmax() - self.xmin(), self.ymax() - self.ymin()) + +def lbwh_to_bbox(left, bottom, width, height): + return Bbox([[left, bottom], [left + width, bottom + height]]) + +def bbox_union(bboxes): + """ + Return the Bbox that bounds all bboxes + """ + assert(len(bboxes)) + + if len(bboxes) == 1: + return bboxes[0] + + bbox = bboxes[0] + xmin = bbox.xmin + ymin = bbox.ymin + xmax = bbox.xmax + ymax = bbox.ymax + + for bbox in bboxes[1:]: + xmin = min(xmin, bbox.xmin) + ymin = min(ymin, bbox.ymin) + xmax = max(xmax, bbox.xmax) + ymax = max(ymax, bbox.ymax) + + return Bbox(xmin, ymin, xmax, ymax) + +# MGDTODO: There's probably a better place for this +def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): + ''' + Ensure the endpoints of a range are not too close together. + + "too close" means the interval is smaller than 'tiny' times + the maximum absolute value. + + If they are too close, each will be moved by the 'expander'. + If 'increasing' is True and vmin > vmax, they will be swapped, + regardless of whether they are too close. + ''' + swapped = False + if vmax < vmin: + vmin, vmax = vmax, vmin + swapped = True + if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny: + if vmin==0.0: + vmin = -expander + vmax = expander + else: + vmin -= expander*abs(vmin) + vmax += expander*abs(vmax) + if swapped and not increasing: + vmin, vmax = vmax, vmin + return vmin, vmax This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-12 13:36:31
|
Revision: 3835 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3835&view=rev Author: mdboom Date: 2007-09-12 06:36:25 -0700 (Wed, 12 Sep 2007) Log Message: ----------- Second pass, using a stateful transform tree. Modified Paths: -------------- branches/transforms/lib/matplotlib/affine.py branches/transforms/lib/matplotlib/artist.py branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/backends/backend_agg.py branches/transforms/lib/matplotlib/backends/backend_tkagg.py branches/transforms/lib/matplotlib/figure.py branches/transforms/lib/matplotlib/legend.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/table.py branches/transforms/lib/matplotlib/text.py branches/transforms/lib/matplotlib/ticker.py Removed Paths: ------------- branches/transforms/lib/matplotlib/bbox.py Modified: branches/transforms/lib/matplotlib/affine.py =================================================================== --- branches/transforms/lib/matplotlib/affine.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/affine.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -6,19 +6,203 @@ import numpy as N from numpy.linalg import inv +from sets import Set -class Transform(object): +# MGDTODO: This creates a ton of cyclical references. We may want to +# consider using weak references + +class TransformNode(object): + def __init__(self): + self._parents = Set() + + def invalidate(self): + if not self._do_invalidation(): + for parent in self._parents: + parent.invalidate() + + def _do_invalidation(self): + return False + + def add_children(self, children): + for child in children: + child._parents.add(self) + +class Bbox(TransformNode): + def __init__(self, points): + TransformNode.__init__(self) + self._points = N.asarray(points, N.float_) + self.track = False + + def __del__(self): + if self.track: + print "Bbox::__del__" + + #@staticmethod + def unit(): + return Bbox([[0,0], [1,1]]) + unit = staticmethod(unit) + + #@staticmethod + def from_lbwh(left, bottom, width, height): + return Bbox([[left, bottom], [left + width, bottom + height]]) + from_lbwh = staticmethod(from_lbwh) + + #@staticmethod + def from_lbrt(left, bottom, right, top): + return Bbox([[left, bottom], [right, top]]) + from_lbrt = staticmethod(from_lbrt) + + def update_from_data(self, x, y): + self._points = N.array([[x.min(), y.min()], [x.max(), y.max()]], N.float_) + self.invalidate() + if self.track: + print "Bbox::update_from_data", self._points + + def copy(self): + if self.track: + print "Bbox::copy" + return Bbox(self._points.copy()) + + def __repr__(self): + return 'Bbox(%s)' % repr(self._points) + __str__ = __repr__ + + def __cmp__(self, other): + # MGDTODO: Totally suboptimal + if isinstance(other, Bbox): + return (self._points == other._points).all() + return -1 + + # MGDTODO: Probably a more efficient ways to do this... + def _get_xmin(self): + if self.track: + print "Bbox::_get_xmin" + return self._points[0, 0] + def _set_xmin(self, val): + print "Bbox::_set_xmin" + self._points[0, 0] = val + self.invalidate() + xmin = property(_get_xmin, _set_xmin) + + def _get_ymin(self): + return self._points[0, 1] + def _set_ymin(self, val): + self._points[0, 1] = val + self.invalidate() + ymin = property(_get_ymin, _set_ymin) + + def _get_xmax(self): + return self._points[1, 0] + def _set_xmax(self, val): + self._points[1, 0] = val + self.invalidate() + xmax = property(_get_xmax, _set_xmax) + + def _get_ymax(self): + return self._points[1, 1] + def _set_ymax(self, val): + self._points[1, 1] = val + self.invalidate() + ymax = property(_get_ymax, _set_ymax) + + def _get_min(self): + return self._points[0] + def _set_min(self, val): + self._points[0] = val + self.invalidate() + min = property(_get_min, _set_min) + + def _get_max(self): + return self._points[1] + def _set_max(self, val): + self._points[1] = val + self.invalidate() + max = property(_get_max, _set_max) + + def _get_intervalx(self): + return self._points[:,0] + def _set_intervalx(self, interval): + self._points[:,0] = interval + self.invalidate() + intervalx = property(_get_intervalx, _set_intervalx) + + def _get_intervaly(self): + return self._points[:,1] + def _set_intervaly(self, interval): + self._points[:,1] = interval + self.invalidate() + intervaly = property(_get_intervaly, _set_intervaly) + + def _get_width(self): + return self.xmax - self.xmin + width = property(_get_width) + + def _get_height(self): + return self.ymax - self.ymin + height = property(_get_height) + + def transformed(self, transform): + return Bbox(self.transform(self._points)) + + def inverse_transformed(self, transform): + return Bbox(self.transform.inverted()(self._points)) + + def get_bounds(self): + return (self.xmin, self.ymin, + self.xmax - self.xmin, self.ymax - self.ymin) + + def expanded(self, sw, sh): + width = self.width() + height = self.height() + deltaw = (sw * width - width) / 2.0 + deltah = (sh * height - height) / 2.0 + a = N.array([[-deltaw, -deltah], [deltaw, deltah]]) + return Bbox(self._points + a) + + def contains(self, x, y): + return (x >= self.xmin and x <= self.xmax and + y >= self.ymin and y <= self.ymax) + + #@staticmethod + def union(bboxes): + """ + Return the Bbox that bounds all bboxes + """ + assert(len(bboxes)) + + if len(bboxes) == 1: + return bboxes[0] + + bbox = bboxes[0] + xmin = bbox.xmin + ymin = bbox.ymin + xmax = bbox.xmax + ymax = bbox.ymax + + for bbox in bboxes[1:]: + xmin = min(xmin, bbox.xmin) + ymin = min(ymin, bbox.ymin) + xmax = max(xmax, bbox.xmax) + ymax = max(ymax, bbox.ymax) + + return Bbox.from_lbrt(xmin, ymin, xmax, ymax) + union = staticmethod(union) + +class Transform(TransformNode): + def __init__(self): + TransformNode.__init__(self) + def __call__(self, points): raise NotImplementedError() def __add__(self, other): if isinstance(other, Transform): - return CompositeTransform(self, other) + return composite_transform_factory(self, other) raise TypeError("Can not add Transform to object of type '%s'" % type(other)) def __radd__(self, other): if isinstance(other, Transform): - return CompositeTransform(other, self) + return composite_transform_factory(other, self) raise TypeError("Can not add Transform to object of type '%s'" % type(other)) def has_inverse(self): @@ -30,15 +214,9 @@ def is_separable(self): return False -class CompositeTransform(Transform): - def __init__(self, a, b): - assert a.output_dims == b.input_dims - self.a = a - self.b = b - - def __call__(self, points): - return self.b(self.a(points)) - + def is_affine(self): + return False + class Affine2D(Transform): input_dims = 2 output_dims = 2 @@ -51,15 +229,22 @@ b d f 0 0 1 """ + Transform.__init__(self) if matrix is None: matrix = N.identity(3) else: assert matrix.shape == (3, 3) - self.mtx = matrix + self._mtx = matrix + self._inverted = None def __repr__(self): - return "Affine2D(%s)" % repr(self.mtx) + return "Affine2D(%s)" % repr(self._mtx) __str__ = __repr__ + + def _do_invalidation(self): + result = self._inverted is None + self._inverted = None + return result #@staticmethod def from_values(a, b, c, d, e, f): @@ -67,7 +252,8 @@ from_values = staticmethod(from_values) def to_values(self): - return tuple(self.mtx[:2].swapaxes(0, 1).flatten()) + mtx = self.get_matrix() + return tuple(mtx[:2].swapaxes(0, 1).flatten()) #@staticmethod def matrix_from_values(a, b, c, d, e, f): @@ -78,6 +264,9 @@ return affine matrix_from_values = staticmethod(matrix_from_values) + def get_matrix(self): + return self._mtx + def __call__(self, points): """ Applies the transformation to a set of 2D points and @@ -91,13 +280,14 @@ # to separate the matrix out into the translation and scale components # and apply each separately (which is still sub-optimal) - # This is nicer for now, however, since we can just keep a + # This is easier for now, however, since we can just keep a # regular affine matrix around # MGDTODO: Trap cases where this isn't an array and fix there - points = N.array(points, N.float_) + mtx = self.get_matrix() + points = N.asarray(points, N.float_) new_points = points.swapaxes(0, 1) new_points = N.vstack((new_points, N.ones((1, points.shape[0])))) - result = N.dot(self.mtx, new_points)[:2] + result = N.dot(mtx, new_points)[:2] return result.swapaxes(0, 1) #@staticmethod @@ -105,8 +295,9 @@ return N.dot(b, a) _concat = staticmethod(_concat) + #@staticmethod def concat(a, b): - return Affine2D(Affine2D._concat(a.mtx, b.mtx)) + return Affine2D(Affine2D._concat(a._mtx, b._mtx)) concat = staticmethod(concat) #@staticmethod @@ -114,100 +305,246 @@ return Affine2D(N.identity(3)) identity = staticmethod(identity) - def __add__(self, other): - if isinstance(other, Affine2D): - return Affine2D.concat(self, other) - return Transform.__add__(self, other) - - def __radd__(self, other): - if isinstance(other, Affine2D): - return Affine2D.concat(other, self) - return Transform.__radd__(self, other) - - def rotated(self, theta): + def rotate(self, theta): a = N.cos(theta) b = N.sin(theta) rotate_mtx = self.matrix_from_values(a, b, -b, a, 0, 0) - return Affine2D(self._concat(self.mtx, rotate_mtx)) + self._mtx = self._concat(self._mtx, rotate_mtx) + self.invalidate() + return self - def rotated_deg(self, degrees): - return self.rotated(degrees*N.pi/180.) + def rotate_deg(self, degrees): + return self.rotate(degrees*N.pi/180.) - def translated(self, tx, ty): + def translate(self, tx, ty): translate_mtx = self.matrix_from_values(1., 0., 0., 1., tx, ty) - return Affine2D(self._concat(self.mtx, translate_mtx)) + self._mtx = self._concat(self._mtx, translate_mtx) + self.invalidate() + return self - def scaled(self, sx, sy=None): + def scale(self, sx, sy=None): if sy is None: sy = sx scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.) - return Affine2D(self._concat(self.mtx, scale_mtx)) + self._mtx = self._concat(self._mtx, scale_mtx) + self.invalidate() + return self def inverted(self): - # MGDTODO: We may want to optimize by storing the inverse - # of the transform with every transform - return Affine2D(inv(self.mtx)) + if self._inverted is None: + mtx = self.get_matrix() + self._inverted = Affine2D(inv(mtx)) + return self._inverted def is_separable(self): - mtx = self.mtx + mtx = self.get_matrix() return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + def is_affine(self): + return True + class BlendedAffine2D(Affine2D): def __init__(self, x_transform, y_transform): -# assert isinstance(x_transform, Affine2D) -# assert isinstance(y_transform, Affine2D) + assert x_transform.is_affine() + assert y_transform.is_affine() assert x_transform.is_separable() assert y_transform.is_separable() - x_mtx = x_transform.mtx - y_mtx = y_transform.mtx - self.mtx = self.matrix_from_values( - x_mtx[0,0], 0.0, 0.0, y_mtx[1,1], x_mtx[0,2], y_mtx[1,2]) -# This is a placeholder since eventually we may need to handle the -# more general case of two transforms that aren't affines -BlendedTransform = BlendedAffine2D + Transform.__init__(self) + self.add_children([x_transform, y_transform]) + self._x = x_transform + self._y = y_transform + self._mtx = None + self._inverted = None -def blend_xy_sep_transform(x_transform, y_transform): - return BlendedAffine2D(x_transform, y_transform) + def __repr__(self): + return "BlendedAffine2D(%s,%s)" % (self._x, self._y) + __str__ = __repr__ + + def _do_invalidation(self): + if self._mtx is not None: + self._mtx = None + Affine2D._do_invalidation(self) + return False + return True -def get_bbox_transform(boxin, boxout): - x_scale = boxout.width() / boxin.width() - y_scale = boxout.height() / boxin.height() + def _make__mtx(self): + if self._mtx is None: + x_mtx = self._x.get_matrix() + y_mtx = self._y.get_matrix() + self._mtx = N.vstack([x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]]) +# self._mtx = self.matrix_from_values( +# x_mtx[0,0], 0.0, 0.0, y_mtx[1,1], x_mtx[0,2], y_mtx[1,2]) + print "Blended", x_mtx, y_mtx, self._mtx + + def is_separable(self): + return True + + def get_matrix(self): + self._make__mtx() + return self._mtx - # MGDTODO: Optimize - return Affine2D() \ - .translated(-boxin.xmin(), -boxin.ymin()) \ - .scaled(x_scale, y_scale) \ - .translated(boxout.xmin(), boxout.ymin()) +class BlendedTransform(Transform): + def __init__(self, x_transform, y_transform): + assert x_transform.is_separable() + assert y_transform.is_separable() + + Transform.__init__(self) + self.add_children([x_transform, y_transform]) + self._x = x_transform + self._y = y_transform + + def __call__(self, points): + # MGDTODO: Implement me + pass + +class CompositeAffine2D(Affine2D): + def __init__(self, a, b): + assert a.is_affine() + assert b.is_affine() + + Transform.__init__(self) + self.add_children([a, b]) + self._a = a + self._b = b + self._mtx = None + self._inverted = None + + def __repr__(self): + return "CompositeAffine2D(%s, %s)" % (self._a, self._b) + __str__ = __repr__ + + def _do_invalidation(self): + self._mtx = None + Affine2D._do_invalidation(self) -if __name__ == '__main__': - print Affine2D.from_values(1., 0, 0, 1, 0, 0) - - print "translated", Affine2D.identity().translated(5, 4) - print "rotated", Affine2D.identity().rotated_deg(30) - print "scaled", Affine2D.identity().scaled(5, 4) - - transform = Affine2D.identity().rotated_deg(30).translated(5, 4) + def _make__mtx(self): + if self._mtx is None: + self._mtx = self._concat( + self._b.get_matrix(), + self._a.get_matrix()) - points = N.array([[1, 2], [3, 4], [5, 6]]) + def get_matrix(self): + self._make__mtx() + return self._mtx + +class CompositeTransform(Transform): + def __init__(self, a, b): + assert a.output_dims == b.input_dims - print inv(transform.mtx) + Transform.__init__(self) + self.add_children([a, b]) + self._a = a + self._b = b + + def __call__(self, points): + # MGDTODO: Optimize here by concatenating affines if possible + return self._b(self._a(points)) + +class BboxTransform(Affine2D): + def __init__(self, boxin, boxout): + assert isinstance(boxin, Bbox) + assert isinstance(boxout, Bbox) + + Transform.__init__(self) + self.add_children([boxin, boxout]) + self._boxin = boxin + self._boxout = boxout + self._mtx = None + self._inverted = None + + def __repr__(self): + return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) + __str__ = __repr__ + + def _do_invalidation(self): + if self._mtx is not None: + self._mtx = None + Affine2D._do_invalidation(self) + return False + return True + + def _make__mtx(self): + if self._mtx is None: + boxin = self._boxin + boxout = self._boxout + x_scale = boxout.width / boxin.width + y_scale = boxout.height / boxin.height + + # MGDTODO: Optimize + affine = Affine2D() \ + .translate(-boxin.xmin, -boxin.ymin) \ + .scale(x_scale, y_scale) \ + .translate(boxout.xmin, boxout.ymin) + + self._mtx = affine._mtx + + def __call__(self, points): + self._make__mtx() + return Affine2D.__call__(self, points) + + def inverted(self): + self._make__mtx() + return Affine2D.inverted(self) + + def is_separable(self): + return True + + def get_matrix(self): + self._make__mtx() + return self._mtx - print transform(points) +def blend_xy_sep_transform(x_transform, y_transform): + if x_transform.is_affine() and y_transform.is_affine(): + return BlendedAffine2D(x_transform, y_transform) + return BlendedTransform(x_transform, y_transform) - transform = Affine2D.identity().scaled(5., 1.).translated(10, 0) - print transform - print transform.inverted() +def composite_transform_factory(a, b): + if a.is_affine() and b.is_affine(): + return CompositeAffine2D(a, b) + return CompositeTransform(a, b) - from bbox import Bbox - print "BBOX" - boxin = Bbox([[10, 10], [320, 240]]) - boxout = Bbox([[25, 25], [640, 400]]) - print boxin._points, boxin.xmin(), boxin.ymin(), boxin.xmax(), boxin.ymax() - print boxout._points, boxout.xmin(), boxout.ymin(), boxout.xmax(), boxout.ymax() - trans = get_bbox_transform(boxin, boxout) - print trans - print trans(N.array([[10, 10], [320, 240]])) - print trans([[10, 10]]) +# MGDTODO: There's probably a better place for this +def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): + ''' + Ensure the endpoints of a range are not too close together. + + "too close" means the interval is smaller than 'tiny' times + the maximum absolute value. + + If they are too close, each will be moved by the 'expander'. + If 'increasing' is True and vmin > vmax, they will be swapped, + regardless of whether they are too close. + ''' + swapped = False + if vmax < vmin: + vmin, vmax = vmax, vmin + swapped = True + if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny: + if vmin==0.0: + vmin = -expander + vmax = expander + else: + vmin -= expander*abs(vmin) + vmax += expander*abs(vmax) + if swapped and not increasing: + vmin, vmax = vmax, vmin + return vmin, vmax + +# MGDTODO: Optimize +def interval_contains(interval, val): + return interval[0] <= val and interval[1] >= val + +def interval_contains_open(interval, val): + return interval[0] < val and interval[1] > val +if __name__ == '__main__': + bbox1 = Bbox([[10., 15.], [20., 25.]]) + bbox2 = Bbox([[30., 35.], [40., 45.]]) + trans = BboxTransform(bbox1, bbox2) + print trans(bbox1._points) + + bbox2.intervalx = 50, 55 + print trans(bbox1._points) + __all__ = ['Transform', 'Affine2D'] Modified: branches/transforms/lib/matplotlib/artist.py =================================================================== --- branches/transforms/lib/matplotlib/artist.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/artist.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -138,7 +138,6 @@ ACCEPTS: a matplotlib.transform transformation instance """ - print "set_transform", t self._transform = t self._transformSet = True self.pchanged() Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -12,7 +12,6 @@ from matplotlib import affine as maffine from matplotlib import agg from matplotlib import axis as maxis -from matplotlib import bbox as mbbox from matplotlib import cbook from matplotlib import collections as mcoll from matplotlib import colors as mcolors @@ -33,7 +32,6 @@ iterable = cbook.iterable is_string_like = cbook.is_string_like -Wrapper = cbook.Wrapper def delete_masked_points(*args): @@ -484,8 +482,8 @@ """ martist.Artist.__init__(self) - self._position = rect - self._originalPosition = rect + self._position = maffine.Bbox.from_lbwh(*rect) + self._originalPosition = self._position.copy() self.set_axes(self) self.set_aspect('auto') self.set_adjustable('box') @@ -615,11 +613,11 @@ """ martist.Artist.set_figure(self, fig) - l, b, w, h = self._position - xmin = fig.bbox.xmin() - xmax = fig.bbox.xmax() - ymin = fig.bbox.ymin() - ymax = fig.bbox.ymax() + l, b, w, h = self._position.get_bounds() + xmin = fig.bbox.xmin + xmax = fig.bbox.xmax + ymin = fig.bbox.ymin + ymax = fig.bbox.ymax figw = xmax-xmin figh = ymax-ymin self.left = l*figw @@ -627,7 +625,7 @@ self.right = (l+w)*figw self.top = (b+h)*figh - self.bbox = mbbox.Bbox.from_lbrt( + self.bbox = maffine.Bbox.from_lbrt( self.left, self.bottom, self.right, self.top, ) @@ -639,7 +637,7 @@ set the dataLim and viewLim BBox attributes and the transData and transAxes Transformation attributes """ - Bbox = mbbox.Bbox + Bbox = maffine.Bbox if self._sharex is not None: left = self._sharex.viewLim.xmin() right = self._sharex.viewLim.xmax() @@ -655,11 +653,12 @@ self.viewLim = Bbox.from_lbrt(left, bottom, right, top) self.dataLim = Bbox.unit() + self.dataLim.track = True - self.transData = maffine.get_bbox_transform( + self.transData = maffine.BboxTransform( self.viewLim, self.bbox) - self.transAxes = maffine.get_bbox_transform( - self.dataLim, self.bbox) + self.transAxes = maffine.BboxTransform( + Bbox.unit(), self.bbox) print "_set_lim_and_transforms", self.viewLim, self.transData, self.dataLim, self.transAxes, self.bbox @@ -1184,9 +1183,8 @@ # MGDTODO ## self.dataLim.update_numerix(x, y, -1) print "update_datalim_numerix", self.dataLim, - self.dataLim = mbbox.Bbox.from_data(x, y) + self.dataLim.update_from_data(x, y) print self.dataLim - # MGDTODO side-effects def _get_verts_in_data_coords(self, trans, xys): if trans == self.transData: @@ -1247,7 +1245,7 @@ axis direction reversal that has already been done. """ # if image data only just use the datalim - print "autoscale_view" + print "autoscale_view", self._autoscaleon, scalex, scaley if not self._autoscaleon: return if (tight or (len(self.images)>0 and @@ -1504,7 +1502,7 @@ def get_xlim(self): 'Get the x axis range [xmin, xmax]' - return self.viewLim.intervalx().get_bounds() + return self.viewLim.intervalx def set_xlim(self, xmin=None, xmax=None, emit=True, **kwargs): @@ -1549,12 +1547,10 @@ # and min(xmin, xmax)<=0): # raise ValueError('Cannot set nonpositive limits with log transform') - xmin, xmax = mbbox.nonsingular(xmin, xmax, increasing=False) + xmin, xmax = maffine.nonsingular(xmin, xmax, increasing=False) - # MGDTODO: This is fairly cumbersome - # MGDTODO: side-effects on x-bounds should propagate - self.viewLim = mbbox.Bbox.from_lbrt(xmin, self.viewLim.ymin(), xmax, self.viewLim.ymax()) - print 'set_xlim', self.viewLim + self.viewLim.intervalx = (xmin, xmax) + print 'set_xlim', self.viewLim, xmin, xmax return xmin, xmax @@ -1634,7 +1630,7 @@ def get_ylim(self): 'Get the y axis range [ymin, ymax]' - return self.viewLim.intervaly().get_bounds() + return self.viewLim.intervaly def set_ylim(self, ymin=None, ymax=None, emit=True, **kwargs): """ @@ -1677,9 +1673,8 @@ # and min(ymin, ymax)<=0): # raise ValueError('Cannot set nonpositive limits with log transform') - ymin, ymax = mbbox.nonsingular(ymin, ymax, increasing=False) - # MGDTODO: side-effects on y-bounds should propagate - self.viewLim = mbbox.Bbox.from_lbrt(self.viewLim.xmin(), ymin, self.viewLim.xmax(), ymax) + ymin, ymax = maffine.nonsingular(ymin, ymax, increasing=False) + self.viewLim.intervaly = (ymin, ymax) if emit: self.callbacks.process('ylim_changed', self) print "set_ylim", self.viewLim Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -15,8 +15,8 @@ from ticker import NullFormatter, FixedFormatter, ScalarFormatter, LogFormatter from ticker import NullLocator, FixedLocator, LinearLocator, LogLocator, AutoLocator -from affine import Affine2D, blend_xy_sep_transform -from bbox import bbox_union +from affine import Affine2D, Bbox, blend_xy_sep_transform, interval_contains, \ + interval_contains_open from font_manager import FontProperties from text import Text, TextWithDash, _process_text_args from patches import bbox_artist @@ -160,7 +160,7 @@ def draw(self, renderer): if not self.get_visible(): return renderer.open_group(self.__name__) - midPoint = self.get_view_interval().contains_open( self.get_loc() ) + midPoint = interval_contains_open(self.get_view_interval(), self.get_loc() ) if midPoint: if self.gridOn: self.gridline.draw(renderer) @@ -239,7 +239,7 @@ trans = blend_xy_sep_transform( self.axes.transData, self.axes.transAxes) #offset the text downward with a post transformation - trans = trans + Affine2D().translated(0, -1 * self._padPixels) + trans = trans + Affine2D().translate(0, -1 * self._padPixels) t.set_transform(trans) self._set_artist_props(t) @@ -264,7 +264,7 @@ trans = blend_xy_sep_transform( self.axes.transData, self.axes.transAxes) # offset the text upward with a post transformation - trans = trans + Affine2D().translated(0, self._padPixels) + trans = trans + Affine2D().translate(0, self._padPixels) t.set_transform( trans ) self._set_artist_props(t) return t @@ -331,11 +331,11 @@ def get_view_interval(self): 'return the Interval instance for this axis view limits' - return self.axes.viewLim.intervalx() + return self.axes.viewLim.intervalx def get_data_interval(self): 'return the Interval instance for this axis data limits' - return self.axes.dataLim.intervalx() + return self.axes.dataLim.intervalx class YTick(Tick): @@ -362,7 +362,7 @@ trans = blend_xy_sep_transform( self.axes.transAxes, self.axes.transData) # offset the text leftward with a post transformation - trans = trans + Affine2D().translated(-1 * self._padPixels, 0) + trans = trans + Affine2D().translate(-1 * self._padPixels, 0) t.set_transform( trans ) #t.set_transform( self.axes.transData ) @@ -385,7 +385,7 @@ trans = blend_xy_sep_transform( self.axes.transAxes, self.axes.transData) # offset the text rightward with a post transformation - trans = trans + Affine2D().translated(self._padPixels, 0) + trans = trans + Affine2D().translate(self._padPixels, 0) t.set_transform( trans ) self._set_artist_props(t) return t @@ -455,11 +455,11 @@ def get_view_interval(self): 'return the Interval instance for this axis view limits' - return self.axes.viewLim.intervaly() + return self.axes.viewLim.intervaly def get_data_interval(self): 'return the Interval instance for this axis data limits' - return self.axes.dataLim.intervaly() + return self.axes.dataLim.intervaly class Ticker: @@ -575,7 +575,7 @@ interval = self.get_view_interval() for tick, loc, label in zip(majorTicks, majorLocs, majorLabels): if tick is None: continue - if not interval.contains(loc): continue + if not interval_contains(interval, loc): continue seen[loc] = 1 tick.update_position(loc) tick.set_label1(label) @@ -1034,19 +1034,19 @@ bottom = self.axes.bbox.ymin() else: - bbox = bbox_union(bboxes) - bottom = bbox.ymin() + bbox = Bbox.union(bboxes) + bottom = bbox.ymin self.label.set_position( (x, bottom-self.LABELPAD*self.figure.dpi/72.0)) # self.label.set_position( (x, bottom-self.LABELPAD*self.figure.dpi.get()/72.0)) MGDTODO else: if not len(bboxes2): - top = self.axes.bbox.ymax() + top = self.axes.bbox.ymax else: bbox = bbox_union(bboxes2) - top = bbox.ymax() + top = bbox.ymax self.label.set_position( (x, top+self.LABELPAD*self.figure.dpi.get()/72.0)) @@ -1059,8 +1059,8 @@ if not len(bboxes): bottom = self.axes.bbox.ymin() else: - bbox = bbox_union(bboxes) - bottom = bbox.ymin() + bbox = Bbox.union(bboxes) + bottom = bbox.ymin self.offsetText.set_position((x, bottom-self.OFFSETTEXTPAD*self.figure.dpi/72.0)) # self.offsetText.set_position((x, bottom-self.OFFSETTEXTPAD*self.figure.dpi.get()/72.0)) MGDTODO @@ -1133,11 +1133,11 @@ def get_view_interval(self): 'return the Interval instance for this axis view limits' - return self.axes.viewLim.intervalx() + return self.axes.viewLim.intervalx def get_data_interval(self): 'return the Interval instance for this axis data limits' - return self.axes.dataLim.intervalx() + return self.axes.dataLim.intervalx class YAxis(Axis): @@ -1223,11 +1223,11 @@ x,y = self.label.get_position() if self.label_position == 'left': if not len(bboxes): - left = self.axes.bbox.xmin() + left = self.axes.bbox.xmin else: - bbox = bbox_union(bboxes) - left = bbox.xmin() + bbox = Bbox.union(bboxes) + left = bbox.xmin self.label.set_position( (left-self.LABELPAD*self.figure.dpi/72.0, y)) # self.label.set_position( (left-self.LABELPAD*self.figure.dpi.get()/72.0, y)) MGDTODO @@ -1248,7 +1248,7 @@ boxes of all the ticklabels """ x,y = self.offsetText.get_position() - top = self.axes.bbox.ymax() + top = self.axes.bbox.ymax self.offsetText.set_position((x, top+self.OFFSETTEXTPAD*self.figure.dpi/72.0)) # self.offsetText.set_position((x, top+self.OFFSETTEXTPAD*self.figure.dpi.get()/72.0)) MGDTODO @@ -1335,11 +1335,11 @@ def get_view_interval(self): 'return the Interval instance for this axis view limits' - return self.axes.viewLim.intervaly() + return self.axes.viewLim.intervaly def get_data_interval(self): 'return the Interval instance for this axis data limits' - return self.axes.dataLim.intervaly() + return self.axes.dataLim.intervaly Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -84,7 +84,7 @@ from matplotlib.font_manager import findfont from matplotlib.ft2font import FT2Font, LOAD_DEFAULT from matplotlib.mathtext import MathTextParser -from matplotlib.bbox import Bbox +from matplotlib.affine import Bbox from _backend_agg import RendererAgg as _RendererAgg Modified: branches/transforms/lib/matplotlib/backends/backend_tkagg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_tkagg.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/backends/backend_tkagg.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -219,13 +219,13 @@ def motion_notify_event(self, event): x = event.x # flipy so y=0 is bottom of canvas - y = self.figure.bbox.height() - event.y + y = self.figure.bbox.height - event.y FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event) def button_press_event(self, event): x = event.x # flipy so y=0 is bottom of canvas - y = self.figure.bbox.height() - event.y + y = self.figure.bbox.height - event.y num = getattr(event, 'num', None) if sys.platform=='darwin': @@ -239,7 +239,7 @@ def button_release_event(self, event): x = event.x # flipy so y=0 is bottom of canvas - y = self.figure.bbox.height() - event.y + y = self.figure.bbox.height - event.y num = getattr(event, 'num', None) @@ -609,7 +609,7 @@ return b def _init_toolbar(self): - xmin, xmax = self.canvas.figure.bbox.intervalx().get_bounds() + xmin, xmax = self.canvas.figure.bbox.intervalx height, width = 50, xmax-xmin Tk.Frame.__init__(self, master=self.window, width=width, height=height, Deleted: branches/transforms/lib/matplotlib/bbox.py =================================================================== --- branches/transforms/lib/matplotlib/bbox.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/bbox.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -1,161 +0,0 @@ -""" -A convenience class for handling bounding boxes - -2007 Michael Droettboom -""" - -import numpy as N - -class Interval: - def __init__(self, bounds): - self._bounds = N.array(bounds, N.float_) - - def contains(self, value): - bounds = self._bounds - return value >= bounds[0] and value <= bounds[1] - - def contains_open(self, value): - bounds = self._bounds - return value > bounds[0] and value < bounds[1] - - def get_bounds(self): - return self._bounds - - def span(self): - bounds = self._bounds - return bounds[1] - bounds[0] - -class Bbox: - def __init__(self, points): - self._points = N.array(points, N.float_) - - #@staticmethod - def unit(): - return Bbox([[0,0], [1,1]]) - unit = staticmethod(unit) - - #@staticmethod - def from_lbwh(left, bottom, width, height): - return Bbox([[left, bottom], [left + width, bottom + height]]) - from_lbwh = staticmethod(from_lbwh) - - #@staticmethod - def from_lbrt(left, bottom, right, top): - return Bbox([[left, bottom], [right, top]]) - from_lbrt = staticmethod(from_lbrt) - - #@staticmethod - def from_data(x, y): - return Bbox([[x.min(), y.min()], [x.max(), y.max()]]) - from_data = staticmethod(from_data) - - def copy(self): - return Bbox(self._points.copy()) - - def __repr__(self): - return 'Bbox(%s)' % repr(self._points) - __str__ = __repr__ - - def __cmp__(self, other): - # MGDTODO: Totally suboptimal - if isinstance(other, Bbox): - return (self._points == other._points).all() - return -1 - - # MGDTODO: Probably a more efficient ways to do this... - def xmin(self): - return self._points[0,0] - - def ymin(self): - return self._points[0,1] - - def xmax(self): - return self._points[1,0] - - def ymax(self): - return self._points[1,1] - - def width(self): - return self.xmax() - self.xmin() - - def height(self): - return self.ymax() - self.ymin() - - def transform(self, transform): - return Bbox(transform(self._points)) - - def inverse_transform(self, transform): - return Bbox(transform.inverted()(self._points)) - - def get_bounds(self): - return (self.xmin(), self.ymin(), - self.xmax() - self.xmin(), self.ymax() - self.ymin()) - - # MGDTODO: This is an inefficient way to do this - def intervalx(self): - return Interval(self._points[0]) - - def intervaly(self): - return Interval(self._points[1]) - - def scaled(self, sw, sh): - width = self.width() - height = self.height() - deltaw = (sw * width - width) / 2.0 - deltah = (sh * height - height) / 2.0 - a = N.array([[-deltaw, -deltah], [deltaw, deltah]]) - return Bbox(self._points + a) - - def contains(self, x, y): - return (x >= self.xmin() and x <= self.xmax() and - y >= self.ymin() and y <= self.ymax()) - -def bbox_union(bboxes): - """ - Return the Bbox that bounds all bboxes - """ - assert(len(bboxes)) - - if len(bboxes) == 1: - return bboxes[0] - - bbox = bboxes[0] - xmin = bbox.xmin() - ymin = bbox.ymin() - xmax = bbox.xmax() - ymax = bbox.ymax() - - for bbox in bboxes[1:]: - xmin = min(xmin, bbox.xmin()) - ymin = min(ymin, bbox.ymin()) - xmax = max(xmax, bbox.xmax()) - ymax = max(ymax, bbox.ymax()) - - return Bbox.from_lbrt(xmin, ymin, xmax, ymax) - -# MGDTODO: There's probably a better place for this -def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): - ''' - Ensure the endpoints of a range are not too close together. - - "too close" means the interval is smaller than 'tiny' times - the maximum absolute value. - - If they are too close, each will be moved by the 'expander'. - If 'increasing' is True and vmin > vmax, they will be swapped, - regardless of whether they are too close. - ''' - swapped = False - if vmax < vmin: - vmin, vmax = vmax, vmin - swapped = True - if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny: - if vmin==0.0: - vmin = -expander - vmax = expander - else: - vmin -= expander*abs(vmin) - vmax += expander*abs(vmax) - if swapped and not increasing: - vmin, vmax = vmax, vmin - return vmin, vmax Modified: branches/transforms/lib/matplotlib/figure.py =================================================================== --- branches/transforms/lib/matplotlib/figure.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/figure.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -18,8 +18,7 @@ from text import Text, _process_text_args from legend import Legend -from affine import get_bbox_transform -from bbox import Bbox +from affine import Bbox, BboxTransform from ticker import FormatStrFormatter from cm import ScalarMappable from contour import ContourSet @@ -105,7 +104,7 @@ class Figure(Artist): def __str__(self): - return "Figure(%gx%g)"%(self.figwidth, self.figheight) + return "Figure(%gx%g)"%(self.bbox.max) # return "Figure(%gx%g)"%(self.figwidth.get(),self.figheight.get()) def __init__(self, @@ -130,13 +129,13 @@ if edgecolor is None: edgecolor = rcParams['figure.edgecolor'] self.dpi = dpi - self.figwidth = figsize[0] * dpi - self.figheight = figsize[1] * dpi - self.bbox = Bbox.from_lbwh(0, 0, self.figwidth, self.figheight) + figwidth = figsize[0] * dpi + figheight = figsize[1] * dpi + self.bbox = Bbox.from_lbwh(0, 0, figwidth, figheight) self.frameon = frameon - self.transFigure = get_bbox_transform( Bbox.unit(), self.bbox) + self.transFigure = BboxTransform( Bbox.unit(), self.bbox) @@ -325,9 +324,9 @@ else: w,h = args - - self.figwidth = w - self.figheight = h + dpival = self.dpi + self.bbox.max = w * dpival, h * dpival + print self.bbox # self.figwidth.set(w) MGDTODO # self.figheight.set(h) @@ -341,7 +340,7 @@ manager.resize(int(canvasw), int(canvash)) def get_size_inches(self): - return self.figwidth, self.figheight + return self.bbox.max # return self.figwidth.get(), self.figheight.get() MGDTODO def get_edgecolor(self): @@ -354,12 +353,12 @@ def get_figwidth(self): 'Return the figwidth as a float' - return self.figwidth + return self.bbox.xmax # return self.figwidth.get() MGDTODO def get_figheight(self): 'Return the figheight as a float' - return self.figheight.get() + return self.bbox.ymax def get_dpi(self): 'Return the dpi as a float' @@ -402,7 +401,7 @@ ACCEPTS: float """ # self.figwidth.set(val) MGDTODO - self.figwidth = val + self.bbox.xmax = val def set_figheight(self, val): """ @@ -411,7 +410,7 @@ ACCEPTS: float """ # MGDTODO (set()) - self.figheight = val + self.bbox.ymax = val def set_frameon(self, b): """ Modified: branches/transforms/lib/matplotlib/legend.py =================================================================== --- branches/transforms/lib/matplotlib/legend.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/legend.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -34,8 +34,7 @@ from patches import Patch, Rectangle, RegularPolygon, Shadow, bbox_artist, draw_bbox from collections import LineCollection, RegularPolyCollection, PatchCollection from text import Text -from affine import get_bbox_transform -from bbox import Bbox, bbox_union +from affine import Bbox, BboxTransform def line_cuts_bbox(line, bbox): """ Return True if and only if line cuts bbox. """ @@ -165,7 +164,7 @@ else: raise TypeError("Legend needs either Axes or Figure as parent") self.parent = parent - self.set_transform( get_bbox_transform( Bbox.unit(), parent.bbox) ) + self.set_transform( BboxTransform( Bbox.unit(), parent.bbox) ) if loc is None: loc = rcParams["legend.loc"] @@ -260,10 +259,10 @@ bboxesAll = bboxesText bboxesAll.extend(bboxesHandles) - bbox = bbox_union(bboxesAll) + bbox = Bbox.union(bboxesAll) self.save = bbox - ibox = bbox.inverse_transform(self.get_transform()) + ibox = bbox.inverse_transformed(self.get_transform()) self.ibox = ibox return ibox @@ -531,7 +530,7 @@ if not len(self.legendHandles) and not len(self.texts): return def get_tbounds(text): #get text bounds in axes coords bbox = text.get_window_extent(renderer) - bboxa = bbox.inverse_transform(self.get_transform()) + bboxa = bbox.inverse_transformed(self.get_transform()) return bboxa.get_bounds() hpos = [] Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -14,11 +14,11 @@ import numerix.ma as ma from matplotlib import verbose import artist +from affine import Bbox from artist import Artist, setp from cbook import iterable, is_string_like, is_numlike from colors import colorConverter -from bbox import Bbox from matplotlib import rcParams # special-purpose marker identifiers: Modified: branches/transforms/lib/matplotlib/table.py =================================================================== --- branches/transforms/lib/matplotlib/table.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/table.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -29,7 +29,7 @@ from patches import Rectangle from cbook import enumerate, is_string_like, flatten from text import Text -from bbox import Bbox, bbox_union +from affine import Bbox @@ -130,7 +130,7 @@ def get_text_bounds(self, renderer): """ Get text bounds in axes co-ordinates. """ bbox = self._text.get_window_extent(renderer) - bbox.inverse_transform(self.get_transform()) + bboxa = bbox.inverse_transformed(self.get_transform()) return bboxa.get_bounds() def get_required_width(self, renderer): Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/text.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -15,7 +15,7 @@ from cbook import enumerate, is_string_like, maxdict, is_numlike from font_manager import FontProperties from patches import bbox_artist, YAArrow -from bbox import Bbox, bbox_union +from affine import Bbox from lines import Line2D import matplotlib.nxutils as nxutils Modified: branches/transforms/lib/matplotlib/ticker.py =================================================================== --- branches/transforms/lib/matplotlib/ticker.py 2007-09-12 07:04:38 UTC (rev 3834) +++ branches/transforms/lib/matplotlib/ticker.py 2007-09-12 13:36:25 UTC (rev 3835) @@ -115,7 +115,7 @@ import matplotlib as mpl from matplotlib import verbose, rcParams from matplotlib import cbook -from matplotlib import bbox as mbbox +from matplotlib import affine as maffine @@ -148,8 +148,9 @@ cases where the Intervals do not need to be updated automatically. ''' - self.dataInterval = mbbox.Interval([vmin, vmax]) - self.viewInterval = mbbox.Interval([vmin, vmax]) + # MGDTODO: Interval no longer exists + self.dataInterval = maffine.Interval([vmin, vmax]) + self.viewInterval = maffine.Interval([vmin, vmax]) class Formatter(TickHelper): """ @@ -341,7 +342,8 @@ self.locs = locs if len(self.locs) > 0: self.verify_intervals() - d = abs(self.viewInterval.span()) + vmin, vmax = self.viewInterval + d = abs(vmax - vmin) if self._useOffset: self._set_offset(d) self._set_orderOfMagnitude(d) self._set_format() @@ -571,7 +573,7 @@ def autoscale(self): 'autoscale the view limits' self.verify_intervals() - return mbbox.nonsingular(*self.dataInterval.get_bounds()) + return maffine.nonsingular(*self.dataInterval.get_bounds()) def pan(self, numsteps): 'Pan numticks (can be positive or negative)' @@ -713,7 +715,7 @@ vmin = math.floor(scale*vmin)/scale vmax = math.ceil(scale*vmax)/scale - return mbbox.nonsingular(vmin, vmax) + return maffine.nonsingular(vmin, vmax) def closeto(x,y): @@ -797,7 +799,7 @@ vmin -=1 vmax +=1 - return mbbox.nonsingular(vmin, vmax) + return maffine.nonsingular(vmin, vmax) def scale_range(vmin, vmax, n = 1, threshold=100): dv = abs(vmax - vmin) @@ -864,14 +866,14 @@ def __call__(self): self.verify_intervals() - vmin, vmax = self.viewInterval.get_bounds() - vmin, vmax = mbbox.nonsingular(vmin, vmax, expander = 0.05) + vmin, vmax = self.viewInterval + vmin, vmax = maffine.nonsingular(vmin, vmax, expander = 0.05) return self.bin_boundaries(vmin, vmax) def autoscale(self): self.verify_intervals() - dmin, dmax = self.dataInterval.get_bounds() - dmin, dmax = mbbox.nonsingular(dmin, dmax, expander = 0.05) + dmin, dmax = self.dataInterval + dmin, dmax = maffine.nonsingular(dmin, dmax, expander = 0.05) return npy.take(self.bin_boundaries(dmin, dmax), [0,-1]) @@ -972,7 +974,7 @@ if vmin==vmax: vmin = decade_down(vmin,self._base) vmax = decade_up(vmax,self._base) - return mbbox.nonsingular(vmin, vmax) + return maffine.nonsingular(vmin, vmax) class AutoLocator(MaxNLocator): def __init__(self): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jd...@us...> - 2007-09-12 15:41:28
|
Revision: 3838 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3838&view=rev Author: jdh2358 Date: 2007-09-12 08:41:22 -0700 (Wed, 12 Sep 2007) Log Message: ----------- removed Interval from canonical tickers and formatters Modified Paths: -------------- branches/transforms/lib/matplotlib/affine.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/ticker.py Modified: branches/transforms/lib/matplotlib/affine.py =================================================================== --- branches/transforms/lib/matplotlib/affine.py 2007-09-12 14:46:03 UTC (rev 3837) +++ branches/transforms/lib/matplotlib/affine.py 2007-09-12 15:41:22 UTC (rev 3838) @@ -33,6 +33,9 @@ self._points = N.asarray(points, N.float_) self.track = False + # JDH: if you define a del method, the garbage collector won't + # destory cyclic references, so make sure you either manage these + # yourself or remove the __del__ after testing def __del__(self): if self.track: print "Bbox::__del__" @@ -52,6 +55,11 @@ return Bbox([[left, bottom], [right, top]]) from_lbrt = staticmethod(from_lbrt) + + # JDH: the update method will update the box limits from the + # existing limits and the new data; it appears here you are just + # using the new data. We use an "ignore" flag to specify whether + # you want to include the existing data or not in the update def update_from_data(self, x, y): self._points = N.array([[x.min(), y.min()], [x.max(), y.max()]], N.float_) self.invalidate() Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-12 14:46:03 UTC (rev 3837) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-12 15:41:22 UTC (rev 3838) @@ -855,9 +855,9 @@ ACCEPTS: A Formatter instance """ self.major.formatter = formatter - self.major.formatter.set_view_interval( self.get_view_interval() ) - self.major.formatter.set_data_interval( self.get_data_interval() ) + self.major.formatter.set_axis(self) + def set_minor_formatter(self, formatter): """ Set the formatter of the minor ticker @@ -865,8 +865,7 @@ ACCEPTS: A Formatter instance """ self.minor.formatter = formatter - self.minor.formatter.set_view_interval( self.get_view_interval() ) - self.minor.formatter.set_data_interval( self.get_data_interval() ) + self.minor.formatter.set_axis(self) def set_major_locator(self, locator): @@ -876,8 +875,7 @@ ACCEPTS: a Locator instance """ self.major.locator = locator - self.major.locator.set_view_interval( self.get_view_interval() ) - self.major.locator.set_data_interval( self.get_data_interval() ) + self.major.locator.set_axis(self) def set_minor_locator(self, locator): @@ -887,8 +885,7 @@ ACCEPTS: a Locator instance """ self.minor.locator = locator - self.minor.locator.set_view_interval( self.get_view_interval() ) - self.minor.locator.set_data_interval( self.get_data_interval() ) + self.minor.locator.set_axis(self) def set_pickradius(self, pickradius): """ Modified: branches/transforms/lib/matplotlib/ticker.py =================================================================== --- branches/transforms/lib/matplotlib/ticker.py 2007-09-12 14:46:03 UTC (rev 3837) +++ branches/transforms/lib/matplotlib/ticker.py 2007-09-12 15:41:22 UTC (rev 3838) @@ -99,13 +99,7 @@ minor ticks. See the matplotlib.dates module for more information and examples of using date locators and formatters. -DEVELOPERS NOTE -If you are implementing your own class or modifying one of these, it -is critical that you use viewlim and dataInterval READ ONLY MODE so -multiple axes can share the same locator w/o side effects! - - """ @@ -121,37 +115,11 @@ class TickHelper: + axis = None + def set_axis(self, axis): + self.axis = axis - viewInterval = None - dataInterval = None - def verify_intervals(self): - if self.dataInterval is None: - raise RuntimeError("You must set the data interval to use this function") - - if self.viewInterval is None: - raise RuntimeError("You must set the view interval to use this function") - - - def set_view_interval(self, interval): - self.viewInterval = interval - - def set_data_interval(self, interval): - self.dataInterval = interval - - def set_bounds(self, vmin, vmax): - ''' - Set dataInterval and viewInterval from numeric vmin, vmax. - - This is for stand-alone use of Formatters and/or - Locators that require these intervals; that is, for - cases where the Intervals do not need to be updated - automatically. - ''' - # MGDTODO: Interval no longer exists - self.dataInterval = maffine.Interval([vmin, vmax]) - self.viewInterval = maffine.Interval([vmin, vmax]) - class Formatter(TickHelper): """ Convert the tick location to a string @@ -341,9 +309,8 @@ 'set the locations of the ticks' self.locs = locs if len(self.locs) > 0: - self.verify_intervals() - vmin, vmax = self.viewInterval - d = abs(vmax - vmin) + vmin, vmax = self.axis.get_view_interval() + d = abs(vmax-vmin) if self._useOffset: self._set_offset(d) self._set_orderOfMagnitude(d) self._set_format() @@ -865,14 +832,12 @@ def __call__(self): - self.verify_intervals() - vmin, vmax = self.viewInterval + vmin, vmax = self.axis.get_view_interval() vmin, vmax = maffine.nonsingular(vmin, vmax, expander = 0.05) return self.bin_boundaries(vmin, vmax) def autoscale(self): - self.verify_intervals() - dmin, dmax = self.dataInterval + dmin, dmax = self.axis.get_data_interval() dmin, dmax = maffine.nonsingular(dmin, dmax, expander = 0.05) return npy.take(self.bin_boundaries(dmin, dmax), [0,-1]) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-12 17:25:26
|
Revision: 3839 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3839&view=rev Author: mdboom Date: 2007-09-12 10:25:19 -0700 (Wed, 12 Sep 2007) Log Message: ----------- Milestone -- simple_plot.py working with new affine framework (with the exception of dpi propagation) Modified Paths: -------------- branches/transforms/lib/matplotlib/affine.py branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/figure.py branches/transforms/lib/matplotlib/lines.py Modified: branches/transforms/lib/matplotlib/affine.py =================================================================== --- branches/transforms/lib/matplotlib/affine.py 2007-09-12 15:41:22 UTC (rev 3838) +++ branches/transforms/lib/matplotlib/affine.py 2007-09-12 17:25:19 UTC (rev 3839) @@ -33,29 +33,29 @@ self._points = N.asarray(points, N.float_) self.track = False - # JDH: if you define a del method, the garbage collector won't - # destory cyclic references, so make sure you either manage these - # yourself or remove the __del__ after testing - def __del__(self): - if self.track: - print "Bbox::__del__" - #@staticmethod def unit(): - return Bbox([[0,0], [1,1]]) + return Bbox.from_lbrt(0., 0., 1., 1.) unit = staticmethod(unit) #@staticmethod def from_lbwh(left, bottom, width, height): - return Bbox([[left, bottom], [left + width, bottom + height]]) + return Bbox.from_lbrt(left, bottom, left + width, bottom + height) from_lbwh = staticmethod(from_lbwh) #@staticmethod - def from_lbrt(left, bottom, right, top): - return Bbox([[left, bottom], [right, top]]) + def from_lbrt(*args): + points = N.array(args, dtype=N.float_).reshape(2, 2) + return Bbox(points) from_lbrt = staticmethod(from_lbrt) - + def __cmp__(self, other): + # MGDTODO: Totally suboptimal + if isinstance(other, Bbox): + if (self._points == other._points).all(): + return 0 + return -1 + # JDH: the update method will update the box limits from the # existing limits and the new data; it appears here you are just # using the new data. We use an "ignore" flag to specify whether @@ -63,31 +63,18 @@ def update_from_data(self, x, y): self._points = N.array([[x.min(), y.min()], [x.max(), y.max()]], N.float_) self.invalidate() - if self.track: - print "Bbox::update_from_data", self._points def copy(self): - if self.track: - print "Bbox::copy" return Bbox(self._points.copy()) def __repr__(self): return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ - def __cmp__(self, other): - # MGDTODO: Totally suboptimal - if isinstance(other, Bbox): - return (self._points == other._points).all() - return -1 - # MGDTODO: Probably a more efficient ways to do this... def _get_xmin(self): - if self.track: - print "Bbox::_get_xmin" return self._points[0, 0] def _set_xmin(self, val): - print "Bbox::_set_xmin" self._points[0, 0] = val self.invalidate() xmin = property(_get_xmin, _set_xmin) @@ -150,10 +137,10 @@ height = property(_get_height) def transformed(self, transform): - return Bbox(self.transform(self._points)) + return Bbox(transform(self._points)) def inverse_transformed(self, transform): - return Bbox(self.transform.inverted()(self._points)) + return Bbox(transform.inverted()(self._points)) def get_bounds(self): return (self.xmin, self.ymin, @@ -249,6 +236,14 @@ return "Affine2D(%s)" % repr(self._mtx) __str__ = __repr__ + def __cmp__(self, other): + # MGDTODO: We need to decide if we want deferred transforms + # to be equal to this one + if isinstance(other, Affine2D): + if (self.get_matrix() == other.get_matrix()).all(): + return 0 + return -1 + def _do_invalidation(self): result = self._inverted is None self._inverted = None @@ -380,10 +375,9 @@ if self._mtx is None: x_mtx = self._x.get_matrix() y_mtx = self._y.get_matrix() + # This works because we already know the transforms are + # separable self._mtx = N.vstack([x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]]) -# self._mtx = self.matrix_from_values( -# x_mtx[0,0], 0.0, 0.0, y_mtx[1,1], x_mtx[0,2], y_mtx[1,2]) - print "Blended", x_mtx, y_mtx, self._mtx def is_separable(self): return True @@ -429,8 +423,8 @@ def _make__mtx(self): if self._mtx is None: self._mtx = self._concat( - self._b.get_matrix(), - self._a.get_matrix()) + self._a.get_matrix(), + self._b.get_matrix()) def get_matrix(self): self._make__mtx() @@ -547,12 +541,70 @@ return interval[0] < val and interval[1] > val if __name__ == '__main__': + bbox = Bbox.from_lbrt(10., 15., 20., 25.) + assert bbox.xmin == 10 + assert bbox.ymin == 15 + assert bbox.xmax == 20 + assert bbox.ymax == 25 + + assert N.all(bbox.min == [10, 15]) + assert N.all(bbox.max == [20, 25]) + assert N.all(bbox.intervalx == (10, 20)) + assert N.all(bbox.intervaly == (15, 25)) + + assert bbox.width == 10 + assert bbox.height == 10 + + assert bbox.get_bounds() == (10, 15, 10, 10) + + bbox.intervalx = (11, 21) + bbox.intervaly = (16, 26) + + assert bbox.get_bounds() == (11, 16, 10, 10) + + bbox.xmin = 12 + bbox.ymin = 17 + bbox.xmax = 22 + bbox.ymax = 27 + + assert bbox.get_bounds() == (12, 17, 10, 10) + + bbox = Bbox.from_lbwh(10, 11, 12, 13) + assert bbox.get_bounds() == (10, 11, 12, 13) + + bbox_copy = bbox.copy() + assert bbox == bbox_copy + bbox_copy.max = (14, 15) + assert bbox.get_bounds() == (10, 11, 12, 13) + assert bbox_copy.get_bounds() == (10, 11, 4, 4) + bbox1 = Bbox([[10., 15.], [20., 25.]]) bbox2 = Bbox([[30., 35.], [40., 45.]]) trans = BboxTransform(bbox1, bbox2) - print trans(bbox1._points) + bbox3 = bbox1.transformed(trans) + assert bbox3 == bbox2 - bbox2.intervalx = 50, 55 - print trans(bbox1._points) + translation = Affine2D().translate(10, 20) + assert translation.to_values() == (1, 0, 0, 1, 10, 20) + scale = Affine2D().scale(10, 20) + assert scale.to_values() == (10, 0, 0, 20, 0, 0) + rotation = Affine2D().rotate_deg(30) + print rotation.to_values() == (0.86602540378443871, 0.49999999999999994, -0.49999999999999994, 0.86602540378443871, 0.0, 0.0) + points = N.array([[1,2],[3,4],[5,6],[7,8]], N.float_) + translated_points = translation(points) + assert (translated_points == [[11., 22.], [13., 24.], [15., 26.], [17., 28.]]).all() + scaled_points = scale(points) + print scaled_points + rotated_points = rotation(points) + print rotated_points + + tpoints1 = rotation(translation(scale(points))) + trans_sum = rotation + translation + scale + tpoints2 = trans_sum(points) + print tpoints1, tpoints2 + print tpoints1 == tpoints2 + # Need to do some sort of fuzzy comparison here? + # assert (tpoints1 == tpoints2).all() + __all__ = ['Transform', 'Affine2D'] Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-12 15:41:22 UTC (rev 3838) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-12 17:25:19 UTC (rev 3839) @@ -653,15 +653,12 @@ self.viewLim = Bbox.from_lbrt(left, bottom, right, top) self.dataLim = Bbox.unit() - self.dataLim.track = True self.transData = maffine.BboxTransform( self.viewLim, self.bbox) self.transAxes = maffine.BboxTransform( Bbox.unit(), self.bbox) - print "_set_lim_and_transforms", self.viewLim, self.transData, self.dataLim, self.transAxes, self.bbox - # MGDTODO # if self._sharex: # self.transData.set_funcx(self._sharex.transData.get_funcx()) @@ -697,7 +694,6 @@ # # Change values within self._position--don't replace it. # for num,val in zip(pos, self._position): # val.set(num) - print "set_position", self._position, pos self._position = pos # MGDTODO: side-effects if which in ('both', 'original'): @@ -1182,9 +1178,7 @@ #print type(x), type(y) # MGDTODO ## self.dataLim.update_numerix(x, y, -1) - print "update_datalim_numerix", self.dataLim, self.dataLim.update_from_data(x, y) - print self.dataLim def _get_verts_in_data_coords(self, trans, xys): if trans == self.transData: @@ -1245,8 +1239,6 @@ axis direction reversal that has already been done. """ # if image data only just use the datalim - print "autoscale_view", self._autoscaleon, scalex, scaley - if not self._autoscaleon: return if (tight or (len(self.images)>0 and len(self.lines)==0 and @@ -1274,7 +1266,7 @@ def draw(self, renderer=None, inframe=False): "Draw everything (plot lines, axes, labels)" - if renderer is None: + if renderer is None: renderer = self._cachedRenderer if renderer is None: @@ -1550,7 +1542,6 @@ xmin, xmax = maffine.nonsingular(xmin, xmax, increasing=False) self.viewLim.intervalx = (xmin, xmax) - print 'set_xlim', self.viewLim, xmin, xmax return xmin, xmax @@ -1654,7 +1645,6 @@ ACCEPTS: len(2) sequence of floats """ - print "set_ylim", ymin, ymax, emit if ymax is None and iterable(ymin): ymin,ymax = ymin @@ -1676,7 +1666,6 @@ ymin, ymax = maffine.nonsingular(ymin, ymax, increasing=False) self.viewLim.intervaly = (ymin, ymax) if emit: self.callbacks.process('ylim_changed', self) - print "set_ylim", self.viewLim return ymin, ymax Modified: branches/transforms/lib/matplotlib/figure.py =================================================================== --- branches/transforms/lib/matplotlib/figure.py 2007-09-12 15:41:22 UTC (rev 3838) +++ branches/transforms/lib/matplotlib/figure.py 2007-09-12 17:25:19 UTC (rev 3839) @@ -326,7 +326,6 @@ dpival = self.dpi self.bbox.max = w * dpival, h * dpival - print self.bbox # self.figwidth.set(w) MGDTODO # self.figheight.set(h) Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-12 15:41:22 UTC (rev 3838) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-12 17:25:19 UTC (rev 3839) @@ -389,7 +389,6 @@ def set_axes(self, ax): - print "set_axes" Artist.set_axes(self, ax) if ax.xaxis is not None: self._xcid = ax.xaxis.callbacks.connect('units', self.recache) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-12 19:48:00
|
Revision: 3842 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3842&view=rev Author: mdboom Date: 2007-09-12 12:47:56 -0700 (Wed, 12 Sep 2007) Log Message: ----------- More progress. Zooming and panning working thanks to John's patch. Modified Paths: -------------- branches/transforms/lib/matplotlib/affine.py branches/transforms/lib/matplotlib/artist.py branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/backends/backend_agg.py branches/transforms/lib/matplotlib/backends/backend_tkagg.py Modified: branches/transforms/lib/matplotlib/affine.py =================================================================== --- branches/transforms/lib/matplotlib/affine.py 2007-09-12 18:22:24 UTC (rev 3841) +++ branches/transforms/lib/matplotlib/affine.py 2007-09-12 19:47:56 UTC (rev 3842) @@ -8,9 +8,16 @@ from numpy.linalg import inv from sets import Set +# MGDTODO: The name of this module is bad, since it deals with +# non-affine transformations as well. It should probably just be +# "transforms", but we already had one of those... ;) + # MGDTODO: This creates a ton of cyclical references. We may want to # consider using weak references +# MGDTODO: deep copying is probably incorrect wrt the parent/child +# relationships + class TransformNode(object): def __init__(self): self._parents = Set() @@ -48,29 +55,31 @@ points = N.array(args, dtype=N.float_).reshape(2, 2) return Bbox(points) from_lbrt = staticmethod(from_lbrt) + + def __copy__(self): + return Bbox(self._points.copy()) + def __deepcopy__(self, memo): + return Bbox(self._points.copy()) + def __cmp__(self, other): # MGDTODO: Totally suboptimal - if isinstance(other, Bbox): - if (self._points == other._points).all(): - return 0 + if isinstance(other, Bbox) and (self._points == other._points).all(): + return 0 return -1 - + + def __repr__(self): + return 'Bbox(%s)' % repr(self._points) + __str__ = __repr__ + # JDH: the update method will update the box limits from the # existing limits and the new data; it appears here you are just # using the new data. We use an "ignore" flag to specify whether # you want to include the existing data or not in the update - def update_from_data(self, x, y): + def update_from_data(self, x, y, ignore=True): self._points = N.array([[x.min(), y.min()], [x.max(), y.max()]], N.float_) self.invalidate() - - def copy(self): - return Bbox(self._points.copy()) - def __repr__(self): - return 'Bbox(%s)' % repr(self._points) - __str__ = __repr__ - # MGDTODO: Probably a more efficient ways to do this... def _get_xmin(self): return self._points[0, 0] @@ -136,19 +145,24 @@ return self.ymax - self.ymin height = property(_get_height) + def _get_bounds(self): + return (self.xmin, self.ymin, + self.xmax - self.xmin, self.ymax - self.ymin) + def _set_bounds(self, bounds): + l,b,w,h = bounds + self._points = N.array([[l, b], [l+w, b+h]], N.float_) + self.invalidate() + bounds = property(_get_bounds, _set_bounds) + def transformed(self, transform): return Bbox(transform(self._points)) def inverse_transformed(self, transform): return Bbox(transform.inverted()(self._points)) - def get_bounds(self): - return (self.xmin, self.ymin, - self.xmax - self.xmin, self.ymax - self.ymin) - def expanded(self, sw, sh): - width = self.width() - height = self.height() + width = self.width + height = self.height deltaw = (sw * width - width) / 2.0 deltah = (sh * height - height) / 2.0 a = N.array([[-deltaw, -deltah], [deltaw, deltah]]) @@ -199,6 +213,9 @@ if isinstance(other, Transform): return composite_transform_factory(other, self) raise TypeError("Can not add Transform to object of type '%s'" % type(other)) + + def transform_point(self, point): + return self.__call__([point])[0] def has_inverse(self): raise NotImplementedError() @@ -211,49 +228,27 @@ def is_affine(self): return False - -class Affine2D(Transform): + +# MGDTODO: Separate out Affine2DBase / Affine2DConcrete so BlendedAffine and CompositeAffine don't have translate/scale/rotate members + +class Affine2DBase(Transform): input_dims = 2 output_dims = 2 - - def __init__(self, matrix = None): - """ - Initialize an Affine transform from a 3x3 numpy float array. - a c e - b d f - 0 0 1 - """ + def __init__(self): Transform.__init__(self) - if matrix is None: - matrix = N.identity(3) - else: - assert matrix.shape == (3, 3) - self._mtx = matrix self._inverted = None - def __repr__(self): - return "Affine2D(%s)" % repr(self._mtx) - __str__ = __repr__ - - def __cmp__(self, other): - # MGDTODO: We need to decide if we want deferred transforms - # to be equal to this one - if isinstance(other, Affine2D): - if (self.get_matrix() == other.get_matrix()).all(): - return 0 - return -1 - def _do_invalidation(self): result = self._inverted is None self._inverted = None return result + + #@staticmethod + def _concat(a, b): + return N.dot(b, a) + _concat = staticmethod(_concat) - #@staticmethod - def from_values(a, b, c, d, e, f): - return Affine2D(Affine2D.matrix_from_values(a, b, c, d, e, f)) - from_values = staticmethod(from_values) - def to_values(self): mtx = self.get_matrix() return tuple(mtx[:2].swapaxes(0, 1).flatten()) @@ -268,7 +263,7 @@ matrix_from_values = staticmethod(matrix_from_values) def get_matrix(self): - return self._mtx + raise NotImplementedError() def __call__(self, points): """ @@ -278,26 +273,74 @@ points must be a numpy array of shape (N, 2), where N is the number of points. """ - # MGDTODO: This involves a copy. We may need to do something like - # http://neuroimaging.scipy.org/svn/ni/ni/trunk/neuroimaging/core/reference/mapping.py - # to separate the matrix out into the translation and scale components - # and apply each separately (which is still sub-optimal) - - # This is easier for now, however, since we can just keep a - # regular affine matrix around - # MGDTODO: Trap cases where this isn't an array and fix there + # MGDTODO: The major speed trap here is just converting to + # the points to an array in the first place. If we can use + # more arrays upstream, that should help here. mtx = self.get_matrix() points = N.asarray(points, N.float_) - new_points = points.swapaxes(0, 1) - new_points = N.vstack((new_points, N.ones((1, points.shape[0])))) - result = N.dot(mtx, new_points)[:2] - return result.swapaxes(0, 1) + points = points.transpose() + points = N.dot(mtx[0:2, 0:2], points) + points = points + mtx[0:2, 2:] + return points.transpose() + def inverted(self): + if self._inverted is None: + mtx = self.get_matrix() + self._inverted = Affine2D(inv(mtx)) + return self._inverted + + def is_separable(self): + mtx = self.get_matrix() + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + + def is_affine(self): + return True + + +class Affine2D(Affine2DBase): + input_dims = 2 + output_dims = 2 + + def __init__(self, matrix = None): + """ + Initialize an Affine transform from a 3x3 numpy float array. + + a c e + b d f + 0 0 1 + """ + Affine2DBase.__init__(self) + if matrix is None: + matrix = N.identity(3) + else: + assert matrix.shape == (3, 3) + self._mtx = matrix + self._inverted = None + + def __repr__(self): + return "Affine2D(%s)" % repr(self._mtx) + __str__ = __repr__ + + def __cmp__(self, other): + if (isinstance(other, Affine2D) and + (self.get_matrix() == other.get_matrix()).all()): + return 0 + return -1 + + def __copy__(self): + return Affine2D(self._mtx.copy()) + + def __deepcopy__(self, memo): + return Affine2D(self._mtx.copy()) + #@staticmethod - def _concat(a, b): - return N.dot(b, a) - _concat = staticmethod(_concat) + def from_values(a, b, c, d, e, f): + return Affine2D(Affine2D.matrix_from_values(a, b, c, d, e, f)) + from_values = staticmethod(from_values) + def get_matrix(self): + return self._mtx + #@staticmethod def concat(a, b): return Affine2D(Affine2D._concat(a._mtx, b._mtx)) @@ -346,19 +389,18 @@ def is_affine(self): return True -class BlendedAffine2D(Affine2D): +class BlendedAffine2D(Affine2DBase): def __init__(self, x_transform, y_transform): assert x_transform.is_affine() assert y_transform.is_affine() assert x_transform.is_separable() assert y_transform.is_separable() - Transform.__init__(self) + Affine2DBase.__init__(self) self.add_children([x_transform, y_transform]) self._x = x_transform self._y = y_transform self._mtx = None - self._inverted = None def __repr__(self): return "BlendedAffine2D(%s,%s)" % (self._x, self._y) @@ -367,7 +409,7 @@ def _do_invalidation(self): if self._mtx is not None: self._mtx = None - Affine2D._do_invalidation(self) + Affine2DBase._do_invalidation(self) return False return True @@ -376,8 +418,9 @@ x_mtx = self._x.get_matrix() y_mtx = self._y.get_matrix() # This works because we already know the transforms are - # separable - self._mtx = N.vstack([x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0]]) + # separable, though normally one would want to set b and + # c to zero. + self._mtx = N.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) def is_separable(self): return True @@ -397,20 +440,22 @@ self._y = y_transform def __call__(self, points): - # MGDTODO: Implement me - pass + x_points = self._x(points) + y_points = self._y(points) + # This works because we already know the transforms are + # separable + return N.hstack((x_points[:, 0:1], y_points[:, 1:2])) -class CompositeAffine2D(Affine2D): +class CompositeAffine2D(Affine2DBase): def __init__(self, a, b): assert a.is_affine() assert b.is_affine() - Transform.__init__(self) + Affine2DBase.__init__(self) self.add_children([a, b]) self._a = a self._b = b self._mtx = None - self._inverted = None def __repr__(self): return "CompositeAffine2D(%s, %s)" % (self._a, self._b) @@ -418,7 +463,7 @@ def _do_invalidation(self): self._mtx = None - Affine2D._do_invalidation(self) + Affine2DBase._do_invalidation(self) def _make__mtx(self): if self._mtx is None: @@ -433,22 +478,23 @@ class CompositeTransform(Transform): def __init__(self, a, b): assert a.output_dims == b.input_dims - + self.input_dims = a.input_dims + self.output_dims = b.output_dims + Transform.__init__(self) self.add_children([a, b]) self._a = a self._b = b def __call__(self, points): - # MGDTODO: Optimize here by concatenating affines if possible return self._b(self._a(points)) -class BboxTransform(Affine2D): +class BboxTransform(Affine2DBase): def __init__(self, boxin, boxout): assert isinstance(boxin, Bbox) assert isinstance(boxout, Bbox) - Transform.__init__(self) + Affine2DBase.__init__(self) self.add_children([boxin, boxout]) self._boxin = boxin self._boxout = boxout @@ -462,7 +508,7 @@ def _do_invalidation(self): if self._mtx is not None: self._mtx = None - Affine2D._do_invalidation(self) + Affine2DBase._do_invalidation(self) return False return True @@ -481,14 +527,6 @@ self._mtx = affine._mtx - def __call__(self, points): - self._make__mtx() - return Affine2D.__call__(self, points) - - def inverted(self): - self._make__mtx() - return Affine2D.inverted(self) - def is_separable(self): return True @@ -541,6 +579,9 @@ return interval[0] < val and interval[1] > val if __name__ == '__main__': + from random import random + import timeit + bbox = Bbox.from_lbrt(10., 15., 20., 25.) assert bbox.xmin == 10 assert bbox.ymin == 15 @@ -589,7 +630,9 @@ scale = Affine2D().scale(10, 20) assert scale.to_values() == (10, 0, 0, 20, 0, 0) rotation = Affine2D().rotate_deg(30) - print rotation.to_values() == (0.86602540378443871, 0.49999999999999994, -0.49999999999999994, 0.86602540378443871, 0.0, 0.0) + print rotation.to_values() == (0.86602540378443871, 0.49999999999999994, + -0.49999999999999994, 0.86602540378443871, + 0.0, 0.0) points = N.array([[1,2],[3,4],[5,6],[7,8]], N.float_) translated_points = translation(points) @@ -600,11 +643,20 @@ print rotated_points tpoints1 = rotation(translation(scale(points))) - trans_sum = rotation + translation + scale + trans_sum = scale + translation + rotation tpoints2 = trans_sum(points) print tpoints1, tpoints2 print tpoints1 == tpoints2 # Need to do some sort of fuzzy comparison here? # assert (tpoints1 == tpoints2).all() + + # Here are some timing tests + points = [(random(), random()) for i in xrange(10000)] + t = timeit.Timer("trans_sum(points)", "from __main__ import trans_sum, points") + print "Time to transform 10000 x 10 points as tuples:", t.timeit(10) + + points2 = N.asarray(points) + t = timeit.Timer("trans_sum(points2)", "from __main__ import trans_sum, points2") + print "Time to transform 10000 x 10 points as numpy array:", t.timeit(10) __all__ = ['Transform', 'Affine2D'] Modified: branches/transforms/lib/matplotlib/artist.py =================================================================== --- branches/transforms/lib/matplotlib/artist.py 2007-09-12 18:22:24 UTC (rev 3841) +++ branches/transforms/lib/matplotlib/artist.py 2007-09-12 19:47:56 UTC (rev 3842) @@ -337,7 +337,7 @@ def _set_gc_clip(self, gc): 'set the clip properly for the gc' if self.clipbox is not None: - gc.set_clip_rectangle(self.clipbox.get_bounds()) + gc.set_clip_rectangle(self.clipbox.bounds) gc.set_clip_path(self._clippath) def draw(self, renderer, *args, **kwargs): Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-12 18:22:24 UTC (rev 3841) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-12 19:47:56 UTC (rev 3842) @@ -1,5 +1,5 @@ from __future__ import division, generators -import math, sys, warnings +import math, sys, warnings, copy import numpy as npy @@ -483,7 +483,7 @@ """ martist.Artist.__init__(self) self._position = maffine.Bbox.from_lbwh(*rect) - self._originalPosition = self._position.copy() + self._originalPosition = copy.deepcopy(self._position) self.set_axes(self) self.set_aspect('auto') self.set_adjustable('box') @@ -613,7 +613,7 @@ """ martist.Artist.set_figure(self, fig) - l, b, w, h = self._position.get_bounds() + l, b, w, h = self._position.bounds xmin = fig.bbox.xmin xmax = fig.bbox.xmax ymin = fig.bbox.ymin @@ -669,9 +669,9 @@ def get_position(self, original=False): 'Return the axes rectangle left, bottom, width, height' if original: - return self._originalPosition[:] + return self._originalPosition.bounds else: - return self._position[:] + return self._position.bounds # return [val.get() for val in self._position] def set_position(self, pos, which='both'): @@ -694,10 +694,10 @@ # # Change values within self._position--don't replace it. # for num,val in zip(pos, self._position): # val.set(num) - self._position = pos + self._position.bounds = pos.bounds # MGDTODO: side-effects if which in ('both', 'original'): - self._originalPosition = pos + self._originalPosition.bounds = pos.bounds def _set_artist_props(self, a): @@ -1547,7 +1547,9 @@ def get_xscale(self): 'return the xaxis scale string: log or linear' - return self.scaled[self.transData.get_funcx().get_type()] + # MGDTODO + # return self.scaled[self.transData.get_funcx().get_type()] + return 'linear' def set_xscale(self, value, basex = 10, subsx=None): """ @@ -1671,7 +1673,8 @@ def get_yscale(self): 'return the yaxis scale string: log or linear' - return self.scaled[self.transData.get_funcy().get_type()] + # return self.scaled[self.transData.get_funcy().get_type()] + return 'linear' def set_yscale(self, value, basey=10, subsy=None): """ Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-09-12 18:22:24 UTC (rev 3841) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-09-12 19:47:56 UTC (rev 3842) @@ -4,7 +4,7 @@ """ from __future__ import division -import os, sys, warnings +import os, sys, warnings, copy import numpy as npy import matplotlib.numerix.npyma as ma @@ -1070,7 +1070,7 @@ def get_width_height(self): """return the figure width and height in points or pixels (depending on the backend), truncated to integers""" - return int(self.figure.bbox.width()), int(self.figure.bbox.height()) + return int(self.figure.bbox.width), int(self.figure.bbox.height) filetypes = { 'emf': 'Enhanced Metafile', @@ -1544,7 +1544,7 @@ xmin, xmax = a.get_xlim() ymin, ymax = a.get_ylim() lim = xmin, xmax, ymin, ymax - self._xypress.append((x, y, a, i, lim,a.transData.deepcopy())) + self._xypress.append((x, y, a, i, lim, copy.deepcopy(a.transData))) self.canvas.mpl_disconnect(self._idDrag) self._idDrag=self.canvas.mpl_connect('motion_notify_event', self.drag_pan) @@ -1571,7 +1571,7 @@ xmin, xmax = a.get_xlim() ymin, ymax = a.get_ylim() lim = xmin, xmax, ymin, ymax - self._xypress.append(( x, y, a, i, lim, a.transData.deepcopy() )) + self._xypress.append(( x, y, a, i, lim, copy.deepcopy(a.transData) )) self.press(event) @@ -1637,8 +1637,9 @@ #safer to use the recorded button at the press than current button: #multiple button can get pressed during motion... if self._button_pressed==1: - lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) ) - x, y = trans.inverse_xy_tup( (event.x, event.y) ) + inverse = trans.inverted() + lastx, lasty = inverse.transform_point((lastx, lasty)) + x, y = inverse.transform_point( (event.x, event.y) ) if a.get_xscale()=='log': dx=1-lastx/x else: @@ -1664,15 +1665,16 @@ ymax -= dy elif self._button_pressed==3: try: - dx=(lastx-event.x)/float(a.bbox.width()) - dy=(lasty-event.y)/float(a.bbox.height()) + dx=(lastx-event.x)/float(a.bbox.width) + dy=(lasty-event.y)/float(a.bbox.height) dx,dy=format_deltas(event,dx,dy) if a.get_aspect() != 'auto': dx = 0.5*(dx + dy) dy = dx alphax = pow(10.0,dx) alphay = pow(10.0,dy)#use logscaling, avoid singularities and smother scaling... - lastx, lasty = trans.inverse_xy_tup( (lastx, lasty) ) + inverse = trans.inverted() + lastx, lasty = inverse.transform_point( (lastx, lasty) ) if a.get_xscale()=='log': xmin = lastx*(xmin/lastx)**alphax xmax = lastx*(xmax/lastx)**alphax @@ -1710,8 +1712,9 @@ xmin, ymin, xmax, ymax = lim # zoom to rect - lastx, lasty = a.transData.inverse_xy_tup( (lastx, lasty) ) - x, y = a.transData.inverse_xy_tup( (x, y) ) + inverse = a.transData.inverted() + lastx, lasty = inverse.transform_point( (lastx, lasty) ) + x, y = inverse.transform_point( (x, y) ) Xmin,Xmax=a.get_xlim() Ymin,Ymax=a.get_ylim() Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-12 18:22:24 UTC (rev 3841) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-12 19:47:56 UTC (rev 3842) @@ -398,7 +398,7 @@ self.figure.draw(self.renderer) def get_renderer(self): - l,b,w,h = self.figure.bbox.get_bounds() + l,b,w,h = self.figure.bbox.bounds # MGDTODO # key = w, h, self.figure.dpi.get() key = w, h, self.figure.dpi Modified: branches/transforms/lib/matplotlib/backends/backend_tkagg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_tkagg.py 2007-09-12 18:22:24 UTC (rev 3841) +++ branches/transforms/lib/matplotlib/backends/backend_tkagg.py 2007-09-12 19:47:56 UTC (rev 3842) @@ -147,7 +147,7 @@ def __init__(self, figure, master=None, resize_callback=None): FigureCanvasAgg.__init__(self, figure) self._idle = True - t1,t2,w,h = self.figure.bbox.get_bounds() + t1,t2,w,h = self.figure.bbox.bounds w, h = int(w), int(h) self._tkcanvas = Tk.Canvas( master=master, width=w, height=h, borderwidth=4) @@ -288,7 +288,7 @@ self.window.wm_title("Figure %d" % num) self.canvas = canvas self._num = num - t1,t2,w,h = canvas.figure.bbox.get_bounds() + t1,t2,w,h = canvas.figure.bbox.bounds w, h = int(w), int(h) self.window.minsize(int(w*3/4),int(h*3/4)) if matplotlib.rcParams['toolbar']=='classic': @@ -436,7 +436,7 @@ self.canvas = canvas self.window = window - xmin, xmax = canvas.figure.bbox.intervalx().get_bounds() + xmin, xmax = canvas.figure.bbox.intervalx height, width = 50, xmax-xmin Tk.Frame.__init__(self, master=self.window, width=width, height=height, @@ -582,7 +582,7 @@ self.message.set(s) def draw_rubberband(self, event, x0, y0, x1, y1): - height = self.canvas.figure.bbox.height() + height = self.canvas.figure.bbox.height y0 = height-y0 y1 = height-y1 try: self.lastrect This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-13 12:44:23
|
Revision: 3846 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3846&view=rev Author: mdboom Date: 2007-09-13 05:44:16 -0700 (Thu, 13 Sep 2007) Log Message: ----------- Minor changes -- committing so I can merge again. Modified Paths: -------------- branches/transforms/lib/matplotlib/affine.py branches/transforms/lib/matplotlib/patches.py branches/transforms/lib/matplotlib/text.py Modified: branches/transforms/lib/matplotlib/affine.py =================================================================== --- branches/transforms/lib/matplotlib/affine.py 2007-09-13 06:29:14 UTC (rev 3845) +++ branches/transforms/lib/matplotlib/affine.py 2007-09-13 12:44:16 UTC (rev 3846) @@ -4,7 +4,7 @@ 2007 Michael Droettboom """ -import numpy as N +import numpy as npy from numpy.linalg import inv from sets import Set @@ -37,7 +37,7 @@ class Bbox(TransformNode): def __init__(self, points): TransformNode.__init__(self) - self._points = N.asarray(points, N.float_) + self._points = npy.asarray(points, npy.float_) self.track = False #@staticmethod @@ -52,7 +52,7 @@ #@staticmethod def from_lbrt(*args): - points = N.array(args, dtype=N.float_).reshape(2, 2) + points = npy.array(args, dtype=npy.float_).reshape(2, 2) return Bbox(points) from_lbrt = staticmethod(from_lbrt) @@ -72,12 +72,15 @@ return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ + def __array__(self): + return self._points + # JDH: the update method will update the box limits from the # existing limits and the new data; it appears here you are just # using the new data. We use an "ignore" flag to specify whether # you want to include the existing data or not in the update def update_from_data(self, x, y, ignore=True): - self._points = N.array([[x.min(), y.min()], [x.max(), y.max()]], N.float_) + self._points = npy.array([[x.min(), y.min()], [x.max(), y.max()]], npy.float_) self.invalidate() # MGDTODO: Probably a more efficient ways to do this... @@ -150,7 +153,7 @@ self.xmax - self.xmin, self.ymax - self.ymin) def _set_bounds(self, bounds): l,b,w,h = bounds - self._points = N.array([[l, b], [l+w, b+h]], N.float_) + self._points = npy.array([[l, b], [l+w, b+h]], npy.float_) self.invalidate() bounds = property(_get_bounds, _set_bounds) @@ -165,7 +168,7 @@ height = self.height deltaw = (sw * width - width) / 2.0 deltah = (sh * height - height) / 2.0 - a = N.array([[-deltaw, -deltah], [deltaw, deltah]]) + a = npy.array([[-deltaw, -deltah], [deltaw, deltah]]) return Bbox(self._points + a) def contains(self, x, y): @@ -215,7 +218,7 @@ raise TypeError("Can not add Transform to object of type '%s'" % type(other)) def transform_point(self, point): - return self.__call__([point])[0] + return self.__call__(npy.asarray([point]))[0] def has_inverse(self): raise NotImplementedError() @@ -229,8 +232,6 @@ def is_affine(self): return False -# MGDTODO: Separate out Affine2DBase / Affine2DConcrete so BlendedAffine and CompositeAffine don't have translate/scale/rotate members - class Affine2DBase(Transform): input_dims = 2 output_dims = 2 @@ -246,7 +247,7 @@ #@staticmethod def _concat(a, b): - return N.dot(b, a) + return npy.dot(b, a) _concat = staticmethod(_concat) def to_values(self): @@ -255,7 +256,7 @@ #@staticmethod def matrix_from_values(a, b, c, d, e, f): - affine = N.zeros((3,3), N.float_) + affine = npy.zeros((3,3), npy.float_) affine[0,] = a, c, e affine[1,] = b, d, f affine[2,2] = 1 @@ -267,7 +268,7 @@ def __call__(self, points): """ - Applies the transformation to a set of 2D points and + Applies the transformation to an array of 2D points and returns the result. points must be a numpy array of shape (N, 2), where N is the @@ -277,9 +278,9 @@ # the points to an array in the first place. If we can use # more arrays upstream, that should help here. mtx = self.get_matrix() - points = N.asarray(points, N.float_) + points = npy.asarray(points, npy.float_) points = points.transpose() - points = N.dot(mtx[0:2, 0:2], points) + points = npy.dot(mtx[0:2, 0:2], points) points = points + mtx[0:2, 2:] return points.transpose() @@ -311,7 +312,7 @@ """ Affine2DBase.__init__(self) if matrix is None: - matrix = N.identity(3) + matrix = npy.identity(3) else: assert matrix.shape == (3, 3) self._mtx = matrix @@ -348,19 +349,19 @@ #@staticmethod def identity(): - return Affine2D(N.identity(3)) + return Affine2D(npy.identity(3)) identity = staticmethod(identity) def rotate(self, theta): - a = N.cos(theta) - b = N.sin(theta) + a = npy.cos(theta) + b = npy.sin(theta) rotate_mtx = self.matrix_from_values(a, b, -b, a, 0, 0) self._mtx = self._concat(self._mtx, rotate_mtx) self.invalidate() return self def rotate_deg(self, degrees): - return self.rotate(degrees*N.pi/180.) + return self.rotate(degrees*npy.pi/180.) def translate(self, tx, ty): translate_mtx = self.matrix_from_values(1., 0., 0., 1., tx, ty) @@ -420,7 +421,7 @@ # This works because we already know the transforms are # separable, though normally one would want to set b and # c to zero. - self._mtx = N.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) + self._mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) def is_separable(self): return True @@ -444,7 +445,7 @@ y_points = self._y(points) # This works because we already know the transforms are # separable - return N.hstack((x_points[:, 0:1], y_points[:, 1:2])) + return npy.hstack((x_points[:, 0:1], y_points[:, 1:2])) class CompositeAffine2D(Affine2DBase): def __init__(self, a, b): @@ -579,6 +580,7 @@ return interval[0] < val and interval[1] > val if __name__ == '__main__': + import copy from random import random import timeit @@ -588,36 +590,38 @@ assert bbox.xmax == 20 assert bbox.ymax == 25 - assert N.all(bbox.min == [10, 15]) - assert N.all(bbox.max == [20, 25]) - assert N.all(bbox.intervalx == (10, 20)) - assert N.all(bbox.intervaly == (15, 25)) + assert npy.all(bbox.min == [10, 15]) + assert npy.all(bbox.max == [20, 25]) + assert npy.all(bbox.intervalx == (10, 20)) + assert npy.all(bbox.intervaly == (15, 25)) assert bbox.width == 10 assert bbox.height == 10 - assert bbox.get_bounds() == (10, 15, 10, 10) + assert bbox.bounds == (10, 15, 10, 10) + print npy.asarray(bbox) + bbox.intervalx = (11, 21) bbox.intervaly = (16, 26) - assert bbox.get_bounds() == (11, 16, 10, 10) + assert bbox.bounds == (11, 16, 10, 10) bbox.xmin = 12 bbox.ymin = 17 bbox.xmax = 22 bbox.ymax = 27 - assert bbox.get_bounds() == (12, 17, 10, 10) + assert bbox.bounds == (12, 17, 10, 10) bbox = Bbox.from_lbwh(10, 11, 12, 13) - assert bbox.get_bounds() == (10, 11, 12, 13) + assert bbox.bounds == (10, 11, 12, 13) - bbox_copy = bbox.copy() + bbox_copy = copy.copy(bbox) assert bbox == bbox_copy bbox_copy.max = (14, 15) - assert bbox.get_bounds() == (10, 11, 12, 13) - assert bbox_copy.get_bounds() == (10, 11, 4, 4) + assert bbox.bounds == (10, 11, 12, 13) + assert bbox_copy.bounds == (10, 11, 4, 4) bbox1 = Bbox([[10., 15.], [20., 25.]]) bbox2 = Bbox([[30., 35.], [40., 45.]]) @@ -634,7 +638,7 @@ -0.49999999999999994, 0.86602540378443871, 0.0, 0.0) - points = N.array([[1,2],[3,4],[5,6],[7,8]], N.float_) + points = npy.array([[1,2],[3,4],[5,6],[7,8]], npy.float_) translated_points = translation(points) assert (translated_points == [[11., 22.], [13., 24.], [15., 26.], [17., 28.]]).all() scaled_points = scale(points) @@ -655,7 +659,7 @@ t = timeit.Timer("trans_sum(points)", "from __main__ import trans_sum, points") print "Time to transform 10000 x 10 points as tuples:", t.timeit(10) - points2 = N.asarray(points) + points2 = npy.asarray(points) t = timeit.Timer("trans_sum(points2)", "from __main__ import trans_sum, points2") print "Time to transform 10000 x 10 points as numpy array:", t.timeit(10) Modified: branches/transforms/lib/matplotlib/patches.py =================================================================== --- branches/transforms/lib/matplotlib/patches.py 2007-09-13 06:29:14 UTC (rev 3845) +++ branches/transforms/lib/matplotlib/patches.py 2007-09-13 12:44:16 UTC (rev 3846) @@ -356,9 +356,9 @@ left, right = self.convert_xunits((x, x + self.width)) bottom, top = self.convert_yunits((y, y + self.height)) - return ( (left, bottom), (left, top), - (right, top), (right, bottom), - ) + return npy.array([[left, bottom], [left, top], + [right, top], [right, bottom]], + npy.float_) def get_x(self): "Return the left coord of the rectangle" Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-09-13 06:29:14 UTC (rev 3845) +++ branches/transforms/lib/matplotlib/text.py 2007-09-13 12:44:16 UTC (rev 3846) @@ -149,7 +149,7 @@ def _get_xy_display(self): 'get the (possibly unit converted) transformed x,y in display coords' x, y = self.get_position() - return self.get_transform()([[x,y]])[0] + return self.get_transform().transform_point((x,y)) def _get_multialignment(self): if self._multialignment is not None: return self._multialignment This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-13 18:00:12
|
Revision: 3848 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3848&view=rev Author: mdboom Date: 2007-09-13 11:00:10 -0700 (Thu, 13 Sep 2007) Log Message: ----------- New milestone -- resizing figure window works. shared_axis_demo.py works. (Uses callbacks to track changes between axes's). Modified Paths: -------------- branches/transforms/lib/matplotlib/affine.py branches/transforms/lib/matplotlib/artist.py branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/figure.py branches/transforms/lib/matplotlib/pyplot.py branches/transforms/lib/matplotlib/text.py Modified: branches/transforms/lib/matplotlib/affine.py =================================================================== --- branches/transforms/lib/matplotlib/affine.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/affine.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -20,284 +20,461 @@ class TransformNode(object): def __init__(self): - self._parents = Set() - + self._parents = Set() + def invalidate(self): - if not self._do_invalidation(): - for parent in self._parents: - parent.invalidate() + if not self._do_invalidation(): + for parent in self._parents: + parent.invalidate() def _do_invalidation(self): - return False - - def add_children(self, children): - for child in children: - child._parents.add(self) + return False -class Bbox(TransformNode): + def set_children(self, children): + for child in children: + getattr(self, child)._parents.add(self) + self._children = children + +# def replace_child(self, index, child): +# children = self._children +# getattr(self, children[index])._parents.remove(self) +# setattr(self, children[index], child) +# # We have to reset children in case two or more +# # of the children are the same +# for child in children: +# getattr(self, child)._parents.add(self) +# self.invalidate() + +class BboxBase(TransformNode): + ''' + This is the read-only part of a bounding-box + ''' + + def __init__(self): + TransformNode.__init__(self) + + def __array__(self): + return self.get_points() + + # MGDTODO: Probably a more efficient ways to do this... + def _get_xmin(self): + return self.get_points()[0, 0] + xmin = property(_get_xmin) + + def _get_ymin(self): + return self.get_points()[0, 1] + ymin = property(_get_ymin) + + def _get_xmax(self): + return self.get_points()[1, 0] + xmax = property(_get_xmax) + + def _get_ymax(self): + return self.get_points()[1, 1] + ymax = property(_get_ymax) + + def _get_min(self): + return self.get_points()[0] + min = property(_get_min) + + def _get_max(self): + return self.get_points()[1] + max = property(_get_max) + + def _get_intervalx(self): + return self.get_points()[:, 0] + intervalx = property(_get_intervalx) + + def _get_intervaly(self): + return self.get_points()[:, 1] + intervaly = property(_get_intervaly) + + def _get_width(self): + return self.xmax - self.xmin + width = property(_get_width) + + def _get_height(self): + return self.ymax - self.ymin + height = property(_get_height) + + def _get_bounds(self): + return (self.xmin, self.ymin, + self.xmax - self.xmin, self.ymax - self.ymin) + bounds = property(_get_bounds) + + def get_points(self): + return NotImplementedError() + + # MGDTODO: Optimize + def containsx(self, x): + return x >= self.xmin and x <= self.xmax + + def containsy(self, y): + return y >= self.ymin and y <= self.ymax + + def contains(self, x, y): + return self.containsx(x) and self.containsy(y) + + def overlapsx(self, other): + return self.containsx(other.xmin) \ + or self.containsx(other.xmax) + + def overlapsy(self, other): + return self.containsy(other.ymin) \ + or self.containsx(other.ymax) + + def overlaps(self, other): + return self.overlapsx(other) \ + and self.overlapsy(other) + + def fully_containsx(self, x): + return x > self.xmin and x < self.xmax + + def fully_containsy(self, y): + return y > self.ymin and y < self.ymax + + def fully_contains(self, x, y): + return self.fully_containsx(x) \ + and self.fully_containsy(y) + + def fully_overlapsx(self, other): + return self.fully_containsx(other.xmin) \ + or self.fully_containsx(other.xmax) + + def fully_overlapsy(self, other): + return self.fully_containsy(other.ymin) \ + or self.fully_containsx(other.ymax) + + def fully_overlaps(self, other): + return self.fully_overlapsx(other) and \ + self.fully_overlapsy(other) + + +class Bbox(BboxBase): def __init__(self, points): - TransformNode.__init__(self) - self._points = npy.asarray(points, npy.float_) - self.track = False + BboxBase.__init__(self) + self._points = npy.asarray(points, npy.float_) #@staticmethod def unit(): - return Bbox.from_lbrt(0., 0., 1., 1.) + return Bbox.from_lbrt(0., 0., 1., 1.) unit = staticmethod(unit) #@staticmethod def from_lbwh(left, bottom, width, height): - return Bbox.from_lbrt(left, bottom, left + width, bottom + height) + return Bbox.from_lbrt(left, bottom, left + width, bottom + height) from_lbwh = staticmethod(from_lbwh) #@staticmethod def from_lbrt(*args): - points = npy.array(args, dtype=npy.float_).reshape(2, 2) - return Bbox(points) + points = npy.array(args, dtype=npy.float_).reshape(2, 2) + return Bbox(points) from_lbrt = staticmethod(from_lbrt) def __copy__(self): - return Bbox(self._points.copy()) + return Bbox(self._points.copy()) def __deepcopy__(self, memo): - return Bbox(self._points.copy()) + return Bbox(self._points.copy()) def __cmp__(self, other): - # MGDTODO: Totally suboptimal - if isinstance(other, Bbox) and (self._points == other._points).all(): - return 0 - return -1 + # MGDTODO: Totally suboptimal + if isinstance(other, Bbox) and (self._points == other._points).all(): + return 0 + return -1 def __repr__(self): - return 'Bbox(%s)' % repr(self._points) + return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ - def __array__(self): - return self._points - # JDH: the update method will update the box limits from the # existing limits and the new data; it appears here you are just # using the new data. We use an "ignore" flag to specify whether # you want to include the existing data or not in the update def update_from_data(self, x, y, ignore=True): - self._points = npy.array([[x.min(), y.min()], [x.max(), y.max()]], npy.float_) - self.invalidate() + self._points = npy.array( + [[x.min(), y.min()], [x.max(), y.max()]], + npy.float_) + self.invalidate() # MGDTODO: Probably a more efficient ways to do this... - def _get_xmin(self): - return self._points[0, 0] def _set_xmin(self, val): - self._points[0, 0] = val - self.invalidate() - xmin = property(_get_xmin, _set_xmin) + self._points[0, 0] = val + self.invalidate() + xmin = property(BboxBase._get_xmin, _set_xmin) - def _get_ymin(self): - return self._points[0, 1] def _set_ymin(self, val): - self._points[0, 1] = val - self.invalidate() - ymin = property(_get_ymin, _set_ymin) + self._points[0, 1] = val + self.invalidate() + ymin = property(BboxBase._get_ymin, _set_ymin) - def _get_xmax(self): - return self._points[1, 0] def _set_xmax(self, val): - self._points[1, 0] = val - self.invalidate() - xmax = property(_get_xmax, _set_xmax) + self._points[1, 0] = val + self.invalidate() + xmax = property(BboxBase._get_xmax, _set_xmax) - def _get_ymax(self): - return self._points[1, 1] def _set_ymax(self, val): - self._points[1, 1] = val - self.invalidate() - ymax = property(_get_ymax, _set_ymax) + self._points[1, 1] = val + self.invalidate() + ymax = property(BboxBase._get_ymax, _set_ymax) - def _get_min(self): - return self._points[0] def _set_min(self, val): - self._points[0] = val - self.invalidate() - min = property(_get_min, _set_min) + self._points[0] = val + self.invalidate() + min = property(BboxBase._get_min, _set_min) - def _get_max(self): - return self._points[1] def _set_max(self, val): - self._points[1] = val - self.invalidate() - max = property(_get_max, _set_max) + self._points[1] = val + self.invalidate() + max = property(BboxBase._get_max, _set_max) - def _get_intervalx(self): - return self._points[:,0] def _set_intervalx(self, interval): - self._points[:,0] = interval - self.invalidate() - intervalx = property(_get_intervalx, _set_intervalx) + self._points[:, 0] = interval + self.invalidate() + intervalx = property(BboxBase._get_intervalx, _set_intervalx) - def _get_intervaly(self): - return self._points[:,1] def _set_intervaly(self, interval): - self._points[:,1] = interval - self.invalidate() - intervaly = property(_get_intervaly, _set_intervaly) + self._points[:, 1] = interval + self.invalidate() + intervaly = property(BboxBase._get_intervaly, _set_intervaly) - def _get_width(self): - return self.xmax - self.xmin - width = property(_get_width) + def _set_bounds(self, bounds): + l,b,w,h = bounds + self._points = npy.array([[l, b], [l+w, b+h]], npy.float_) + self.invalidate() + bounds = property(BboxBase._get_bounds, _set_bounds) - def _get_height(self): - return self.ymax - self.ymin - height = property(_get_height) + def get_points(self): + return self._points - def _get_bounds(self): - return (self.xmin, self.ymin, - self.xmax - self.xmin, self.ymax - self.ymin) - def _set_bounds(self, bounds): - l,b,w,h = bounds - self._points = npy.array([[l, b], [l+w, b+h]], npy.float_) - self.invalidate() - bounds = property(_get_bounds, _set_bounds) - + def set_points(self, points): + self._points = points + self.invalidate() + + def set(self, other): + self._points = other.get_points() + self.invalidate() + def transformed(self, transform): - return Bbox(transform(self._points)) + return Bbox(transform(self._points)) def inverse_transformed(self, transform): - return Bbox(transform.inverted()(self._points)) + return Bbox(transform.inverted()(self._points)) def expanded(self, sw, sh): - width = self.width - height = self.height - deltaw = (sw * width - width) / 2.0 - deltah = (sh * height - height) / 2.0 - a = npy.array([[-deltaw, -deltah], [deltaw, deltah]]) - return Bbox(self._points + a) + width = self.width + height = self.height + deltaw = (sw * width - width) / 2.0 + deltah = (sh * height - height) / 2.0 + a = npy.array([[-deltaw, -deltah], [deltaw, deltah]]) + return Bbox(self._points + a) - def contains(self, x, y): - return (x >= self.xmin and x <= self.xmax and - y >= self.ymin and y <= self.ymax) - #@staticmethod def union(bboxes): - """ - Return the Bbox that bounds all bboxes - """ - assert(len(bboxes)) + """ + Return the Bbox that bounds all bboxes + """ + assert(len(bboxes)) - if len(bboxes) == 1: - return bboxes[0] + if len(bboxes) == 1: + return bboxes[0] - bbox = bboxes[0] - xmin = bbox.xmin - ymin = bbox.ymin - xmax = bbox.xmax - ymax = bbox.ymax + bbox = bboxes[0] + xmin = bbox.xmin + ymin = bbox.ymin + xmax = bbox.xmax + ymax = bbox.ymax - for bbox in bboxes[1:]: - xmin = min(xmin, bbox.xmin) - ymin = min(ymin, bbox.ymin) - xmax = max(xmax, bbox.xmax) - ymax = max(ymax, bbox.ymax) + for bbox in bboxes[1:]: + xmin = min(xmin, bbox.xmin) + ymin = min(ymin, bbox.ymin) + xmax = max(xmax, bbox.xmax) + ymax = max(ymax, bbox.ymax) - return Bbox.from_lbrt(xmin, ymin, xmax, ymax) + return Bbox.from_lbrt(xmin, ymin, xmax, ymax) union = staticmethod(union) + +class TransformedBbox(BboxBase): + def __init__(self, bbox, transform): + assert isinstance(bbox, Bbox) + assert isinstance(transform, Transform) + + BboxBase.__init__(self) + self.bbox = bbox + self.transform = transform + self.set_children(['bbox', 'transform']) + self._points = None + + def __repr__(self): + return "TransformedBbox(%s, %s)" % (self.bbox, self.transform) + __str__ = __repr__ + def _do_invalidation(self): + self._points = None + + def get_points(self): + if self._points is None: + self._points = self.transform(self.bbox.get_points()) + return self._points + +# MGDTODO: This code probably works, but I don't think it's a good idea +# (from a code clarity perspective) +# class BlendedBbox(BboxBase): +# def __init__(self, bbox_x, bbox_y): +# assert isinstance(bbox_x, BboxBase) +# assert isinstance(bbox_y, BboxBase) + +# BboxBase.__init__(self) +# self._x = bbox_x +# self._y = bbox_y +# self.set_children(['_x', '_y']) +# self._points = None + +# def __repr__(self): +# return "TransformedBbox(%s, %s)" % (self.bbox, self.transform) +# __str__ = __repr__ + +# def _do_invalidation(self): +# self._points = None + +# def get_points(self): +# if self._points is None: +# # MGDTODO: Optimize +# if self._x == self._y: +# self._points = self._x.get_points() +# else: +# x_points = self._x.get_points() +# y_points = self._y.get_points() +# self._points = npy.array( +# [[x_points[0,0], y_points[0,1]], +# [x_points[1,0], y_points[1,1]]], +# npy.float_) +# return self._points + +# def _set_intervalx(self, pair): +# # MGDTODO: Optimize +# bbox = Bbox([[pair[0], 0.0], [pair[1], 0.0]]) +# self.replace_child(0, bbox) +# intervalx = property(BboxBase._get_intervalx, _set_intervalx) + +# def _set_intervaly(self, pair): +# # MGDTODO: Optimize +# bbox = Bbox([[0.0, pair[0]], [0.0, pair[1]]]) +# self.replace_child(1, bbox) +# intervaly = property(BboxBase._get_intervaly, _set_intervaly) + class Transform(TransformNode): def __init__(self): - TransformNode.__init__(self) + TransformNode.__init__(self) def __call__(self, points): - raise NotImplementedError() + raise NotImplementedError() def __add__(self, other): - if isinstance(other, Transform): - return composite_transform_factory(self, other) - raise TypeError("Can not add Transform to object of type '%s'" % type(other)) + if isinstance(other, Transform): + return composite_transform_factory(self, other) + raise TypeError( + "Can not add Transform to object of type '%s'" % type(other)) def __radd__(self, other): - if isinstance(other, Transform): - return composite_transform_factory(other, self) - raise TypeError("Can not add Transform to object of type '%s'" % type(other)) + if isinstance(other, Transform): + return composite_transform_factory(other, self) + raise TypeError( + "Can not add Transform to object of type '%s'" % type(other)) def transform_point(self, point): - return self.__call__(npy.asarray([point]))[0] + return self.__call__(npy.asarray([point]))[0] def has_inverse(self): - raise NotImplementedError() + raise NotImplementedError() def inverted(self): - raise NotImplementedError() + raise NotImplementedError() def is_separable(self): - return False + return False def is_affine(self): - return False + return False class Affine2DBase(Transform): input_dims = 2 output_dims = 2 def __init__(self): - Transform.__init__(self) - self._inverted = None + Transform.__init__(self) + self._inverted = None def _do_invalidation(self): - result = self._inverted is None - self._inverted = None - return result + result = self._inverted is None + self._inverted = None + return result #@staticmethod def _concat(a, b): return npy.dot(b, a) _concat = staticmethod(_concat) + + #@staticmethod + def concat(a, b): + return Affine2D(Affine2D._concat(a.get_matrix(), b.get_matrix())) + concat = staticmethod(concat) def to_values(self): - mtx = self.get_matrix() - return tuple(mtx[:2].swapaxes(0, 1).flatten()) + mtx = self.get_matrix() + return tuple(mtx[:2].swapaxes(0, 1).flatten()) #@staticmethod def matrix_from_values(a, b, c, d, e, f): - affine = npy.zeros((3,3), npy.float_) - affine[0,] = a, c, e - affine[1,] = b, d, f - affine[2,2] = 1 - return affine + affine = npy.zeros((3, 3), npy.float_) + affine[0, ] = a, c, e + affine[1, ] = b, d, f + affine[2, 2] = 1 + return affine matrix_from_values = staticmethod(matrix_from_values) def get_matrix(self): - raise NotImplementedError() + raise NotImplementedError() def __call__(self, points): """ Applies the transformation to an array of 2D points and - returns the result. + returns the result. - points must be a numpy array of shape (N, 2), where N is the - number of points. - """ - # MGDTODO: The major speed trap here is just converting to - # the points to an array in the first place. If we can use - # more arrays upstream, that should help here. - mtx = self.get_matrix() - points = npy.asarray(points, npy.float_) - points = points.transpose() - points = npy.dot(mtx[0:2, 0:2], points) - points = points + mtx[0:2, 2:] - return points.transpose() + points must be a numpy array of shape (N, 2), where N is the + number of points. + """ + # MGDTODO: The major speed trap here is just converting to + # the points to an array in the first place. If we can use + # more arrays upstream, that should help here. + if not isinstance(points, npy.ndarray): + import traceback + print '-' * 60 + print 'A non-numpy array was passed in for transformation. Please ' + print 'correct this.' + print "".join(traceback.format_stack()) + print points + mtx = self.get_matrix() + points = npy.asarray(points, npy.float_) + points = points.transpose() + points = npy.dot(mtx[0:2, 0:2], points) + points = points + mtx[0:2, 2:] + return points.transpose() def inverted(self): - if self._inverted is None: - mtx = self.get_matrix() - self._inverted = Affine2D(inv(mtx)) - return self._inverted + if self._inverted is None: + mtx = self.get_matrix() + self._inverted = Affine2D(inv(mtx)) + return self._inverted def is_separable(self): - mtx = self.get_matrix() - return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + mtx = self.get_matrix() + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 def is_affine(self): - return True + return True - + class Affine2D(Affine2DBase): input_dims = 2 output_dims = 2 @@ -310,29 +487,29 @@ b d f 0 0 1 """ - Affine2DBase.__init__(self) - if matrix is None: - matrix = npy.identity(3) - else: - assert matrix.shape == (3, 3) - self._mtx = matrix - self._inverted = None + Affine2DBase.__init__(self) + if matrix is None: + matrix = npy.identity(3) + else: + assert matrix.shape == (3, 3) + self._mtx = matrix + self._inverted = None def __repr__(self): - return "Affine2D(%s)" % repr(self._mtx) + return "Affine2D(%s)" % repr(self._mtx) __str__ = __repr__ def __cmp__(self, other): - if (isinstance(other, Affine2D) and - (self.get_matrix() == other.get_matrix()).all()): - return 0 - return -1 + if (isinstance(other, Affine2D) and + (self.get_matrix() == other.get_matrix()).all()): + return 0 + return -1 def __copy__(self): - return Affine2D(self._mtx.copy()) + return Affine2D(self._mtx.copy()) def __deepcopy__(self, memo): - return Affine2D(self._mtx.copy()) + return Affine2D(self._mtx.copy()) #@staticmethod def from_values(a, b, c, d, e, f): @@ -340,209 +517,224 @@ from_values = staticmethod(from_values) def get_matrix(self): - return self._mtx + return self._mtx + + def set_matrix(self, mtx): + self._mtx = mtx + self.invalidate() + + def set(self, other): + self._mtx = other.get_matrix() + self.invalidate() #@staticmethod - def concat(a, b): - return Affine2D(Affine2D._concat(a._mtx, b._mtx)) - concat = staticmethod(concat) - - #@staticmethod def identity(): return Affine2D(npy.identity(3)) identity = staticmethod(identity) + def clear(self): + self._mtx = npy.identity(3) + self.invalidate() + return self + def rotate(self, theta): a = npy.cos(theta) b = npy.sin(theta) rotate_mtx = self.matrix_from_values(a, b, -b, a, 0, 0) self._mtx = self._concat(self._mtx, rotate_mtx) - self.invalidate() - return self + self.invalidate() + return self def rotate_deg(self, degrees): return self.rotate(degrees*npy.pi/180.) + def rotate_around(self, x, y, theta): + return self.translate(-x, -y).rotate(theta).translate(x, y) + + def rotate_deg_around(self, x, y, degrees): + return self.translate(-x, -y).rotate_deg(degrees).translate(x, y) + def translate(self, tx, ty): translate_mtx = self.matrix_from_values(1., 0., 0., 1., tx, ty) self._mtx = self._concat(self._mtx, translate_mtx) - self.invalidate() - return self + self.invalidate() + return self def scale(self, sx, sy=None): - if sy is None: - sy = sx - scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.) + if sy is None: + sy = sx + scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.) self._mtx = self._concat(self._mtx, scale_mtx) - self.invalidate() - return self + self.invalidate() + return self def inverted(self): - if self._inverted is None: - mtx = self.get_matrix() - self._inverted = Affine2D(inv(mtx)) - return self._inverted + if self._inverted is None: + mtx = self.get_matrix() + self._inverted = Affine2D(inv(mtx)) + return self._inverted def is_separable(self): - mtx = self.get_matrix() - return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + mtx = self.get_matrix() + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 def is_affine(self): - return True + return True -class BlendedAffine2D(Affine2DBase): +class BlendedTransform(Transform): def __init__(self, x_transform, y_transform): - assert x_transform.is_affine() - assert y_transform.is_affine() - assert x_transform.is_separable() - assert y_transform.is_separable() + assert x_transform.is_separable() + assert y_transform.is_separable() - Affine2DBase.__init__(self) - self.add_children([x_transform, y_transform]) - self._x = x_transform - self._y = y_transform - self._mtx = None + Transform.__init__(self) + self._x = x_transform + self._y = y_transform + self.set_children(['_x', '_y']) + def __call__(self, points): + if self._x == self._y: + return self._x(points) + + x_points = self._x(points) + y_points = self._y(points) + # This works because we already know the transforms are + # separable + return npy.hstack((x_points[:, 0:1], y_points[:, 1:2])) + +# def set_x_transform(self, x_transform): +# self.replace_child(0, x_transform) + +# def set_y_transform(self, y_transform): +# self.replace_child(1, y_transform) + +class BlendedAffine2D(Affine2DBase, BlendedTransform): + def __init__(self, x_transform, y_transform): + assert x_transform.is_affine() + assert y_transform.is_affine() + assert x_transform.is_separable() + assert y_transform.is_separable() + BlendedTransform.__init__(self, x_transform, y_transform) + + Affine2DBase.__init__(self) + self._mtx = None + def __repr__(self): - return "BlendedAffine2D(%s,%s)" % (self._x, self._y) + return "BlendedAffine2D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - + def _do_invalidation(self): - if self._mtx is not None: - self._mtx = None - Affine2DBase._do_invalidation(self) - return False - return True + if self._mtx is not None: + self._mtx = None + Affine2DBase._do_invalidation(self) + return False + return True - def _make__mtx(self): - if self._mtx is None: - x_mtx = self._x.get_matrix() - y_mtx = self._y.get_matrix() - # This works because we already know the transforms are - # separable, though normally one would want to set b and - # c to zero. - self._mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) - def is_separable(self): - return True + return True def get_matrix(self): - self._make__mtx() - return self._mtx + if self._mtx is None: + if self._x == self._y: + self._mtx = self._x.get_matrix() + else: + x_mtx = self._x.get_matrix() + y_mtx = self._y.get_matrix() + # This works because we already know the transforms are + # separable, though normally one would want to set b and + # c to zero. + self._mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) + return self._mtx + +class CompositeTransform(Transform): + def __init__(self, a, b): + assert a.output_dims == b.input_dims + self.input_dims = a.input_dims + self.output_dims = b.output_dims + + Transform.__init__(self) + self._a = a + self._b = b + self.set_children(['_a', '_b']) + + def __call__(self, points): + return self._b(self._a(points)) -class BlendedTransform(Transform): - def __init__(self, x_transform, y_transform): - assert x_transform.is_separable() - assert y_transform.is_separable() - - Transform.__init__(self) - self.add_children([x_transform, y_transform]) - self._x = x_transform - self._y = y_transform - - def __call__(self, points): - x_points = self._x(points) - y_points = self._y(points) - # This works because we already know the transforms are - # separable - return npy.hstack((x_points[:, 0:1], y_points[:, 1:2])) - class CompositeAffine2D(Affine2DBase): def __init__(self, a, b): - assert a.is_affine() - assert b.is_affine() + assert a.is_affine() + assert b.is_affine() - Affine2DBase.__init__(self) - self.add_children([a, b]) - self._a = a - self._b = b - self._mtx = None + Affine2DBase.__init__(self) + self._a = a + self._b = b + self.set_children(['_a', '_b']) + self._mtx = None def __repr__(self): - return "CompositeAffine2D(%s, %s)" % (self._a, self._b) + return "CompositeAffine2D(%s, %s)" % (self._a, self._b) __str__ = __repr__ def _do_invalidation(self): - self._mtx = None - Affine2DBase._do_invalidation(self) + self._mtx = None + Affine2DBase._do_invalidation(self) - def _make__mtx(self): - if self._mtx is None: - self._mtx = self._concat( - self._a.get_matrix(), - self._b.get_matrix()) - def get_matrix(self): - self._make__mtx() - return self._mtx - -class CompositeTransform(Transform): - def __init__(self, a, b): - assert a.output_dims == b.input_dims - self.input_dims = a.input_dims - self.output_dims = b.output_dims - - Transform.__init__(self) - self.add_children([a, b]) - self._a = a - self._b = b + if self._mtx is None: + self._mtx = self._concat( + self._a.get_matrix(), + self._b.get_matrix()) + return self._mtx - def __call__(self, points): - return self._b(self._a(points)) - class BboxTransform(Affine2DBase): def __init__(self, boxin, boxout): - assert isinstance(boxin, Bbox) - assert isinstance(boxout, Bbox) + assert isinstance(boxin, BboxBase) + assert isinstance(boxout, BboxBase) - Affine2DBase.__init__(self) - self.add_children([boxin, boxout]) - self._boxin = boxin - self._boxout = boxout - self._mtx = None - self._inverted = None + Affine2DBase.__init__(self) + self._boxin = boxin + self._boxout = boxout + self.set_children(['_boxin', '_boxout']) + self._mtx = None + self._inverted = None def __repr__(self): - return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) + return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) __str__ = __repr__ - + def _do_invalidation(self): - if self._mtx is not None: - self._mtx = None - Affine2DBase._do_invalidation(self) - return False - return True + if self._mtx is not None: + self._mtx = None + Affine2DBase._do_invalidation(self) + return False + return True - def _make__mtx(self): - if self._mtx is None: - boxin = self._boxin - boxout = self._boxout - x_scale = boxout.width / boxin.width - y_scale = boxout.height / boxin.height + def is_separable(self): + return True - # MGDTODO: Optimize - affine = Affine2D() \ - .translate(-boxin.xmin, -boxin.ymin) \ - .scale(x_scale, y_scale) \ - .translate(boxout.xmin, boxout.ymin) + def get_matrix(self): + if self._mtx is None: + boxin = self._boxin + boxout = self._boxout + x_scale = boxout.width / boxin.width + y_scale = boxout.height / boxin.height - self._mtx = affine._mtx - - def is_separable(self): - return True + # MGDTODO: Optimize + affine = Affine2D() \ + .translate(-boxin.xmin, -boxin.ymin) \ + .scale(x_scale, y_scale) \ + .translate(boxout.xmin, boxout.ymin) - def get_matrix(self): - self._make__mtx() - return self._mtx - + self._mtx = affine._mtx + return self._mtx + def blend_xy_sep_transform(x_transform, y_transform): if x_transform.is_affine() and y_transform.is_affine(): - return BlendedAffine2D(x_transform, y_transform) + return BlendedAffine2D(x_transform, y_transform) return BlendedTransform(x_transform, y_transform) def composite_transform_factory(a, b): if a.is_affine() and b.is_affine(): - return CompositeAffine2D(a, b) + return CompositeAffine2D(a, b) return CompositeTransform(a, b) # MGDTODO: There's probably a better place for this @@ -562,7 +754,7 @@ vmin, vmax = vmax, vmin swapped = True if vmax - vmin <= max(abs(vmin), abs(vmax)) * tiny: - if vmin==0.0: + if vmin == 0.0: vmin = -expander vmax = expander else: @@ -635,8 +827,8 @@ assert scale.to_values() == (10, 0, 0, 20, 0, 0) rotation = Affine2D().rotate_deg(30) print rotation.to_values() == (0.86602540378443871, 0.49999999999999994, - -0.49999999999999994, 0.86602540378443871, - 0.0, 0.0) + -0.49999999999999994, 0.86602540378443871, + 0.0, 0.0) points = npy.array([[1,2],[3,4],[5,6],[7,8]], npy.float_) translated_points = translation(points) Modified: branches/transforms/lib/matplotlib/artist.py =================================================================== --- branches/transforms/lib/matplotlib/artist.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/artist.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -21,7 +21,7 @@ # http://groups.google.com/groups?hl=en&lr=&threadm=mailman.5090.1098044946.5135.python-list%40python.org&rnum=1&prev=/groups%3Fq%3D__doc__%2Bauthor%253Ajdhunter%2540ace.bsd.uchicago.edu%26hl%3Den%26btnG%3DGoogle%2BSearch -class Artist: +class Artist(object): """ Abstract base class for someone who renders into a FigureCanvas """ Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -436,8 +436,7 @@ } def __str__(self): - return "Axes(%g,%g;%gx%g)"%(self._position[0].get(),self._position[1].get(), - self._position[2].get(),self._position[3].get()) + return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds) def __init__(self, fig, rect, axisbg = None, # defaults to rc axes.facecolor frameon = True, @@ -590,13 +589,13 @@ def follow_foreign_ylim(ax): ymin, ymax = axforeign.get_ylim() - # do not emit here or we'll get a ping png effect + # 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 png effect + # do not emit here or we'll get a ping pong effect axforeign.set_ylim(ymin, ymax, emit=False) axforeign.figure.canvas.draw_idle() @@ -613,65 +612,66 @@ """ martist.Artist.set_figure(self, fig) - l, b, w, h = self._position.bounds - xmin = fig.bbox.xmin - xmax = fig.bbox.xmax - ymin = fig.bbox.ymin - ymax = fig.bbox.ymax - figw = xmax-xmin - figh = ymax-ymin - self.left = l*figw - self.bottom = b*figh - self.right = (l+w)*figw - self.top = (b+h)*figh - - self.bbox = maffine.Bbox.from_lbrt( - self.left, self.bottom, - self.right, self.top, - ) + self.bbox = maffine.TransformedBbox(self._position, fig.transFigure) #these will be updated later as data is added self._set_lim_and_transforms() + def _shared_xlim_callback(self, ax): + xmin, xmax = ax.get_xlim() + self.set_xlim(xmin, xmax, emit=False) + self.figure.canvas.draw_idle() + + def _shared_ylim_callback(self, ax): + ymin, ymax = ax.get_ylim() + self.set_ylim(ymin, ymax, emit=False) + self.figure.canvas.draw_idle() + def _set_lim_and_transforms(self): """ set the dataLim and viewLim BBox attributes and the transData and transAxes Transformation attributes """ - Bbox = maffine.Bbox + Bbox = maffine.Bbox + self.viewLim = Bbox.unit() + if self._sharex is not None: - left = self._sharex.viewLim.xmin() - right = self._sharex.viewLim.xmax() - else: - left = 0.0 - right = 1.0 + # MGDTODO: This may be doing at least one too many updates + # than necessary + self._sharex.callbacks.connect( + 'xlim_changed', self._shared_xlim_callback) + self.viewLim.intervalx = self._sharex.viewLim.intervalx if self._sharey is not None: - bottom = self._sharey.viewLim.ymin() - top = self._sharey.viewLim.ymax() - else: - bottom = 0.0 - top = 1.0 + self._sharey.callbacks.connect( + 'ylim_changed', self._shared_ylim_callback) + self.viewLim.intervaly = self._sharex.viewLim.intervaly - self.viewLim = Bbox.from_lbrt(left, bottom, right, top) self.dataLim = Bbox.unit() - self.transData = maffine.BboxTransform( - self.viewLim, self.bbox) self.transAxes = maffine.BboxTransform( Bbox.unit(), self.bbox) - # MGDTODO -# if self._sharex: -# self.transData.set_funcx(self._sharex.transData.get_funcx()) - -# if self._sharey: -# self.transData.set_funcy(self._sharey.transData.get_funcy()) - + localTransData = maffine.BboxTransform( + self.viewLim, self.bbox) + if self._sharex: + transDataX = self._sharex.transData + else: + transDataX = localTransData + if self._sharey: + transDataY = self._sharey.transData + else: + transDataY = localTransData + self.transData = localTransData # maffine.blend_xy_sep_transform(transDataX, transDataY) + + def get_position(self, original=False): 'Return the axes rectangle left, bottom, width, height' + # MGDTODO: This changed from returning a list to returning a Bbox + # If you get any errors with the result of this function, please + # update the calling code if original: - return self._originalPosition.bounds + return copy.copy(self._originalPosition) else: - return self._position.bounds + return copy.copy(self._position) # return [val.get() for val in self._position] def set_position(self, pos, which='both'): @@ -690,14 +690,9 @@ ACCEPTS: len(4) sequence of floats """ if which in ('both', 'active'): - # MGDTODO -# # Change values within self._position--don't replace it. -# for num,val in zip(pos, self._position): -# val.set(num) - self._position.bounds = pos.bounds - # MGDTODO: side-effects + self._position.set(pos) if which in ('both', 'original'): - self._originalPosition.bounds = pos.bounds + self._originalPosition.set(pos) def _set_artist_props(self, a): @@ -1546,7 +1541,14 @@ xmin, xmax = maffine.nonsingular(xmin, xmax, increasing=False) self.viewLim.intervalx = (xmin, xmax) - + if emit: + self.callbacks.process('xlim_changed', self) + # MGDTODO: It would be nice to do this is in the above callback list, + # but it's difficult to tell how to initialize this at the + # right time + if self._sharex: + self._sharex.set_xlim(*self.viewLim.intervalx) + return xmin, xmax def get_xscale(self): @@ -1650,7 +1652,6 @@ ACCEPTS: len(2) sequence of floats """ - if ymax is None and iterable(ymin): ymin,ymax = ymin @@ -1671,8 +1672,14 @@ ymin, ymax = maffine.nonsingular(ymin, ymax, increasing=False) self.viewLim.intervaly = (ymin, ymax) - if emit: self.callbacks.process('ylim_changed', self) - + if emit: + self.callbacks.process('ylim_changed', self) + # MGDTODO: It would be nice to do this is in the above callback list, + # but it's difficult to tell how to initialize this at the + # right time + if self._sharey: + self._sharey.set_ylim(*self.viewLim.intervaly) + return ymin, ymax def get_yscale(self): Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -1028,9 +1028,8 @@ x,y = self.label.get_position() if self.label_position == 'bottom': if not len(bboxes): - bottom = self.axes.bbox.ymin() + bottom = self.axes.bbox.ymin else: - bbox = Bbox.union(bboxes) bottom = bbox.ymin @@ -1041,7 +1040,6 @@ if not len(bboxes2): top = self.axes.bbox.ymax else: - bbox = bbox_union(bboxes2) top = bbox.ymax @@ -1054,7 +1052,7 @@ """ x,y = self.offsetText.get_position() if not len(bboxes): - bottom = self.axes.bbox.ymin() + bottom = self.axes.bbox.ymin else: bbox = Bbox.union(bboxes) bottom = bbox.ymin Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -752,7 +752,8 @@ else: # Just found one hit self.inaxes = axes_list[0] - try: xdata, ydata = self.inaxes.transData.inverted()([[x, y]])[0] + try: + xdata, ydata = self.inaxes.transData.inverted().transform_point((x, y)) except ValueError: self.xdata = None self.ydata = None @@ -1584,8 +1585,8 @@ lims.append( (xmin, xmax, ymin, ymax) ) # Store both the original and modified positions pos.append( ( - tuple( a.get_position(True) ), - tuple( a.get_position() ) ) ) + a.get_position(True), + a.get_position() ) ) self._views.push(lims) self._positions.push(pos) self.set_history_buttons() Modified: branches/transforms/lib/matplotlib/figure.py =================================================================== --- branches/transforms/lib/matplotlib/figure.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/figure.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -18,7 +18,7 @@ from text import Text, _process_text_args from legend import Legend -from affine import Bbox, BboxTransform +from affine import Affine2D, Bbox, BboxTransform, TransformedBbox from ticker import FormatStrFormatter from cm import ScalarMappable from contour import ContourSet @@ -128,17 +128,15 @@ if facecolor is None: facecolor = rcParams['figure.facecolor'] if edgecolor is None: edgecolor = rcParams['figure.edgecolor'] + self._dpi_scale_trans = Affine2D() self.dpi = dpi - figwidth = figsize[0] * dpi - figheight = figsize[1] * dpi - self.bbox = Bbox.from_lbwh(0, 0, figwidth, figheight) + self.bbox_inches = Bbox.from_lbwh(0, 0, *figsize) + self.bbox = TransformedBbox(self.bbox_inches, self._dpi_scale_trans) self.frameon = frameon - self.transFigure = BboxTransform( Bbox.unit(), self.bbox) + self.transFigure = BboxTransform(Bbox.unit(), self.bbox) - - self.figurePatch = Rectangle( xy=(0,0), width=1, height=1, facecolor=facecolor, edgecolor=edgecolor, @@ -160,6 +158,15 @@ self._cachedRenderer = None + def _get_dpi(self): + return self._dpi + def _set_dpi(self, dpi): + print "setting dpi" + self._dpi = dpi + self._dpi_scale_trans.clear().scale(dpi, dpi) + print self._dpi_scale_trans + dpi = property(_get_dpi, _set_dpi) + def autofmt_xdate(self, bottom=0.2, rotation=30, ha='right'): """ A common use case is a number of subplots with shared xaxes @@ -325,7 +332,7 @@ w,h = args dpival = self.dpi - self.bbox.max = w * dpival, h * dpival + self.bbox_inches.max = w, h # self.figwidth.set(w) MGDTODO # self.figheight.set(h) @@ -339,7 +346,7 @@ manager.resize(int(canvasw), int(canvash)) def get_size_inches(self): - return self.bbox.max + return self.bbox_inches.max # return self.figwidth.get(), self.figheight.get() MGDTODO def get_edgecolor(self): @@ -352,12 +359,12 @@ def get_figwidth(self): 'Return the figwidth as a float' - return self.bbox.xmax + return self.bbox_inches.xmax # return self.figwidth.get() MGDTODO def get_figheight(self): 'Return the figheight as a float' - return self.bbox.ymax + return self.bbox_inches.ymax def get_dpi(self): 'Return the dpi as a float' @@ -400,7 +407,7 @@ ACCEPTS: float """ # self.figwidth.set(val) MGDTODO - self.bbox.xmax = val + self.bbox_inches.xmax = val def set_figheight(self, val): """ @@ -409,7 +416,7 @@ ACCEPTS: float """ # MGDTODO (set()) - self.bbox.ymax = val + self.bbox_inches.ymax = val def set_frameon(self, b): """ Modified: branches/transforms/lib/matplotlib/pyplot.py =================================================================== --- branches/transforms/lib/matplotlib/pyplot.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/pyplot.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -481,7 +481,7 @@ byebye = [] for other in fig.axes: if other==a: continue - if bbox.overlaps(other.bbox, ignoreend=True): + if bbox.fully_overlaps(other.bbox): byebye.append(other) for ax in byebye: delaxes(ax) Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-09-13 12:50:05 UTC (rev 3847) +++ branches/transforms/lib/matplotlib/text.py 2007-09-13 18:00:10 UTC (rev 3848) @@ -15,7 +15,7 @@ from cbook import enumerate, is_string_like, maxdict, is_numlike from font_manager import FontProperties from patches import bbox_artist, YAArrow -from affine import Bbox +from affine import Affine2D, Bbox from lines import Line2D import matplotlib.nxutils as nxutils @@ -213,30 +213,32 @@ M = self.get_rotation_matrix(xmin, ymin) # the corners of the unrotated bounding box - cornersHoriz = ( (xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin) ) - offsetLayout = [] + cornersHoriz = npy.array( + [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)], + npy.float_) + offsetLayout = npy.zeros((len(lines), 2)) # now offset the individual text lines within the box if len(lines)>1: # do the multiline aligment malign = self._get_multialignment() - for line, thisx, thisy, w, h in horizLayout: + for i, (line, thisx, thisy, w, h) in enumerate(horizLayout): if malign=='center': offsetx = width/2.0-w/2.0 elif malign=='right': offsetx = width-w else: offsetx = 0 thisx += offsetx - offsetLayout.append( (thisx, thisy )) + offsetLayout[i] = (thisx, thisy) else: # no additional layout needed - offsetLayout = [ (thisx, thisy) for line, thisx, thisy, w, h in horizLayout] + offsetLayout[0] = horizLayout[0][1:3] # now rotate the bbox - cornersRotated = [npy.dot(M,npy.array([[thisx],[thisy],[1]])) for thisx, thisy in cornersHoriz] + cornersRotated = M(cornersHoriz) - txs = [float(v[0][0]) for v in cornersRotated] - tys = [float(v[1][0]) for v in cornersRotated] + txs = cornersRotated[:, 0] + tys = cornersRotated[:, 1] # compute the bounds of the rotated box - xmin, xmax = min(txs), max(txs) - ymin, ymax = min(tys), max(tys) + xmin, xmax = txs.min(), txs.max() + ymin, ymax = tys.min(), tys.max() width = xmax - xmin height = ymax - ymin @@ -264,17 +266,18 @@ bbox = Bbox.from_lbwh(xmin, ymin, width, height) + # now rotate the positions around the first x,y position - xys = [npy.dot(M,npy.array([[thisx],[thisy],[1]])) for thisx, thisy in offsetLayout] + xys = M(offsetLayout) + tx = xys[:, 0] + ty = xys[:, 1] + tx += offsetx + ty += offsety - - tx = [float(v[0][0])+offsetx for v in xys] - ty = [float(v[1][0])+offsety for v in xys] - # now inverse transform back to data coords inverse_transform = self.get_transform().inverted() - xys = inverse_transform(zip(tx, ty)) + xys = inverse_transform(xys) xs, ys = zip(*xys) @@ -327,7 +330,7 @@ return for line, wh, x, y in info: - x, y = trans([[x, y]])[0] + x, y = trans.transform_point((x, y)) if renderer.flipy(): canvasw, canvash = renderer.get_canvas_width_height() @@ -435,29 +438,9 @@ bbox, info = self._get_layout(self._renderer) return bbox - - def get_rotation_matrix(self, x0, y0): + return Affine2D().rotate_deg_around(x0, y0, self.get_rotation()) - theta = npy.pi/180.0*self.get_rotation() - # translate x0,y0 to origin - Torigin = npy.array([ [1, 0, -x0], - [0, 1, -y0], - [0, 0, 1 ]]) - - # rotate by theta - R = npy.array([ [npy.cos(theta), -npy.sin(theta), 0], - [npy.sin(theta), npy.cos(theta), 0], - [0, 0, 1]]) - - # translate origin back to x0,y0 - Tback = npy.array([ [1, 0, x0], - [0, 1, y0], - [0, 0, 1 ]]) - - - return npy.dot(npy.dot(Tback,R), Torigin) - def set_backgroundcolor(self, color): """ Set the background color of the text by updating the bbox (see set_bbox for more info) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-19 13:28:21
|
Revision: 3857 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3857&view=rev Author: mdboom Date: 2007-09-19 06:28:11 -0700 (Wed, 19 Sep 2007) Log Message: ----------- Got legend working with new transforms Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/legend.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/patches.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-18 19:29:21 UTC (rev 3856) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-19 13:28:11 UTC (rev 3857) @@ -632,6 +632,7 @@ self.transAxes = mtransforms.BboxTransform( mtransforms.Bbox.unit(), self.bbox) + # self.set_transform(self.transAxes) self.transData = mtransforms.BboxTransform( self.viewLim, self.bbox) @@ -724,6 +725,7 @@ self.axesPatch.set_figure(self.figure) self.axesPatch.set_transform(self.transAxes) self.axesPatch.set_linewidth(rcParams['axes.linewidth']) + # MGDTODO: What is axesFrame for? We already have axesPatch self.axesFrame = mlines.Line2D((0,1,1,0,0), (0,0,1,1,0), linewidth=rcParams['axes.linewidth'], color=rcParams['axes.edgecolor'], @@ -5201,7 +5203,7 @@ Subplot(211) # 2 rows, 1 column, first (upper) plot """ def __str__(self): - return "Subplot(%g,%g)"%(self.bottom.get(),self.left.get()) + return "Subplot(%f,%f,%f,%f)" % (self.bbox.bounds) def __init__(self, fig, *args, **kwargs): """ Modified: branches/transforms/lib/matplotlib/legend.py =================================================================== --- branches/transforms/lib/matplotlib/legend.py 2007-09-18 19:29:21 UTC (rev 3856) +++ branches/transforms/lib/matplotlib/legend.py 2007-09-19 13:28:11 UTC (rev 3857) @@ -164,7 +164,7 @@ else: raise TypeError("Legend needs either Axes or Figure as parent") self.parent = parent - self.set_transform( BboxTransform( Bbox.unit(), parent.bbox) ) + self.set_transform( BboxTransform(Bbox.unit(), parent.bbox) ) if loc is None: loc = rcParams["legend.loc"] @@ -223,7 +223,7 @@ a.set_transform(self.get_transform()) def _approx_text_height(self): - return self.fontsize/72.0*self.figure.dpi/self.parent.bbox.height() + return self.fontsize/72.0*self.figure.dpi/self.parent.bbox.height def draw(self, renderer): @@ -531,7 +531,7 @@ def get_tbounds(text): #get text bounds in axes coords bbox = text.get_window_extent(renderer) bboxa = bbox.inverse_transformed(self.get_transform()) - return bboxa.get_bounds() + return bboxa.bounds hpos = [] for t, tabove in zip(self.texts[1:], self.texts[:-1]): @@ -560,10 +560,10 @@ # Set the data for the legend patch bbox = copy.copy(self._get_handle_text_bbox(renderer)) - bbox = bbox.scaled(1 + self.pad, 1 + self.pad) - l,b,w,h = bbox.get_bounds() - self.legendPatch.set_bounds(l,b,w,h) - + bbox = bbox.expanded(1 + self.pad, 1 + self.pad) + l, b, w, h = bbox.bounds + self.legendPatch.set_bounds(l, b, w, h) + ox, oy = 0, 0 # center if iterable(self._loc) and len(self._loc)==2: Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-18 19:29:21 UTC (rev 3856) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-19 13:28:11 UTC (rev 3857) @@ -117,7 +117,7 @@ '--' : '_draw_dashed', '-.' : '_draw_dash_dot', ':' : '_draw_dotted', - 'steps': '_draw_steps', + 'steps': '_draw_solid', 'None' : '_draw_nothing', ' ' : '_draw_nothing', '' : '_draw_nothing', @@ -352,10 +352,10 @@ self._picker = p def get_window_extent(self, renderer): - xys = self.get_transform()(self._xys) + xy = self.get_transform()(self._xy) - x = xys[:, 0] - y = xys[:, 1] + x = xy[:, 0] + y = xy[:, 1] left = x.min() bottom = y.min() width = x.max() - left @@ -426,9 +426,19 @@ npy.asarray(y, npy.float_))).transpose() self._x = self._xy[:, 0] self._y = self._xy[:, 1] - self._path = Path(self._xy, closed=False) - self._logcache = None + + if self._linestyle == 'steps': + siz=len(xt) + if siz<2: return + xt, yt = self._x, self._y + xt2=npy.ones((2*siz,), xt.dtype) + xt2[0:-1:2], xt2[1:-1:2], xt2[-1] = xt, xt[1:], xt[-1] + yt2=npy.ones((2*siz,), yt.dtype) + yt2[0:-1:2], yt2[1::2] = yt, yt + self._path = Path(npy.vstack((xt2, yt2)).transpose(), closed=False) + else: + self._path = Path(self._xy, closed=False) def _is_sorted(self, x): @@ -700,24 +710,6 @@ pass - def _draw_steps(self, renderer, gc, xt, yt): - # MGDTODO: This is a quirky one. The step-plotting part - # should probably be moved to where the path is generated - # in recache, and then just drawn with _draw_solid - siz=len(xt) - if siz<2: return - xt2=npy.ones((2*siz,), xt.dtype) - xt2[0:-1:2], xt2[1:-1:2], xt2[-1]=xt, xt[1:], xt[-1] - yt2=npy.ones((2*siz,), yt.dtype) - yt2[0:-1:2], yt2[1::2]=yt, yt - gc.set_linestyle('solid') - - if self._newstyle: - renderer.draw_lines(gc, xt2, yt2, self.get_transform()) - else: - renderer.draw_lines(gc, xt2, yt2) - - def _draw_solid(self, renderer, gc, path): gc.set_linestyle('solid') renderer.draw_path(gc, path, self.get_transform()) Modified: branches/transforms/lib/matplotlib/patches.py =================================================================== --- branches/transforms/lib/matplotlib/patches.py 2007-09-18 19:29:21 UTC (rev 3856) +++ branches/transforms/lib/matplotlib/patches.py 2007-09-19 13:28:11 UTC (rev 3857) @@ -212,8 +212,8 @@ gc.set_hatch(self._hatch ) path = self.get_path() - transform = self.get_transform() - + transform = self.get_patch_transform() + self.get_transform() + renderer.draw_path(gc, path, transform, rgbFace) #renderer.close_group('patch') @@ -284,7 +284,7 @@ self.patch = patch self.props = props self.ox, self.oy = ox, oy - self._shadow_transform = transforms.Affine2D.translate(self.ox, self.oy) + self._shadow_transform = transforms.Affine2D().translate(self.ox, self.oy) self._update() __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd @@ -305,8 +305,8 @@ def get_path(self): return self.patch.get_path() - def get_transform(self): - return self._transform + self._shadow_transform + def get_patch_transform(self): + return self._shadow_transform class Rectangle(Patch): """ @@ -315,8 +315,6 @@ """ - _path = Path.unit_rectangle() - def __str__(self): return str(self.__class__).split('.')[-1] \ + "(%g,%g;%gx%g)"%(self.xy[0],self.xy[1],self.width,self.height) @@ -346,16 +344,14 @@ """ Return the vertices of the rectangle """ - # This is a "class-static" variable, so all rectangles in the plot - # will be shared (and merely have different transforms) - return self._path + 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_transform(self): - return self._rect_transform + self._transform + def get_patch_transform(self): + return self._rect_transform def get_x(self): "Return the left coord of the rectangle" @@ -379,7 +375,8 @@ ACCEPTS: float """ - self._bbox.xmin = x + w = self._bbox.width + self._bbox.intervalx = (x, x + w) def set_y(self, y): """ @@ -387,7 +384,8 @@ ACCEPTS: float """ - self._bbox.ymin = y + h = self._bbox.height + self._bbox.intervaly = (y, y + h) def set_width(self, w): """ @@ -395,7 +393,7 @@ ACCEPTS: float """ - self._bbox.width = w + self._bbox.xmax = self._bbox.xmin + w def set_height(self, h): """ @@ -403,7 +401,7 @@ ACCEPTS: float """ - self._bbox.height = h + self._bbox.ymax = self._bbox.ymin + h def set_bounds(self, *args): """ Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-18 19:29:21 UTC (rev 3856) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-19 13:28:11 UTC (rev 3857) @@ -8,6 +8,8 @@ from numpy.linalg import inv from sets import Set +DEBUG = True + # MGDTODO: This creates a ton of cyclical references. We may want to # consider using weak references @@ -53,6 +55,13 @@ def __array__(self): return self.get_points() + + if DEBUG: + def invalidate(self): + points = self.get_points() + assert points[0, 0] <= points[1, 0] + assert points[0, 1] <= points[1, 1] + TransformNode.invalidate(self) # MGDTODO: Probably a more efficient ways to do this... def _get_xmin(self): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-19 19:46:41
|
Revision: 3859 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3859&view=rev Author: mdboom Date: 2007-09-19 12:46:34 -0700 (Wed, 19 Sep 2007) Log Message: ----------- Lots of minor fixes Modified Paths: -------------- branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/patches.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/pbox.py branches/transforms/lib/matplotlib/text.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-19 16:18:51 UTC (rev 3858) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-19 19:46:34 UTC (rev 3859) @@ -252,12 +252,9 @@ if not is_numlike(self.pickradius): raise ValueError,"pick radius should be a distance" - if self._newstyle: - # transform in backend - x = self._x - y = self._y - else: - x, y = self._get_plottable() + # transform in backend + x = self._x + y = self._y if len(x)==0: return False,{} xt, yt = self.get_transform().numerix_x_y(x, y) @@ -337,7 +334,6 @@ ACCEPTS: (npy.array xdata, npy.array ydata) """ - if len(args)==1: x, y = args[0] else: @@ -347,8 +343,9 @@ self._yorig = y self.recache() + # MGDTODO: Masked data arrays are broken _masked_array_to_path_code_mapping = npy.array( - [Path.LINETO, Path.IGNORE, Path.MOVETO], Path.code_type) + [Path.LINETO, Path.MOVETO, Path.MOVETO], Path.code_type) def recache(self): #if self.axes is None: print 'recache no axes' #else: print 'recache units', self.axes.xaxis.units, self.axes.yaxis.units @@ -387,18 +384,18 @@ # MGDTODO: If _draw_steps is removed, remove the following line also self._step_path = None - def _is_sorted(self, x): "return true if x is sorted" 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: Deal with the log scale here + # MGDTODO: (log-scaling) # try: logx = self.get_transform().get_funcx().get_type()==LOG10 # except RuntimeError: logx = False # non-separable Modified: branches/transforms/lib/matplotlib/patches.py =================================================================== --- branches/transforms/lib/matplotlib/patches.py 2007-09-19 16:18:51 UTC (rev 3858) +++ branches/transforms/lib/matplotlib/patches.py 2007-09-19 19:46:34 UTC (rev 3859) @@ -319,8 +319,6 @@ return str(self.__class__).split('.')[-1] \ + "(%g,%g;%gx%g)"%(self.xy[0],self.xy[1],self.width,self.height) - # MGDTODO: Perhaps pass in a Bbox here instead, then the updates will - # happen automatically (without needing to call set_x etc. def __init__(self, xy, width, height, **kwargs): """ xy is an x,y tuple lower, left @@ -459,17 +457,14 @@ def __init__(self, xy, **kwargs): """ - xy is a sequence of (x,y) 2 tuples + xy is a numpy array with shape Nx2 Valid kwargs are: %(Patch)s See Patch documentation for additional kwargs """ - # MGDTODO: This should encourage the use of numpy arrays of shape Nx2 Patch.__init__(self, **kwargs) - if not isinstance(xy, list): - xy = list(xy) - self._path = Path(xy, closed=False) + self._path = Path(xy, closed=True) __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd def get_verts(self): Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-09-19 16:18:51 UTC (rev 3858) +++ branches/transforms/lib/matplotlib/path.py 2007-09-19 19:46:34 UTC (rev 3859) @@ -1,15 +1,13 @@ import numpy as npy -DEBUG = True - class Path(object): # Path codes - IGNORE = 0 # 1 vertex + STOP = 0 # 1 vertex MOVETO = 1 # 1 vertex LINETO = 2 # 1 vertex CURVE3 = 3 # 2 vertices CURVE4 = 4 # 3 vertices - CLOSEPOLY = 5 + 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 @@ -18,38 +16,36 @@ UBSPLINE = 8 #### - NUM_VERTICES = [1, 1, 1, 2, 3, 0] + NUM_VERTICES = [1, 1, 1, 2, 3, 1] code_type = npy.uint8 def __init__(self, vertices, codes=None, closed=True): - self._vertices = npy.asarray(vertices, npy.float_) - assert self._vertices.ndim == 2 - assert self._vertices.shape[1] == 2 - + vertices = npy.asarray(vertices, npy.float_) + assert vertices.ndim == 2 + assert vertices.shape[1] == 2 + if codes is None: if closed: codes = self.LINETO * npy.ones( - self._vertices.shape[0] + 1, self.code_type) + vertices.shape[0] + 1, self.code_type) codes[0] = self.MOVETO - codes[-1] = self.CLOSEPOLY + codes[-1] = self.CLOSEPOLY + vertices = npy.concatenate((vertices, [[0.0, 0.0]])) else: codes = self.LINETO * npy.ones( - self._vertices.shape[0], self.code_type) + vertices.shape[0], self.code_type) codes[0] = self.MOVETO else: codes = npy.asarray(codes, self.code_type) - self._codes = codes - + assert codes.ndim == 1 + assert len(codes) == len(vertices) + + self._codes = codes + self._vertices = vertices + assert self._codes.ndim == 1 - if DEBUG: - i = 0 - NUM_VERTICES = self.NUM_VERTICES - for code in codes: - i += NUM_VERTICES[code] - assert i == len(self.vertices) - def __repr__(self): return "Path(%s, %s)" % (self.vertices, self.codes) @@ -66,11 +62,13 @@ NUM_VERTICES = self.NUM_VERTICES vertices = self.vertices for code in self.codes: - num_vertices = NUM_VERTICES[code] - if num_vertices >= 1: - i += num_vertices - 1 - yield vertices[i] - i += 1 + if code == self.CLOSEPOLY: + i += 1 + else: + num_vertices = NUM_VERTICES[code] + i += num_vertices - 1 + yield vertices[i] + i += 1 _unit_rectangle = None #@classmethod @@ -118,16 +116,18 @@ [-offset, -1.0], [-1.0, -offset], - [-1.0, 0.0]], - npy.float_) - codes = npy.array( - [cls.MOVETO, - cls.CURVE4, - cls.CURVE4, - cls.CURVE4, - cls.CURVE4, - cls.CLOSEPOLY], - cls.code_type) + [-1.0, 0.0], + + [-1.0, 0.0]], + npy.float_) + + codes = cls.CURVE4 + npy.ones((len(vertices))) + codes[0] = cls.MOVETO + codes[-1] = cls.CLOSEPOLY + cls._unit_circle = Path(vertices, codes) return cls._unit_circle unit_circle = classmethod(unit_circle) + +# MGDTODO: Add a transformed path that would automatically invalidate +# itself when its transform changes Modified: branches/transforms/lib/matplotlib/pbox.py =================================================================== --- branches/transforms/lib/matplotlib/pbox.py 2007-09-19 16:18:51 UTC (rev 3858) +++ branches/transforms/lib/matplotlib/pbox.py 2007-09-19 19:46:34 UTC (rev 3859) @@ -1,5 +1,3 @@ -# MGDTODO: Just included verbatim for now - class PBox(list): ''' A left-bottom-width-height (lbwh) specification of a bounding box, Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-09-19 16:18:51 UTC (rev 3858) +++ branches/transforms/lib/matplotlib/text.py 2007-09-19 19:46:34 UTC (rev 3859) @@ -231,7 +231,7 @@ # now rotate the bbox - cornersRotated = M(cornersHoriz) + cornersRotated = M.transform(cornersHoriz) txs = cornersRotated[:, 0] tys = cornersRotated[:, 1] @@ -269,7 +269,7 @@ # now rotate the positions around the first x,y position - xys = M(offsetLayout) + xys = M.transform(offsetLayout) tx = xys[:, 0] ty = xys[:, 1] tx += offsetx @@ -277,7 +277,7 @@ # now inverse transform back to data coords inverse_transform = self.get_transform().inverted() - xys = inverse_transform(xys) + xys = inverse_transform.transform(xys) xs, ys = zip(*xys) @@ -407,7 +407,7 @@ return (x, y, self._text, self._color, self._verticalalignment, self._horizontalalignment, hash(self._fontproperties), self._rotation, - self.get_transform().to_values(), + self.get_transform(), ) def get_text(self): Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-19 16:18:51 UTC (rev 3858) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-19 19:46:34 UTC (rev 3859) @@ -32,7 +32,8 @@ for child in children: getattr(self, child)._parents.add(self) self._children = children - + + class BboxBase(TransformNode): ''' This is the read-only part of a bounding-box @@ -293,6 +294,7 @@ return Bbox.from_lbrt(xmin, ymin, xmax, ymax) union = staticmethod(union) + class TransformedBbox(BboxBase): def __init__(self, bbox, transform): assert isinstance(bbox, Bbox) @@ -313,16 +315,20 @@ def get_points(self): if self._points is None: - self._points = self.transform(self.bbox.get_points()) + self._points = self.transform.transform(self.bbox.get_points()) return self._points + class Transform(TransformNode): def __init__(self): TransformNode.__init__(self) - def __call__(self, points): + def transform(self, points): raise NotImplementedError() + def transform_without_affine(self, points): + return self.transform(points), IDENTITY + def __add__(self, other): if isinstance(other, Transform): return composite_transform_factory(self, other) @@ -336,7 +342,7 @@ "Can not add Transform to object of type '%s'" % type(other)) def transform_point(self, point): - return self.__call__(npy.asarray([point]))[0] + return self.transform(npy.asarray([point]))[0] def has_inverse(self): raise NotImplementedError() @@ -350,6 +356,7 @@ def is_affine(self): return False + class Affine2DBase(Transform): input_dims = 2 output_dims = 2 @@ -390,7 +397,7 @@ def get_matrix(self): raise NotImplementedError() - def __call__(self, points): + def transform(self, points): """ Applies the transformation to an array of 2D points and returns the result. @@ -414,6 +421,11 @@ points = npy.dot(mtx[0:2, 0:2], points) points = points + mtx[0:2, 2:] return points.transpose() + + def transform_without_affine(self, points): + # MGDTODO: Should we copy the points here? I'd like to avoid it, + # if possible + return points, self def inverted(self): if self._inverted is None: @@ -430,9 +442,6 @@ class Affine2D(Affine2DBase): - input_dims = 2 - output_dims = 2 - def __init__(self, matrix = None): """ Initialize an Affine transform from a 3x3 numpy float array. @@ -535,40 +544,82 @@ def is_affine(self): return True + +IDENTITY = Affine2D() class BlendedGenericTransform(Transform): + input_dims = 2 + output_dims = 2 + def __init__(self, x_transform, y_transform): # Here we ask: "Does it blend?" assert x_transform.is_separable() assert y_transform.is_separable() - + assert x_transform.input_dims == x_transform.output_dims == 2 + assert y_transform.input_dims == y_transform.output_dims == 2 + Transform.__init__(self) self._x = x_transform self._y = y_transform self.set_children(['_x', '_y']) - def __call__(self, points): - if self._x == self._y: + def transform(self, points): + # MGDTODO: Optimize the case where one of these is + # an affine + x = self._x + y = self._y + if x == y and x.input_dims == 2: return self._x(points) - - x_points = self._x(points) - y_points = self._y(points) - # This works because we already know the transforms are - # separable - return npy.hstack((x_points[:, 0:1], y_points[:, 1:2])) -# def set_x_transform(self, x_transform): -# self.replace_child(0, x_transform) + if x.input_dims == 2: + x_points = x.transform(points)[:, 0] + else: + x_points = x.transform(points[:, 0]) -# def set_y_transform(self, y_transform): -# self.replace_child(1, y_transform) + if y.input_dims == 2: + y_points = y.transform(points)[:, 1] + else: + y_points = y.transform(points[:, 1]) + return npy.vstack((x_points, y_points)).transpose() + + def inverted(self): + return BlendedGenericTransform(self._x.inverted(), self._y.inverted()) -class BlendedAffine2D(Affine2DBase, BlendedGenericTransform): + def is_separable(self): + return True + + +class BlendedSeparableTransform(Transform): + input_dims = 2 + output_dims = 2 + def __init__(self, x_transform, y_transform): + # Here we ask: "Does it blend?" + assert x_transform.is_separable() + assert y_transform.is_separable() + assert x_transform.input_dims == x.transform.output_dims == 1 + assert y_transform.input_dims == y.transform.output_dims == 1 + + Transform.__init__(self) + self._x = x_transform + self._y = y_transform + self.set_children(['_x', '_y']) + + def transform(self, points): + x_points = self._x(points[:, 0]) + y_points = self._y(points[:, 1]) + return npy.vstack((x_points[:, 0:1], y_points[:, 1:2])).transpose() + + +class BlendedAffine2D(Affine2DBase, Transform): + def __init__(self, x_transform, y_transform): assert x_transform.is_affine() assert y_transform.is_affine() - BlendedGenericTransform.__init__(self, x_transform, y_transform) + Transform.__init__(self) + self._x = x_transform + self._y = y_transform + self.set_children(['_x', '_y']) Affine2DBase.__init__(self) self._mtx = None @@ -597,12 +648,14 @@ # c to zero. self._mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) return self._mtx - + + def blended_transform_factory(x_transform, y_transform): if x_transform.is_affine() and y_transform.is_affine(): return BlendedAffine2D(x_transform, y_transform) return BlendedGenericTransform(x_transform, y_transform) + class CompositeGenericTransform(Transform): def __init__(self, a, b): assert a.output_dims == b.input_dims @@ -614,9 +667,17 @@ self._b = b self.set_children(['_a', '_b']) - def __call__(self, points): - return self._b(self._a(points)) + def transform(self, points): + return self._b.transform(self._a.transform(points)) + + def inverted(self): + return CompositeGenericTransform(self._b.inverted(), self._a.inverted()) + def is_separable(self): + return True + return self._a.is_separable() and self._b.is_separable() + + class CompositeAffine2D(Affine2DBase): def __init__(self, a, b): assert a.is_affine() @@ -643,11 +704,32 @@ self._b.get_matrix()) return self._mtx + def composite_transform_factory(a, b): if a.is_affine() and b.is_affine(): return CompositeAffine2D(a, b) return CompositeGenericTransform(a, b) + + +class LogTransform(Transform): + input_dims = 1 + output_dims = 1 + def transform(self, a): + m = npy.ma.masked_where(a < 0, a) + return npy.log10(m) + + +class TestLogTransform(Transform): + input_dims = 2 + output_dims = 2 + def transform(self, xy): + return xy * 2 + + def inverted(self): + return self + + class BboxTransform(Affine2DBase): def __init__(self, boxin, boxout): assert isinstance(boxin, BboxBase) @@ -688,6 +770,7 @@ self._mtx = affine._mtx return self._mtx + def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): ''' Ensure the endpoints of a range are not too close together. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-20 18:00:34
|
Revision: 3869 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3869&view=rev Author: mdboom Date: 2007-09-20 11:00:32 -0700 (Thu, 20 Sep 2007) Log Message: ----------- First baby step in getting arbitrary non-linear transformations into the pipeline. Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/backends/backend_agg.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/text.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-20 14:26:27 UTC (rev 3868) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-20 18:00:32 UTC (rev 3869) @@ -633,10 +633,16 @@ self.transAxes = mtransforms.BboxTransform( mtransforms.Bbox.unit(), self.bbox) # self.set_transform(self.transAxes) - self.transData = mtransforms.BboxTransform( - self.viewLim, self.bbox) +# self.transData = mtransforms.BboxTransform( +# self.viewLim, self.bbox) + self.preDataTransform = mtransforms.BboxTransform( + self.viewLim, mtransforms.Bbox.unit()) + self.dataTransform = mtransforms.TestLogTransform() + # self.dataTransform = mtransforms.Affine2D().scale(1.5) + self.transData = self.preDataTransform + self.dataTransform + mtransforms.BboxTransform( + mtransforms.Bbox.unit(), self.bbox) + - def get_position(self, original=False): 'Return the axes rectangle left, bottom, width, height' if original: Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-20 14:26:27 UTC (rev 3868) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-20 18:00:32 UTC (rev 3869) @@ -1032,18 +1032,17 @@ else: bbox = Bbox.union(bboxes) bottom = bbox.ymin - - self.label.set_position( (x, bottom-self.LABELPAD*self.figure.dpi / 72.0)) - + self.label.set_position( (x, bottom - self.LABELPAD*self.figure.dpi / 72.0)) + else: if not len(bboxes2): top = self.axes.bbox.ymax else: bbox = bbox_union(bboxes2) top = bbox.ymax + + self.label.set_position( (x, top+self.LABELPAD*self.figure.dpi / 72.0)) - self.label.set_position( (x, top+self.LABELPAD*self.figure.dpi.get()/72.0)) - def _update_offset_text_position(self, bboxes, bboxes2): """ Update the offset_text position based on the sequence of bounding Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-20 14:26:27 UTC (rev 3868) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-20 18:00:32 UTC (rev 3869) @@ -84,7 +84,8 @@ from matplotlib.font_manager import findfont from matplotlib.ft2font import FT2Font, LOAD_DEFAULT from matplotlib.mathtext import MathTextParser -from matplotlib.transforms import Bbox +from matplotlib.path import Path +from matplotlib.transforms import Affine2D, Bbox from _backend_agg import RendererAgg as _RendererAgg @@ -117,8 +118,8 @@ debug=False) if __debug__: verbose.report('RendererAgg.__init__ _RendererAgg done', 'debug-annoying') - self.draw_path = self._renderer.draw_path - self.draw_markers = self._renderer.draw_markers + #self.draw_path = self._renderer.draw_path + #self.draw_markers = self._renderer.draw_markers self.draw_image = self._renderer.draw_image self.copy_from_bbox = self._renderer.copy_from_bbox self.restore_region = self._renderer.restore_region @@ -129,6 +130,17 @@ if __debug__: verbose.report('RendererAgg.__init__ done', 'debug-annoying') + # MGDTODO: This is a hack for now to allow for arbitrary transformations + def draw_path(self, gc, path, trans, rgbFace=None): + new_path, affine = path.transformed_without_affine(trans) + self._renderer.draw_path(gc, new_path, affine, rgbFace) + + # MGDTODO: This is a hack for now to allow for arbitrary transformations + def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): + assert marker_trans.is_affine() + new_path, affine = path.transformed_without_affine(trans) + self._renderer.draw_markers(gc, marker_path, marker_trans, new_path, affine, rgbFace) + def draw_mathtext(self, gc, x, y, s, prop, angle): """ Draw the math text using matplotlib.mathtext Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-20 14:26:27 UTC (rev 3868) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-20 18:00:32 UTC (rev 3869) @@ -1102,12 +1102,14 @@ """ return self._dashcapstyle + def get_solid_capstyle(self): """ Get the cap style for solid linestyles """ return self._solidcapstyle + def is_dashed(self): 'return True if line is dashstyle' return self._linestyle in ('--', '-.', ':') Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-09-20 14:26:27 UTC (rev 3868) +++ branches/transforms/lib/matplotlib/path.py 2007-09-20 18:00:32 UTC (rev 3869) @@ -70,6 +70,13 @@ yield vertices[i] i += 1 + def transformed(self, transform): + return Path(transform.transform(self.vertices), self.codes) + + def transformed_without_affine(self, transform): + vertices, affine = transform.transform_without_affine(self.vertices) + return Path(vertices, self.codes), affine + _unit_rectangle = None #@classmethod def unit_rectangle(cls): Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-09-20 14:26:27 UTC (rev 3868) +++ branches/transforms/lib/matplotlib/text.py 2007-09-20 18:00:32 UTC (rev 3869) @@ -185,7 +185,8 @@ xmin, ymin = thisx, thisy lines = self._text.split('\n') - + + # MGDTODO: whs could be a numpy.array whs = [] # Find full vertical extent of font, # including ascenders and descenders: @@ -260,27 +261,21 @@ else: offsety = ty - ymin xmin += offsetx - xmax += offsetx ymin += offsety - ymax += offsety bbox = Bbox.from_lbwh(xmin, ymin, width, height) - - # now rotate the positions around the first x,y position xys = M.transform(offsetLayout) - tx = xys[:, 0] - ty = xys[:, 1] - tx += offsetx - ty += offsety + xys[:, 0] += offsetx + xys[:, 1] += offsety # now inverse transform back to data coords inverse_transform = self.get_transform().inverted() xys = inverse_transform.transform(xys) - xs, ys = zip(*xys) - + xs, ys = xys[:, 0], xys[:, 1] + ret = bbox, zip(lines, whs, xs, ys) self.cached[key] = ret return ret @@ -331,15 +326,15 @@ for line, wh, x, y in info: x, y = trans.transform_point((x, y)) - + if renderer.flipy(): canvasw, canvash = renderer.get_canvas_width_height() y = canvash-y - + renderer.draw_text(gc, x, y, line, self._fontproperties, angle, ismath=self.is_math_text(line)) - + def get_color(self): "Return the color of the text" return self._color @@ -407,7 +402,9 @@ return (x, y, self._text, self._color, self._verticalalignment, self._horizontalalignment, hash(self._fontproperties), self._rotation, - self.get_transform(), + # MGDTODO: Find a better way to determine if the + # transform as changed + str(self.get_transform()) ) def get_text(self): Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-20 14:26:27 UTC (rev 3868) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-20 18:00:32 UTC (rev 3869) @@ -256,10 +256,10 @@ self.invalidate() def transformed(self, transform): - return Bbox(transform(self._points)) + return Bbox(transform.transform(self._points)) def inverse_transformed(self, transform): - return Bbox(transform.inverted()(self._points)) + return Bbox(transform.inverted().transform(self._points)) def expanded(self, sw, sh): width = self.width @@ -408,13 +408,13 @@ # MGDTODO: The major speed trap here is just converting to # the points to an array in the first place. If we can use # more arrays upstream, that should help here. - if not isinstance(points, npy.ndarray): - import traceback - print '-' * 60 - print 'A non-numpy array was passed in for transformation. Please ' - print 'correct this.' - print "".join(traceback.format_stack()) - print points +# if not isinstance(points, npy.ndarray): +# import traceback +# print '-' * 60 +# print 'A non-numpy array was passed in for transformation. Please ' +# print 'correct this.' +# print "".join(traceback.format_stack()) +# print points mtx = self.get_matrix() points = npy.asarray(points, npy.float_) points = points.transpose() @@ -563,6 +563,10 @@ self._y = y_transform self.set_children(['_x', '_y']) + def __repr__(self): + return "BlendedGenericTransform(%s,%s)" % (self._x, self._y) + __str__ = __repr__ + def transform(self, points): # MGDTODO: Optimize the case where one of these is # an affine @@ -590,28 +594,6 @@ return True -class BlendedSeparableTransform(Transform): - input_dims = 2 - output_dims = 2 - - def __init__(self, x_transform, y_transform): - # Here we ask: "Does it blend?" - assert x_transform.is_separable() - assert y_transform.is_separable() - assert x_transform.input_dims == x.transform.output_dims == 1 - assert y_transform.input_dims == y.transform.output_dims == 1 - - Transform.__init__(self) - self._x = x_transform - self._y = y_transform - self.set_children(['_x', '_y']) - - def transform(self, points): - x_points = self._x(points[:, 0]) - y_points = self._y(points[:, 1]) - return npy.vstack((x_points[:, 0:1], y_points[:, 1:2])).transpose() - - class BlendedAffine2D(Affine2DBase, Transform): def __init__(self, x_transform, y_transform): assert x_transform.is_affine() @@ -666,6 +648,10 @@ self._a = a self._b = b self.set_children(['_a', '_b']) + + def __repr__(self): + return "CompositeGenericTransform(%s, %s)" % (self._a, self._b) + __str__ = __repr__ def transform(self, points): return self._b.transform(self._a.transform(points)) @@ -724,10 +710,21 @@ input_dims = 2 output_dims = 2 def transform(self, xy): - return xy * 2 + marray = npy.ma.masked_where(xy <= 0.0, xy * 10.0) + return npy.log10(marray) + + def inverted(self): + return TestInvertLogTransform() + +class TestInvertLogTransform(Transform): + input_dims = 2 + output_dims = 2 + def transform(self, xy): + return npy.power(10, xy) / 10.0 + def inverted(self): - return self + return TestLogTransform() class BboxTransform(Affine2DBase): @@ -825,7 +822,7 @@ assert bbox.bounds == (10, 15, 10, 10) - print npy.asarray(bbox) + assert tuple(npy.asarray(bbox).flatten()) == (10, 15, 20, 25) bbox.intervalx = (11, 21) bbox.intervaly = (16, 26) @@ -859,29 +856,35 @@ scale = Affine2D().scale(10, 20) assert scale.to_values() == (10, 0, 0, 20, 0, 0) rotation = Affine2D().rotate_deg(30) - print rotation.to_values() == (0.86602540378443871, 0.49999999999999994, + assert rotation.to_values() == (0.86602540378443871, 0.49999999999999994, -0.49999999999999994, 0.86602540378443871, 0.0, 0.0) points = npy.array([[1,2],[3,4],[5,6],[7,8]], npy.float_) - translated_points = translation(points) + translated_points = translation.transform(points) assert (translated_points == [[11., 22.], [13., 24.], [15., 26.], [17., 28.]]).all() - scaled_points = scale(points) + scaled_points = scale.transform(points) print scaled_points - rotated_points = rotation(points) + rotated_points = rotation.transform(points) print rotated_points - tpoints1 = rotation(translation(scale(points))) + tpoints1 = rotation.transform(translation.transform(scale.transform(points))) trans_sum = scale + translation + rotation - tpoints2 = trans_sum(points) - print tpoints1, tpoints2 - print tpoints1 == tpoints2 + tpoints2 = trans_sum.transform(points) # Need to do some sort of fuzzy comparison here? - # assert (tpoints1 == tpoints2).all() + assert (tpoints1.round() == tpoints2.round()).all() + print points + + comp = TestLogTransform() + Affine2D().rotate_deg(15) + tpoints = comp.transform(points) + itpoints = comp.inverted().transform(tpoints) + print tpoints, itpoints + assert (points.round() == itpoints.round()).all() + # Here are some timing tests points = npy.asarray([(random(), random()) for i in xrange(10000)]) - t = timeit.Timer("trans_sum(points)", "from __main__ import trans_sum, points") + t = timeit.Timer("trans_sum.transform(points)", "from __main__ import trans_sum, points") print "Time to transform 10000 x 10 points:", t.timeit(10) __all__ = ['Transform', 'Affine2D'] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-21 16:52:54
|
Revision: 3872 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3872&view=rev Author: mdboom Date: 2007-09-21 09:52:50 -0700 (Fri, 21 Sep 2007) Log Message: ----------- Further progress on arbitrary transformations -- zooming and panning now works without any log-scale-specific hacks. (Though the underlying model is slightly wrong.) Added graphviz output support for debugging transformation trees. Masked array handling much more robust. Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-21 15:33:18 UTC (rev 3871) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-21 16:52:50 UTC (rev 3872) @@ -637,10 +637,14 @@ # self.viewLim, self.bbox) self.preDataTransform = mtransforms.BboxTransform( self.viewLim, mtransforms.Bbox.unit()) - self.dataTransform = mtransforms.TestLogTransform() - # self.dataTransform = mtransforms.Affine2D().scale(1.5) +# self.dataTransform = mtransforms.TestPolarTransform() +# self.dataTransform = mtransforms.blended_transform_factory( +# mtransforms.TestLogTransform(), +# mtransforms.Affine2D()) + self.dataTransform = mtransforms.Affine2D() self.transData = self.preDataTransform + self.dataTransform + mtransforms.BboxTransform( mtransforms.Bbox.unit(), self.bbox) + self.transData.make_graphviz(open("trans.dot", "w")) def get_position(self, original=False): @@ -1523,7 +1527,7 @@ 'return the xaxis scale string: log or linear' # MGDTODO # return self.scaled[self.transData.get_funcx().get_type()] - return 'linear' + return 'log' def set_xscale(self, value, basex = 10, subsx=None): """ Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-09-21 15:33:18 UTC (rev 3871) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-09-21 16:52:50 UTC (rev 3872) @@ -1655,60 +1655,30 @@ #multiple button can get pressed during motion... if self._button_pressed==1: inverse = trans.inverted() - lastx, lasty = inverse.transform_point((lastx, lasty)) - x, y = inverse.transform_point( (event.x, event.y) ) - if a.get_xscale()=='log': - dx=1-lastx/x - else: - dx=x-lastx - if a.get_yscale()=='log': - dy=1-lasty/y - else: - dy=y-lasty - - dx,dy=format_deltas(event,dx,dy) - - if a.get_xscale()=='log': - xmin *= 1-dx - xmax *= 1-dx - else: - xmin -= dx - xmax -= dx - if a.get_yscale()=='log': - ymin *= 1-dy - ymax *= 1-dy - else: - ymin -= dy - ymax -= dy + dx, dy = event.x - lastx, event.y - lasty + dx, dy = format_deltas(event, dx, dy) + delta = npy.array([[dx, dy], [dx, dy]], npy.float_) + bbox = transforms.Bbox(a.bbox.get_points() - delta) + result = bbox.transformed(inverse) elif self._button_pressed==3: try: + inverse = trans.inverted() dx=(lastx-event.x)/float(a.bbox.width) dy=(lasty-event.y)/float(a.bbox.height) - dx,dy=format_deltas(event,dx,dy) - if a.get_aspect() != 'auto': - dx = 0.5*(dx + dy) - dy = dx - alphax = pow(10.0,dx) - alphay = pow(10.0,dy)#use logscaling, avoid singularities and smother scaling... - inverse = trans.inverted() - lastx, lasty = inverse.transform_point( (lastx, lasty) ) - if a.get_xscale()=='log': - xmin = lastx*(xmin/lastx)**alphax - xmax = lastx*(xmax/lastx)**alphax - else: - xmin = lastx+alphax*(xmin-lastx) - xmax = lastx+alphax*(xmax-lastx) - if a.get_yscale()=='log': - ymin = lasty*(ymin/lasty)**alphay - ymax = lasty*(ymax/lasty)**alphay - else: - ymin = lasty+alphay*(ymin-lasty) - ymax = lasty+alphay*(ymax-lasty) + alphax = pow(10.0, dx) + alphay = pow(10.0, dy) + # MGDTODO: Make better use of numpy + lastx, lasty = inverse.transform_point((lastx, lasty)) + xmin = (lastx + alphax * (xmin - lastx)) + xmax = (lastx + alphax * (xmax - lastx)) + ymin = (lasty + alphay * (ymin - lasty)) + ymax = (lasty + alphay * (ymax - lasty)) + result = transforms.Bbox.from_lbrt(xmin, ymin, xmax, ymax) except OverflowError: warnings.warn('Overflow while panning') return - a.set_xlim(xmin, xmax) - a.set_ylim(ymin, ymax) + a.set_xlim(*result.intervalx) + a.set_ylim(*result.intervaly) self.dynamic_update() Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-21 15:33:18 UTC (rev 3871) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-21 16:52:50 UTC (rev 3872) @@ -25,6 +25,53 @@ (TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN, CARETLEFT, CARETRIGHT, CARETUP, CARETDOWN) = range(8) +def unmasked_index_ranges(mask, compressed = True): + ''' + Calculate the good data ranges in a masked 1-D npy.array, based on mask. + + Returns Nx2 npy.array with each row the start and stop indices + for slices of the compressed npy.array corresponding to each of N + uninterrupted runs of unmasked values. + If optional argument compressed is False, it returns the + start and stop indices into the original npy.array, not the + compressed npy.array. + Returns None if there are no unmasked values. + + Example: + + y = ma.array(npy.arange(5), mask = [0,0,1,0,0]) + #ii = unmasked_index_ranges(y.mask()) + ii = unmasked_index_ranges(ma.getmask(y)) + # returns [[0,2,] [2,4,]] + + y.compressed().filled()[ii[1,0]:ii[1,1]] + # returns npy.array [3,4,] + # (The 'filled()' method converts the masked npy.array to a numerix npy.array.) + + #i0, i1 = unmasked_index_ranges(y.mask(), compressed=False) + i0, i1 = unmasked_index_ranges(ma.getmask(y), compressed=False) + # returns [[0,3,] [2,5,]] + + y.filled()[ii[1,0]:ii[1,1]] + # returns npy.array [3,4,] + + ''' + m = npy.concatenate(((1,), mask, (1,))) + indices = npy.arange(len(mask) + 1) + mdif = m[1:] - m[:-1] + i0 = npy.compress(mdif == -1, indices) + i1 = npy.compress(mdif == 1, indices) + assert len(i0) == len(i1) + if len(i1) == 0: + return None + if not compressed: + return npy.concatenate((i0[:, npy.newaxis], i1[:, npy.newaxis]), axis=1) + seglengths = i1 - i0 + breakpoints = npy.cumsum(seglengths) + ic0 = npy.concatenate(((0,), breakpoints[:-1])) + ic1 = breakpoints + return npy.concatenate((ic0[:, npy.newaxis], ic1[:, npy.newaxis]), axis=1) + def segment_hits(cx,cy,x,y,radius): """Determine if any line segments are within radius of a point. Returns the list of line segments that are within that radius. @@ -302,7 +349,7 @@ self._picker = p def get_window_extent(self, renderer): - xy = self.get_transform()(self._xy) + xy = self.get_transform().transform(self._xy) x = xy[:, 0] y = xy[:, 1] @@ -343,9 +390,6 @@ self._yorig = y self.recache() - # MGDTODO: Masked data arrays are broken - _masked_array_to_path_code_mapping = npy.array( - [Path.LINETO, Path.MOVETO, Path.MOVETO], Path.code_type) def recache(self): #if self.axes is None: print 'recache no axes' #else: print 'recache units', self.axes.xaxis.units, self.axes.yaxis.units @@ -363,24 +407,15 @@ if len(x) != len(y): raise RuntimeError('xdata and ydata must be the same length') - self._xy = npy.vstack((npy.asarray(x, npy.float_), - npy.asarray(y, npy.float_))).transpose() + x = x.reshape((len(x), 1)) + y = y.reshape((len(y), 1)) + + self._xy = ma.concatenate((x, y), 1) self._x = self._xy[:, 0] # just a view self._y = self._xy[:, 1] # just a view self._logcache = None - - mx = ma.getmask(x) - my = ma.getmask(y) - mask = ma.mask_or(mx, my) - codes = None - if mask is not ma.nomask: - m = npy.concatenate(((1,), mask, (1,))) - mdif = m[1:] - m[:-1] - mdif = npy.maximum((mdif[:-1] * -2), mask) - codes = npy.take( - self._masked_array_to_path_code_mapping, - mdif) - self._path = Path(self._xy, codes, closed=False) + # Masked arrays are now handled by the Path class itself + self._path = Path(self._xy, closed=False) # MGDTODO: If _draw_steps is removed, remove the following line also self._step_path = None Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-09-21 15:33:18 UTC (rev 3871) +++ branches/transforms/lib/matplotlib/path.py 2007-09-21 16:52:50 UTC (rev 3872) @@ -1,4 +1,5 @@ import numpy as npy +from numpy import ma as ma class Path(object): # Path codes @@ -21,10 +22,8 @@ code_type = npy.uint8 def __init__(self, vertices, codes=None, closed=True): - vertices = npy.asarray(vertices, npy.float_) - assert vertices.ndim == 2 - assert vertices.shape[1] == 2 - + vertices = ma.asarray(vertices, npy.float_) + if codes is None: if closed: codes = self.LINETO * npy.ones( @@ -41,10 +40,27 @@ assert codes.ndim == 1 assert len(codes) == len(vertices) + # The path being passed in may have masked values. However, + # the backends are not expected to deal with masked arrays, so + # we must remove them from the array (using compressed), and + # add MOVETO commands to the codes array accordingly. + mask = ma.getmask(vertices) + if mask is not ma.nomask: + mask1d = ma.mask_or(mask[:, 0], mask[:, 1]) + vertices = ma.compress(npy.invert(mask1d), vertices, 0) + codes = npy.where(npy.concatenate((mask1d[-1:], mask1d[:-1])), + self.MOVETO, codes) + codes = ma.masked_array(codes, mask=mask1d).compressed() + codes = npy.asarray(codes, self.code_type) + + vertices = npy.asarray(vertices, npy.float_) + + assert vertices.ndim == 2 + assert vertices.shape[1] == 2 + assert codes.ndim == 1 + self._codes = codes self._vertices = vertices - - assert self._codes.ndim == 1 def __repr__(self): return "Path(%s, %s)" % (self.vertices, self.codes) @@ -91,10 +107,11 @@ def unit_regular_polygon(cls, numVertices): path = cls._unit_regular_polygons.get(numVertices) if path is None: - theta = 2*npy.pi/numVertices * npy.arange(numVertices) - # This is to make sure the polygon always "points-up" + 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 - verts = npy.vstack((npy.cos(theta), npy.sin(theta))).transpose() + verts = npy.concatenate((npy.cos(theta), npy.sin(theta))) path = Path(verts) cls._unit_regular_polygons[numVertices] = path return path Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-21 15:33:18 UTC (rev 3871) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-21 16:52:50 UTC (rev 3872) @@ -5,6 +5,7 @@ """ import numpy as npy +from numpy import ma as ma from numpy.linalg import inv from sets import Set @@ -19,6 +20,7 @@ class TransformNode(object): def __init__(self): self._parents = Set() + self._children = [] def invalidate(self): self._do_invalidation() @@ -33,7 +35,34 @@ getattr(self, child)._parents.add(self) self._children = children + def make_graphviz(self, fobj): + def recurse(root): + fobj.write('%s [label="%s"];\n' % + (hash(root), root.__class__.__name__)) + if isinstance(root, Affine2DBase): + fobj.write('%s [style=filled, color=".7 .7 .9"];\n' % + hash(root)) + elif isinstance(root, BboxBase): + fobj.write('%s [style=filled, color=".9 .9 .7"];\n' % + hash(root)) + for child_name in root._children: + child = getattr(root, child_name) + fobj.write("%s -> %s;\n" % ( + hash(root), + hash(child))) + recurse(child) + fobj.write("digraph G {\n") + recurse(self) + fobj.write("}\n") + + def is_affine(self): + return isinstance(self, Affine2DBase) + + def is_bbox(self): + return isinstance(self, BboxBase) + + class BboxBase(TransformNode): ''' This is the read-only part of a bounding-box @@ -169,12 +198,6 @@ return Bbox(points) from_lbrt = staticmethod(from_lbrt) - def __copy__(self): - return Bbox(self._points.copy()) - - def __deepcopy__(self, memo): - return Bbox(self._points.copy()) - def __cmp__(self, other): # MGDTODO: Totally suboptimal if isinstance(other, Bbox) and (self._points == other._points).all(): @@ -274,6 +297,8 @@ """ Return the Bbox that bounds all bboxes """ + # MGDTODO: There's got to be a way to utilize numpy here + # to make this faster... assert(len(bboxes)) if len(bboxes) == 1: @@ -297,7 +322,7 @@ class TransformedBbox(BboxBase): def __init__(self, bbox, transform): - assert isinstance(bbox, Bbox) + assert bbox.is_bbox() assert isinstance(transform, Transform) BboxBase.__init__(self) @@ -353,9 +378,6 @@ def is_separable(self): return False - def is_affine(self): - return False - class Affine2DBase(Transform): input_dims = 2 @@ -416,9 +438,9 @@ # print "".join(traceback.format_stack()) # print points mtx = self.get_matrix() - points = npy.asarray(points, npy.float_) + points = ma.asarray(points, npy.float_) points = points.transpose() - points = npy.dot(mtx[0:2, 0:2], points) + points = ma.dot(mtx[0:2, 0:2], points) points = points + mtx[0:2, 2:] return points.transpose() @@ -437,9 +459,6 @@ mtx = self.get_matrix() return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 - def is_affine(self): - return True - class Affine2D(Affine2DBase): def __init__(self, matrix = None): @@ -469,12 +488,6 @@ return 0 return -1 - def __copy__(self): - return Affine2D(self._mtx.copy()) - - def __deepcopy__(self, memo): - return Affine2D(self._mtx.copy()) - #@staticmethod def from_values(a, b, c, d, e, f): return Affine2D(Affine2D.matrix_from_values(a, b, c, d, e, f)) @@ -542,9 +555,31 @@ mtx = self.get_matrix() return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 - def is_affine(self): - return True +class IdentityTransform(Affine2DBase): + """ + A special class that does the identity transform quickly. + """ + _mtx = npy.identity(3) + + def __cmp__(self, other): + if (isinstance(other, Affine2D) and + (other == IDENTITY)): + return 0 + return -1 + + def get_matrix(self): + return _mtx + + def transform(self, points): + return points + + def transform_without_affine(self, points): + return points, self + + def inverted(self): + return self + IDENTITY = Affine2D() class BlendedGenericTransform(Transform): @@ -553,10 +588,9 @@ def __init__(self, x_transform, y_transform): # Here we ask: "Does it blend?" - assert x_transform.is_separable() - assert y_transform.is_separable() - assert x_transform.input_dims == x_transform.output_dims == 2 - assert y_transform.input_dims == y_transform.output_dims == 2 + # MGDTODO: Turn these checks back on + # assert x_transform.is_separable() + # assert y_transform.is_separable() Transform.__init__(self) self._x = x_transform @@ -576,16 +610,18 @@ return self._x(points) if x.input_dims == 2: - x_points = x.transform(points)[:, 0] + x_points = x.transform(points)[:, 0:1] else: x_points = x.transform(points[:, 0]) - + x_points = x_points.reshape((len(x_points), 1)) + if y.input_dims == 2: - y_points = y.transform(points)[:, 1] + y_points = y.transform(points)[:, 1:] else: y_points = y.transform(points[:, 1]) + y_points = y_points.reshape((len(y_points), 1)) - return npy.vstack((x_points, y_points)).transpose() + return ma.concatenate((x_points, y_points), 1) def inverted(self): return BlendedGenericTransform(self._x.inverted(), self._y.inverted()) @@ -598,6 +634,9 @@ def __init__(self, x_transform, y_transform): assert x_transform.is_affine() assert y_transform.is_affine() + # MGDTODO: Turn these checks back on + # assert x_transform.is_separable() + # assert y_transform.is_separable() Transform.__init__(self) self._x = x_transform self._y = y_transform @@ -649,6 +688,8 @@ self._b = b self.set_children(['_a', '_b']) + self.take_shortcut = b.is_affine() + def __repr__(self): return "CompositeGenericTransform(%s, %s)" % (self._a, self._b) __str__ = __repr__ @@ -656,11 +697,15 @@ def transform(self, points): return self._b.transform(self._a.transform(points)) + def transform_without_affine(self, points): + if self.take_shortcut: + return self._a.transform(points), self._b + return self.transform(points), IDENTITY + def inverted(self): return CompositeGenericTransform(self._b.inverted(), self._a.inverted()) def is_separable(self): - return True return self._a.is_separable() and self._b.is_separable() @@ -702,35 +747,81 @@ output_dims = 1 def transform(self, a): - m = npy.ma.masked_where(a < 0, a) + m = ma.masked_where(a < 0, a) return npy.log10(m) class TestLogTransform(Transform): - input_dims = 2 - output_dims = 2 + input_dims = 1 + output_dims = 1 def transform(self, xy): - marray = npy.ma.masked_where(xy <= 0.0, xy * 10.0) - return npy.log10(marray) + marray = ma.masked_where(xy <= 0.0, xy * 10.0) + return (npy.log10(marray) * 0.5) + 0.5 def inverted(self): return TestInvertLogTransform() + def is_separable(self): + return True + class TestInvertLogTransform(Transform): - input_dims = 2 - output_dims = 2 + input_dims = 1 + output_dims = 1 def transform(self, xy): - return npy.power(10, xy) / 10.0 + return ma.power(10, (xy - 0.5) * 2.0) / 10.0 def inverted(self): return TestLogTransform() + def is_separable(self): + return True + + +class TestPolarTransform(Transform): + input_dims = 2 + output_dims = 2 + + def transform(self, xy): + debug = len(xy) > 4 + x = xy[:, 0:1] + y = xy[:, 1:] + x, y = ((y * npy.cos(x)) + 1.0) * 0.5, ((y * npy.sin(x)) + 1.0) * 0.5 + if debug: + print npy.min(xy[:, 0:1]), npy.max(xy[:, 0:1]), npy.min(xy[:, 1:]), npy.max(xy[:, 1:]) + print x.min(), x.max(), y.min(), y.max() + return ma.concatenate((x, y), 1) + + def inverted(self): + return TestInvertPolarTransform() + def is_separable(self): + return False + + +class TestInvertPolarTransform(Transform): + input_dims = 2 + output_dims = 2 + + def transform(self, xy): + x = xy[:, 0:1] + y = xy[:, 1:] + r = ma.sqrt(ma.power(x, 2) + ma.power(y, 2)) + theta = ma.arccos(x / r) + theta = ma.where(y < 0, 2 * npy.pi - theta, theta) + return ma.concatenate((theta / (npy.pi * 2), r), 1) + + def inverted(self): + return TestInvertPolarTransform() + + def is_separable(self): + return False + + class BboxTransform(Affine2DBase): def __init__(self, boxin, boxout): - assert isinstance(boxin, BboxBase) - assert isinstance(boxout, BboxBase) + assert boxin.is_bbox() + assert boxout.is_bbox() Affine2DBase.__init__(self) self._boxin = boxin This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-24 16:53:40
|
Revision: 3884 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3884&view=rev Author: mdboom Date: 2007-09-24 09:53:38 -0700 (Mon, 24 Sep 2007) Log Message: ----------- More progress. (Kind of a broken mess at the moment.) Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/ticker.py branches/transforms/lib/matplotlib/transforms.py Added Paths: ----------- branches/transforms/lib/matplotlib/scale.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-24 15:11:58 UTC (rev 3883) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-24 16:53:38 UTC (rev 3884) @@ -25,6 +25,7 @@ from matplotlib import patches as mpatches from matplotlib import pbox as mpbox from matplotlib import quiver as mquiver +from matplotlib import scale as mscale from matplotlib import table as mtable from matplotlib import text as mtext from matplotlib import ticker as mticker @@ -449,7 +450,6 @@ **kwargs ): """ - Build an Axes instance in Figure with rect=[left, bottom, width,height in Figure coords @@ -467,8 +467,8 @@ navigate: True|False navigate_mode: the navigation toolbar button status: 'PAN', 'ZOOM', or None position: [left, bottom, width,height in Figure coords - sharex : an Axes instance to share the x-axis with - sharey : an Axes instance to share the y-axis with + sharex: an Axes instance to share the x-axis with + sharey: an Axes instance to share the y-axis with title: the title string visible: a boolean - whether the axes is visible xlabel: the xlabel @@ -491,7 +491,7 @@ self.set_adjustable('box') self.set_anchor('C') - # must be set before set_figure + # MGDTODO: Check that the axes being shared are scalable self._sharex = sharex self._sharey = sharey if sharex is not None: @@ -508,7 +508,7 @@ # this call may differ for non-sep axes, eg polar self._init_axis() - + if axisbg is None: axisbg = rcParams['axes.facecolor'] self._axisbg = axisbg self._frameon = frameon @@ -545,8 +545,8 @@ "move this out of __init__ because non-separable axes don't use it" self.xaxis = maxis.XAxis(self) self.yaxis = maxis.YAxis(self) + self._update_transAxisXY() - def sharex_foreign(self, axforeign): """ You can share your x-axis view limits with another Axes in the @@ -627,26 +627,17 @@ set the dataLim and viewLim BBox attributes and the transData and transAxes Transformation attributes """ - self.viewLim = mtransforms.Bbox.unit() self.dataLim = mtransforms.Bbox.unit() - + self.viewLim = mtransforms.Bbox.unit() self.transAxes = mtransforms.BboxTransform( mtransforms.Bbox.unit(), self.bbox) - # self.set_transform(self.transAxes) -# self.transData = mtransforms.BboxTransform( -# self.viewLim, self.bbox) - self.preDataTransform = mtransforms.BboxTransform( - self.viewLim, mtransforms.Bbox.unit()) -# self.dataTransform = mtransforms.TestPolarTransform() -# self.dataTransform = mtransforms.blended_transform_factory( -# mtransforms.TestLogTransform(), -# mtransforms.Affine2D()) - self.dataTransform = mtransforms.Affine2D() - self.transData = self.preDataTransform + self.dataTransform + mtransforms.BboxTransform( - mtransforms.Bbox.unit(), self.bbox) - self.transData.make_graphviz(open("trans.dot", "w")) + self.transAxisXY = mtransforms.TransformWrapper() + self.transData = self.transAxisXY + self.transAxes + + def _update_transAxisXY(self): + self.transAxisXY.set(mtransforms.blended_transform_factory( + self.xaxis.get_transform(), self.yaxis.get_transform())) - def get_position(self, original=False): 'Return the axes rectangle left, bottom, width, height' if original: @@ -1525,11 +1516,9 @@ def get_xscale(self): 'return the xaxis scale string: log or linear' - # MGDTODO - # return self.scaled[self.transData.get_funcx().get_type()] - return 'log' + return self.xaxis.get_scale() - def set_xscale(self, value, basex = 10, subsx=None): + def set_xscale(self, value, **kwargs): """ SET_XSCALE(value, basex=10, subsx=None) @@ -1547,27 +1536,9 @@ ACCEPTS: ['log' | 'linear' ] """ - - #if subsx is None: subsx = range(2, basex) - assert(value.lower() in ('log', 'linear', )) - if value == 'log': - # MGDTODO -# self.xaxis.set_major_locator(mticker.LogLocator(basex)) -# self.xaxis.set_major_formatter(mticker.LogFormatterMathtext(basex)) -# self.xaxis.set_minor_locator(mticker.LogLocator(basex,subsx)) -# self.transData.get_funcx().set_type(mtrans.LOG10) -# minx, maxx = self.get_xlim() -# if min(minx, maxx)<=0: -# self.autoscale_view() - pass - elif value == 'linear': - self.xaxis.set_major_locator(mticker.AutoLocator()) - self.xaxis.set_major_formatter(mticker.ScalarFormatter()) - self.xaxis.set_minor_locator(mticker.NullLocator()) - self.xaxis.set_minor_formatter(mticker.NullFormatter()) - # self.transData.get_funcx().set_type( mtrans.IDENTITY ) - self.transData.get_funcx().set_type( 0 ) # MGDTODO - + self.xaxis.set_scale(value, **kwargs) + self._update_transAxisXY() + def get_xticks(self): 'Return the x ticks as a list of locations' return self.xaxis.get_ticklocs() @@ -1655,9 +1626,8 @@ def get_yscale(self): 'return the yaxis scale string: log or linear' - # return self.scaled[self.transData.get_funcy().get_type()] - return 'linear' - + return self.yaxis.get_scale() + def set_yscale(self, value, basey=10, subsy=None): """ SET_YSCALE(value, basey=10, subsy=None) @@ -1676,29 +1646,9 @@ ACCEPTS: ['log' | 'linear'] """ - - #if subsy is None: subsy = range(2, basey) - assert(value.lower() in ('log', 'linear', )) - - if value == 'log': - # MGDTODO -# self.yaxis.set_major_locator(mticker.LogLocator(basey)) -# self.yaxis.set_major_formatter(mticker.LogFormatterMathtext(basey)) -# self.yaxis.set_minor_locator(mticker.LogLocator(basey,subsy)) -# self.transData.get_funcy().set_type(mtrans.LOG10) -# miny, maxy = self.get_ylim() -# if min(miny, maxy)<=0: -# self.autoscale_view() - pass - - elif value == 'linear': - self.yaxis.set_major_locator(mticker.AutoLocator()) - self.yaxis.set_major_formatter(mticker.ScalarFormatter()) - self.yaxis.set_minor_locator(mticker.NullLocator()) - self.yaxis.set_minor_formatter(mticker.NullFormatter()) - # self.transData.get_funcy().set_type( mtrans.IDENTITY ) MGDTODO - self.transData.get_funcy().set_type( 0 ) - + self.yaxis.set_scale(value, basey, subsy) + self._update_transAxisXY() + def get_yticks(self): 'Return the y ticks as a list of locations' return self.yaxis.get_ticklocs() Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-24 15:11:58 UTC (rev 3883) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-24 16:53:38 UTC (rev 3884) @@ -12,14 +12,15 @@ from lines import Line2D, TICKLEFT, TICKRIGHT, TICKUP, TICKDOWN from matplotlib import rcParams from patches import bbox_artist -from ticker import NullFormatter, FixedFormatter, ScalarFormatter, LogFormatter +from ticker import NullFormatter, FixedFormatter, ScalarFormatter, LogFormatter, LogFormatterMathtext from ticker import NullLocator, FixedLocator, LinearLocator, LogLocator, AutoLocator from font_manager import FontProperties from text import Text, TextWithDash, _process_text_args from transforms import Affine2D, Bbox, blended_transform_factory, interval_contains, \ - interval_contains_open + interval_contains_open, IntervalTransform from patches import bbox_artist +from scale import LinearScale, LogScale import matplotlib.units as units #import pdb @@ -479,7 +480,7 @@ """ LABELPAD = 5 OFFSETTEXTPAD = 3 - + def __str__(self): return str(self.__class__).split('.')[-1] \ + "(%d,%d)"%self.axes.transAxes.xy_tup((0,0)) @@ -507,9 +508,38 @@ self.majorTicks = [] self.minorTicks = [] self.pickradius = pickradius - + self._transform = LinearScale(self.axes.viewLim, self.axis).get_transform() + self._scale = 'linear' + self.cla() + def get_transform(self): + return self._transform + + def get_scale(self): + return self._scale + + def set_scale(self, value, base=10, subs=None): + # MGDTODO: Move these settings (ticker etc.) into the scale class itself + value = value.lower() + assert value.lower() in ('log', 'linear') + if value == 'linear': + self.set_major_locator(AutoLocator()) + self.set_major_formatter(ScalarFormatter()) + self.set_minor_locator(NullLocator()) + self.set_minor_formatter(NullFormatter()) + self._transform = LinearScale(self.axes.viewLim, self.axis).get_transform() + elif value == 'log': + self.set_major_locator(LogLocator(base)) + self.set_major_formatter(LogFormatterMathtext(base)) + self.set_minor_locator(LogLocator(base,subs)) + # MGDTODO: Pass base along + self._transform = LogScale(self.axes.viewLim, self.axis).get_transform() + miny, maxy = getattr(self.axes.viewLim, 'interval' + self.axis) + if min(miny, maxy)<=0: + self.axes.autoscale_view() + self._scale = value + def get_children(self): children = [self.label] children.extend(self.majorTicks) @@ -595,7 +625,7 @@ for tick, loc, label in zip(minorTicks, minorLocs, minorLabels): if tick is None: continue - if not interval.contains(loc): continue + if not interval_contains(interval, loc): continue #if seen.has_key(loc): continue tick.update_position(loc) tick.set_label1(label) @@ -952,7 +982,8 @@ class XAxis(Axis): __name__ = 'xaxis' - + axis = 'x' + def contains(self,mouseevent): """Test whether the mouse event occured in the x axis. """ @@ -1134,7 +1165,8 @@ class YAxis(Axis): __name__ = 'yaxis' - + axis = 'y' + def contains(self,mouseevent): """Test whether the mouse event occurred in the y axis. Added: branches/transforms/lib/matplotlib/scale.py =================================================================== --- branches/transforms/lib/matplotlib/scale.py (rev 0) +++ branches/transforms/lib/matplotlib/scale.py 2007-09-24 16:53:38 UTC (rev 3884) @@ -0,0 +1,78 @@ +import numpy as npy +from numpy import ma + +from transforms import Affine1D, IntervalTransform, Transform + +class ScaleBase(object): + pass + +class LinearScale(ScaleBase): + def __init__(self, viewLim, direction): + direction = 'interval' + direction + self._transform = IntervalTransform(viewLim, direction) + + def get_transform(self): + return self._transform + +class LogScale(ScaleBase): + class LogTransform(Transform): + input_dims = 1 + output_dims = 1 + def __init__(self, viewLim, direction, base): + Transform.__init__(self) + self._base = base + self._viewLim = viewLim + self._direction = direction + + def transform(self, a): + a, affine = self.transform_without_affine(a) + return affine.transform(a) + + def transform_without_affine(self, a): + # MGDTODO: Support different bases + base = self._base + marray = ma.masked_where(a <= 0.0, a) + marray = npy.log10(marray) + minimum, maximum = getattr(self._viewLim, self._direction) + minimum, maximum = npy.log10([minimum, maximum]) + print marray + print Affine1D.from_values(maximum - minimum, minimum).inverted() + print minimum, maximum + return marray, Affine1D.from_values(maximum - minimum, minimum).inverted() + + def inverted(self): + return LogScale.InvertedLogTransform(self._viewLim, self._direction, self._base) + + class InvertedLogTransform(Transform): + input_dims = 1 + output_dims = 1 + def __init__(self, viewLim, direction, base): + Transform.__init__(self) + self._base = base + self._viewLim = viewLim + self._direction = direction + + def transform(self, a): + minimum, maximum = getattr(self._viewLim, self._direction) + Affine1D.from_values(maximum - minimum, minimum).transform(a) + return ma.power(10.0, a) + + def inverted(self): + return LogScale.LogTransform(self._viewLim, self._direction, self._base) + + def __init__(self, viewLim, direction, base=10): + direction = 'interval' + direction + self._transform = self.LogTransform(viewLim, direction, base) + + def get_transform(self): + return self._transform + + +_scale_mapping = { + 'linear': LinearScale, + 'log': LogScale + } +def scale_factory(scale, viewLim, direction): + if scale is None: + scale = 'linear' + return _scale_mapping[scale](viewLim, direction) Modified: branches/transforms/lib/matplotlib/ticker.py =================================================================== --- branches/transforms/lib/matplotlib/ticker.py 2007-09-24 15:11:58 UTC (rev 3883) +++ branches/transforms/lib/matplotlib/ticker.py 2007-09-24 16:53:38 UTC (rev 3884) @@ -500,8 +500,6 @@ def __call__(self, x, pos=None): 'Return the format for tick val x at position pos' - self.verify_intervals() - b = self._base # only label the decades fx = math.log(x)/math.log(b) @@ -890,10 +888,9 @@ def __call__(self): 'Return the locations of the ticks' - self.verify_intervals() b=self._base - vmin, vmax = self.viewInterval.get_bounds() + vmin, vmax = self.axis.get_view_interval() vmin = math.log(vmin)/math.log(b) vmax = math.log(vmax)/math.log(b) @@ -922,16 +919,16 @@ def autoscale(self): 'Try to choose the view limits intelligently' - self.verify_intervals() - - vmin, vmax = self.dataInterval.get_bounds() + vmin, vmax = self.axis.get_view_interval() if vmax<vmin: vmin, vmax = vmax, vmin - minpos = self.dataInterval.minpos() +# minpos = self.dataInterval.minpos() - if minpos<=0: - raise RuntimeError('No positive data to plot') +# if minpos<=0: +# raise RuntimeError('No positive data to plot') + + minpos = max(vmin, 0.00001) #MGDTODO if vmin<=0: vmin = minpos if not is_decade(vmin,self._base): vmin = decade_down(vmin,self._base) Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-24 15:11:58 UTC (rev 3883) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-24 16:53:38 UTC (rev 3884) @@ -9,7 +9,7 @@ from numpy.linalg import inv from sets import Set -DEBUG = True +DEBUG = False # MGDTODO: This creates a ton of cyclical references. We may want to # consider using weak references @@ -61,8 +61,8 @@ def is_bbox(self): return isinstance(self, BboxBase) + - class BboxBase(TransformNode): ''' This is the read-only part of a bounding-box @@ -378,11 +378,30 @@ def is_separable(self): return False - -class Affine2DBase(Transform): + +class TransformWrapper(Transform): input_dims = 2 output_dims = 2 + + def set(self, child): + self.child = child + self.child._parents.add(self) + self.invalidate() + def transform(self, points): + return self.child.transform(points) + + def transform_without_affine(points): + return self.child.transform_without_affine(points) + + def inverted(self): + return self.child.inverted() + + def is_separable(self): + return self.child.is_separable() + + +class AffineBase(Transform): def __init__(self): Transform.__init__(self) self._inverted = None @@ -400,11 +419,173 @@ #@staticmethod def concat(a, b): - return Affine2D(Affine2D._concat(a.get_matrix(), b.get_matrix())) + return Affine1D(Affine1D._concat(a.get_matrix(), b.get_matrix())) concat = staticmethod(concat) + + def get_matrix(self): + raise NotImplementedError() + + def transform_without_affine(self, points): + # MGDTODO: Should we copy the points here? I'd like to avoid it, + # if possible + return points, self + +class Affine1DBase(AffineBase): + input_dims = 1 + output_dims = 1 + + def __init__(self): + AffineBase.__init__(self) + + def __array__(self, *args, **kwargs): + return self.get_matrix() + def to_values(self): mtx = self.get_matrix() + return tuple(mtx[0]) + + #@staticmethod + def matrix_from_values(a, b): + affine = npy.zeros((2, 2), npy.float_) + affine[0, :] = (a, b) + affine[1, 1] = 1 + return affine + matrix_from_values = staticmethod(matrix_from_values) + + def transform(self, values): + """ + Applies the transformation to an array of values and + returns the result. + """ + # MGDTODO: The major speed trap here is just converting to + # the points to an array in the first place. If we can use + # more arrays upstream, that should help here. +# if not isinstance(points, npy.ndarray): +# import traceback +# print '-' * 60 +# print 'A non-numpy array was passed in for transformation. Please ' +# print 'correct this.' +# print "".join(traceback.format_stack()) +# print points + mtx = self.get_matrix() + points = ma.asarray(values, npy.float_) + return points * mtx[0,0] + mtx[0,1] + + def is_separable(self): + return True + + def inverted(self): + if self._inverted is None: + mtx = self.get_matrix() + self._inverted = Affine1D(inv(mtx)) + return self._inverted + + +class Affine1D(Affine1DBase): + def __init__(self, matrix = None): + """ + Initialize an Affine transform from a 2x2 numpy float array. + + a b + 0 1 + """ + Affine1DBase.__init__(self) + if matrix is None: + matrix = npy.identity(2) + else: + matrix = npy.asarray(matrix, npy.float_) + assert matrix.shape == (2, 2) + self._mtx = matrix + + def __repr__(self): + return "Affine1D(%s)" % repr(self._mtx) + __str__ = __repr__ + + def __cmp__(self, other): + if (isinstance(other, Affine1D) and + (self.get_matrix() == other.get_matrix()).all()): + return 0 + return -1 + + #@staticmethod + def from_values(a, b): + return Affine1D(Affine1D.matrix_from_values(a, b)) + from_values = staticmethod(from_values) + + def get_matrix(self): + return self._mtx + + def set_matrix(self, mtx): + self._mtx = mtx + self.invalidate() + + def set(self, other): + self._mtx = other.get_matrix() + self.invalidate() + + #@staticmethod + def identity(): + return Affine1D(npy.identity(2)) + identity = staticmethod(identity) + + def clear(self): + self._mtx = npy.identity(2) + self.invalidate() + return self + + def translate(self, t): + self._mtx[0, 1] += t + self.invalidate() + return self + + def scale(self, s): + self._mtx[0, 0] *= s + self.invalidate() + return self + + def is_separable(self): + mtx = self.get_matrix() + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + + +class IntervalTransform(Affine1DBase): + def __init__(self, bbox, direction): + Affine1DBase.__init__(self) + self._bbox = bbox + self._direction = direction + self.set_children(['_bbox']) + self._mtx = None + + def __repr__(self): + return "IntervalTransform(%s)" % (getattr(self._bbox, self._direction)) + __str__ = __repr__ + + def _do_invalidation(self): + print "IntervalTransform.invalidation", self._bbox + self._mtx = None + Affine1DBase._do_invalidation(self) + + def get_matrix(self): + if self._mtx is None: + min, max = getattr(self._bbox, self._direction) + self._mtx = inv(npy.array([[max - min, min], + [0.0, 1.0]], npy.float_)) + return self._mtx + + +class Affine2DBase(AffineBase): + input_dims = 2 + output_dims = 2 + + def __init__(self): + AffineBase.__init__(self) + + def __array__(self, *args, **kwargs): + return self.get_matrix() + + def to_values(self): + mtx = self.get_matrix() return tuple(mtx[:2].swapaxes(0, 1).flatten()) #@staticmethod @@ -416,9 +597,6 @@ return affine matrix_from_values = staticmethod(matrix_from_values) - def get_matrix(self): - raise NotImplementedError() - def transform(self, points): """ Applies the transformation to an array of 2D points and @@ -444,11 +622,6 @@ points = points + mtx[0:2, 2:] return points.transpose() - def transform_without_affine(self, points): - # MGDTODO: Should we copy the points here? I'd like to avoid it, - # if possible - return points, self - def inverted(self): if self._inverted is None: mtx = self.get_matrix() @@ -476,7 +649,6 @@ matrix = npy.asarray(matrix, npy.float_) assert matrix.shape == (3, 3) self._mtx = matrix - self._inverted = None def __repr__(self): return "Affine2D(%s)" % repr(self._mtx) @@ -545,12 +717,6 @@ self.invalidate() return self - def inverted(self): - if self._inverted is None: - mtx = self.get_matrix() - self._inverted = Affine2D(inv(mtx)) - return self._inverted - def is_separable(self): mtx = self.get_matrix() return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 @@ -602,12 +768,10 @@ __str__ = __repr__ def transform(self, points): - # MGDTODO: Optimize the case where one of these is - # an affine x = self._x y = self._y if x == y and x.input_dims == 2: - return self._x(points) + return self._x.transform(points) if x.input_dims == 2: x_points = x.transform(points)[:, 0:1] @@ -623,13 +787,69 @@ return ma.concatenate((x_points, y_points), 1) + def transform_without_affine(self, points): + x = self._x + y = self._y + if x == y and x.input_dims == 2: + return self._x.transform_without_affine(points) + + if x.input_dims == 2: + x_points, x_affine = x.transform_without_affine(points) + x_points = x_points[:, 0:1] + else: + x_points, x_affine = x.transform_without_affine(points[:, 0]) + x_points = x_points.reshape((len(x_points), 1)) + + if y.input_dims == 2: + y_points, y_affine = y.transform_without_affine(points) + y_points = y_points[:, 1:] + else: + y_points, y_affine = y.transform_without_affine(points[:, 1]) + y_points = y_points.reshape((len(y_points), 1)) + + return ma.concatenate((x_points, y_points), 1), blended_transform_factory(x_affine, y_affine) + def inverted(self): return BlendedGenericTransform(self._x.inverted(), self._y.inverted()) def is_separable(self): return True + + +class BlendedAffine1D(Affine2DBase, Transform): + def __init__(self, x_transform, y_transform): + assert isinstance(x_transform, Affine1DBase) + assert isinstance(y_transform, Affine1DBase) + + Transform.__init__(self) + self._x = x_transform + self._y = y_transform + self.set_children(['_x', '_y']) + + Affine2DBase.__init__(self) + self._mtx = None + + def __repr__(self): + return "BlendedAffine1D(%s,%s)" % (self._x, self._y) + __str__ = __repr__ + + def _do_invalidation(self): + self._mtx = None + Affine2DBase._do_invalidation(self) + + def is_separable(self): + return True + + def get_matrix(self): + if self._mtx is None: + x_mtx = self._x.get_matrix() + y_mtx = self._y.get_matrix() + self._mtx = npy.array([[x_mtx[0, 0], 0.0, x_mtx[0, 1]], + [0.0, y_mtx[0, 0], y_mtx[0, 1]], + [0.0, 0.0, 1.0]]) + return self._mtx + - class BlendedAffine2D(Affine2DBase, Transform): def __init__(self, x_transform, y_transform): assert x_transform.is_affine() @@ -650,9 +870,8 @@ __str__ = __repr__ def _do_invalidation(self): - if self._mtx is not None: - self._mtx = None - Affine2DBase._do_invalidation(self) + self._mtx = None + Affine2DBase._do_invalidation(self) def is_separable(self): return True @@ -672,8 +891,10 @@ def blended_transform_factory(x_transform, y_transform): - if x_transform.is_affine() and y_transform.is_affine(): + if isinstance(x_transform, Affine2DBase) and isinstance(y_transform, Affine2DBase): return BlendedAffine2D(x_transform, y_transform) + elif isinstance(x_transform, Affine1DBase) and isinstance(y_transform, Affine1DBase): + return BlendedAffine1D(x_transform, y_transform) return BlendedGenericTransform(x_transform, y_transform) @@ -726,7 +947,7 @@ def _do_invalidation(self): self._mtx = None - return Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self) def get_matrix(self): if self._mtx is None: @@ -754,8 +975,8 @@ class TestLogTransform(Transform): input_dims = 1 output_dims = 1 - def transform(self, xy): - marray = ma.masked_where(xy <= 0.0, xy * 10.0) + def transform(self, a): + marray = ma.masked_where(a <= 0.0, a * 10.0) return (npy.log10(marray) * 0.5) + 0.5 def inverted(self): @@ -768,8 +989,8 @@ class TestInvertLogTransform(Transform): input_dims = 1 output_dims = 1 - def transform(self, xy): - return ma.power(10, (xy - 0.5) * 2.0) / 10.0 + def transform(self, a): + return ma.power(10, (a - 0.5) * 2.0) / 10.0 def inverted(self): return TestLogTransform() @@ -782,18 +1003,31 @@ input_dims = 2 output_dims = 2 + def __init__(self, limits): + assert limits.is_bbox() + + Transform.__init__(self) + self._limits = limits + self.set_children(['_limits']) + def transform(self, xy): debug = len(xy) > 4 - x = xy[:, 0:1] - y = xy[:, 1:] - x, y = ((y * npy.cos(x)) + 1.0) * 0.5, ((y * npy.sin(x)) + 1.0) * 0.5 - if debug: - print npy.min(xy[:, 0:1]), npy.max(xy[:, 0:1]), npy.min(xy[:, 1:]), npy.max(xy[:, 1:]) - print x.min(), x.max(), y.min(), y.max() - return ma.concatenate((x, y), 1) + limmin, limmax = self._limits.intervaly + mask = (xy[:, 1:] < limmin) | (xy[:, 1:] > limmax) + mask = ma.concatenate((mask, mask), 1) + masked_xy = npy.ma.masked_where(mask, xy) + x = masked_xy[:, 0:1] + y = masked_xy[:, 1:2] + if x.shape == () or y.shape == (): + return masked_xy + y = (y - limmin) / (limmax - limmin) + x, y = y * ma.cos(x), y * ma.sin(x) + result = ma.concatenate((x, y), 1) + result = result * 0.5 + 0.5 + return result def inverted(self): - return TestInvertPolarTransform() + return TestInvertPolarTransform(self._limits) def is_separable(self): return False @@ -803,16 +1037,26 @@ input_dims = 2 output_dims = 2 + def __init__(self, limits): + assert limits.is_bbox() + + Transform.__init__(self) + self._limits = limits + self.set_children(['_limits']) + def transform(self, xy): + limmin, limmax = self._limits.intervaly + xy = (xy - 0.5) * 2.0 x = xy[:, 0:1] y = xy[:, 1:] r = ma.sqrt(ma.power(x, 2) + ma.power(y, 2)) theta = ma.arccos(x / r) theta = ma.where(y < 0, 2 * npy.pi - theta, theta) - return ma.concatenate((theta / (npy.pi * 2), r), 1) + r = r * (limmax - limmin) + limmin + return ma.concatenate((theta, r), 1) def inverted(self): - return TestInvertPolarTransform() + return TestInvertPolarTransform(self._limits) def is_separable(self): return False @@ -835,9 +1079,8 @@ __str__ = __repr__ def _do_invalidation(self): - if self._mtx is not None: - self._mtx = None - Affine2DBase._do_invalidation(self) + self._mtx = None + Affine2DBase._do_invalidation(self) def is_separable(self): return True This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-24 17:33:08
|
Revision: 3886 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3886&view=rev Author: mdboom Date: 2007-09-24 10:33:03 -0700 (Mon, 24 Sep 2007) Log Message: ----------- Fixed log scaling again. Modified Paths: -------------- branches/transforms/lib/matplotlib/scale.py branches/transforms/lib/matplotlib/ticker.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/scale.py =================================================================== --- branches/transforms/lib/matplotlib/scale.py 2007-09-24 16:54:37 UTC (rev 3885) +++ branches/transforms/lib/matplotlib/scale.py 2007-09-24 17:33:03 UTC (rev 3886) @@ -23,6 +23,7 @@ self._base = base self._viewLim = viewLim self._direction = direction + self.set_children(['_viewLim']) def transform(self, a): a, affine = self.transform_without_affine(a) @@ -31,13 +32,9 @@ def transform_without_affine(self, a): # MGDTODO: Support different bases base = self._base - marray = ma.masked_where(a <= 0.0, a) + marray = ma.masked_where(a <= 0.0, a * 10.0) marray = npy.log10(marray) - minimum, maximum = getattr(self._viewLim, self._direction) - minimum, maximum = npy.log10([minimum, maximum]) - print marray - print Affine1D.from_values(maximum - minimum, minimum).inverted() - print minimum, maximum + minimum, maximum = npy.log10(getattr(self._viewLim, self._direction) * 10.0) return marray, Affine1D.from_values(maximum - minimum, minimum).inverted() def inverted(self): @@ -51,11 +48,12 @@ self._base = base self._viewLim = viewLim self._direction = direction - + self.set_children(['_viewLim']) + def transform(self, a): - minimum, maximum = getattr(self._viewLim, self._direction) - Affine1D.from_values(maximum - minimum, minimum).transform(a) - return ma.power(10.0, a) + minimum, maximum = npy.log10(getattr(self._viewLim, self._direction) * 10.0) + a = Affine1D.from_values(maximum - minimum, minimum).transform(a) + return ma.power(10.0, a) / 10.0 def inverted(self): return LogScale.LogTransform(self._viewLim, self._direction, self._base) Modified: branches/transforms/lib/matplotlib/ticker.py =================================================================== --- branches/transforms/lib/matplotlib/ticker.py 2007-09-24 16:54:37 UTC (rev 3885) +++ branches/transforms/lib/matplotlib/ticker.py 2007-09-24 17:33:03 UTC (rev 3886) @@ -928,15 +928,19 @@ # if minpos<=0: # raise RuntimeError('No positive data to plot') - minpos = max(vmin, 0.00001) #MGDTODO - if vmin<=0: - vmin = minpos + # MGDTODO: Find a good way to track minpos + if vmin <= 0.0: + vmin = 0.1 + if not is_decade(vmin,self._base): vmin = decade_down(vmin,self._base) if not is_decade(vmax,self._base): vmax = decade_up(vmax,self._base) if vmin==vmax: vmin = decade_down(vmin,self._base) vmax = decade_up(vmax,self._base) - return mtransforms.nonsingular(vmin, vmax) + print vmin, vmax + result = mtransforms.nonsingular(vmin, vmax) + print result + return result class AutoLocator(MaxNLocator): def __init__(self): Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-24 16:54:37 UTC (rev 3885) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-24 17:33:03 UTC (rev 3886) @@ -181,7 +181,7 @@ def __init__(self, points): BboxBase.__init__(self) self._points = npy.asarray(points, npy.float_) - + #@staticmethod def unit(): return Bbox.from_lbrt(0., 0., 1., 1.) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-25 17:05:56
|
Revision: 3889 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3889&view=rev Author: mdboom Date: 2007-09-25 10:04:51 -0700 (Tue, 25 Sep 2007) Log Message: ----------- Automaticall separate affine from non-affine transforms Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/backends/backend_agg.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/scale.py branches/transforms/lib/matplotlib/ticker.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -545,7 +545,7 @@ "move this out of __init__ because non-separable axes don't use it" self.xaxis = maxis.XAxis(self) self.yaxis = maxis.YAxis(self) - self._update_transAxisXY() + self._update_transScale() def sharex_foreign(self, axforeign): """ @@ -631,12 +631,28 @@ self.viewLim = mtransforms.Bbox.unit() self.transAxes = mtransforms.BboxTransform( mtransforms.Bbox.unit(), self.bbox) - self.transAxisXY = mtransforms.TransformWrapper() - self.transData = self.transAxisXY + self.transAxes - def _update_transAxisXY(self): - self.transAxisXY.set(mtransforms.blended_transform_factory( + # Transforms the x and y axis separately by a scale factor + # It is assumed that this part will have non-linear components + self.transScale = mtransforms.TransformWrapper(mtransforms.IdentityTransform()) + + # A (possibly non-linear) projection on the (already scaled) data + self.transProjection = mtransforms.IdentityTransform() + + # An affine transformation on the data, generally to limit the + # range of the axes + self.transLimits = mtransforms.BboxTransform( + mtransforms.TransformedBbox(self.viewLim, self.transScale), mtransforms.Bbox.unit()) + + self.transData = self.transScale + self.transProjection + self.transLimits + self.transAxes + + + 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' @@ -1537,7 +1553,7 @@ ACCEPTS: ['log' | 'linear' ] """ self.xaxis.set_scale(value, **kwargs) - self._update_transAxisXY() + self._update_transScale() def get_xticks(self): 'Return the x ticks as a list of locations' @@ -1647,7 +1663,7 @@ ACCEPTS: ['log' | 'linear'] """ self.yaxis.set_scale(value, basey, subsy) - self._update_transAxisXY() + self._update_transScale() def get_yticks(self): 'Return the y ticks as a list of locations' Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -508,16 +508,15 @@ self.majorTicks = [] self.minorTicks = [] self.pickradius = pickradius - self._transform = LinearScale(self.axes.viewLim, self.axis).get_transform() - self._scale = 'linear' + self._scale = LinearScale() self.cla() def get_transform(self): - return self._transform - + return self._scale.get_transform() + def get_scale(self): - return self._scale + return self._scale.name def set_scale(self, value, base=10, subs=None): # MGDTODO: Move these settings (ticker etc.) into the scale class itself @@ -528,17 +527,16 @@ self.set_major_formatter(ScalarFormatter()) self.set_minor_locator(NullLocator()) self.set_minor_formatter(NullFormatter()) - self._transform = LinearScale(self.axes.viewLim, self.axis).get_transform() + self._scale = LinearScale() elif value == 'log': self.set_major_locator(LogLocator(base)) self.set_major_formatter(LogFormatterMathtext(base)) self.set_minor_locator(LogLocator(base,subs)) # MGDTODO: Pass base along - self._transform = LogScale(self.axes.viewLim, self.axis).get_transform() + self._scale = LogScale() miny, maxy = getattr(self.axes.viewLim, 'interval' + self.axis) if min(miny, maxy)<=0: self.axes.autoscale_view() - self._scale = value def get_children(self): children = [self.label] Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -132,14 +132,14 @@ # MGDTODO: This is a hack for now to allow for arbitrary transformations def draw_path(self, gc, path, trans, rgbFace=None): - new_path, affine = path.transformed_without_affine(trans) - self._renderer.draw_path(gc, new_path, affine, rgbFace) + assert trans.is_affine() + self._renderer.draw_path(gc, path, trans, rgbFace) # MGDTODO: This is a hack for now to allow for arbitrary transformations def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): assert marker_trans.is_affine() - new_path, affine = path.transformed_without_affine(trans) - self._renderer.draw_markers(gc, marker_path, marker_trans, new_path, affine, rgbFace) + assert trans.is_affine() + self._renderer.draw_markers(gc, marker_path, marker_trans, path, trans, rgbFace) def draw_mathtext(self, gc, x, y, s, prop, angle): """ Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -17,7 +17,7 @@ from cbook import iterable, is_string_like, is_numlike from colors import colorConverter from path import Path -from transforms import Affine2D, Bbox +from transforms import Affine2D, Bbox, TransformedPath from matplotlib import rcParams @@ -416,8 +416,18 @@ 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 + + ACCEPTS: a matplotlib.transform transformation instance + """ + Artist.set_transform(self, t) + self._transformed_path = TransformedPath(self._path, self.get_transform()) def _is_sorted(self, x): "return true if x is sorted" @@ -486,10 +496,10 @@ funcname = self._lineStyles.get(self._linestyle, '_draw_nothing') lineFunc = getattr(self, funcname) - lineFunc(renderer, gc, self._path) + lineFunc(renderer, gc, *self._transformed_path.get_path_and_affine()) # MGDTODO: Deal with markers - if self._marker is not None: + if self._marker is not None and False: gc = renderer.new_gc() self._set_gc_clip(gc) gc.set_foreground(self.get_markeredgecolor()) @@ -678,7 +688,7 @@ self.set_linestyle('--') self._dashSeq = seq # TODO: offset ignored for now - def _draw_nothing(self, renderer, gc, path): + def _draw_nothing(self, *args, **kwargs): pass @@ -704,191 +714,191 @@ renderer.draw_path(gc, self._step_path, self.get_transform()) - def _draw_solid(self, renderer, gc, path): + def _draw_solid(self, renderer, gc, path, trans): gc.set_linestyle('solid') - renderer.draw_path(gc, path, self.get_transform()) + renderer.draw_path(gc, path, trans) - def _draw_dashed(self, renderer, gc, path): + def _draw_dashed(self, renderer, gc, path, trans): gc.set_linestyle('dashed') if self._dashSeq is not None: gc.set_dashes(0, self._dashSeq) - renderer.draw_path(gc, path, self.get_transform()) + renderer.draw_path(gc, path, trans) - def _draw_dash_dot(self, renderer, gc, path): + def _draw_dash_dot(self, renderer, gc, path, trans): gc.set_linestyle('dashdot') - renderer.draw_path(gc, path, self.get_transform()) + renderer.draw_path(gc, path, trans) - def _draw_dotted(self, renderer, gc, path): + def _draw_dotted(self, renderer, gc, path, trans): gc.set_linestyle('dotted') - renderer.draw_path(gc, path, self.get_transform()) + renderer.draw_path(gc, path, trans) - def _draw_point(self, renderer, gc, path): + def _draw_point(self, renderer, gc, path, path_trans): w = renderer.points_to_pixels(self._markersize) * \ self._point_size_reduction * 0.5 rgbFace = self._get_rgb_face() transform = Affine2D().scale(w) renderer.draw_markers( - gc, Path.unit_circle(), transform, path, self.get_transform(), + gc, Path.unit_circle(), transform, path, path_trans, rgbFace) - def _draw_pixel(self, renderer, gc, path): + def _draw_pixel(self, renderer, gc, path, path_trans): rgbFace = self._get_rgb_face() transform = Affine2D().translate(-0.5, -0.5) renderer.draw_markers(gc, Path.unit_rectangle, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_circle(self, renderer, gc, path): + def _draw_circle(self, renderer, gc, path, path_trans): w = renderer.points_to_pixels(self._markersize) * 0.5 rgbFace = self._get_rgb_face() transform = Affine2D().scale(w, w) renderer.draw_markers( - gc, Path.unit_circle(), transform, path, self.get_transform(), + gc, Path.unit_circle(), transform, path, path_trans, rgbFace) _triangle_path = Path([[0.0, 1.0], [-1.0, -1.0], [1.0, -1.0]]) - def _draw_triangle_up(self, renderer, gc, path): + def _draw_triangle_up(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset, offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, self._triangle_path, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_triangle_down(self, renderer, gc, path): + def _draw_triangle_down(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset, -offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, self._triangle_path, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_triangle_left(self, renderer, gc, path): + def _draw_triangle_left(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset, offset).rotate_deg(90) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, self._triangle_path, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_triangle_right(self, renderer, gc, path): + def _draw_triangle_right(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset, offset).rotate_deg(-90) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, self._triangle_path, transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_square(self, renderer, gc, path): + def _draw_square(self, renderer, gc, path, path_trans): side = renderer.points_to_pixels(self._markersize) transform = Affine2D().translate(-0.5, -0.5).scale(side) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_rectangle(), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_diamond(self, renderer, gc, path): + def _draw_diamond(self, renderer, gc, path, path_trans): side = renderer.points_to_pixels(self._markersize) transform = Affine2D().translate(0.5, 0.5).rotate_deg(45).scale(side) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_rectangle(), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_thin_diamond(self, renderer, gc, path): + def _draw_thin_diamond(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) transform = Affine2D().translate(0.5, 0.5) \ .rotate_deg(45).scale(offset * 0.8, offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_rectangle(), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_pentagon(self, renderer, gc, path): + def _draw_pentagon(self, renderer, gc, path, path_trans): offset = 0.5 * renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_regular_polygon(5), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_hexagon1(self, renderer, gc, path): + def _draw_hexagon1(self, renderer, gc, path, path_trans): offset = 0.5 * renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_regular_polygon(6), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) - def _draw_hexagon2(self, renderer, gc, path): + def _draw_hexagon2(self, renderer, gc, path, path_trans): offset = 0.5 * renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(30) rgbFace = self._get_rgb_face() renderer.draw_markers(gc, Path.unit_regular_polygon(6), transform, - path, self.get_transform(), rgbFace) + path, path_trans, rgbFace) _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]], closed=False) - def _draw_vline(self, renderer, gc, path): + def _draw_vline(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._line_marker_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_hline(self, renderer, gc, path): + def _draw_hline(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(90) renderer.draw_markers(gc, self._line_marker_path, transform, - path, self.get_transform()) + path, path_trans) _tickhoriz_path = Path([[0.0, 0.5], [1.0, 0.5]], closed=False) - def _draw_tickleft(self, renderer, gc, path): + def _draw_tickleft(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) marker_transform = Affine2D().scale(-offset, 1.0) renderer.draw_markers(gc, self._tickhoriz_path, marker_transform, - path, self.get_transform()) + path, path_trans) - def _draw_tickright(self, renderer, gc, path): + def _draw_tickright(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) marker_transform = Affine2D().scale(offset, 1.0) renderer.draw_markers(gc, self._tickhoriz_path, marker_transform, - path, self.get_transform()) + path, path_trans) _tickvert_path = Path([[-0.5, 0.0], [-0.5, 1.0]], closed=False) - def _draw_tickup(self, renderer, gc, path): + def _draw_tickup(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) marker_transform = Affine2D().scale(1.0, offset) renderer.draw_markers(gc, self._tickvert_path, marker_transform, - path, self.get_transform()) + path, path_trans) - def _draw_tickdown(self, renderer, gc, path): + def _draw_tickdown(self, renderer, gc, path, path_trans): offset = renderer.points_to_pixels(self._markersize) marker_transform = Affine2D().scale(1.0, -offset) renderer.draw_markers(gc, self._tickvert_path, marker_transform, - path, self.get_transform()) + path, path_trans) _plus_path = Path([[-1.0, 0.0], [1.0, 0.0], [0.0, -1.0], [0.0, 1.0]], [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO]) - def _draw_plus(self, renderer, gc, path): + def _draw_plus(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._plus_path, transform, - path, self.get_transform()) + path, path_trans) _tri_path = Path([[0.0, 0.0], [0.0, -1.0], @@ -897,61 +907,61 @@ [Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO, Path.MOVETO, Path.LINETO]) - def _draw_tri_down(self, renderer, gc, path): + def _draw_tri_down(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._tri_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_tri_up(self, renderer, gc, path): + def _draw_tri_up(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(180) renderer.draw_markers(gc, self._tri_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_tri_left(self, renderer, gc, path): + def _draw_tri_left(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(90) renderer.draw_markers(gc, self._tri_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_tri_right(self, renderer, gc, path): + def _draw_tri_right(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(270) renderer.draw_markers(gc, self._tri_path, transform, - path, self.get_transform()) + path, path_trans) _caret_path = Path([[-1.0, 1.5], [0.0, 0.0], [1.0, 1.5]], closed=False) - def _draw_caretdown(self, renderer, gc, path): + def _draw_caretdown(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._caret_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_caretup(self, renderer, gc, path): + def _draw_caretup(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(180) renderer.draw_markers(gc, self._caret_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_caretleft(self, renderer, gc, path): + def _draw_caretleft(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(90) renderer.draw_markers(gc, self._caret_path, transform, - path, self.get_transform()) + path, path_trans) - def _draw_caretright(self, renderer, gc, path): + def _draw_caretright(self, renderer, gc, path, path_trans): offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset).rotate_deg(270) renderer.draw_markers(gc, self._caret_path, transform, - path, self.get_transform()) + path, path_trans) _x_path = Path([[-1.0, -1.0], [1.0, 1.0], @@ -962,7 +972,7 @@ offset = 0.5*renderer.points_to_pixels(self._markersize) transform = Affine2D().scale(offset) renderer.draw_markers(gc, self._x_path, transform, - path, self.get_transform()) + path, path_trans) def update_from(self, other): Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/path.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -88,10 +88,6 @@ def transformed(self, transform): return Path(transform.transform(self.vertices), self.codes) - - def transformed_without_affine(self, transform): - vertices, affine = transform.transform_without_affine(self.vertices) - return Path(vertices, self.codes), affine _unit_rectangle = None #@classmethod @@ -152,6 +148,3 @@ cls._unit_circle = Path(vertices, codes) return cls._unit_circle unit_circle = classmethod(unit_circle) - -# MGDTODO: Add a transformed path that would automatically invalidate -# itself when its transform changes Modified: branches/transforms/lib/matplotlib/scale.py =================================================================== --- branches/transforms/lib/matplotlib/scale.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/scale.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -1,66 +1,54 @@ import numpy as npy from numpy import ma +from numpy.linalg import inv -from transforms import Affine1D, IntervalTransform, Transform +from transforms import Affine1DBase, IntervalTransform, Transform, \ + composite_transform_factory, IdentityTransform class ScaleBase(object): pass class LinearScale(ScaleBase): - def __init__(self, viewLim, direction): - direction = 'interval' + direction - self._transform = IntervalTransform(viewLim, direction) - def get_transform(self): - return self._transform + return IdentityTransform() class LogScale(ScaleBase): class LogTransform(Transform): input_dims = 1 output_dims = 1 - def __init__(self, viewLim, direction, base): + def __init__(self, base): Transform.__init__(self) self._base = base - self._viewLim = viewLim - self._direction = direction - self.set_children(['_viewLim']) + def is_separable(self): + return True + def transform(self, a): - a, affine = self.transform_without_affine(a) - return affine.transform(a) + if len(a) > 10: + print "Log Transforming..." + return ma.log10(ma.masked_where(a <= 0.0, a * 10.0)) - def transform_without_affine(self, a): - # MGDTODO: Support different bases - base = self._base - marray = ma.masked_where(a <= 0.0, a * 10.0) - marray = npy.log10(marray) - minimum, maximum = npy.log10(getattr(self._viewLim, self._direction) * 10.0) - return marray, Affine1D.from_values(maximum - minimum, minimum).inverted() - def inverted(self): - return LogScale.InvertedLogTransform(self._viewLim, self._direction, self._base) + return LogScale.InvertedLogTransform(self._base) class InvertedLogTransform(Transform): input_dims = 1 output_dims = 1 - def __init__(self, viewLim, direction, base): + def __init__(self, base): Transform.__init__(self) self._base = base - self._viewLim = viewLim - self._direction = direction - self.set_children(['_viewLim']) + + def is_separable(self): + return True def transform(self, a): - minimum, maximum = npy.log10(getattr(self._viewLim, self._direction) * 10.0) - a = Affine1D.from_values(maximum - minimum, minimum).transform(a) return ma.power(10.0, a) / 10.0 def inverted(self): - return LogScale.LogTransform(self._viewLim, self._direction, self._base) + return LogScale.LogTransform(self._base) - def __init__(self, viewLim, direction, base=10): - direction = 'interval' + direction - self._transform = self.LogTransform(viewLim, direction, base) + def __init__(self, base=10): + self._transform = self.LogTransform(base) def get_transform(self): return self._transform Modified: branches/transforms/lib/matplotlib/ticker.py =================================================================== --- branches/transforms/lib/matplotlib/ticker.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/ticker.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -937,9 +937,7 @@ if vmin==vmax: vmin = decade_down(vmin,self._base) vmax = decade_up(vmax,self._base) - print vmin, vmax result = mtransforms.nonsingular(vmin, vmax) - print result return result class AutoLocator(MaxNLocator): Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-25 12:15:22 UTC (rev 3888) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-25 17:04:51 UTC (rev 3889) @@ -8,7 +8,10 @@ from numpy import ma as ma from numpy.linalg import inv from sets import Set +from weakref import WeakKeyDictionary +from path import Path + DEBUG = False # MGDTODO: This creates a ton of cyclical references. We may want to @@ -22,45 +25,54 @@ self._parents = Set() self._children = [] - def invalidate(self): - self._do_invalidation() + def invalidate(self, which_child=None, affine_only=[]): + if which_child is None: + which_child = self + self._do_invalidation(which_child, affine_only) + # affine_only = affine_only and (self.is_affine() or self.is_bbox()) for parent in self._parents: - parent.invalidate() + parent.invalidate(self, affine_only + [self]) - def _do_invalidation(self): - return False - + def _do_invalidation(self, which_child, affine_only): + pass + def set_children(self, children): for child in children: getattr(self, child)._parents.add(self) self._children = children def make_graphviz(self, fobj): + seen = Set() + def recurse(root): + if root in seen: + return + seen.add(root) fobj.write('%s [label="%s"];\n' % (hash(root), root.__class__.__name__)) - if isinstance(root, Affine2DBase): + if root.is_affine(): fobj.write('%s [style=filled, color=".7 .7 .9"];\n' % hash(root)) - elif isinstance(root, BboxBase): + elif root.is_bbox(): fobj.write('%s [style=filled, color=".9 .9 .7"];\n' % hash(root)) for child_name in root._children: child = getattr(root, child_name) - fobj.write("%s -> %s;\n" % ( + fobj.write('%s -> %s [label="%s"];\n' % ( hash(root), - hash(child))) + hash(child), + child_name)) recurse(child) - + fobj.write("digraph G {\n") recurse(self) fobj.write("}\n") def is_affine(self): - return isinstance(self, Affine2DBase) + return False def is_bbox(self): - return isinstance(self, BboxBase) + return False class BboxBase(TransformNode): @@ -70,7 +82,10 @@ def __init__(self): TransformNode.__init__(self) - + + def is_bbox(self): + return True + def __array__(self): return self.get_points() @@ -125,63 +140,81 @@ height = property(_get_height) def _get_bounds(self): - return (self.xmin, self.ymin, - self.xmax - self.xmin, self.ymax - self.ymin) + ((xmin, ymin), (xmax, ymax)) = self.get_points() + return (xmin, ymin, xmax - xmin, ymax - ymin) bounds = property(_get_bounds) + def _get_lbrt(self): + return self.get_points().flatten().copy() + lbrt = property(_get_lbrt) + def get_points(self): return NotImplementedError() - + # MGDTODO: Optimize def containsx(self, x): - return x >= self.xmin and x <= self.xmax + xmin, xmax = self.intervalx + return x >= xmin and x <= xmax def containsy(self, y): - return y >= self.ymin and y <= self.ymax + ymin, ymax = self.intervaly + return y >= ymin and y <= ymax def contains(self, x, y): return self.containsx(x) and self.containsy(y) def overlapsx(self, other): - return self.containsx(other.xmin) \ - or self.containsx(other.xmax) + xmin, xmax = other.intervalx + return self.containsx(xmin) \ + or self.containsx(xmax) def overlapsy(self, other): - return self.containsy(other.ymin) \ - or self.containsx(other.ymax) + ymin, ymax = other.intervaly + return self.containsy(ymin) \ + or self.containsx(ymax) def overlaps(self, other): return self.overlapsx(other) \ and self.overlapsy(other) def fully_containsx(self, x): - return x > self.xmin and x < self.xmax + xmin, xmax = self.intervalx + return x > xmin and x < xmax def fully_containsy(self, y): - return y > self.ymin and y < self.ymax + ymin, ymax = self.intervaly + return y > ymin and y < ymax def fully_contains(self, x, y): return self.fully_containsx(x) \ and self.fully_containsy(y) def fully_overlapsx(self, other): - return self.fully_containsx(other.xmin) \ - or self.fully_containsx(other.xmax) + xmin, xmax = other.intervalx + return self.fully_containsx(xmin) \ + or self.fully_containsx(xmax) def fully_overlapsy(self, other): - return self.fully_containsy(other.ymin) \ - or self.fully_containsx(other.ymax) + ymin, ymax = other.intervaly + return self.fully_containsy(ymin) \ + or self.fully_containsx(ymax) def fully_overlaps(self, other): return self.fully_overlapsx(other) and \ self.fully_overlapsy(other) + def transformed(self, transform): + return Bbox(transform.transform(self.get_points())) + + def inverse_transformed(self, transform): + return Bbox(transform.inverted().transform(self.get_points())) + class Bbox(BboxBase): def __init__(self, points): BboxBase.__init__(self) self._points = npy.asarray(points, npy.float_) - + #@staticmethod def unit(): return Bbox.from_lbrt(0., 0., 1., 1.) @@ -198,12 +231,6 @@ return Bbox(points) from_lbrt = staticmethod(from_lbrt) - def __cmp__(self, other): - # MGDTODO: Totally suboptimal - if isinstance(other, Bbox) and (self._points == other._points).all(): - return 0 - return -1 - def __repr__(self): return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ @@ -219,8 +246,7 @@ [max(x.max(), self.xmax), max(y.max(), self.ymax)]], npy.float_) self.invalidate() - - # MGDTODO: Probably a more efficient ways to do this... + def _set_xmin(self, val): self._points[0, 0] = val self.invalidate() @@ -278,12 +304,6 @@ self._points = other.get_points() self.invalidate() - def transformed(self, transform): - return Bbox(transform.transform(self._points)) - - def inverse_transformed(self, transform): - return Bbox(transform.inverted().transform(self._points)) - def expanded(self, sw, sh): width = self.width height = self.height @@ -324,6 +344,8 @@ def __init__(self, bbox, transform): assert bbox.is_bbox() assert isinstance(transform, Transform) + assert transform.input_dims == 2 + assert transform.output_dims == 2 BboxBase.__init__(self) self.bbox = bbox @@ -335,7 +357,7 @@ return "TransformedBbox(%s, %s)" % (self.bbox, self.transform) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._points = None def get_points(self): @@ -347,13 +369,10 @@ class Transform(TransformNode): def __init__(self): TransformNode.__init__(self) - - def transform(self, points): - raise NotImplementedError() - def transform_without_affine(self, points): - return self.transform(points), IDENTITY - + def is_separable(self): + return False + def __add__(self, other): if isinstance(other, Transform): return composite_transform_factory(self, other) @@ -366,39 +385,69 @@ raise TypeError( "Can not add Transform to object of type '%s'" % type(other)) + def transform(self, points): + raise NotImplementedError + + def transform_affine(self, points): + raise NotImplementedError + + def transform_non_affine(self, points): + raise NotImplementedError + + def get_affine(self): + raise NotImplementedError + def transform_point(self, point): return self.transform(npy.asarray([point]))[0] - + def has_inverse(self): raise NotImplementedError() def inverted(self): raise NotImplementedError() - def is_separable(self): - return False +class TransformWrapper(Transform): + def __init__(self, child): + assert isinstance(child, Transform) + + Transform.__init__(self) + self.input_dims = child.input_dims + self.output_dims = child.output_dims + self._child = child + self.set_children(['_child']) -class TransformWrapper(Transform): - input_dims = 2 - output_dims = 2 - + def __repr__(self): + return "TransformWrapper(%r)" % self._child + __str__ = __repr__ + def set(self, child): - self.child = child - self.child._parents.add(self) + assert child.input_dims == self.input_dims + assert child.output_dims == self.output_dims + self._child = child + self.set_children(['_child']) self.invalidate() + + def is_separable(self): + return self._child.is_separable() + def is_affine(self): + return self._child.is_affine() + def transform(self, points): - return self.child.transform(points) + return self._child.transform(points) - def transform_without_affine(points): - return self.child.transform_without_affine(points) + def transform_affine(self, points): + return self._child.transform_affine(points) + + def transform_non_affine(self, points): + return self._child.transform_non_affine(points) + + def get_affine(self): + return self._child.get_affine() def inverted(self): - return self.child.inverted() - - def is_separable(self): - return self.child.is_separable() + return self._child.inverted() class AffineBase(Transform): @@ -406,10 +455,13 @@ Transform.__init__(self) self._inverted = None + def is_affine(self): + return True + def __array__(self, *args, **kwargs): return self.get_matrix() - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._inverted = None #@staticmethod @@ -425,10 +477,14 @@ def get_matrix(self): raise NotImplementedError() - def transform_without_affine(self, points): - # MGDTODO: Should we copy the points here? I'd like to avoid it, - # if possible - return points, self + def transform_affine(self, points): + return self.transform(points) + + def transform_non_affine(self, points): + return points + + def get_affine(self): + return self class Affine1DBase(AffineBase): @@ -437,13 +493,16 @@ def __init__(self): AffineBase.__init__(self) - + + def is_separable(self): + return True + def __array__(self, *args, **kwargs): return self.get_matrix() - + def to_values(self): mtx = self.get_matrix() - return tuple(mtx[0]) + return mtx[0] #@staticmethod def matrix_from_values(a, b): @@ -472,9 +531,6 @@ points = ma.asarray(values, npy.float_) return points * mtx[0,0] + mtx[0,1] - def is_separable(self): - return True - def inverted(self): if self._inverted is None: mtx = self.get_matrix() @@ -551,9 +607,12 @@ class IntervalTransform(Affine1DBase): def __init__(self, bbox, direction): + assert direction in ('x', 'y') + assert bbox.is_bbox() + Affine1DBase.__init__(self) self._bbox = bbox - self._direction = direction + self._direction = "interval" + direction self.set_children(['_bbox']) self._mtx = None @@ -561,10 +620,9 @@ return "IntervalTransform(%s)" % (getattr(self._bbox, self._direction)) __str__ = __repr__ - def _do_invalidation(self): - print "IntervalTransform.invalidation", self._bbox + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine1DBase._do_invalidation(self) + Affine1DBase._do_invalidation(self, which_child, affine_only) def get_matrix(self): if self._mtx is None: @@ -581,6 +639,10 @@ def __init__(self): AffineBase.__init__(self) + def is_separable(self): + mtx = self.get_matrix() + return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + def __array__(self, *args, **kwargs): return self.get_matrix() @@ -627,10 +689,6 @@ mtx = self.get_matrix() self._inverted = Affine2D(inv(mtx)) return self._inverted - - def is_separable(self): - mtx = self.get_matrix() - return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 class Affine2D(Affine2DBase): @@ -728,6 +786,10 @@ """ _mtx = npy.identity(3) + def __repr__(self): + return "IdentityTransform()" + __str__ = __repr__ + def __cmp__(self, other): if (isinstance(other, Affine2D) and (other == IDENTITY)): @@ -735,34 +797,44 @@ return -1 def get_matrix(self): - return _mtx + return self._mtx def transform(self, points): return points - def transform_without_affine(self, points): - return points, self + def transform_affine(self, points): + return points + def transform_non_affine(self, points): + return points + + def get_affine(self): + return self + def inverted(self): return self + -IDENTITY = Affine2D() - class BlendedGenericTransform(Transform): input_dims = 2 output_dims = 2 def __init__(self, x_transform, y_transform): # Here we ask: "Does it blend?" - # MGDTODO: Turn these checks back on - # assert x_transform.is_separable() - # assert y_transform.is_separable() + assert x_transform.is_separable() + assert y_transform.is_separable() Transform.__init__(self) self._x = x_transform self._y = y_transform self.set_children(['_x', '_y']) + def is_affine(self): + return self._x.is_affine() and self._y.is_affine() + + def is_separable(self): + return True + def __repr__(self): return "BlendedGenericTransform(%s,%s)" % (self._x, self._y) __str__ = __repr__ @@ -787,33 +859,17 @@ return ma.concatenate((x_points, y_points), 1) - def transform_without_affine(self, points): - x = self._x - y = self._y - if x == y and x.input_dims == 2: - return self._x.transform_without_affine(points) - - if x.input_dims == 2: - x_points, x_affine = x.transform_without_affine(points) - x_points = x_points[:, 0:1] - else: - x_points, x_affine = x.transform_without_affine(points[:, 0]) - x_points = x_points.reshape((len(x_points), 1)) - - if y.input_dims == 2: - y_points, y_affine = y.transform_without_affine(points) - y_points = y_points[:, 1:] - else: - y_points, y_affine = y.transform_without_affine(points[:, 1]) - y_points = y_points.reshape((len(y_points), 1)) - - return ma.concatenate((x_points, y_points), 1), blended_transform_factory(x_affine, y_affine) + def transform_affine(self, points): + return points + + def transform_non_affine(self, points): + return self.transform(points) + + def get_affine(self): + return IdentityTransform() def inverted(self): return BlendedGenericTransform(self._x.inverted(), self._y.inverted()) - - def is_separable(self): - return True class BlendedAffine1D(Affine2DBase, Transform): @@ -829,17 +885,17 @@ Affine2DBase.__init__(self) self._mtx = None + def is_separable(self): + return True + def __repr__(self): return "BlendedAffine1D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self, which_child, affine_only) - def is_separable(self): - return True - def get_matrix(self): if self._mtx is None: x_mtx = self._x.get_matrix() @@ -854,9 +910,9 @@ def __init__(self, x_transform, y_transform): assert x_transform.is_affine() assert y_transform.is_affine() - # MGDTODO: Turn these checks back on - # assert x_transform.is_separable() - # assert y_transform.is_separable() + assert x_transform.is_separable() + assert y_transform.is_separable() + Transform.__init__(self) self._x = x_transform self._y = y_transform @@ -865,17 +921,17 @@ Affine2DBase.__init__(self) self._mtx = None + def is_separable(self): + return True + def __repr__(self): return "BlendedAffine2D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self, which_child, affine_only) - def is_separable(self): - return True - def get_matrix(self): if self._mtx is None: if self._x == self._y: @@ -909,8 +965,12 @@ self._b = b self.set_children(['_a', '_b']) - self.take_shortcut = b.is_affine() - + def is_affine(self): + return self._a.is_affine() and self._b.is_affine() + + def is_separable(self): + return self._a.is_separable() and self._b.is_separable() + def __repr__(self): return "CompositeGenericTransform(%s, %s)" % (self._a, self._b) __str__ = __repr__ @@ -918,20 +978,24 @@ def transform(self, points): return self._b.transform(self._a.transform(points)) - def transform_without_affine(self, points): - if self.take_shortcut: - return self._a.transform(points), self._b - return self.transform(points), IDENTITY + def transform_affine(self, points): + return self._b.transform_affine(self._a.transform_affine(points)) + + def transform_non_affine(self, points): + return self._b.transform_non_affine(self._a.transform_non_affine(points)) + + def get_affine(self): + return self._a.get_affine() + self._b.get_affine() def inverted(self): return CompositeGenericTransform(self._b.inverted(), self._a.inverted()) - - def is_separable(self): - return self._a.is_separable() and self._b.is_separable() class CompositeAffine2D(Affine2DBase): def __init__(self, a, b): + assert a.output_dims == b.input_dims + self.input_dims = a.input_dims + self.output_dims = b.output_dims assert a.is_affine() assert b.is_affine() @@ -945,9 +1009,9 @@ return "CompositeAffine2D(%s, %s)" % (self._a, self._b) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self, which_child, affine_only) def get_matrix(self): if self._mtx is None: @@ -958,7 +1022,9 @@ def composite_transform_factory(a, b): - if a.is_affine() and b.is_affine(): + if isinstance(a, BboxTransform) and isinstance(b, BboxTransform): + return BboxTransform(a._boxin, b._boxout) + if isinstance(a, AffineBase) and isinstance(b, AffineBase): return CompositeAffine2D(a, b) return CompositeGenericTransform(a, b) @@ -972,33 +1038,6 @@ return npy.log10(m) -class TestLogTransform(Transform): - input_dims = 1 - output_dims = 1 - def transform(self, a): - marray = ma.masked_where(a <= 0.0, a * 10.0) - return (npy.log10(marray) * 0.5) + 0.5 - - def inverted(self): - return TestInvertLogTransform() - - def is_separable(self): - return True - - -class TestInvertLogTransform(Transform): - input_dims = 1 - output_dims = 1 - def transform(self, a): - return ma.power(10, (a - 0.5) * 2.0) / 10.0 - - def inverted(self): - return TestLogTransform() - - def is_separable(self): - return True - - class TestPolarTransform(Transform): input_dims = 2 output_dims = 2 @@ -1078,9 +1117,9 @@ return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) __str__ = __repr__ - def _do_invalidation(self): + def _do_invalidation(self, which_child, affine_only): self._mtx = None - Affine2DBase._do_invalidation(self) + Affine2DBase._do_invalidation(self, which_child, affine_only) def is_separable(self): return True @@ -1101,7 +1140,38 @@ self._mtx = affine._mtx return self._mtx + +class TransformedPath(TransformNode): + def __init__(self, path, transform): + assert isinstance(transform, Transform) + TransformNode.__init__(self) + + self._path = path + self._transform = transform + self.set_children(['_transform']) + self._transformed_path = None + + def _do_invalidation(self, which_child, affine_only): + if not (affine_only[0].is_affine() or affine_only[0].is_bbox()): + self._transformed_path = None + + def get_path_and_affine(self): + if self._transformed_path is None: + vertices = self._transform.transform_non_affine(self._path.vertices) + self._transformed_path = Path(vertices, self._path.codes) + return self._transformed_path, self._transform.get_affine() + + def get_path(self): + if self._transformed_path is None: + vertices = self._tranform.transform_non_affine(self._path.vertices) + self._transformed_path = Path(vertices, self._path.codes) + vertices = self._transform.transform_affine(self._transformed_path.vertices) + return Path(vertices, self._transformed_path.codes) + + def get_affine(self): + return self._transform.get_affine() + def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): ''' Ensure the endpoints of a range are not too close together. @@ -1128,6 +1198,7 @@ vmin, vmax = vmax, vmin return vmin, vmax + # MGDTODO: Optimize (perhaps in an extension) def interval_contains(interval, val): return interval[0] <= val and interval[1] >= val @@ -1173,8 +1244,8 @@ bbox = Bbox.from_lbwh(10, 11, 12, 13) assert bbox.bounds == (10, 11, 12, 13) - bbox_copy = copy.copy(bbox) - assert bbox == bbox_copy + bbox_copy = copy.deepcopy(bbox) + assert (bbox.lbrt == bbox_copy.lbrt).all() bbox_copy.max = (14, 15) assert bbox.bounds == (10, 11, 12, 13) assert bbox_copy.bounds == (10, 11, 4, 4) @@ -1183,7 +1254,7 @@ bbox2 = Bbox([[30., 35.], [40., 45.]]) trans = BboxTransform(bbox1, bbox2) bbox3 = bbox1.transformed(trans) - assert bbox3 == bbox2 + assert (bbox3.lbrt == bbox2.lbrt).all() translation = Affine2D().translate(10, 20) assert translation.to_values() == (1, 0, 0, 1, 10, 20) @@ -1210,12 +1281,6 @@ print points - comp = TestLogTransform() + Affine2D().rotate_deg(15) - tpoints = comp.transform(points) - itpoints = comp.inverted().transform(tpoints) - print tpoints, itpoints - assert (points.round() == itpoints.round()).all() - # Here are some timing tests points = npy.asarray([(random(), random()) for i in xrange(10000)]) t = timeit.Timer("trans_sum.transform(points)", "from __main__ import trans_sum, points") This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-25 18:29:47
|
Revision: 3890 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3890&view=rev Author: mdboom Date: 2007-09-25 11:29:44 -0700 (Tue, 25 Sep 2007) Log Message: ----------- Minor speed improvements in new transformations. Modified Paths: -------------- branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/text.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-09-25 17:04:51 UTC (rev 3889) +++ branches/transforms/lib/matplotlib/path.py 2007-09-25 18:29:44 UTC (rev 3890) @@ -41,9 +41,10 @@ assert len(codes) == len(vertices) # The path being passed in may have masked values. However, - # the backends are not expected to deal with masked arrays, so - # we must remove them from the array (using compressed), and - # add MOVETO commands to the codes array accordingly. + # the backends (and any affine transformations in matplotlib + # itself), are not expected to deal with masked arrays, so we + # must remove them from the array (using compressed), and add + # MOVETO commands to the codes array accordingly. mask = ma.getmask(vertices) if mask is not ma.nomask: mask1d = ma.mask_or(mask[:, 0], mask[:, 1]) Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-09-25 17:04:51 UTC (rev 3889) +++ branches/transforms/lib/matplotlib/text.py 2007-09-25 18:29:44 UTC (rev 3890) @@ -402,9 +402,7 @@ return (x, y, self._text, self._color, self._verticalalignment, self._horizontalalignment, hash(self._fontproperties), self._rotation, - # MGDTODO: Find a better way to determine if the - # transform as changed - str(self.get_transform()) + self.get_transform().get_id() ) def get_text(self): Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-25 17:04:51 UTC (rev 3889) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-25 18:29:44 UTC (rev 3890) @@ -21,24 +21,28 @@ # relationships class TransformNode(object): + _gid = 0 + def __init__(self): - self._parents = Set() - self._children = [] + self._parents = WeakKeyDictionary() + self._children = Set() + self._id = TransformNode._gid - def invalidate(self, which_child=None, affine_only=[]): - if which_child is None: - which_child = self - self._do_invalidation(which_child, affine_only) - # affine_only = affine_only and (self.is_affine() or self.is_bbox()) - for parent in self._parents: - parent.invalidate(self, affine_only + [self]) + def invalidate(self, affine_only=None): + if affine_only is None: + affine_only = self.is_affine() or self.is_bbox() + if not self._do_invalidation(affine_only): + self._id = TransformNode._gid + TransformNode._gid += 1 + for parent in self._parents.iterkeys(): + parent.invalidate(affine_only) - def _do_invalidation(self, which_child, affine_only): - pass + def _do_invalidation(self, affine_only): + return False def set_children(self, children): for child in children: - getattr(self, child)._parents.add(self) + getattr(self, child)._parents[self] = None self._children = children def make_graphviz(self, fobj): @@ -74,7 +78,10 @@ def is_bbox(self): return False + def get_id(self): + return self._id + class BboxBase(TransformNode): ''' This is the read-only part of a bounding-box @@ -214,6 +221,7 @@ def __init__(self, points): BboxBase.__init__(self) self._points = npy.asarray(points, npy.float_) + self._invalid = False #@staticmethod def unit(): @@ -235,6 +243,11 @@ return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ + def _do_invalidation(self, affine_only): + result = self._invalid + self._invalid = True + return result + def update_from_data(self, x, y, ignore=True): if ignore: self._points = npy.array( @@ -294,6 +307,7 @@ bounds = property(BboxBase._get_bounds, _set_bounds) def get_points(self): + self._invalid = False return self._points def set_points(self, points): @@ -348,21 +362,23 @@ assert transform.output_dims == 2 BboxBase.__init__(self) - self.bbox = bbox - self.transform = transform - self.set_children(['bbox', 'transform']) + self._bbox = bbox + self._transform = transform + self.set_children(['_bbox', '_transform']) self._points = None def __repr__(self): - return "TransformedBbox(%s, %s)" % (self.bbox, self.transform) + return "TransformedBbox(%s, %s)" % (self._bbox, self._transform) __str__ = __repr__ - def _do_invalidation(self, which_child, affine_only): + def _do_invalidation(self, affine_only): + result = self._points is None self._points = None + return result def get_points(self): if self._points is None: - self._points = self.transform.transform(self.bbox.get_points()) + self._points = self._transform.transform(self._bbox.get_points()) return self._points @@ -461,9 +477,6 @@ def __array__(self, *args, **kwargs): return self.get_matrix() - def _do_invalidation(self, which_child, affine_only): - self._inverted = None - #@staticmethod def _concat(a, b): return npy.dot(b, a) @@ -477,9 +490,6 @@ def get_matrix(self): raise NotImplementedError() - def transform_affine(self, points): - return self.transform(points) - def transform_non_affine(self, points): return points @@ -528,9 +538,11 @@ # print "".join(traceback.format_stack()) # print points mtx = self.get_matrix() - points = ma.asarray(values, npy.float_) + points = npy.asarray(values, npy.float_) return points * mtx[0,0] + mtx[0,1] + transform_affine = transform + def inverted(self): if self._inverted is None: mtx = self.get_matrix() @@ -575,7 +587,7 @@ def set_matrix(self, mtx): self._mtx = mtx self.invalidate() - + def set(self, other): self._mtx = other.get_matrix() self.invalidate() @@ -620,9 +632,11 @@ return "IntervalTransform(%s)" % (getattr(self._bbox, self._direction)) __str__ = __repr__ - def _do_invalidation(self, which_child, affine_only): + def _do_invalidation(self, affine_only): + result = self._mtx is None self._mtx = None - Affine1DBase._do_invalidation(self, which_child, affine_only) + self._inverted = None + return result def get_matrix(self): if self._mtx is None: @@ -678,12 +692,14 @@ # print "".join(traceback.format_stack()) # print points mtx = self.get_matrix() - points = ma.asarray(points, npy.float_) + points = npy.asarray(points, npy.float_) points = points.transpose() - points = ma.dot(mtx[0:2, 0:2], points) + points = npy.dot(mtx[0:2, 0:2], points) points = points + mtx[0:2, 2:] return points.transpose() + transform_affine = transform + def inverted(self): if self._inverted is None: mtx = self.get_matrix() @@ -801,19 +817,12 @@ def transform(self, points): return points + transform_affine = transform_non_affine = transform - def transform_affine(self, points): - return points - - def transform_non_affine(self, points): - return points - def get_affine(self): return self + inverted = get_affine - def inverted(self): - return self - class BlendedGenericTransform(Transform): input_dims = 2 @@ -857,14 +866,12 @@ y_points = y.transform(points[:, 1]) y_points = y_points.reshape((len(y_points), 1)) - return ma.concatenate((x_points, y_points), 1) - + return npy.concatenate((x_points, y_points), 1) + transform_non_affine = transform + def transform_affine(self, points): return points - def transform_non_affine(self, points): - return self.transform(points) - def get_affine(self): return IdentityTransform() @@ -892,9 +899,10 @@ return "BlendedAffine1D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - def _do_invalidation(self, which_child, affine_only): + def _do_invalidation(self, affine_only): + result = self._mtx is None self._mtx = None - Affine2DBase._do_invalidation(self, which_child, affine_only) + self._inverted = None def get_matrix(self): if self._mtx is None: @@ -928,9 +936,11 @@ return "BlendedAffine2D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - def _do_invalidation(self, which_child, affine_only): + def _do_invalidation(self, affine_only): + result = self._mtx is None self._mtx = None - Affine2DBase._do_invalidation(self, which_child, affine_only) + self._inverted = None + return result def get_matrix(self): if self._mtx is None: @@ -985,7 +995,7 @@ return self._b.transform_non_affine(self._a.transform_non_affine(points)) def get_affine(self): - return self._a.get_affine() + self._b.get_affine() + return CompositeAffine2D(self._a.get_affine(), self._b.get_affine()) def inverted(self): return CompositeGenericTransform(self._b.inverted(), self._a.inverted()) @@ -1009,9 +1019,11 @@ return "CompositeAffine2D(%s, %s)" % (self._a, self._b) __str__ = __repr__ - def _do_invalidation(self, which_child, affine_only): + def _do_invalidation(self, affine_only): + result = self._mtx is None self._mtx = None - Affine2DBase._do_invalidation(self, which_child, affine_only) + self._inverted = None + return result def get_matrix(self): if self._mtx is None: @@ -1117,10 +1129,12 @@ return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) __str__ = __repr__ - def _do_invalidation(self, which_child, affine_only): + def _do_invalidation(self, affine_only): + result = self._mtx is None self._mtx = None - Affine2DBase._do_invalidation(self, which_child, affine_only) - + self._inverted = None + return result + def is_separable(self): return True @@ -1148,21 +1162,19 @@ self._path = path self._transform = transform - self.set_children(['_transform']) self._transformed_path = None + self._last_id = transform.get_id() - def _do_invalidation(self, which_child, affine_only): - if not (affine_only[0].is_affine() or affine_only[0].is_bbox()): - self._transformed_path = None - def get_path_and_affine(self): - if self._transformed_path is None: + if (self._transformed_path is None or + self._last_id != self._transform.get_id()): vertices = self._transform.transform_non_affine(self._path.vertices) self._transformed_path = Path(vertices, self._path.codes) return self._transformed_path, self._transform.get_affine() def get_path(self): - if self._transformed_path is None: + if (self._transformed_path is None or + self._last_id != self._transform.get_id()): vertices = self._tranform.transform_non_affine(self._path.vertices) self._transformed_path = Path(vertices, self._path.codes) vertices = self._transform.transform_affine(self._transformed_path.vertices) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-25 19:54:01
|
Revision: 3892 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3892&view=rev Author: mdboom Date: 2007-09-25 12:53:56 -0700 (Tue, 25 Sep 2007) Log Message: ----------- Important bugfixes. Modified Paths: -------------- branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-25 19:08:51 UTC (rev 3891) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-25 19:53:56 UTC (rev 3892) @@ -499,7 +499,7 @@ lineFunc(renderer, gc, *self._transformed_path.get_path_and_affine()) # MGDTODO: Deal with markers - if self._marker is not None and False: + if self._marker is not None: gc = renderer.new_gc() self._set_gc_clip(gc) gc.set_foreground(self.get_markeredgecolor()) @@ -507,7 +507,7 @@ gc.set_alpha(self._alpha) funcname = self._markers.get(self._marker, '_draw_nothing') markerFunc = getattr(self, funcname) - markerFunc(renderer, gc, self._path) + markerFunc(renderer, gc, *self._transformed_path.get_path_and_affine()) #renderer.close_group('line2d') Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-09-25 19:08:51 UTC (rev 3891) +++ branches/transforms/lib/matplotlib/path.py 2007-09-25 19:53:56 UTC (rev 3892) @@ -65,7 +65,10 @@ def __repr__(self): return "Path(%s, %s)" % (self.vertices, self.codes) - + + def __len__(self): + return len(self._vertices) + def _get_codes(self): return self._codes codes = property(_get_codes) @@ -108,7 +111,7 @@ # This initial rotation is to make sure the polygon always # "points-up" theta += npy.pi / 2.0 - verts = npy.concatenate((npy.cos(theta), npy.sin(theta))) + verts = npy.concatenate((npy.cos(theta), npy.sin(theta)), 1) path = Path(verts) cls._unit_regular_polygons[numVertices] = path return path Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-25 19:08:51 UTC (rev 3891) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-25 19:53:56 UTC (rev 3892) @@ -24,7 +24,10 @@ _gid = 0 def __init__(self): - self._parents = WeakKeyDictionary() + # MGDTODO: I'd like to use a WeakKeyDictionary here, but it makes + # these instances uncopyable. As it stands, _parents grows + # unboundedly... Not a good idea. + self._parents = Set() self._children = Set() self._id = TransformNode._gid @@ -34,7 +37,7 @@ if not self._do_invalidation(affine_only): self._id = TransformNode._gid TransformNode._gid += 1 - for parent in self._parents.iterkeys(): + for parent in self._parents: parent.invalidate(affine_only) def _do_invalidation(self, affine_only): @@ -42,7 +45,7 @@ def set_children(self, children): for child in children: - getattr(self, child)._parents[self] = None + getattr(self, child)._parents.add(self) self._children = children def make_graphviz(self, fobj): @@ -1034,8 +1037,8 @@ def composite_transform_factory(a, b): - if isinstance(a, BboxTransform) and isinstance(b, BboxTransform): - return BboxTransform(a._boxin, b._boxout) +# if isinstance(a, BboxTransform) and isinstance(b, BboxTransform): +# return BboxTransform(a._boxin, b._boxout) if isinstance(a, AffineBase) and isinstance(b, AffineBase): return CompositeAffine2D(a, b) return CompositeGenericTransform(a, b) @@ -1162,19 +1165,23 @@ self._path = path self._transform = transform + self.set_children(['_transform']) self._transformed_path = None - self._last_id = transform.get_id() + + def _do_invalidation(self, affine_only): + if not affine_only: + self._transformed_path = None + return True def get_path_and_affine(self): - if (self._transformed_path is None or - self._last_id != self._transform.get_id()): + if self._transformed_path is None: vertices = self._transform.transform_non_affine(self._path.vertices) self._transformed_path = Path(vertices, self._path.codes) + return self._transformed_path, self._transform.get_affine() def get_path(self): - if (self._transformed_path is None or - self._last_id != self._transform.get_id()): + if self._transformed_path is None: vertices = self._tranform.transform_non_affine(self._path.vertices) self._transformed_path = Path(vertices, self._path.codes) vertices = self._transform.transform_affine(self._transformed_path.vertices) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-26 13:54:39
|
Revision: 3895 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3895&view=rev Author: mdboom Date: 2007-09-26 06:53:53 -0700 (Wed, 26 Sep 2007) Log Message: ----------- Fix log limits. For minor speed improvements. Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/scale.py branches/transforms/lib/matplotlib/ticker.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-09-26 13:41:15 UTC (rev 3894) +++ branches/transforms/lib/matplotlib/axes.py 2007-09-26 13:53:53 UTC (rev 3895) @@ -1095,18 +1095,9 @@ line._remove_method = lambda h: self.lines.remove(h) def _update_line_limits(self, line): - xdata = line.get_xdata(orig=False) - ydata = line.get_ydata(orig=False) + xydata = line.get_xydata() + self.update_datalim( xydata ) - if line.get_transform() != self.transData: - xys = self._get_verts_in_data_coords( - line.get_transform(), zip(xdata, ydata)) - xdata = npy.array([x for x,y in xys]) - ydata = npy.array([y for x,y in xys]) - - self.update_datalim_numerix( xdata, ydata ) - - def add_patch(self, p): """ Add a patch to the list of Axes patches; the clipbox will be @@ -1151,7 +1142,6 @@ xys = npy.asarray(xys) self.update_datalim_numerix(xys[:, 0], xys[:, 1]) - def update_datalim_numerix(self, x, y): 'Update the data lim bbox with seq of xy tups' # if no data is set currently, the bbox will ignore it's Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-09-26 13:41:15 UTC (rev 3894) +++ branches/transforms/lib/matplotlib/axis.py 2007-09-26 13:53:53 UTC (rev 3895) @@ -334,6 +334,9 @@ 'return the Interval instance for this axis view limits' return self.axes.viewLim.intervalx + def get_minpos(self): + return self.axes.dataLim.minposx + def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.dataLim.intervalx @@ -458,6 +461,9 @@ 'return the Interval instance for this axis view limits' return self.axes.viewLim.intervaly + def get_minpos(self): + return self.axes.dataLim.minposy + def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.dataLim.intervaly @@ -518,7 +524,13 @@ def get_scale(self): return self._scale.name - def set_scale(self, value, base=10, subs=None): + def set_scale(self, value, basex=10, subsx=None, basey=10, subsy=None): + if self.axis_name == 'x': + base = basex + subs = subsx + else: + base = basey + subs = subsy # MGDTODO: Move these settings (ticker etc.) into the scale class itself value = value.lower() assert value.lower() in ('log', 'linear') @@ -534,7 +546,7 @@ self.set_minor_locator(LogLocator(base,subs)) # MGDTODO: Pass base along self._scale = LogScale() - miny, maxy = getattr(self.axes.viewLim, 'interval' + self.axis) + miny, maxy = getattr(self.axes.viewLim, 'interval' + self.axis_name) if min(miny, maxy)<=0: self.axes.autoscale_view() @@ -980,7 +992,7 @@ class XAxis(Axis): __name__ = 'xaxis' - axis = 'x' + axis_name = 'x' def contains(self,mouseevent): """Test whether the mouse event occured in the x axis. @@ -1156,6 +1168,9 @@ 'return the Interval instance for this axis view limits' return self.axes.viewLim.intervalx + def get_minpos(self): + return self.axes.dataLim.minposx + def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.dataLim.intervalx @@ -1163,7 +1178,7 @@ class YAxis(Axis): __name__ = 'yaxis' - axis = 'y' + axis_name = 'y' def contains(self,mouseevent): """Test whether the mouse event occurred in the y axis. @@ -1357,6 +1372,9 @@ 'return the Interval instance for this axis view limits' return self.axes.viewLim.intervaly + def get_minpos(self): + return self.axes.dataLim.minposy + def get_data_interval(self): 'return the Interval instance for this axis data limits' return self.axes.dataLim.intervaly Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-09-26 13:41:15 UTC (rev 3894) +++ branches/transforms/lib/matplotlib/lines.py 2007-09-26 13:53:53 UTC (rev 3895) @@ -564,6 +564,9 @@ return self._yorig return self._y + def get_xydata(self): + return self._xy + def set_antialiased(self, b): """ True if line should be drawin with antialiased rendering Modified: branches/transforms/lib/matplotlib/scale.py =================================================================== --- branches/transforms/lib/matplotlib/scale.py 2007-09-26 13:41:15 UTC (rev 3894) +++ branches/transforms/lib/matplotlib/scale.py 2007-09-26 13:53:53 UTC (rev 3895) @@ -13,6 +13,66 @@ return IdentityTransform() class LogScale(ScaleBase): + class Log10Transform(Transform): + input_dims = 1 + output_dims = 1 + def __init__(self): + Transform.__init__(self) + + def is_separable(self): + return True + + def transform(self, a): + return ma.log10(ma.masked_where(a <= 0.0, a * 10.0)) + + def inverted(self): + return LogScale.InvertedLog10Transform() + + class InvertedLog10Transform(Transform): + input_dims = 1 + output_dims = 1 + def __init__(self): + Transform.__init__(self) + + def is_separable(self): + return True + + def transform(self, a): + return ma.power(10.0, a) / 10.0 + + def inverted(self): + return LogScale.Log10Transform() + + class Log2Transform(Transform): + input_dims = 1 + output_dims = 1 + def __init__(self): + Transform.__init__(self) + + def is_separable(self): + return True + + def transform(self, a): + return ma.log2(ma.masked_where(a <= 0.0, a * 2.0)) + + def inverted(self): + return LogScale.InvertedLog2Transform() + + class InvertedLog2Transform(Transform): + input_dims = 1 + output_dims = 1 + def __init__(self): + Transform.__init__(self) + + def is_separable(self): + return True + + def transform(self, a): + return ma.power(2.0, a) / 2.0 + + def inverted(self): + return LogScale.Log2Transform() + class LogTransform(Transform): input_dims = 1 output_dims = 1 @@ -26,12 +86,12 @@ def transform(self, a): if len(a) > 10: print "Log Transforming..." - return ma.log10(ma.masked_where(a <= 0.0, a * 10.0)) + return ma.log(ma.masked_where(a <= 0.0, a * self._base)) / npy.log(self._base) def inverted(self): return LogScale.InvertedLogTransform(self._base) - class InvertedLogTransform(Transform): + class InvertedLog2Transform(Transform): input_dims = 1 output_dims = 1 def __init__(self, base): @@ -42,14 +102,21 @@ return True def transform(self, a): - return ma.power(10.0, a) / 10.0 + return ma.power(self._base, a) / self._base def inverted(self): return LogScale.LogTransform(self._base) + + + def __init__(self, base=10): + if base == 10.0: + self._transform = self.Log10Transform() + elif base == 2.0: + self._transform = self.Log2Transform() + # MGDTODO: Natural log etc. + else: + self._transform = self.LogTransform(base) - def __init__(self, base=10): - self._transform = self.LogTransform(base) - def get_transform(self): return self._transform Modified: branches/transforms/lib/matplotlib/ticker.py =================================================================== --- branches/transforms/lib/matplotlib/ticker.py 2007-09-26 13:41:15 UTC (rev 3894) +++ branches/transforms/lib/matplotlib/ticker.py 2007-09-26 13:53:53 UTC (rev 3895) @@ -919,21 +919,21 @@ def autoscale(self): 'Try to choose the view limits intelligently' - vmin, vmax = self.axis.get_view_interval() + vmin, vmax = self.axis.get_data_interval() if vmax<vmin: vmin, vmax = vmax, vmin -# minpos = self.dataInterval.minpos() + minpos = self.axis.get_minpos() -# if minpos<=0: -# raise RuntimeError('No positive data to plot') + if minpos<=0: + raise RuntimeError('No positive data to plot') - # MGDTODO: Find a good way to track minpos - if vmin <= 0.0: - vmin = 0.1 - + if vmin <= minpos: + vmin = minpos + if not is_decade(vmin,self._base): vmin = decade_down(vmin,self._base) if not is_decade(vmax,self._base): vmax = decade_up(vmax,self._base) + if vmin==vmax: vmin = decade_down(vmin,self._base) vmax = decade_up(vmax,self._base) Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-26 13:41:15 UTC (rev 3894) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-26 13:53:53 UTC (rev 3895) @@ -224,6 +224,7 @@ def __init__(self, points): BboxBase.__init__(self) self._points = npy.asarray(points, npy.float_) + self._minpos = npy.array([0.0000001, 0.0000001]) self._invalid = False #@staticmethod @@ -252,15 +253,22 @@ return result def update_from_data(self, x, y, ignore=True): - if ignore: + if ignore: + self._points = npy.array( + [[x.min(), y.min()], [x.max(), y.max()]], + npy.float_) + self._minpos = npy.array( + [npy.where(x > 0.0, x, npy.inf).min(), npy.where(y > 0.0, y, npy.inf).min()], + npy.float_) + else: self._points = npy.array( - [[x.min(), y.min()], [x.max(), y.max()]], - npy.float_) - else: - self._points = npy.array( [[min(x.min(), self.xmin), min(y.min(), self.ymin)], [max(x.max(), self.xmax), max(y.max(), self.ymax)]], npy.float_) + minpos = npy.array( + [npy.where(x > 0.0, x, npy.inf).min(), npy.where(y > 0.0, y, npy.inf).min()], + npy.float_) + self._minpos = npy.minimum(minpos, self._minpos) self.invalidate() def _set_xmin(self, val): @@ -309,6 +317,18 @@ self.invalidate() bounds = property(BboxBase._get_bounds, _set_bounds) + def _get_minpos(self): + return self._minpos + minpos = property(_get_minpos) + + def _get_minposx(self): + return self._minpos[0] + minposx = property(_get_minposx) + + def _get_minposy(self): + return self._minpos[1] + minposy = property(_get_minposy) + def get_points(self): self._invalid = False return self._points @@ -541,7 +561,7 @@ # print "".join(traceback.format_stack()) # print points mtx = self.get_matrix() - points = npy.asarray(values, npy.float_) + # points = npy.asarray(values, npy.float_) return points * mtx[0,0] + mtx[0,1] transform_affine = transform @@ -695,10 +715,15 @@ # print "".join(traceback.format_stack()) # print points mtx = self.get_matrix() - points = npy.asarray(points, npy.float_) - points = points.transpose() - points = npy.dot(mtx[0:2, 0:2], points) - points = points + mtx[0:2, 2:] + if ma.isarray(points): + points = points.transpose() + points = ma.dot(mtx[0:2, 0:2], points) + points = points + mtx[0:2, 2:] + else: + points = npy.asarray(points, npy.float_) + points = points.transpose() + points = npy.dot(mtx[0:2, 0:2], points) + points = points + mtx[0:2, 2:] return points.transpose() transform_affine = transform @@ -869,7 +894,7 @@ y_points = y.transform(points[:, 1]) y_points = y_points.reshape((len(y_points), 1)) - return npy.concatenate((x_points, y_points), 1) + return ma.concatenate((x_points, y_points), 1) transform_non_affine = transform def transform_affine(self, points): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-09-26 14:09:29
|
Revision: 3897 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3897&view=rev Author: mdboom Date: 2007-09-26 07:08:12 -0700 (Wed, 26 Sep 2007) Log Message: ----------- Fix log transforms a little. Modified Paths: -------------- branches/transforms/lib/matplotlib/scale.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/scale.py =================================================================== --- branches/transforms/lib/matplotlib/scale.py 2007-09-26 13:55:41 UTC (rev 3896) +++ branches/transforms/lib/matplotlib/scale.py 2007-09-26 14:08:12 UTC (rev 3897) @@ -73,6 +73,36 @@ def inverted(self): return LogScale.Log2Transform() + class NaturalLogTransform(Transform): + input_dims = 1 + output_dims = 1 + def __init__(self): + Transform.__init__(self) + + def is_separable(self): + return True + + def transform(self, a): + return ma.log(ma.masked_where(a <= 0.0, a * npy.e)) + + def inverted(self): + return LogScale.InvertedNaturalLogTransform() + + class InvertedNaturalLogTransform(Transform): + input_dims = 1 + output_dims = 1 + def __init__(self): + Transform.__init__(self) + + def is_separable(self): + return True + + def transform(self, a): + return ma.power(npy.e, a) / npy.e + + def inverted(self): + return LogScale.Log2Transform() + class LogTransform(Transform): input_dims = 1 output_dims = 1 @@ -84,14 +114,12 @@ return True def transform(self, a): - if len(a) > 10: - print "Log Transforming..." return ma.log(ma.masked_where(a <= 0.0, a * self._base)) / npy.log(self._base) def inverted(self): return LogScale.InvertedLogTransform(self._base) - class InvertedLog2Transform(Transform): + class InvertedLogTransform(Transform): input_dims = 1 output_dims = 1 def __init__(self, base): @@ -113,7 +141,8 @@ self._transform = self.Log10Transform() elif base == 2.0: self._transform = self.Log2Transform() - # MGDTODO: Natural log etc. + elif base == npy.e: + self._transform = self.NaturalLogTransform() else: self._transform = self.LogTransform(base) Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-09-26 13:55:41 UTC (rev 3896) +++ branches/transforms/lib/matplotlib/transforms.py 2007-09-26 14:08:12 UTC (rev 3897) @@ -1069,15 +1069,6 @@ return CompositeGenericTransform(a, b) -class LogTransform(Transform): - input_dims = 1 - output_dims = 1 - - def transform(self, a): - m = ma.masked_where(a < 0, a) - return npy.log10(m) - - class TestPolarTransform(Transform): input_dims = 2 output_dims = 2 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-10-01 11:45:06
|
Revision: 3905 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3905&view=rev Author: mdboom Date: 2007-10-01 04:44:54 -0700 (Mon, 01 Oct 2007) Log Message: ----------- Move ticking/formatting defaults to scale.py. Speed improvements in transforms.py 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/cbook.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/scale.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-10-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/axes.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -23,6 +23,7 @@ from matplotlib import mlab from matplotlib import cm from matplotlib import patches as mpatches +from matplotlib import path as mpath from matplotlib import pbox as mpbox from matplotlib import quiver as mquiver from matplotlib import scale as mscale @@ -1461,7 +1462,7 @@ self.axesPatch.set_facecolor(color) ### data limits, ticks, tick labels, and formatting - + def get_xlim(self): 'Get the x axis range [xmin, xmax]' return self.viewLim.intervalx @@ -1503,11 +1504,6 @@ if xmin is None: xmin = old_xmin if xmax is None: xmax = old_xmax - # MGDTODO -# if (self.transData.get_funcx().get_type()==mtrans.LOG10 -# and min(xmin, xmax)<=0): -# raise ValueError('Cannot set nonpositive limits with log transform') - xmin, xmax = mtransforms.nonsingular(xmin, xmax, increasing=False) self.viewLim.intervalx = (xmin, xmax) @@ -1516,7 +1512,7 @@ # Call all of the other x-axes that are shared with this one for other in self._shared_x_axes.get_siblings(self): if other is not self: - other.set_xlim(self.viewLim.xmin, self.viewLim.xmax, emit=False) + other.set_xlim(self.viewLim.intervalx, emit=False) return xmin, xmax @@ -1634,7 +1630,7 @@ 'return the yaxis scale string: log or linear' return self.yaxis.get_scale() - def set_yscale(self, value, basey=10, subsy=None): + def set_yscale(self, value, **kwargs): """ SET_YSCALE(value, basey=10, subsy=None) @@ -1652,7 +1648,7 @@ ACCEPTS: ['log' | 'linear'] """ - self.yaxis.set_scale(value, basey, subsy) + self.yaxis.set_scale(value, **kwargs) self._update_transScale() def get_yticks(self): @@ -1788,6 +1784,32 @@ """ self._navigate_mode = b + def drag_pan(self, button, x, y, startx, starty, start_lim, start_trans): + if button == 1: + inverse = start_trans.inverted() + dx = startx - x + dy = starty - y + result = self.bbox.frozen().translated(dx, dy).transformed(inverse) + elif button == 3: + try: + inverse = start_trans.inverted() + dx = (startx - x) / float(self.bbox.width) + dy = (starty - y) / float(self.bbox.height) + xmin, ymin, xmax, ymax = start_lim.lbrt + + alpha = npy.power(10.0, (dx, dy)) + start = inverse.transform_point((startx, starty)) + lim_points = start_lim.get_points() + result = start + alpha * (lim_points - start) + result = mtransforms.Bbox(result) + except OverflowError: + warnings.warn('Overflow while panning') + return + + # MGDTODO: Could we do this with a single set_lim? + self.set_xlim(*result.intervalx) + self.set_ylim(*result.intervaly) + def get_cursor_props(self): """return the cursor props as a linewidth, color tuple where linewidth is a float and color is an RGBA tuple""" @@ -5658,8 +5680,253 @@ self, fig, [self.figLeft, self.figBottom, self.figW, self.figH], **kwargs) +###################################################################### +# New Polar Axes + +class PolarAxes(Axes): + class PolarTransform(mtransforms.Transform): + input_dims = 2 + output_dims = 2 + is_separable = False + def transform(self, tr): + xy = npy.zeros(tr.shape, npy.float_) + t = tr[:, 0:1] + r = tr[:, 1:2] + x = xy[:, 0:1] + y = xy[:, 1:2] + x += r * npy.cos(t) + y += r * npy.sin(t) + return xy + transform_non_affine = transform + def interpolate(self, a, steps): + steps = npy.floor(steps) + new_length = ((len(a) - 1) * steps) + 1 + new_shape = list(a.shape) + new_shape[0] = new_length + result = npy.zeros(new_shape, a.dtype) + + result[0] = a[0] + a0 = a[0:-1] + a1 = a[1: ] + delta = ((a1 - a0) / steps) + + for i in range(1, int(steps)+1): + result[i::steps] = delta * i + a0 + + return result + +# def transform_path(self, path): +# twopi = 2.0 * npy.pi +# halfpi = 0.5 * npy.pi + +# vertices = path.vertices +# t0 = vertices[0:-1, 0] +# t1 = vertices[1: , 0] +# td = npy.where(t1 > t0, t1 - t0, twopi - (t0 - t1)) +# maxtd = td.max() +# interpolate = npy.ceil(maxtd / halfpi) +# if interpolate > 1.0: +# vertices = self.interpolate(vertices, interpolate) + +# vertices = self.transform(vertices) + +# result = npy.zeros((len(vertices) * 3 - 2, 2), npy.float_) +# codes = mpath.Path.CURVE4 * npy.ones((len(vertices) * 3 - 2, ), mpath.Path.code_type) +# result[0] = vertices[0] +# codes[0] = mpath.Path.MOVETO + +# kappa = 4.0 * ((npy.sqrt(2.0) - 1.0) / 3.0) +# kappa = 0.5 + +# p0 = vertices[0:-1] +# p1 = vertices[1: ] + +# x0 = p0[:, 0:1] +# y0 = p0[:, 1: ] +# b0 = ((y0 - x0) - y0) / ((x0 + y0) - x0) +# a0 = y0 - b0*x0 + +# x1 = p1[:, 0:1] +# y1 = p1[:, 1: ] +# b1 = ((y1 - x1) - y1) / ((x1 + y1) - x1) +# a1 = y1 - b1*x1 + +# x = -(a0-a1) / (b0-b1) +# y = a0 + b0*x + +# xk = (x - x0) * kappa + x0 +# yk = (y - y0) * kappa + y0 + +# result[1::3, 0:1] = xk +# result[1::3, 1: ] = yk + +# xk = (x - x1) * kappa + x1 +# yk = (y - y1) * kappa + y1 + +# result[2::3, 0:1] = xk +# result[2::3, 1: ] = yk + +# result[3::3] = p1 + +# print vertices[-2:] +# print result[-2:] + +# return mpath.Path(result, codes) + +# twopi = 2.0 * npy.pi +# halfpi = 0.5 * npy.pi + +# vertices = path.vertices +# t0 = vertices[0:-1, 0] +# t1 = vertices[1: , 0] +# td = npy.where(t1 > t0, t1 - t0, twopi - (t0 - t1)) +# maxtd = td.max() +# interpolate = npy.ceil(maxtd / halfpi) + +# print "interpolate", interpolate +# if interpolate > 1.0: +# vertices = self.interpolate(vertices, interpolate) + +# result = npy.zeros((len(vertices) * 3 - 2, 2), npy.float_) +# codes = mpath.Path.CURVE4 * npy.ones((len(vertices) * 3 - 2, ), mpath.Path.code_type) +# result[0] = vertices[0] +# codes[0] = mpath.Path.MOVETO + +# kappa = 4.0 * ((npy.sqrt(2.0) - 1.0) / 3.0) +# tkappa = npy.arctan(kappa) +# hyp_kappa = npy.sqrt(kappa*kappa + 1.0) + +# t0 = vertices[0:-1, 0] +# t1 = vertices[1: , 0] +# r0 = vertices[0:-1, 1] +# r1 = vertices[1: , 1] + +# td = npy.where(t1 > t0, t1 - t0, twopi - (t0 - t1)) +# td_scaled = td / (npy.pi * 0.5) +# rd = r1 - r0 +# r0kappa = r0 * kappa * td_scaled +# r1kappa = r1 * kappa * td_scaled +# ravg_kappa = ((r1 + r0) / 2.0) * kappa * td_scaled + +# result[1::3, 0] = t0 + (tkappa * td_scaled) +# result[1::3, 1] = r0*hyp_kappa +# # result[1::3, 1] = r0 / npy.cos(tkappa * td_scaled) # npy.sqrt(r0*r0 + ravg_kappa*ravg_kappa) + +# result[2::3, 0] = t1 - (tkappa * td_scaled) +# result[2::3, 1] = r1*hyp_kappa +# # result[2::3, 1] = r1 / npy.cos(tkappa * td_scaled) # npy.sqrt(r1*r1 + ravg_kappa*ravg_kappa) + +# result[3::3, 0] = t1 +# result[3::3, 1] = r1 + +# print vertices[:6], result[:6], t0[:6], t1[:6], td[:6], td_scaled[:6], tkappa +# result = self.transform(result) +# return mpath.Path(result, codes) +# transform_path_non_affine = transform_path + + def inverted(self): + return PolarAxes.InvertedPolarTransform() + + class PolarAffine(mtransforms.Affine2DBase): + def __init__(self, limits): + mtransforms.Affine2DBase.__init__(self) + self._limits = limits + self.set_children(limits) + self._mtx = None + + def get_matrix(self): + if self._invalid: + xmin, ymin, xmax, ymax = self._limits.lbrt + affine = mtransforms.Affine2D().rotate(xmin).scale(0.5 / ymax).translate(0.5, 0.5) + self._mtx = affine.get_matrix() + self._inverted = None + self._invalid = 0 + return self._mtx + + class InvertedPolarTransform(mtransforms.Transform): + input_dims = 2 + output_dims = 2 + is_separable = False + + def transform(self, xy): + x = xy[:, 0:1] + y = xy[:, 1:] + r = npy.sqrt(x*x + y*y) + theta = npy.arccos(x / r) + theta = npy.where(y < 0, 2 * npy.pi - theta, theta) + return npy.concatenate((theta, r), 1) + + def inverted(self): + return PolarAxes.PolarTransform() + + def _set_lim_and_transforms(self): + """ + set the dataLim and viewLim BBox attributes and the + transData and transAxes Transformation attributes + """ + self.dataLim = mtransforms.Bbox.unit() + self.viewLim = mtransforms.Bbox.unit() + self.transAxes = mtransforms.BboxTransform( + mtransforms.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 = mtransforms.TransformWrapper(mtransforms.IdentityTransform()) + + # A (possibly non-linear) projection on the (already scaled) data + self.transProjection = self.PolarTransform() + + # 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 + + def drag_pan(self, button, x, y, startx, starty, start_lim, start_trans): + if button == 1: + inverse = start_trans.inverted() + startt, startr = inverse.transform_point((startx, starty)) + t, r = inverse.transform_point((x, y)) + + scale = r / startr + self.set_ylim(start_lim.ymin, start_lim.ymax / scale) + + dt0 = t - startt + dt1 = startt - t + if abs(dt1) < abs(dt0): + dt = abs(dt1) * sign(dt0) * -1.0 + else: + dt = dt0 * -1.0 + self.set_xlim(start_lim.xmin - dt, start_lim.xmin - dt + npy.pi*2.0) + + def set_rmax(self, rmax): + self.viewLim.maxy = rmax + +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) """ # this is some discarded code I was using to find the minimum positive Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-10-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/axis.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -20,7 +20,7 @@ from transforms import Affine2D, Bbox, blended_transform_factory, interval_contains, \ interval_contains_open, IntervalTransform from patches import bbox_artist -from scale import LinearScale, LogScale +from scale import scale_factory import matplotlib.units as units #import pdb @@ -514,41 +514,19 @@ self.majorTicks = [] self.minorTicks = [] self.pickradius = pickradius - self._scale = LinearScale() self.cla() - + self.set_scale('linear') + def get_transform(self): return self._scale.get_transform() - + def get_scale(self): return self._scale.name - def set_scale(self, value, basex=10, subsx=None, basey=10, subsy=None): - if self.axis_name == 'x': - base = basex - subs = subsx - else: - base = basey - subs = subsy - # MGDTODO: Move these settings (ticker etc.) into the scale class itself - value = value.lower() - assert value.lower() in ('log', 'linear') - if value == 'linear': - self.set_major_locator(AutoLocator()) - self.set_major_formatter(ScalarFormatter()) - self.set_minor_locator(NullLocator()) - self.set_minor_formatter(NullFormatter()) - self._scale = LinearScale() - elif value == 'log': - self.set_major_locator(LogLocator(base)) - self.set_major_formatter(LogFormatterMathtext(base)) - self.set_minor_locator(LogLocator(base,subs)) - # MGDTODO: Pass base along - self._scale = LogScale() - miny, maxy = getattr(self.axes.viewLim, 'interval' + self.axis_name) - if min(miny, maxy)<=0: - self.axes.autoscale_view() + def set_scale(self, value, **kwargs): + self._scale = scale_factory(value, self, **kwargs) + self._scale.set_default_locators_and_formatters(self) def get_children(self): children = [self.label] Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-10-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -1483,7 +1483,7 @@ self._lastCursor = cursors.SELECT_REGION if self._xypress: x, y = event.x, event.y - lastx, lasty, a, ind, lim, trans= self._xypress[0] + lastx, lasty, a, ind, lim, trans = self._xypress[0] self.draw_rubberband(event, x, y, lastx, lasty) elif (self._active=='PAN' and self._lastCursor != cursors.MOVE): @@ -1558,10 +1558,7 @@ 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(x, y) and a.get_navigate(): - xmin, xmax = a.get_xlim() - ymin, ymax = a.get_ylim() - lim = xmin, xmax, ymin, ymax - self._xypress.append((x, y, a, i, lim, copy.deepcopy(a.transData))) + self._xypress.append((x, y, a, i, a.viewLim.frozen(), a.transData.frozen())) self.canvas.mpl_disconnect(self._idDrag) self._idDrag=self.canvas.mpl_connect('motion_notify_event', self.drag_pan) @@ -1585,10 +1582,7 @@ 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(x, y) and a.get_navigate(): - xmin, xmax = a.get_xlim() - ymin, ymax = a.get_ylim() - lim = xmin, xmax, ymin, ymax - self._xypress.append(( x, y, a, i, lim, copy.deepcopy(a.transData) )) + self._xypress.append(( x, y, a, i, a.viewLim.frozen(), a.transData.frozen())) self.press(event) @@ -1648,38 +1642,12 @@ dx=dx/abs(dx)*abs(dy) return (dx,dy) - for cur_xypress in self._xypress: - lastx, lasty, a, ind, lim, trans = cur_xypress - xmin, xmax, ymin, ymax = lim + for lastx, lasty, a, ind, old_lim, old_trans in self._xypress: #safer to use the recorded button at the press than current button: #multiple button can get pressed during motion... - if self._button_pressed==1: - inverse = trans.inverted() - dx, dy = event.x - lastx, event.y - lasty - dx, dy = format_deltas(event, dx, dy) - delta = npy.array([[dx, dy], [dx, dy]], npy.float_) - bbox = transforms.Bbox(a.bbox.get_points() - delta) - result = bbox.transformed(inverse) - elif self._button_pressed==3: - try: - inverse = trans.inverted() - dx=(lastx-event.x)/float(a.bbox.width) - dy=(lasty-event.y)/float(a.bbox.height) - alphax = pow(10.0, dx) - alphay = pow(10.0, dy) - # MGDTODO: Make better use of numpy - lastx, lasty = inverse.transform_point((lastx, lasty)) - xmin = (lastx + alphax * (xmin - lastx)) - xmax = (lastx + alphax * (xmax - lastx)) - ymin = (lasty + alphay * (ymin - lasty)) - ymax = (lasty + alphay * (ymax - lasty)) - result = transforms.Bbox.from_lbrt(xmin, ymin, xmax, ymax) - except OverflowError: - warnings.warn('Overflow while panning') - return - a.set_xlim(*result.intervalx) - a.set_ylim(*result.intervaly) - + dx, dy = event.x - lastx, event.y - lasty + dx, dy = format_deltas(event, dx, dy) + a.drag_pan(self._button_pressed, lastx + dx, lasty + dy, lastx, lasty, old_lim, old_trans) 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-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -130,16 +130,16 @@ if __debug__: verbose.report('RendererAgg.__init__ done', 'debug-annoying') - # MGDTODO: This is a hack for now to allow for arbitrary transformations + # 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, rgbFace) + assert trans.is_affine + self._renderer.draw_path(gc, path, trans.frozen(), rgbFace) - # MGDTODO: This is a hack for now to allow for arbitrary transformations + # 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() - self._renderer.draw_markers(gc, marker_path, marker_trans, path, trans, rgbFace) + assert marker_trans.is_affine + assert trans.is_affine + self._renderer.draw_markers(gc, marker_path, marker_trans.frozen(), path, trans.frozen(), rgbFace) def draw_mathtext(self, gc, x, y, s, prop, angle): """ Modified: branches/transforms/lib/matplotlib/cbook.py =================================================================== --- branches/transforms/lib/matplotlib/cbook.py 2007-10-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/cbook.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -7,10 +7,11 @@ import time, datetime import numpy as npy -try: set +try: + set = set except NameError: from sets import Set as set - + major, minor1, minor2, s, tmp = sys.version_info Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-10-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/lines.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -496,7 +496,7 @@ funcname = self._lineStyles.get(self._linestyle, '_draw_nothing') lineFunc = getattr(self, funcname) - lineFunc(renderer, gc, *self._transformed_path.get_path_and_affine()) + lineFunc(renderer, gc, *self._transformed_path.get_transformed_path_and_affine()) # MGDTODO: Deal with markers if self._marker is not None: @@ -507,7 +507,7 @@ gc.set_alpha(self._alpha) funcname = self._markers.get(self._marker, '_draw_nothing') markerFunc = getattr(self, funcname) - markerFunc(renderer, gc, *self._transformed_path.get_path_and_affine()) + markerFunc(renderer, gc, *self._transformed_path.get_transformed_path_and_affine()) #renderer.close_group('line2d') Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-10-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/path.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -1,6 +1,8 @@ import numpy as npy from numpy import ma as ma +KAPPA = 4.0 * (npy.sqrt(2) - 1) / 3.0 + class Path(object): # Path codes STOP = 0 # 1 vertex @@ -122,7 +124,7 @@ def unit_circle(cls): # MGDTODO: Optimize? if cls._unit_circle is None: - offset = 4.0 * (npy.sqrt(2) - 1) / 3.0 + offset = KAPPA vertices = npy.array( [[-1.0, 0.0], Modified: branches/transforms/lib/matplotlib/scale.py =================================================================== --- branches/transforms/lib/matplotlib/scale.py 2007-10-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/scale.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -2,25 +2,38 @@ from numpy import ma from numpy.linalg import inv +from ticker import NullFormatter, FixedFormatter, ScalarFormatter, \ + LogFormatter, LogFormatterMathtext +from ticker import NullLocator, FixedLocator, LinearLocator, LogLocator, AutoLocator from transforms import Affine1DBase, IntervalTransform, Transform, \ composite_transform_factory, IdentityTransform class ScaleBase(object): - pass + def set_default_locators_and_formatters(self, axis): + raise NotImplementedError + +class LinearScale(ScaleBase): + name = 'linear' + + def __init__(self, axis, **kwargs): + pass -class LinearScale(ScaleBase): + def set_default_locators_and_formatters(self, axis): + axis.set_major_locator(AutoLocator()) + axis.set_major_formatter(ScalarFormatter()) + axis.set_minor_locator(NullLocator()) + axis.set_minor_formatter(NullFormatter()) + def get_transform(self): return IdentityTransform() class LogScale(ScaleBase): + name = 'log' + class Log10Transform(Transform): input_dims = 1 output_dims = 1 - def __init__(self): - Transform.__init__(self) - - def is_separable(self): - return True + is_separable = True def transform(self, a): return ma.log10(ma.masked_where(a <= 0.0, a * 10.0)) @@ -31,11 +44,7 @@ class InvertedLog10Transform(Transform): input_dims = 1 output_dims = 1 - def __init__(self): - Transform.__init__(self) - - def is_separable(self): - return True + is_separable = True def transform(self, a): return ma.power(10.0, a) / 10.0 @@ -46,11 +55,7 @@ class Log2Transform(Transform): input_dims = 1 output_dims = 1 - def __init__(self): - Transform.__init__(self) - - def is_separable(self): - return True + is_separable = True def transform(self, a): return ma.log2(ma.masked_where(a <= 0.0, a * 2.0)) @@ -61,11 +66,7 @@ class InvertedLog2Transform(Transform): input_dims = 1 output_dims = 1 - def __init__(self): - Transform.__init__(self) - - def is_separable(self): - return True + is_separable = True def transform(self, a): return ma.power(2.0, a) / 2.0 @@ -76,12 +77,8 @@ class NaturalLogTransform(Transform): input_dims = 1 output_dims = 1 - def __init__(self): - Transform.__init__(self) - - def is_separable(self): - return True - + is_separable = True + def transform(self, a): return ma.log(ma.masked_where(a <= 0.0, a * npy.e)) @@ -91,12 +88,8 @@ class InvertedNaturalLogTransform(Transform): input_dims = 1 output_dims = 1 - def __init__(self): - Transform.__init__(self) - - def is_separable(self): - return True - + is_separable = True + def transform(self, a): return ma.power(npy.e, a) / npy.e @@ -106,12 +99,11 @@ class LogTransform(Transform): input_dims = 1 output_dims = 1 + is_separable = True + def __init__(self, base): Transform.__init__(self) self._base = base - - def is_separable(self): - return True def transform(self, a): return ma.log(ma.masked_where(a <= 0.0, a * self._base)) / npy.log(self._base) @@ -122,21 +114,27 @@ class InvertedLogTransform(Transform): input_dims = 1 output_dims = 1 + is_separable = True + def __init__(self, base): Transform.__init__(self) self._base = base - def is_separable(self): - return True - def transform(self, a): return ma.power(self._base, a) / self._base def inverted(self): return LogScale.LogTransform(self._base) + - - def __init__(self, base=10): + def __init__(self, axis, **kwargs): + if axis.axis_name == 'x': + base = kwargs.pop('basex') + subs = kwargs.pop('subsx') + else: + base = kwargs.pop('basey') + subs = kwargs.pop('subsy') + if base == 10.0: self._transform = self.Log10Transform() elif base == 2.0: @@ -145,16 +143,30 @@ self._transform = self.NaturalLogTransform() else: self._transform = self.LogTransform(base) + + self._base = base + self._subs = subs + + def set_default_locators_and_formatters(self, axis): + axis.set_major_locator(LogLocator(self._base)) + axis.set_major_formatter(LogFormatterMathtext(self._base)) + axis.set_minor_locator(LogLocator(self._base, self._subs)) + axis.set_minor_formatter(NullFormatter()) def get_transform(self): return self._transform _scale_mapping = { - 'linear': LinearScale, - 'log': LogScale + 'linear' : LinearScale, + 'log' : LogScale } -def scale_factory(scale, viewLim, direction): +def scale_factory(scale, axis, **kwargs): + scale = scale.lower() if scale is None: scale = 'linear' - return _scale_mapping[scale](viewLim, direction) + + if not _scale_mapping.has_key(scale): + raise ValueError("Unknown scale type '%s'" % scale) + + return _scale_mapping[scale](axis, **kwargs) Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-10-01 07:06:43 UTC (rev 3904) +++ branches/transforms/lib/matplotlib/transforms.py 2007-10-01 11:44:54 UTC (rev 3905) @@ -7,49 +7,57 @@ import numpy as npy from numpy import ma as ma from numpy.linalg import inv -from sets import Set -from weakref import WeakKeyDictionary +from copy import deepcopy +from math import sqrt +from weakref import ref, WeakKeyDictionary + +import cbook from path import Path DEBUG = False -# MGDTODO: This creates a ton of cyclical references. We may want to -# consider using weak references - # MGDTODO: deep copying is probably incorrect wrt the parent/child # relationships class TransformNode(object): _gid = 0 + + is_affine = False + is_bbox = False def __init__(self): - # MGDTODO: I'd like to use a WeakKeyDictionary here, but it makes - # these instances uncopyable. As it stands, _parents grows - # unboundedly... Not a good idea. - self._parents = Set() - self._children = Set() + self._parents = WeakKeyDictionary() self._id = TransformNode._gid + TransformNode._gid += 1 + self._invalid = 1 + self._children = [] - def invalidate(self, affine_only=None): - if affine_only is None: - affine_only = self.is_affine() or self.is_bbox() - if not self._do_invalidation(affine_only): - self._id = TransformNode._gid - TransformNode._gid += 1 - for parent in self._parents: - parent.invalidate(affine_only) - - def _do_invalidation(self, affine_only): - return False + def invalidate(self): + if self._invalid: + return - def set_children(self, children): + value = (self.is_affine or self.is_bbox) and 2 or 1 + stack = [self] + + while len(stack): + root = stack.pop() + if root._invalid == 0: + root._id = TransformNode._gid + TransformNode._gid += 1 + root._invalid = value + stack.extend(root._parents.keys()) + + def set_children(self, *children): for child in children: - getattr(self, child)._parents.add(self) + child._parents[self] = None self._children = children + def frozen(self): + return self + def make_graphviz(self, fobj): - seen = Set() + seen = cbook.set() def recurse(root): if root in seen: @@ -57,30 +65,22 @@ seen.add(root) fobj.write('%s [label="%s"];\n' % (hash(root), root.__class__.__name__)) - if root.is_affine(): + if root.is_affine: fobj.write('%s [style=filled, color=".7 .7 .9"];\n' % hash(root)) - elif root.is_bbox(): + elif root.is_bbox: fobj.write('%s [style=filled, color=".9 .9 .7"];\n' % hash(root)) - for child_name in root._children: - child = getattr(root, child_name) - fobj.write('%s -> %s [label="%s"];\n' % ( + for child in root._children: + fobj.write('%s -> %s;\n' % ( hash(root), - hash(child), - child_name)) + hash(child))) recurse(child) fobj.write("digraph G {\n") recurse(self) fobj.write("}\n") - - def is_affine(self): - return False - def is_bbox(self): - return False - def get_id(self): return self._id @@ -89,13 +89,14 @@ ''' This is the read-only part of a bounding-box ''' + is_bbox = True def __init__(self): TransformNode.__init__(self) - def is_bbox(self): - return True - + def frozen(self): + return Bbox(self.get_points().copy()) + def __array__(self): return self.get_points() @@ -225,7 +226,6 @@ BboxBase.__init__(self) self._points = npy.asarray(points, npy.float_) self._minpos = npy.array([0.0000001, 0.0000001]) - self._invalid = False #@staticmethod def unit(): @@ -247,11 +247,6 @@ return 'Bbox(%s)' % repr(self._points) __str__ = __repr__ - def _do_invalidation(self, affine_only): - result = self._invalid - self._invalid = True - return result - def update_from_data(self, x, y, ignore=True): if ignore: self._points = npy.array( @@ -330,7 +325,7 @@ minposy = property(_get_minposy) def get_points(self): - self._invalid = False + self._invalid = 0 return self._points def set_points(self, points): @@ -349,6 +344,9 @@ a = npy.array([[-deltaw, -deltah], [deltaw, deltah]]) return Bbox(self._points + a) + def translated(self, tx, ty): + return Bbox(self._points + (tx, ty)) + #@staticmethod def union(bboxes): """ @@ -362,24 +360,22 @@ return bboxes[0] bbox = bboxes[0] - xmin = bbox.xmin - ymin = bbox.ymin - xmax = bbox.xmax - ymax = bbox.ymax + xmin0, ymin0, xmax0, ymax0 = bbox.bounds for bbox in bboxes[1:]: - xmin = min(xmin, bbox.xmin) - ymin = min(ymin, bbox.ymin) - xmax = max(xmax, bbox.xmax) - ymax = max(ymax, bbox.ymax) + xmin, ymin, xmax, ymax = bbox.bounds + xmin0 = min(xmin0, xmin) + ymin0 = min(ymin0, ymin) + xmax0 = max(xmax0, xmax) + ymax0 = max(ymax0, ymax) - return Bbox.from_lbrt(xmin, ymin, xmax, ymax) + return Bbox.from_lbrt(xmin0, ymin0, xmax0, ymax0) union = staticmethod(union) class TransformedBbox(BboxBase): def __init__(self, bbox, transform): - assert bbox.is_bbox() + assert bbox.is_bbox assert isinstance(transform, Transform) assert transform.input_dims == 2 assert transform.output_dims == 2 @@ -387,31 +383,26 @@ BboxBase.__init__(self) self._bbox = bbox self._transform = transform - self.set_children(['_bbox', '_transform']) + self.set_children(bbox, transform) self._points = None def __repr__(self): return "TransformedBbox(%s, %s)" % (self._bbox, self._transform) __str__ = __repr__ - def _do_invalidation(self, affine_only): - result = self._points is None - self._points = None - return result - def get_points(self): - if self._points is None: + if self._invalid: self._points = self._transform.transform(self._bbox.get_points()) + self._invalid = 0 return self._points - + class Transform(TransformNode): + is_separable = False + def __init__(self): TransformNode.__init__(self) - def is_separable(self): - return False - def __add__(self, other): if isinstance(other, Transform): return composite_transform_factory(self, other) @@ -428,17 +419,26 @@ raise NotImplementedError def transform_affine(self, points): - raise NotImplementedError + return points def transform_non_affine(self, points): - raise NotImplementedError + return self.transform(points) def get_affine(self): - raise NotImplementedError + return IdentityTransform() def transform_point(self, point): return self.transform(npy.asarray([point]))[0] + def transform_path(self, path): + return Path(self.transform(path.vertices), path.codes) + + def transform_path_affine(self, path): + return path + + def transform_path_non_affine(self, path): + return Path(self.transform_non_affine(path.vertices), path.codes) + def has_inverse(self): raise NotImplementedError() @@ -454,24 +454,35 @@ self.input_dims = child.input_dims self.output_dims = child.output_dims self._child = child - self.set_children(['_child']) + self.set_children(child) def __repr__(self): return "TransformWrapper(%r)" % self._child __str__ = __repr__ - + + def frozen(self): + return self._child.frozen() + def set(self, child): assert child.input_dims == self.input_dims assert child.output_dims == self.output_dims self._child = child - self.set_children(['_child']) + self.set_children(child) + self._invalid = 0 self.invalidate() + self._invalid = 0 - def is_separable(self): - return self._child.is_separable() + def _get_is_separable(self): + return self._child.is_separable + is_separable = property(_get_is_separable) - def is_affine(self): - return self._child.is_affine() + def _get_is_affine(self): + return self._child.is_affine + is_affine = property(_get_is_affine) + + def get_matrix(self): + assert self._child.is_affine + return self._child.get_matrix() def transform(self, points): return self._child.transform(points) @@ -482,6 +493,15 @@ def transform_non_affine(self, points): return self._child.transform_non_affine(points) + def transform_path(self, path): + return self._child.transform_path(path) + + def transform_path_affine(self, path): + return self._child.transform_path_affine(path) + + def transform_path_non_affine(self, path): + return self._child.transform_path_non_affine(path) + def get_affine(self): return self._child.get_affine() @@ -490,13 +510,12 @@ class AffineBase(Transform): + is_affine = True + def __init__(self): Transform.__init__(self) self._inverted = None - def is_affine(self): - return True - def __array__(self, *args, **kwargs): return self.get_matrix() @@ -507,7 +526,7 @@ #@staticmethod def concat(a, b): - return Affine1D(Affine1D._concat(a.get_matrix(), b.get_matrix())) + return Affine1D(npy.dot(b.get_matrix(), a.get_matrix())) concat = staticmethod(concat) def get_matrix(self): @@ -516,6 +535,12 @@ def transform_non_affine(self, points): return points + def transform_path_affine(self, path): + return self.transform_path(path) + + def transform_path_non_affine(self, path): + return path + def get_affine(self): return self @@ -523,12 +548,13 @@ class Affine1DBase(AffineBase): input_dims = 1 output_dims = 1 + is_separable = True def __init__(self): AffineBase.__init__(self) - def is_separable(self): - return True + def frozen(self): + return Affine1D(self.get_matrix().copy()) def __array__(self, *args, **kwargs): return self.get_matrix() @@ -539,10 +565,7 @@ #@staticmethod def matrix_from_values(a, b): - affine = npy.zeros((2, 2), npy.float_) - affine[0, :] = (a, b) - affine[1, 1] = 1 - return affine + return npy.array([[a, b], [0.0, 1.0]], npy.float_) matrix_from_values = staticmethod(matrix_from_values) def transform(self, values): @@ -561,15 +584,16 @@ # print "".join(traceback.format_stack()) # print points mtx = self.get_matrix() - # points = npy.asarray(values, npy.float_) + points = npy.asarray(values, npy.float_) return points * mtx[0,0] + mtx[0,1] transform_affine = transform def inverted(self): - if self._inverted is None: + if self._inverted is None or self._invalid: mtx = self.get_matrix() self._inverted = Affine1D(inv(mtx)) + self._invalid = 0 return self._inverted @@ -605,6 +629,7 @@ from_values = staticmethod(from_values) def get_matrix(self): + self._invalid = 0 return self._mtx def set_matrix(self, mtx): @@ -635,37 +660,29 @@ self.invalidate() return self - def is_separable(self): - mtx = self.get_matrix() - return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 - class IntervalTransform(Affine1DBase): def __init__(self, bbox, direction): assert direction in ('x', 'y') - assert bbox.is_bbox() + assert bbox.is_bbox Affine1DBase.__init__(self) self._bbox = bbox self._direction = "interval" + direction - self.set_children(['_bbox']) + self.set_children(bbox) self._mtx = None def __repr__(self): return "IntervalTransform(%s)" % (getattr(self._bbox, self._direction)) __str__ = __repr__ - def _do_invalidation(self, affine_only): - result = self._mtx is None - self._mtx = None - self._inverted = None - return result - def get_matrix(self): - if self._mtx is None: + if self._invalid: min, max = getattr(self._bbox, self._direction) self._mtx = inv(npy.array([[max - min, min], [0.0, 1.0]], npy.float_)) + self._inverted = None + self._invalid = 0 return self._mtx @@ -676,6 +693,9 @@ def __init__(self): AffineBase.__init__(self) + def frozen(self): + return Affine2D(self.get_matrix().copy()) + def is_separable(self): mtx = self.get_matrix() return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 @@ -689,11 +709,7 @@ #@staticmethod def matrix_from_values(a, b, c, d, e, f): - affine = npy.zeros((3, 3), npy.float_) - affine[0, ] = a, c, e - affine[1, ] = b, d, f - affine[2, 2] = 1 - return affine + return npy.array([[a, c, e], [b, d, f], [0.0, 0.0, 1.0]], npy.float_) matrix_from_values = staticmethod(matrix_from_values) def transform(self, points): @@ -715,7 +731,7 @@ # print "".join(traceback.format_stack()) # print points mtx = self.get_matrix() - if ma.isarray(points): + if ma.isMaskedArray(points): points = points.transpose() points = ma.dot(mtx[0:2, 0:2], points) points = points + mtx[0:2, 2:] @@ -729,9 +745,10 @@ transform_affine = transform def inverted(self): - if self._inverted is None: + if self._inverted is None or self._invalid: mtx = self.get_matrix() self._inverted = Affine2D(inv(mtx)) + self._invalid = 0 return self._inverted @@ -768,6 +785,7 @@ from_values = staticmethod(from_values) def get_matrix(self): + self._invalid = 0 return self._mtx def set_matrix(self, mtx): @@ -791,8 +809,8 @@ def rotate(self, theta): a = npy.cos(theta) b = npy.sin(theta) - rotate_mtx = self.matrix_from_values(a, b, -b, a, 0, 0) - self._mtx = self._concat(self._mtx, rotate_mtx) + rotate_mtx = npy.array([[a, -b, 0.0], [b, a, 0.0], [0.0, 0.0, 1.0]], npy.float_) + self._mtx = npy.dot(rotate_mtx, self._mtx) self.invalidate() return self @@ -806,30 +824,33 @@ return self.translate(-x, -y).rotate_deg(degrees).translate(x, y) def translate(self, tx, ty): - translate_mtx = self.matrix_from_values(1., 0., 0., 1., tx, ty) - self._mtx = self._concat(self._mtx, translate_mtx) + translate_mtx = npy.array([[1.0, 0.0, tx], [0.0, 1.0, ty], [0.0, 0.0, 1.0]], npy.float_) + self._mtx = npy.dot(translate_mtx, self._mtx) self.invalidate() return self def scale(self, sx, sy=None): if sy is None: sy = sx - scale_mtx = self.matrix_from_values(sx, 0., 0., sy, 0., 0.) - self._mtx = self._concat(self._mtx, scale_mtx) + scale_mtx = npy.array([[sx, 0.0, 0.0], [0.0, sy, 0.0], [0.0, 0.0, 1.0]], npy.float_) + self._mtx = npy.dot(scale_mtx, self._mtx) self.invalidate() return self - def is_separable(self): + def _get_is_separable(self): mtx = self.get_matrix() return mtx[0, 1] == 0.0 and mtx[1, 0] == 0.0 + is_separable = property(_get_is_separable) - class IdentityTransform(Affine2DBase): """ A special class that does the identity transform quickly. """ _mtx = npy.identity(3) + def frozen(self): + return self + def __repr__(self): return "IdentityTransform()" __str__ = __repr__ @@ -847,6 +868,10 @@ return points transform_affine = transform_non_affine = transform + def transform_path(self, path): + return path + transform_path_affine = transform_path_non_affine = transform_path + def get_affine(self): return self inverted = get_affine @@ -855,33 +880,37 @@ class BlendedGenericTransform(Transform): input_dims = 2 output_dims = 2 + is_separable = True def __init__(self, x_transform, y_transform): # Here we ask: "Does it blend?" - assert x_transform.is_separable() - assert y_transform.is_separable() + # MGDTODO: Reinvoke these asserts? + # assert x_transform.is_separable() + # assert y_transform.is_separable() Transform.__init__(self) self._x = x_transform self._y = y_transform - self.set_children(['_x', '_y']) + self.set_children(x_transform, y_transform) - def is_affine(self): - return self._x.is_affine() and self._y.is_affine() - - def is_separable(self): - return True - + def _get_is_affine(self): + return self._x.is_affine and self._y.is_affine + is_affine = property(_get_is_affine) + + def frozen(self): + return blended_transform_factory(self._x.frozen(), self._y.frozen()) + def __repr__(self): return "BlendedGenericTransform(%s,%s)" % (self._x, self._y) __str__ = __repr__ - + def transform(self, points): x = self._x y = self._y - if x == y and x.input_dims == 2: - return self._x.transform(points) + if x is y and x.input_dims == 2: + return x.transform(points) + if x.input_dims == 2: x_points = x.transform(points)[:, 0:1] else: @@ -895,11 +924,12 @@ y_points = y_points.reshape((len(y_points), 1)) return ma.concatenate((x_points, y_points), 1) + transform_non_affine = transform - + def transform_affine(self, points): return points - + def get_affine(self): return IdentityTransform() @@ -908,6 +938,8 @@ class BlendedAffine1D(Affine2DBase, Transform): + is_separable = True + def __init__(self, x_transform, y_transform): assert isinstance(x_transform, Affine1DBase) assert isinstance(y_transform, Affine1DBase) @@ -915,63 +947,50 @@ Transform.__init__(self) self._x = x_transform self._y = y_transform - self.set_children(['_x', '_y']) + self.set_children(x_transform, y_transform) Affine2DBase.__init__(self) self._mtx = None - def is_separable(self): - return True - def __repr__(self): return "BlendedAffine1D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - def _do_invalidation(self, affine_only): - result = self._mtx is None - self._mtx = None - self._inverted = None - def get_matrix(self): - if self._mtx is None: + if self._invalid: x_mtx = self._x.get_matrix() y_mtx = self._y.get_matrix() self._mtx = npy.array([[x_mtx[0, 0], 0.0, x_mtx[0, 1]], [0.0, y_mtx[0, 0], y_mtx[0, 1]], [0.0, 0.0, 1.0]]) + self._inverted = None + self._invalid = 0 return self._mtx class BlendedAffine2D(Affine2DBase, Transform): + is_separable = True + def __init__(self, x_transform, y_transform): - assert x_transform.is_affine() - assert y_transform.is_affine() - assert x_transform.is_separable() - assert y_transform.is_separable() + assert x_transform.is_affine + assert y_transform.is_affine + assert x_transform.is_separable + assert y_transform.is_separable Transform.__init__(self) self._x = x_transform self._y = y_transform - self.set_children(['_x', '_y']) + self.set_children(x_transform, y_transform) Affine2DBase.__init__(self) self._mtx = None - def is_separable(self): - return True - def __repr__(self): return "BlendedAffine2D(%s,%s)" % (self._x, self._y) __str__ = __repr__ - def _do_invalidation(self, affine_only): - result = self._mtx is None - self._mtx = None - self._inverted = None - return result - def get_matrix(self): - if self._mtx is None: + if self._invalid: if self._x == self._y: self._mtx = self._x.get_matrix() else: @@ -981,6 +1000,8 @@ # separable, though normally one would want to set b and # c to zero. self._mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) + self._inverted = None + self._invalid = 0 return self._mtx @@ -1001,18 +1022,31 @@ Transform.__init__(self) self._a = a self._b = b - self.set_children(['_a', '_b']) + self.set_children(a, b) + self._mtx = None - def is_affine(self): - return self._a.is_affine() and self._b.is_affine() + def frozen(self): + return composite_transform_factory(self._a.frozen(), self._b.frozen()) - def is_separable(self): + def _get_is_affine(self): + return self._a.is_affine and self._b.is_affine + is_affine = property(_get_is_affine) + + def _get_is_separable(self): return self._a.is_separable() and self._b.is_separable() + is_separable = property(_get_is_separable) def __repr__(self): return "CompositeGenericTransform(%s, %s)" % (self._a, self._b) __str__ = __repr__ - + + def get_matrix(self): + if self._invalid: + assert self._a.is_affine and self._b.is_affine + self._mtx = npy.dot(self._b.get_matrix(), self._a.get_matrix()) + self._invalid = 0 + return self._mtx + def transform(self, points): return self._b.transform(self._a.transform(points)) @@ -1022,6 +1056,15 @@ def transform_non_affine(self, points): return self._b.transform_non_affine(self._a.transform_non_affine(points)) + def transform_path(self, path): + return self._b.transform_path(self._a.transform_path(path)) + + def transform_path_affine(self, path): + return self._b.transform_path_affine(self._a.transform_path_affine(path)) + + def transform_path_non_affine(self, path): + return self._b.transform_path_non_affine(self._a.transform_path_non_affine(path)) + def get_affine(self): return CompositeAffine2D(self._a.get_affine(), self._b.get_affine()) @@ -1034,113 +1077,46 @@ assert a.output_dims == b.input_dims self.input_dims = a.input_dims self.output_dims = b.output_dims - assert a.is_affine() - assert b.is_affine() + assert a.is_affine + assert b.is_affine Affine2DBase.__init__(self) self._a = a self._b = b - self.set_children(['_a', '_b']) + self.set_children(a, b) self._mtx = None def __repr__(self): return "CompositeAffine2D(%s, %s)" % (self._a, self._b) __str__ = __repr__ - def _do_invalidation(self, affine_only): - result = self._mtx is None - self._mtx = None - self._inverted = None - return result - def get_matrix(self): - if self._mtx is None: - self._mtx = self._concat( - self._a.get_matrix(), - self._b.get_matrix()) + if self._invalid: + self._mtx = npy.dot( + self._b.get_matrix(), + self._a.get_matrix()) + self._inverted = None + self._invalid = 0 return self._mtx def composite_transform_factory(a, b): -# if isinstance(a, BboxTransform) and isinstance(b, BboxTransform): -# return BboxTransform(a._boxin, b._boxout) if isinstance(a, AffineBase) and isinstance(b, AffineBase): return CompositeAffine2D(a, b) return CompositeGenericTransform(a, b) - - -class TestPolarTransform(Transform): - input_dims = 2 - output_dims = 2 - - def __init__(self, limits): - assert limits.is_bbox() - - Transform.__init__(self) - self._limits = limits - self.set_children(['_limits']) - def transform(self, xy): - debug = len(xy) > 4 - limmin, limmax = self._limits.intervaly - mask = (xy[:, 1:] < limmin) | (xy[:, 1:] > limmax) - mask = ma.concatenate((mask, mask), 1) - masked_xy = npy.ma.masked_where(mask, xy) - x = masked_xy[:, 0:1] - y = masked_xy[:, 1:2] - if x.shape == () or y.shape == (): - return masked_xy - y = (y - limmin) / (limmax - limmin) - x, y = y * ma.cos(x), y * ma.sin(x) - result = ma.concatenate((x, y), 1) - result = result * 0.5 + 0.5 - return result - - def inverted(self): - return TestInvertPolarTransform(self._limits) - def is_separable(self): - return False - - -class TestInvertPolarTransform(Transform): - input_dims = 2 - output_dims = 2 - - def __init__(self, limits): - assert limits.is_bbox() - - Transform.__init__(self) - self._limits = limits - self.set_children(['_limits']) - - def transform(self, xy): - limmin, limmax = self._limits.intervaly - xy = (xy - 0.5) * 2.0 - x = xy[:, 0:1] - y = xy[:, 1:] - r = ma.sqrt(ma.power(x, 2) + ma.power(y, 2)) - theta = ma.arccos(x / r) - theta = ma.where(y < 0, 2 * npy.pi - theta, theta) - r = r * (limmax - limmin) + limmin - return ma.concatenate((theta, r), 1) - - def inverted(self): - return TestInvertPolarTransform(self._limits) - - def is_separable(self): - return False - - class BboxTransform(Affine2DBase): + is_separable = True + def __init__(self, boxin, boxout): - assert boxin.is_bbox() - assert boxout.is_bbox() + assert boxin.is_bbox + assert boxout.is_bbox Affine2DBase.__init__(self) self._boxin = boxin self._boxout = boxout - self.set_children(['_boxin', '_boxout']) + self.set_children(boxin, boxout) self._mtx = None self._inverted = None @@ -1148,29 +1124,17 @@ return "BboxTransform(%s, %s)" % (self._boxin, self._boxout) __str__ = __repr__ - def _do_invalidation(self, affine_only): - result = self._mtx is None - self._mtx = None - self._inverted = None - return result - - def is_separable(self): - return True - def get_matrix(self): - if self._mtx is None: - boxin = self._boxin - boxout = self._boxout - x_scale = boxout.width / boxin.width - y_scale = boxout.height / boxin.height - - # MGDTODO: Optimize - affine = Affine2D() \ - .translate(-boxin.xmin, -boxin.ymin) \ - .scale(x_scale, y_scale) \ - .translate(boxout.xmin, boxout.ymin) - - self._mtx = affine._mtx + if self._invalid: + inl, inb, inw, inh = self._boxin.bounds + outl, outb, outw, outh = self._boxout.bounds + x_scale = outw / inw + y_scale = outh / inh + self._mtx = npy.array([[x_scale, 0.0 , (-inl*x_scale+outl)], + [0.0 , y_scale, (-inb*y_scale+outb)], + [0.0 , 0.0 , 1.0 ]], + npy.float_) + self._inverted = None return self._mtx @@ -1181,27 +1145,20 @@ self._path = path self._transform = transform - self.set_children(['_transform']) + self.set_children(transform) self._transformed_path = None - def _do_invalidation(self, affine_only): - if not affine_only: - self._transformed_path = None - return True - - def get_path_and_affine(self): - if self._transformed_path is None: - vertices = self._transform.transform_non_affine(self._path.vertices) - self._transformed_path = Path(vertices, self._path.codes) - + def get_transformed_path_and_affine(self): + if self._invalid == 1 or self._transformed_path is None: + self._transformed_path = self._transform.transform_path_non_affine(self._path) + self._invalid = 0 return self._transformed_path, self._transform.get_affine() - def get_path(self): - if self._transformed_path is None: - vertices = self._tranform.transform_non_affine(self._path.vertices) - self._transformed_path = Path(vertices, self._path.codes) - vertices = self._transform.transform_affine(self._transformed_path.vertices) - return Path(vertices, self._transformed_path.codes) + def get_fully_transformed_path(self): + if self._invalid == 1 or self._transformed_path is None: + self._transformed_path = self._transform.transform_path_non_affine(self._path) + self._invalid = 0 + return self._transform.transform_path_affine(self._transformed_path) def get_affine(self): return self._transform.get_affine() ... [truncated message content] |
From: <md...@us...> - 2007-10-04 19:12:31
|
Revision: 3915 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3915&view=rev Author: mdboom Date: 2007-10-04 12:12:20 -0700 (Thu, 04 Oct 2007) Log Message: ----------- Merged from trunk (a somewhat hairy manual merge this time). Fixed bug (on this branch only) where inverted axes were broken. Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/backends/backend_qt.py branches/transforms/lib/matplotlib/backends/backend_qt4.py branches/transforms/lib/matplotlib/backends/backend_qt4agg.py branches/transforms/lib/matplotlib/backends/backend_qtagg.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-10-04 18:57:27 UTC (rev 3914) +++ branches/transforms/lib/matplotlib/axes.py 2007-10-04 19:12:20 UTC (rev 3915) @@ -498,6 +498,9 @@ self.set_label(label) self.set_figure(fig) + self._invertedx = False + self._invertedy = False + # this call may differ for non-sep axes, eg polar self._init_axis() @@ -1422,10 +1425,25 @@ ### data limits, ticks, tick labels, and formatting + def invert_xaxis(self, invert=True): + "Invert the x-axis if 'invert' is True." + self._invertedx = invert + + def xaxis_inverted(self): + 'Returns True if the x-axis is inverted.' + return self._invertedx + def get_xlim(self): - 'Get the x axis range [xmin, xmax]' - return self.viewLim.intervalx + """Get the x-axis range [xmin, xmax] + NOTE: The returned values are always [xmin, xmax] such that + xmin < xmax; regardless of whether or not the axes are inverted. + """ + bound1, bound2 = self.viewLim.intervalx + if ( self._invertedx ): + return bound2, bound1 + else: + return bound1, bound2 def set_xlim(self, xmin=None, xmax=None, emit=True, **kwargs): """ @@ -1463,9 +1481,21 @@ if xmin is None: xmin = old_xmin if xmax is None: xmax = old_xmax - xmin, xmax = mtransforms.nonsingular(xmin, xmax, increasing=False) + # provided for backwards compatability + if ( xmax < xmin ): + # swap the values so that xmin < xmax and set inverted flag + tmp = xmin + xmin = xmax + xmax = tmp + self.invert_xaxis( True ) - self.viewLim.intervalx = (xmin, xmax) + if ( self._invertedx ): + xmax, xmin = mtransforms.nonsingular(xmax, xmin, increasing=False) + self.viewLim.intervalx = (xmax, xmin) + else: + xmin, xmax = mtransforms.nonsingular(xmin, xmax, increasing=False) + self.viewLim.intervalx = (xmin, xmax) + if emit: self.callbacks.process('xlim_changed', self) # Call all of the other x-axes that are shared with this one @@ -1534,10 +1564,26 @@ return self.xaxis.set_ticklabels(labels, fontdict, **kwargs) set_xticklabels.__doc__ = cbook.dedent(set_xticklabels.__doc__) % martist.kwdocd + def invert_yaxis(self, invert=True): + "Invert the y-axis if 'invert' is True." + self._invertedy = invert + + def yaxis_inverted(self): + 'Returns True if the y-axis is inverted.' + return self._invertedy + def get_ylim(self): - 'Get the y axis range [ymin, ymax]' - return self.viewLim.intervaly + """Get the y-axis range [xmin, xmax] + NOTE: The returned values are always [ymin, ymax] such that + ymin < ymax; regardless of whether or not the axes are inverted. + """ + bound1, bound2 = self.viewLim.intervaly + if ( self._invertedy ): + return bound2, bound1 + else: + return bound1, bound2 + def set_ylim(self, ymin=None, ymax=None, emit=True, **kwargs): """ set_ylim(self, *args, **kwargs): @@ -1572,8 +1618,21 @@ if ymin is None: ymin = old_ymin if ymax is None: ymax = old_ymax - ymin, ymax = mtransforms.nonsingular(ymin, ymax, increasing=False) - self.viewLim.intervaly = (ymin, ymax) + # provided for backwards compatability + if ( ymax < ymin ): + # swap the values so that ymin < ymax and set inverted flag + tmp = ymin + ymin = ymax + ymax = tmp + self.invert_yaxis( True ) + + if ( self._invertedy ): + ymax, ymin = mtransforms.nonsingular(ymax, ymin, increasing=False) + self.viewLim.intervaly = (ymax, ymin) + else: + ymin, ymax = mtransforms.nonsingular(ymin, ymax, increasing=False) + self.viewLim.intervaly = (ymin, ymax) + if emit: self.callbacks.process('ylim_changed', self) # Call all of the other y-axes that are shared with this one @@ -1582,7 +1641,6 @@ other.set_ylim(self.viewLim.ymin, self.viewLim.ymax, emit=False) self.figure.canvas.draw_idle() - return ymin, ymax def get_yscale(self): Modified: branches/transforms/lib/matplotlib/backends/backend_qt.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_qt.py 2007-10-04 18:57:27 UTC (rev 3914) +++ branches/transforms/lib/matplotlib/backends/backend_qt.py 2007-10-04 19:12:20 UTC (rev 3915) @@ -136,10 +136,35 @@ def resizeEvent( self, event ): if DEBUG: print 'resize (%d x %d)' % (event.size().width(), event.size().height()) qt.QWidget.resizeEvent( self, event ) + w = event.size().width() + h = event.size().height() + if DEBUG: print "FigureCanvasQt.resizeEvent(", w, ",", h, ")" + dpival = self.figure.dpi.get() + winch = w/dpival + hinch = h/dpival + self.figure.set_size_inches( winch, hinch ) + self.draw() def resize( self, w, h ): + # Pass through to Qt to resize the widget. qt.QWidget.resize( self, w, h ) + # Resize the figure by converting pixels to inches. + pixelPerInch = self.figure.dpi.get() + wInch = w / pixelPerInch + hInch = h / pixelPerInch + self.figure.set_size_inches( wInch, hInch ) + + # Redraw everything. + self.draw() + + def sizeHint( self ): + w, h = self.get_width_height() + return qt.QSize( w, h ) + + def minumumSizeHint( self ): + return qt.QSize( 10, 10 ) + def _get_key( self, event ): if event.key() < 256: key = event.text().latin1() Modified: branches/transforms/lib/matplotlib/backends/backend_qt4.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_qt4.py 2007-10-04 18:57:27 UTC (rev 3914) +++ branches/transforms/lib/matplotlib/backends/backend_qt4.py 2007-10-04 19:12:20 UTC (rev 3915) @@ -135,10 +135,35 @@ def resizeEvent( self, event ): if DEBUG: print 'resize (%d x %d)' % (event.size().width(), event.size().height()) QtGui.QWidget.resizeEvent( self, event ) + w = event.size().width() + h = event.size().height() + if DEBUG: print "FigureCanvasQtAgg.resizeEvent(", w, ",", h, ")" + dpival = self.figure.dpi.get() + winch = w/dpival + hinch = h/dpival + self.figure.set_size_inches( winch, hinch ) + self.draw() def resize( self, w, h ): + # Pass through to Qt to resize the widget. QtGui.QWidget.resize( self, w, h ) + # Resize the figure by converting pixels to inches. + pixelPerInch = self.figure.dpi.get() + wInch = w / pixelPerInch + hInch = h / pixelPerInch + self.figure.set_size_inches( wInch, hInch ) + + # Redraw everything. + self.draw() + + def sizeHint( self ): + w, h = self.get_width_height() + return QtCore.QSize( w, h ) + + def minumumSizeHint( self ): + return QtCore.QSize( 10, 10 ) + def _get_key( self, event ): if event.key() < 256: key = str(event.text()) Modified: branches/transforms/lib/matplotlib/backends/backend_qt4agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_qt4agg.py 2007-10-04 18:57:27 UTC (rev 3914) +++ branches/transforms/lib/matplotlib/backends/backend_qt4agg.py 2007-10-04 19:12:20 UTC (rev 3915) @@ -65,14 +65,6 @@ def resizeEvent( self, e ): FigureCanvasQT.resizeEvent( self, e ) - w = e.size().width() - h = e.size().height() - if DEBUG: print "FigureCanvasQtAgg.resizeEvent(", w, ",", h, ")" - dpival = self.figure.dpi.get() - winch = w/dpival - hinch = h/dpival - self.figure.set_size_inches( winch, hinch ) - self.draw() def drawRectangle( self, rect ): self.rect = rect Modified: branches/transforms/lib/matplotlib/backends/backend_qtagg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_qtagg.py 2007-10-04 18:57:27 UTC (rev 3914) +++ branches/transforms/lib/matplotlib/backends/backend_qtagg.py 2007-10-04 19:12:20 UTC (rev 3915) @@ -64,14 +64,6 @@ def resizeEvent( self, e ): FigureCanvasQT.resizeEvent( self, e ) - w = e.size().width() - h = e.size().height() - if DEBUG: print "FigureCanvasQtAgg.resizeEvent(", w, ",", h, ")" - dpival = self.figure.dpi.get() - winch = w/dpival - hinch = h/dpival - self.figure.set_size_inches( winch, hinch ) - self.draw() def drawRectangle( self, rect ): self.rect = rect Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-10-04 18:57:27 UTC (rev 3914) +++ branches/transforms/lib/matplotlib/transforms.py 2007-10-04 19:12:20 UTC (rev 3915) @@ -1951,10 +1951,12 @@ # MGDTODO: Optimize (perhaps in an extension) def interval_contains(interval, val): - return interval[0] <= val and interval[1] >= val + return ((interval[0] <= val and interval[1] >= val) or + (interval[1] <= val and interval[0] >= val)) def interval_contains_open(interval, val): - return interval[0] < val and interval[1] > val + return ((interval[0] < val and interval[1] > val) or + (interval[1] < val and interval[0] > val)) if __name__ == '__main__': import copy This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-10-15 13:49:52
|
Revision: 3947 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3947&view=rev Author: mdboom Date: 2007-10-15 06:49:25 -0700 (Mon, 15 Oct 2007) Log Message: ----------- Significant speed improvement in text layout. Reverted to fix bug in ticklabels. Lots of other minor things. Modified Paths: -------------- branches/transforms/lib/matplotlib/axes.py branches/transforms/lib/matplotlib/axis.py branches/transforms/lib/matplotlib/collections.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/text.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-10-14 21:14:18 UTC (rev 3946) +++ branches/transforms/lib/matplotlib/axes.py 2007-10-15 13:49:25 UTC (rev 3947) @@ -1037,6 +1037,7 @@ a.set_axes(self) self.artists.append(a) self._set_artist_props(a) + a.set_clip_path(self.axesPatch) a._remove_method = lambda h: self.artists.remove(h) def add_collection(self, collection, autolim=False): @@ -1091,6 +1092,7 @@ 'Add a table instance to the list of axes tables' self._set_artist_props(tab) self.tables.append(tab) + tab.set_clip_path(self.axesPatch) tab._remove_method = lambda h: self.tables.remove(h) def relim(self): Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-10-14 21:14:18 UTC (rev 3946) +++ branches/transforms/lib/matplotlib/axis.py 2007-10-15 13:49:25 UTC (rev 3947) @@ -91,7 +91,6 @@ self._size = size self._padPixels = self.figure.dpi * self._pad * (1/72.0) - self._locTransform = Affine2D() self.tick1line = self._get_tick1line() self.tick2line = self._get_tick2line() @@ -286,7 +285,7 @@ markersize=self._size, ) - l.set_transform(self._locTransform + self.axes.get_xaxis_transform()) + l.set_transform(self.axes.get_xaxis_transform()) self._set_artist_props(l) return l @@ -298,16 +297,20 @@ linestyle=rcParams['grid.linestyle'], linewidth=rcParams['grid.linewidth'], ) - l.set_transform(self._locTransform + self.axes.get_xaxis_transform()) + l.set_transform(self.axes.get_xaxis_transform()) self._set_artist_props(l) return l def update_position(self, loc): 'Set the location of tick in data coords with scalar loc' - self._locTransform.clear().translate(loc, 0.0) - self.label1.set_x(loc) - self.label2.set_x(loc) + x = loc + + self.tick1line.set_xdata((x,)) + self.tick2line.set_xdata((x,)) + self.gridline.set_xdata((x, )) + self.label1.set_x(x) + self.label2.set_x(x) self._loc = loc def get_view_interval(self): @@ -385,7 +388,7 @@ linestyle = 'None', markersize=self._size, ) - l.set_transform(self._locTransform + self.axes.get_yaxis_transform()) + l.set_transform(self.axes.get_yaxis_transform()) self._set_artist_props(l) return l @@ -398,7 +401,7 @@ markersize=self._size, ) - l.set_transform(self._locTransform + self.axes.get_yaxis_transform()) + l.set_transform(self.axes.get_yaxis_transform()) self._set_artist_props(l) return l @@ -411,19 +414,24 @@ linewidth=rcParams['grid.linewidth'], ) - l.set_transform(self._locTransform + self.axes.get_yaxis_transform()) + l.set_transform(self.axes.get_yaxis_transform()) self._set_artist_props(l) return l def update_position(self, loc): 'Set the location of tick in data coords with scalar loc' - self._locTransform.clear().translate(0.0, loc) - self.label1.set_y(loc) - self.label2.set_y(loc) + y = loc + self.tick1line.set_ydata((y,)) + self.tick2line.set_ydata((y,)) + self.gridline.set_ydata((y, )) + + self.label1.set_y( y ) + self.label2.set_y( y ) + self._loc = loc - + def get_view_interval(self): 'return the Interval instance for this axis view limits' return self.axes.viewLim.intervaly @@ -751,7 +759,7 @@ if len(self.minorTicks) < numticks: # update the new tick label properties from the old for i in range(numticks - len(self.minorTicks)): - tick = self._get_tick(minor=True) + tick = self._get_tick(major=False) self.minorTicks.append(tick) if self._lastNumMinorTicks < numticks: Modified: branches/transforms/lib/matplotlib/collections.py =================================================================== --- branches/transforms/lib/matplotlib/collections.py 2007-10-14 21:14:18 UTC (rev 3946) +++ branches/transforms/lib/matplotlib/collections.py 2007-10-15 13:49:25 UTC (rev 3947) @@ -43,7 +43,7 @@ linewidths=None, antialiaseds = None, offsets = None, - transOffset = transforms.identity_transform(), + transOffset = transforms.IdentityTransform(), norm = None, # optional for cm.ScalarMappable cmap = None, # ditto @@ -376,7 +376,7 @@ linewidths=None, antialiaseds = None, offsets = None, - transOffset = transforms.identity_transform(), + transOffset = transforms.IdentityTransform(), norm = None, # optional for cm.ScalarMappable cmap = None, # ditto Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-10-14 21:14:18 UTC (rev 3946) +++ branches/transforms/lib/matplotlib/lines.py 2007-10-15 13:49:25 UTC (rev 3947) @@ -455,8 +455,10 @@ gc.set_capstyle(cap) funcname = self._lineStyles.get(self._linestyle, '_draw_nothing') - lineFunc = getattr(self, funcname) - lineFunc(renderer, gc, *self._transformed_path.get_transformed_path_and_affine()) + if funcname != '_draw_nothing': + tpath, affine = self._transformed_path.get_transformed_path_and_affine() + lineFunc = getattr(self, funcname) + lineFunc(renderer, gc, tpath, affine) if self._marker is not None: gc = renderer.new_gc() @@ -465,8 +467,10 @@ gc.set_linewidth(self._markeredgewidth) gc.set_alpha(self._alpha) funcname = self._markers.get(self._marker, '_draw_nothing') - markerFunc = getattr(self, funcname) - markerFunc(renderer, gc, *self._transformed_path.get_transformed_path_and_affine()) + if funcname != '_draw_nothing': + tpath, affine = self._transformed_path.get_transformed_path_and_affine() + markerFunc = getattr(self, funcname) + markerFunc(renderer, gc, tpath, affine) renderer.close_group('line2d') @@ -621,11 +625,9 @@ ACCEPTS: npy.array """ - try: del self._xsorted - except AttributeError: pass + self._xorig = x + self.recache() - self.set_data(x, self.get_ydata()) - def set_ydata(self, y): """ Set the data npy.array for y @@ -633,9 +635,9 @@ ACCEPTS: npy.array """ - self.set_data(self.get_xdata(), y) + self._yorig = y + self.recache() - def set_dashes(self, seq): """ Set the dash sequence, sequence of dashes with on off ink in Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-10-14 21:14:18 UTC (rev 3946) +++ branches/transforms/lib/matplotlib/path.py 2007-10-15 13:49:25 UTC (rev 3947) @@ -84,7 +84,8 @@ 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 not ma.isMaskedArray(vertices): + vertices = ma.asarray(vertices, npy.float_) if codes is None: if len(vertices) == 0: Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-10-14 21:14:18 UTC (rev 3946) +++ branches/transforms/lib/matplotlib/text.py 2007-10-15 13:49:25 UTC (rev 3947) @@ -172,24 +172,18 @@ self._linespacing = other._linespacing def _get_layout(self, renderer): - # layout the xylocs in display coords as if angle = zero and - # then rotate them around self._x, self._y - #return _unit_box key = self.get_prop_tup() if self.cached.has_key(key): return self.cached[key] + horizLayout = [] - transform = self.get_transform() - x, y = self.get_position() - thisx, thisy = transform.transform_point((x, y)) - tx, ty = thisx, thisy - width = 0 - height = 0 - - xmin, ymin = thisx, thisy + thisx, thisy = 0.0, 0.0 + xmin, ymin = 0.0, 0.0 + width, height = 0.0, 0.0 lines = self._text.split('\n') - whs = [] + whs = npy.zeros((len(lines), 2)) + horizLayout = npy.zeros((len(lines), 4)) # Find full vertical extent of font, # including ascenders and descenders: tmp, heightt, bl = renderer.get_text_width_height_descent( @@ -197,43 +191,39 @@ offsety = heightt * self._linespacing baseline = None - for line in lines: + for i, line in enumerate(lines): w, h, d = renderer.get_text_width_height_descent( line, self._fontproperties, ismath=self.is_math_text(line)) if baseline is None: baseline = h - d - whs.append( (w,h) ) - horizLayout.append((line, thisx, thisy, w, h)) + whs[i] = w, h + horizLayout[i] = thisx, thisy, w, h thisy -= offsety width = max(width, w) - ymin = horizLayout[-1][2] - ymax = horizLayout[0][2] + horizLayout[0][-1] + ymin = horizLayout[-1][1] + ymax = horizLayout[0][1] + horizLayout[0][3] height = ymax-ymin - xmax = xmin + width + # get the rotation matrix - M = self.get_rotation_matrix(xmin, ymin) + M = Affine2D().rotate_deg(self.get_rotation()) - # the corners of the unrotated bounding box - cornersHoriz = npy.array( - [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)], - npy.float_) offsetLayout = npy.zeros((len(lines), 2)) + offsetLayout[:] = horizLayout[:, 0:2] # now offset the individual text lines within the box if len(lines)>1: # do the multiline aligment malign = self._get_multialignment() - for i, (line, thisx, thisy, w, h) in enumerate(horizLayout): - if malign=='center': offsetx = width/2.0-w/2.0 - elif malign=='right': offsetx = width-w - else: offsetx = 0 - thisx += offsetx - offsetLayout[i] = (thisx, thisy) - else: # no additional layout needed - offsetLayout[0] = horizLayout[0][1:3] + if malign == 'center': + offsetLayout[:, 0] += width/2.0 - horizLayout[:, 2] / 2.0 + elif malign == 'right': + offsetLayout[:, 0] += width - horizLayout[:, 2] + # the corners of the unrotated bounding box + cornersHoriz = npy.array( + [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)], + npy.float_) # now rotate the bbox - cornersRotated = M.transform(cornersHoriz) txs = cornersRotated[:, 0] @@ -251,37 +241,30 @@ # compute the text location in display coords and the offsets # necessary to align the bbox with that location - tx, ty = self._get_xy_display() + if halign=='center': offsetx = (xmin + width/2.0) + elif halign=='right': offsetx = (xmin + width) + else: offsetx = xmin - if halign=='center': offsetx = tx - (xmin + width/2.0) - elif halign=='right': offsetx = tx - (xmin + width) - else: offsetx = tx - xmin + if valign=='center': offsety = (ymin + height/2.0) + elif valign=='top': offsety = (ymin + height) + elif valign=='baseline': offsety = (ymin + height) + baseline + else: offsety = ymin - if valign=='center': offsety = ty - (ymin + height/2.0) - elif valign=='top': offsety = ty - (ymin + height) - elif valign=='baseline': offsety = ty - (ymin + height) + baseline - else: offsety = ty - ymin + xmin -= offsetx + ymin -= offsety - xmin += offsetx - ymin += offsety - bbox = Bbox.from_lbwh(xmin, ymin, width, height) # now rotate the positions around the first x,y position xys = M.transform(offsetLayout) - xys += (offsetx, offsety) + xys -= (offsetx, offsety) - # now inverse transform back to data coords - inverse_transform = transform.inverted() - xys = inverse_transform.transform(xys) - xs, ys = xys[:, 0], xys[:, 1] ret = bbox, zip(lines, whs, xs, ys) self.cached[key] = ret return ret - def set_bbox(self, rectprops): """ Draw a bounding box around self. rect props are any settable @@ -307,18 +290,20 @@ if self.get_clip_on(): gc.set_clip_rectangle(self.clipbox) - - if self._bbox: bbox_artist(self, renderer, self._bbox) angle = self.get_rotation() bbox, info = self._get_layout(renderer) trans = self.get_transform() + posx, posy = self.get_position() + posx, posy = trans.transform_point((posx, posy)) + canvasw, canvash = renderer.get_canvas_width_height() + if rcParams['text.usetex']: - canvasw, canvash = renderer.get_canvas_width_height() for line, wh, x, y in info: - x, y = trans.transform_point((x, y)) + x = x + posx + y = y + posy if renderer.flipy(): y = canvash-y @@ -326,9 +311,9 @@ self._fontproperties, angle) return - canvasw, canvash = renderer.get_canvas_width_height() for line, wh, x, y in info: - x, y = trans.transform_point((x, y)) + x = x + posx + y = y + posy if renderer.flipy(): y = canvash-y @@ -402,8 +387,7 @@ x, y = self.get_position() return (x, y, self._text, self._color, self._verticalalignment, self._horizontalalignment, - hash(self._fontproperties), self._rotation, - self.get_transform().id + hash(self._fontproperties), self._rotation ) def get_text(self): @@ -432,11 +416,11 @@ angle = self.get_rotation() bbox, info = self._get_layout(self._renderer) + x, y = self.get_position() + x, y = self.get_transform().transform_point((x, y)) + bbox = bbox.translated(x, y) return bbox - def get_rotation_matrix(self, x0, y0): - return Affine2D().rotate_deg_around(x0, y0, self.get_rotation()) - def set_backgroundcolor(self, color): """ Set the background color of the text by updating the bbox (see set_bbox for more info) Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-10-14 21:14:18 UTC (rev 3946) +++ branches/transforms/lib/matplotlib/transforms.py 2007-10-15 13:49:25 UTC (rev 3947) @@ -34,8 +34,6 @@ DEBUG = False -# MGDTODO: Cache get_affine??? - class TransformNode(object): """ TransformNode is the base class for anything that participates in @@ -51,7 +49,7 @@ # INVALID_AFFINE_ONLY INVALID_NON_AFFINE = 1 INVALID_AFFINE = 2 - INVALID = INVALID_NON_AFFINE & INVALID_AFFINE + INVALID = INVALID_NON_AFFINE | INVALID_AFFINE # Some metadata about the transform, used to determine whether an # invalidation is affine-only @@ -71,11 +69,6 @@ # them alive. self._parents = WeakKeyDictionary() - # id is an arbitrary integer that is updated every time the node - # is invalidated. - self.id = TransformNode._gid - TransformNode._gid += 1 - # TransformNodes start out as invalid until their values are # computed for the first time. self._invalid = 1 @@ -105,15 +98,14 @@ value = ((self.is_affine or self.is_bbox) and self.INVALID_AFFINE or self.INVALID) - + # Invalidate all ancestors of self using pseudo-recursion. + parent = None stack = [self] while len(stack): root = stack.pop() # Stop at subtrees that have already been invalidated if root._invalid == 0 or root.pass_through: - root.id = TransformNode._gid - TransformNode._gid += 1 root._invalid = value stack.extend(root._parents.keys()) @@ -466,8 +458,7 @@ count = 0 for bbox in bboxes: - # bx1, by1, bx2, by2 = bbox._get_lbrt() - # The above, inlined... + # bx1, by1, bx2, by2 = bbox._get_lbrt() ... inlined... bx1, by1, bx2, by2 = bbox.get_points().flatten() if bx2 < bx1: bx2, bx1 = bx1, bx2 @@ -497,6 +488,26 @@ """ return Bbox(self._points + (tx, ty)) + def corners(self): + """ + Return an array of points which are the four corners of this + rectangle. + """ + l, b, r, t = self.get_points().flatten() + return npy.array([[l, b], [l, t], [r, b], [r, t]]) + + def rotated(self, radians): + """ + Return a new bounding box that bounds a rotated version of this + bounding box. The new bounding box is still aligned with the + axes, of course. + """ + corners = self.corners() + corners_rotated = Affine2D().rotate(radians).transform(corners) + bbox = Bbox.unit() + bbox.update_from_data(corners_rotated, ignore=True) + return bbox + #@staticmethod def union(bboxes): """ @@ -965,7 +976,6 @@ self.transform_path_non_affine = child.transform_path_non_affine self.get_affine = child.get_affine self.inverted = child.inverted - # self.get_matrix = child.get_matrix def set(self, child): """ @@ -1609,7 +1619,8 @@ self._x = x_transform self._y = y_transform self.set_children(x_transform, y_transform) - + self._affine = None + def _get_is_affine(self): return self._x.is_affine and self._y.is_affine is_affine = property(_get_is_affine) @@ -1648,9 +1659,7 @@ transform.__doc__ = Transform.transform.__doc__ def transform_affine(self, points): - if self._x.is_affine and self._y.is_affine: - return self.transform(points) - return points + return self.get_affine().transform(points) transform_affine.__doc__ = Transform.transform_affine.__doc__ def transform_non_affine(self, points): @@ -1664,18 +1673,22 @@ inverted.__doc__ = Transform.inverted.__doc__ def get_affine(self): - if self._x.is_affine and self._y.is_affine: - if self._x == self._y: - return self._x.get_affine() + if self._invalid or self._affine is None: + if self._x.is_affine and self._y.is_affine: + if self._x == self._y: + self._affine = self._x.get_affine() + else: + x_mtx = self._x.get_affine().get_matrix() + y_mtx = self._y.get_affine().get_matrix() + # This works because we already know the transforms are + # separable, though normally one would want to set b and + # c to zero. + mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) + self._affine = Affine2D(mtx) else: - x_mtx = self._x.get_affine().get_matrix() - y_mtx = self._y.get_affine().get_matrix() - # This works because we already know the transforms are - # separable, though normally one would want to set b and - # c to zero. - mtx = npy.vstack((x_mtx[0], y_mtx[1], [0.0, 0.0, 1.0])) - return Affine2D(mtx) - return IdentityTransform() + self._affine = IdentityTransform() + self._invalid = 0 + return self._affine get_affine.__doc__ = Transform.get_affine.__doc__ @@ -1830,6 +1843,7 @@ self._b = b self.set_children(a, b) self._mtx = None + self._affine = None def frozen(self): self._invalid = 0 @@ -1857,8 +1871,7 @@ transform.__doc__ = Transform.transform.__doc__ def transform_affine(self, points): - return self._b.transform_affine( - self._a.transform(points)) + return self.get_affine().transform(points) transform_affine.__doc__ = Transform.transform_affine.__doc__ def transform_non_affine(self, points): @@ -1886,10 +1899,14 @@ transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__ def get_affine(self): - if self._a.is_affine and self._b.is_affine: - return Affine2D(npy.dot(self._b.get_affine().get_matrix(), - self._a.get_affine().get_matrix())) - return self._b.get_affine() + if self._invalid or self._affine is None: + if self._a.is_affine and self._b.is_affine: + self._affine = Affine2D(npy.dot(self._b.get_affine().get_matrix(), + self._a.get_affine().get_matrix())) + else: + self._affine = self._b.get_affine() + self._invalid = 0 + return self._affine get_affine.__doc__ = Transform.get_affine.__doc__ def inverted(self): @@ -2023,6 +2040,7 @@ self._transform = transform self.set_children(transform) self._transformed_path = None + self.get_affine = self._transform.get_affine def get_transformed_path_and_affine(self): """ @@ -2035,7 +2053,7 @@ self._transformed_path = \ self._transform.transform_path_non_affine(self._path) self._invalid = 0 - return self._transformed_path, self._transform.get_affine() + return self._transformed_path, self.get_affine() def get_fully_transformed_path(self): """ @@ -2047,12 +2065,6 @@ self._transform.transform_path_non_affine(self._path) self._invalid = 0 return self._transform.transform_path_affine(self._transformed_path) - - def get_affine(self): - """ - Get the affine part of the child transform. - """ - return self._transform.get_affine() def nonsingular(vmin, vmax, expander=0.001, tiny=1e-15, increasing=True): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-10-16 14:17:56
|
Revision: 3955 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3955&view=rev Author: mdboom Date: 2007-10-16 07:17:53 -0700 (Tue, 16 Oct 2007) Log Message: ----------- First pass at PS backend updates. Modified Paths: -------------- branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/backends/backend_ps.py branches/transforms/lib/matplotlib/cbook.py branches/transforms/lib/matplotlib/collections.py branches/transforms/lib/matplotlib/lines.py branches/transforms/lib/matplotlib/path.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-10-16 13:45:59 UTC (rev 3954) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-10-16 14:17:53 UTC (rev 3955) @@ -49,7 +49,62 @@ trans is an affine transform applied to the path. """ raise NotImplementedError - + + def draw_path_collection(self, master_transform, cliprect, clippath, + clippath_trans, paths, all_transforms, offsets, + offsetTrans, facecolors, edgecolors, linewidths, + linestyles, antialiaseds): + """ + MGDTODO: Document me. Explain that often the backend will not + want to override this. + """ + Npaths = len(paths) + Noffsets = len(offsets) + N = max(Npaths, Noffsets) + Ntransforms = min(len(all_transforms), N) + Nfacecolors = len(facecolors) + Nedgecolors = len(edgecolors) + Nlinewidths = len(linewidths) + Nlinestyles = len(linestyles) + Naa = len(antialiaseds) + + if (Nfacecolors == 0 and Nedgecolors == 0) or N == 0: + return + + ttransforms = [] + for i in range(Ntransforms): + transform = all_transforms[i] + if transform is None: + transform = transforms.IdentityTransform() + ttransforms.append((transform + master_transform).frozen()) + + toffsets = offsetTrans.transform(offsets) + + gc = self.new_gc() + gc.set_clip_rectangle(cliprect) + if clippath is not None: + clippath = transforms.TransformedPath(clippath, clippath_trans) + gc.set_clippath(clippath) + + if Nfacecolors == 0: + rgbFace = None + + print linewidths, edgecolors + + for i in xrange(N): + path = paths[i % Npaths] + xo, yo = toffsets[i % Noffsets] + transform = ttransforms[i % Ntransforms].frozen().translate(xo, yo) + if Nfacecolors: + rgbFace = facecolors[i % Nfacecolors] + if Nedgecolors: + gc.set_foreground(edgecolors[i % Nedgecolors]) + gc.set_linewidth(linewidths[i % Nlinewidths]) + gc.set_dashes(*linestyles[i % Nlinestyles]) + gc.set_antialiased(antialiaseds[i % Naa]) + + self.draw_path(gc, path, transform, rgbFace) + def get_image_magnification(self): """ Get the factor by which to magnify images passed to draw_image. @@ -78,328 +133,6 @@ """ return False - ###################################################################### - ## OLD API IS BELOW - ## These functions no longer need to be implemented in the backends -- - ## they now perform all of their functions in terms of the new API. - -# def draw_arc(self, gc, rgbFace, x, y, width, height, angle1, angle2, -# rotation): -# """ -# Draw an arc using GraphicsContext instance gcEdge, centered at x,y, -# with width and height and angles from 0.0 to 360.0 -# 0 degrees is at 3-o'clock -# positive angles are anti-clockwise -# draw rotated 'rotation' degrees anti-clockwise about x,y - -# If the color rgbFace is not None, fill the arc with it. -# """ -# raise NotImplementedError - -# def draw_line_collection(self, segments, transform, clipbox, -# colors, linewidths, linestyle, antialiaseds, -# offsets, transOffset): -# """ -# This is a function for optimized line drawing. If you need to draw -# many line segments with similar properties, it is faster to avoid the -# overhead of all the object creation etc. The lack of total -# configurability is compensated for with efficiency. Hence we don't use -# a GC and many of the line props it supports. See -# matplotlib.collections for more details. - -# segments is a sequence of ( line0, line1, line2), where linen = -# is an Mx2 array with columns x, y. Each line can be a -# different length - -# transform is used to Transform the lines - -# clipbox is a xmin, ymin, width, height clip rect - -# colors is a tuple of RGBA tuples - -# linewidths is a tuple of linewidths -# *** really should be called 'dashes' not 'linestyle', since -# we call gc.set_dashes() not gc.set_linestyle() *** - -# linestyle is an (offset, onoffseq) tuple or None,None for solid - -# antialiseds is a tuple of ones or zeros indicating whether the -# segment should be aa or not - -# offsets, if not None, is an Nx2 array of x,y offsets to -# translate the lines by after transform is used to transform -# the offset coords - -# This function could be overridden in the backend to possibly implement -# faster drawing, but it is already much faster than using draw_lines() -# by itself. -# """ - -# newstyle = getattr(self, 'draw_markers', None) is not None -# identity = transforms.identity_transform() -# gc = self.new_gc() -# if clipbox is not None: -# gc.set_clip_rectangle(clipbox.get_bounds()) -# gc.set_dashes(*linestyle) - -# Nc = len(colors) -# Nlw = len(linewidths) -# Naa = len(antialiaseds) -# Nsegments = len(segments) - -# usingOffsets = offsets is not None -# Noffsets = 0 -# if usingOffsets: -# Noffsets = offsets.shape[0] -# offsets = transOffset.numerix_xy(offsets) - -# for i in xrange(max(Noffsets, Nsegments)): -# color = colors[i % Nc] -# rgb = color[0], color[1], color[2] -# alpha = color[-1] - -# gc.set_foreground(rgb, isRGB=True) -# gc.set_alpha( alpha ) -# gc.set_linewidth( linewidths[i % Nlw] ) -# gc.set_antialiased( antialiaseds[i % Naa] ) -# seg = segments[i % Nsegments] -# if not len(seg): continue -# xy = transform.numerix_xy(seg) -# if usingOffsets: -# xy = xy + offsets[i % Noffsets] - -# if newstyle: self.draw_lines(gc, xy[:,0], xy[:,1], identity) -# else: self.draw_lines(gc, xy[:,0], xy[:,1]) - -# def draw_line(self, gc, x1, y1, x2, y2): -# """ -# Draw a single line from x1,y1 to x2,y2 -# """ -# raise NotImplementedError - -# def draw_lines(self, gc, x, y, transform=None): -# """ -# x and y are equal length arrays, draw lines connecting each -# point in x, y -# """ -# raise NotImplementedError - -# def draw_point(self, gc, x, y): -# """ -# Draw a single point at x,y -# Where 'point' is a device-unit point (or pixel), not a matplotlib point -# """ -# raise NotImplementedError - -# def draw_quad_mesh(self, meshWidth, meshHeight, colors, -# xCoords, yCoords, clipbox, -# transform, offsets, transOffset, showedges): -# """ -# Draw a quadrilateral mesh -# See documentation in QuadMesh class in collections.py for details -# """ -# # print "draw_quad_mesh not found, using function in backend_bases" -# verts = npy.zeros(((meshWidth * meshHeight), 4, 2), npy.float32) -# indices = npy.arange((meshWidth + 1) * (meshHeight + 1)) -# indices = npy.compress((indices + 1) % (meshWidth + 1), indices) -# indices = indices[:(meshWidth * meshHeight)] -# verts[:, 0, 0] = npy.take(xCoords, indices) -# verts[:, 0, 1] = npy.take(yCoords, indices) -# verts[:, 1, 0] = npy.take(xCoords, (indices + 1)) -# verts[:, 1, 1] = npy.take(yCoords, (indices + 1)) -# verts[:, 2, 0] = npy.take(xCoords, (indices + meshWidth + 2)) -# verts[:, 2, 1] = npy.take(yCoords, (indices + meshWidth + 2)) -# verts[:, 3, 0] = npy.take(xCoords, (indices + meshWidth + 1)) -# verts[:, 3, 1] = npy.take(yCoords, (indices + meshWidth + 1)) -# if (showedges): -# edgecolors = colors -# else: -# edgecolors = (0, 0, 0, 0), -# self.draw_poly_collection(verts, transform, -# clipbox, colors, edgecolors, -# (0.25,), (0,), offsets, transOffset) - -# def draw_poly_collection( -# self, verts, transform, clipbox, facecolors, edgecolors, -# linewidths, antialiaseds, offsets, transOffset): -# """ -# Draw a polygon collection - -# verts are a sequence of polygon vectors, where each polygon -# vector is a sequence of x,y tuples of vertices - -# facecolors and edgecolors are a sequence of RGBA tuples -# linewidths are a sequence of linewidths -# antialiaseds are a sequence of 0,1 integers whether to use aa - -# If a linewidth is zero or an edgecolor alpha is zero, the -# line will be omitted; similarly, the fill will be omitted -# if the facecolor alpha is zero. -# """ -# ## line and/or fill OK -# Nface = len(facecolors) -# Nedge = len(edgecolors) -# Nlw = len(linewidths) -# Naa = len(antialiaseds) - -# usingOffsets = offsets is not None -# Noffsets = 0 -# Nverts = len(verts) -# if usingOffsets: -# Noffsets = len(offsets) - -# N = max(Noffsets, Nverts) - -# gc = self.new_gc() -# if clipbox is not None: -# gc.set_clip_rectangle(clipbox.get_bounds()) - - -# for i in xrange(N): -# polyverts = ma.filled(verts[i % Nverts], npy.nan) -# if npy.any(npy.isnan(polyverts)): -# continue -# linewidth = linewidths[i % Nlw] -# rf,gf,bf,af = facecolors[i % Nface] -# re,ge,be,ae = edgecolors[i % Nedge] -# if af==0: -# if ae==0 or linewidth == 0: -# continue -# rgbFace = None -# alpha = ae -# else: -# rgbFace = rf,gf,bf -# if ae==0: -# alpha = af -# gc.set_linewidth(0) -# else: -# # the draw_poly interface can't handle separate alphas for -# # edge and face so we'll just use the maximum -# alpha = max(af,ae) -# gc.set_foreground( (re,ge,be), isRGB=True) -# gc.set_linewidth( linewidths[i % Nlw] ) -# #print 'verts', zip(thisxverts, thisyverts) - -# gc.set_antialiased( antialiaseds[i % Naa] ) # Used for fill only? -# gc.set_alpha( alpha ) -# tverts = transform.seq_xy_tups(polyverts) -# if usingOffsets: -# xo,yo = transOffset.xy_tup(offsets[i % Noffsets]) -# tverts = [(x+xo,y+yo) for x,y in tverts] - -# self.draw_polygon(gc, rgbFace, tverts) - -# def draw_polygon(self, gc, rgbFace, points): -# """ -# Draw a polygon using the GraphicsContext instance gc. -# points is a len vertices tuple, each element -# giving the x,y coords a vertex - -# If the color rgbFace is not None, fill the polygon with it -# """ -# raise NotImplementedError - -# def draw_rectangle(self, gcEdge, rgbFace, x, y, width, height): -# """ -# Draw a non-filled rectangle using the GraphicsContext instance gcEdge, -# with lower left at x,y with width and height. - -# If rgbFace is not None, fill the rectangle with it. -# """ -# warnings.warn("draw_rectangle called", warnings.PendingDeprecationWarning) -# transform = transforms.Affine2D().scale(width, height).translate(x, y) -# self.draw_path(gcEdge, Path.unit_rectangle(), transform, rgbFace) - -# def draw_regpoly_collection( -# self, clipbox, offsets, transOffset, verts, sizes, -# facecolors, edgecolors, linewidths, antialiaseds): -# """ -# Draw a regular poly collection - -# offsets - is a sequence is x,y tuples -# transOffset - maps this to display coords - -# verts - are the vertices of the regular polygon at the origin - -# sizes are the area of the circle that circumscribes the -# polygon in points^2 - -# facecolors and edgecolors are a sequence of RGBA tuples -# linewidths are a sequence of linewidths -# antialiaseds are a sequence of 0,1 integers whether to use aa -# """ -# ## line and/or fill OK -# gc = self.new_gc() -# if clipbox is not None: -# gc.set_clip_rectangle(clipbox.get_bounds()) - -# xverts, yverts = zip(*verts) -# xverts = npy.asarray(xverts) -# yverts = npy.asarray(yverts) - -# Nface = len(facecolors) -# Nedge = len(edgecolors) -# Nlw = len(linewidths) -# Naa = len(antialiaseds) -# Nsizes = len(sizes) - -# for i, loc in enumerate(offsets): -# xo,yo = transOffset.xy_tup(loc) -# #print 'xo, yo', loc, (xo, yo) -# scale = sizes[i % Nsizes] - -# thisxverts = scale*xverts + xo -# thisyverts = scale*yverts + yo -# #print 'xverts', xverts - -# linewidth = linewidths[i % Nlw] -# rf,gf,bf,af = facecolors[i % Nface] -# re,ge,be,ae = edgecolors[i % Nedge] -# if af==0: -# if ae==0 or linewidth == 0: -# continue -# rgbFace = None -# alpha = ae -# else: -# rgbFace = rf,gf,bf -# if ae==0: -# alpha = af -# gc.set_linewidth(0) -# else: -# # the draw_poly interface can't handle separate alphas for -# # edge and face so we'll just use the maximum -# alpha = max(af,ae) -# gc.set_foreground( (re,ge,be), isRGB=True) -# gc.set_linewidth( linewidths[i % Nlw] ) -# #print 'verts', zip(thisxverts, thisyverts) - -# gc.set_antialiased( antialiaseds[i % Naa] ) # Used for fill only? -# gc.set_alpha( alpha ) -# #print 'verts', zip(thisxverts, thisyverts) -# self.draw_polygon(gc, rgbFace, zip(thisxverts, thisyverts)) - - -# def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!'): -# raise NotImplementedError - -# def draw_text(self, gc, x, y, s, prop, angle, ismath=False): -# """ -# Draw the text.Text instance s at x,y (display coords) with font -# properties instance prop at angle in degrees, using GraphicsContext gc - -# **backend implementers note** - -# When you are trying to determine if you have gotten your bounding box -# right (which is what enables the text layout/alignment to work -# properly), it helps to change the line in text.py - -# if 0: bbox_artist(self, renderer) - -# to if 1, and then the actual bounding box will be blotted along with -# your text. -# """ -# raise NotImplementedError - def flipy(self): """return true if y small numbers are top for renderer Is used for drawing text (text.py) and images (image.py) only @@ -416,12 +149,6 @@ self._texmanager = TexManager() return self._texmanager - def get_text_extent(self, text): # is not used, can be removed? - """ - Get the text extent in window coords - """ - return transforms.lbwh_to_bbox(0,0,1,1) # your values here - def get_text_width_height_descent(self, s, prop, ismath): """ get the width and height, and the offset from the bottom to the Modified: branches/transforms/lib/matplotlib/backends/backend_ps.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_ps.py 2007-10-16 13:45:59 UTC (rev 3954) +++ branches/transforms/lib/matplotlib/backends/backend_ps.py 2007-10-16 14:17:53 UTC (rev 3955) @@ -24,9 +24,9 @@ from matplotlib.mathtext import MathTextParser from matplotlib._mathtext_data import uni2type1 from matplotlib.text import Text +from matplotlib.path import Path +from matplotlib.transforms import IdentityTransform -from matplotlib.transforms import get_vec6_scales - import numpy as npy import binascii import re @@ -141,7 +141,9 @@ self.fontsize = None self.hatch = None self.image_magnification = dpi/72.0 - + self._clip_paths = {} + self._path_collection_id = 0 + self.fontd = {} self.afmfontd = {} self.used_characters = {} @@ -247,7 +249,7 @@ hatchl cvi hatchgap idiv hatchgap mul hatchgap hatchr cvi hatchgap idiv hatchgap mul - {hatcht moveto 0 hatchb hatcht sub rlineto} + {hatcht m 0 hatchb hatcht sub r } for stroke grestore @@ -330,18 +332,6 @@ font.set_size(size, 72.0) return font - def draw_arc(self, gc, rgbFace, x, y, width, height, angle1, angle2, rotation): - """ - Draw an arc centered at x,y with width and height and angles - from 0.0 to 360.0 - - If gcFace is not None, fill the arc slice with it. gcEdge - is a GraphicsContext instance - """ - ps = '%f %f translate\n%f rotate\n%f %f translate\n%s ellipse' % \ - (x, y, rotation, -x, -y, _nums_to_str(angle1, angle2, 0.5*width, 0.5*height, x, y)) - self._draw_ps(ps, gc, rgbFace, "arc") - def _rgba(self, im): return im.as_rgba_str() @@ -428,14 +418,47 @@ # unflip im.flipud_out() - def draw_line(self, gc, x0, y0, x1, y1): + def _convert_path(self, path, transform): + path = transform.transform_path(path) + + ps = [] + for points, code in path.iter_segments(): + if code == Path.MOVETO: + ps.append("%g %g m" % tuple(points)) + elif code == Path.LINETO: + ps.append("%g %g l" % tuple(points)) + elif code == Path.CURVE3: + ps.append("%g %g %g %g %g %g c" % + (points[0], points[1], + points[0], points[1], + points[2], points[3])) + elif code == Path.CURVE4: + ps.append("%g %g %g %g %g %g c" % tuple(points)) + elif code == Path.CLOSEPOLY: + ps.append("cl") + ps = "\n".join(ps) + + return ps + + def _get_clip_path(self, clippath, clippath_transform): + id = self._clip_paths.get((clippath, clippath_transform)) + if id is None: + id = 'c%x' % len(self._clip_paths) + ps_cmd = ['/%s {' % id] + ps_cmd.append(self._convert_path(clippath, clippath_transform)) + ps_cmd.extend(['clip', 'newpath', '} bind def\n']) + self._pswriter.write('\n'.join(ps_cmd)) + self._clip_paths[(clippath, clippath_transform)] = id + return id + + def draw_path(self, gc, path, transform, rgbFace=None): """ - Draw a single line from x0,y0 to x1,y1 - """ - ps = '%1.4g %1.4g m %1.4g %1.4g l'%(x0, y0, x1, y1) - self._draw_ps(ps, gc, None, "line") + Draws a Path instance using the given affine transform. + """ + ps = self._convert_path(path, transform) + self._draw_ps(ps, gc, rgbFace) - def draw_markers(self, gc, path, rgbFace, x, y, transform): + def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): """ Draw the markers defined by path at each of the positions in x and y. path coordinates are points, x and y coords will be @@ -452,216 +475,82 @@ ps_color = '%1.3f %1.3f %1.3f setrgbcolor' % rgbFace # construct the generic marker command: - ps_cmd = ['gsave'] # dont want the translate to be global - ps_cmd.append('newpath') - ps_cmd.append('translate') - while 1: - code, xp, yp = path.vertex() - if code == agg.path_cmd_stop: - ps_cmd.append('closepath') # Hack, path_cmd_end_poly not found - break - elif code == agg.path_cmd_move_to: - ps_cmd.append('%g %g m' % (xp,yp)) - elif code == agg.path_cmd_line_to: - ps_cmd.append('%g %g l' % (xp,yp)) - elif code == agg.path_cmd_curve3: - pass - elif code == agg.path_cmd_curve4: - pass - elif code == agg.path_cmd_end_poly: - pass - ps_cmd.append('closepath') - elif code == agg.path_cmd_mask: - pass - else: - pass - #print code + ps_cmd = ['/o {', 'gsave', 'newpath', 'translate'] # dont want the translate to be global + ps_cmd.append(self._convert_path(marker_path, marker_trans)) if rgbFace: - ps_cmd.append('gsave') - ps_cmd.append(ps_color) - ps_cmd.append('fill') - ps_cmd.append('grestore') + ps_cmd.extend(['gsave', ps_color, 'fill', 'grestore']) - ps_cmd.append('stroke') - ps_cmd.append('grestore') # undo translate() - ps_cmd = '\n'.join(ps_cmd) + ps_cmd.extend(['stroke', 'grestore', '} bind def']) + + tpath = trans.transform_path(path) + for x, y in tpath.vertices: + ps_cmd.append("%1.3g %1.3g o" % (x, y)) - self.push_gc(gc, store=1) + ps = '\n'.join(ps_cmd) + self._draw_ps(ps, gc, rgbFace, fill=False, stroke=False) - def drawone(x, y): - try: - xt, yt = transform.xy_tup((x, y)) - ret = '%g %g o' % (xt, yt) - except ValueError: - pass - else: - return ret - - step = 500 - start = 0 - end = step - - mask = npy.where(npy.isnan(x) + npy.isnan(y), 0, 1) - - cliprect = gc.get_clip_rectangle() - if cliprect: - write('gsave\n') - xc,yc,wc,hc=cliprect - write('%g %g %g %g clipbox\n' % (wc,hc,xc,yc)) - write(' '.join(['/o {', ps_cmd, '} bind def\n'])) - # Now evaluate the marker command at each marker location: - while start < len(x): - todraw = izip(x[start:end+1], y[start:end+1], mask[start:end+1]) - ps = [i for i in [drawone(xi,yi) for xi,yi,mi in todraw if mi] if i] - write('\n'.join(ps)+'\n') - start = end - end += step - if cliprect: write('grestore\n') - - def draw_path(self,gc,rgbFace,path,trans): - pass - - def draw_lines(self, gc, x, y, transform): - """ - x and y are npy.equal length arrays, draw lines connecting each - point in x, y - """ - if debugPS: self._pswriter.write('% draw_lines \n') - + def draw_path_collection(self, master_transform, cliprect, clippath, + clippath_trans, paths, all_transforms, offsets, + offsetTrans, facecolors, edgecolors, linewidths, + linestyles, antialiaseds): write = self._pswriter.write + + Npaths = len(paths) + Noffsets = len(offsets) + N = max(Npaths, Noffsets) + Ntransforms = min(len(all_transforms), N) + Ntpaths = max(Npaths, Ntransforms) + Nfacecolors = len(facecolors) + Nedgecolors = len(edgecolors) + Nlinewidths = len(linewidths) + Nlinestyles = len(linestyles) + Naa = len(antialiaseds) - def drawone(x, y, skip): - try: - if skip: raise(ValueError) - xt, yt = transform.xy_tup((x, y)) - ret = '%g %g %c' % (xt, yt, drawone.state) - except ValueError: - drawone.state = 'm' - else: - drawone.state = 'l' - return ret + if (Nfacecolors == 0 and Nedgecolors == 0) or N == 0: + return + + for i in range(Ntpaths): + path = paths[i % Npaths] + transform = all_transforms[i % Ntransforms] + if transform is None: + transform = IdentityTransform() + transform += master_transform - step = 100000 - start = 0 - end = step + ps_cmd = ['/p%x_%x {' % (self._path_collection_id, i), + 'newpath', 'translate'] + ps_cmd.append(self._convert_path(path, transform)) + ps_cmd.extend(['} bind def\n']) + write('\n'.join(ps_cmd)) + + toffsets = offsetTrans.transform(offsets) + + gc = self.new_gc() - skip = npy.where(npy.isnan(x) + npy.isnan(y), 1, 0) - points = zip(x,y,skip) + gc.set_clip_rectangle(cliprect) + if clippath is not None: + clippath = transforms.TransformedPath(clippath, clippath_trans) + gc.set_clippath(clippath) + + if Nfacecolors == 0: + rgbFace = None - self.push_gc(gc, store=1) - cliprect = gc.get_clip_rectangle() - if cliprect: - write('gsave\n') - xc,yc,wc,hc=cliprect - write('%g %g %g %g clipbox\n' % (wc,hc,xc,yc)) - while start < len(points): - drawone.state = 'm' - ps = [i for i in [drawone(x,y,s) for x,y,s in points[start:end+1]]\ - if i] - ps.append('stroke') - write('\n'.join(ps)+'\n') - start = end - end += step - if cliprect: write('grestore\n') + for i in xrange(N): + path_id = i % Ntpaths + xo, yo = toffsets[i % Noffsets] + if Nfacecolors: + rgbFace = facecolors[i % Nfacecolors] + if Nedgecolors: + gc.set_foreground(edgecolors[i % Nedgecolors]) + gc.set_linewidth(linewidths[i % Nlinewidths]) + gc.set_dashes(*linestyles[i % Nlinestyles]) + gc.set_antialiased(antialiaseds[i % Naa]) + ps = "%g %g p%x_%x" % (xo, yo, self._path_collection_id, path_id) + self._draw_ps(ps, gc, rgbFace) - def draw_lines_old(self, gc, x, y, transform=None): - """ - x and y are npy.equal length arrays, draw lines connecting each - point in x, y - """ - if debugPS: self._pswriter.write('% draw_lines \n') - - write = self._pswriter.write - - mask = npy.where(npy.isnan(x) + npy.isnan(y), 0, 1) - if transform: # this won't be called if draw_markers is hidden - if transform.need_nonlinear(): - x,y,mask = transform.nonlinear_only_numerix(x, y, returnMask=1) - - # a,b,c,d,tx,ty affine which transforms x and y into ps coordinates - a,b,c,d,tx,ty = transform.as_vec6_val() - - xo = a*x+c*y+tx - yo = b*x+d*y+ty - x,y = xo,yo - - self.push_gc(gc, store=1) - - cliprect = gc.get_clip_rectangle() - if cliprect: - write('gsave\n') - xc,yc,wc,hc=cliprect - write('%g %g %g %g clipbox\n' % (wc,hc,xc,yc)) - - steps = 50 - start = 0 - end = steps - points = zip(x,y) - - while start < len(x): - # npy.put moveto on all the bad data and on the first good - # point after the bad data - codes = [('m','l')[int(i)] for i in mask] - ind = npy.nonzero(mask[start:end+1]==0)+1 - if len(ind): - if ind[-1]>=len(codes): - ind = ind[:-1] - for i in ind: - codes[i] = 'm' - # npy.put a moveto on the first point, regardless - codes[0] = 'm' - - thisx = x[start:end+1] - thisy = y[start:end+1] - to_draw = izip(thisx, thisy, codes, mask) - if not to_draw: - break - - ps = ["%g %g %c" % (xp, yp, c) for xp, yp, c, m in to_draw if m] - if transform: - ps.append('stroke') - write('\n'.join(ps)+'\n') - else: - self._draw_ps("\n".join(ps)+'\n', gc, None) - start = end - end += steps - if transform: - if cliprect: write("grestore\n") - - def draw_point(self, gc, x, y): - """ - Draw a single point at x,y - """ - # TODO: is there a better way to draw points in postscript? - # (use a small circle?) - self.draw_line(gc, x, y, x+1, y+1) - - def draw_polygon(self, gc, rgbFace, points): - """ - Draw a polygon. points is a len vertices tuple, each element - giving the x,y coords a vertex - - If rgbFace is not None, fill the poly with it. gc - is a GraphicsContext instance - """ - ps = ["%s m\n" % _nums_to_str(*points[0])] - ps.extend([ "%s l\n" % _nums_to_str(x, y) for x,y in points[1:] ]) - ps.append("closepath") - self._draw_ps(''.join(ps), gc, rgbFace, "polygon") - - def draw_rectangle(self, gc, rgbFace, x, y, width, height): - """ - Draw a rectangle with lower left at x,y with width and height. - - If gcFace is not None, fill the rectangle with it. gcEdge - is a GraphicsContext instance - """ - # TODO: use rectstroke - ps = '%s box' % _nums_to_str(width, height, x, y) - self._draw_ps(ps, gc, rgbFace, "rectangle") - + self._path_collection_id += 1 + def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!'): """ draw a Text instance @@ -853,7 +742,6 @@ """ % locals() self._pswriter.write(ps) - def draw_mathtext(self, gc, x, y, s, prop, angle): """ @@ -875,68 +763,57 @@ """ % locals() self._pswriter.write(ps) - def _draw_ps(self, ps, gc, rgbFace, command=None): + def _draw_ps(self, ps, gc, rgbFace, fill=True, stroke=True, command=None): """ Emit the PostScript sniplet 'ps' with all the attributes from 'gc' applied. 'ps' must consist of PostScript commands to construct a path. """ # local variable eliminates all repeated attribute lookups write = self._pswriter.write - write('gsave\n') + # write('gsave\n') if debugPS and command: write("% "+command+"\n") - cliprect = gc.get_clip_rectangle() - self.set_color(*gc.get_rgb()) self.set_linewidth(gc.get_linewidth()) jint = gc.get_joinstyle() self.set_linejoin(jint) cint = gc.get_capstyle() self.set_linecap(cint) self.set_linedash(*gc.get_dashes()) - + + cliprect = gc.get_clip_rectangle() if cliprect: - x,y,w,h=cliprect + x,y,w,h=cliprect.bounds write('gsave\n%1.4g %1.4g %1.4g %1.4g clipbox\n' % (w,h,x,y)) + clippath, clippath_trans = gc.get_clip_path() + if clippath: + id = self._get_clip_path(clippath, clippath_trans) + write('gsave\n%s\n' % id) + # Jochen, is the strip necessary? - this could be a honking big string write(ps.strip()) write("\n") - if rgbFace: + + if rgbFace is not None and fill: #print 'rgbface', rgbFace write("gsave\n") - self.set_color(store=0, *rgbFace) + self.set_color(store=0, *rgbFace[:3]) write("fill\ngrestore\n") hatch = gc.get_hatch() if (hatch): self.set_hatch(hatch) - if self.linewidth > 0: + if self.linewidth > 0 and stroke: + self.set_color(*gc.get_rgb()[:3]) write("stroke\n") + if clippath: + write("grestore\n") if cliprect: write("grestore\n") - write('grestore\n') - def push_gc(self, gc, store=1): - """ - Push the current onto stack, with the exception of the clip box, which - must be isolated in a gsave/grestore pair. - """ - # local variable eliminates all repeated attribute lookups - write = self._pswriter.write + #write('grestore\n') - self.set_color(store=store, *gc.get_rgb()) - self.set_linewidth(gc.get_linewidth(), store=store) - self.set_linejoin(gc.get_joinstyle(), store=store) - self.set_linecap(gc.get_capstyle(), store=store) - self.set_linedash(store=store, *gc.get_dashes()) - -## cliprect = gc.get_clip_rectangle() -## if cliprect: -## x,y,w,h=cliprect -## write('%1.3f %1.3f %1.3f %1.3f clipbox\n' % (w,h,x,y)) - -## write("\n") - + class GraphicsContextPS(GraphicsContextBase): def get_capstyle(self): return {'butt':0, @@ -1044,7 +921,7 @@ xo = 72*0.5*(paperWidth - width) yo = 72*0.5*(paperHeight - height) - l, b, w, h = self.figure.bbox.get_bounds() + l, b, w, h = self.figure.bbox.bounds llx = xo lly = yo urx = llx + w @@ -1521,33 +1398,6 @@ # http://www.mactech.com/articles/mactech/Vol.09/09.04/PostscriptTutorial/ # http://www.math.ubc.ca/people/faculty/cass/graphics/text/www/ # -# Some comments about the implementation: -# -# Drawing ellipses: -# -# ellipse adds a counter-clockwise segment of an elliptical arc to the -# current path. The ellipse procedure takes six operands: the x and y -# coordinates of the center of the ellipse (the center is defined as -# the point of intersection of the major and minor axes), the -# ``radius'' of the ellipse in the x direction, the ``radius'' of the -# ellipse in the y direction, the starting angle of the elliptical arc -# and the ending angle of the elliptical arc. -# -# The basic strategy used in drawing the ellipse is to translate to -# the center of the ellipse, scale the user coordinate system by the x -# and y radius values, and then add a circular arc, centered at the -# origin with a 1 unit radius to the current path. We will be -# transforming the user coordinate system with the translate and -# rotate operators to add the elliptical arc segment but we don't want -# these transformations to affect other parts of the program. In other -# words, we would like to localize the effect of the transformations. -# Usually the gsave and grestore operators would be ideal candidates -# for this task. Unfortunately gsave and grestore are inappropriate -# for this situation because we cannot save the arc segment that we -# have added to the path. Instead we will localize the effect of the -# transformations by saving the current transformation matrix and -# restoring it explicitly after we have added the elliptical arc to -# the path. # The usage comments use the notation of the operator summary # in the PostScript Language reference manual. @@ -1558,28 +1408,22 @@ "/l { lineto } bind def", # x y *r* - "/r { rlineto } bind def", + # x1 y1 x2 y2 x y *c* - + "/c { curveto } bind def", + # *closepath* - + "/cl { closepath } bind def", # w h x y *box* - """/box { m 1 index 0 r 0 exch r neg 0 r - closepath + cl } bind def""", # w h x y *clipbox* - """/clipbox { box clip newpath - } bind def""", - # angle1 angle2 rx ry x y *ellipse* - - """/ellipse { - newpath - matrix currentmatrix 7 1 roll - translate - scale - 0 0 1 5 3 roll arc - setmatrix - closepath } bind def""" ] Modified: branches/transforms/lib/matplotlib/cbook.py =================================================================== --- branches/transforms/lib/matplotlib/cbook.py 2007-10-16 13:45:59 UTC (rev 3954) +++ branches/transforms/lib/matplotlib/cbook.py 2007-10-16 14:17:53 UTC (rev 3955) @@ -174,6 +174,7 @@ def __str__(self): return '<a list of %d %s objects>' % (len(self), self.type) +# MGDTODO: This is very incomplete def strip_math(s): 'remove latex formatting from mathtext' remove = (r'\rm', '\cal', '\tt', '\it', '\\', '{', '}') Modified: branches/transforms/lib/matplotlib/collections.py =================================================================== --- branches/transforms/lib/matplotlib/collections.py 2007-10-16 13:45:59 UTC (rev 3954) +++ branches/transforms/lib/matplotlib/collections.py 2007-10-16 14:17:53 UTC (rev 3955) @@ -167,7 +167,9 @@ self.update_scalarmappable() clippath, clippath_trans = self.get_transformed_clip_path_and_affine() - + if clippath_trans is not None: + clippath_trans = clippath_trans.frozen() + # MGDTODO: This may benefit from using TransformedPath if not transform.is_affine: paths = [transform.transform_path_non_affine(path) for path in paths] @@ -175,9 +177,9 @@ if not transOffset.is_affine: offsets = transOffset.transform_non_affine(offsets) transOffset = transOffset.get_affine() - + renderer.draw_path_collection( - transform, self.clipbox, clippath, clippath_trans, + transform.frozen(), self.clipbox, clippath, clippath_trans, paths, self.get_transforms(), npy.asarray(offsets, npy.float_), transOffset, self._facecolors, self._edgecolors, self._linewidths, Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-10-16 13:45:59 UTC (rev 3954) +++ branches/transforms/lib/matplotlib/lines.py 2007-10-16 14:17:53 UTC (rev 3955) @@ -458,7 +458,7 @@ if funcname != '_draw_nothing': tpath, affine = self._transformed_path.get_transformed_path_and_affine() lineFunc = getattr(self, funcname) - lineFunc(renderer, gc, tpath, affine) + lineFunc(renderer, gc, tpath, affine.frozen()) if self._marker is not None: gc = renderer.new_gc() @@ -470,7 +470,7 @@ if funcname != '_draw_nothing': tpath, affine = self._transformed_path.get_transformed_path_and_affine() markerFunc = getattr(self, funcname) - markerFunc(renderer, gc, tpath, affine) + markerFunc(renderer, gc, tpath, affine.frozen()) renderer.close_group('line2d') Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-10-16 13:45:59 UTC (rev 3954) +++ branches/transforms/lib/matplotlib/path.py 2007-10-16 14:17:53 UTC (rev 3955) @@ -142,7 +142,7 @@ return self._vertices vertices = property(_get_vertices) - def iter_endpoints(self): + def iter_segments(self): """ Iterates over all of the endpoints in the path. Unlike iterating directly over the vertices array, curve control @@ -151,15 +151,20 @@ i = 0 NUM_VERTICES = self.NUM_VERTICES vertices = self.vertices - for code in self.codes: + codes = self.codes + + while i < len(vertices): + code = codes[i] if code == self.CLOSEPOLY: + yield [], code i += 1 + elif code == self.STOP: + return else: num_vertices = NUM_VERTICES[code] - i += num_vertices - 1 - yield vertices[i] - i += 1 - + yield vertices[i:i+num_vertices].flatten(), code + i += num_vertices + def transformed(self, transform): """ Return a transformed copy of the path. Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-10-16 13:45:59 UTC (rev 3954) +++ branches/transforms/lib/matplotlib/transforms.py 2007-10-16 14:17:53 UTC (rev 3955) @@ -822,6 +822,12 @@ raise TypeError( "Can not add Transform to object of type '%s'" % type(other)) + def __array__(self): + """ + Used by C/C++ -based backends to get at the array matrix data. + """ + return self.frozen().__array__() + def transform(self, values): """ Performs the transformation on the given array of values. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-10-23 16:40:32
|
Revision: 3985 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3985&view=rev Author: mdboom Date: 2007-10-23 09:40:25 -0700 (Tue, 23 Oct 2007) Log Message: ----------- More progress on SVG. Refactored PS collection drawing to make it easier to reuse the (fairly complex) code. Modified Paths: -------------- branches/transforms/lib/matplotlib/backend_bases.py branches/transforms/lib/matplotlib/backends/backend_ps.py branches/transforms/lib/matplotlib/backends/backend_svg.py Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-10-23 14:30:57 UTC (rev 3984) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-10-23 16:40:25 UTC (rev 3985) @@ -55,13 +55,90 @@ offsetTrans, facecolors, edgecolors, linewidths, linestyles, antialiaseds): """ - MGDTODO: Document me. Explain that often the backend will not - want to override this. + This provides a fallback implementation of + draw_path_collection that makes multiple calls to draw_path. + Often, the backend will want to override this in order to + render each set of path data only once, and then reference + that path multiple times with the different offsets, colors, + styles etc. The methods _iter_collection_raw_paths and + _iter_collection are provided to help with (and standardize) + the implementation that in each backend. """ + path_ids = [] + for path, transform in self._iter_collection_raw_paths( + master_transform, paths, all_transforms): + path_ids.append((path, transform)) + + for xo, yo, path_id, gc, rgbFace in self._iter_collection( + path_ids, cliprect, clippath, clippath_trans, + offsets, offsetTrans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds): + path, transform = path_id + transform = transform.frozen().translate(xo, yo) + self.draw_path(gc, path, transform, rgbFace) + + def _iter_collection_raw_paths(self, master_transform, paths, all_transforms): + """ + This is a helper method (along with _iter_collection) to make + it easier to write a space-efficent draw_path_collection + implementation in a backend. + + This method yields all of the base path/transform + combinations, given a master transform, a list of paths and + list of transforms. + + The arguments should be exactly what is passed in to + draw_path_collection. + + The backend should take each yielded path and transform and + create an object can be referenced (reused) later. + """ Npaths = len(paths) + Ntransforms = len(all_transforms) + N = max(Npaths, Ntransforms) + + if Npaths == 0: + return + + for i in xrange(N): + path = paths[i % Npaths] + transform = all_transforms[i % Ntransforms] + if transform is None: + transform = transforms.IdentityTransform() + transform += master_transform + yield path, transform + + def _iter_collection(self, path_ids, cliprect, clippath, clippath_trans, + offsets, offsetTrans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds): + """ + This is a helper method (along with + _iter_collection_raw_paths) to make it easier to write a + space-efficent draw_path_collection implementation in a + backend. + + This method yields all of the path, offset and graphics + context combinations to draw the path collection. The caller + should already have looped over the results of + _iter_collection_raw_paths to draw this collection. + + The arguments should be the same as that passed into + draw_path_collection, with the exception of path_ids, which + is a list of arbitrary objects that the backend will use to + reference one of the paths created in the + _iter_collection_raw_paths stage. + + Each yielded result is of the form: + + xo, yo, path_id, gc, rgbFace + + where xo, yo is an offset; path_id is one of the elements of + path_ids; gc is a graphics context and rgbFace is a color to + use for filling the path. + """ + Npaths = len(path_ids) Noffsets = len(offsets) N = max(Npaths, Noffsets) - Ntransforms = min(len(all_transforms), N) Nfacecolors = len(facecolors) Nedgecolors = len(edgecolors) Nlinewidths = len(linewidths) @@ -71,16 +148,10 @@ if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0: return - ttransforms = [] - for i in range(Ntransforms): - transform = all_transforms[i] - if transform is None: - transform = transforms.IdentityTransform() - ttransforms.append((transform + master_transform).frozen()) - toffsets = offsetTrans.transform(offsets) gc = self.new_gc() + gc.set_clip_rectangle(cliprect) if clippath is not None: clippath = transforms.TransformedPath(clippath, clippath_trans) @@ -89,12 +160,9 @@ if Nfacecolors == 0: rgbFace = None - print linewidths, edgecolors - for i in xrange(N): - path = paths[i % Npaths] + path_id = path_ids[i % Npaths] xo, yo = toffsets[i % Noffsets] - transform = ttransforms[i % Ntransforms].frozen().translate(xo, yo) if Nfacecolors: rgbFace = facecolors[i % Nfacecolors] if Nedgecolors: @@ -103,8 +171,8 @@ gc.set_dashes(*linestyles[i % Nlinestyles]) gc.set_antialiased(antialiaseds[i % Naa]) - self.draw_path(gc, path, transform, rgbFace) - + yield xo, yo, path_id, gc, rgbFace + def get_image_magnification(self): """ Get the factor by which to magnify images passed to draw_image. Modified: branches/transforms/lib/matplotlib/backends/backend_ps.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_ps.py 2007-10-23 14:30:57 UTC (rev 3984) +++ branches/transforms/lib/matplotlib/backends/backend_ps.py 2007-10-23 16:40:25 UTC (rev 3985) @@ -503,57 +503,22 @@ linestyles, antialiaseds): write = self._pswriter.write - Npaths = len(paths) - Noffsets = len(offsets) - N = max(Npaths, Noffsets) - Ntransforms = min(len(all_transforms), N) - Ntpaths = max(Npaths, Ntransforms) - Nfacecolors = len(facecolors) - Nedgecolors = len(edgecolors) - Nlinewidths = len(linewidths) - Nlinestyles = len(linestyles) - Naa = len(antialiaseds) - - if (Nfacecolors == 0 and Nedgecolors == 0) or Npaths == 0: - return - - for i in range(Ntpaths): - path = paths[i % Npaths] - transform = all_transforms[i % Ntransforms] - if transform is None: - transform = IdentityTransform() - transform += master_transform - + path_codes = [] + for i, (path, transform) in enumerate(self._iter_collection_raw_paths( + master_transform, paths, all_transforms)): ps_cmd = ['/p%x_%x {' % (self._path_collection_id, i), 'newpath', 'translate'] ps_cmd.append(self._convert_path(path, transform)) ps_cmd.extend(['} bind def\n']) write('\n'.join(ps_cmd)) + path_codes.append("p%x_%x" % (self._path_collection_id, i)) - toffsets = offsetTrans.transform(offsets) - - gc = self.new_gc() + for xo, yo, path_id, gc, rgbFace in self._iter_collection( + path_codes, cliprect, clippath, clippath_trans, + offsets, offsetTrans, facecolors, edgecolors, + linewidths, linestyles, antialiaseds): - gc.set_clip_rectangle(cliprect) - if clippath is not None: - clippath = transforms.TransformedPath(clippath, clippath_trans) - gc.set_clippath(clippath) - - if Nfacecolors == 0: - rgbFace = None - - for i in xrange(N): - path_id = i % Ntpaths - xo, yo = toffsets[i % Noffsets] - if Nfacecolors: - rgbFace = facecolors[i % Nfacecolors] - if Nedgecolors: - gc.set_foreground(edgecolors[i % Nedgecolors]) - gc.set_linewidth(linewidths[i % Nlinewidths]) - gc.set_dashes(*linestyles[i % Nlinestyles]) - gc.set_antialiased(antialiaseds[i % Naa]) - - ps = "%g %g p%x_%x" % (xo, yo, self._path_collection_id, path_id) + ps = "%g %g %s" % (xo, yo, path_id) self._draw_ps(ps, gc, rgbFace) self._path_collection_id += 1 @@ -786,7 +751,9 @@ cint = gc.get_capstyle() self.set_linecap(cint) self.set_linedash(*gc.get_dashes()) - + if self.linewidth > 0 and stroke: + self.set_color(*gc.get_rgb()[:3]) + cliprect = gc.get_clip_rectangle() if cliprect: x,y,w,h=cliprect.bounds Modified: branches/transforms/lib/matplotlib/backends/backend_svg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_svg.py 2007-10-23 14:30:57 UTC (rev 3984) +++ branches/transforms/lib/matplotlib/backends/backend_svg.py 2007-10-23 16:40:25 UTC (rev 3985) @@ -47,16 +47,16 @@ svgwriter.write(svgProlog%(width,height,width,height)) def _draw_svg_element(self, element, details, gc, rgbFace): - cliprect, clipid = self._get_gc_clip_svg(gc) + clipid = self._get_gc_clip_svg(gc) if clipid is None: clippath = '' else: clippath = 'clip-path="url(#%s)"' % clipid style = self._get_style(gc, rgbFace) - self._svgwriter.write ('%s<%s style="%s" %s %s/>\n' % ( - cliprect, element, style, clippath, details)) - + self._svgwriter.write ('<%s style="%s" %s %s/>\n' % ( + element, style, clippath, details)) + def _get_font(self, prop): key = hash(prop) font = self.fontd.get(key) @@ -108,36 +108,23 @@ cliprect = gc.get_clip_rectangle() clippath, clippath_trans = gc.get_clip_path() if clippath is not None: - pathkey = (hash(clippath), hash(clippath_trans)) - path = '' - if self._clipd.get(pathkey) is None: - self._clipd[pathkey] = clippath - path_data = self._convert_path(clippath, clippath_trans) - path = """\ -<defs> - <clipPath id="%(pathkey)s"> - <path d="%(path_data)s"/> - </clipPath> -</defs> -""" % locals() - return path, pathkey + path_data = self._convert_path(clippath, clippath_trans) + path = '<path d="%s"/>' % path_data elif cliprect is not None: - rectkey = hash(cliprect) - box = '' - if self._clipd.get(rectkey) is None: - self._clipd[rectkey] = cliprect - x, y, w, h = cliprect.bounds - y = self.height-(y+h) - box = """\ -<defs> - <clipPath id="%(rectkey)s"> - <rect x="%(x)s" y="%(y)s" width="%(w)s" height="%(h)s"/> - </clipPath> -</defs> -""" % locals() - return box, rectkey - - return '', None + x, y, w, h = cliprect.bounds + y = self.height-(y+h) + path = '<rect x="%(x)s" y="%(y)s" width="%(w)s" height="%(h)s"/>' % locals() + else: + return None + + id = self._clipd.get(path) + if id is None: + id = 'p%x' % len(self._clipd) + self._svgwriter.write('<defs>\n <clipPath id="%s">\n' % id) + self._svgwriter.write(path) + self._svgwriter.write('\n </clipPath>\n</defs>') + self._clipd[path] = id + return id def open_group(self, s): self._groupd[s] = self._groupd.get(s,0) + 1 @@ -193,17 +180,17 @@ def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): write = self._svgwriter.write - key = self._convert_path(marker_path, marker_trans + Affine2D().scale(0, -1.0)) + key = self._convert_path(marker_path, marker_trans + Affine2D().scale(1.0, -1.0)) name = self._markers.get(key) if name is None: - name = 'm_%x' % len(self._markers) + name = 'm%x' % len(self._markers) write('<defs><path id="%s" d="%s"/></defs>\n' % (name, key)) self._markers[key] = name trans_and_flip = self._make_flip_transform(trans) tpath = trans_and_flip.transform_path(path) for x, y in tpath.vertices: - details = 'xlink:href="#%s" transform="translate(%f, %f)"' % (name, x, y) + details = 'xlink:href="#%s" x="%f" y="%f"' % (name, x, y) self._draw_svg_element('use', details, gc, rgbFace) def draw_image(self, x, y, im, bbox): @@ -307,7 +294,7 @@ svg.append('<use xlink:href="#%s"' % charid) if currx != 0: - svg.append(' transform="translate(%s)"' % + svg.append(' x="%s"' % (currx * (self.FONT_SCALE / fontsize))) svg.append('/>\n') currx += (glyph.linearHoriAdvance / 65536.0) @@ -364,7 +351,7 @@ if step[0] != 4: currx, curry = step[-2], -step[-1] - char_num = 'c_%x' % len(self._char_defs) + char_num = 'c%x' % len(self._char_defs) path_element = '<path id="%s" d="%s"/>\n' % (char_num, ''.join(path_data)) self._char_defs[char_id] = (char_num, path_element) return char_num This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <md...@us...> - 2007-10-24 19:22:04
|
Revision: 3997 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3997&view=rev Author: mdboom Date: 2007-10-24 12:22:00 -0700 (Wed, 24 Oct 2007) Log Message: ----------- Renamed [xmin, ymin, xmax, ymax] in Bbox to [x0, y0, x1, y1] and provide functions that really do give xmax etc. as well. Renamed lbrt to extents and lbwh to bounds (for consistency). Removed some dead code. 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/backends/backend_pdf.py branches/transforms/lib/matplotlib/collections.py branches/transforms/lib/matplotlib/figure.py branches/transforms/lib/matplotlib/image.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/text.py branches/transforms/lib/matplotlib/transforms.py Modified: branches/transforms/lib/matplotlib/axes.py =================================================================== --- branches/transforms/lib/matplotlib/axes.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/axes.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -480,7 +480,7 @@ if isinstance(rect, mtransforms.Bbox): self._position = rect else: - self._position = mtransforms.Bbox.from_lbwh(*rect) + self._position = mtransforms.Bbox.from_bounds(*rect) self._originalPosition = self._position.frozen() self.set_axes(self) self.set_aspect('auto') @@ -1696,7 +1696,7 @@ # Call all of the other y-axes that are shared with this one 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) + other.set_ylim(self.viewLim.intervaly, emit=False) if self.figure.canvas is not None: self.figure.canvas.draw_idle() @@ -1902,7 +1902,6 @@ if self.get_aspect() != 'auto': dx = 0.5 * (dx + dy) dy = dx - xmin, ymin, xmax, ymax = p.lim.lbrt alpha = npy.power(10.0, (dx, dy)) start = p.trans_inverse.transform_point((p.x, p.y)) @@ -5207,7 +5206,7 @@ figBottom = top - (rowNum+1)*figH - rowNum*sepH figLeft = left + colNum*(figW + sepW) - self.figbox = mtransforms.Bbox.from_lbwh(figLeft, figBottom, figW, figH) + self.figbox = mtransforms.Bbox.from_bounds(figLeft, figBottom, figW, figH) self.rowNum = rowNum self.colNum = colNum self.numRows = rows Modified: branches/transforms/lib/matplotlib/axis.py =================================================================== --- branches/transforms/lib/matplotlib/axis.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/axis.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -1070,7 +1070,7 @@ bottom = self.axes.bbox.ymin else: bbox = Bbox.union(bboxes) - bottom = bbox.ymin + bottom = bbox.y0 self.label.set_position( (x, bottom - self.LABELPAD*self.figure.dpi / 72.0)) else: @@ -1078,8 +1078,7 @@ top = self.axes.bbox.ymax else: bbox = bbox_union(bboxes2) - top = bbox.ymax - + top = bbox.y1 self.label.set_position( (x, top+self.LABELPAD*self.figure.dpi / 72.0)) def _update_offset_text_position(self, bboxes, bboxes2): @@ -1092,7 +1091,7 @@ bottom = self.axes.bbox.ymin else: bbox = Bbox.union(bboxes) - bottom = bbox.ymin + bottom = bbox.y0 self.offsetText.set_position((x, bottom-self.OFFSETTEXTPAD*self.figure.dpi/72.0)) def set_ticks_position(self, position): @@ -1280,9 +1279,8 @@ if not len(bboxes): left = self.axes.bbox.xmin else: - bbox = Bbox.union(bboxes) - left = bbox.xmin + left = bbox.x0 self.label.set_position( (left-self.LABELPAD*self.figure.dpi/72.0, y)) @@ -1291,7 +1289,7 @@ right = self.axes.bbox.xmax else: bbox = Bbox.union(bboxes2) - right = bbox.xmax + right = bbox.x1 self.label.set_position( (right+self.LABELPAD*self.figure.dpi/72.0, y)) Modified: branches/transforms/lib/matplotlib/backend_bases.py =================================================================== --- branches/transforms/lib/matplotlib/backend_bases.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/backend_bases.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -1422,7 +1422,7 @@ self.draw() return - xmin, ymin, xmax, ymax = lim.lbrt + x0, y0, x1, y1 = lim.extents # zoom to rect inverse = a.transData.inverted() @@ -1432,49 +1432,49 @@ Ymin,Ymax=a.get_ylim() if Xmin < Xmax: - if x<lastx: xmin, xmax = x, lastx - else: xmin, xmax = lastx, x - if xmin < Xmin: xmin=Xmin - if xmax > Xmax: xmax=Xmax + if x<lastx: x0, x1 = x, lastx + else: x0, x1 = lastx, x + if x0 < Xmin: x0=Xmin + if x1 > Xmax: x1=Xmax else: - if x>lastx: xmin, xmax = x, lastx - else: xmin, xmax = lastx, x - if xmin > Xmin: xmin=Xmin - if xmax < Xmax: xmax=Xmax + if x>lastx: x0, x1 = x, lastx + else: x0, x1 = lastx, x + if x0 > Xmin: x0=Xmin + if x1 < Xmax: x1=Xmax if Ymin < Ymax: - if y<lasty: ymin, ymax = y, lasty - else: ymin, ymax = lasty, y - if ymin < Ymin: ymin=Ymin - if ymax > Ymax: ymax=Ymax + if y<lasty: y0, y1 = y, lasty + else: y0, y1 = lasty, y + if y0 < Ymin: y0=Ymin + if y1 > Ymax: y1=Ymax else: - if y>lasty: ymin, ymax = y, lasty - else: ymin, ymax = lasty, y - if ymin > Ymin: ymin=Ymin - if ymax < Ymax: ymax=Ymax + if y>lasty: y0, y1 = y, lasty + else: y0, y1 = lasty, y + if y0 > Ymin: y0=Ymin + if y1 < Ymax: y1=Ymax if self._button_pressed == 1: - a.set_xlim((xmin, xmax)) - a.set_ylim((ymin, ymax)) + a.set_xlim((x0, x1)) + a.set_ylim((y0, y1)) elif self._button_pressed == 3: if a.get_xscale()=='log': - alpha=npy.log(Xmax/Xmin)/npy.log(xmax/xmin) - x1=pow(Xmin/xmin,alpha)*Xmin - x2=pow(Xmax/xmin,alpha)*Xmin + alpha=npy.log(Xmax/Xmin)/npy.log(x1/x0) + rx1=pow(Xmin/x0,alpha)*Xmin + x2=pow(Xmax/x0,alpha)*Xmin else: - alpha=(Xmax-Xmin)/(xmax-xmin) - x1=alpha*(Xmin-xmin)+Xmin - x2=alpha*(Xmax-xmin)+Xmin + alpha=(Xmax-Xmin)/(x1-x0) + rx1=alpha*(Xmin-x0)+Xmin + x2=alpha*(Xmax-x0)+Xmin if a.get_yscale()=='log': - alpha=npy.log(Ymax/Ymin)/npy.log(ymax/ymin) - y1=pow(Ymin/ymin,alpha)*Ymin - y2=pow(Ymax/ymin,alpha)*Ymin + alpha=npy.log(Ymax/Ymin)/npy.log(y1/y0) + ry1=pow(Ymin/y0,alpha)*Ymin + ry2=pow(Ymax/y0,alpha)*Ymin else: - alpha=(Ymax-Ymin)/(ymax-ymin) - y1=alpha*(Ymin-ymin)+Ymin - y2=alpha*(Ymax-ymin)+Ymin - a.set_xlim((x1, x2)) - a.set_ylim((y1, y2)) + alpha=(Ymax-Ymin)/(y1-y0) + ry1=alpha*(Ymin-y0)+Ymin + ry2=alpha*(Ymax-y0)+Ymin + a.set_xlim((rx1, rx2)) + a.set_ylim((ry1, ry2)) self.draw() self._xypress = None Modified: branches/transforms/lib/matplotlib/backends/backend_agg.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/backends/backend_agg.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -118,7 +118,7 @@ self.mathtext_parser = MathTextParser('Agg') self._fontd = {} - self.bbox = Bbox.from_lbwh(0,0, self.width, self.height) + self.bbox = Bbox.from_bounds(0, 0, self.width, self.height) if __debug__: verbose.report('RendererAgg.__init__ done', 'debug-annoying') @@ -227,7 +227,7 @@ cliprect = gc.get_clip_rectangle() if cliprect is None: bbox = None - else: bbox = Bbox.from_lbwh(*cliprect) + else: bbox = Bbox.from_bounds(*cliprect) self.draw_image(x, self.height-y, im, bbox) def get_canvas_width_height(self): Modified: branches/transforms/lib/matplotlib/backends/backend_pdf.py =================================================================== --- branches/transforms/lib/matplotlib/backends/backend_pdf.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/backends/backend_pdf.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -1032,12 +1032,12 @@ def writeMarkers(self): for tup in self.markers.values(): name, object, path, trans, fillp, lw = tup - bbox = Bbox.from_lbrt(*path.get_extents(trans)) + bbox = Bbox.from_extents(*path.get_extents(trans)) bbox = bbox.padded(lw * 0.5) self.beginStream( object.id, None, {'Type': Name('XObject'), 'Subtype': Name('Form'), - 'BBox': list(bbox.lbrt) }) + 'BBox': list(bbox.extents) }) self.writePath(path, trans) if fillp: self.output(Op.fill_stroke) Modified: branches/transforms/lib/matplotlib/collections.py =================================================================== --- branches/transforms/lib/matplotlib/collections.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/collections.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -126,12 +126,12 @@ return self._transforms def get_datalim(self, transData): - result = transforms.Bbox.from_lbrt(*path.get_path_collection_extents( - self.get_transform().frozen(), - self.get_paths(), - self.get_transforms(), - self._offsets, - self._transOffset.frozen())) + result = path.get_path_collection_extents( + self.get_transform().frozen(), + self.get_paths(), + self.get_transforms(), + self._offsets, + self._transOffset.frozen()) result = result.transformed(transData.inverted()) return result Modified: branches/transforms/lib/matplotlib/figure.py =================================================================== --- branches/transforms/lib/matplotlib/figure.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/figure.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -131,7 +131,7 @@ self._dpi_scale_trans = Affine2D() self.dpi = dpi - self.bbox_inches = Bbox.from_lbwh(0, 0, *figsize) + self.bbox_inches = Bbox.from_bounds(0, 0, *figsize) self.bbox = TransformedBbox(self.bbox_inches, self._dpi_scale_trans) self.frameon = frameon @@ -351,11 +351,11 @@ def get_figwidth(self): 'Return the figwidth as a float' - return self.bbox_inches.xmax + return self.bbox_inches.width def get_figheight(self): 'Return the figheight as a float' - return self.bbox_inches.ymax + return self.bbox_inches.height def get_dpi(self): 'Return the dpi as a float' @@ -395,7 +395,7 @@ ACCEPTS: float """ - self.bbox_inches.xmax = val + self.bbox_inches.x1 = val def set_figheight(self, val): """ @@ -403,7 +403,7 @@ ACCEPTS: float """ - self.bbox_inches.ymax = val + self.bbox_inches.y1 = val def set_frameon(self, b): """ Modified: branches/transforms/lib/matplotlib/image.py =================================================================== --- branches/transforms/lib/matplotlib/image.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/image.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -156,8 +156,8 @@ im.apply_translation(-1, -1) # the viewport translation - tx = (xmin-self.axes.viewLim.xmin)/dxintv * numcols - ty = (ymin-self.axes.viewLim.ymin)/dyintv * numrows + tx = (xmin-self.axes.viewLim.x0)/dxintv * numcols + ty = (ymin-self.axes.viewLim.y0)/dyintv * numrows l, b, widthDisplay, heightDisplay = self.axes.bbox.bounds widthDisplay *= magnification Modified: branches/transforms/lib/matplotlib/legend.py =================================================================== --- branches/transforms/lib/matplotlib/legend.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/legend.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -442,7 +442,7 @@ candidates = [] for l, b in consider: - legendBox = Bbox.from_lbwh(l, b, width, height) + legendBox = Bbox.from_bounds(l, b, width, height) badness = 0 badness = legendBox.count_contains(verts) badness += legendBox.count_overlaps(bboxes) Modified: branches/transforms/lib/matplotlib/lines.py =================================================================== --- branches/transforms/lib/matplotlib/lines.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/lines.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -673,39 +673,6 @@ gc.set_linestyle('solid') renderer.draw_path(gc, path, trans) - - def _step(self, x, y, where): - if not cbook.iterable(x): - x = ma.array([x], dtype=npy.float_) - if not cbook.iterable(y): - y = ma.array([y], dtype=npy.float_) - - if where=='pre': - x2 = ma.zeros((2*len(x)-1,), npy.float_) - y2 = ma.zeros((2*len(y)-1,), npy.float_) - - x2[0::2], x2[1::2] = x, x[:-1] - y2[0::2], y2[1:-1:2] = y, y[1:] - - elif where=='post': - x2 = ma.zeros((2*len(x)-1,), npy.float_) - y2 = ma.zeros((2*len(y)-1,), npy.float_) - - x2[::2], x2[1:-1:2] = x, x[1:] - y2[0::2], y2[1::2] = y, y[:-1] - - elif where=='mid': - x2 = ma.zeros((2*len(x),), npy.float_) - y2 = ma.zeros((2*len(y),), npy.float_) - - x2[1:-1:2] = 0.5*(x[:-1]+x[1:]) - x2[2::2] = 0.5*(x[:-1]+x[1:]) - x2[0], x2[-1] = x[0], x[-1] - - y2[0::2], y2[1::2] = y, y - - return x2, y2 - def _draw_steps_pre(self, renderer, gc, path, trans): vertices = self._xy Modified: branches/transforms/lib/matplotlib/patches.py =================================================================== --- branches/transforms/lib/matplotlib/patches.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/patches.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -346,7 +346,7 @@ 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._bbox = transforms.Bbox.from_extents(left, bottom, right, top) self._rect_transform = transforms.BboxTransform( transforms.Bbox.unit(), self._bbox) __init__.__doc__ = cbook.dedent(__init__.__doc__) % artist.kwdocd @@ -367,11 +367,11 @@ def get_x(self): "Return the left coord of the rectangle" - return self._bbox.xmin + return self._bbox.x0 def get_y(self): "Return the bottom coord of the rectangle" - return self._bbox.ymin + return self._bbox.y0 def get_width(self): "Return the width of the rectangle" @@ -405,7 +405,7 @@ ACCEPTS: float """ - self._bbox.xmax = self._bbox.xmin + w + self._bbox.x1 = self._bbox.x0 + w def set_height(self, h): """ @@ -413,7 +413,7 @@ ACCEPTS: float """ - self._bbox.ymax = self._bbox.ymin + h + self._bbox.y1 = self._bbox.y0 + h def set_bounds(self, *args): """ Modified: branches/transforms/lib/matplotlib/path.py =================================================================== --- branches/transforms/lib/matplotlib/path.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/path.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -10,7 +10,8 @@ from numpy import ma as ma from matplotlib._path import point_in_path, get_path_extents, \ - get_path_collection_extents, point_in_path_collection + point_in_path_collection +import matplotlib._path as _path from matplotlib.cbook import simple_linear_interpolation KAPPA = 4.0 * (npy.sqrt(2) - 1) / 3.0 @@ -199,7 +200,7 @@ from transforms import Affine2D, Bbox if transform is None: transform = Affine2D() - return Bbox.from_lbrt(*get_path_extents(self, transform)) + return Bbox.from_extents(*get_path_extents(self, transform)) def interpolated(self, steps): """ @@ -402,3 +403,6 @@ """ return cls.arc(theta1, theta2, True) wedge = classmethod(wedge) + +def get_path_collection_extents(*args): + return Bbox.from_extents(*_path.get_path_collection_extents(*args)) Modified: branches/transforms/lib/matplotlib/projections/polar.py =================================================================== --- branches/transforms/lib/matplotlib/projections/polar.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/projections/polar.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -256,7 +256,7 @@ return Circle((0.5, 0.5), 0.5) def set_rmax(self, rmax): - self.viewLim.ymax = rmax + self.viewLim.y1 = rmax angle = self._r_label1_position.to_values()[4] self._r_label1_position.clear().translate( angle, rmax * self._rpad) Modified: branches/transforms/lib/matplotlib/text.py =================================================================== --- branches/transforms/lib/matplotlib/text.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/text.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -253,7 +253,7 @@ xmin -= offsetx ymin -= offsety - bbox = Bbox.from_lbwh(xmin, ymin, width, height) + bbox = Bbox.from_bounds(xmin, ymin, width, height) # now rotate the positions around the first x,y position xys = M.transform(offsetLayout) @@ -407,7 +407,7 @@ if not self.get_visible(): return Bbox.unit() if self._text == '': tx, ty = self._get_xy_display() - return Bbox.from_lbwh(tx,ty,0,0) + return Bbox.from_bounds(tx,ty,0,0) if renderer is not None: self._renderer = renderer Modified: branches/transforms/lib/matplotlib/transforms.py =================================================================== --- branches/transforms/lib/matplotlib/transforms.py 2007-10-24 18:49:08 UTC (rev 3996) +++ branches/transforms/lib/matplotlib/transforms.py 2007-10-24 19:22:00 UTC (rev 3997) @@ -196,28 +196,46 @@ def __array__(self, *args, **kwargs): return self.get_points() + def _get_x0(self): + return self.get_points()[0, 0] + x0 = property(_get_x0) + + def _get_y0(self): + return self.get_points()[0, 1] + y0 = property(_get_y0) + + def _get_x1(self): + return self.get_points()[1, 0] + x1 = property(_get_x1) + + def _get_y1(self): + return self.get_points()[1, 1] + y1 = property(_get_y1) + def _get_xmin(self): - return self.get_points()[0, 0] + return min(self.get_points()[:, 0]) xmin = property(_get_xmin) - + def _get_ymin(self): - return self.get_points()[0, 1] + return min(self.get_points()[:, 1]) ymin = property(_get_ymin) def _get_xmax(self): - return self.get_points()[1, 0] + return max(self.get_points()[:, 0]) xmax = property(_get_xmax) def _get_ymax(self): - return self.get_points()[1, 1] + return max(self.get_points()[:, 1]) ymax = property(_get_ymax) - + def _get_min(self): - return self.get_points()[0] + return [min(self.get_points()[:, 0]), + min(self.get_points()[:, 1])] min = property(_get_min) def _get_max(self): - return self.get_points()[1] + return [max(self.get_points()[:, 0]), + max(self.get_points()[:, 1])] max = property(_get_max) def _get_intervalx(self): @@ -244,35 +262,35 @@ size = property(_get_size) def _get_bounds(self): - ((xmin, ymin), (xmax, ymax)) = self.get_points() - return (xmin, ymin, xmax - xmin, ymax - ymin) + ((x0, y0), (x1, y1)) = self.get_points() + return (x0, y0, x1 - x0, y1 - y0) bounds = property(_get_bounds) - def _get_lbrt(self): + def _get_extents(self): return self.get_points().flatten().copy() - lbrt = property(_get_lbrt) + extents = property(_get_extents) def get_points(self): return NotImplementedError() def containsx(self, x): - xmin, xmax = self.intervalx - return ((xmin < xmax - and (x >= xmin and x <= xmax)) - or (x >= xmax and x <= xmin)) + x0, x1 = self.intervalx + return ((x0 < x1 + and (x >= x0 and x <= x1)) + or (x >= x1 and x <= x0)) def containsy(self, y): - ymin, ymax = self.intervaly - return ((ymin < ymax - and (y >= ymin and y <= ymax)) - or (y >= ymax and y <= ymin)) + y0, y1 = self.intervaly + return ((y0 < y1 + and (y >= y0 and y <= y1)) + or (y >= y1 and y <= y0)) def contains(self, x, y): return self.containsx(x) and self.containsy(y) def overlaps(self, other): - ax1, ay1, ax2, ay2 = self._get_lbrt() - bx1, by1, bx2, by2 = other._get_lbrt() + ax1, ay1, ax2, ay2 = self._get_extents() + bx1, by1, bx2, by2 = other._get_extents() if ax2 < ax1: ax2, ax1 = ax1, ax2 @@ -289,24 +307,24 @@ (by1 > ay2)) def fully_containsx(self, x): - xmin, xmax = self.intervalx - return ((xmin < xmax - and (x > xmin and x < xmax)) - or (x > xmax and x < xmin)) + x0, x1 = self.intervalx + return ((x0 < x1 + and (x > x0 and x < x1)) + or (x > x1 and x < x0)) def fully_containsy(self, y): - ymin, ymax = self.intervaly - return ((ymin < ymax - and (x > ymin and x < ymax)) - or (x > ymax and x < ymin)) + y0, y1 = self.intervaly + return ((y0 < y1 + and (x > y0 and x < y1)) + or (x > y1 and x < y0)) def fully_contains(self, x, y): return self.fully_containsx(x) \ and self.fully_containsy(y) def fully_overlaps(self, other): - ax1, ay1, ax2, ay2 = self._get_lbrt() - bx1, by1, bx2, by2 = other._get_lbrt() + ax1, ay1, ax2, ay2 = self._get_extents() + bx1, by1, bx2, by2 = other._get_extents() if ax2 < ax1: ax2, ax1 = ax1, ax2 @@ -354,7 +372,7 @@ or b) a string: C for centered, S for bottom-center, SE for bottom-left, E for left, etc. - Optional arg container is the lbwh box within which the BBox + Optional arg container is the box within which the BBox is positioned; it defaults to the initial BBox. """ if container is None: @@ -413,10 +431,10 @@ """ boxes = [] xf = [0] + list(args) + [1] - l, b, r, t = self.lbrt - w = r - l + x0, y0, x1, y1 = self._get_extents() + w = x1 - x0 for xf0, xf1 in zip(xf[:-1], xf[1:]): - boxes.append(Bbox([[l + xf0 * w, b], [l + xf1 * w, t]])) + boxes.append(Bbox([[x0 + xf0 * w, y0], [x0 + xf1 * w, y1]])) return boxes def splity(self, *args): @@ -429,10 +447,10 @@ """ boxes = [] yf = [0] + list(args) + [1] - l, b, r, t = self.lbrt - h = t - b + x0, y0, x1, y1 = self._get_extents() + h = y1 - y0 for yf0, yf1 in zip(yf[:-1], yf[1:]): - boxes.append(Bbox([[l, b + yf0 * h], [r, b + yf1 * h]])) + boxes.append(Bbox([[x0, y0 + yf0 * h], [x1, y0 + yf1 * h]])) return boxes def count_contains(self, vertices): @@ -444,12 +462,12 @@ if len(vertices) == 0: return 0 vertices = npy.asarray(vertices) - xmin, ymin, xmax, ymax = self._get_lbrt() - dxmin = npy.sign(vertices[:, 0] - xmin) - dymin = npy.sign(vertices[:, 1] - ymin) - dxmax = npy.sign(vertices[:, 0] - xmax) - dymax = npy.sign(vertices[:, 1] - ymax) - inside = (abs(dxmin + dxmax) + abs(dymin + dymax)) <= 2 + x0, y0, x1, y1 = self._get_extents() + dx0 = npy.sign(vertices[:, 0] - x0) + dy0 = npy.sign(vertices[:, 1] - y0) + dx1 = npy.sign(vertices[:, 0] - x1) + dy1 = npy.sign(vertices[:, 1] - y1) + inside = (abs(dx0 + dx1) + abs(dy0 + dy1)) <= 2 return N.sum(inside) def count_overlaps(self, bboxes): @@ -458,7 +476,7 @@ bboxes is a sequence of Bbox objects """ - ax1, ay1, ax2, ay2 = self._get_lbrt() + ax1, ay1, ax2, ay2 = self._get_extents() if ax2 < ax1: ax2, ax1 = ax1, ax2 if ay2 < ay1: @@ -466,7 +484,7 @@ count = 0 for bbox in bboxes: - # bx1, by1, bx2, by2 = bbox._get_lbrt() ... inlined... + # bx1, by1, bx2, by2 = bbox._get_extents() ... inlined... bx1, by1, bx2, by2 = bbox.get_points().flatten() if bx2 < bx1: bx2, bx1 = bx1, bx2 @@ -534,21 +552,21 @@ if len(bboxes) == 1: return bboxes[0] - xmin = npy.inf - ymin = npy.inf - xmax = -npy.inf - ymax = -npy.inf + x0 = npy.inf + y0 = npy.inf + x1 = -npy.inf + y1 = -npy.inf for bbox in bboxes: points = bbox.get_points() xs = points[:, 0] ys = points[:, 1] - xmin = min(xmin, npy.min(xs)) - ymin = min(ymin, npy.min(ys)) - xmax = max(xmax, npy.max(xs)) - ymax = max(ymax, npy.max(ys)) + x0 = min(x0, npy.min(xs)) + y0 = min(y0, npy.min(ys)) + x1 = max(x1, npy.max(xs)) + y1 = max(y1, npy.max(ys)) - return Bbox.from_lbrt(xmin, ymin, xmax, ymax) + return Bbox.from_extents(x0, y0, x1, y1) union = staticmethod(union) @@ -557,10 +575,10 @@ """ Create a new bounding box. - points: a 2x2 numpy array of the form [[xmin, ymin], [xmax, ymax]] + points: a 2x2 numpy array of the form [[x0, y0], [x1, y1]] If you need to create Bbox from another form of data, consider the - class methods unit, from_lbwh and from_lbrt. + class methods unit, from_bounds and from_extents. """ BboxBase.__init__(self) self._points = npy.asarray(points, npy.float_) @@ -572,19 +590,19 @@ """ Create a new unit BBox from (0, 0) to (1, 1). """ - return Bbox.from_lbrt(0., 0., 1., 1.) + return Bbox.from_extents(0., 0., 1., 1.) unit = staticmethod(unit) #@staticmethod - def from_lbwh(left, bottom, width, height): + def from_bounds(left, bottom, width, height): """ Create a new Bbox from left, bottom, width and height. """ - return Bbox.from_lbrt(left, bottom, left + width, bottom + height) - from_lbwh = staticmethod(from_lbwh) + return Bbox.from_extents(left, bottom, left + width, bottom + height) + from_bounds = staticmethod(from_bounds) #@staticmethod - def from_lbrt(*args): + def from_extents(*args): """ Create a new Bbox from left, bottom, right and top. @@ -592,7 +610,7 @@ """ points = npy.array(args, dtype=npy.float_).reshape(2, 2) return Bbox(points) - from_lbrt = staticmethod(from_lbrt) + from_extents = staticmethod(from_extents) def __repr__(self): return 'Bbox(%s)' % repr(self._points) @@ -648,11 +666,12 @@ npy.float_) self._minpos = minpos else: + x0, y0, x1, y1 = self._get_extents() points = npy.array( - [[min(x.min(), self.xmin), - min(y.min(), self.ymin)], - [max(x.max(), self.xmax), - max(y.max(), self.ymax)]], + [[min(x.min(), x0, x1), + min(y.min(), y0, y1)], + [max(x.max(), x0, x1), + max(y.max(), y0, y1)]], npy.float_) self._minpos = npy.minimum(minpos, self._minpos) @@ -672,25 +691,25 @@ """ return self.update_from_data(xy[:, 0], xy[:, 1], ignore) - def _set_xmin(self, val): + def _set_x0(self, val): self._points[0, 0] = val self.invalidate() - xmin = property(BboxBase._get_xmin, _set_xmin) + x0 = property(BboxBase._get_x0, _set_x0) - def _set_ymin(self, val): + def _set_y0(self, val): self._points[0, 1] = val self.invalidate() - ymin = property(BboxBase._get_ymin, _set_ymin) + y0 = property(BboxBase._get_y0, _set_y0) - def _set_xmax(self, val): + def _set_x1(self, val): self._points[1, 0] = val self.invalidate() - xmax = property(BboxBase._get_xmax, _set_xmax) + x1 = property(BboxBase._get_x1, _set_x1) - def _set_ymax(self, val): + def _set_y1(self, val): self._points[1, 1] = val self.invalidate() - ymax = property(BboxBase._get_ymax, _set_ymax) + y1 = property(BboxBase._get_y1, _set_y1) def _set_min(self, val): self._points[0] = val @@ -735,7 +754,7 @@ def get_points(self): """ Set the points of the bounding box directly as a numpy array - of the form: [[xmin, ymin], [xmax, ymax]]. + of the form: [[x0, y0], [x1, y1]]. """ self._invalid = 0 return self._points @@ -743,7 +762,7 @@ def set_points(self, points): """ Set the points of the bounding box directly from a numpy array - of the form: [[xmin, ymin], [xmax, ymax]]. No error checking + of the form: [[x0, y0], [x1, y1]]. No error checking is performed, as this method is mainly for internal use. """ if npy.any(self._points != points): @@ -2137,11 +2156,11 @@ from random import random import timeit - bbox = Bbox.from_lbrt(10., 15., 20., 25.) - assert bbox.xmin == 10 - assert bbox.ymin == 15 - assert bbox.xmax == 20 - assert bbox.ymax == 25 + bbox = Bbox.from_extents(10., 15., 20., 25.) + assert bbox.x0 == 10 + assert bbox.y0 == 15 + assert bbox.x1 == 20 + assert bbox.y1 == 25 assert npy.all(bbox.min == [10, 15]) assert npy.all(bbox.max == [20, 25]) @@ -2160,18 +2179,18 @@ assert bbox.bounds == (11, 16, 10, 10) - bbox.xmin = 12 - bbox.ymin = 17 - bbox.xmax = 22 - bbox.ymax = 27 + bbox.x0 = 12 + bbox.y0 = 17 + bbox.x1 = 22 + bbox.y1 = 27 assert bbox.bounds == (12, 17, 10, 10) - bbox = Bbox.from_lbwh(10, 11, 12, 13) + bbox = Bbox.from_bounds(10, 11, 12, 13) assert bbox.bounds == (10, 11, 12, 13) bbox_copy = copy.deepcopy(bbox) - assert (bbox.lbrt == bbox_copy.lbrt).all() + assert (bbox.extents == bbox_copy.extents).all() bbox_copy.max = (14, 15) assert bbox.bounds == (10, 11, 12, 13) assert bbox_copy.bounds == (10, 11, 4, 4) @@ -2180,7 +2199,7 @@ bbox2 = Bbox([[30., 35.], [40., 45.]]) trans = BboxTransform(bbox1, bbox2) bbox3 = bbox1.transformed(trans) - assert (bbox3.lbrt == bbox2.lbrt).all() + assert (bbox3.extents == bbox2.extents).all() translation = Affine2D().translate(10, 20) assert translation.to_values() == (1, 0, 0, 1, 10, 20) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |