I've been trying to make my annotations avoid the plot area and get drawn completely outside, using AdjustText so they don't overlap. I haven't found any solution or hidden parameter in the matplotlib or AdjustText docs so far to do this. What am I missing? All I found were ways to clip the annotations
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
from adjustText import adjust_text
x_axis1 = [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 5.5, 6.0, 10.5, 15.0, 15.5]
y_axis1 = [60.0, 80.0, 70.0, 60.0, 70.0, 50.0, 80.0, 100.0, 80.0, 60.0, 50.0]
x_axis2 = [0.0, 0.3, 0.6, 0.9]
y_axis2_labels = ['First Station', 'Second Station', 'Third Station', 'Last station']
max_y = max(y_axis1)
fig, ax = plt.subplots()
ax.set_xlabel("Distance [km]")
ax.set_ylabel("Speed [km/h]")
l0, = ax.step(x_axis1, y_axis1, label="Speed", where="post")
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
ax2.set_label('Stations')
plt.xticks([])
ax2.tick_params(
axis="x",
which='major',
direction="in",
width=1.5,
length=7,
labelsize=10,
color="red",
)
for x in x_axis2:
ax2.axvline(x, color='red', ls=':', lw=1.5)
# -------------------
y_axis2 = [max(ax.get_ylim()) for i in range(len(x_axis2))]
texts = [
plt.text(
x_axis2[i],
y_axis2[i],
y_axis2_labels[i],
ha='center',
va='center',
# annotation_clip=False,
rotation=20,
clip_on=False
) for i in range(len(x_axis2))
]
adjust_text(
texts,
[ax.get_xlim()],
[ax.get_ylim()],
arrowprops=dict(
arrowstyle='->',
connectionstyle="arc,angleA=-90,angleB=0,armA=30,armB=30,rad=5",
color='red'
),
)
# -------------------
lines = [l0, ax2]
rax = plt.axes([0, 0, 0.12, 0.1])
labels = [str(line.get_label()) for line in lines]
visibility = [line.get_visible() for line in lines]
check = CheckButtons(rax, labels, visibility)
def func(label):
index = labels.index(label)
lines[index].set_visible(not lines[index].get_visible())
plt.draw()
check.on_clicked(func)
fig.tight_layout()
plt.show()
This is the current output I get:
Unfortunately AdjustText moves everything inside the limits by default. I ended up adjusting the code in Matplotlib overlapping annotations / text and messing with some obscure matplotlib parameters:
import matplotlib.pyplot as plt
import numpy as np
def get_text_positions(x_data, y_data, txt_width, txt_height):
"""Get plot tick labels to check for collision."""
a = list(zip(y_data, x_data))
text_positions = y_data.copy()
for index, (y, x) in reversed(list(enumerate(a))):
local_text_positions = [
i for i in a
if i[0] > (y - txt_height) and (abs(i[1] - x) < txt_width * 2) and i != (y, x)
]
if local_text_positions:
sorted_ltp = sorted(local_text_positions)
if abs(sorted_ltp[0][0] - y) < txt_height: #True == collision
differ = np.diff(sorted_ltp, axis=0)
a[index] = (sorted_ltp[-1][0] + txt_height, a[index][1])
text_positions[index] = sorted_ltp[-1][0] + txt_height * 2
for k, (j, m) in enumerate(differ):
#j is the vertical distance between words
if j > txt_height * 2: #if True then room to fit a word in
a[index] = (sorted_ltp[k][0] + txt_height, a[index][1])
text_positions[index] = sorted_ltp[k][0] + txt_height
break
return text_positions
def text_plotter(x_data, y_data, y_heigth, text_positions, axis, txt_width, txt_height):
"""Changes label text location and adds arrow if there's a collision."""
for x, y, h, t in list(zip(x_data, y_data, y_heigth, text_positions)):
axis.text(
x - txt_width,
1.02 * t,
str(y),
rotation=70,
color='blue',
clip_on=False,
)
if h != t:
axis.arrow(
x,
t,
0,
h - t,
color='black',
alpha=0.2,
width=txt_width * 0.1,
head_width=txt_width / 2,
head_length=txt_height * 0.3,
zorder=0,
length_includes_head=True,
clip_on=False,
)
x_axis1 = [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 5.5, 6.0, 10.5, 15.0, 15.5]
y_axis1 = [60.0, 80.0, 70.0, 60.0, 70.0, 50.0, 80.0, 100.0, 80.0, 60.0, 50.0]
x_axis2 = [0, 0.2, 0.3, 1.2, 1.5, 1.8, 2, 3, 4, 5, 5.5, 6, 7, 7.5, 8, 9, 10, 13, 15]
y_axis2 = [
'Station 1', 'Station 2', 'Station 3', 'Station 4', 'Station 5', 'Station 6', 'Station 7',
'Station 8', 'Station 9', 'Station 10', 'Station 11', 'Station 12', 'Station 13', 'Station 14',
'Station 15', 'Station 16', 'Station 17', 'Station 18', 'Station 19'
]
fig, ax = plt.subplots()
ax.set_xlabel("Distance [km]")
ax.set_ylabel("Speed [km/h]")
l0, = ax.step(x_axis1, y_axis1, label="Speed", where="post")
ax2 = ax.twiny()
ax2.set_xlim(ax.get_xlim())
ax2.set_label('Stations')
txt_height = 0.35 * (plt.ylim()[1] - plt.ylim()[0])
txt_width = 0.01 * (plt.xlim()[1] - plt.xlim()[0])
y_height = [max(ax.get_ylim())] * len(x_axis2) # labels on spines
x_data = [i / max(x_axis1) for i in x_axis1]
text_positions = get_text_positions(x_axis2, y_height, txt_width, txt_height)
text_plotter(x_axis2, y_axis2, y_height, text_positions, ax, txt_width, txt_height)
plt.ylim(0, max(y_height)) #+ 2 * txt_height
plt.xticks([])
plt.subplots_adjust(top=0.5) # manual adjustment
plt.show()
which outputs:
Related
I am creating subplots in matplotlib but not all xticks and yticks are being displayed. I have tried everything from setting xlim and ylim, chainging figure size etc. The thing is this is a handson on hackerrnak and they are evaluating my output against their expected output. The 0.0 in xaxis and 1.0 on yaxis are simply not matching up. What am I doing wrong here.
Here is the code,
import matplotlib.pyplot as plt
import numpy as np
def test_generate_figure2():
np.random.seed(1000)
x = np.random.rand(10)
y = np.random.rand(10)
z = np.sqrt(x**2 + y**2)
fig = plt.figure(figsize=(8,6))
axes1 = plt.subplot(2, 2, 1, title="Scatter plot with Upper Triangle Markers")
axes1.set_xticks([0.0, 0.4, 0.8, 1.2])
axes1.set_yticks([-0.2, 0.2, 0.6, 1.0])
axes1.set_ylim(-0.2,1.0) #Doing this still doesnot get the expected output
axes1.set_xlim(0.0,1.2)
print(axes1.get_yticks())
axes1.scatter(x, y, marker="^", s=80, c=z)
axes2 = plt.subplot(2, 2, 2, title="Scatter plot with Plus Markers")
axes2.set_xticks([0.0, 0.4, 0.8, 1.2])
axes2.set_yticks([-0.2, 0.2, 0.6, 1.0])
axes2.scatter(x, y, marker="+", s=80, c=z)
axes3 = plt.subplot(2, 2, 3, title="Scatter plot with Circle Markers")
axes3.set_xticks([0.0, 0.4, 0.8, 1.2])
axes3.set_yticks([-0.2, 0.2, 0.6, 1.0])
axes3.scatter(x, y, marker="o", s=80, c=z)
axes4 = plt.subplot(2, 2, 4, title="Scatter plot with Diamond Markers")
axes4.set_xticks([0.0, 0.4, 0.8, 1.2])
axes4.set_yticks([-0.2, 0.2, 0.6, 1.0])
axes4.scatter(x, y, marker="d", s=80,c=z)
plt.tight_layout()
plt.show()
test_generate_figure2()
My Output,
Expected Output,
Your set_xlim & set_ylim approach works. You just need to set it for every subplot:
https://akuiper.com/console/5vaLIq0ZC_KO
import matplotlib.pyplot as plt
import numpy as np
def test_generate_figure2():
np.random.seed(1000)
x = np.random.rand(10)
y = np.random.rand(10)
z = np.sqrt(x**2 + y**2)
fig = plt.figure(figsize=(8,6))
axes1 = plt.subplot(2, 2, 1, title="Scatter plot with Upper Triangle Markers")
axes1.set_xticks([0.0, 0.4, 0.8, 1.2])
axes1.set_yticks([-0.2, 0.2, 0.6, 1.0])
axes1.set_ylim(-0.2,1.0) #Doing this still doesnot get the expected output
axes1.set_xlim(0.0,1.2)
print(axes1.get_yticks())
axes1.scatter(x, y, marker="^", s=80, c=z)
axes2 = plt.subplot(2, 2, 2, title="Scatter plot with Plus Markers")
axes2.set_xticks([0.0, 0.4, 0.8, 1.2])
axes2.set_yticks([-0.2, 0.2, 0.6, 1.0])
axes2.set_ylim(-0.2,1.0) #Doing this still doesnot get the expected output
axes2.set_xlim(0.0,1.2)
axes2.scatter(x, y, marker="+", s=80, c=z)
axes3 = plt.subplot(2, 2, 3, title="Scatter plot with Circle Markers")
axes3.set_xticks([0.0, 0.4, 0.8, 1.2])
axes3.set_yticks([-0.2, 0.2, 0.6, 1.0])
axes3.set_ylim(-0.2,1.0) #Doing this still doesnot get the expected output
axes3.set_xlim(0.0,1.2)
axes3.scatter(x, y, marker="o", s=80, c=z)
axes4 = plt.subplot(2, 2, 4, title="Scatter plot with Diamond Markers")
axes4.set_xticks([0.0, 0.4, 0.8, 1.2])
axes4.set_yticks([-0.2, 0.2, 0.6, 1.0])
axes4.set_ylim(-0.2,1.0) #Doing this still doesnot get the expected output
axes4.set_xlim(0.0,1.2)
axes4.scatter(x, y, marker="d", s=80,c=z)
plt.tight_layout()
plt.show()
test_generate_figure2()
This is a follow up to my question posted here. A network diagram is added as an inset in matplotlib figure.
import networkx as nx
import matplotlib.pyplot as plt
G = nx.gnm_random_graph(n=10, m=15, seed=1)
nxpos = nx.spring_layout(G, dim=3, seed=1)
nxpts = [nxpos[pt] for pt in sorted(nxpos)]
nx_lines = [(nxpts[i], nxpts[j]) for i, j in G.edges()]
# node values
values = [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[30, 80, 10, 79, 70, 60, 75, 78, 65, 10],
[1, .30, .10, .79, .70, .60, .75, .78, .65, .90]]
time = [0.0, 0.1, 0.2] # in seconds
fig, ax = plt.subplots()
ax.plot(
[1, 2, 3], [1, 2, 3],
'go-',
label='line 1',
linewidth=2
)
from mpl_toolkits.mplot3d import (Axes3D)
from matplotlib.transforms import Bbox
rect = [.6, 0, .5, .5]
bbox = Bbox.from_bounds(*rect)
inax = fig.add_axes(bbox, projection = '3d')
# inax.axis('off')
# set angle
angle = 25
inax.view_init(10, angle)
# hide axes, make transparent
# inax.set_facecolor('none')
inax.grid('off')
import numpy as np
# plot 3d
seen = set()
for i, j in G.edges():
x = np.stack((nxpos[i], nxpos[j]))
inax.plot(*x.T, color = 'k')
if i not in seen:
inax.scatter(*x[0], color = 'skyblue')
seen.add(i)
if j not in seen:
inax.scatter(*x[1], color = "skyblue")
seen.add(j)
fig.show()
I would like to change the grid properties i.e set the grid color to red and change line width. I tried inax.grid('on', color='r') but this doesn't change the color. Suggestions on how to change the settings will be really helpful.
You can do it like this:
inax.w_xaxis._axinfo.update({'grid' : {'color': 'red', 'linewidth': 0.8, 'linestyle': '-'}})
inax.w_yaxis._axinfo.update({'grid' : {'color': 'red', 'linewidth': 0.8, 'linestyle': '-'}})
inax.w_zaxis._axinfo.update({'grid' : {'color': 'red', 'linewidth': 0.8, 'linestyle': '-'}})
Output:
I have some 2D data with x and y coordinates both within [0,1], plotted using pcolormesh.
Now I want to symmetrize the plot to [-0.5, 0.5] for both x and y coordinates. In Matlab I was able to achieve this by changing x and y from e.g. [0, 0.2, 0.4, 0.6, 0.8] to [0, 0.2, 0.4, -0.4, -0.2], without rearranging the data. However, with pcolormesh I cannot get the desired result.
A minimum example is shown below, with data represented simply by x+y:
import matplotlib.pyplot as plt
import numpy as np
x,y = np.mgrid[0:1:5j,0:1:5j]
fig,(ax1,ax2,ax3) = plt.subplots(1,3,figsize=(9,3.3),constrained_layout=1)
# original plot spanning [0,1]
img1 = ax1.pcolormesh(x,y,x+y,shading='auto')
# shift x and y from [0,1] to [-0.5,0.5]
x = x*(x<0.5)+(x-1)*(x>0.5)
y = y*(y<0.5)+(y-1)*(y>0.5)
img2 = ax2.pcolormesh(x,y,x+y,shading='auto') # similar code works in Matlab
# for this specific case, the following is close to the desired result, I can just rename x and y tick labels
# to [-0.5,0.5], but in general data is not simply x+y
img3 = ax3.pcolormesh(x+y,shading='auto')
fig.colorbar(img1,ax=[ax1,ax2,ax3],orientation='horizontal')
The corresponding figure is below, any suggestion on what is missed would be appreciated!
Let's look at what you want to achieve in a 1D example.
You have x values between 0 and 1 and a dummy function f(x) = 20*x to produce some values.
# x = [0, .2, .4, .6, .8] -> [0, .2, .4, -.4, -.2] -> [-.4, .2, .0, .2, .4])
# fx = [0, 4, 8, 12, 16] -> [0, 4, 8, 12, 16] -> [ 12, 16, 0, 4, 8]
# ^ only flip and shift x not fx ^
You could use np.roll() to achieve the last operation.
I used n=14 to make the result better visible and show that this approach works for arbitrary n.
import numpy as np
import matplotlib.pyplot as plt
n = 14
x, y = np.meshgrid(np.linspace(0, 1, n, endpoint=False),
np.linspace(0, 1, n, endpoint=False))
z = x + y
x_sym = x*(x <= .5)+(x-1)*(x > .5)
# array([[ 0. , 0.2, 0.4, -0.4, -0.2], ...
x_sym = np.roll(x_sym, n//2, axis=(0, 1))
# array([[-0.4, -0.2, 0. , 0.2, 0.4], ...
y_sym = y*(y <= .5)+(y-1)*(y > .5)
y_sym = np.roll(y_sym, n//2, axis=(0, 1))
z_sym = np.roll(z, n//2, axis=(0, 1))
# array([[1.2, 1.4, 0.6, 0.8, 1. ],
# [1.4, 1.6, 0.8, 1. , 1.2],
# [0.6, 0.8, 0. , 0.2, 0.4],
# [0.8, 1. , 0.2, 0.4, 0.6],
# [1. , 1.2, 0.4, 0.6, 0.8]])
fig, (ax1, ax2) = plt.subplots(1, 2)
img1 = ax1.imshow(z, origin='lower', extent=(.0, 1., .0, 1.))
img2 = ax2.imshow(z_sym, origin='lower', extent=(-.5, .5, -.5, .5))
I have the following pandas dataframe:
My goal is to plot the dataframe in 3 columns, where each column is a 'section'. And, at the same time, each plot is a subplot of 3 lines and 1 column, where one line is 'Col1 [%]', second line is 'Col 2' and last is 'Col 3 [%]'
If I set subplots=True, I obtain the following plot:
Else, with subplots=False, I obtain:
But what I need is to obtain the 3 columns, but where each column plot will be equals to the graph with suplots=True. How can I do that?
Thanks a lot in advance!
My code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
# DATA
dfplot = pd.DataFrame(columns = ['section', 'description', 'Col1 [%]', 'Col 2', 'Col 3 [%]'])
dfplot['description'] = ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9']
dfplot['section'] = [1, 1, 1, 2, 2, 2, 3, 3, 3]
dfplot['Col1 [%]'] = [82, 89, 86, 100, 100, 99, 16, 16, 16]
dfplot['Col 2'] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
dfplot['Col 3 [%]'] = [99.19, 98.7, 99.36, 99.9, 99.93, 99.5, 97.66, 97.84, 97.66]
dfplot = dfplot.groupby(['section', 'description'], as_index=True).last()
# PLOT -------------
# Set levels to group labels in ax X
cols = list(set(l_columns_values))
dfplot.index.set_levels([cols, l_strains], level=[0,1])
fig, axes = plt.subplots(nrows=1, ncols=len(cols),
sharey=True, sharex=True,
figsize=(14 / 2.54, 10 / 2.54) # width, height
)
for i, col in enumerate(list(set(l_contigs))):
ax = axes[i] #, j]
print(ax)
print("i= {}, col= {}".format(i, col))
dfplot.loc[col].plot.area(ax=ax,
#layout=(3, 1),
stacked=True,
subplots=True, ## <--
grid=True,
table=False,
sharex=True,
sharey=True,
figsize=(20,7),
fontsize=12,
#xticks = np.arange(0, len(cols)+1, 1)
)
#ax[i].set_ylim(-1,100)
ax.set_xlabel(col, weight='bold', fontsize=20)
ax.set_axisbelow(True)
for tick in ax.get_xticklabels():
tick.set_rotation(90)
# make the ticklines invisible
ax.tick_params(axis=u'both', which=u'both', length=0)
plt.tight_layout()
# remove spacing in between
fig.subplots_adjust(wspace=0.5) # space between plots
# legend
plt.legend(loc='upper right')
# Add title
fig.suptitle('My title')
plt.show()
A bit of interpretation - a graph for each column and section.
There was an issue in your code - you were overwriting ax array with a reference to it. I've used a different variable name: axt
dfplot = pd.DataFrame(columns = ['section', 'description', 'Col1 [%]', 'Col 2', 'Col 3 [%]'])
dfplot['description'] = ['d1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9']
dfplot['section'] = [1, 1, 1, 2, 2, 2, 3, 3, 3]
dfplot['Col1 [%]'] = [82, 89, 86, 100, 100, 99, 16, 16, 16]
dfplot['Col 2'] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
dfplot['Col 3 [%]'] = [99.19, 98.7, 99.36, 99.9, 99.93, 99.5, 97.66, 97.84, 97.66]
# dfplot = dfplot.groupby(['section', 'description'], as_index=True).last()
dfplot = dfplot.set_index(["section", "description"])
fig, ax = plt.subplots(len(dfplot.index.get_level_values(0).unique()),len(dfplot.columns), figsize=[20,5],
sharey=True, sharex=False)
# Add title
fig.suptitle('My title')
for i,v in enumerate(dfplot.index.get_level_values(0).unique()):
for j, c in enumerate(dfplot.columns):
axt = ax[j][i]
dfplot.loc[(v),[c]].plot.area(ax=axt, stacked=True)
axt.set_xlabel(f"Section {v}", weight='bold', fontsize=20)
axt.set_axisbelow(True)
# make the ticklines invisible
axt.tick_params(axis=u'both', which=u'both', length=0)
axt.legend(loc='upper right')
for tick in axt.get_xticklabels():
tick.set_rotation(90)
output
Would you please help
I have this data where z is a function for specific x and y
xs = [0.15, 0.35, 0.5, 0.67, 0.8]
ys = [0.01,0.01, 0.01, 0.01, 0.01]
z = [0.75, 0.83, 1.00, 0.92, 0.91]
I arranged the values in this shape
How can I do interpolation for the points so I can call z value later different than the the one I have?
A simple search would have helped already.
Your problem is basically the example of the
scipy.interpolate.interp2d documentation.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy import interpolate
import numpy as np
xs = [ 0.15, 0.35, 0.5, 0.67, 0.8 ]
ys = [ 0.01, 0.05, 0.1, 0.2, 0.3 ]
zz = np.array( [
0.75, 0.83, 1.00, 0.92, 0.91,
0.75, 0.82, 0.87, 0.88, 0.88,
0.74, 0.81, 0.84, 0.83, 0.83,
0.72, 0.76, 0.77, 0.76, 0.76,
0.72, 0.72, 0.72, 0.72, 0.72
] ).reshape( ( 5, 5 ) )
xx, yy = np.meshgrid( xs, ys )
f = interpolate.interp2d( xx, yy, zz, kind='cubic' )
fig = plt.figure()
ax = fig.add_subplot( 1, 1, 1, projection='3d' )
ax.plot_surface( xx, yy, zz)
x2 = np.linspace( .15,.8,50 )
y2 = np.linspace( .01,.3,50 )
xx2, yy2 = np.meshgrid( x2, y2 )
zz2 = f( x2, y2 )
fig2 = plt.figure()
bx = fig2.add_subplot( 1, 1, 1, projection='3d' )
bx.plot_surface( xx2, yy2, zz2 )
plt.show()
providing the original data
and the cubic interpolation on a 50 by 50 grid
tck = interpolate.bisplrep(x, y, z, s=0)
def givemeZ(x,y):
return interpolate.bisplev(x,y,tck)
Now by running the code, it will give z for specific x and y.
This can be used without plot. just put it under the values and make sure that the values are arranged in the same way