how to plot figure with arrows - python

How can I add arrows on x and y axes, show only the values that are used and add label for coordinates
My code on this moment:
x = [9, 8, 11, 11, 14, 13, 16, 14, 14]
y = [9, 16, 15, 11, 10, 11, 10, 8, 8]
fig = plt.figure(figsize=(7,7), dpi=300)
axes = fig.add_axes([0,1,1,1])
axes.set_xlim(0, 17)
axes.set_ylim(0, 17)
axes.invert_yaxis()
axes.scatter(x, y, color='green')
axes.vlines(x, 0, y, linestyle="dashed", color='green')
axes.hlines(y, 0, x, linestyle="dashed", color='green')
axes.spines.right.set_visible(False)
axes.spines.bottom.set_visible(False)
plt.show()
Visually:
enter image description here
And plot that I want to realize
enter image description here

You can draw arrows by overlaying triangle shaped points over the ends of your spines.
You'll need to leverage some transforms, but you can also create your labels by manually adding text to your Axes objects as well.
Labelling each coordinate can be done via axes.annotate, but you'll need to manually specify the location of each annotation to ensure they don't overlap with lines or other annotations.
import matplotlib.pyplot as plt
from matplotlib.ticker import FixedLocator
x = [9, 8, 11, 11, 14, 13, 16, 14, 14]
y = [9, 16, 15, 11, 10, 11, 10, 8, 8]
fig = plt.figure(figsize=(7,7), dpi=300)
axes = fig.add_axes([.05,.05,.9,.9])
# Plots the data
axes.scatter(x, y, color='green')
axes.vlines(x, 0, y, linestyle="dashed", color='green')
axes.hlines(y, 0, x, linestyle="dashed", color='green')
axes.set_xlim(0, 17)
axes.set_ylim(0, 17)
axes.set_xticks(x)
axes.set_yticks(y)
axes.invert_yaxis()
# Move ticks to top side of plot
axes.xaxis.set_tick_params(
length=0, bottom=False, labelbottom=False, top=True, labeltop=True
)
axes.xaxis.set_tick_params(length=0)
# Add arrows to the spines by drawing triangle shaped points over them
axes.plot(1, 1, '>k', transform=axes.transAxes, clip_on=False)
axes.plot(0, 0, 'vk', transform=axes.transAxes, clip_on=False)
axes.spines[['bottom', 'right']].set_visible(False)
# Add labels for 0, F_1 and F_2
from matplotlib.transforms import offset_copy
axes.text(
0, 1, s='0', fontstyle='italic', ha='right', va='bottom',
transform=offset_copy(axes.transAxes, x=-5, y=5, fig=fig, units='points'),
)
axes.text(
1, 1, s='$F_1$', fontstyle='italic', ha='right', va='bottom',
transform=offset_copy(axes.transAxes, x=0, y=5, fig=fig, units='points'),
)
axes.text(
0, 0, s='$F_2$', fontstyle='italic', ha='right',
transform=offset_copy(axes.transAxes, x=-5, y=0, fig=fig, units='points'),
)
# Add labels at each point. Leveraging the alignment of the text
# AND padded offset.
lc = ('top', 'center', 0, -5)
ll = ('top', 'right', -5, -5)
lr = ('top', 'left', 5, -5)
ur = ('bottom', 'left', 5, 5)
alignments = [lc, lc, lc, ll, lc, ll, lc, ur, lr]
for i, (xc, yc, (va, ha, padx, pady)) in enumerate(zip(x, y, alignments)):
axes.annotate(
xy=(xc, yc), xytext=(padx, pady),
text=f'$F(x_{i})$', ha=ha, va=va, textcoords='offset points')
plt.show()

Adding
axes.plot(1, 0, ">k", transform=axes.get_yaxis_transform(), clip_on=False)
axes.plot(0, 0, "vk", transform=axes.get_xaxis_transform(), clip_on=False)
Will do it for you. This is basically just a cheat plot of markers.
There are also
from mpl_toolkits.axisartist.axislines.AxesZero
which allow
for direction in ["xzero", "yzero"]:
# adds arrows at the ends of each axis
ax.axis[direction].set_axisline_style("-|>")
but they can't handle the reversed y axis in your case with default settings.

