|
From: <jd...@us...> - 2007-07-20 15:17:39
|
Revision: 3591
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=3591&view=rev
Author: jdh2358
Date: 2007-07-20 08:17:37 -0700 (Fri, 20 Jul 2007)
Log Message:
-----------
affines are first class objects
Modified Paths:
--------------
trunk/matplotlib/mpl1/mpl1.py
Modified: trunk/matplotlib/mpl1/mpl1.py
===================================================================
--- trunk/matplotlib/mpl1/mpl1.py 2007-07-20 14:19:48 UTC (rev 3590)
+++ trunk/matplotlib/mpl1/mpl1.py 2007-07-20 15:17:37 UTC (rev 3591)
@@ -22,9 +22,89 @@
from matplotlib import agg
from matplotlib import colors as mcolors
+from matplotlib import cbook
import numpy as npy
+is_string_like = cbook.is_string_like
+
+
+class Affine(traits.HasTraits):
+ """
+ An affine 3x3 matrix that supports matrix multiplication with
+ other Affine instances or numpy arrays.
+
+ a = Affine()
+ a.translate = 10,20
+ a.scale = 20, 40
+
+ Be careful not to do *inplace* operations on the array components
+ or the update callbacks will not be triggered, eg DO NOT
+
+ a.translate += 10, 20
+
+ rather DO
+
+ a.translate_delta(10, 20)
+ """
+ data = traits.Array('d', (3,3), npy.array([[1,0,0],[0,1,0],[0,0,1]], npy.float_))
+
+ translate = traits.Array('d', (2,), [0,0])
+ scale = traits.Array('d', (2,), [0,0])
+ vec6 = traits.Array('d', (6,), [1,0,0,1,0,0])
+
+ def translate_delta(self, tx, ty):
+ oldtx, oldty = self.translate
+ self.translate = oldtx + tx, oldty + ty
+
+ def _translate_changed(self, old, new):
+ #print 'translate change', new
+ tx, ty = new
+ self.data[0][-1] = tx
+ self.data[1][-1] = ty
+ self.vec6[-2:] = tx, ty
+
+ def _vec6_changed(self, old, new):
+ #print 'vec6 change', new
+ sx, b, c, sy, tx, ty = new
+ self.data[0] = sx, b, tx
+ self.data[1] = c, sy, ty
+ self.translate = tx, ty
+ self.scale = sx, sy
+
+ def _scale_changed(self, old, new):
+ #print 'scale change', new
+ sx, sy = new
+ self.data[0][0] = sx
+ self.data[1][1] = sy
+
+
+ def _data_changed(self, old, new):
+ #print 'data change', new
+
+ sx, b, tx = self.data[0]
+ c, sy, ty = self.data[1]
+
+ self.translate = tx, ty
+ self.scale = sx, sy
+ self.vec6 = sx, b, c, sy, tx, ty
+
+
+ def __mul__(self, other):
+ if isinstance(other, Affine):
+ new = Affine()
+ new.data = npy.dot(self.data, other.data)
+ return new
+ elif isinstance(other, npy.ndarray):
+ return npy.dot(self.data, other)
+ raise TypeError('Do not know how to multiply Affine by %s'%type(other))
+
+
+ def __repr__(self):
+
+ return 'AFFINE:\n%s'%self.data
+
+
class ColorHandler(traits.TraitHandler):
"""
This is a clever little traits mechanism -- users can specify the
@@ -39,8 +119,8 @@
c = C()
c.fillcolor = 'red'
- print c.fillcolor
- print c.fillcolor_ #
+ print c.fillcolor # prints red
+ print c.fillcolor_ # print (1,0,0,1)
"""
is_mapped = True
@@ -49,9 +129,9 @@
def mapped_value(self, value ):
if value is None: return None
+ if is_string_like(value): value = value.lower()
return mcolors.colorConverter.to_rgba(value)
-
-
+
def validate(self, object, name, value):
try:
self.mapped_value(value)
@@ -68,8 +148,9 @@
class MTraitsNamespace:
DPI = traits.Float(72.)
- Affine = traits.Array('d', (3,3), npy.array([[1,0,0],[0,1,0],[0,0,1]], npy.float_))
+
Alpha = traits.Range(0., 1., 0.)
+ Affine = traits.Trait(Affine())
AntiAliased = traits.true
Codes = traits.Array('b', value=npy.array([0,0], dtype=npy.uint8))
Color = traits.Trait('black', ColorHandler())
@@ -82,54 +163,39 @@
'p', '1', '2', '3', '4')
MarkerSize = traits.Float(6)
Verts = traits.Array('d', value=npy.array([[0,0],[0,0]], npy.float_))
- PathData = traits.Tuple(Codes, Verts)
+ PathData = traits.Tuple(Codes(), Verts())
Visible = traits.true
mtraits = MTraitsNamespace()
-def affine_axes(rect):
- 'make an affine for a typical l,b,w,h axes rectangle'
- l,b,w,h = rect
- return npy.array([[w, 0, l], [0, h, b], [0, 0, 1]], dtype=npy.float_)
+
+
+
-def affine_identity():
- return npy.array([[1,0,0],
- [0,1,0],
- [0,0,1]],
- dtype=npy.float_)
-def affine_translation(tx, ty):
- return npy.array([[1,0,tx],
- [0,1,ty],
- [0,0,1]],
- dtype=npy.float_)
+class Renderer(traits.HasTraits):
+ dpi = mtraits.DPI
+ size = traits.Tuple(traits.Int(600), traits.Int(400))
-def affine_rotation(theta):
- a = npy.cos(theta)
- b = -npy.sin(theta)
- c = npy.sin(theta)
- d = npy.cos(theta)
-
- return npy.array([[a,b,0],
- [c,d,0],
- [0,0,1]],
- dtype=npy.float_)
+ affinerenderer = mtraits.Affine
+ def __init__(self, size=(600,400)):
-class Renderer(traits.HasTraits):
- dpi = traits.Float(72.)
-
- def __init__(self, width, height):
- self.width, self.height = width, height
+ self.pathd = dict() # path id -> Path instance
+ self.markersd = dict() # path id -> Markers instance
+ self._size_changed(None, size)
+
+ def _size_changed(self, old, new):
+ width, height = new
+
# almost all renderers assume 0,0 is left, upper, so we'll flip y here by default
- self.affinerenderer = npy.array(
- [[width, 0, 0], [0, -height, height], [0, 0, 1]], dtype=npy.float_)
- self.pathd = dict() # dict mapping path id -> Path instance
- self.markersd = dict() # dict mapping path id -> Markers instance
+ self.affinerenderer.translate = 0, height
+ self.affinerenderer.scale = width, -height
+
def add_path(self, pathid, path):
self.pathd[pathid] = path
@@ -156,9 +222,11 @@
blue = agg.rgba8(0,0,255,255)
black = agg.rgba8(0,0,0,0)
- def __init__(self, width, height):
- Renderer.__init__(self, width, height)
-
+
+ def _size_changed(self, old, new):
+ Renderer._size_changed(self, old, new)
+
+ width, height = self.size
stride = width*4
self.buf = buf = agg.buffer(width, height, stride)
@@ -198,13 +266,11 @@
render_scanlines = agg.render_scanlines_bin_rgba
- affine = npy.dot(self.affinerenderer, path.affine)
+ affine = self.affinerenderer * path.affine
#print 'display affine:\n', self.affinerenderer
#print 'path affine:\n', path.affine
#print 'product affine:\n', affine
- a, b, tx = affine[0]
- c, d, ty = affine[1]
- aggaffine = agg.trans_affine(a,b,c,d,tx,ty)
+ aggaffine = agg.trans_affine(*affine.vec6)
transpath = agg.conv_transform_path(path.agg_path, aggaffine)
if path.fillcolor is not None:
@@ -235,13 +301,14 @@
- affineverts = npy.dot(self.affinerenderer, markers.affine)
+ affineverts = self.affinerenderer * markers.affine
Nmarkers = markers.verts.shape[0]
Locs = npy.ones((3, Nmarkers))
Locs[0] = markers.verts[:,0]
Locs[1] = markers.verts[:,1]
- Locs = npy.dot(affineverts, Locs)
+
+ Locs = affineverts * Locs
dpiscale = self.dpi/72. # for some reason this is broken
@@ -280,7 +347,8 @@
# we'll cheat a little and use pylab for display
X = npy.fromstring(self.buf.to_string(), npy.uint8)
- X.shape = self.height, self.width, 4
+ width, height = self.size
+ X.shape = height, width, 4
if 1:
import pylab
fig = pylab.figure()
@@ -353,22 +421,17 @@
alpha = mtraits.Alpha(1.0)
linewidth = mtraits.LineWidth(1.0)
antialiased = mtraits.AntiAliased
- pathdata = mtraits.PathData()
- affine = mtraits.Affine()
+ pathdata = mtraits.PathData
+ affine = mtraits.Affine
def __init__(self):
# this is a quick workaround to deal with the problem that
# traits inited at the class level are shared between
# instances, which is not what I want
- self.strokecolor = 'black'
- self.fillcolor = 'blue'
- self.affine = affine_identity()
self.pathdata = (npy.array([0,0], npy.uint8), # codes
npy.array([[0,0], [0,0]])) # verts
-mtraits.Path = traits.Trait(Path())
-
class AggPath(Path):
def __init__(self, path):
@@ -432,20 +495,17 @@
rgba = [int(255*c) for c in color]
return agg.rgba8(*rgba)
+
+mtraits.Path = traits.Instance(Path, ())
class Markers(traits.HasTraits):
- verts = mtraits.Verts() # locations to draw the markers at
- path = mtraits.Path() # marker path in points
- affine = mtraits.Affine() # transformation for the verts
+ verts = mtraits.Verts # locations to draw the markers at
+ path = mtraits.Path # marker path in points
+ affine = mtraits.Affine # transformation for the verts
x = traits.Float(1.0)
- def __init__(self):
- # this is a quick workaround to prevent sharing obs; see Path
- self.verts = npy.array([[0,0], [0,0]], npy.float_)
- self.path = Path()
- self.affine = affine_identity()
-mtraits.Markers = traits.Trait(Markers())
+mtraits.Markers = traits.Instance(Markers, ())
# coordinates:
#
# artist model : a possibly nonlinear transformation (Func instance)
@@ -491,12 +551,12 @@
zorder = traits.Float(1.0)
alpha = mtraits.Alpha()
visible = mtraits.Visible()
- affine = mtraits.Affine()
+ affine = mtraits.Affine
def __init__(self):
self.artistid = artistID()
self.renderer = None
- self.affine = affine_identity()
+
def set_renderer(self, renderer):
self.renderer = renderer
@@ -519,9 +579,9 @@
markeredgecolor = mtraits.Color('black')
markeredgewidth = mtraits.LineWidth(0.5)
markersize = mtraits.MarkerSize(6.0)
- path = mtraits.Path()
- markers = mtraits.Markers()
- X = mtraits.Verts()
+ path = mtraits.Path
+ markers = mtraits.Markers
+ X = mtraits.Verts
model = mtraits.Model
zorder = traits.Float(2.0)
@@ -536,14 +596,7 @@
# which attrs may be shared and hence have to be initialized
# and which ones don't. Eg, if you comment out the self.path
# init, the code breaks
- self.color = 'blue'
- self.markerfacecolor = 'blue'
- self.markeredgecolor = 'black'
- self.path = Path()
- self.markers = Markers()
- self.X = npy.array([[0,1], [0,1]], npy.float_)
- self.model = Identity()
- #self.model = None # switch comments with above to reveal bug
+
self.sync_trait('linewidth', self.path, 'linewidth', mutual=False)
self.sync_trait('color', self.path, 'strokecolor', mutual=False)
@@ -623,20 +676,16 @@
class Rectangle(Artist):
- facecolor = mtraits.Color('Yellow')
- edgecolor = mtraits.Color('Black')
+ facecolor = mtraits.Color('yellow')
+ edgecolor = mtraits.Color('black')
edgewidth = mtraits.LineWidth(1.0)
lbwh = traits.Array('d', (4,), [0,0,1,1])
- path = mtraits.Path()
+ path = mtraits.Path
zorder = traits.Float(1.0)
def __init__(self):
Artist.__init__(self)
- self.facecolor = 'yellow'
- self.edgecolor = 'black'
- self.edgewidth = 1.0
- self.lbwh = 0,0,1,1
- self.path = Path()
+
self.sync_trait('facecolor', self.path, 'fillcolor', mutual=False)
self.sync_trait('edgecolor', self.path, 'strokecolor', mutual=False)
@@ -694,41 +743,34 @@
class AxesCoords(traits.HasTraits):
- xviewlim = mtraits.Interval()
- yviewlim = mtraits.Interval()
- affineview = mtraits.Affine()
- affineaxes = mtraits.Affine()
- affine = mtraits.Affine()
-
- def __init__(self):
- self.xviewlim = npy.array([0., 1.])
- self.yviewlim = npy.array([0., 1.])
- self.affineview = affine_identity()
- self.affineaxes = affine_identity()
- self.affine = affine_identity()
+ xviewlim = mtraits.Interval
+ yviewlim = mtraits.Interval
+ affineview = mtraits.Affine
+ affineaxes = mtraits.Affine
+ affine = mtraits.Affine
def _affineview_changed(self, old, new):
- self.affine = npy.dot(self.affineaxes, new)
+ self.affine = self.affineaxes * new
def _affineaxes_changed(self, old, new):
- self.affine = npy.dot(new, self.affineview)
+ self.affine = new * self.affineview
def _xviewlim_changed(self, old, new):
xmin, xmax = new
scale = 1./(xmax-xmin)
tx = -xmin*scale
- self.affineview[0][0] = scale
- self.affineview[0][-1] = tx
- self.affine = npy.dot(self.affineaxes, self.affineview)
+ self.affineview.data[0][0] = scale
+ self.affineview.data[0][-1] = tx
+ self.affine = self.affineaxes * self.affineview
def _yviewlim_changed(self, old, new):
ymin, ymax = new
scale = 1./(ymax-ymin)
ty = -ymin*scale
- self.affineview[1][1] = scale
- self.affineview[1][-1] = ty
- self.affine = npy.dot(self.affineaxes, self.affineview)
+ self.affineview.data[1][1] = scale
+ self.affineview.data[1][-1] = ty
+ self.affine = self.affineaxes * self.affineview
x1 = npy.arange(0, 10., 0.05)
@@ -737,9 +779,10 @@
y2 = 10*npy.exp(-x1)
# the axes rectangle
-axrect1 = [0.1, 0.1, 0.4, 0.4]
coords1 = AxesCoords()
-coords1.affineaxes = affine_axes(axrect1)
+coords1.affineaxes = Affine()
+coords1.affineaxes.scale = 0.4, 0.4
+coords1.affineaxes.translate = 0.1, 0.1
fig = Figure()
@@ -767,11 +810,12 @@
if 1:
- axrect2 = [0.55, 0.55, 0.4, 0.4]
coords2 = AxesCoords()
- coords2.affineaxes = affine_axes(axrect2)
+ coords2.affineaxes = Affine()
+ coords2.affineaxes.scale = 0.4, 0.4
+ coords2.affineaxes.translate = 0.55, 0.55
+
-
r = npy.arange(0.0, 1.0, 0.01)
theta = r*4*npy.pi
@@ -796,7 +840,7 @@
if 1:
- renderer = RendererAgg(600,400)
+ renderer = RendererAgg()
fig.set_renderer(renderer)
fig.draw()
renderer.show()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|