I'm trying to break up my x axis in 5 parts. There exists many answers on how to break it up in 2 parts, so I've followed the same strategy but it doesn't work for more than 2 parts.
Has anyone ever succeeded breaking up an axis in more than 2 parts?
import numpy as np
from pylab import *
import matplotlib.pyplot as plt
fig,(ax,ax2,ax3,ax4,ax5) = plt.subplots(1,2,sharey=True)
ax.plot(wvln0,alb0,linestyle='-', marker='o', color='r',linewidth=1.0,label='Haze = 0T')
ax2.plot(wvln0,alb0,linestyle='-', marker='o', color='r',linewidth=1.0,label='Haze = 0T')
ax3.plot(wvln0,alb0,linestyle='-', marker='o', color='r',linewidth=1.0,label='Haze = 0T')
ax4.plot(wvln0,alb0,linestyle='-', marker='o', color='r',linewidth=1.0,label='Haze = 0T')
ax5.plot(wvln0,alb0,linestyle='-', marker='o', color='r',linewidth=1.0,label='Haze = 0T')
ax.set_xlim(0.15,1.10)
ax2.set_xlim(1.15,2.25)
ax3.set_xlim(1.20,1.30)
ax4.set_xlim(1.55,1.65)
ax5.set_xlim(1.95,2.15)
ax.spines['right'].set_visible(False)
ax2.spines['left'].set_visible(False)
ax.yaxis.tick_left()
ax.tick_params(labeltop='off') # don't put tick labels at the top
ax2.yaxis.tick_right()
plt.subplots_adjust(wspace=0.1)
axes = plt.gca()
axes.set_ylim([0.0,0.72])
plt.show()
Returns
Traceback (most recent call last):
File "/Users/jadecheclair/Documents/NASA Ames Research/NASA Codes/Wavlengths_Rages/wvln_alb.py", line 37, in <module>
fig,(ax,ax2,ax3,ax4,ax5) = plt.subplots(1,2,sharey=True)
ValueError: need more than 2 values to unpack
And if I try to change the line to
fig,(ax,ax2,ax3,ax4,ax5) = plt.subplots(1,2,3,4,5,sharey=True)
It returns
Traceback (most recent call last):
File "/Users/jadecheclair/Documents/NASA Ames Research/NASA Codes/Wavlengths_Rages/wvln_alb.py", line 37, in <module>
fig,(ax,ax2,ax3,ax4,ax5) = plt.subplots(1,2,3,4,5,sharey=True)
TypeError: subplots() got multiple values for keyword argument 'sharey'
This is a example showing how I've done it by placing three axes manually in a figure.
from __future__ import division, unicode_literals
import matplotlib.pyplot as plt
import numpy as np
plt.close('all')
# ----- PREPARE DATA ----
t = np.arange(0, 7 * 10**5, 10)
x = np.abs(np.sin(t/10.**2)) * 300
# ----- FIG CREATION ----
fig = plt.figure(figsize=(8, 5), facecolor='white')
# Margins (dimensions are in inches):
left_margin = 0.6 / fig.get_figwidth()
right_margin = 0.25 / fig.get_figwidth()
bottom_margin = 0.75 / fig.get_figheight()
top_margin = 0.25 / fig.get_figwidth()
mid_margin = 0.1 / fig.get_figwidth() # horizontal space between subplots
# ----- DEFINE PARAMETERS FOR EACH AXE ----
# Proportion of the figure's width taken by each axe (the sum must == 1):
f = [0.5, 0.3, 0.2]
xmin = [200, 50, 0] # xaxis minimum values for each axe
xmax = [8200, 200, 50] # xaxis maximum values for each axe
xscl = [2000, 50, 10] # xaxis scale for each axe
ymin, ymax = 0, 320 # yaxis minimum and maximum values
colors = ['green', 'blue', 'red'] # colors of each plot
labels = ['label1', 'label2', 'label3'] # labels of each plot for the legend
Naxes = len(f) # Total number of axes to add to the figure.
x0, y0 = left_margin, bottom_margin # origin point of the axe
h = 1 - (bottom_margin + top_margin) # height of the axe
# total width of the axes:
wtot = 1 - (left_margin + right_margin + (Naxes-1)*mid_margin)
lines = [] # to store handles for generating the legend later on
for i in range(Naxes):
# ----- AXES CREATION ----
w = wtot*f[i] # width of the current axe
ax = fig.add_axes([x0, y0, w, h], frameon=True, axisbg='none')
if i == 0: # First axe to the left
ax.spines['right'].set_visible(False)
ax.tick_params(right='off', labelright='off')
elif i == Naxes-1: # Last axe to the right
ax.spines['left'].set_visible(False)
ax.tick_params(left='off', labelleft='off',
right='off', labelright='off')
else:
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.tick_params(left='off', labelleft='off',
right='off', labelright='off')
# origin point of the next axe to be added to the figure:
x0 += w + mid_margin
# ----- SETUP XTICKS ----
if i == Naxes-1:
xticks = np.arange(xmin[i], xmax[i] + xscl[i]/2, xscl[i])
else:
xticks = np.arange(xmin[i]+xscl[i], xmax[i] + xscl[i]/2, xscl[i])
ax.set_xticks(xticks)
ax.xaxis.set_ticks_position('bottom')
ax.tick_params(axis='x', direction='out', labelsize=8)
xticks_minor = np.arange(xmin[i], xmax[i] + xscl[i]/5., xscl[i] / 5.)
ax.set_xticks(xticks_minor, minor=True)
ax.tick_params(axis='x', which='minor', direction='out')
# ----- PLOT DATA ----
line, = ax.plot(t, x, color=colors[i])
lines.append(line) # for plotting the legend
ax.axis([xmin[i], xmax[i], ymin, ymax])
ax.invert_xaxis()
# ---- SET XAXIS LABEL ----
fig.axes[0].set_xlabel('Time (years)', fontsize=12, va='bottom', ha='center')
fig.axes[0].xaxis.set_label_coords(0.5, 0.05, transform=fig.transFigure)
# ----- LEGEND ----
fig.axes[0].legend(lines, labels, loc=(0.1, 0.1), ncol=1, fancybox=True,
fontsize=12)
# ----- SHOW FIG ----
fig.savefig('SingleAxeThreeScale.png')
plt.show()
Which results in:
Problem 1
You are not calling plt.subplots correctly. Here is the default usage
subplots(nrows=1, ncols=1, sharex=False, sharey=False, squeeze=True, subplot_kw=None, gridspec_kw=None, **fig_kw)
Seems like you want 5 subplots in a row, so set nrows=1, and ncols=5
fig,axs = plt.subplots(nrows=1,ncols=5,sharey=True)
ax, ax2, ax3, ax4, ax5 = axs
Problem 2
You need to set the spines correctly- you only want the leftmost and rightmost to ve visible. It is easy to accomplish this with a function:
def multi_spine_adj( axs):
axs[0].spines['right'].set_visible(False)
axs[0].yaxis.tick_left()
for ax in axs[1:-1]:
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
axs[-1].spines['left'].set_visible(False)
axs[-1].yaxis.tick_right()
#wvln0 = np.linspace( 0,5,50) # make some fake data
#alb0 = np.random.random(50) #make some fake data
opts = {'linestyle':'-', 'marker':'o', 'color':'r','linewidth':1,'label':'Haze = 0T'}
fig,axs = plt.subplots(1,5,sharey=True)
xlims = [(0.15,1.10),
(1.15,2.25),
(1.20,1.30),
(1.55,1.65),
(1.95,2.15)]
for i,ax in enumerate(axs):
x1,x2 = xlims[i]
ax.plot(wvln0,alb0,**opts)
ax.set_xlim(x1,x2)
multi_spine_adj(axs)
plt.show()
Tip
Note how you can easily iterate over the axs, this makes your code cleaner and easier to modify (if you want to add more breaks for example)
Related
I observe some obscure behaviour of the ticks in a matplotlib.pyplot log-log plot.
#!/usr/bin/python3
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
dtFractions = [1/1, 1/2, 1/4, 1/8]
L2Err = [4.41669808e-06, 1.10121946e-06, 2.78272254e-07, 8.18570087e-08]
LInfErr = [7.68589519e-05, 2.00625677e-05, 6.05176778e-06, 2.55658820e-06]
InchesX = 7
fig, ax = plt.subplots()
Blue_RGB = [(0, 84/256, 159/256)]
Petrol_RGB = [(0/256, 152/256, 161/256)]
### ACTUAL PLOTTING: Infty errors ###
ax.scatter(dtFractions, LInfErr, label = r'$e^\infty$', color = Blue_RGB)
ax.plot(dtFractions, LInfErr, color = Blue_RGB[0], linestyle='dashed')
ax.scatter(dtFractions, L2Err, label = r'$e^2$', color = Petrol_RGB)
ax.plot(dtFractions, L2Err, color = Petrol_RGB[0], linestyle='dashed')
ax.loglog(dtFractions, np.multiply(1e-5, np.power(dtFractions, 2) ), linestyle='dotted',
label = r'$\mathcal{O}\left(\Delta t^2\right)$',
color = 'black') # Order two line fitted
# Turn on logscale (no native support for logarithmic scatter)
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_xlabel(r'$\Delta t^\mathrm{Eff}_S$')
### GRID SECTION ###
ax.grid(axis ='both', which='major', alpha=0.1, linewidth = 1.5, color ='black')
ax.set_axisbelow(True) # Hide grid behind bars
### LEGEND SECTION ###
ax.legend(loc = "upper left")
### TICKS SECTION ###
plt.tick_params(axis='x', which = 'both', bottom=False)
ax.set_xticks(dtFractions)
ax.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
ax.get_xaxis().set_tick_params(which='minor', size=0)
ax.get_xaxis().set_tick_params(which='minor', width=0)
ax.set_xticklabels([r"$1$", r"$1/2$", r"$1/4$", r"$1/8$"])
### TITLE SECTION ###
plt.title(r"$l^\infty$ Error for CEE: Isentropic Vortex on Uniform Grid")
# Scale while preserving aspect ratio
width, height = fig.get_size_inches()
factor = InchesX / width
fig.set_size_inches(width * factor, height * factor)
plt.tight_layout() # Title, labels, ... to screen
plt.savefig('Convergence2DComprEulerVortex_PERK.pgf', bbox_inches = 'tight', pad_inches = 0)
plt.show()
This produces
Note in particular the strange additional ticks.
If I add one more custom tick, i.e.,
#!/usr/bin/python3
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
dtFractions = [1/1, 1/2, 1/4, 1/8, 1/16]
L2Err = [4.41669808e-06, 1.10121946e-06, 2.78272254e-07, 8.18570087e-08, 42]
LInfErr = [7.68589519e-05, 2.00625677e-05, 6.05176778e-06, 2.55658820e-06, 42]
InchesX = 7
fig, ax = plt.subplots()
Blue_RGB = [(0, 84/256, 159/256)]
Petrol_RGB = [(0/256, 152/256, 161/256)]
### ACTUAL PLOTTING: Infty errors ###
ax.scatter(dtFractions, LInfErr, label = r'$e^\infty$', color = Blue_RGB)
ax.plot(dtFractions, LInfErr, color = Blue_RGB[0], linestyle='dashed')
ax.scatter(dtFractions, L2Err, label = r'$e^2$', color = Petrol_RGB)
ax.plot(dtFractions, L2Err, color = Petrol_RGB[0], linestyle='dashed')
ax.loglog(dtFractions, np.multiply(1e-5, np.power(dtFractions, 2) ), linestyle='dotted',
label = r'$\mathcal{O}\left(\Delta t^2\right)$',
color = 'black') # Order two line fitted
# Turn on logscale (no native support for logarithmic scatter)
ax.set_yscale('log')
ax.set_xscale('log')
ax.set_xlabel(r'$\Delta t^\mathrm{Eff}_S$')
### GRID SECTION ###
ax.grid(axis ='both', which='major', alpha=0.1, linewidth = 1.5, color ='black')
ax.set_axisbelow(True) # Hide grid behind bars
### LEGEND SECTION ###
ax.legend(loc = "upper left")
### TICKS SECTION ###
plt.tick_params(axis='x', which = 'both', bottom=False)
ax.set_xticks(dtFractions)
ax.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
ax.get_xaxis().set_tick_params(which='minor', size=0)
ax.get_xaxis().set_tick_params(which='minor', width=0)
ax.set_xticklabels([r"$1$", r"$1/2$", r"$1/4$", r"$1/8$", r"$1/16$"])
### TITLE SECTION ###
plt.title(r"$l^\infty$ Error for CEE: Isentropic Vortex on Uniform Grid")
# Scale while preserving aspect ratio
width, height = fig.get_size_inches()
factor = InchesX / width
fig.set_size_inches(width * factor, height * factor)
plt.tight_layout() # Title, labels, ... to screen
plt.savefig('Convergence2DComprEulerVortex_PERK.pgf', bbox_inches = 'tight', pad_inches = 0)
plt.show()
Then everything works as expected:
Is this a bug? I tried also the second version and then setting xlim to something fitting - the obscure ticks are arising again.
I need to create a plot as close to this picture as possible (given the generated dataframe code below):
And here's the output plot of my code:
What I am having problems with is:
The edge of fill_between is not sharp as in the picture. What I have is some kind of white shadow. How do I change the line between the fillings to match a target picture?
How do I aling legend color lines to the center, but not to the left border which my code does?
Here's my code:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cm as cm
import numpy as np
import pandas as pd
ncols = 10
figsize = (20, 5)
fontsize = 14
dti = pd.date_range('2013-01-01', '2020-12-31', freq='2W')
probabilities_in_time = np.random.random((ncols, len(dti)))
probabilities_in_time = probabilities_in_time / \
probabilities_in_time.sum(axis=0)
probabilities_in_time = pd.DataFrame(probabilities_in_time).T
probabilities_in_time.index = dti
cm_subsection = np.linspace(0, 1, ncols)
colors = [cm.coolwarm(x) for x in cm_subsection]
def plot_time_probabilities(probabilities_in_time, figsize):
plt.figure(figsize=figsize)
plt.yticks(np.arange(0, 1.2, 0.2), fontsize=fontsize)
plt.xticks(fontsize=fontsize)
draw_stack_plot(colors, probabilities_in_time)
set_grid()
set_legend()
plt.show()
def draw_stack_plot(colors, probabilities_in_time):
for i, color in enumerate(colors):
if i == 0:
plt.plot(probabilities_in_time[i], color=color)
plt.fill_between(probabilities_in_time.index,
probabilities_in_time[0], color=color)
else:
probabilities_in_time[i] += probabilities_in_time[i-1]
plt.fill_between(probabilities_in_time.index,
probabilities_in_time[i], probabilities_in_time[i-1],
color=color)
plt.plot(probabilities_in_time[i], label=' Probability: {}'.format(
i), color=color)
def set_grid():
ax = plt.gca()
ax.set_axisbelow(False)
ax.xaxis.grid(True, linestyle='-', lw=1)
def set_legend():
leg = plt.legend(loc='lower left', fontsize=14, handlelength=1.3)
for i in leg.legendHandles:
i.set_linewidth(12)
plot_time_probabilities(probabilities_in_time, figsize)
To set the legend in the center, you can set loc='center', or you can put the legend outside. To avoid that the legend handles grow to larger, you can leave out .set_linewidth(12) (this sets a very wide edge width of 12 points).
Shifting the colors by one position can help to show the fill borders more pronounced. To still have a correct legend, the label should then be added to fill_between.
The code below also tries to simplify part of the calls:
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import numpy as np
import pandas as pd
ncols = 10
figsize = (20, 5)
fontsize = 14
dti = pd.date_range('2013-01-01', '2020-12-31', freq='2W')
probabilities_in_time = np.random.random((ncols, len(dti)))
probabilities_in_time = probabilities_in_time / probabilities_in_time.sum(axis=0)
probabilities_in_time = pd.DataFrame(probabilities_in_time).T
probabilities_in_time.index = dti
cm_subsection = np.linspace(0, 1, ncols)
colors = cm.coolwarm(cm_subsection)
def plot_time_probabilities(probabilities_in_time, figsize):
plt.figure(figsize=figsize)
plt.yticks(np.arange(0, 1.2, 0.2), fontsize=fontsize)
plt.xticks(fontsize=fontsize)
draw_stack_plot(colors, probabilities_in_time)
set_grid()
set_legend()
# plt.margins(x=0, y=0)
plt.margins(x=0.02)
plt.tight_layout()
plt.show()
def draw_stack_plot(colors, probabilities_in_time):
current_probabilities = 0
for i, color in enumerate(colors):
plt.fill_between(probabilities_in_time.index,
probabilities_in_time[i] + current_probabilities, current_probabilities,
color=color, label=f' Probability: {i}')
current_probabilities += probabilities_in_time[i]
plt.plot(current_probabilities,
color=colors[0] if i <= 1 else colors[-1] if i >= 8 else colors[i - 1] if i < 5 else colors[i + 1])
def set_grid():
ax = plt.gca()
ax.set_axisbelow(False)
ax.xaxis.grid(True, linestyle='-', lw=1)
def set_legend():
leg = plt.legend(loc='lower left', fontsize=14, handlelength=1.3)
# leg = plt.legend(loc='upper left', bbox_to_anchor=(1.01, 1), fontsize=14, handlelength=1.3)
# for i in leg.legendHandles:
# i.set_linewidth(12)
plot_time_probabilities(probabilities_in_time, figsize)
I calculated the rttMeans and rttStds arrays. However, the value of rttStds makes the lower error less than 0.
rttStds = [3.330311915835426, 3.3189677330174883, 3.3319538853150386, 3.325173772304221, 3.3374145232695813]
How to set lower error to 0 instead of -#?
The python bar plot code is bellow.
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(rc={'figure.figsize':(18,16)},style='ticks',font_scale = 1.5,font='serif')
N = 5
ind = ['RSU1', 'RSU2', 'RSU3', 'RSU4', 'RSU5'] # the x locations for the groups
width = 0.4 # the width of the bars: can also be len(x) sequence
fig = plt.figure(figsize=(10,6))
ax = fig.add_subplot(111)
p1 = plt.bar(ind, rttMeans, width, yerr=rttStds, log=False, capsize = 16, color='green', hatch="/", error_kw=dict(elinewidth=3,ecolor='black'))
plt.margins(0.01, 0)
#Optional code - Make plot look nicer
plt.xticks(rotation=0)
i=0.18
for row in rttMeans:
plt.text(i, row, "{0:.1f}".format(row), color='black', ha="center")
i = i + 1
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
params = {'axes.titlesize':24,
'axes.labelsize':24,
'xtick.labelsize':28,
'ytick.labelsize':28,
'legend.fontsize': 24,
'axes.spines.right':False,
'axes.spines.top':False}
plt.rcParams.update(params)
plt.tick_params(axis="y", labelsize=28, labelrotation=20, labelcolor="black")
plt.tick_params(axis="x", labelsize=28, labelrotation=20, labelcolor="black")
plt.ylabel('RT Time (millisecond)', fontsize=24)
plt.title('# Participating RSUs', fontsize=24)
# plt.savefig('RSUs.pdf', bbox_inches='tight')
plt.show()
You can pass yerr as a pair [lower_errors, upper_errors] where you can control lower_errors :
lowers = np.minimum(rttStds,rttMeans)
p1 = plt.bar(ind, rttMeans, width, yerr=[lowers,rttStds], log=False, capsize = 16, color='green', hatch="/", error_kw=dict(elinewidth=3,ecolor='black'))
Output:
When I make figure with 5 subplots and annotate the bars in each subplot, matplotlib appears to scale the figure so that the maximum from the largest y-axis scales to the smallest y-axis.
I can't describe the problem too well, but see this image:
where there's tons of white-space above where the figure should begin.
However, the figure would ideally look like this
When I set the 4 smallest axes to have the same upper y-limit as the largest axis, then the figure scales correctly, but for the purpose of the visualization, I would prefer not to do that.
Why does this happen? Is there anyway to control the figure so that it's not automatically scaled as in the first image? Or otherwise, a more appropriate way of plotting what I hope to achieve?
The code I'm using to generate the figure:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Patch
from matplotlib import rcParams
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['Arial']
department = ["100", "1,000", "10,000", \
"100,000", "1,000,000"]
quarter = ["Serial", "MPI", "CUDA", "Hybrid"]
budgets = np.array([[0.049979, 0.43584, 2.787366, 19.75062, 201.6935],\
[2.184624, 0.175213, 0.677837, 5.265575, 46.33678],\
[0.050294, 0.068537, 0.23739, 1.93778, 18.55734],\
[3.714284, 3.9917, 4.977599, 6.174967, 37.732232]])
budgets = np.transpose(budgets)
em = np.zeros((len(department), len(quarter)))
# set up barchart
x = np.arange(len(department)) # label locations
width = 0.8 # width of all the bars
# set up figure
fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(1, 5)
axes = [ax1, ax2, ax3, ax4, ax5]
# generate bars
rects = []
color = ["tomato", "royalblue", "limegreen", "orange"]
n = len(quarter)
for i in range(n):
bar_x = x - width/2.0 + i/float(n)*width + width/(n*2)
m = len(budgets[:,i])
for j in range(m):
bar_x = x[j] - width/2.0 + i/float(n)*width + width/(n*2)
e = budgets[j,i]
#bar_x = x - width/2.0 + i/float(n)*width + width/(n*2)
rects.append(axes[j].bar(bar_x, e, width=width/float(n), \
label=quarter[i], color=color[i]))
# set figure properties
fig.set_size_inches(12, 2.5)
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
nAx = len(axes)
for i in range(nAx):
#axes[i].set_aspect("auto")
axes[i].tick_params(axis='x', which='both', bottom=False, top=False,
labelbottom=False)
ax1.set_ylabel("Time (ms)")
for i in range(nAx):
axes[i].yaxis.grid(which="major", color="white", lw=0.75)
ax1.set_ylim([0, 4])
fig.suptitle("Time per iteration for differing dataset sizes") # title
for i in range(nAx):
axes[i].set_xlabel(department[i])
# annotate bars
for i in range(nAx):
for rect in rects:
j = 0;
for bar in rect:
y_bottom, y_top = axes[i].get_ylim() # axis limits
height = bar.get_height() # bar's height
va = 'bottom'
offset = 3
color = 'k'
fg = 'w'
# keep label within plot
if (y_top < 1.1 * height):
offset = -3
va = 'top'
color='w'
fg = 'k'
# annotate the bar
axes[i].annotate('{:.2f}'.format(height),
xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0,offset),
textcoords="offset points",
ha='center', va=va, color=color)
# set custom legend
legend_elements = [Patch(facecolor='tomato', label='Serial'),
Patch(facecolor='royalblue', label='MPI'),
Patch(facecolor='limegreen', label='CUDA'),
Patch(facecolor='orange', label='Hybrid')]
plt.legend(handles=legend_elements, loc="upper center", fancybox=False,
edgecolor='k', ncol=4, bbox_to_anchor=(-2, -0.1))
plt.show()
This is a partial answer.
This might be a bug, since I couldn't reproduce the problem until I switched to a Jupyter notebook in a Debian system (different hardware too). Your figure gets drawn correctly in my macOS Jupyter notebook, and in Debian when displayed from a .py script.
The problem appears to be with your annotations. If you make the tight_layout call after annotation, you might get a warning like this:
<ipython-input-80-f9f592f5efc5>:88: UserWarning: Tight layout not applied. The bottom and top margins cannot be made large enough to accommodate all axes decorations.
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
It seems like the annotate function is calculating some totally wacky coordinates for your annotations, though the text ends up in the right spot. If you remove them, the white space disappears. You can try calculating the xy coordinates a for your annotations a different way. This might get you started:
axes[i].annotate('{:.2f}'.format(height),
xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0,offset),
textcoords="offset points",
xycoords="axes points", # change
ha='center', va=va, color=color)
Output:
To correctly calculate the points, you can try using the appropriate axis transformation, though again, I couldn't get it to work and it might be related to a bug.
try putting the fig.tight_layout(rect=[0, 0.03, 1, 0.95]) after all the plotting commands, as below.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.patches import Patch
from matplotlib import rcParams
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['Arial']
department = ["100", "1,000", "10,000", \
"100,000", "1,000,000"]
quarter = ["Serial", "MPI", "CUDA", "Hybrid"]
budgets = np.array([[0.049979, 0.43584, 2.787366, 19.75062, 201.6935],\
[2.184624, 0.175213, 0.677837, 5.265575, 46.33678],\
[0.050294, 0.068537, 0.23739, 1.93778, 18.55734],\
[3.714284, 3.9917, 4.977599, 6.174967, 37.732232]])
budgets = np.transpose(budgets)
em = np.zeros((len(department), len(quarter)))
# set up barchart
x = np.arange(len(department)) # label locations
width = 0.8 # width of all the bars
# set up figure
fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(1, 5)
axes = [ax1, ax2, ax3, ax4, ax5]
# generate bars
rects = []
color = ["tomato", "royalblue", "limegreen", "orange"]
n = len(quarter)
for i in range(n):
bar_x = x - width/2.0 + i/float(n)*width + width/(n*2)
m = len(budgets[:,i])
for j in range(m):
bar_x = x[j] - width/2.0 + i/float(n)*width + width/(n*2)
e = budgets[j,i]
#bar_x = x - width/2.0 + i/float(n)*width + width/(n*2)
rects.append(axes[j].bar(bar_x, e, width=width/float(n), \
label=quarter[i], color=color[i]))
# set figure properties
fig.set_size_inches(12, 2.5)
#fig.tight_layout(rect=[0, 0.03, 1, 0.95])
nAx = len(axes)
for i in range(nAx):
#axes[i].set_aspect("auto")
axes[i].tick_params(axis='x', which='both', bottom=False, top=False,
labelbottom=False)
ax1.set_ylabel("Time (ms)")
for i in range(nAx):
axes[i].yaxis.grid(which="major", color="white", lw=0.75)
ax1.set_ylim([0, 4])
fig.suptitle("Time per iteration for differing dataset sizes") # title
for i in range(nAx):
axes[i].set_xlabel(department[i])
# annotate bars
for i in range(nAx):
for rect in rects:
j = 0;
for bar in rect:
y_bottom, y_top = axes[i].get_ylim() # axis limits
height = bar.get_height() # bar's height
va = 'bottom'
offset = 3
color = 'k'
fg = 'w'
# keep label within plot
if (y_top < 1.1 * height):
offset = -3
va = 'top'
color='w'
fg = 'k'
# annotate the bar
axes[i].annotate('{:.2f}'.format(height),
xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0,offset),
textcoords="offset points",
ha='center', va=va, color=color)
# set custom legend
legend_elements = [Patch(facecolor='tomato', label='Serial'),
Patch(facecolor='royalblue', label='MPI'),
Patch(facecolor='limegreen', label='CUDA'),
Patch(facecolor='orange', label='Hybrid')]
plt.legend(handles=legend_elements, loc="upper center", fancybox=False,
edgecolor='k', ncol=4, bbox_to_anchor=(-2, -0.1))
fig.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
I am using python to plot and my codes are:
import matplotlib.pyplot as plt
import numpy as np
# these are the data to be plot
x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14]
x_test = ['grid50', 'grid100', 'grid150', 'grid250', 'grid500', 'grid750', 'NN5', 'NN10', 'NN15', 'NN20', 'NN50', 'NN100', 'CB', 'CBG']
clf = [0.58502, 0.60799, 0.60342, 0.59629, 0.56464, 0.53757, 0.62567, 0.63429, 0.63583, 0.63239, 0.63315, 0.63156, 0.60630, 0.52755]
hitrate = [0.80544, 0.89422, 0.94029, 0.98379, 0.99413, 0.99921, 0.99478, 0.99961, 0.99997, 0.99980, 0.99899, 0.99991, 0.88435, 1.0]
level = [23.04527, 9.90955, 4.35757, 1.46438, 0.51277, 0.15071, 1.30057, 0.00016, 0.00001, 0.00021, 0.00005, 0.00004, 6.38019, 0]
fig = plt.figure(figsize=(20,7))
ax = fig.add_subplot(111)
fig.subplots_adjust(right=0.8)
# this is the function to put annotation on bars
def autolabel(rects):
# attach some text labels
for ii,rect in enumerate(rects):
height = rect.get_height()
plt. text(rect.get_x()+rect.get_width()/2., 1.02*height, '%s'% (clf[ii]),ha='center', va='bottom')
plt.xticks(x,x_test)
# this part is to plot the red bar charts
ins1 = ax.bar(x,clf,color='Red', align='center',label='classification results')
ax.set_ylabel('classification results', color='Red')
ax.tick_params(axis='y',colors='Red')
ax.set_ylim(0,1.5)
autolabel(ins1)
# this part is to plot the green hitrate and the for-loop is to put annotation next to the line
ax2 = ax.twinx()
ins2, = ax2.plot(x,hitrate,marker='o',color='Green', linewidth=3.0, label='hitrate')
ax2.set_ylabel('hitrate', color='Green')
ax2.tick_params(axis='y',colors='Green')
ax2.set_ylim(0,1.5)
for i,j in zip(x, hitrate):
ax2.annotate(str(j),xy=(i,j+0.02))
# this part is to plot the blue level, forloop same as that of hitrate
ax3 = ax.twinx()
axes = [ax, ax2, ax3]
ax3.spines['right'].set_position(('axes', 1.1))
ax3.set_frame_on(True)
ax3.patch.set_visible(False)
ins3, = ax3.plot(x,level,marker='^', color='Blue', linewidth=3.0, label='obfuscation level')
ax3.set_ylabel('obfuscation level', color='Blue')
ax3.tick_params(axis='y',colors='Blue')
ax3.set_ylim(0,25)
for i,j in zip(x, level):
ax3.annotate(str(j),xy=(i,j+0.02))
ax.set_xlabel('Cell Configurations')
ax.set_xlim(0,15)
ax.set_title('benchmark')
ax.legend([ins1,ins2,ins3],['clf', 'hit', 'level'])
plt.grid()
plt.show()
And I got a figure like :
The problem is that, some numbers are not put in a good place so to be read clearly, but I don't know whether there is a method to put the annotation naturally at a blank area. Any ideas?