possible regression in plt.draw (mpl 1.4.3 -> 1.5.0) when using fig.text using transformations

Dear matplotlib users and developers,

I?m using plt.draw() to force the rendering of all artists and then, based
on their newly calculated positions, place a text label on the figure
window in figure coordinates.

The goal is to add a text label near the conventional y-axis, at the top,
right-aligned. Example code that demonstrates the problem:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
print(mpl.__version__)

x = np.linspace(0, 50)
y = 4*np.sin(x) + 5

fig = plt.figure(figsize=(18,9.8))
ax = fig.add_axes((0.1, 0.1, 0.8, 0.8),
    frameon=True,
    aspect='equal',
    adjustable='box',
    xlim=(x.min(), x.max()),
    ylim=(0, 10),
    xticks=[x.min(), x.max()],
    yticks=[0, 10],
    xlabel='dimension (unit)')
ax.plot(x, y)
plt.draw() # force redraw

ylabel_pos = fig.transFigure.inverted().transform_point(ax.transAxes.transform_point((0,1)))
label1 = fig.text(ylabel_pos[0], ylabel_pos[1], "label1", ha="right",
va="bottom")
plt.savefig('/tmp/test_pre_mpl_v_{}.png'.format(mpl.__version__))
ylabel_pos = fig.transFigure.inverted().transform_point(ax.transAxes.transform_point((0,1)))
label2 = fig.text(ylabel_pos[0], ylabel_pos[1], "label2", ha="right",
va="bottom")
plt.savefig('/tmp/test_post_mpl_v_{}.png'.format(mpl.__version__))

The code shows that in mpl 1.4.3 both label1 and label2 end up at the same
(desired) position. However, mpl 1.5.0 and 1.5.1 (just installed to check)
show that label1 is at a height of 0.9 in the figure coordinates. After the
first call to savefig, the figure is rendered with the axes getting a new
height and width (due to the call to aspect='equal', adjustable='box') and
so the subsequent call to savefig renders label2 in the correct position.

Using ax.text(x=0, y=1, s='label', transform=ax.transAxes, ha="right",
va="bottom") gets the job done alright (both in 1.4.3, as well as 1.5.0),
but the call to fig.text using the subsequent transforms should have
worked, I believe, and so this seems to indicate something has changed in
the rendering of a rescaled axes using plt.draw.

Kind regards,
Oliver

P.S. I posted this to the older sourceforge mailing list as well, by
accident. Hadn?t changed the send-to address yet, even though Thomas
Caswell had pointed it out to me already in August 2015. My apologies.
?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-users/attachments/20160303/33fbf3eb/attachment.html>

If you want to _really_ force a redraw, you will need to to
`fig.canvas.draw()`.

In FIX: set internal flags first in FigureCanvasBase by tacaswell · Pull Request #5150 · matplotlib/matplotlib · GitHub we change
`plt.draw()` to use the `canvas.draw_idle` method. The draw_idle
effectively asks the GUI framework to please re-draw the next time it is
convenient. The main benefit of this is if you request multiple draw_idle
before giving the GUI a chance to re-paint there will only be one actual
call to `canvas.draw`.

Using `ax.text` is much simpler you should just use that :wink:

In general going through screen coordinates in user code is not advised.
In this particular case you are going right back to 'normalized' unit so
you will not have DPI / figure size issues, but in general those will come
up because the Transforms close over many values that can (and do!) change
from user input or as part of the draw process. In almost all cases, it is
better to pass a transfrom into the artists and let mpl internals sort of
exactly when to evaluate them.

Tom

···

On Thu, Mar 3, 2016 at 6:31 AM Oliver Willekens <oliver.willekens at gmail.com> wrote:

Dear matplotlib users and developers,

I?m using plt.draw() to force the rendering of all artists and then,
based on their newly calculated positions, place a text label on the figure
window in figure coordinates.

The goal is to add a text label near the conventional y-axis, at the top,
right-aligned. Example code that demonstrates the problem:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
print(mpl.__version__)

x = np.linspace(0, 50)
y = 4*np.sin(x) + 5

fig = plt.figure(figsize=(18,9.8))
ax = fig.add_axes((0.1, 0.1, 0.8, 0.8),
    frameon=True,
    aspect='equal',
    adjustable='box',
    xlim=(x.min(), x.max()),
    ylim=(0, 10),
    xticks=[x.min(), x.max()],
    yticks=[0, 10],
    xlabel='dimension (unit)')
ax.plot(x, y)
plt.draw() # force redraw

ylabel_pos = fig.transFigure.inverted().transform_point(ax.transAxes.transform_point((0,1)))
label1 = fig.text(ylabel_pos[0], ylabel_pos[1], "label1", ha="right", va="bottom")
plt.savefig('/tmp/test_pre_mpl_v_{}.png'.format(mpl.__version__))
ylabel_pos = fig.transFigure.inverted().transform_point(ax.transAxes.transform_point((0,1)))
label2 = fig.text(ylabel_pos[0], ylabel_pos[1], "label2", ha="right", va="bottom")
plt.savefig('/tmp/test_post_mpl_v_{}.png'.format(mpl.__version__))

