Skip to content

[Bug]: LaTeX X-Tick Labels are Vertically Misaligned When DPI is Set #31802

@mikewacker

Description

@mikewacker

Bug summary

x-tick labels using LaTeX are vertically misaligned when you call savefig() with dpi=300. The misalignment occurs when two labels have a superscript and subscript, while the third misaligned label only has a superscript.

Code for reproduction

import sys

import matplotlib
import matplotlib.pyplot as plt

print(matplotlib.__version__)
print(matplotlib.get_backend())
print(sys.version)

idxs = list(range(3))
labels = ["$w^{(2)}_1$", "$w^{(2)}_2$", "$b^{(2)}$"]

fig, ax = plt.subplots(figsize=(3, 3))
ax.set_xlim(-0.5, 2.5)
ax.set_ylim(-3, 3)
ax.set_xticks(idxs, labels)
ax.set_yticks([])
ax.tick_params(which="both", length=0)
fig.tight_layout()
fig.savefig("test.png", dpi=300)

Actual outcome

Here is the image produced by the code. You can see that the $b^{(2)}$ label is vertically misaligned; it's higher than the other two labels.

Image

Expected outcome

Here is the same image if you run the code without the dpi arg in savefig(). You can see that the $b^{(2)}$ label is vertically aligned with the other two labels.

Image

Additional information

[EDIT: slight fix to code] Here's the reproduction code modified to use a workaround:

import sys

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.artist import Artist
from matplotlib.figure import Figure

print(matplotlib.__version__)
print(matplotlib.get_backend())
print(sys.version)


def get_center(fig: Figure, artist: Artist) -> tuple[float, float]:
    renderer = fig.canvas.get_renderer()  # type: ignore  # get_renderer() not in base class
    bbox = artist.get_window_extent(renderer)
    fig_box = bbox.transformed(fig.transFigure.inverted())
    x = (fig_box.xmin + fig_box.xmax) / 2
    y = (fig_box.ymin + fig_box.ymax) / 2
    return x, y


idxs = list(range(3))
labels = ["$w^{(2)}_1$", "$w^{(2)}_2$", "$b^{(2)}_1$"]  # add subscript to b2

fig, ax = plt.subplots(figsize=(3, 3))
ax.set_xlim(-0.5, 2.5)
ax.set_ylim(-3, 3)
ax.set_xticks(idxs, labels)
ax.set_yticks([])
ax.tick_params(which="both", length=0)
fig.tight_layout()  # call the layout function before fixing the label

orig_b2_label = ax.get_xticklabels()[-1]
orig_b2_x, orig_b2_y = get_center(fig, orig_b2_label)  # measure while it's still visible
orig_b2_label.set_visible(False)
b2_label = fig.text(
    orig_b2_x, orig_b2_y, "$b^{(2)}$", ha="center", va="center", transform=fig.transFigure
)
b2_label.set_in_layout(False)

fig.savefig("test-workaround.png", dpi=300)

Here is the image with the workaround:

Image

(Feel free to let me know if there is a better workaround.)

Operating system

Ubuntu 24

Matplotlib Version

3.10.9

Matplotlib Backend

module://backend_interagg

Python version

Python 3.12.3

Jupyter version

No response

Installation

uv

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions