Matplotlib outline stickers overlap axes

I do some contour plots with contour that are labeled via clabel . The problem is that outline labels tend to overlap with axes: enter image description here

(some other tags are dirty, ignore this). For the left section, 10 ^ -3 and 10 are problematic. On the right, 10 ^ 3 is the only problem. Here is the code that one of them generates:

 fig = plt.figure(figsize=(6,3)) ax = fig.add_subplot(121) ax.set_xscale('log') ax.set_yscale('log') ax.set_xlabel(r'$T_e$ (eV)', fontsize=10) ax.set_ylabel(r'$n_e$ (1/cm$^3$)', fontsize=10) ax.set_xlim(0.1, 1e4) ax.set_ylim(1e16, 1e28) CS = ax.contour(X, Y, Z, V, colors='k') ax.clabel(CS, inline=True, inline_spacing=3, rightside_up=True, colors='k', fontsize=8, fmt=fmt) 

Is there a way to get clabel to behave better about its placement?

+6
source share
1 answer

Given that the examples in the documentation suffer from one disease, they say that it will not be painless. It would seem that you have to live with automatic ones, use manual placement or get your hands dirty.

As a compromise, I would try one of two things. Both start by allowing matplotlib offer shortcut positions for you, and then process those that are too close to the axes.

A simpler case, which is also safer, is to simply get rid of those clabel that are close to the border by filling in these contour lines:

 # based on matplotlib.pyplot.clabel example: import matplotlib import numpy as np import matplotlib.cm as cm import matplotlib.mlab as mlab import matplotlib.pyplot as plt delta = 0.025 x = np.arange(-3.0, 3.0, delta) y = np.arange(-2.0, 2.0, delta) X, Y = np.meshgrid(x, y) Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0) Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1) # difference of Gaussians Z = 10.0 * (Z2 - Z1) plt.figure() CS = plt.contour(X, Y, Z) CLS = plt.clabel(CS, inline=1, fontsize=10) # now CLS is a list of the labels, we have to find offending ones thresh = 0.05 # ratio in x/y range in border to discard # get limits if they're automatic xmin,xmax,ymin,ymax = plt.axis() Dx = xmax-xmin Dy = ymax-ymin # check which labels are near a border keep_labels = [] for label in CLS: lx,ly = label.get_position() if xmin+thresh*Dx<lx<xmax-thresh*Dx and ymin+thresh*Dy<ly<ymax-thresh*Dy: # inlier, redraw it later keep_labels.append((lx,ly)) # delete the original lines, redraw manually the labels we want to keep # this will leave unlabelled full contour lines instead of overlapping labels for cline in CS.collections: cline.remove() for label in CLS: label.remove() CS = plt.contour(X, Y, Z) CLS = plt.clabel(CS, inline=1, fontsize=10, manual=keep_labels) 

The disadvantage is that some shortcuts will obviously be missing, and, of course, the 5% threshold should need to be manually configured for your specific application. The result is higher compared to the original (see above):

before after

Another solution I mentioned would be to take offensive labels, look at the Path their respective CS.collections data and try to find a point that is closer to the inside of the figure. Since there is no trivial collections pair of labels with labels (since each path of the path level with several segments corresponds to one CS.collections element), this may not be worth the effort. Especially that you may encounter such lowercase lines that it is impossible to place a label on them, and you will also need to estimate the size of each label.


Considering that in your case the contour lines are quite simple, you can also try to look at each contour line and find the point that is closest to the center of the figure.

So, here is a reconstruction of your dataset for demo purposes:

 # guesstimated dummy data X,Y = np.meshgrid(np.logspace(-3,7,200),np.logspace(13,31,200)) Z = X/Y*10**21 Vrange = range(-3,5) V = [10**k for k in Vrange] fmt = {lev: '$10^{%d}$'%k for (k,lev) in zip(Vrange,V)} fig = plt.figure(figsize=(3,3)) ax = fig.add_subplot(111) ax.set_xscale('log') ax.set_yscale('log') ax.set_xlabel(r'$T_e$ (eV)', fontsize=10) ax.set_ylabel(r'$n_e$ (1/cm$^3$)', fontsize=10) ax.set_xlim(0.1, 1e4) ax.set_ylim(1e16, 1e28) CS = ax.contour(X, Y, Z, V, colors='k') ax.clabel(CS, inline=True, inline_spacing=3, rightside_up=True, colors='k', fontsize=8, fmt=fmt) 

I explicitly use the fact that both of your axes are logarithmic, the main idea is to replace the last call above with clabel with:

 # get limits if they're automatic xmin,xmax,ymin,ymax = plt.axis() # work with logarithms for loglog scale # middle of the figure: logmid = (np.log10(xmin)+np.log10(xmax))/2, (np.log10(ymin)+np.log10(ymax))/2 label_pos = [] for line in CS.collections: for path in line.get_paths(): logvert = np.log10(path.vertices) # find closest point logdist = np.linalg.norm(logvert-logmid, ord=2, axis=1) min_ind = np.argmin(logdist) label_pos.append(10**logvert[min_ind,:]) # draw labels, hope for the best ax.clabel(CS, inline=True, inline_spacing=3, rightside_up=True, colors='k', fontsize=8, fmt=fmt, manual=label_pos) 

Result (right) compared to the original (left):

before 2 after 2

I did not make much effort to make axis annotations pretty, so please ignore these details. You can see that the labels are really well assembled near the middle of the figure. Depending on your application, this may or may not be what you want.

As a final note, the reason the labels are not located diagonally on the axes is because the scaling is different on the X and Y axes. This may cause some of the shortcuts to still flash. The most reliable solution would be to consider the line [xmin,ymax] - [xmax,ymin] (logarithmic) and find the intersection of this line with each of Path s. You should be very invested in it if it’s worth it: you can also place your tags completely manually.

+3
source

Source: https://habr.com/ru/post/975363/


All Articles