Discussion:
mapper scaling on 2D plots
Zafer Leylek
2013-05-06 03:31:50 UTC
Permalink
I am trying to construct a 2D plot of several lines that has the same
screen scaling for both the x and y axis. I want to view the plot to scale
and not stretched in one axis (relative to the other).

What is the best/easiest way to do this????

Zack
Brennan Sellner
2013-05-06 04:47:37 UTC
Permalink
Post by Zafer Leylek
I am trying to construct a 2D plot of several lines that has the same
screen scaling for both the x and y axis. I want to view the plot to
scale and not stretched in one axis (relative to the other).
What is the best/easiest way to do this????
Zack
I don't know if this is the best or easiest way, but here's what I've
done. If there's a cleaner approach, I'm all ears! I'd be happy to
know that I've reinvented the wheel. :-)

In short, you need to:
1. Tell Chaco not to change the data:pixel ratio when resizing a plot.
2. Set up the axes to have the same initial data:pixel ratio.
3. Ensure that your zoom tools maintain the aspect ratio.

In the code below, self.plot is built using chaco.api.Plot().

First, tell Chaco to not stretch the data:pixel ratios as you resize the
plot:

# Okay, this one's obvious.
self.plot.x_mapper.stretch_data = False
self.plot.y_mapper.stretch_data = False

# But this one makes *no* sense! The aspect_ratio.py example
# indicates that we should iterate through all plots and set
# stretch_data on all of them, but that results in a plot
# where the pixel/data ratio shrinks as the window grows, and
# grows as the window shrinks, with the change in ratio a
# function of the number of plots. Through
# far too much experimentation, it appears that you just need
# to set stretch_data on the *last* of the plots. Who the
# heck knows why...
plotList = self.plot.plots.values()
if len ( plotList ) > 0:
plotList[len(plotList)-1][0].index_mapper.stretch_data = False
plotList[len(plotList)-1][0].value_mapper.stretch_data = False


Now that Chaco won't mess with the data:pixel ratios, force the ratios
of the two axes to be the same by calling equalizeDataToScreenRatios on
[ self.plot.x_axis.mapper, self.plot.y_axis.mapper ]. Note that this
has to happen after the plot has been drawn, or the screen bounds will
be invalid. I'm using a Qt/Twisted main loop, so I just use
reactor.callLater on a lambda-binding of equalizeDataToScreenRatios.

def dataToScreenRatio ( self, mapper ):
"""
@param mapper A Chaco mapper.
@brief Compute the current ratio of displayed data to screen
space, and return it. Returns None if either the data
range or screen range is zero.
"""
deltaData = mapper.range._high_value - mapper.range._low_value
deltaScreen = mapper.high_pos - mapper.low_pos
if deltaData > 0 and deltaScreen > 0:
return deltaData / deltaScreen
return None

def equalizeDataToScreenRatios ( self, mapperList ):
"""
@param mapperList A list of Chaco mappers.
@brief Compute the ratio between unit data and screen pixels
and normalize all mappers in the list to have the
highest ratio present. This has the effect of making a
pixel 'worth' the same amount in either direction
(assuming square pixels).
"""
ratios = [ self.dataToScreenRatio ( mapper )
for mapper in mapperList ]
maxRatio = max ( ratios )
for ratio, mapper in zip ( ratios, mapperList ):
# If the display extents aren't valid, or if the
# data:pixel ratio is already right, there's no point.
if mapper.high_pos <= mapper.low_pos or ratio == maxRatio:
continue
# Otherwise, scale out the axes around the data to match
# the data:pixel ratio elsewhere.
else:
dataExtent = maxRatio * ( mapper.high_pos
- mapper.low_pos )
rangeMid = ( mapper.range.high + mapper.range.low ) / 2
mapper.range.set_bounds ( rangeMid - dataExtent / 2,
rangeMid + dataExtent / 2 )


At this point, you've set up a plot whose initial X/Y axes are scaled
equally, and which won't be deformed by window resizes. Zooming with
DragZoom works well, as long as you set its maintain_aspect_ratio trait
to true. Using the mouse wheel with ZoomTool/BetterSelectingZoom also
maintains the aspect ratio.

In Chaco 4.3.0, it's not possible to do a constant-aspect-ratio box zoom
with ZoomTool. Peter submitted a pull request to fix this back in
February; I believe it's included in the latest release
(https://github.com/enthought/chaco/pull/90). I'm currently using 4.3.0
with a class deriving from ZoomTool that incorporates the PR, so I can't
speak with certainty about the latest version.

Good luck! It took quite a bit of mucking about to end up with the
above; as I said at the beginning, I'd be happy to hear there's an
easier way. :-)

Cheers,

-Brennan

My apologies for the mail-gateway-added spam below:

Email Confidentiality Notice

The information contained in this transmission is confidential, proprietary or privileged and may be subject to protection under the law. This message is intended for the sole use of the individual or entity to whom it's addressed. If you are not the intended recipient, you are notified that any use, distribution or copying of the message is strictly prohibited and may subject you to criminal or civil penalties. If you received this transmission in error, please contact the sender immediately by replying to this email and delete the material from any computer.
Loading...