Related
I am writing code for my internship and I am trying to modify the font size on my axes so that it can look a bit better. The section that does not seem to be working is this part.
ax.set_xticks([-95, -97, -99, -101, -103])
ax.set_yticks([33, 34, 35, 36, 37])
secax = ax.secondary_yaxis(1.0)
secax.set_yticks([33, 34, 35, 36, 37])
ax.tick_params(axis = 'both', labelsize = 16)
When I run all of my code x axis and first y axis font size changes fine but my secondary y axis font size does not change. Is there any way I can change the font size of my secondary y axis?
This is the output I get when I run the code:
In your code, ax.tick_params(axis = 'both', labelsize = 16) changes font size for primary axes. To set fonts for secondary axis add the line secax.tick_params(labelsize=16).
Here's a working MRE
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
xdata = list(range(10))
yF = 85 + 10*np.random.random((10,1))
def fahrenheit_to_celsius(x):
return (x - 32) / 1.8
def celsius_to_fahrenheit(x):
return x * 1.8 + 32
yC = (yF-32)/1.8
ax.plot(xdata, yF)
secax = ax.secondary_yaxis('right', functions = (fahrenheit_to_celsius, celsius_to_fahrenheit))
ax.tick_params(axis = 'both', labelsize = 16)
secax.tick_params(labelsize = 16)
plt.show()
Another strategy is to add a block of code near the top of your file to control font sizes. I don't remember where I found this code, but it comes in handy. Interestingly setting xtick or ytick labelsize also works for secondary axes:
SMALL_SIZE = 10
MEDIUM_SIZE = 16
BIGGER_SIZE = 18
plt.rc('font', size=SMALL_SIZE) # controls default text sizes
plt.rc('axes', titlesize=MEDIUM_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=MEDIUM_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=MEDIUM_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title
plt.rc('axes', titleweight='bold') # fontsize of the axes title
plt.rc('axes', labelweight='bold') # fontsize of the x and y labels
I'm trying to change a colorbar attached to a scatter plot so that the minimum and maximum of the colorbar are the minimum and maximum of the data, but I want the data to be centred at zero as I'm using a colormap with white at zero. Here is my example
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 1, 61)
y = np.linspace(0, 1, 61)
C = np.linspace(-10, 50, 61)
M = np.abs(C).max() # used for vmin and vmax
fig, ax = plt.subplots(1, 1, figsize=(5,3), dpi=150)
sc=ax.scatter(x, y, c=C, marker='o', edgecolor='k', vmin=-M, vmax=M, cmap=plt.cm.RdBu_r)
cbar=fig.colorbar(sc, ax=ax, label='$R - R_0$ (mm)')
ax.set_xlabel('x')
ax.set_ylabel('y')
As you can see from the attached figure, the colorbar goes down to -M, where as I want the bar to just go down to -10, but if I let vmin=-10 then the colorbar won't be zerod at white. Normally, setting vmin to +/- M when using contourf the colorbar automatically sorts to how I want. This sort of behaviour is what I expect when contourf uses levels=np.linspace(-M,M,61) rather than setting it with vmin and vmax with levels=62. An example showing the default contourf colorbar behaviour I want in my scatter example is shown below
plt.figure(figsize=(6,5), dpi=150)
plt.contourf(x, x, np.reshape(np.linspace(-10, 50, 61*61), (61,61)),
levels=62, vmin=-M, vmax=M, cmap=plt.cm.RdBu_r)
plt.colorbar(label='$R - R_0$ (mm)')
Does anyone have any thoughts? I found this link which I thought might solve the problem, but when executing the cbar.outline.set_ydata line I get this error AttributeError: 'Polygon' object has no attribute 'set_ydata' .
EDIT a little annoyed that someone has closed this question without allowing me to clarify any questions they might have, as none of the proposed solutions are what I'm asking for.
As for Normalize.TwoSlopeNorm, I do not want to rescale the smaller negative side to use the entire colormap range, I just want the colorbar attached to the side of my graph to stop at -10.
This link also does not solve my issue, as it's the TwoSlopeNorm solution again.
After changing the ylim of the colorbar, the rectangle formed by the surrounding spines is too large. You can make this outline invisible. And then add a new rectangular border:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 1, 61)
y = np.linspace(0, 1, 61)
C = np.linspace(-10, 50, 61)
M = np.abs(C).max() # used for vmin and vmax
fig, ax = plt.subplots(1, 1, figsize=(5, 3), dpi=150)
sc = ax.scatter(x, y, c=C, marker='o', edgecolor='k', vmin=-M, vmax=M, cmap=plt.cm.RdBu_r)
cbar = fig.colorbar(sc, ax=ax, label='$R - R_0$ (mm)')
cb_ymin = C.min()
cb_ymax = C.max()
cb_xmin, cb_xmax = cbar.ax.get_xlim()
cbar.ax.set_ylim(cb_ymin, cb_ymax)
cbar.outline.set_visible(False) # hide the surrounding spines, which are too large after set_ylim
cbar.ax.add_patch(plt.Rectangle((cb_xmin, cb_ymin), cb_xmax - cb_xmin, cb_ymax - cb_ymin,
fc='none', ec='black', clip_on=False))
plt.show()
Another approach until v3.5 is released is to make a custom colormap that does what you want (see also https://matplotlib.org/stable/tutorials/colors/colormap-manipulation.html#sphx-glr-tutorials-colors-colormap-manipulation-py)
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
from matplotlib.colors import ListedColormap
fig, axs = plt.subplots(2, 1)
X = np.random.randn(32, 32) + 2
pc = axs[0].pcolormesh(X, vmin=-6, vmax=6, cmap='RdBu_r')
fig.colorbar(pc, ax=axs[0])
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
from matplotlib.colors import ListedColormap
fig, axs = plt.subplots(2, 1)
X = np.random.randn(32, 32) + 2
pc = axs[0].pcolormesh(X, vmin=-6, vmax=6, cmap='RdBu_r')
fig.colorbar(pc, ax=axs[0])
def keep_center_colormap(vmin, vmax, center=0):
vmin = vmin - center
vmax = vmax - center
dv = max(-vmin, vmax) * 2
N = int(256 * dv / (vmax-vmin))
RdBu_r = cm.get_cmap('RdBu_r', N)
newcolors = RdBu_r(np.linspace(0, 1, N))
beg = int((dv / 2 + vmin)*N / dv)
end = N - int((dv / 2 - vmax)*N / dv)
newmap = ListedColormap(newcolors[beg:end])
return newmap
newmap = keep_center_colormap(-2, 6, center=0)
pc = axs[1].pcolormesh(X, vmin=-2, vmax=6, cmap=newmap)
fig.colorbar(pc, ax=axs[1])
plt.show()
I am having a multicolored line plot and I want to add a color bar under it in the same figure like as shown in the image below, Is it possible?
I have attached a color bar image as a reference which I took from another code.
My intention here is to use the color bar like a legend for each segment of the line in the plot.
Edit-1: I want to have the color bar using a mappable object such as an image, So don't want to create a new subplot for the sole purpose of the color bar.
Any suggestion is welcome. Thanks in Advance.
This is the code for multicolored line plot
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
Segments=[[[3,1],[6,1]],[[6,2],[9,2]],[[9,3],[12,3]],[[12,4],[15,4]], [[12,4],[15,4]]]
Points_1 = np.concatenate([Segments[:-1], Segments[1:]], axis=1)
lc = LineCollection(Points_1, colors=['r','g','b','y'], linewidths=2)
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
plt.show()
This is a workaround I'am using:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import matplotlib.colorbar as mcolorbar
import matplotlib.colors as mcolors
Segments=[[[3,1],[6,1]],[[6,2],[9,2]],[[9,3],[12,3]],[[12,4],[15,4]], [[12,4],[15,4]]]
Points_1 = np.concatenate([Segments[:-1], Segments[1:]], axis=1)
lc = LineCollection(Points_1, colors=['r','g','b','y'], linewidths=2)
fig, ax = plt.subplots(2, 1, gridspec_kw={'height_ratios' : [5,1]})
ax[0].add_collection(lc)
bounds = np.linspace(0, 1, 5)[:-1]
labels = ['Action1', 'Action2', 'Action3', 'Action4']
ax[0].set_xlim([0, 15])
ax[0].set_ylim([0, 10])
cb2 = mcolorbar.ColorbarBase(ax = ax[1], cmap = cmap, orientation = 'horizontal', extendfrac='auto')
cb2.set_ticks(bounds)
cb2.set_ticklabels(labels)
plt.tight_layout()
plt.show()
If you specifically want to avoid subplots, you can use a scalar mappable:
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
cmap = mcolors.ListedColormap(['r','g','b','y'])
sm = plt.cm.ScalarMappable(cmap=cmap)
sm.set_array([]) # this line may be ommitted for matplotlib >= 3.1
cbar = fig.colorbar(sm, ax=ax, orientation='horizontal',aspect=90)
bounds = np.linspace(0, 1, 5)[:-1]
labels = ['Action1', 'Action2', 'Action3', 'Action4']
ax.set_xlim([0, 15])
ax.set_ylim([0, 10])
cbar.set_ticks(bounds)
cbar.set_ticklabels(labels)
plt.tight_layout()
plt.show()
This helped me to get what I asked.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.collections import LineCollection
Segments=[[[3,1],[6,1]],[[6,2],[9,2]],[[9,3],[12,3]],[[12,4],[15,4]], [[12,4],[15,4]]]
Points_1 = np.concatenate([Segments[:-1], Segments[1:]], axis=1)
lc = LineCollection(Points_1, colors=['r','g','b','y'], linewidths=2)
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
c=[1,2,3,4,5]
labels = ['Action1', 'Action2', 'Action3', 'Action4']
cmap = mcolors.ListedColormap(['r','g','b','y'])
norm = mcolors.BoundaryNorm([1,2,3,4,5],4)
sm = plt.cm.ScalarMappable(norm=norm, cmap=cmap)
sm.set_array([]) # this line may be ommitted for matplotlib >= 3.1
cbar=fig.colorbar(sm, ticks=c, orientation='horizontal')
cbar.set_ticklabels(['Action1', 'Action2', 'Action3', 'Action4'])
plt.show()
I have a plot with a background grid. I need grid cells to be square (both major grid and minor grid cells) even though the limits of X and Y axes are different.
My current code is as follows:
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
import numpy as np
data = [0.014, 0.84, 0.95, -0.42, -0.79, 0.84, 0.98, 1.10, 0.56, -0.49]
fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()
# Set major and minor grid lines on X
ax.set_xticks(np.arange(0, 10, 0.2))
ax.xaxis.set_minor_locator(plticker.MultipleLocator(base=0.2 / 5.))
for xmaj in ax.xaxis.get_majorticklocs():
ax.axvline(x=xmaj, ls='-', color='red', linewidth=0.8)
for xmin in ax.xaxis.get_minorticklocs():
ax.axvline(x=xmin, ls=':', color='red', linewidth=0.6)
# Set major and minor grid lines on Y
ylim = int(np.ceil(max(abs(min(data)), max(data))))
yticks = np.arange(-ylim, ylim + 0.5, 0.5)
ax.set_yticks(yticks)
ax.yaxis.set_minor_locator(plticker.MultipleLocator(base=0.5 / 5.))
for ymaj in ax.yaxis.get_majorticklocs():
ax.axhline(y=ymaj, ls='-', color='red', linewidth=0.8)
for ymin in ax.yaxis.get_minorticklocs():
ax.axhline(y=ymin, ls=':', color='red', linewidth=0.6)
ax.axis([0, 10, -ylim, ylim])
fig.tight_layout()
# Plot
ax.plot(data)
# Set equal aspect ratio NOT WORKING
plt.gca().set_aspect('equal', adjustable='box')
plt.show()
Which generates the following plot:
Large grid cells contain 5 smaller cells each. However, the aspect ratio of large grid is not 1.
Question: How can I make sure that large grid is square?
EDIT
Current approach is to set same tick locations as suggested by #ImportanceOfBeingErnest, but change Y labels:
ylim = int(np.ceil(max(abs(min(data)), max(data))))
yticks = np.arange(-ylim, ylim + 0.2, 0.2)
ax.set_yticks(yticks)
labels = ['{:.1f}'.format(v if abs(v) < 1e-3 else (1 if v > 0 else -1)*((0.5 - abs(v)%0.5) + abs(v)))
if i%2==0 else "" for i, v in enumerate(np.arange(-ylim, ylim, 0.2))]
ax.set_yticklabels(labels)
Result: seems too hacky.
When using equal aspect ratio and aiming for a square grid you would need to use the same tickspacing for both axes. This can be achieved with a MultipleLocator where the interval needs to be the same for x and y axis.
In general, grids can be created with the grid command.
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
data = [0.014, 0.84, 0.95, -0.42, -0.79, 0.84, 0.98, 1.10, 0.56, -0.49]
fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()
# Set major and minor grid lines on X
ax.xaxis.set_major_locator(mticker.MultipleLocator(base=.5))
ax.xaxis.set_minor_locator(mticker.MultipleLocator(base=0.5 / 5.))
ax.yaxis.set_major_locator(mticker.MultipleLocator(base=.5))
ax.yaxis.set_minor_locator(mticker.MultipleLocator(base=0.5 / 5.))
ax.grid(ls='-', color='red', linewidth=0.8)
ax.grid(which="minor", ls=':', color='red', linewidth=0.6)
## Set limits
ylim = int(np.ceil(max(abs(min(data)), max(data))))
ax.axis([0, 10, -ylim, ylim])
plt.gca().set_aspect('equal', adjustable='box')
fig.tight_layout()
# Plot
ax.plot(data)
plt.show()
If you instead want to have different tick spacings with square major cells in the grid, you would need to give up the equal aspect ratio and instead set it to the quotient of the tick spacings.
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np
data = [0.014, 0.84, 0.95, -0.42, -0.79, 0.84, 0.98, 1.10, 0.56, -0.49]
fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()
xm = 0.2
ym = 0.25
# Set major and minor grid lines on X
ax.xaxis.set_major_locator(mticker.MultipleLocator(base=xm))
ax.xaxis.set_minor_locator(mticker.MultipleLocator(base=xm / 5.))
ax.yaxis.set_major_locator(mticker.MultipleLocator(base=ym))
ax.yaxis.set_minor_locator(mticker.MultipleLocator(base=ym / 5.))
ax.grid(ls='-', color='red', linewidth=0.8)
ax.grid(which="minor", ls=':', color='red', linewidth=0.6)
## Set limits
ylim = int(np.ceil(max(abs(min(data)), max(data))))
ax.axis([0, 10, -ylim, ylim])
plt.gca().set_aspect(xm/ym, adjustable='box')
fig.tight_layout()
# Plot
ax.plot(data)
plt.show()
To then get rid of every second ticklabel, an option is
fmt = lambda x,p: "%.2f" % x if not x%(2*ym) else ""
ax.yaxis.set_major_formatter(mticker.FuncFormatter(fmt))
You should be able to achieve this by using the same locator for the both axis. However matplotlib has a limitation currently, so here's a workaround:
# matplotlib doesnt (currently) allow two axis to share the same locator
# so make two wrapper locators and combine their view intervals
def share_locator(locator):
class _SharedLocator(matplotlib.ticker.Locator):
def tick_values(self, vmin, vmax):
return locator.tick_values(vmin, vmax)
def __call__(self):
min0, max0 = shared_locators[0].axis.get_view_interval()
min1, max1 = shared_locators[1].axis.get_view_interval()
return self.tick_values(min(min0, min1), max(max0, max1))
shared_locators = (_SharedLocator(), _SharedLocator())
return shared_locators
Use like:
lx, ly = share_locator(matplotlib.ticker.AutoLocator()) # or any other locator
ax.xaxis.set_major_locator(lx)
ax.yaxis.set_major_locator(ly)
I am trying to plot a graph with two separate x-axis. One being some valve openning and the other the corresponding leak rate. I managed to make it work pretty well, though the format of that secondary axis doesn't always show scientific notations as seen on the figure down bellow
Awful overlapping labels, see the upper axis
How to force scientific notation display so that the labels wont overlap?
Here is the script I am using:
#HEADERS
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker
from matplotlib import rc
rc('font', **{'family':'sans-serif','sans-serif':['Helvetica']})
rc('text', usetex=True)
#/HEADERS
turns = np.array([11.000, 11.500, 11.750, 12.000, 12.250, 12.375])
leak = np.array([3.89e-05, 4.63e-05, 1.67e-04, 1.45000000e-03, 8.61e-03, 1.71e-02])
pressure1 = np.array([7.9e-07, 3.0e-06, 3.5e-05, 6.1e-04, 5.1e-03, 1.8e-02])
pressure2 = np.array([8.22e-07, 8.22e-07, 8.71e-07, 1.8e-06, 1.150e-05, 7.24e-05])
pressure3 = np.array([2e-06, 2e-06, 2e-06, 1.2e-05, 1.2e-04, 6e-04])
fig = plt.figure(num='valve', figsize = (6.68, 6.68*1.3))
fig, ax1 = plt.subplots()
ax1.plot(turns, pressure1, 'r.', label= '$P_1$')
ax1.plot(turns, pressure2, 'b.', label= '$P_2$')
ax1.plot(turns, pressure3,'k.', label= '$P_3$')
plt.legend()
plt.minorticks_on()
plt.grid(b = True, which = 'major', axis = 'both')
ax1.errorbar(turns, pressure1, yerr = .4*pressure1, fmt='none', ecolor = 'k', elinewidth = 1, capsize = 1, label= '$P_{1err}$')
ax1.errorbar(turns, pressure2, yerr = .15*pressure2, fmt='none', ecolor = 'k', elinewidth = 1, capsize = 1, label= '$P_{2err}$')
plt.rc('text', usetex=True)
plt.rc('font', family='serif')
ax1.set_yscale('log', nonposy = 'mask')
ax1.set_ylabel(r'$P$')
ax1.set_xscale('linear')
ax1.set_xlabel('Opening (turns)')
plt.minorticks_on()
#plt.grid(b = True, which = 'major', axis = 'both')
#adding a secondary x-axis above
ax2 = ax1.twiny()
ax2.set_xlim(ax1.get_xlim())
new_tick_locations = turns
new_tick_label = leak #dtype here ?
ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(new_tick_label)
# I tried those commands from other threads but they all result in an error.
#ax2.xaxis.set_scientific(True)
#ax2.get_xaxis().set_major_formatter((matplotlib.ticker.Formatter(set_scientific(True)))
#ax2.get_xaxis().set_major_formatter().set_scientific(True)
ax2.set_xlabel(r'Leak rate (mbar$\times$L/s)')
plt.tight_layout()
#export png
plt.savefig(('export.png'), format = 'png', transparent=False, dpi = 300)
plt.show()
I'm using Python 3.6.
Thanks for your help.
Since you override the ticks, you can format them yourself and rotate them as well for more space:
new_tick_label = ['{:5.2e}'.format(x) for x in leak]
ax2.set_xticks(new_tick_locations)
ax2.set_xticklabels(new_tick_label, rotation=30)
Result: