Align labels in a circle in matplotlib - python

I have difficulties with text alignment in pyplot. I am trying to annotate points arranged in a circular fashion in a circular dendrogram, so it is important that the labels are pointing away from the dots and keeping the right angle. Here is the relevant part of what I have so far.
The horizontal labels work like a charm, but the vertical ones are obviously off. It seems that horizontalalignment / verticalalignment is applied on the original coordinates and on the bounding box. Is there any option / way to correctly align the labels without performing some crazy stunts like figuring out the text hight and moving the labels accordingly. I was wondering if it would make sense to overlay a second plot / axis with polar coordinates and put the text on it, but I am not sure if this will lead me anywhere. Or wether I am missing something really obvious...
Here is a minimal working example:
import matplotlib.pyplot as plt
(fig, ax) = plt.subplots(figsize = (4,4))
def kex(N):
alpha = 360. / N
coordX = []
coordY = []
alphas = []
for i in range(0,N):
alpha_loop = alpha * i
coordX.append( math.cos(math.radians(alpha_loop)) )
coordY.append( math.sin(math.radians(alpha * i)) )
alphas.append(alpha_loop)
return [coordX, coordY, alphas]
N = 10
points = kex(N)
ax.scatter(points[0], points[1])
for i in range(0,N):
x = points[0][i]
y = points[1][i]
a = points[2][i]
if x > 0:
ax.text(x + x * 0.1, y + y * 0.1, "AAA", rotation = a,
bbox=dict(facecolor = "none", edgecolor ="red"))
else:
ax.text(x + x * 0.1, y + y * 0.1, "AAA", rotation = a - 180,
bbox=dict(facecolor = "none", edgecolor ="red"), ha = "right")
ax.axis("off")
plt.show()
Any help is appreciated!

You may offset the text enough not to have it overlap with the points. The idea is then to center-align (ha="center", va="center") the text, such that will be sitting on an extended (virtual) line between the graph's midpoint and the dot it annotates.
import matplotlib.pyplot as plt
import numpy as np
(fig, ax) = plt.subplots(figsize = (4,4))
def kex(N):
alpha=2*np.pi/N
alphas = alpha*np.arange(N)
coordX = np.cos(alphas)
coordY = np.sin(alphas)
return np.c_[coordX, coordY, alphas]
N = 10
r = 1.2
points = kex(N)
ax.scatter(points[:,0], points[:,1])
for i in range(0,N):
a = points[i,2]
x,y = (r*np.cos(a), r*np.sin(a))
if points[i,0] < 0: a = a - np.pi
ax.text(x,y, "AAA", rotation = np.rad2deg(a), ha="center", va="center",
bbox=dict(facecolor = "none", edgecolor ="red"))
ax.axis("off")
plt.show()

Related

How do I fill/shade a cetain section of my graph in python?

I have a function that I'd like to plot in python and shade the region of interest. I've tried using pyplot.fill_between() but can not quite get what I want. I've attached an image and shaded in orange the region I want to be filled:
I plot the function (in blue) and then the graph is bounded by y=0, y ≈ 0.05 and x = 0.And I wish to shade the relevant region (in orange).
Any tips as to how to go about this?
Thanks in advance.
import numpy as np
import matplotlib.pyplot as plt
def fn (M, r_min):
d = (1- 2*M/ r_min)
x = M/(r_min)**2
A_0 = d**-0.5
A_dot = np.arange(-0.6,0.5,0.0001) #X axis
a = np.zeros(len(A_dot))
for i in range(1,len(A_dot)):
a[i] = -3*d*A_dot[i]**2 -2*x*A_dot[i] + A_0**2*x**2 #Y axis
plt.plot(A_dot, a)
plt.xlim(-0.55,0.55)
plt.axhline(y = 0, color='black', linestyle='--')
plt.axhline(y = 0.049382716, color = 'black', linestyle = '--')
plt.axvline(x = 0,color ='black', linestyle = '--')
idx = np.argwhere(np.diff(np.sign(a))).flatten() #Finding intersection on x+axis
plt.plot(A_dot[idx], a[idx], 'ro')
plt.xlabel('$\\frac{dA_0}{d\tau}$')
plt.ylabel('$|a|^2$')
plt.show()
return(A_dot,a)
fn(1,3)
You need to give the x and y vectors as inputs to fill_between. To do that, you can define a mask selecting between the interception point and 0 (add to your fn function):
x_min = A_dot[idx[1]]
x_max = 0.0
mask_x = np.logical_and(A_dot >= x_min, A_dot <= x_max)
plt.fill_between(x=A_dot[mask_x], y1=a[mask_x], y2=0, color='orange')
Result:

Make overlapping markers reduce alpha level rather than increase in python matplotlib

The alpha levels are increased where the points overlap, so the darkest areas are where points are overlapping.
I would instead like the alpha levels to subtract from each other somehow - so that if there was an overlapping section it would be lighter than a section with no overlap.
Here is an example of what I mean - from left to right the points become darker as there are more overlapped:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(20, 3))
# X and Y coordinates for red circles
red_xs = [1]
red_ys = [1]
# Plot with a large markersize
markersize = 35
alpha = 0.1
for i in range(20):
red_xs[0] += 1
for add in range(i):
ax.plot(
red_xs,
red_ys,
marker="o",
color="r",
linestyle="",
markersize=markersize,
alpha=alpha,
)
which looks as:
I would like the inverse of this - to be able to start with an alpha level of the point on the far right and for the point to be come fainter in the areas that overlap, which would have the result of rendering as the point in the far left as many are overlayed.
To give a more concrete example where points are no perfectly overlayed:
import matplotlib.pyplot as plt
import random
fig, ax = plt.subplots(figsize=(20, 3))
# X and Y coordinates for red circles
red_xs = [1]
red_ys = [1]
# Plot with a large markersize
markersize = 35
alpha = 0.01
random.seed(1)
for j in range(5):
red_xs = [1]
red_ys = [1]
for i in range(20):
u = 0.1
v = 0.00000001
dx = random.uniform(-u, u)
dy = random.uniform(-u, u)
red_xs[0] += 2 + dx
red_ys[0] += dy
for add in range(i):
ax.plot(
red_xs,
red_ys,
marker="o",
color="r",
linestyle="",
markersize=markersize,
alpha=alpha,
)
looks as:
For parts where there are overlapping points such as these:
The solution should render y (where they intersect) the alpha of x, and x the alpha of y. And this should work for any number of layers.

How to colour selected range of histogram matplotlib?

I have a data named prices, and I use a prices.tail(1) to build a histogram.
Also I have some variables: left_border = 341.086, right_border = 437.177, line_length = 1099.
And the next code:
plt.figure(figsize=(9,6))
plt.hist(prices.tail(1), bins = 400)
x2 = [left_border,left_border]
y2 = [0, line_length]
plt.plot(x2, y2, color = 'green')
x3 = [right_border, right_border]
y3 = [0, line_length]
plt.plot(x3, y3, color = 'green')
plt.show()
Produce an output:
How I can colour part of histogram which is between the green borders differently from part outside the green borders, gradientally? Also to pick bins which are nearly by green borders and turn them into another colour?
Thanks.
The exact meaning of 'gradiently' here is uncertain to me. Here are some ideas that can serve as a base to create the desired solution.
hist returns the values of each bin, the limits of the bins and the patches that were drawn; you can color the patches depending on their mean x-position
to create a gradient like effect, the simplest is interpolating linearly between two colors; a function such as sqrt can be used to make the effect start quicker
axvspan can draw a vertical span between two given x coordinates; set zorder=0 to make sure the span stays behind the histogram bars; or set an alpha=0.3 to draw it as a transparent layer over the bars
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
prices_np = 14*np.random.normal(5, 1, 10000)**2
left_border = 341.086
right_border = 437.177
# line_length = 1099
divisor_tickness = 10
main_color = mcolors.to_rgb('dodgerblue')
highlight_color = mcolors.to_rgb('limegreen')
divisor_color = mcolors.to_rgb('crimson')
binvals, bins, patches = plt.hist(prices_np, bins = 400, color=main_color)
bin_centers = 0.5 * (bins[:-1] + bins[1:])
for p, x in zip(patches, bin_centers):
#x, _ = p.get_xy()
#w = p.get_width()
if left_border < x < right_border:
f = 2*min(x-left_border, right_border-x) / (right_border - left_border)
f = f ** 0.5
p.set_facecolor([ (h_rgb*f + m_rgb * (1-f)) for m_rgb, h_rgb in zip(main_color, highlight_color)] )
elif left_border-divisor_tickness < x <= left_border or right_border <= x < right_border + divisor_tickness:
p.set_facecolor(divisor_color)
plt.axvspan(left_border, right_border, color='lightgoldenrodyellow', zorder=0)
plt.show()
To get a smooth gradient depending on the bar heights, a gaussian kde could be useful:
kde = gaussian_kde(prices_np)
max_kde = max([kde(x)[0] for x in bin_centers])
for x, p in zip(bin_centers, patches):
p.set_facecolor(plt.cm.viridis((kde(x)[0] / max_kde) ))

Add colorbar labels as text on scatter plot

I have a scatter plot generated using:
x = list(auto['umap1'])
y = list(auto['umap2'])
final_df2 = pd.DataFrame(list(zip(x,y,communities)), columns =['x', 'y', 'cluster'])
no_clusters = max(communities)
cluster_list = list(range (min(communities), no_clusters+1))
fig2, ax = plt.subplots(figsize = (20,15))
plt.scatter(x,y, c=final_df2['cluster'], cmap=plt.cm.get_cmap('hsv', max(cluster_list)), s = 0.5)
plt.title('Phenograph on UMAP - All Markers (auto)', fontsize=15)
plt.xlabel('umap_1', fontsize=15)
plt.ylabel('umap_2', fontsize=15)
plt.colorbar(extend='both',ticks = range(max(cluster_list)))
plt.show()
I wanted to know how can I add the colorbar labels (numbers from 1-31) to the actual clusters on the graph (as text) that each one corresponds to. This is because it is quite hard to tell this from the colours as they loop back to red.
I tried:
n = list(final_df2['cluster'])
for i, txt in enumerate(n):
ax.annotate(txt, (y[i], x[i]))
But this is giving me no luck.
Your code for the annotations is writing an annotation for each and every dot. This just ends in a sea of numbers.
Somehow, you should find a kind of center for each cluster, for example by averaging all the points that belong to the same cluster.
Then, you use the coordinates of the center to position the text. You can give it a background to make it easier to read.
As I don't have your data, the code below simulates some points already around a center.
from matplotlib import pyplot as plt
import pandas as pd
import numpy as np
# calculate some random points to serve as cluster centers; run a few steps of a relaxing algorithm to separate them a bit
def random_distibuted_centers():
cx = np.random.uniform(-10, 10, MAX_CLUST + 1)
cy = np.random.uniform(-10, 10, MAX_CLUST + 1)
for _ in range(10):
for i in range(1, MAX_CLUST + 1):
for j in range(1, MAX_CLUST + 1):
if i != j:
dist = np.linalg.norm([cx[i] - cx[j], cy[i] - cy[j]])
if dist < 4:
cx[i] += 0.4 * (cx[i] - cx[j]) / dist
cy[i] += 0.4 * (cy[i] - cy[j]) / dist
return cx, cy
N = 1000
MAX_CLUST = 31
cx, cy = random_distibuted_centers()
# for demonstration purposes, just generate some random points around the centers
x = np.concatenate( [np.random.normal(cx[i], 2, N) for i in range(1,MAX_CLUST+1)])
y = np.concatenate( [np.random.normal(cy[i], 2, N) for i in range(1,MAX_CLUST+1)])
communities = np.repeat(range(1,MAX_CLUST+1), N)
final_df2 = pd.DataFrame({'x':x, 'y':y, 'cluster': communities})
no_clusters = max(communities)
cluster_list = list(range (min(communities), no_clusters+1))
fig2, ax = plt.subplots(figsize = (20,15))
plt.scatter(x,y, c=final_df2['cluster'], cmap=plt.cm.get_cmap('hsv', max(cluster_list)), s=0.5)
plt.title('Phenograph on UMAP - All Markers (auto)', fontsize=15)
plt.xlabel('umap_1', fontsize=15)
plt.ylabel('umap_2', fontsize=15)
plt.colorbar(extend='both',ticks = cluster_list)
bbox_props = dict(boxstyle="circle,pad=0.3", fc="white", ec="black", lw=2, alpha=0.9)
for i in range(1,MAX_CLUST+1):
ax.annotate(i, xy=(cx[i], cy[i]), ha='center', va='center', bbox=bbox_props)
plt.show()

Python matplotlib: position colorbar in data coordinates

I would like to position a colorbar inside a scatter plot by specifying the position in data coordinates.
Here is an example of how it works when specifying figure coordinates:
import numpy as np
import matplotlib.pyplot as plt
#Generate some random data:
a = -2
b = 2
x = (b - a) * np.random.random(50) + a
y = (b - a) * np.random.random(50) + a
z = (b) * np.random.random(50)
#Do a scatter plot
fig = plt.figure()
hdl = plt.scatter(x,y,s=20,c=z,marker='o',vmin=0,vmax=2)
ax = plt.gca()
ax.set_xlim([-2,2])
ax.set_ylim([-2,2])
#Specifying figure coordinates works fine:
fig_coord = [0.2,0.8,0.25,0.05]
cbar_ax = fig.add_axes(fig_coord)
clevs = [0, 1 , 2]
cb1 = plt.colorbar(hdl, cax=cbar_ax, orientation='horizontal', ticks=clevs)
plt.show()
...Ok, can't include an image of the plot here because I am lacking reputation. But the above code will give you an impression....
Now the question is, how could I position the colorbar at data coordinates, to appear at e.g.:
left, bottom, width, height: -1.5, 1.5, 1, 0.25
I have experimented with a few things, like determining the axes position within the figure and transforming it to data coordinates but didn't succeed.
Many thanks for ideas or pointing me to already answered similar questions!
Here is what I did (not particularly beautiful but it helps). Thanks tcaswell !
#[lower left x, lower left y, upper right x, upper right y] of the desired colorbar:
dat_coord = [-1.5,1.5,-0.5,1.75]
#transform the two points from data coordinates to display coordinates:
tr1 = ax.transData.transform([(dat_coord[0],dat_coord[1]),(dat_coord[2],dat_coord[3])])
#create an inverse transversion from display to figure coordinates:
inv = fig.transFigure.inverted()
tr2 = inv.transform(tr1)
#left, bottom, width, height are obtained like this:
datco = [tr2[0,0], tr2[0,1], tr2[1,0]-tr2[0,0],tr2[1,1]-tr2[0,1]]
#and finally the new colorabar axes at the right position!
cbar_ax = fig.add_axes(datco)
#the rest stays the same:
clevs = [0, 1 , 2]
cb1 = plt.colorbar(hdl, cax=cbar_ax, orientation='horizontal', ticks=clevs)
plt.show()
Here is what I did, based on the comments to my original question:
import numpy as np
import matplotlib.pyplot as plt
a = -2
b = 2
x = (b - a) * np.random.random(50) + a
y = (b - a) * np.random.random(50) + a
z = (b) * np.random.random(50)
fig = plt.figure()
hdl = plt.scatter(x,y,s=20,c=z,marker='o',vmin=0,vmax=2)
ax = plt.gca()
ax.set_xlim([-2,2])
ax.set_ylim([-2,2])
#[(lower left x, lower left y), (upper right x, upper right y)] of the desired colorbar:
dat_coord = [(-1.5,1.5),(-0.5,1.75)]
#transform the two points from data coordinates to display coordinates:
tr1 = ax.transData.transform(dat_coord)
#create an inverse transversion from display to figure coordinates:
inv = fig.transFigure.inverted()
tr2 = inv.transform(tr1)
#left, bottom, width, height are obtained like this:
datco = [tr2[0,0], tr2[0,1], tr2[1,0]-tr2[0,0],tr2[1,1]-tr2[0,1]]
#and finally the new colorabar axes at the right position!
cbar_ax = fig.add_axes(datco)
#the rest stays the same:
clevs = [0, 1 , 2]
cb1 = plt.colorbar(hdl, cax=cbar_ax, orientation='horizontal', ticks=clevs)
plt.show()
Two step to specify the position in data coordinates of an Axes:
use Axes.set_axes_locator() to set a function that return a Bbox object in figure coordinate.
set the clip box of all children in the Axes by set_clip_box() method:
Here is the full code:
import numpy as np
import matplotlib.pyplot as plt
#Generate some random data:
a = -2
b = 2
x = (b - a) * np.random.random(50) + a
y = (b - a) * np.random.random(50) + a
z = (b) * np.random.random(50)
#Do a scatter plot
fig = plt.figure()
hdl = plt.scatter(x,y,s=20,c=z,marker='o',vmin=0,vmax=2)
ax = plt.gca()
ax.set_xlim([-2,2])
ax.set_ylim([-2,2])
#Specifying figure coordinates works fine:
fig_coord = [0.2,0.8,0.25,0.05]
cbar_ax = fig.add_axes(fig_coord)
def get_ax_loc(cbar_ax, render):
from matplotlib.transforms import Bbox
tr = ax.transData + fig.transFigure.inverted()
bbox = Bbox(tr.transform([[1, -0.5], [1.8, 0]]))
return bbox
clevs = [0, 1 , 2]
cb1 = plt.colorbar(hdl, cax=cbar_ax, orientation='horizontal', ticks=clevs)
def get_ax_loc(cbar_ax, render):
from matplotlib.transforms import Bbox
tr = ax.transData + fig.transFigure.inverted()
bbox = Bbox(tr.transform([[1, -0.5], [1.8, 0]]))
return bbox
def set_children_clip_box(artist, box):
for c in artist.get_children():
c.set_clip_box(box)
set_children_clip_box(c, box)
cbar_ax.set_axes_locator(get_ax_loc)
set_children_clip_box(cbar_ax, hdl.get_clip_box())
plt.show()
And here is the output:

Categories