Help with transforms of collections

I'd like to plot a collection and scale the size of the collection elements in relation to the data. My guess is that I need to use the data transformation (ax.transData) since I would like the size of the collection elements altered when zooming in/out.

Unfortunately, my attempt has led to weird results: the collection offsets are shifted from the desired coordinates when using ax.transData. Weirder still: the collection elements move *relative to the data coordinates* when panning the figure.

I suspect that setting the collection transform to ax.transData is somehow applying some part of the transform twice. Does anyone know what I'm doing wrong here and how I can fix this?

Thanks!
-Tony

Attached is a toy example of what I'm trying to do. The radii of the circles are plotted correctly, but notice the x, y coordinates don't match the circle centers in the plot. Also, try panning the plot and watch as the circles move relative to the tick marks.

···

import matplotlib.pyplot as plt

>>> import matplotlib.transforms as transforms
>>> import numpy as np
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> x = [0.25, 0.75, 0.25, 0.75]
>>> y = [0.25, 0.25, 0.75, 0.75]
>>> r = 0.1 * np.ones(4)
>>> col = plt.scatter(x, y, np.pi*r**2)
>>> pts2pixels = transforms.Affine2D().scale(72.0/fig.dpi)
>>> col.set_transform(pts2pixels + ax.transData)
>>> plt.axis('equal')
>>> plt.show()

Tony,

My understanding is that (which might be wrong) drawing collections
involves (at least) 2 transforms. The first transform is (mostly) for
scaling, and the second transform is for offset. And this seems to be
true for PolygonCollection (which scatter creates) as far as I can
see.

set_transform() method sets the transform for scaling, which means
that (0,0) should transform to (0,0). Otherwise the polygon is not
drawn where you intended.
And, obviously transData does NOT, which I think is the root of the problem.

Unfortunately, the current PolygonCollection class does not seem to
have any support for what you want. On the other hand, it seems that
EllipseCollection lets you specify the ellipse size in data
coordinate.

The easiest solution I can think of is doing some monkey patching.

import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
x = [0.25, 0.75, 0.25, 0.75]
y = [0.25, 0.25, 0.75, 0.75]
r = 0.1 * np.ones(4)
col = plt.scatter(x, y, np.pi*r**2)

from matplotlib.collections import RegularPolyCollection
class RegularPolyCollection2(RegularPolyCollection):
    def get_transform(self):
        ax = self.axes

        sc_x = ax.bbox.width / ax.viewLim.width
        sc_y = ax.bbox.height / ax.viewLim.height

        return transforms.Affine2D().scale(sc_x, sc_y)

col.__class__ = RegularPolyCollection2

plt.axis('equal')
plt.show()

Alternatively, you may modify your code to use EllipseCollection (if
what you want are just cricles).
I hope this solution fits your need.
Regards,

-JJ

···

On Thu, Jun 18, 2009 at 7:24 PM, Tony S Yu<tonyyu@...1166...> wrote:

I'd like to plot a collection and scale the size of the collection
elements in relation to the data. My guess is that I need to use the
data transformation (ax.transData) since I would like the size of the
collection elements altered when zooming in/out.

Unfortunately, my attempt has led to weird results: the collection
offsets are shifted from the desired coordinates when using
ax.transData. Weirder still: the collection elements move *relative to
the data coordinates* when panning the figure.

I suspect that setting the collection transform to ax.transData is
somehow applying some part of the transform twice. Does anyone know
what I'm doing wrong here and how I can fix this?

Thanks!
-Tony

Attached is a toy example of what I'm trying to do. The radii of the
circles are plotted correctly, but notice the x, y coordinates don't
match the circle centers in the plot. Also, try panning the plot and
watch as the circles move relative to the tick marks.

>>> import matplotlib.pyplot as plt
>>> import matplotlib.transforms as transforms
>>> import numpy as np
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> x = [0.25, 0.75, 0.25, 0.75]
>>> y = [0.25, 0.25, 0.75, 0.75]
>>> r = 0.1 * np.ones(4)
>>> col = plt.scatter(x, y, np.pi*r**2)
>>> pts2pixels = transforms.Affine2D().scale(72.0/fig.dpi)
>>> col.set_transform(pts2pixels + ax.transData)
>>> plt.axis('equal')
>>> plt.show()

------------------------------------------------------------------------------
Crystal Reports - New Free Runtime and 30 Day Trial
Check out the new simplified licensing option that enables unlimited
royalty-free distribution of the report engine for externally facing
server and web deployment.
http://p.sf.net/sfu/businessobjects
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users

Thanks Jae-Joon! This is exactly what I was looking for.

-Tony

···

On Jun 22, 2009, at 2:57 PM, Jae-Joon Lee wrote:

The easiest solution I can think of is doing some monkey patching.

import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
x = [0.25, 0.75, 0.25, 0.75]
y = [0.25, 0.25, 0.75, 0.75]
r = 0.1 * np.ones(4)
col = plt.scatter(x, y, np.pi*r**2)

from matplotlib.collections import RegularPolyCollection
class RegularPolyCollection2(RegularPolyCollection):
   def get_transform(self):
       ax = self.axes

       sc_x = ax.bbox.width / ax.viewLim.width
       sc_y = ax.bbox.height / ax.viewLim.height

       return transforms.Affine2D().scale(sc_x, sc_y)

col.__class__ = RegularPolyCollection2

plt.axis('equal')
plt.show()