Using pyplot circle function I made a circle, then I have used text function to place the text(parameters) across the circle(PLOT ATTACHED) but the thing is if let's say I want to list out only 6 or say 11 parameters equally spaced across the circle I'll have to chage the coordinates as well as the rotation in text(and the coordinates and rotation value has been manually set). I want something that'll automate these things like given a number of parameter it will place parameter with equal spacing between them around the circle
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
fig, ax = plt.subplots(figsize=(30, 20))
ax.axis('equal')
ax.set(xlim=(-10, 23), ylim = (-10, 10))
circle = plt.Circle((0, 0), 4.7, fc='#cfe2f3')
ax.add_patch(circle)
ax.text(-0.4, 4.9, 'npxG', fontsize=15)
ax.text(3.35, 3.5, 'xA', rotation=310, fontsize=15)
ax.text(4.8, -0.5, 'Shots on Target', rotation=270, fontsize=15)
ax.text(3.35, -3.55, 'Dribbles', rotation=50, fontsize=15)
ax.text(-1, -5., 'Through Balls', fontsize=15)
ax.text(-4.6, -3.6, 'Passes 1/3', rotation=305, fontsize=15)
ax.text(-5, -0.5, 'Key Passes', rotation=90, fontsize=15)
ax.text(-4., 3.3, 'Crosses', rotation=42, fontsize=15)
ax.axis('off')
Edit:
for i in range(0, len(data)):
a = points[i,2]
x,y = (radius*np.sin(a), radius*np.cos(a))
a = a - 0.5*np.pi
if points[i,1] < 0:
a = a - np.pi
ax.text(x, y, data[i], rotation = np.rad2deg(a), ha="center", va="center", fontsize=15)
On changing order of the array:
On flipping the x and y values:
Using code and inspiration from this question and answer and a bit of coordinate geometry:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(30, 20))
ax.axis('equal')
ax.set(xlim=(-10, 10), ylim=(-10, 10))
circle = plt.Circle((0, 0), 2.7, fc='#cfe2f3')
ax.add_patch(circle)
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]
data = ["npxG", "xA", "Shots on Target", "Dribbles", "Through Balls",
"Passes 1/3", "Key Passes", "Crosses"]
radius = 3.2
points = kex(len(data))
for i in range(0, len(data)):
a = points[i,2]
x,y = (radius*np.cos(a), radius*np.sin(a))
if points[i,0] < 0:
a = a - np.pi
ax.text(x, y, data[i], ha="center", va="center", fontsize=15)
ax.axis("off")
plt.show()
Gives this:
If you wish to adapt something like the linked answer and rotate the labels as a perpendicular to the circle, change this line:
ax.text(x, y, data[i], rotation = np.rad2deg(a), ha="center", va="center", fontsize=15)
Note the added roatation parameter. This gives:
To adapt something like the sample image in the question:
for i in range(0, len(data)):
a = points[i,2]
x,y = (radius*np.cos(a), radius*np.sin(a))
a = a - 0.5*np.pi
if points[i,1] < 0:
a = a - np.pi
ax.text(x, y, data[i], rotation = np.rad2deg(a), ha="center", va="center", fontsize=15)
This gives:
The list data can be populated with label text. On changing the number of labels, the plot should adapt accordingly. The parameter radius adjusts the distance of the text from the center of the circle. You can add in extra parameters to the .text() function such as fontsize as required for the labels.
Note: View this answer on the SO white theme to see the labels clearly. I took the liberty to change the plot size to fit it here. Huge thanks to #ImportanceOfBeingErnest for the linked question's answer.
Related
So I have some data in spherical coords, but r is not important. So I really have (theta,phi,value), where theta goes 0-360 deg and phi 0-90 deg... Values go from -40 to 40 ... I can plot this data using pcolormesh on a polar diagram,
phis2 = np.linspace(0.001,63,201)
thetas2 = np.linspace(0,2*np.pi,201)
# Using same number of samples in phi and thera to simplify plotting
print(phis2.shape,thetas2.shape)
X,Y = np.meshgrid(thetas2,phis2)
doppMap2 =orbits.doppler(X*units.rad,Y*deg) # Calling function with a vector: MUCH faster than looping as above
fig, ax = plt.subplots(figsize=(8,7),subplot_kw=dict(projection='polar'))
im=ax.pcolormesh(X,Y,doppMap2,cmap=mpl.cm.jet_r, edgecolors='face')
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.set_xticks([x for x in np.linspace(0,2*np.pi,13)][:-1]) # ignore label 360
ax.grid(True)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.text(.6, 1.025, "Nadir ang", transform=ax.transAxes, fontsize=14)
## Add colorbar
cbar_ax = fig.add_axes([0.95, 0.15, 0.015, 0.7])
cbar = fig.colorbar(im, cax=cbar_ax)
cbar.ax.tick_params(labelsize=14)
#cbar.ax.set_yticklabels(['1', '2', '4', '6', '10', maxCV], size=24)
#cbar.set_label(r"log ($P(\overline{Z_{G}} /Z_{\odot})$ / $d(M_{G}/M_{\odot})$)",fontsize=36)
cbar.set_label(r"$d$f [kHz]",fontsize=24)
gc.collect()
but I'd like to generate isochrone lines instead. How would I do that?
Data for doppMap2 is here...
Matplotlib calls that a contour map:
# answering https://stackoverflow.com/questions/74073323/isochrone-plot-in-polar-coordinates
import numpy as np
import pandas
import matplotlib as mpl
import matplotlib.pyplot as plt
phis2 = np.linspace(0.001,63,201)
thetas2 = np.linspace(0,2*np.pi,201)
# Using same number of samples in phi and thera to simplify plotting
print(phis2.shape,thetas2.shape)
X,Y = np.meshgrid(thetas2,phis2)
# doppMap2 = orbits.doppler(X*units.rad,Y*deg) # Calling function with a vector: MUCH faster than looping as above
doppMap2 = pandas.read_csv('dopMap.csv', header=None)
print(doppMap2.shape)
fig, ax = plt.subplots(figsize=(8,7),subplot_kw=dict(projection='polar'))
im = ax.contour(X, Y, doppMap2, 12)
ax.set_theta_direction(-1)
ax.set_theta_offset(np.pi / 2.0)
ax.set_xticks([x for x in np.linspace(0,2*np.pi,13)][:-1]) # ignore label 360
ax.grid(True)
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
plt.text(.6, 1.025, "Nadir ang",
transform=ax.transAxes, fontsize=14)
## Add colorbar
cbar_ax = fig.add_axes([0.95, 0.15, 0.015, 0.7])
cbar = fig.colorbar(im, cax=cbar_ax)
cbar.ax.tick_params(labelsize=14)
cbar.set_label(r"$d$f [kHz]",fontsize=24)
plt.show()
I'm trying to create an inset figure that has a different projection from the parent. The only issue I have at this point is the inset figures's tick labels are not legible because they are black and blend in with the plot behind it. I could change the color of the ticks and labels to white, but that does not help when the data in ax0 yields lighter colors. Here is the MWE:
import calipsoFunctions as cf
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import numpy as np
import pylab as pl
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
from mpl_toolkits.axes_grid1.inset_locator import inset_axes, mark_inset, InsetPosition
x, y = np.arange(100), np.arange(200)
X, Y = np.meshgrid(x, y)
C = np.random.randint(0, 100, (200, 100))
fig = pl.figure(figsize=(6.5, 5.25))
gs0 = pl.GridSpec(3, 1)
gs0.update(left=0.08, right=0.925,
top=0.95, bottom=0.33,
hspace=0.10, wspace=0.0)
gs1 = pl.GridSpec(1, 2)
gs1.update(left=0.08, right=0.925,
top=0.225, bottom=0.05,
hspace=0.0, wspace=0.025)
# create primary axes
ax0 = pl.subplot(gs0[0])
ax1 = pl.subplot(gs0[1])
ax0.pcolormesh(X, Y, C, vmin=0, vmax=75)
ax1.pcolormesh(X, Y, C, vmin=0, vmax=75)
# add map plot (inset axis)
loc_box = [0.8, 0.55, 0.20, 0.45]
ax0_inset = fig.add_axes(loc_box,
projection=ccrs.PlateCarree(),
aspect="auto",
facecolor="w",
frameon=True)
lat_array = np.arange(-20, 20)
lon_array = np.arange(-10, 10, 0.5)
ax0_inset.plot(lat_array, lon_array, "k-", lw=1)
ip = InsetPosition(ax0, loc_box)
ax0_inset.set_axes_locator(ip)
ax0_inset.coastlines(resolution="10m", linewidth=0.25, color="k")
ax0_inset.add_feature(cfeature.LAND)
llat, ulat = lat_array.min(), lat_array.max()
llon, ulon = lon_array.min(), lon_array.max()
llat = np.round(llat / 10) * 10
ulat = np.round(ulat / 10) * 10
llon = np.round(llon / 5) * 5
ulon = np.round(ulon / 5) * 5
ax0_inset.set_yticks(np.arange(llat, ulat, 20), minor=False)
ax0_inset.set_yticks(np.arange(llat, ulat, 10), minor=True)
ax0_inset.set_yticklabels(np.arange(llat, ulat, 20),
fontsize=8)
ax0_inset.yaxis.set_major_formatter(LatitudeFormatter())
ax0_inset.set_xticks(np.arange(llon, ulon, 5), minor=False)
ax0_inset.set_xticks(np.arange(llon, ulon, 1), minor=True)
ax0_inset.set_xticklabels(np.arange(llon, ulon, 5),
fontsize=8,
rotation=45)
ax0_inset.xaxis.set_major_formatter(LongitudeFormatter())
ax0_inset.grid()
ax0_inset.tick_params(which="both",
axis="both",
direction="in",
labelsize=8)
fig.show()
Is there a way to change the background color of ax0_inset so that these tick labels are legible? I tried changing the face_color to "w", but that did not work. Ideally, I want the same behavior as ax0.figure.set_facecolor("w"), but for the ax0_inset axis. Is this doable?
Following #Mr. T's comment suggestion, a work-around solution could be:
# insert transparent (or opaque) rectangle around inset_axes plot
# to make axes labels more visible
# make buffer variable to control amount of buffer around inset_axes
buffer = 0.1 # fractional axes coordinates
# use ax inset tuple coords in loc_box to add rectangle patch
# [left, bottom, width, height] (fractional axes coordinates)
fig.add_patch(plt.Rectangle((
loc_box[0]-buffer, loc_box[1]-buffer),
loc_box[2]+buffer, loc_box[3]+buffer,
linestyle="-", edgecolor="k", facecolor="w",
linewidth=1, alpha=0.75, zorder=5,
transform=ax0.transAxes))
Similar to this post, I would like to transform my data coordinates to figure coordinates. Unfortunately, the transformation tutorial doesn't seem to talk about it. So I came up with something analogous to the answer by wilywampa, but for some reason, there is something wrong and I can't figure it out:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
t = [
0, 6.297, 39.988, 46.288, 79.989, 86.298, 120.005, 126.314, 159.994,
166.295, 200.012, 206.314, 240.005, 246.301, 280.05, 286.35, 320.032,
326.336, 360.045, 366.345, 480.971, 493.146, 1080.117, 1093.154, 1681.019,
1692.266, 2281.008, 2293.146, 2881.014, 2893.178, 3480.988, 3493.149,
4080.077, 4092.298, 4681.007, 4693.275, 5281.003, 5293.183, 5881.023,
5893.188, 6481.002, 6492.31
]
y = np.zeros(len(t))
fig, (axA, axB) = plt.subplots(2, 1)
fig.tight_layout()
for ax in (axA, axB):
ax.set_frame_on(False)
ax.axes.get_yaxis().set_visible(False)
axA.plot(t[:22], y[:22], c='black')
axA.plot(t[:22], y[:22], 'o', c='#ff4500')
axA.set_ylim((-0.05, 1))
axB.plot(t, y, c='black')
axB.plot(t, y, 'o', c='#ff4500')
axB.set_ylim((-0.05, 1))
pos1 = axB.get_position()
pos2 = [pos1.x0, pos1.y0 + 0.3, pos1.width, pos1.height]
axB.set_position(pos2)
trans = [
# (ax.transAxes + ax.transData.inverted()).inverted().transform for ax in
(fig.transFigure + ax.transData.inverted()).inverted().transform for ax in
(axA, axB)
]
con1 = ConnectionPatch(
xyA=trans[0]((0, 0)), xyB=(0, 0.1), coordsA="figure fraction",
coordsB="data", axesA=axA, axesB=axB, color="black"
)
con2 = ConnectionPatch(
xyA=(500, 0), xyB=(500, 0.1), coordsA="data", coordsB="data",
axesA=axA, axesB=axB, color="black"
)
print(trans[0]((0, 0)))
axB.add_artist(con1)
axB.add_artist(con2)
plt.show()
The line on the left is supposed to go to (0, 0) of the upper axis, but it doesn't. The same happens btw if I try to convert to axes coordinates, so there seems be to something fundamentally wrong.
The reason why I want to use figure coords is because I don't actually want the line to end at (0, 0), but slightly below the '0' tick label. I cannot do that in data coords so I tried to swap to figure coods.
Adapting the second example from this tutorial code, it seems no special combinations of transforms is needed. You can use coordsA=axA.get_xaxis_transform(), if x is in data coordinates and y in figure coordinates. Or coordsA=axA.transData if x and y are both in data coordinates. Note that when using data coordinates you are allowed to give coordinates outside the view window; by default a ConnectionPatch isn't clipped.
The following code uses z-order to put the connection lines behind the rest and adds a semi-transparent background to the tick labels of axA (avoiding that the text gets crossed out by the connection line):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
t = [0, 6.297, 39.988, 46.288, 79.989, 86.298, 120.005, 126.314, 159.994, 166.295, 200.012, 206.314, 240.005, 246.301, 280.05, 286.35, 320.032, 326.336, 360.045, 366.345, 480.971, 493.146, 1080.117, 1093.154, 1681.019, 1692.266, 2281.008, 2293.146, 2881.014, 2893.178, 3480.988, 3493.149, 4080.077, 4092.298, 4681.007, 4693.275, 5281.003, 5293.183, 5881.023, 5893.188, 6481.002, 6492.31]
y = np.zeros(len(t))
fig, (axA, axB) = plt.subplots(2, 1)
fig.tight_layout()
for ax in (axA, axB):
ax.set_frame_on(False)
ax.axes.get_yaxis().set_visible(False)
axA.plot(t[:22], y[:22], c='black')
axA.plot(t[:22], y[:22], 'o', c='#ff4500')
axA.set_ylim((-0.05, 1))
axB.plot(t, y, c='black')
axB.plot(t, y, 'o', c='#ff4500')
axB.set_ylim((-0.05, 1))
pos1 = axB.get_position()
pos2 = [pos1.x0, pos1.y0 + 0.3, pos1.width, pos1.height]
axB.set_position(pos2)
con1 = ConnectionPatch(xyA=(0, 0.02), coordsA=axA.get_xaxis_transform(),
xyB=(0, 0.05), coordsB=axB.get_xaxis_transform(),
# linestyle='--', color='black', zorder=-1)
linestyle='--', color='darkgrey', zorder=-1)
con2 = ConnectionPatch(xyA=(500, 0.02), coordsA=axA.get_xaxis_transform(),
xyB=(500, 0.05), coordsB=axB.get_xaxis_transform(),
linestyle='--', color='darkgrey', zorder=-1)
fig.add_artist(con1)
fig.add_artist(con2)
for lbl in axA.get_xticklabels():
lbl.set_backgroundcolor((1, 1, 1, 0.8))
plt.show()
Possible answer to your last comment:
As you're dealing with figure coords, these can change depending on your screen resolution. So if your other machine has a different res then this could be why its changing. You'll have to look into using Axes coords instead if you don't want these random changes.
I've seen numerous examples of 3D plots using matplotlib/seaborn in Python but can't seem to get what I'm looking for; I have 50 or so timeseries that I would like to plot cleanly as in the following example below but with the name of the series on the axis; as an example I've marked in Goog, IBM, GE, Pepsi etc. Appreciate any pointers or examples. Thank you,
Example PLOT Click Here Please
Matplotlib has very rich gallery. I found this, you can only plot it once instead of animation. And manually put y-axis legend wherever you want.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Fixing random state for reproducibility
np.random.seed(19680801)
# Create new Figure with black background
fig = plt.figure(figsize=(12, 8))
# Add a subplot with no frame
ax = plt.subplot(111, frameon=False)
# Generate random data
data = np.random.uniform(0, 1, (64, 75))
X = np.linspace(-1, 1, data.shape[-1])
G = 1.5 * np.exp(-4 * X ** 2)
# Generate line plots
lines = []
for i in range(len(data)):
# Small reduction of the X extents to get a cheap perspective effect
xscale = 1 - i / 200.
# Same for linewidth (thicker strokes on bottom)
lw = 1.5 - i / 100.0
line, = ax.plot(xscale * X, i + G * data[i], color="b", lw=lw)
lines.append(line)
# Set y limit (or first line is cropped because of thickness)
ax.set_ylim(-1, 70)
# No ticks
ax.set_xticks([])
ax.set_yticks([])
# 2 part titles to get different font weights
ax.text(0.5, 1.0, "MATPLOTLIB ", transform=ax.transAxes,
ha="right", va="bottom", color="k",
family="sans-serif", fontweight="light", fontsize=16)
ax.text(0.5, 1.0, "UNCHAINED", transform=ax.transAxes,
ha="left", va="bottom", color="k",
family="sans-serif", fontweight="bold", fontsize=16)
def update(*args):
# Shift all data to the right
data[:, 1:] = data[:, :-1]
# Fill-in new values
data[:, 0] = np.random.uniform(0, 1, len(data))
# Update data
for i in range(len(data)):
lines[i].set_ydata(i + G * data[i])
# Return modified artists
return lines
# Construct the animation, using the update function as the animation director.
anim = animation.FuncAnimation(fig, update, interval=10)
plt.show()
When plotting several y axis in Matplotlib, is there a way to specify how to align the origin (and/or some ytick labels) of the right axis with a specific value of the left axis?
Here is my problem: I would like to plot two set of data as well as their difference (basically, I am trying to reproduce this kind of graph).
I can reproduce it, but I have to manually adjust the ylim of the right axis so that the origin is aligned with the value I want from the left axis.
I putted below an example of a simplified version of the code I use. As you can see, I have to manually adjust scale of the right axis to align the origin of the right axis as well as the square.
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
grp1 = np.array([1.202, 1.477, 1.223, 1.284, 1.701, 1.724, 1.099,
1.242, 1.099, 1.217, 1.291, 1.305, 1.333, 1.246])
grp2 = np.array([1.802, 2.399, 2.559, 2.286, 2.460, 2.511, 2.296,
1.975])
fig = plt.figure(figsize=(6, 6))
ax = fig.add_axes([0.17, 0.13, 0.6, 0.7])
# remove top and right spines and turn ticks off if no spine
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.spines['bottom'].set_color('none')
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('left')
# postition of tick out
ax.tick_params(axis='both', direction='out', width=3, length=7,
labelsize=24, pad=8)
ax.spines['left'].set_linewidth(3)
# plot groups vs random numbers to create dot plot
ax.plot(np.random.normal(1, 0.05, grp2.size), grp2, 'ok', markersize=10)
ax.plot(np.random.normal(2, 0.05, grp1.size), grp1, 'ok', markersize=10)
ax.errorbar(1, np.mean(grp2), fmt='_r', markersize=50,
markeredgewidth=3)
ax.errorbar(2, np.mean(grp1), fmt='_r', markersize=50,
markeredgewidth=3)
ax.set_xlim((0.5, 3.5))
ax.set_ylim((0, 2.7))
# create right axis
ax2 = fig.add_axes(ax.get_position(), sharex=ax, frameon=False)
ax2.spines['left'].set_color('none')
ax2.spines['top'].set_color('none')
ax2.spines['bottom'].set_color('none')
ax2.xaxis.set_ticks_position('none')
ax2.yaxis.set_ticks_position('right')
# postition of tick out
ax2.tick_params(axis='both', direction='out', width=3, length=7,
labelsize=24, pad=8)
ax2.spines['right'].set_linewidth(3)
ax2.set_xticks([1, 2, 3])
ax2.set_xticklabels(('gr2', 'gr1', 'D'))
ax2.hlines(0, 0.5, 3.5, linestyle='dotted')
#ax2.hlines((np.mean(adult)-np.mean(nrvm)), 0, 3.5, linestyle='dotted')
ax2.plot(3, (np.mean(grp1)-np.mean(grp2)), 'sk', markersize=12)
# manual adjustment so the origin is aligned width left group2
ax2.set_ylim((-2.3, 0.42))
ax2.set_xlim((0.5, 3.5))
plt.show()
You can make a little function that calculates the alignment of ax2:
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
by using align_yaxis(), you can align the axes quickly:
#...... your code
# adjustment so the origin is aligned width left group2
ax2.set_ylim((0, 2.7))
align_yaxis(ax, np.mean(grp2), ax2, 0)
plt.show()
The above answer is Okay, but sometimes cuts out data, it is more thoroughly answered in the second answer here,
Matplotlib axis with two scales shared origin
or with a quick hack
def align_yaxis(ax1, v1, ax2, v2, y2min, y2max):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1."""
"""where y2max is the maximum value in your secondary plot. I haven't
had a problem with minimum values being cut, so haven't set this. This
approach doesn't necessarily make for axis limits at nice near units,
but does optimist plot space"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
scale = 1
while scale*(maxy+dy) < y2max:
scale += 0.05
ax2.set_ylim(scale*(miny+dy), scale*(maxy+dy))