The code shows that in mpl 1.4.3 both label1 and label2 end up at the same
(desired) position. However, mpl 1.5.0 and 1.5.1 (just installed to check)
show that label1 is at a height of 0.9 in the figure coordinates. After the
first call to savefig, the figure is rendered with the axes getting a new
height and width (due to the call to aspect='equal', adjustable='box')
and so the subsequent call to savefig renders label2 in the correct
position.

Using ax.text(x=0, y=1, s='label', transform=ax.transAxes, ha="right",
va="bottom") gets the job done alright (both in 1.4.3, as well as 1.5.0),
but the call to fig.text using the subsequent transforms should have
worked, I believe, and so this seems to indicate something has changed in
the rendering of a rescaled axes using plt.draw.

Kind regards,
Oliver

P.S. I posted this to the older sourceforge mailing list as well, by
accident. Hadn?t changed the send-to address yet, even though Thomas
Caswell had pointed it out to me already in August 2015. My apologies.
?
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users at python.org
Matplotlib-users Info Page

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-users/attachments/20160303/9693af0a/attachment-0001.html&gt;

[...] in general [DPI/figure issues] will come up because the Transforms

close over many values that can (and do!) change from user input or as part
of the draw process.

Thought I had the transformations all figured out, but it looks like I
should definitely play some more with it.

Thank you, Tom, for referring me to the git pull request and explaining the
cause of this.

Oliver

2016-03-03 14:54 GMT+01:00 Thomas Caswell <tcaswell at gmail.com>:

If you want to _really_ force a redraw, you will need to to
`fig.canvas.draw()`.

In FIX: set internal flags first in FigureCanvasBase by tacaswell · Pull Request #5150 · matplotlib/matplotlib · GitHub we change
`plt.draw()` to use the `canvas.draw_idle` method. The draw_idle
effectively asks the GUI framework to please re-draw the next time it is
convenient. The main benefit of this is if you request multiple draw_idle
before giving the GUI a chance to re-paint there will only be one actual
call to `canvas.draw`.

Using `ax.text` is much simpler you should just use that :wink:

In general going through screen coordinates in user code is not advised.
In this particular case you are going right back to 'normalized' unit so
you will not have DPI / figure size issues, but in general those will come
up because the Transforms close over many values that can (and do!) change
from user input or as part of the draw process. In almost all cases, it is
better to pass a transfrom into the artists and let mpl internals sort of
exactly when to evaluate them.

Tom

Dear matplotlib users and developers,

I?m using plt.draw() to force the rendering of all artists and then,
based on their newly calculated positions, place a text label on the figure
window in figure coordinates.

The goal is to add a text label near the conventional y-axis, at the top,
right-aligned. Example code that demonstrates the problem:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
print(mpl.__version__)

x = np.linspace(0, 50)
y = 4*np.sin(x) + 5

fig = plt.figure(figsize=(18,9.8))
ax = fig.add_axes((0.1, 0.1, 0.8, 0.8),
    frameon=True,
    aspect='equal',
    adjustable='box',
    xlim=(x.min(), x.max()),
    ylim=(0, 10),
    xticks=[x.min(), x.max()],
    yticks=[0, 10],
    xlabel='dimension (unit)')
ax.plot(x, y)
plt.draw() # force redraw

ylabel_pos = fig.transFigure.inverted().transform_point(ax.transAxes.transform_point((0,1)))
label1 = fig.text(ylabel_pos[0], ylabel_pos[1], "label1", ha="right", va="bottom")
plt.savefig('/tmp/test_pre_mpl_v_{}.png'.format(mpl.__version__))
ylabel_pos = fig.transFigure.inverted().transform_point(ax.transAxes.transform_point((0,1)))
label2 = fig.text(ylabel_pos[0], ylabel_pos[1], "label2", ha="right", va="bottom")
plt.savefig('/tmp/test_post_mpl_v_{}.png'.format(mpl.__version__))

The code shows that in mpl 1.4.3 both label1 and label2 end up at the
same (desired) position. However, mpl 1.5.0 and 1.5.1 (just installed to
check) show that label1 is at a height of 0.9 in the figure coordinates.
After the first call to savefig, the figure is rendered with the axes
getting a new height and width (due to the call to aspect='equal',
adjustable='box') and so the subsequent call to savefig renders label2
in the correct position.

Using ax.text(x=0, y=1, s='label', transform=ax.transAxes, ha="right",
va="bottom") gets the job done alright (both in 1.4.3, as well as
1.5.0), but the call to fig.text using the subsequent transforms should
have worked, I believe, and so this seems to indicate something has changed
in the rendering of a rescaled axes using plt.draw.

