Changing position of vertical (z) axis of 3D plot (Matplotlib)? - python

I'm doing some 3D surface plots using Matplotlib in Python and have noticed an annoying phenomenon. Depending on how I set the viewpoint (camera location), the vertical (z) axis moves between the left and right side. Here are two examples: Example 1, Axis left, Example 2, Axis right. The first example has ax.view_init(25,-135) while the second has ax.view_init(25,-45).
I would like to keep the viewpoints the same (best way to view the data). Is there any way to force the axis to one side or the other?

I needed something similar: drawing the zaxis on both sides. Thanks to the answer by #crayzeewulf I came to following workaround (for left, righ, or both sides):
First plot your 3d as you need, then before you call show() wrap the Axes3D with a Wrapper class that simply overrides the draw() method.
The Wrapper Class calls simply sets the visibility of some features to False, it draws itself and finally draws the zaxis with modified PLANES. This Wrapper Class allows you to draw the zaxis on the left, on the rigth or on both sides.
import matplotlib
matplotlib.use('QT4Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
class MyAxes3D(axes3d.Axes3D):
def __init__(self, baseObject, sides_to_draw):
self.__class__ = type(baseObject.__class__.__name__,
(self.__class__, baseObject.__class__),
{})
self.__dict__ = baseObject.__dict__
self.sides_to_draw = list(sides_to_draw)
self.mouse_init()
def set_some_features_visibility(self, visible):
for t in self.w_zaxis.get_ticklines() + self.w_zaxis.get_ticklabels():
t.set_visible(visible)
self.w_zaxis.line.set_visible(visible)
self.w_zaxis.pane.set_visible(visible)
self.w_zaxis.label.set_visible(visible)
def draw(self, renderer):
# set visibility of some features False
self.set_some_features_visibility(False)
# draw the axes
super(MyAxes3D, self).draw(renderer)
# set visibility of some features True.
# This could be adapted to set your features to desired visibility,
# e.g. storing the previous values and restoring the values
self.set_some_features_visibility(True)
zaxis = self.zaxis
draw_grid_old = zaxis.axes._draw_grid
# disable draw grid
zaxis.axes._draw_grid = False
tmp_planes = zaxis._PLANES
if 'l' in self.sides_to_draw :
# draw zaxis on the left side
zaxis._PLANES = (tmp_planes[2], tmp_planes[3],
tmp_planes[0], tmp_planes[1],
tmp_planes[4], tmp_planes[5])
zaxis.draw(renderer)
if 'r' in self.sides_to_draw :
# draw zaxis on the right side
zaxis._PLANES = (tmp_planes[3], tmp_planes[2],
tmp_planes[1], tmp_planes[0],
tmp_planes[4], tmp_planes[5])
zaxis.draw(renderer)
zaxis._PLANES = tmp_planes
# disable draw grid
zaxis.axes._draw_grid = draw_grid_old
def example_surface(ax):
""" draw an example surface. code borrowed from http://matplotlib.org/examples/mplot3d/surface3d_demo.html """
from matplotlib import cm
import numpy as np
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm, linewidth=0, antialiased=False)
if __name__ == '__main__':
fig = plt.figure(figsize=(15, 5))
ax = fig.add_subplot(131, projection='3d')
ax.set_title('z-axis left side')
ax = fig.add_axes(MyAxes3D(ax, 'l'))
example_surface(ax) # draw an example surface
ax = fig.add_subplot(132, projection='3d')
ax.set_title('z-axis both sides')
ax = fig.add_axes(MyAxes3D(ax, 'lr'))
example_surface(ax) # draw an example surface
ax = fig.add_subplot(133, projection='3d')
ax.set_title('z-axis right side')
ax = fig.add_axes(MyAxes3D(ax, 'r'))
example_surface(ax) # draw an example surface
plt.show()

As pointed out in a comment below by OP, the method suggested below did not provide adequate answer to the original question.
As mentioned in this note, there are lots of hard-coded values in axis3d that make it difficult to customize its behavior. So, I do not think there is a good way to do this in the current API. You can "hack" it by modifying the _PLANES parameter of the zaxis as shown below:
tmp_planes = ax.zaxis._PLANES
ax.zaxis._PLANES = ( tmp_planes[2], tmp_planes[3],
tmp_planes[0], tmp_planes[1],
tmp_planes[4], tmp_planes[5])
view_1 = (25, -135)
view_2 = (25, -45)
init_view = view_2
ax.view_init(*init_view)
Now the z-axis will always be on the left side of the figure no matter how you rotate the figure (as long as positive-z direction is pointing up). The x-axis and y-axis will keep flipping though. You can play with _PLANES and might be able to get the desired behavior for all axes but this is likely to break in future versions of matplotlib.

Related

Polar grid on left hand side of rectangular plot

I am trying to reproduce a plot like this:
So the requirements are actually that the grid (that is to be present just on the left side) behaves just like a grid, that is, if we zoom in and out, it is always there present and not dependent on specific x-y limits for the actual data.
Unfortunately there is no diagonal version of axhline/axvline (open issue here) so I was thinking about using the grid from polar plots.
So for that I have two problems:
This answer shows how to overlay a polar axis on top of a rectangular one, but it does not match the origins and x-y values. How can I do that?
I also tried the suggestion from this answer for having polar plots using ax.set_thetamin/max but I get an AttributeError: 'AxesSubplot' object has no attribute 'set_thetamin' How can I use these functions?
This is the code I used to try to add a polar grid to an already existing rectangular plot on ax axis:
ax_polar = fig.add_axes(ax, polar=True, frameon=False)
ax_polar.set_thetamin(90)
ax_polar.set_thetamax(270)
ax_polar.grid(True)
I was hoping I could get some help from you guys. Thanks!
The mpl_toolkits.axisartist has the option to plot a plot similar to the desired one. The following is a slightly modified version of the example from the mpl_toolkits.axisartist tutorial:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cbook as cbook
from mpl_toolkits.axisartist import SubplotHost, ParasiteAxesAuxTrans
from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
import mpl_toolkits.axisartist.angle_helper as angle_helper
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D
# PolarAxes.PolarTransform takes radian. However, we want our coordinate
# system in degree
tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform()
# polar projection, which involves cycle, and also has limits in
# its coordinates, needs a special method to find the extremes
# (min, max of the coordinate within the view).
# 20, 20 : number of sampling points along x, y direction
extreme_finder = angle_helper.ExtremeFinderCycle(20, 20,
lon_cycle=360,
lat_cycle=None,
lon_minmax=None,
lat_minmax=(0, np.inf),)
grid_locator1 = angle_helper.LocatorDMS(36)
tick_formatter1 = angle_helper.FormatterDMS()
grid_helper = GridHelperCurveLinear(tr,
extreme_finder=extreme_finder,
grid_locator1=grid_locator1,
tick_formatter1=tick_formatter1
)
fig = plt.figure(1, figsize=(7, 4))
fig.clf()
ax = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)
# make ticklabels of right invisible, and top axis visible.
ax.axis["right"].major_ticklabels.set_visible(False)
ax.axis["right"].major_ticks.set_visible(False)
ax.axis["top"].major_ticklabels.set_visible(True)
# let left axis shows ticklabels for 1st coordinate (angle)
ax.axis["left"].get_helper().nth_coord_ticks = 0
# let bottom axis shows ticklabels for 2nd coordinate (radius)
ax.axis["bottom"].get_helper().nth_coord_ticks = 1
fig.add_subplot(ax)
## A parasite axes with given transform
## This is the axes to plot the data to.
ax2 = ParasiteAxesAuxTrans(ax, tr)
## note that ax2.transData == tr + ax1.transData
## Anything you draw in ax2 will match the ticks and grids of ax1.
ax.parasites.append(ax2)
intp = cbook.simple_linear_interpolation
ax2.plot(intp(np.array([150, 230]), 50),
intp(np.array([9., 3]), 50),
linewidth=2.0)
ax.set_aspect(1.)
ax.set_xlim(-12, 1)
ax.set_ylim(-5, 5)
ax.grid(True, zorder=0)
wp = plt.Rectangle((0,-5),width=1,height=10, facecolor="w", edgecolor="none")
ax.add_patch(wp)
ax.axvline(0, color="grey", lw=1)
plt.show()

How to check if colorbar exists on figure

