It might be nice if we could add a "what if" type function to all the
backends for text sizing. That way we could write much better auto-layout
algorithms for the axis and legend code. A very simple improvement to the
auto-date labeller would then work like this:
- Find min, max values of the time axis
- Convert to the label format using the date formatter
- Ask the backend what the size of the min/max labels would be
- Take the max of that as a reasonable guess for the date label size
- Divide up the total interval (in pixels) by the label size + pad
- Pick nice ticks based on the total time interval and number of ticks you
This would be really handy for us in that we do a lot of date plotting with
widely varying time ranges and it would be nice to have an "auto-ticker"
that insures the labels don't overlap for a plot of a given size and range.
On a scale of 1-10, how difficult do you think this type of thing would be
At 07:52 PM 3/17/2006, John Hunter wrote:
> >>>>> "Michael" == Michael P Mossey <mossey@...> writes:
> Michael> Is there a way to find out in terms of pixels, points, or
> Michael> fraction of figure, how large a text string is? In other
> Michael> words if I generate "Some label" as the x-axis label, can
> Michael> I find out how tall and wide it is?
>It can be done -- it's not terribly elegant. The basic problem is
>that text size depends on the backend and configuration settings.
>Since we have arbitrary fonts, multiline text with arbitrary rotation,
>possible using TeX to render, with multiple output targets that might
>handle text differently, you can see how this seemingly simple
>question is actually tough.
>All text instances have a method called "get_window_extent" which
>returns a matplotlib.transforms.BBox that bounds the text instance.
>This bbox is in pixel coords.
> bbox = sometext.get_window_extent()
> l,b,w,h = bbox.get_bounds() # left, bottom, width, height
>Here's the rub: we can't realistically know the text size until we
>have a renderer, because the same text may be different in different
>renderers. matplotlib sets the figure renderer at draw time, and so
>by default we don't know the text size until draw size. This is
>One solution is to do something like
> sometext = text(x,y,s)
> draw() # force a draw to set the renderer
> l,b,w,h = sometext.get_window_extent().get_bounds()
> ..adjust the postion of some objects based on this info
> ...draw again
>not terribly elegant because we must redraw. If you did not first
>force the draw command before calling get_window_extent, you would get
>an exception about needing to first set the renderer.
>Another approach if you want to avoid the duplicate drawing is to
>create a proxy renderer and pass that off to the text instance.
> l,b,w,h = fig.bbox.get_bounds()
> renderer = RendererAgg(w, h, fig.dpi)
> l,b,w,h = sometext.get_window_extent(renderer).get_bounds()
>This is not ideal either because you have a duplicate renderer.
>If you are using a *Agg backend and have a figure instance, you can do
>the following, which requires no duplicate drawing and no duplicate
>renderer (the get_renderer method below caches the return value so
>repeated calls do not create duplicate renderers)
>Here is a complete example
> from matplotlib.patches import Rectangle
> from matplotlib.transforms import identity_transform
> from pylab import figure, show
> fig = figure()
> ax = fig.add_subplot(111)
> ax.plot([1,2,3,4], [1,2,3,4])
> t = ax.text(2,2,'Look Ma!\nNo hands', fontsize=40, rotation=-45)
> renderer = fig.canvas.get_renderer()
> bbox = t.get_window_extent(renderer)
> l,b,w,h = bbox.get_bounds()
> # no transformation necessary, already in pixel coords
> r = Rectangle((l,b),w,h, transform=identity_transform())
>If we made the get_renderer method standard across backends, we could
>probably hide the get_renderer call from the user and make this a
>little more friendly, but the above should suffice. Note if you have
>some data in pixel coords, you can transform it into another
>coordinate system (eg data coords) using the inverse transform
>methods. The l,b,w,h bounding box of the text bbox in "data"
>coordinates (ie, the coords of the [1,2,3,4] plot) can be obtained
> from matplotlib.transforms import inverse_transform_bbox
> databbox = inverse_transform_bbox(ax.transData, bbox)
> print databbox.get_bounds()
>In general, different coordinate systems in matplotlib communicate
>with one another by transforming a data point to pixel space and then
>inverse transforming that point into a different own space. There are
>two inverse methods to help with these tasks. The first is a
>transform method to handle single points:
> # apply the inverse transformation to tuple xy
> xi, yi = trans.inverse_xy_tup(xy)
>and the second is a stand-alone function defined in
>matplotlib.transforms to handle bboxes
> # apply the inverse transformation of a bbox
> bboxi = inverse_transform_bbox(trans, bbox)
>You might be thinking: why aren't we simply using an affine
>transformation matrix with a standard matrix inverse? All I can offer
>in response is that this architecture supports nonlinear
>transformations, ie, an affine plus a nonlinear transformation, for
>polar, log, etc.... There is probably a better way, but this is what
>This SF.Net email is sponsored by xPML, a groundbreaking scripting language
>that extends applications into web and mobile media. Attend the live webcast
>and join the prime developer group breaking into this new coding territory!
>Matplotlib-users mailing list