I would like to plot circles with matplotlib (patches), and annotate them. The annotation would be a word, and it needs to be in the centre of the circle.
So far, I can plot a circle and annotate it:
But the annotation is not centred, neither horizontally or vertically. In order to do that, I would need access to the dimensions of the text.
Is there a way to access the dimensions of the text in "the coordinate systems" ?. For example, if the circle has a radius of 15 (15 something, not pixels), the text would have a length of 12 something (not pixels).
I'm open to any other suggestion on how to do that.
Here is my code so far:
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
fig, ax = plt.subplots()
ax = fig.add_subplot(111)
x = 0
y = 0
circle = plt.Circle((x, y), radius=1)
ax.add_patch(circle)
label = ax.annotate("cpicpi", xy=(x, y), fontsize=30)
ax.axis('off')
ax.set_aspect('equal')
ax.autoscale_view()
plt.show()
You need to set the horizontal alignment in ax.annotate using ha="center". The same thing can be done for the vertical direction if necessary using the argument va="center"
fig, ax = plt.subplots()
ax = fig.add_subplot(111)
x = 0
y = 0
circle = plt.Circle((x, y), radius=1)
ax.add_patch(circle)
label = ax.annotate("cpicpi", xy=(x, y), fontsize=30, ha="center")
ax.axis('off')
ax.set_aspect('equal')
ax.autoscale_view()
plt.show()
You can add two additional arguments to the annotate() call:
label = ax.annotate(
"cpicpi",
xy=(x, y),
fontsize=30,
verticalalignment="center",
horizontalalignment="center"
)
(See the docs for the arguments of annotate and of Text – whose constructor is called by annotate)
Related
I'm plotting 3 things at once: a multicolored line via a LineCollection (following this) and a scatter (for the markers to "cover" where the lines are joining) for an average value, and a fill_between for min/max. I get all the legend returns to plot a single legend handle. The graph looks like this:
As one can note, the circle marker is not aligned with the line. How can I adjust this?
The piece of the code that is plotting them and the legend looks like:
lc = LineCollection(segments, cmap='turbo',zorder=3)
p1 = ax.add_collection(lc)
p2 = ax.fill_between(x, errmin,errmax, color=colors[1],zorder=2)
ps = ax.scatter(x,y,marker='o',s=1,c=y,cmap='turbo',zorder=4)
ax.legend([(p2, p1, ps)], ["(min/avg/max)"],fontsize=tinyfont, facecolor='white', loc='lower right')
The legend has a parameter scatteryoffsets= which defaults to [0.375, 0.5, 0.3125]. As only one point is shown, setting it to [0.5] should show the dot in the center of the legend marker.
To change the color of the line in the legend, one could create a copy of the existing line, change its color and create the legend with that copy.
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import numpy as np
from copy import copy
x = np.arange(150)
y = np.random.randn(150).cumsum()
y -= y.min()
y /= y.max()
errmin = 0
errmax = 1
segments = np.array([x[:-1], y[:-1], x[1:], y[1:]]).T.reshape(-1, 2, 2)
fig, ax = plt.subplots(figsize=(12, 3))
lc = LineCollection(segments, cmap='turbo', zorder=3)
lc.set_array(y[:-1])
p1 = ax.add_collection(lc)
p2 = ax.fill_between(x, errmin, errmax, color='lightblue', zorder=2, alpha=0.4)
ps = ax.scatter(x, y, marker='o', s=5, color='black', zorder=4)
p1copy = copy(p1)
p1copy.set_color('crimson')
leg = ax.legend([(p2, p1copy, ps)], ["(min/avg/max)"], fontsize=10, facecolor='white', loc='lower right',
scatteryoffsets=[0.5])
ax.margins(x=0.02)
plt.show()
I have some datasets that I'm visualizing in a scatter plot. I have a bunch of mean values, and a global mean. What I'm after, but cant really achieve,is to have a scatter plot that is centered in the plot, while also placing the origin at the global mean.
This is the code that defines the layout of the plot:
plt.figure(1)
plt.suptitle('Example')
plt.xlabel('x (pixels)')
plt.ylabel('y (pixels)')
ax = plt.gca()
ax.spines['left'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['bottom'].set_position('center')
ax.spines['top'].set_color('none')
ax.scatter(x_data, y_data, color=color, alpha=0.08, label=csv_file_name)
ax.plot(global_mean[0], global_mean[1], color='green',
marker='x', label='Global mean')
This produces the following plot (the ax.scatter() is called multiple times for each dataset, but it's not in the code above):
I've tried playing around with the ax.set_position() parameters but nothing have worked well so far. Is there a way to do what I'm after with matplotlib, or do I need to use some other plot library?
You can use the ax.spines() method to move them around.
import numpy as np
import random
import matplotlib.pyplot as plt
#generate some random data
x = np.linspace(1,2, 100)
y = [random.random() for _ in range(100)]
fig = plt.figure(figsize=(10,5))
# original plot
ax = fig.add_subplot(1,2,1)
ax.scatter(x, y)
# same plot, but with the spines moved
ax2 = fig.add_subplot(1,2,2)
ax2.scatter(x, y)
# move the left spine (y axis) to the right
ax2.spines['left'].set_position(('axes', 0.5))
# move the bottom spine (x axis) up
ax2.spines['bottom'].set_position(('axes', 0.5))
# turn off the right and top spines
ax2.spines['right'].set_visible(False)
ax2.spines['top'].set_visible(False)
plt.show()
I want to plot a square on the right hand side of the colorbar as a reference with the same color coding (see the image below).
But I couldn't find a way to achieve this goal. Is there any kind and intelligent man that could make this happen?
You can create a custom legend object and locate it next to the colorbar. Shown in a random plot:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
import matplotlib.patches as patches
class SquareObject(object):
pass
# Custom legend object
class SquareObjectHandler(object):
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
l1 = patches.Rectangle(
(x0, y0), # (x,y)
width / 2, # width
height, # height
fill=True,
facecolor="green",
)
handlebox.add_artist(l1)
return [l1]
fig, ax1 = plt.subplots(1, 1, figsize=(14, 6))
im = ax1.imshow(np.arange(100).reshape((10, 10)))
# To locate the colorbar
divider = make_axes_locatable(ax1)
cax = divider.append_axes('right', size='5%', pad=0.05)
plt.colorbar(im, cax=cax, label="colorbar")
# Add the legend
ax1.legend([SquareObject()],
['Reference'],
handler_map={SquareObject: SquareObjectHandler()},
loc='right center',
bbox_to_anchor=(1.4, 0.8), #(x, y)
frameon=False,
handletextpad=-0.5)
plt.show()
You can move the legend with the bbox_to_anchor parameter.
Just to post it here if someone could have the same question that I did. To get the color from the colorbar, I calculated the corresponding proportion of the given reference in the colorbar.
cmap = cm.get_cmap("OrRd") # get the corresponding colorbar
reference = 90 # set the reference
rgb = cmap( (reference - vmin) / (vmax - vmin) ) # find the color in the colorbar
finallty, set it to the "facecolor" in the class "SquareObjectHandler".
The location would be the same way. Figure out the coordinates of the colorbar and set the "bbox_to_anchor" in the "legend" accordingly.
The end result I'm attempting to achieve is to have a "thicker" black boarder around my plot, along xmin, xmax, ymin, & ymax. I've tried a couple of different things (such as just drawing a rectangle on the plot, see below), but I have not been able to achieve the desired results for a few reasons.
Because I cannot just use the spines (I've set 2 of them to always be at 0), I need to add some other line or rectangle to create the desired border.
By default the first and last tick labels overhang the axes. I "overcame" this by changing the horizontal or vertical alignment, but they could still use some more padding. I know this is possible, but requires a transform and is a bit clunky.
Now I'd like to remove the first and last tick marks on both axis. This is because given the way the rectangle is drawn it is always inside the plot area, but the first and last tick mark are always outside it, regardless of how thick the rectangle is. Making the rectangle thicker only causes it to overlap the first and last tick label more, which the actual tick mark remains outside the rectangle.
Any other suggestions on how to achieve this kind of border while always maintaining an axis at 0, 0 would be welcomed. That is the overall desired result.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.patches import Rectangle
X = np.random.randint(low=-9, high=9, size=10)
Y = np.random.randint(low=-9, high=9, size=10)
fig, ax = plt.subplots()
ax.axis([-10, 10, -10, 10])
ax.spines['left'].set_position('zero')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.setp(ax.xaxis.get_majorticklabels()[0], ha='left')
plt.setp(ax.xaxis.get_majorticklabels()[-1], ha='right')
plt.setp(ax.yaxis.get_majorticklabels()[0], va='bottom')
plt.setp(ax.yaxis.get_majorticklabels()[-1], va='top')
patPlotBorder = ax.add_artist(Rectangle((-10, -10), 20, 20, fill=False, color='k', linewidth=2))
ax.grid(True)
fig.set_tight_layout(True)
ax.scatter(X, Y, c="b", marker="o", s=40)
plt.show()
Without changing much of your code, you can set the clip_on to False, such that the complete rectangle is shown.
border = Rectangle((-10, -10), 20, 20, fill=False, color='k', linewidth=3, clip_on=False)
ax.add_artist(border)
Since the gridlines are shown above the axes content, you have some grey line within the rectangle border.
Alternatively, you can use two axes. One with all the content and the modified spine positions etc., and one where you just make the spines bold and remove all the rest.
import numpy as np
import matplotlib.pyplot as plt
X = np.random.randint(low=-9, high=9, size=10)
Y = np.random.randint(low=-9, high=9, size=10)
fig, ax = plt.subplots()
ax2 = fig.add_subplot(111)
ax2.patch.set_visible(False)
ax2.tick_params(left=False, bottom=False, labelleft=False, labelbottom=False)
for _, sp in ax2.spines.items():
sp.set_linewidth(3)
ax.axis([-10, 10, -10, 10])
ax.spines['left'].set_position('zero')
ax.spines['bottom'].set_position('zero')
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.setp(ax.xaxis.get_majorticklabels()[0], ha='left')
plt.setp(ax.xaxis.get_majorticklabels()[-1], ha='right')
plt.setp(ax.yaxis.get_majorticklabels()[0], va='bottom')
plt.setp(ax.yaxis.get_majorticklabels()[-1], va='top')
ax.grid(True)
fig.set_tight_layout(True)
ax.scatter(X, Y, c="b", marker="o", s=40)
plt.show()
You can access the individual grid lines by calling get_{x|y}gridlines(). Each grid line is an object of type Line2D, and you can change any of their properties, such as thickness, color, etc.
ax.get_xgridlines()[0].set_linewidth(5)
ax.get_xgridlines()[-1].set_linewidth(5)
ax.get_ygridlines()[0].set_linewidth(5)
ax.get_ygridlines()[-1].set_linewidth(5)
Hey I'm using rainbow text function, which can be found in here
in order to make y axis label have colors that match closest colors of the conosle names on y axis.
So currently I've came up with this code:
fig, ax= plt.subplots(figsize=(5,6)) #used to take care of the size
sns.barplot(x=gbyplat,y=gbyplat.index, palette='husl') #creating barplot
ax.set_ylabel('Publisher', color='deepskyblue', size=15, alpha=0.8) #setting labels
ax.set_xlabel('Number of titles published', color='slateblue', size=15, alpha=0.7)
ax.set_title('Titles per platform ranking', color='deeppink', size=17, alpha=0.6)
ax.set_xlim(0,2350) #setting limit for the plot
ax.set_xticks(np.arange(0, max(gbyplat), 250)) #ticks frequency
ax.annotate('newest', size=12, xy=(390, 13), xytext=(700, 13.3),
arrowprops=dict(arrowstyle="fancy")) #annotations on plot
ax.annotate('max', size=9, xy=(2230,0.3), bbox=dict(boxstyle="round", fc="w", alpha=0.5))
ax.plot(2161,0, 'o', color='cyan') #creating the cricle highlight for PS2 max
p = sns.color_palette("husl", len(gbyplat))
for i, label in enumerate(ax.get_yticklabels()):
label.set_color(p[i])
rainbow_text(0,5, "Pub lis her".split(),
[p[10],p[11],p[12]],
size=10)
However, the issue is that I have to manually set coordinates for newly produced 'Publisher' label. According to the function code i can pass ax argument which would automatically fit the label to the y axis (if I understood correctly). So how can I do that? And second question, is there a way to access ylabel coordinates (of the current y axis label 'Publisher')?
Thanks
One can set the text at the position where the ylabel would normally reside by first drawing the ylabel, obtaining its coordinates and then setting it to an empty string. One can then adapt the example rainbow text function to use the obtained coordinates.
It will still be very tricky to select the colors and coordinates such that the text will have exactly the color of the bars next to it. This probably involves a lot a trial and error.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import transforms
import seaborn as sns
l =list("ABCDEFGHIJK")
x = np.arange(1,len(l)+1)[::-1]
f, ax=plt.subplots(figsize=(7,4.5))
sns.barplot(x=x,y=l, palette='husl', ax=ax)
plt.xlabel('Number of titles published', color='slateblue', size=15, alpha=0.7)
p = sns.color_palette("husl", len(l))
for i, label in enumerate(ax.get_yticklabels()):
label.set_color(p[i])
def rainbow_text(x, y, strings, colors, ax=None, **kw):
if ax is None:
ax = plt.gca()
canvas = ax.figure.canvas
lab = ax.set_ylabel("".join(strings))
canvas.draw()
labex = lab.get_window_extent()
t = ax.transAxes
labex_data = t.inverted().transform((labex.x0, labex.y0- labex.height/2.))
ax.set_ylabel("")
for s, c in zip(strings, colors):
text = ax.text(labex_data[0]+x, labex_data[1]+y, s, color=c, transform=t,
rotation=90, va='bottom', ha='center', **kw)
text.draw(canvas.get_renderer())
ex = text.get_window_extent()
t = transforms.offset_copy(text._transform, y=ex.height, units='dots')
rainbow_text(0, 0.06, ["Pub", "lish", "er"],[p[6], p[5],p[4]],size=15)
plt.show()