Question: Is there a way to check if a color bar already exists?
I am making many plots with a loop. The issue is that the color bar is drawn every iteration!
If I could determine if the color bar exists then I can put the color bar function in an if statement.
if cb_exists:
# do nothing
else:
plt.colorbar() #draw the colorbar
If I use multiprocessing to make the figures, is it possible to prevent multiple color bars from being added?
import numpy as np
import matplotlib.pyplot as plt
import multiprocessing
def plot(number):
a = np.random.random([5,5])*number
plt.pcolormesh(a)
plt.colorbar()
plt.savefig('this_'+str(number))
# I want to make a 50 plots
some_list = range(0,50)
num_proc = 5
p = multiprocessing.Pool(num_proc)
temps = p.map(plot, some_list)
I realize I can clear the figure with plt.clf() and plt.cla() before plotting the next iteration. But, I have data on my basemap layer I don't want to re-plot (that adds to the time it takes to create the plot). So, if I could remove the colorbar and add a new one I'd save some time.
Is is actually not easy to remove a colorbar from a plot and later draw a new one to it.
The best solution I can come up with at the moment is the following, which assumes that there is only one axes present in the plot. Now, if there was a second axis, it must be the colorbar beeing present. So by checking how many axes we find on the plot, we can judge upon whether or not there is a colorbar.
Here we also mind the user's wish not to reference any named objects from outside. (Which does not makes much sense, as we need to use plt anyways, but hey.. so was the question)
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
im = ax.pcolormesh(np.array(np.random.rand(2,2) ))
ax.plot(np.cos(np.linspace(0.2,1.8))+0.9, np.sin(np.linspace(0.2,1.8))+0.9, c="k", lw=6)
ax.set_title("Title")
cbar = plt.colorbar(im)
cbar.ax.set_ylabel("Label")
for i in range(10):
# inside this loop we should not access any variables defined outside
# why? no real reason, but questioner asked for it.
#draw new colormesh
im = plt.gcf().gca().pcolormesh(np.random.rand(2,2))
#check if there is more than one axes
if len(plt.gcf().axes) > 1:
# if so, then the last axes must be the colorbar.
# we get its extent
pts = plt.gcf().axes[-1].get_position().get_points()
# and its label
label = plt.gcf().axes[-1].get_ylabel()
# and then remove the axes
plt.gcf().axes[-1].remove()
# then we draw a new axes a the extents of the old one
cax= plt.gcf().add_axes([pts[0][0],pts[0][1],pts[1][0]-pts[0][0],pts[1][1]-pts[0][1] ])
# and add a colorbar to it
cbar = plt.colorbar(im, cax=cax)
cbar.ax.set_ylabel(label)
# unfortunately the aspect is different between the initial call to colorbar
# without cax argument. Try to reset it (but still it's somehow different)
cbar.ax.set_aspect(20)
else:
plt.colorbar(im)
plt.show()
In general a much better solution would be to operate on the objects already present in the plot and only update them with the new data. Thereby, we suppress the need to remove and add axes and find a much cleaner and faster solution.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
im = ax.pcolormesh(np.array(np.random.rand(2,2) ))
ax.plot(np.cos(np.linspace(0.2,1.8))+0.9, np.sin(np.linspace(0.2,1.8))+0.9, c="k", lw=6)
ax.set_title("Title")
cbar = plt.colorbar(im)
cbar.ax.set_ylabel("Label")
for i in range(10):
data = np.array(np.random.rand(2,2) )
im.set_array(data.flatten())
cbar.set_clim(vmin=data.min(),vmax=data.max())
cbar.draw_all()
plt.draw()
plt.show()
Update:
Actually, the latter approach of referencing objects from outside even works together with the multiprocess approach desired by the questioner.
So, here is a code that updates the figure, without the need to delete the colorbar.
import matplotlib.pyplot as plt
import numpy as np
import multiprocessing
import time
fig, ax = plt.subplots()
im = ax.pcolormesh(np.array(np.random.rand(2,2) ))
ax.plot(np.cos(np.linspace(0.2,1.8))+0.9, np.sin(np.linspace(0.2,1.8))+0.9, c="w", lw=6)
ax.set_title("Title")
cbar = plt.colorbar(im)
cbar.ax.set_ylabel("Label")
tx = ax.text(0.2,0.8, "", fontsize=30, color="w")
tx2 = ax.text(0.2,0.2, "", fontsize=30, color="w")
def do(number):
start = time.time()
tx.set_text(str(number))
data = np.array(np.random.rand(2,2)*(number+1) )
im.set_array(data.flatten())
cbar.set_clim(vmin=data.min(),vmax=data.max())
tx2.set_text("{m:.2f} < {ma:.2f}".format(m=data.min(), ma= data.max() ))
cbar.draw_all()
plt.draw()
plt.savefig("multiproc/{n}.png".format(n=number))
stop = time.time()
return np.array([number, start, stop])
if __name__ == "__main__":
multiprocessing.freeze_support()
some_list = range(0,50)
num_proc = 5
p = multiprocessing.Pool(num_proc)
nu = p.map(do, some_list)
nu = np.array(nu)
plt.close("all")
fig, ax = plt.subplots(figsize=(16,9))
ax.barh(nu[:,0], nu[:,2]-nu[:,1], height=np.ones(len(some_list)), left=nu[:,1], align="center")
plt.show()
(The code at the end shows a timetable which allows to see that multiprocessing has indeed taken place)
If you can access to axis and image information, colorbar can be retrieved
as a property of the image (or the mappable to which associate colorbar).
Following a previous answer (How to retrieve colorbar instance from figure in matplotlib), an example could be:
ax=plt.gca() #plt.gca() for current axis, otherwise set appropriately.
im=ax.images #this is a list of all images that have been plotted
if im[-1].colorbar is None: #in this case I assume to be interested to the last one plotted, otherwise use the appropriate index or loop over
plt.colorbar() #plot a new colorbar
Note that an image without colorbar returns None to im[-1].colorbar
One approach is:
initially (prior to having any color bar drawn), set a variable
colorBarPresent = False
in the method for drawing the color bar, check to see if it's already drawn. If not, draw it and set the colorBarPresent variable True:
def drawColorBar():
if colorBarPresent:
# leave the function and don't draw the bar again
else:
# draw the color bar
colorBarPresent = True
There is an indirect way of guessing (with reasonable accuracy for most applications, I think) whether an Axes instance is home to a color bar. Depending on whether it is a horizontal or vertical color bar, either the X axis or Y axis (but not both) will satisfy all of these conditions:
No ticks
No tick labels
No axis label
Axis range is (0, 1)
So here's a function for you:
def is_colorbar(ax):
"""
Guesses whether a set of Axes is home to a colorbar
:param ax: Axes instance
:return: bool
True if the x xor y axis satisfies all of the following and thus looks like it's probably a colorbar:
No ticks, no tick labels, no axis label, and range is (0, 1)
"""
xcb = (len(ax.get_xticks()) == 0) and (len(ax.get_xticklabels()) == 0) and (len(ax.get_xlabel()) == 0) and \
(ax.get_xlim() == (0, 1))
ycb = (len(ax.get_yticks()) == 0) and (len(ax.get_yticklabels()) == 0) and (len(ax.get_ylabel()) == 0) and \
(ax.get_ylim() == (0, 1))
return xcb != ycb # != is effectively xor in this case, since xcb and ycb are both bool
Thanks to this answer for the cool != xor trick: https://stackoverflow.com/a/433161/6605826
With this function, you can see if a colorbar exists by:
colorbar_exists = any([is_colorbar(ax) for ax in np.atleast_1d(gcf().axes).flatten()])
or if you're sure the colorbar will always be last, you can get off easy with:
colorbar_exists = is_colorbar(gcf().axes[-1])

Draw arrows on matplotlib figure directly

I created a plot containing multiple subplots on a grid. The plots differ in two parameters, so I would like it to look like they ordered in a coordinate system.
I managed to plot lines using matplotlib.lines.Line2D() next to the subplots directly on the figure.
But I would prefer to have an arrow instead of a line to make it more clear.
(I can add the specific parameter values using fig.text().)
I'd like the blue lines to be arrows
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from itertools import product
fig = plt.figure()
plotGrid = mpl.gridspec.GridSpec(2, 2)
x = np.linspace(0,10,10000)
y = [j* np.sin(x + i) for i,j in product(range(2), range(1,3))]
for i in range(4):
ax = plt.Subplot(fig, plotGrid[i])
for sp in ax.spines.values():
sp.set_visible(False)
ax.plot(x,y[i], color = 'r')
ax.set_xticks([])
ax.set_yticks([])
fig.add_subplot(ax)
all_axes = fig.get_axes()
#I would like these lines to be arrows
blcorPosn = 0.08 #bottom corner position
l1 = mpl.lines.Line2D([blcorPosn,blcorPosn], [1, blcorPosn],
transform=fig.transFigure, fig)
l2 = mpl.lines.Line2D([blcorPosn, 1], [blcorPosn, blcorPosn],
transform=fig.transFigure, fig)
fig.lines.extend([l1, l2])
I'm not sure if this is the way to go. But I spend like a day on this by now and the only way I see so far to draw arrows is drawing them directly on an axes but thats not an option for me as far as I can see.
Also this is my first post here so advice on how to ask questions is highly appreciated.
Thanks
You can replace the Line2D along each axis with a slightly modified call to FancyArrow patch. The main difference is that that origin and destination x,y coords are replaced with origin x,y and a x,y distance to draw. The values are also passed as parameters directly, not as lists:
l1 = mpl.patches.FancyArrow(blcorPosn, blcorPosn, 1, 0,
transform=fig.transFigure, figure=fig)
l2 = mpl.patches.FancyArrow(blcorPosn, blcorPosn, 0, 1,
transform=fig.transFigure, figure=fig)
The FancyArrow patch accepts a few other parameters to allow you to customise the appearance of the arrow including width (for line width), head_width and head_length.

python/matplotlib - Specifying Parasite Axis Length

I am plotting data from several sources and need multiple x axes, preferably offset such as those seen in the link. I would very much like my x axes to have variable length, allowing me to put many plots on the same figure. What I have done so far is:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
host = host_subplot(111, axes_class=AA.Axes,yscale='log')
plt.subplots_adjust(bottom=0.25)
par1 = host.twiny()
offset = 60
new_fixed_axis = par1.get_grid_helper().new_fixed_axis
par1.axis['bottom'] = new_fixed_axis(loc='bottom',
axes=par1,
offset=(0, -60))
host.set_xlim(200, 350)
host.set_ylim(1050, 100)
par1.set_xlim(0, 1)
host.set_xlabel('Temperature (K)')
host.set_ylabel('Pressure (hPa)')
par1.set_xlabel('Relative Humidity (%)')
p1, = host.plot(T,P)
p2, = host.plot(pT,P)
p2, = par1.plot(RH,P)
So I get the axis to drop down, but cannot, for the life of me, figure out how to get the axis to actually compress horizontally (e.g. like the blue axes in the linked figure above).
My question is how can this be done (if at all)?
#Oz123
Here is what I have:
host = host_subplot(111, axes_class=AA.Axes,yscale='log')
plt.subplots_adjust(bottom=0.25)
par1 = host.twiny()
new_fixed_axis = par1.get_grid_helper().new_fixed_axis
cax1 = plt.axes(axisbg='none',frameon=False)
cax1 = plt.add_axes(plt.get_position(), frameon=False)
par1.axis['bottom'] = new_fixed_axis(loc='bottom',
axes=cax1,
offset=(0, -60))
When I get to:
cax1 = plt.add_axes(plt.get_position(), frameon=False)
My previous x/y axes disappear, and I am left with a grey screen with only cax1.
My apologies, I'm just picking up matplotlib so I'M afraid I'm still quite a novice here.
You are creating par1.axis['bottom'] with the major axobject, so you are quite limited in what you can actually do.
Instead you should create 2 or more axes instances. And put them on the figure instance.
adding new axes instance
cax1 = plt.axes(axisbg='none', frameon=False)
Like this you could you have fine grain control of the size of your humidity scale.
The following line:
par1.axis['bottom'] = new_fixed_axis(loc='bottom',
axes=par1,
offset=(0, -60))
should be for example:
par1.axis['bottom'] = new_fixed_axis(loc='bottom',
axes=cax1, # custom axis number 1
offset=(0, -60))
Note that using IPython, you can quickly find which methods are available to control
your newly created axes instance.
In [38]: cax1.set_ #tab pressed
cax1.set_adjustable cax1.set_axis_bgcolor cax1.set_frame_on cax1.set_subplotspec cax1.set_xticks
cax1.set_agg_filter cax1.set_axis_off cax1.set_gid cax1.set_title cax1.set_ybound
cax1.set_alpha cax1.set_axis_on
# many more options trimmed, but I think you might want to take a look in:
controlling the location of your newly created instance:
In [38]: cax1.set_position?
Type: instancemethod
String Form:<bound method AxesSubplot.set_position of <matplotlib.axes.AxesSubplot object at 0x2d7fb90>>
File: /usr/lib/pymodules/python2.7/matplotlib/axes.py
Definition: cax1.set_position(self, pos, which='both')
Docstring:
Set the axes position with::
pos = [left, bottom, width, height]

How to annotate point on a scatter automatically placed arrow

if I make a scatter plot with matplotlib:
plt.scatter(randn(100),randn(100))
# set x, y lims
plt.xlim([...])
plt.ylim([...])
I'd like to annotate a given point (x, y) with an arrow pointing to it and a label. I know this can be done with annotate, but I'd like the arrow and its label to be placed "optimally" in such a way that if it's possible (given the current axis scales/limits) that the arrow and the label do not overlap with the other points. eg if you wanted to label an outlier point. is there a way to do this? it doesn't have to be perfect, but just an intelligent placement of the arrow/label, given only the (x,y) coordinates of the point to be labeled. thanks.
Basically, no, there isn't.
Layout engines that handle placing map labels similar to this are surprisingly complex and beyond the scope of matplotlib. (Bounding box intersections are actually a rather poor way of deciding where to place labels. What's the point in writing a ton of code for something that will only work in one case out of 1000?)
Other than that, due to the amount of complex text rendering that matplotlib does (e.g. latex), it's impossible to determine the extent of text without fully rendering it first (which is rather slow).
However, in many cases, you'll find that using a transparent box behind your label placed with annotate is a suitable workaround.
E.g.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
x, y = np.random.random((2,500))
fig, ax = plt.subplots()
ax.plot(x, y, 'bo')
# The key option here is `bbox`. I'm just going a bit crazy with it.
ax.annotate('Something', xy=(x[0], y[0]), xytext=(-20,20),
textcoords='offset points', ha='center', va='bottom',
bbox=dict(boxstyle='round,pad=0.2', fc='yellow', alpha=0.3),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5',
color='red'))
plt.show()
Use adjustText (full disclosure, I wrote it).
Let's label the first 10 points. The only parameter I changed was lowering the force of repelling from the points, since there is so many of them and we want the algorithm to take a bit more time and place the annotations more carefully.
import numpy as np
import matplotlib.pyplot as plt
from adjustText import adjust_text
np.random.seed(1)
x, y = np.random.random((2,500))
fig, ax = plt.subplots()
ax.plot(x, y, 'bo')
ts = []
for i in range(10):
ts.append(plt.text(x[i], y[i], 'Something'+str(i)))
adjust_text(ts, x=x, y=y, force_points=0.1, arrowprops=dict(arrowstyle='->',
color='red'))
plt.show()
It's not ideal, but the points are really dense here and sometimes there is no way to place the text near to its target without overlapping any of them. But it's all automatic and easy to use, and also doesn't let labels overlap each other.
PS
It uses bounding box intersections, but rather successfully I'd say!
Another example using awesome Phlya's package based on adjustText_mtcars:
from adjustText import adjust_text
import matplotlib.pyplot as plt
mtcars = pd.read_csv(
"https://gist.githubusercontent.com/seankross/a412dfbd88b3db70b74b/raw/5f23f993cd87c283ce766e7ac6b329ee7cc2e1d1/mtcars.csv"
)
def plot_mtcars(adjust=False, force_points=1, *args, **kwargs):
# plt.figure(figsize=(9, 6))
plt.scatter(mtcars["wt"], mtcars["mpg"], s=15, c="r", edgecolors=(1, 1, 1, 0))
texts = []
for x, y, s in zip(mtcars["wt"], mtcars["mpg"], mtcars["model"]):
texts.append(plt.text(x, y, s, size=9))
plt.xlabel("wt")
plt.ylabel("mpg")
if adjust:
plt.title(
"force_points: %.1f\n adjust_text required %s iterations"
% (
force_points,
adjust_text(
texts,
force_points=force_points,
arrowprops=dict(arrowstyle="-", color="k", lw=0.5),
**kwargs,
),
)
)
else:
plt.title("Original")
return plt
fig = plt.figure(figsize=(12, 12))
force_points = [0.5, 1, 2, 4]
for index, k in enumerate(force_points):
fig.add_subplot(2, 2, index + 1)
plot_mtcars(adjust=True, force_points=k)

Categories