Related

How to convert grouped bar chart from vertical to horizontal

How can this vertical grouped bar chart be changed to a horizontal bar chart (grouped, and stacked)? I need help to alter the code such that the bars are displayed horizontally instead of vertically.
import matplotlib.pyplot as plt
import numpy as np
N = 9
labels = ['L', 'S', 'S', 'M', 'W', 'W', 'S', 'R', 'C']
M_means = [1, 45, 28, 11, 4, 7, 1, 0.02, 0.3]
PO_means = [3, 58, 17, 8, 3, 8, 1, 0.06, 1]
K_means = [1, 44, 30, 11, 3, 7, 1, 0.01, 0.5]
x = np.arange(len(labels)) # the label locations
width = 0.30 # the width of the bars
fig, ax = plt.subplots(figsize=(15, 9))
rects1 = ax.bar(x - width, M_means, width, label='M S and K', color=('#b02a2a'))
rects2 = ax.bar(x, PO_means, width, label='P O S and K', color=('#055cad'))
rects3 = ax.bar(x + width, K_means, width, label='M K', color=('#0b7d53'))
# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('% of workday', fontsize=32)
#ax.set_title('Scores by group and gender')
ax.set_xticks(x)
ax.set_xticklabels(labels, fontsize=32, rotation=15)
ax.legend(loc='upper right', frameon=False, fontsize=32, markerscale=2)
ax.bar_label(rects1, size = 32, padding=20, rotation=90)
ax.bar_label(rects2, size = 32, padding=20, rotation=90)
ax.bar_label(rects3, size = 32, padding=20, rotation=90)
plt.xticks(ha='center')
for tick in ax.xaxis.get_major_ticks():
tick.label.set_fontsize(32)
for tick in ax.yaxis.get_major_ticks():
tick.label.set_fontsize(32)
plt.ylim(0, 100)
plt.gca().spines['right'].set_color('none')
plt.gca().spines['top'].set_color('none')
#fig.tight_layout()
plt.show()
Functionally, only two changes are needed:
Change ax.bar to ax.barh
Swap set_x* methods with set_y* methods, e.g. set_xticks() -> set_yticks() and so on
Semantically, the variables x and width should also be renamed to y and height.
import matplotlib.pyplot as plt
import numpy as np
N = 9
labels = list('LSSMWWSRC')
M_means = [1, 45, 28, 11, 4, 7, 1, 0.02, 0.3]
K_means = [2, 40, 21, 18, 3, 3, 2, 0.52, 0.3]
PO_means = [3, 58, 17, 8, 3, 8, 1, 0.06, 1]
K = [1, 44, 30, 11, 3, 7, 1, 0.01, 0.5]
# rename x/width to y/height
y = np.arange(len(labels))
height = 0.30
fig, ax = plt.subplots()
# use ax.barh instead of ax.bar
rects1 = ax.barh(y - height, M_means, height, label='M S and K', color='#b02a2a')
rects2 = ax.barh(y, PO_means, height, label='P O S and K', color='#055cad')
rects3 = ax.barh(y + height, K_means, height, label='M K', color='#0b7d53')
# swap set_x* methods with set_y* methods
ax.set_xlabel('% of workday')
ax.set_yticks(y)
ax.set_yticklabels(labels)
ax.legend(loc='upper right', frameon=False, markerscale=2)
ax.bar_label(rects1, padding=10)
ax.bar_label(rects2, padding=10)
ax.bar_label(rects3, padding=10)
# ...
The easiest solution is to load the data into a pandas.DataFrame, and then use pandas.DataFrame.plot with kind='barh'. This is easier because pandas uses matplotlib as the default plotting backend, and the API groups the bars automatically.
This reduces the code to 14 lines (not including imports).
When using 'barh', xlabel= applies to the y-axis. Therefore, xlabel='' removes the y-axis label.
Adjust figsize=(12, 10) if planning to use smaller / larger font sizes.
See Adding value labels on a matplotlib bar chart for additional details about using .bar_label.
Tested in python 3.10, pandas 1.4.2, matplotlib 3.5.1
import pandas as pd
import matplotlib.pylot as plt
# data
labels = ['L', 'S', 'S', 'M', 'W', 'W', 'S', 'R', 'C']
M_means = [1, 45, 28, 11, 4, 7, 1, 0.02, 0.3]
PO_means = [3, 58, 17, 8, 3, 8, 1, 0.06, 1]
K_means = [1, 44, 30, 11, 3, 7, 1, 0.01, 0.5]
# create a dict with the keys as the desired legend labels
data = {'labels': labels, 'M S and K': M_means, 'P O S and K': PO_means, 'M K': K_means}
# create dataframe
df = pd.DataFrame(data)
# plot: specify y=[...] if only certain columns are desired
ax = df.plot(kind='barh', x='labels', width=.85, figsize=(12, 10), xlabel='', color=['#b02a2a', '#055cad', '#0b7d53'])
ax.set_xlabel('% of workday', fontsize=15)
ax.set_xlim(0, 100)
ax.legend(loc='upper right', frameon=False, fontsize=15, markerscale=2)
for c in ax.containers:
ax.bar_label(c, label_type='edge', padding=1, size=15)
ax.tick_params(axis='both', which='both', labelsize=15)
ax.spines[['top', 'right']].set_visible(False)
Stacked
To manually create the stacked bar without pandas, see Horizontal stacked bar chart in Matplotlib
Use the parameter stacked=True
Some bar patches are to small for the label, so custom labels have been passed to the labels= parameter in .bar_label
Using := requires at least python 3.8. Otherwise use labels = [f'{v.get_width():.0f}' if v.get_width() > 1 else '' for v in c]
ax = df.plot(kind='barh', x='labels', width=.85, figsize=(12, 10), xlabel='',
color=['#b02a2a', '#055cad', '#0b7d53'], stacked=True)
ax.set_xlabel('% of workday', fontsize=15)
ax.set_xlim(0, 100)
ax.legend(loc='upper right', frameon=False, fontsize=15, markerscale=2)
for c in ax.containers:
# custom labels only show label size for values greater than 1
labels = [f'{w:.0f}' if (w := v.get_width()) > 1 else '' for v in c]
ax.bar_label(c, labels=labels, label_type='center', padding=1, size=15)
ax.tick_params(axis='both', which='both', labelsize=15)
ax.spines[['top', 'right']].set_visible(False)

Bar plot: Loop through a list of element and make an automatic annotation on labels that are on the list

I would like to loop through a list of element (annotation_list) and automatically make an annotation on top of the corresponding label saying "Not updated". As in the image.
I would also like to make an annotation in red - centered horizontally and 3/4 vertically saying "Confidential, for internal use only"
Thank you
enter image description here
import matplotlib.pyplot as plt
import numpy as np
import calendar
Country_num = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Country_name=["Algeria","Belgium","Brazil", "China","France","Germany","India","Italy",
"Poland","Mongolia","US","Switzerland"]
print(len(Country_name))
units_sold =[100, 1050, 1000, 950,300, 500, 250, 700, 600, 550, 500, 450]
n=len(units_sold)
line=[round(units_sold[0]/x,1) for x in range(1,n+1)]
anotation_liste=["Algeria","Germany","Mongolia"]
fig, ax = plt.subplots()
fig.set_size_inches(12, 6)
plt.xticks(Country_num,Country_name, rotation=90)
plot = ax.bar(Country_num, units_sold)
for rect in plot:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.002*height,'%d' %
int(height), ha='center', va='bottom')
#### MISSING CODE MAKE ANNOTATION WITH ARROW
plt.plot(Country_num, line, linewidth=2.0)
plt.margins(x=0, tight=True)
plt.show()
An arrow has been added in the annotation. Data coordinates are set in data and text coordinates are set in offset. Also, the character annotations are set to the upper right position (x=0.9, y=0.8) in 'ax.text()'. For more information, see this page.
import matplotlib.pyplot as plt
import numpy as np
import calendar
Country_num = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Country_name=["Algeria","Belgium","Brazil", "China","France","Germany","India","Italy",
"Poland","Mongolia","US","Switzerland"]
print(len(Country_name))
units_sold =[100, 1050, 1000, 950,300, 500, 250, 700, 600, 550, 500, 450]
n=len(units_sold)
line=[round(units_sold[0]/x,1) for x in range(1,n+1)]
anotation_liste=["Algeria","Germany","Mongolia"]
fig, ax = plt.subplots()
fig.set_size_inches(12, 6)
boxdic={'facecolor':'w',
'edgecolor':'b',
'boxstyle':'square',
'linewidth':1}
plt.xticks(Country_num,Country_name, rotation=90)
plot = ax.bar(Country_num, units_sold)
for rect in plot:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width()/2., 1.002*height,'%d' % int(height), ha='center', va='bottom')
if height == 500:
#ax.text(rect.get_x() , height+100, 'Not\nupdated', bbox=boxdic, color='r')
ax.annotate('Not\nupdated',
xy=(rect.get_x()+.4, height+30), xycoords='data',
xytext=(0,60), textcoords='offset points', color='r',
ha='center',va='center',
arrowprops=dict(arrowstyle='->',
fc="k", ec="k",
),
bbox=boxdic)
ax.text(0.9, 0.8, 'Confidential,\nfor internal use only',ha='center',va='center', transform=ax.transAxes)
plt.plot(Country_num, line, linewidth=2.0)
plt.margins(x=0, tight=True)
plt.show()

Create stacked bar with matplotlib

I have data displayed in the following format:
values = np.array([10, 12,13, 5,20], [30, 7, 10, 25,2], [10, 12,13, 5,20]])
And I want to create a straight-up stacked bar chart like the following figure. Each element in the array belongs to a stacked bar.
I have searched to see how can I do this with matplotlib, but unfortunately, I still haven't found a way to do it. How can I do this?
AFAIK, there is now straightforward way to do it. You need to calculate exact position of bars yourself and then normalize it.
import numpy as np
import matplotlib.pyplot as plt
values = np.array([[10, 12,13, 5,20], [30, 7, 10, 25,2], [10, 12,13, 5,20]])
values_normalized = values/np.sum(values, axis=0)
bottom_values = np.cumsum(values_normalized, axis=0)
bottom_values = np.vstack([np.zeros(values_normalized[0].size), bottom_values])
text_positions = (bottom_values[1:] + bottom_values[:-1])/2
r = [0, 1, 2, 3, 4] # position of the bars on the x-axis
names = ['A', 'B', 'C', 'D', 'E'] # names of groups
colors = ['lightblue', 'orange', 'lightgreen']
for i in range(3):
plt.bar(r, values_normalized[i], bottom=bottom_values[i], color=colors[i], edgecolor='white', width=1, tick_label=['a','b','c','d','e'])
for xpos, ypos, yval in zip(r, text_positions[i], values[i]):
plt.text(xpos, ypos, "N=%d"%yval, ha="center", va="center")
# Custom X axis
plt.xticks(r, names, fontweight='bold')
plt.xlabel("group")
plt.show()
There is a source that tells how to add text on top of bars. I'm a bit in a hurry right now so I hope this is useful and I'll update my answer next day if needed.
I've updated my answer. Adding text on top of the bars is tricky, it requires some calculations of their vertical positions.
Btw, I have refactored the most of code that is in a link I shared.
Python 3.8
matplotlib 3.3.1
numpy 1.19.1
Chat Result
import matplotlib.pyplot as plt
import numpy as np
values = np.array([[10, 12, 13, 5, 20], [30, 7, 10, 25, 2], [10, 12, 13, 5, 20]])
row, column = values.shape # (3, 5)
x_type = [x+1 for x in range(column)]
ind = [x for x, _ in enumerate(x_type)]
values_normalized = values/np.sum(values, axis=0)
value1, value2, value3 = values_normalized[0,:], values_normalized[1,:], values_normalized[2,:]
# Create figure
plt.figure(figsize=(8, 6))
plt.bar(ind, value1, width=0.8, label='Searies1', color='#5B9BD5')
plt.bar(ind, value2, width=0.8, label='Searies2', color='#C00000', bottom=value1)
plt.bar(ind, value3, width=0.8, label='Searies3', color='#70AD47', bottom=value1 + value2)
# Show text
bottom_values = np.cumsum(values_normalized, axis=0)
bottom_values = np.vstack([np.zeros(values_normalized[0].size), bottom_values])
text_positions = (bottom_values[1:] + bottom_values[:-1])/2
c = list(range(column))
for i in range(3):
for xpos, ypos, yval in zip(c, text_positions[i], values[i]):
plt.text(xpos, ypos, yval, horizontalalignment='center', verticalalignment='center', color='white')
plt.xticks(ind, x_type)
plt.legend(loc='center', bbox_to_anchor=(0, 1.02, 1, 0.1), handlelength=1, handleheight=1, ncol=row)
plt.title('CHART TITLE', fontdict = {'fontsize': 16,'fontweight': 'bold', 'family': 'serif'}, y=1.1)
# Hide y-axis
plt.gca().axes.yaxis.set_visible(False)
plt.show()

Showing two data sets of `ax2.set_xticklabels` in a subplot

I have an upper subplot that shows two data sets: orange and green.
The following code shows the xtick labels of the green data set,
in the ax2 axis of the upper subplot (axis labelled as X2 in the figure):
ind_pos_Pd3 = [0, 4, 8, 12, 16]
axarr[0].set_xticks(X1_green[ind_pos_Pd3])
ax2.set_xticks(X1_green[ind_pos_Pd3])
ax2.set_xticklabels(["%.2f" % i for i in X2_green[ind_pos_Pd3]])
On the contrary, the following code shows the the xtick labels of the orange data set,
in the ax2 axis of the upper subplot (axis labelled as X2 in the figure):
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
axarr[0].set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticklabels(["%.2f" % i for i in X2_orange[ind_pos_Bd3]])
Is there a way to "mix" both schemes and achieve a ax2 axis divided into two panels: the "lower panel" is filled with the labels from the green data set, and the "upper panel" is filled with the labels from the orange data set ?
Something like:
I was thinking on this pseudo-code:
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
ind_pos_Pd3 = [0, 4, 8, 12, 16]
axarr[0].set_xticks(X1_orange[ind_pos_Bd3])
axarr[0].set_xticks(X1_green[ind_pos_Pd3])
ax2.set_xticks(X1_green[ind_pos_Pd3])
ax2.set_xticklabels(["%.2f \n %.2f" % i for i in zip(X2_orange[ind_pos_Bd3], X1_green[ind_pos_Pd3]])
However, the "%.2f \n %.2f" scheme is not working.
Minimal working example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
X1_orange = np.array([ 10., 30.1, 50.2, 70.3, 90.4, 110.51, 130.61, 150.71, 170.81,
190.91, 211.01, 231.11, 251.21, 271.31, 291.41, 311.52, 331.62, 351.72,
371.82, 391.92, 412.02, 432.12, 452.22, 472.32, 492.42, 512.53, 532.63,
552.73, 572.83, 592.93, 613.03, 633.13, 653.23])
X2_orange = np.array([ 2.56691976, 2.5781089 , 2.59624855, 2.62275805, 2.64568568, 2.66607658,
2.6959714 , 2.7231501 , 2.75529391, 2.78894345, 2.81573712, 2.84711104,
2.88437499, 2.9191375 , 2.95033337, 2.99340594, 3.02206115, 3.06383265,
3.08649135, 3.12707204, 3.18500195, 3.24240915, 3.25965166, 3.36137181,
3.35468811, 3.42661704, 3.46254097, 3.61136855, 3.65505401, 3.89043407,
3.80421353, 3.79380128, 4.01570509])
X1_green = np.array([ 10. , 30.1 , 50.2 , 70.3 , 90.4 , 110.51, 130.61, 150.71, 170.81,
190.91, 211.01, 231.11, 251.21, 271.31, 291.41, 311.52, 331.62])
X2_green = np.array([ 1.9894731 , 2.00259207, 2.01875725, 2.04333825, 2.07700656, 2.09629866,
2.14727031, 2.17488234, 2.2499103 , 2.2698862 , 2.31607409, 2.41452705,
2.50847008, 2.61117044, 2.70657103, 3.25283819, 3.31585812])
Y0_orange = np.array([-1.87483583, -1.82358431, -1.78627169, -1.75242213, -1.7299256 , -1.69363695,
-1.64623615, -1.59656948, -1.56967972, -1.55058869, -1.51874314, -1.45673839,
-1.40554361, -1.39904184, -1.35322104, -1.33906865, -1.30865871, -1.28099683,
-1.24897269, -1.19802619, -1.17268543, -1.13523614, -1.09290541, -1.05642197,
-1.00189406, -0.95390527, -0.90890049, -0.82522042, -0.76334378, -0.64504498,
-0.62782754, -0.47358849, -0.49772039])
Y0_green = np.array([-1.97113854, -1.92908192, -1.84404067, -1.75333855, -1.67575628, -1.58010168,
-1.48746063, -1.40770641, -1.31802444, -1.2302243 , -1.14927205, -1.04251178,
-0.91661452, -0.82924669, -0.65075739, -0.37715382, -0.21349827])
##### THREE SUBPLOTS::
###### Y0 plot:
f, axarr = plt.subplots(3, sharex=True, figsize=(11,5))
axarr[0].plot(X1_orange, Y0_orange, linestyle='--', marker="o", markersize=6, color='orange')
axarr[0].plot(X1_green, Y0_green, linestyle='--', marker="o", markersize=6, color='green')
axarr[0].set_ylabel('Y0', fontsize=15)
# Create a new axis:
axarr[0].grid()
ax2 = axarr[0].twiny()
# Make the ax1-ticks and ax1-tick-labels match the line color (blue):
axarr[0].tick_params('x', colors='blue')
# Make the ax2-ticks and ax2-tick-labels match the red color:
# this only controls the font and color of label
ax2.set_xlabel('x2', fontsize=14, color='red')
# this also adds the numbers on top of the tics,
# but sets the colors of the tics
ax2.tick_params('x', colors='orange')
# Set xlimits of ax2 the same as ax1
ax2.set_xlim(axarr[0].get_xlim())
# Set ticks at desired position
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
axarr[0].set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticklabels(["%.2f" % i for i in X2_orange[ind_pos_Bd3]])
#ind_pos_Pd3 = [0, 4, 8, 12, 16]
#axarr[0].set_xticks(X1_green[ind_pos_Pd3])
#ax2.set_xticks(X1_green[ind_pos_Pd3])
#ax2.set_xticklabels(["%.2f" % i for i in X2_green[ind_pos_Pd3]])
# Just to align the Delta_V ylabel to the other 3 ylabel's
labelx = -0.075 # very close to the plot
axarr[0].yaxis.set_label_coords(labelx, 0.5, transform=None)
##### Y1 plot:
#f, axarr = plt.subplots(3, sharex=True)
axarr[1].set_ylabel('Y1', fontsize=15)
# Create a new axis:
axarr[1].grid()
ax2 = axarr[1].twiny()
# Make the ax1-ticks and ax1-tick-labels match the line color (blue):
axarr[1].tick_params('x', colors='blue')
# Make the ax2-ticks and ax2-tick-labels match the red color:
ax2.tick_params('x', colors='red')
ax2.set_xticklabels([]) # disable the tic labels
# Set xlimits of ax2 the same as ax1
ax2.set_xlim(axarr[1].get_xlim())
# Set ticks at desired position
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
axarr[1].set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticks(X1_orange[ind_pos_Bd3])
# Label ticks of ax2 with values from X2
#ax2.set_xticklabels(["%.2f" % i for i in P])
axarr[1].yaxis.set_major_formatter(mtick.FormatStrFormatter('%1.e'))
#### Y2 plot:
axarr[2].set_ylabel('Y2', fontsize=15)
# Create a new axis:
axarr[2].grid()
ax2 = axarr[2].twiny()
# Make the ax1-ticks and ax1-tick-labels match the line color (blue):
axarr[2].tick_params('x', colors='blue')
# Make the ax2-ticks and ax2-tick-labels match the red color:
axarr[2].set_xlabel('X1', fontsize=14, color='blue')
# this also adds the numbers on top of the tics,
# but sets the colors of the tics
axarr[2].tick_params('x', colors='blue')
# Make the ax2-ticks and ax2-tick-labels match the red color:
ax2.tick_params('x', colors='red')
ax2.set_xticklabels([]) # disable the tic labels
# Set xlimits of ax2 the same as ax1
ax2.set_xlim(axarr[2].get_xlim())
# Set ticks at desired position
ind_pos_Bd3 = [0, 4, 8, 12, 16, 20, 24, 28, 32]
axarr[2].set_xticks(X1_orange[ind_pos_Bd3])
ax2.set_xticks(X1_orange[ind_pos_Bd3])
# Label ticks of ax2 with values from X2
axarr[2].yaxis.set_major_formatter(mtick.FormatStrFormatter('%1.e'))
plt.show()
The trick is to add a new twin axis for each row of tick labels you want.
You can then position them properly setting the pad parameter in tick_params.
Allow me to make your example a little smaller so it's easier to follow
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
fig, ax = plt.subplots(figsize=(12,2))
np.random.seed(42)
x0 = np.arange(50)
y0 = np.random.rand(50)
x1 = np.arange(30)
y1 = np.random.rand(30) + 0.7
ax.plot(x0, y0, ls='--', marker='o', ms=6, color='orange')
ax.grid(True)
ax.tick_params('x', colors='blue')
ax.set_xlabel('x1', fontsize=14, color='blue')
ax.set_ylabel('y', fontsize=14)
tax1 = ax.twiny()
tax1.set_xlim(ax.get_xlim())
tax1.tick_params('x', colors='orange', pad=14)
tax1.set_xlabel('x2', fontsize=14, color='red', labelpad=6)
tax2 = ax.twiny()
tax2.set_xlim(ax.get_xlim())
tax2.plot(x1, y1, ls='--', marker='o', ms=6, color='green')
tax2.tick_params('x', colors='green')
tax2.set_xticks([0, 10, 20, 30])
fig.savefig('img.png', pad_inches=0, bbox_inches='tight')
As you pointed out in the comments an issue with this plot is that you get green and orange tick lines and you would like to have them in red just like the x2 label.
You can hide the green tick lines setting length=0 in tick_params, draw the orange plot ticks in red first and then set labels color to orange.
Here's the modified code
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
fig, ax = plt.subplots(figsize=(12,2))
np.random.seed(42)
x0 = np.arange(50)
y0 = np.random.rand(50)
x1 = np.arange(30)
y1 = np.random.rand(30) + 0.7
ax.plot(x0, y0, ls='--', marker='o', ms=6, color='orange')
ax.grid(True)
ax.tick_params('x', colors='blue')
ax.set_xlabel('x1', fontsize=14, color='blue')
ax.set_ylabel('y', fontsize=14)
tax1 = ax.twiny()
tax1.set_xlim(ax.get_xlim())
tax1.tick_params('x', colors='red', pad=14)
tax1.set_xlabel('x2', fontsize=14, color='red', labelpad=6)
[x.set_color("orange") for x in tax1.get_xticklabels()]
tax2 = ax.twiny()
tax2.set_xlim(ax.get_xlim())
tax2.plot(x1, y1, ls='--', marker='o', ms=6, color='green')
tax2.tick_params('x', colors='green', pad=6, length=0)
tax2.set_xticks([0, 10, 20, 30])
fig.savefig('img2.png', pad_inches=0, bbox_inches='tight')

Connecting lines between points plotted in Matplotlib

I have 4 X and Y lists that I am plotting separately with matplotlib.pyplot, i.e. point1[x1,y1], point2[x2,y2], point3[x3,y3] and point4[x4,y4]. What I am trying to do in the plot is to connect point1 to point2, point2 to point3, etc. until all 4 points are connected which would represent a square in my case. The data is for dynamic x and y displacements for a rectangular pump I'm working on that shows if a displacement limitation is exceeded inside a vessel's moonpool.
Here is the code I have so far that gives me the following plot and the plot generated:
## SSLP displacement time histories to be plotted
point1 = (3.61, 4, -3)
point2 = (3.61, -4, -3)
point3 = (-3.61, -4, -3)
point4 = (-3.61, 4, -3)
SSLPXPoint1 = SSLP.TimeHistory('X', 1, objectExtra=OrcFxAPI.oeBuoy(point1))
SSLPYPoint1 = SSLP.TimeHistory('Y', 1, objectExtra=OrcFxAPI.oeBuoy(point1))
SSLPXPoint2 = SSLP.TimeHistory('X', 1, objectExtra=OrcFxAPI.oeBuoy(point2))
SSLPYPoint2 = SSLP.TimeHistory('Y', 1, objectExtra=OrcFxAPI.oeBuoy(point2))
SSLPXPoint3 = SSLP.TimeHistory('X', 1, objectExtra=OrcFxAPI.oeBuoy(point3))
SSLPYPoint3 = SSLP.TimeHistory('Y', 1, objectExtra=OrcFxAPI.oeBuoy(point3))
SSLPXPoint4 = SSLP.TimeHistory('X', 1, objectExtra=OrcFxAPI.oeBuoy(point4))
SSLPYPoint4 = SSLP.TimeHistory('Y', 1, objectExtra=OrcFxAPI.oeBuoy(point4))
# setup plot
caseName = os.path.splitext(info.modelFileName)[0]
point1Plot = [3.61, 4]
point2Plot = [3.61, -4]
point3Plot = [-3.61, -4]
point4Plot = [-3.61, 4]
vesselPointsX = [90.89, 100.89, 100.89, 90.89, 90.89]
vesselPointsY = [5, 5, -5, -5, 5]
moonpoolCLX = [89, 103]
moonpoolCLY = [0, 0]
fig = plt.figure(figsize=(20, 15))
ax = fig.add_subplot(1, 1, 1)
plt.plot(vesselPointsX, vesselPointsY, 'r', lw=2, label='OCV Moonpool Limits')
plt.plot(moonpoolCLX, moonpoolCLY, 'k--', label='Moonpool CL')
plt.plot(SSLPXPoint1, SSLPYPoint1, 'k')
plt.plot(SSLPXPoint2, SSLPYPoint2, 'k')
plt.plot(SSLPXPoint3, SSLPYPoint3, 'k')
plt.plot(SSLPXPoint4, SSLPYPoint4, 'k')
ax.set_title("SSLP Maximum Offsets Inside Moonpool for {}".format(caseName), fontsize=20)
ax.set_xlabel('Distance Along OCV from Stern [m]', fontsize=15)
ax.set_ylabel('Distance from Moonpool Centerline, (+) Towards Portside [m]', fontsize=15)
ax.set_xlim(89, 103)
ax.set_ylim(-7, 7)
plt.gca().set_aspect('equal', adjustable='box')
plt.draw()
plt.legend()
plt.tight_layout()
plt.show()
Any help would be greatly appreciated.
Thanks,
Brian
This should help you. Make your changes.
from matplotlib.pyplot import plot, show
plot([SSLPXPoint1, SSLPXPoint2], [SSLPYPoint1, SSLPYPoint2])
plot([SSLPXPoint3, SSLPXPoint2], [SSLPYPoint3, SSLPYPoint2])
plot([SSLPXPoint3, SSLPXPoint4], [SSLPYPoint3, SSLPYPoint4])
show()
Edited, because previous one was connecting all dots.

Categories

Resources