简体   繁体   中英

Difference between positive and negative values in xticklabel by using Latex in matplotlib

I set usetext=True in matplotlib to use Latex for managing the font layout in my plot. Now the space between the x-axis and the xticklabel is different for positive and negative values as shown in the picture.

Is there a possibility to get the same space?

Example:

import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(-10.0, 10.0, 100)
s = np.cos(t)

plt.rc('text', usetex=True)

plt.rc('font', family='serif', size=30)
plt.plot(t, s)

plt.show()

在此输入图像描述

It's unfortunate that this bug has been around for so long, and that most answers here thought the question was about horizontal alignment rather than vertical spacing.

The bug is known, and there is a patch to fix it, but it hasn't been merged, and simply been listed as needing review for the last year and a half.

Interestingly, it's a bug that is rather confusing. The problem initially arises in that, for some reason, TeX gives a minus sign (and several other math symbols) a descender value, suggesting that it extends goes below the baseline. dvipng, used to create the labels in raster backends, just crops to the visible, so this wouldn't matter. But to keep things aligned that actually do have descenders, matplotlib has a separate system, dviread, that reads values from the dvi file itself. This picks up the odd descender value. Then matplotlib's alignment system, thinking that part of the png is supposed to be below the baseline, moves it down.

The fix in the bug report is very simple, but I'm not entirely sure how it works. I've tried it, however, and it does work.

Of course, this question was asked in 2013, so it doesn't seem like the patch is going to be applied any time soon. So what are the alternatives?

One easy option is to just ignore the alignment issue when working, but, when doing presentation output, use the pdf backend. If you want images, you can always use other software to convert. The pdf backend does not suffer from the same problem, as it handles TeX portions completely differently.

The other option is to just tweak the position of negative xticks. In theory, you could pull the exact tweak out from _get_layout, which will give you the descender value, but I'm not sure how to convert the values. So here's an example of just eyeing the alignment:

import numpy as np
import matplotlib.pyplot as plt

t = np.linspace(-10.0, 10.0, 100)
s = np.cos(t)

i = [-10,-5,0,5,10]

plt.rc('text', usetex=True)

plt.rc('font', family='serif', size=30)
plt.plot(t, s)

for n,l in zip(*plt.xticks()):
    if n<0: l.set_position((0,0.014))

This is kind of a "hacky" way of doing it and I'm not sure why it fixes it, but using FormatStrFormatter from matplotlib.ticker seems to fix it. Only difference is the font weight looks bold. I can't seem to change that, but maybe you can work with that.

import numpy as np
import matplotlib.ticker as mtick
import matplotlib.pyplot as plt

t = np.linspace(-10.0, 10.0, 100)
s = np.cos(t)

plt.rc('text', usetex=True)
plt.rc('font', family='serif', size=30)

fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(t, s)

fmtx = '%.0f%%'
fmty = '%.1f%%'
xticks = mtick.FormatStrFormatter(fmtx)
yticks = mtick.FormatStrFormatter(fmty)
ax1.xaxis.set_major_formatter(xticks)
ax1.yaxis.set_major_formatter(yticks)
plt.show()

在此输入图像描述

The way I fix minor problems like this is to save the plot as an SVG and then edit it in my favourite Vector Graphics program (such as InkScape ). This will allow you to select the individual parts of the plot and move them while preserving the good quality vector graphics.

so (sadly) I only managed to come up with another "hacky" solution but I thought I'd share:

import numpy as np
import matplotlib as mplib
import matplotlib.pyplot as plt
import types

# set up few plot options
plt.rc('text', usetex=True)
plt.rc('font', family='serif', size=30)

# get some data to plot
t = np.linspace(-10.0, 10.0, 100)
s = np.cos(t)

# plot things
fig = plt.figure()
ax  = fig.add_subplot(111)
ax.plot(t, s)

# define and register onresize callback to handle new ticks that appear on resize
def onresize(event):
    SHIFT = 0.5
    for label in ax.get_xticklabels():
        #print(label.get_text())  # test to see if new ticks appear as expected
        label.set_ha('right')
        label.customShiftValue = SHIFT
        label.set_x = types.MethodType(
                lambda self, x: mplib.text.Text.set_x(self, x+self.customShiftValue),
                label)

cid = fig.canvas.mpl_connect('resize_event', onresize)

# hold plot open
plt.show(block=True)

produces the following output

alt_img


So let me explain what I did here. Initially, I iterated the xticklabels in the for -loop and used label.set_ha('right') to horizontally align all xticklabels to the right. However, the labels were all padded and I failed to shrink their bounding box (even not sure if it works that way!). Hence, I decided to register an onresize callback function because on resize new xticklabels may appear. Then, I augmented the set_x function (code is borrowed from: https://stackoverflow.com/a/33688795/2402281 , credits to them). The only thing that could be done to not manually tweak the SHIFT variable is to get the canvas size on do some meaningful computation that is in range [0.4, 0.6] which appear to be good offset values (empirically tested...).

Even though it is just another "hacky" solution proposal, it may help in finding a suitable answer! I hope it helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM