matplotlib text boxes automatic position - matplotlib

I would like to position a text box with keyword arguments like those used with legend 'loc' option, i.e. 'upper left', 'upper right', 'lower right', 'lower left'.
The basic purpose is to align the text box with legend.
I found a suggestion here : automatically position text box in matplotlib but it still uses coordinates with which I have to play to get what I want, especially if I want to put it on the right of the plot area depending on the length of the text put in the box. Unless I can set one of the right corner of the text box as the reference for coordinates.

You can do this with matplotlib's AnchoredText. As shown here:
automatically position text box in matplotlib
In brief:
from matplotlib.offsetbox import AnchoredText
anchored_text = AnchoredText("Test", loc=2)
ax.plot(x,y)
ax.add_artist(anchored_text)
Each loc number corresponds to a position within the axes, for example loc=2 is "upper left". The full list of positions is given here : http://matplotlib.org/api/offsetbox_api.html

How about setting the location of the text relative to the legend? The trick is, to find the location of the legend you have to draw it first then get the bbox. Here's an example:
import matplotlib.pyplot as plt
from numpy import random
# Plot some stuff
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(random.rand(10))
# Add a legend
leg = ax.legend('line', loc = 'upper right')
# Draw the figure so you can find the positon of the legend.
plt.draw()
# Get the Bbox
bb = leg.legendPatch.get_bbox().inverse_transformed(ax.transAxes)
# Add text relative to the location of the legend.
ax.text(bb.x0, bb.y0 - bb.height, 'text', transform=ax.transAxes)
plt.show()
On the other hand, if you only need to define the location of the text from the right you can set the horizontal alignment to right like this:
plt.text(x, y, 'text', ha = 'right')

Related

matplotlib tick label anchor -- right align tick labels (on right side axis) and "clip" the left (west) side of the tick labels to the axis

I would like to use the "west" anchor for my tick labels for a twinx (right-side) axis. Looking at the plot below, for example, I would like the left side of the tick labels to be aligned with the right axis.
I attempted a few things below, to no avail.
import matplotlib.pyplot as plt
X = [1,2,3]
fig, ax = plt.subplots()
ax.plot(X)
ax.set_ylim([1,3])
ax.set_yticks(X)
axR = ax.twinx()
axR.set_ylim(ax.get_ylim())
axR.set_yticks(ax.get_yticks())
axR.set_yticklabels(['-1.00', '2.00', '3.00'], ha='right')
# axR.set_yticklabels(['-1.00', '2.00', '3.00'], ha='right', bbox_to_anchor='W')
# axR.set_yticklabels(['-1.00', '2.00', '3.00'], ha='right', bbox=dict(bbox_to_anchor='W'))
# bbox can have args from: https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.FancyBboxPatch.html#matplotlib.patches.FancyBboxPatch
fig.show()
So I had the same problem and stumbled on this question. I tried quite a bit and I basically decided I would need to find the right side of the labels when they are left aligned on the right side and then right align them from this point.
I tried a few things but don't have a lot of experience, so it's not perfect, but it seems to work by finding the coordinates as a bbox. I converted that back and forth to get it as an array (probably a shorter way that I don't know). I then took the gap of the largest one and added that to the spacing.
A few notes: I'm doing this on a subplot, hence ax2. I've also already moved the axis tick labels to the right side with ax2.yaxis.tick_right()
r = plt.gcf().canvas.get_renderer()
coord = ax2.yaxis.get_tightbbox(r)
ytickcoord = [yticks.get_window_extent() for yticks in ax2.get_yticklabels()]
inv = ax2.transData.inverted()
ytickdata = [inv.transform(a) for a in ytickcoord]
ytickdatadisplay = [ax2.transData.transform(a) for a in ytickdata]
gap = [a[1][0]-a[0][0] for a in ytickdatadisplay]
for tick in ax2.yaxis.get_majorticklabels():
tick.set_horizontalalignment("right")
ax2.yaxis.set_tick_params(pad=max(gap)+1)}
Update: I have recently been sent the solution to a similar problem with left alignment on the left side. From this solution, I believe this can be simplified to:
import matplotlib as mpl
import matplotlib.pyplot as plt
fig = plt.figure(figsize =(5,3))
ax = fig.add_axes([0,0,1,1])
plt.plot([0,100,200])
ax.yaxis.tick_right()
# Draw plot to have current tick label positions
plt.draw()
# Read max width of tick labels
ytickcoord = max([yticks.get_window_extent(renderer = plt.gcf().canvas.get_renderer()).width for yticks in ax.get_yticklabels()])
# Change ticks to right aligned
ax.axes.set_yticklabels(ax.yaxis.get_majorticklabels(),ha = "right")
# Add max width of tick labels
ax.yaxis.set_tick_params(pad=ytickcoord+1)
plt.show()
plt.close("all")

How to draw a grid in a bar-plot created with plt.vlines()

I want to create a bar-plot in python. I want this plot to be beautiful though and I don't like the looks of python's axes.bar() function. Therefore, I have decided to use plt.vlines(). The challenge here is that my x-data is a list that contains strings and not numerical data. When I plot my graph, the spacing between the two columns (in my example column 2 = 0) is pretty big:
Furthermore, I want a grid. However, I would like to have minor grid lines as well. I know how to get all of this if my data was numerical. But since my x-data contains strings, I don't know how to set x_max. Any suggestions?
Internally, the positions of the labels are numbered 0,1,... So setting the x-limits a bit before 0 and after the last, shows them more centered.
Usually, bars are drawn with their 'feet' on the ground, which can be set via plt.ylim(0, ...). Minor ticks can be positioned for example at multiples of 0.2. Setting the length of the ticks to zero lets the position count for the grid, but suppresses the tick mark.
from matplotlib import pyplot as plt
from matplotlib.ticker import MultipleLocator
import numpy as np
labels = ['Test 1', 'Test 2']
values = [1, 0.7]
fig, ax = plt.subplots()
plt.vlines(labels, 0, values, colors='dodgerblue', alpha=.4, lw=7)
plt.xlim(-0.5, len(labels) - 0.5) # add some padding left and right of the bars
plt.ylim(0, 1.1) # bars usually have their 0 at the bottom
ax.xaxis.set_minor_locator(MultipleLocator(.2))
plt.tick_params(axis='x', which='both', length=0) # ticks not shown, but position serves for gridlines
plt.grid(axis='both', which='both', ls=':') # optionally set the linestyle of the grid
plt.show()

How to make tight bounding boxes respect an invisible artist?

I want to export a figure whose bounding boxes should be tight, but accounting for an artist that is invisible.
(I want to unveil that artist in a later variant of the plot, which shall have the same bounding boxes.)
My approach to this is:
from matplotlib import pyplot as plt
plt.plot([0,1])
title = plt.title("my invisible title")
title.set_visible(False)
plt.savefig(
"invisible_artist.png",
bbox_inches="tight", pad_inches=0,
bbox_extra_artists=[title],
facecolor="grey", # just to visualise the bbox
)
This produces:
For comparison, here is the output with the title left visible, which is what I would expect in this case:
Obviously, when the title is made invisible, no space is left for it, while additional space is added in other directions.
Why does this happen and how can I achieve the desired result, i.e., have the same bounding box in both cases?
Invisible artists are not taken into account for tight bbox calculations. Some workaround could be to make the title transparent,
title.set_alpha(0)
Or to use a whitespace as title
plt.title(" ")
More generally, you can of course get the tight bounding box before making the title invisible, then turn the title invisible and finally save the figure with the previously stored bbox.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([0,1])
title = ax.set_title("my invisible title")
bbox = fig.get_tightbbox(fig.canvas.get_renderer())
title.set_visible(False)
plt.savefig(
"invisible_artist.png",
bbox_inches=bbox,
facecolor="grey", # just to visualise the bbox
)
plt.show()
The drawback is that pad_inches only works for bbox_inches="tight". So to achieve the effect of pad_inches for such manually specified bbox one would need to manipulate the Bbox itself.
Just specify the color of the title to be the same as the facecolor, i.e., 'grey' in your case. Now you don't need title.set_visible(False). I make it more general by using a variable col to specify the color
from matplotlib import pyplot as plt
col = 'grey'
plt.plot([0,1])
title = plt.title("my invisible title", color=col)
plt.savefig(
"invisible_artist.png",
bbox_inches="tight", pad_inches=0,
bbox_extra_artists=[title],
facecolor=col, # just to visualise the bbox
)

How to add a legend to matplotlib pie chart?

Using this example http://matplotlib.org/examples/pie_and_polar_charts/pie_demo_features.html
how could I add a legend to this pie chart?
My problem is that I have One big slice 88.4%, the second largest slice is 10.6%, and the other slices are 0.7 and 0.3%. The labels around the pie don't appear (except for the biggest slice) and neither the percentage values for the smaller slices. So I guess I can add a legend showing the names and the values. But I haven't found out how...
# -*- coding: UTF-8 -*-
import matplotlib.pyplot as plt
# The slices will be ordered and plotted counter-clockwise.
labels = 'Rayos X', 'RMN en solución', 'Microscopía electrónica', 'Otros'
sizes = [88.4, 10.6, 0.7, 0.3]
colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
explode = (0.1, 0, 0, 0)
plt.pie(sizes, explode=explode, labels=labels, colors=colors, shadow=True, startangle=90)
plt.legend(title="técnica")
# Set aspect ratio to be equal so that pie is drawn as a circle.
plt.axis('equal')
plt.show()
I checked your code, and the plt.legend() creates a legend, just how you want it to be; maybe set the loc="lower left", so it does not overlap with the relevant pieces of pie.
For me, the strings are displayed properly, besides the non standard chars - which might cause the problem that they are not displayed to you at all. Only the biggest slice and "Otros" do not contain special chars. Maybe also try to resize the figure, as they might be pushed out of the canvas. Please refer to how to write accents with matplotlib and try again with proper strings.
The percentages are not shown, because you did not set them to be shown. Please refer to the example posted by you, as you omitted autopct='%1.1f%%'which will plot the percentages. In this special case, I would rather not plot the percentages, as they will overlap just like the labels on the border, as some slices are too small. Maybe add these information to the legend.
Putting it all together (besides the special chars - I had some problems activating TeX), try the following code:
# -*- coding: UTF-8 -*-
import matplotlib.pyplot as plt
# The slices will be ordered and plotted counter-clockwise.
labels = [r'Rayos X (88.4 %)', r'RMN en solucion (10.6 %)',
r'Microscopia electronica (0.7 %)', r'Otros (0.3 %)']
sizes = [88.4, 10.6, 0.7, 0.3]
colors = ['yellowgreen', 'gold', 'lightskyblue', 'lightcoral']
patches, texts = plt.pie(sizes, colors=colors, startangle=90)
plt.legend(patches, labels, loc="best")
# Set aspect ratio to be equal so that pie is drawn as a circle.
plt.axis('equal')
plt.tight_layout()
plt.show()
You can change your legend following types-
best
upper right
upper left
lower left
lower right
right
center left
center right
lower center
upper center
center
state = stateData['State/UnionTerritory']
cases = stateData['ConfirmedIndianNational']
explode = stateData.ConfirmedIndianNational.apply(lambda x:x > 100)
explode = explode.apply(lambda x:0.2 if x == True else 0)
plt.title("Covid 19")
plt.pie(cases, explode=explode,autopct='%1.2f%%',shadow=True, radius=3)
plt.legend(state, loc="center")
plt.show()

Reducing the distance between two boxplots

I'm drawing the bloxplot shown below using python and matplotlib. Is there any way I can reduce the distance between the two boxplots on the X axis?
This is the code that I'm using to get the figure above:
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['ytick.direction'] = 'out'
rcParams['xtick.direction'] = 'out'
fig = plt.figure()
xlabels = ["CG", "EG"]
ax = fig.add_subplot(111)
ax.boxplot([values_cg, values_eg])
ax.set_xticks(np.arange(len(xlabels))+1)
ax.set_xticklabels(xlabels, rotation=45, ha='right')
fig.subplots_adjust(bottom=0.3)
ylabels = yticks = np.linspace(0, 20, 5)
ax.set_yticks(yticks)
ax.set_yticklabels(ylabels)
ax.tick_params(axis='x', pad=10)
ax.tick_params(axis='y', pad=10)
plt.savefig(os.path.join(output_dir, "output.pdf"))
And this is an example closer to what I'd like to get visually (although I wouldn't mind if the boxplots were even a bit closer to each other):
You can either change the aspect ratio of plot or use the widths kwarg (doc) as such:
ax.boxplot([values_cg, values_eg], widths=1)
to make the boxes wider.
Try changing the aspect ratio using
ax.set_aspect(1.5) # or some other float
The larger then number, the narrower (and taller) the plot should be:
a circle will be stretched such that the height is num times the width. aspect=1 is the same as aspect=’equal’.
http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.set_aspect
When your code writes:
ax.set_xticks(np.arange(len(xlabels))+1)
You're putting the first box plot on 0 and the second one on 1 (event though you change the tick labels afterwards), just like in the second, "wanted" example you gave they are set on 1,2,3.
So i think an alternative solution would be to play with the xticks position and the xlim of the plot.
for example using
ax.set_xlim(-1.5,2.5)
would place them closer.
positions : array-like, optional
Sets the positions of the boxes. The ticks and limits are automatically set to match the positions. Defaults to range(1, N+1) where N is the number of boxes to be drawn.
https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.boxplot.html
This should do the job!
As #Stevie mentioned, you can use the positions kwarg (doc) to manually set the x-coordinates of the boxes:
ax.boxplot([values_cg, values_eg], positions=[1, 1.3])