Kind regards,
Oliver

P.S. I posted this to the older sourceforge mailing list as well, by
accident. Hadn?t changed the send-to address yet, even though Thomas
Caswell had pointed it out to me already in August 2015. My apologies.
?
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users at python.org
Matplotlib-users Info Page

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-users/attachments/20160304/0acde536/attachment-0001.html&gt;

···

On Thu, Mar 3, 2016 at 6:31 AM Oliver Willekens < > oliver.willekens at gmail.com> wrote:

Sorry this caused grief for you. There is definitely a tension when
working on a library as big, old, and widely used as mpl, we want to make
things better, but we are almost always going to break something for
someone (even bug fixes break people who have work-arounds to the
bug-fixes!).

Tom

···

On Fri, Mar 4, 2016 at 8:39 AM Oliver Willekens <oliver.willekens at gmail.com> wrote:

>[...] in general [DPI/figure issues] will come up because the Transforms
close over many values that can (and do!) change from user input or as part
of the draw process.

Thought I had the transformations all figured out, but it looks like I
should definitely play some more with it.

Thank you, Tom, for referring me to the git pull request and explaining
the cause of this.

Oliver

2016-03-03 14:54 GMT+01:00 Thomas Caswell <tcaswell at gmail.com>:

If you want to _really_ force a redraw, you will need to to
`fig.canvas.draw()`.

In FIX: set internal flags first in FigureCanvasBase by tacaswell · Pull Request #5150 · matplotlib/matplotlib · GitHub we change
`plt.draw()` to use the `canvas.draw_idle` method. The draw_idle
effectively asks the GUI framework to please re-draw the next time it is
convenient. The main benefit of this is if you request multiple draw_idle
before giving the GUI a chance to re-paint there will only be one actual
call to `canvas.draw`.

Using `ax.text` is much simpler you should just use that :wink:

In general going through screen coordinates in user code is not advised.
In this particular case you are going right back to 'normalized' unit so
you will not have DPI / figure size issues, but in general those will come
up because the Transforms close over many values that can (and do!) change
from user input or as part of the draw process. In almost all cases, it is
better to pass a transfrom into the artists and let mpl internals sort of
exactly when to evaluate them.

Tom

On Thu, Mar 3, 2016 at 6:31 AM Oliver Willekens < >> oliver.willekens at gmail.com> wrote:

Dear matplotlib users and developers,

I?m using plt.draw() to force the rendering of all artists and then,
based on their newly calculated positions, place a text label on the figure
window in figure coordinates.

The goal is to add a text label near the conventional y-axis, at the
top, right-aligned. Example code that demonstrates the problem:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
print(mpl.__version__)

x = np.linspace(0, 50)
y = 4*np.sin(x) + 5

fig = plt.figure(figsize=(18,9.8))
ax = fig.add_axes((0.1, 0.1, 0.8, 0.8),
    frameon=True,
    aspect='equal',
    adjustable='box',
    xlim=(x.min(), x.max()),
    ylim=(0, 10),
    xticks=[x.min(), x.max()],
    yticks=[0, 10],
    xlabel='dimension (unit)')
ax.plot(x, y)
plt.draw() # force redraw

ylabel_pos = fig.transFigure.inverted().transform_point(ax.transAxes.transform_point((0,1)))
label1 = fig.text(ylabel_pos[0], ylabel_pos[1], "label1", ha="right", va="bottom")
plt.savefig('/tmp/test_pre_mpl_v_{}.png'.format(mpl.__version__))
ylabel_pos = fig.transFigure.inverted().transform_point(ax.transAxes.transform_point((0,1)))
label2 = fig.text(ylabel_pos[0], ylabel_pos[1], "label2", ha="right", va="bottom")
plt.savefig('/tmp/test_post_mpl_v_{}.png'.format(mpl.__version__))

The code shows that in mpl 1.4.3 both label1 and label2 end up at the
same (desired) position. However, mpl 1.5.0 and 1.5.1 (just installed to
check) show that label1 is at a height of 0.9 in the figure coordinates.
After the first call to savefig, the figure is rendered with the axes
getting a new height and width (due to the call to aspect='equal',
adjustable='box') and so the subsequent call to savefig renders label2
in the correct position.

Using ax.text(x=0, y=1, s='label', transform=ax.transAxes, ha="right",
va="bottom") gets the job done alright (both in 1.4.3, as well as
1.5.0), but the call to fig.text using the subsequent transforms should
have worked, I believe, and so this seems to indicate something has changed
in the rendering of a rescaled axes using plt.draw.

Kind regards,
Oliver

P.S. I posted this to the older sourceforge mailing list as well, by
accident. Hadn?t changed the send-to address yet, even though Thomas
Caswell had pointed it out to me already in August 2015. My apologies.
?
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users at python.org
Matplotlib-users Info Page

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-users/attachments/20160304/8f53d6c0/attachment.html&gt;