x-axis Tick Label Alignment w/Cairo

Hello,

Using the Cairo backend with the following snippet:

from matplotlib.figure import Figure
from matplotlib.artist import setp
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.backends.backend_cairo import FigureCanvasCairo
import numpy as np

fig = Figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
x = np.arange(0, 10, 0.2)

ax1.plot(x, np.sin(x))
ax1.xaxis.set_ticklabels(['Apr', 'Jul', 'Oct', '\'12', 'Apr', 'Jul'])
setp(ax1.yaxis.get_ticklabels(), visible=False)

FigureCanvasCairo(fig).print_figure('test.png')

Results in the x-axis tick labels being significantly displaced.
Specifically, "Oct" and "'12" are positioned far closer to the axis than
either "Apr" or "Jul".

With the AGG backend all of the labels are roughly aligned to the
baseline of the font -- give or take a pixel.

I have observed this on a Gentoo and Debian system, both running 1.1.1,
albeit with different default fonts.

Although I am not completely sure it appears as if a label contains a
glyph that extends below the baseline, e.g. 'p' or 'J', that the label
is forced away from the axis.

Can anyone suggest a workaround for this (or explain where I am going
wrong)?

Regards, Freddie.

I have been looking into this issue today and it /appears/ to be because
the Cairo backend -- or more precisely -- cairo::show_text method takes
a y-coordinate relative to the baseline. This makes sense given that
the descent of a font is a well-defined concept in Cairo (and can be
extracted via the font_extents method). However, as far as I can tell,
matplotlib expects the draw_text method to provide an absolute y-coordinate.

The result is that any text with a descender is pushed downwards
(assuming a default rotation angle). This can be visualized by

setp(ax1.xaxis.get_ticklabels(), backgroundcolor='r')

The solution is to have the draw_text method account for any descenders.
After a bit of juggling the relevant portion of draw_text becomes:

           ctx = gc.ctx
           ctx.new_path()
           #ctx.move_to (x, y)
           ctx.select_font_face (prop.get_name(),
                                 self.fontangles [prop.get_style()],
                                 self.fontweights[prop.get_weight()])

           size = prop.get_size_in_points() * self.dpi / 72.0
           ctx.set_font_size(size)

           y_bearing, w, h = ctx.text_extents(s.encode("utf-8"))[1:4]
           ctx.move_to(x, y - (h + y_bearing))

           ctx.save()
           if angle:
              ctx.rotate (-angle * np.pi / 180)
           #ctx.set_font_size (size)
           ctx.show_text (s.encode("utf-8"))
           ctx.restore()

where commented lines highlight modifications (specifically, the
movement of the set_font_size and move_to calls). In my limited testing
this fixes the aforementioned issues. Discrepancies between baselines
are now limited to +/- 1px (the same as with the AGG backend).
Eliminating these one pixel misalignments is rather difficult so long as
text rendering is performed relative to the bounding box as opposed to a
baseline.

Regards, Freddie.

···

On 19/08/12 23:48, Freddie Witherden wrote:

Hello,

Using the Cairo backend with the following snippet:

from matplotlib.figure import Figure
from matplotlib.artist import setp
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.backends.backend_cairo import FigureCanvasCairo
import numpy as np

fig = Figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
x = np.arange(0, 10, 0.2)

ax1.plot(x, np.sin(x))
ax1.xaxis.set_ticklabels(['Apr', 'Jul', 'Oct', '\'12', 'Apr', 'Jul'])
setp(ax1.yaxis.get_ticklabels(), visible=False)

FigureCanvasCairo(fig).print_figure('test.png')

Results in the x-axis tick labels being significantly displaced.
Specifically, "Oct" and "'12" are positioned far closer to the axis than
either "Apr" or "Jul".

With the AGG backend all of the labels are roughly aligned to the
baseline of the font -- give or take a pixel.

I have observed this on a Gentoo and Debian system, both running 1.1.1,
albeit with different default fonts.

Although I am not completely sure it appears as if a label contains a
glyph that extends below the baseline, e.g. 'p' or 'J', that the label
is forced away from the axis.

Can anyone suggest a workaround for this (or explain where I am going
wrong)?

Thanks for this. It's been a long-standing bug that text is handled by bounding box, but it's been difficult to find a way forward without breaking backward compatibility.

I've filed an issue for this here.

It may be, in the long run, that we want to fix the other backends to use baselines rather than fixing Cairo to use bounding boxes.

Mike

···

On 08/20/2012 01:07 PM, Freddie Witherden wrote:

On 19/08/12 23:48, Freddie Witherden wrote:

Hello,

Using the Cairo backend with the following snippet:

from matplotlib.figure import Figure
from matplotlib.artist import setp
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.backends.backend_cairo import FigureCanvasCairo
import numpy as np

fig = Figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
x = np.arange(0, 10, 0.2)

ax1.plot(x, np.sin(x))
ax1.xaxis.set_ticklabels(['Apr', 'Jul', 'Oct', '\'12', 'Apr', 'Jul'])
setp(ax1.yaxis.get_ticklabels(), visible=False)

FigureCanvasCairo(fig).print_figure('test.png')

Results in the x-axis tick labels being significantly displaced.
Specifically, "Oct" and "'12" are positioned far closer to the axis than
either "Apr" or "Jul".

With the AGG backend all of the labels are roughly aligned to the
baseline of the font -- give or take a pixel.

I have observed this on a Gentoo and Debian system, both running 1.1.1,
albeit with different default fonts.

Although I am not completely sure it appears as if a label contains a
glyph that extends below the baseline, e.g. 'p' or 'J', that the label
is forced away from the axis.

Can anyone suggest a workaround for this (or explain where I am going
wrong)?

I have been looking into this issue today and it /appears/ to be because
the Cairo backend -- or more precisely -- cairo::show_text method takes
a y-coordinate relative to the baseline. This makes sense given that
the descent of a font is a well-defined concept in Cairo (and can be
extracted via the font_extents method). However, as far as I can tell,
matplotlib expects the draw_text method to provide an absolute y-coordinate.

The result is that any text with a descender is pushed downwards
(assuming a default rotation angle). This can be visualized by

setp(ax1.xaxis.get_ticklabels(), backgroundcolor='r')

The solution is to have the draw_text method account for any descenders.
  After a bit of juggling the relevant portion of draw_text becomes:

            ctx = gc.ctx
            ctx.new_path()
            #ctx.move_to (x, y)
            ctx.select_font_face (prop.get_name(),
                                  self.fontangles [prop.get_style()],
                                  self.fontweights[prop.get_weight()])

            size = prop.get_size_in_points() * self.dpi / 72.0
            ctx.set_font_size(size)

            y_bearing, w, h = ctx.text_extents(s.encode("utf-8"))[1:4]
            ctx.move_to(x, y - (h + y_bearing))

            ctx.save()
            if angle:
               ctx.rotate (-angle * np.pi / 180)
            #ctx.set_font_size (size)
            ctx.show_text (s.encode("utf-8"))
            ctx.restore()

where commented lines highlight modifications (specifically, the
movement of the set_font_size and move_to calls). In my limited testing
this fixes the aforementioned issues. Discrepancies between baselines
are now limited to +/- 1px (the same as with the AGG backend).
Eliminating these one pixel misalignments is rather difficult so long as
text rendering is performed relative to the bounding box as opposed to a
baseline.

Regards, Freddie.

------------------------------------------------------------------------------
Live Security Virtual Conference
Exclusive live event will cover all the ways today's security and
threat landscape has changed and how IT managers can respond. Discussions
will include endpoint security, mobile security and the latest in malware
threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/

_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
matplotlib-users List Signup and Options