Matplotlib title and axis label padding - python

I'm just starting out experimenting with Matplotlib today. I've spent the last few hours trying to fix the positioning of the title and axis labels to no avail. I figured out to fix the spacing between the title and top of the chart and the axis labels and ticks using the padding parameter. I can't figure out how to make it so that the title is not crammed to top of the figure and the x/y axis labels aren't crammed to the left/bottom of the figure.
Below is an example that has nothing to do with my actual problem other than illustrating the formatting issue.
import matplotlib.pyplot as plt
import numpy as np
#create data
A = 5
f = 0.5
t = np.arange(0,10,0.01)
y = A * np.sin(2*np.pi*f*t)
#create plot
fig, ax = plt.subplots()
fig.set_size_inches(8*(16/9),8)
ax.plot(t,y)
#format plot
ax.spines.top.set_visible(False)
ax.spines.right.set_visible(False)
ax.set_title('Need this title to move down without moving into subplot so that it is not crammed on top',pad=20)
ax.set_ylabel('Need this label to move to the right',labelpad=20)
ax.set_xlabel('Need this label to move up',labelpad=20)
Any suggestions as to how to increase the margins between the outside of the title/labels and the edge of the figure would be greatly appreciated.

You can try something like that:
import matplotlib.pyplot as plt
import numpy as np
#create data
A = 5
f = 0.5
t = np.arange(0, 10, 0.01)
y = A * np.sin(2 * np.pi * f * t)
#create plot
fig, ax = plt.subplots()
fig.set_size_inches(8 * (16 / 9), 8)
ax.plot(t, y)
#format plot
ax.spines.top.set_visible(False)
ax.spines.right.set_visible(False)
ax.set_title("Title", y=-0.1)
ax.set_xlabel("x-label")
ax.xaxis.set_label_position("top")
ax.set_ylabel("y-label")
ax.yaxis.set_label_position("right")
If you want to move x/y-ticks on top/to the right as well, then use the following commands:
ax.xaxis.tick_top()
ax.yaxis.tick_right()
and then modify:
ax.spines.top.set_visible(False)
ax.spines.right.set_visible(False)
to
ax.spines.bottom.set_visible(False)
ax.spines.left.set_visible(False)

Related

How to put r- and theta-labels plus units on a polar Matplotlib plot (wedge diagram)?

I have plotted a wedge-diagram, but can not seem to figure out how to get labels on the axis and units on the values. The r-axis has to be Dec and the theta-axis has to be RA, both with units of degrees.
Here is my code, hope you can help:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
theta = (np.pi/180)*np.array([340.555906,3.592373,32.473440,33.171584,35.463857,44.268397,339.362504,345.211906,346.485567,346.811945,348.672405,349.180736,349.370850,353.098343])
r = np.array([-32.906663,-33.842402,-32.425917,-32.677975, -30.701083,-31.460307,-32.909861,-30.802969,-33.683759,-32.207783,-33.068686,-33.820102,-31.438195,-31.920375])
colors = 'black'
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
c = ax.scatter(theta, r,marker='.', c=colors, cmap='hsv', alpha=0.75)
ax.set_thetamin(55)
ax.set_thetamax(-45)
fmt = lambda x, pos: "{:g}".format(np.degrees(x if x >= 0 else x + 2 * np.pi))
ax.xaxis.set_major_formatter(ticker.FuncFormatter(fmt))
plt.show()
EDIT: I have tried setting the lables with:
ax.text(0,-29.9,s='RA')
ax.text(2,-29.5,s='Dec')
Which sort of works, but I can still not get units (degrees) on my values. I think the problem might have something to do with my limits, which has to go around zero, but I am not sure.

2nd scale with ticks being a function of first ticks at same position in python/matplotlib

I am using matplotlib in Python and want to use the same plot but with several different axes that are all functions of the first one, but that do not linearly depend on the first y value.
As an example, let's assume a plot that shows a simple line y=x.
Now I have a random function like f(y)=5y^2 + 2.
My ideal output graph should now still be a line, but the equidistant ticks should not be y=1, 2, 3, 4, but f(y)=7, 22, 47, 82, so that I can overlay the two graphs with 2 different axes.
Is this even possible, as the distance between the ticks is not even nor can it be expressed in a log plot? Therefore I simply want to put a function on each tick value, without changing the graph nor the ticks' positions.
In a graphics program this would be straightforward, by simply using the same plot and manually rewriting each tick.
https://drive.google.com/file/d/1fp2vrFvlz-9xdJPmqdQjyMQK7gzPX24G/view?usp=sharing
Thank you in advance! The example code is not really helpful, as it is just the standard matplotlib code but the most important scaling part is missing.
I know that I can set the ticks manually with yticks, but this does not solve the scaling problem and all ticks would appear very close together.
plt.plot(["time_max_axis"], ["position_max_axis"])
plt.xlabel("Time (ms)")
plt.ylabel("Max position (mm)")
plt.ylim(0, z0_mm)
plt.show()
plt.plot(["time_max_axis"], ["frequency_axis"])
plt.xlabel("Oscillation frequency (kHz)")
plt.ylabel("Max position (mm)")
plt.ylim(fion_kHz, fion_kHz * (1 + (f_shift4 + f_shift6) / 100))
plt.show()
import matplotlib.pyplot as plt
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)
x = np.arange(50)
y = x/10 + np.random.rand(50)
fig, axs = plt.subplots(1,2, gridspec_kw={'width_ratios': [1, 20]})
plt.subplots_adjust(wspace=0, hspace=0)
axs[1].plot(x, y)
axs[1].plot(x, 2*y)
axs[1].plot(x, 3*y)
axs[1].grid()
axs[1].set_ylim(0)
axs[1].set_xlim(0)
axs[1].set_ylabel('max displacement $z_{max}$ (mm)')
ymin, ymax = axs[1].get_ylim()
majorlocator = ymax // 8 # 8 horizontal grid lines
ytickloc = np.arange(0, int(ymax), majorlocator)
axs[1].yaxis.set_major_locator(MultipleLocator(majorlocator))
ax1 = axs[1].twinx() # ghost axis of axs[1]
ax1.yaxis.set_ticks_position('left')
ax1.set_yticks([ymin, ymax])
ax1.set_yticklabels(['', f'$z_0$ = {round(ymax,2)}'])
axs[0].spines['top'].set_visible(False)
axs[0].spines['right'].set_visible(False)
axs[0].spines['bottom'].set_visible(False)
axs[0].spines['left'].set_visible(False)
axs[0].set_xticks([])
axs[0].set_yticks(ytickloc)
ytick2 = 5 * ytickloc**2 + 2 # f = 5y^2 + 2
ytick2 = list(ytick2)
ymin2 = ytick2[0]
ytick2[0] = ''
axs[0].set_yticklabels(ytick2)
axs[0].set_ylim(ymin, ymax)
axs[0].set_ylim(0)
axs[0].set_ylabel('Oscillation frequency $f_{osc}$ (kHz)')
ymax2 = 5 * ymax**2 + 2 # f = 5y^2 + 2
ax0 = axs[0].twinx() # ghost axis of axs[0]
ax0.yaxis.set_ticks_position('left')
ax0.spines['top'].set_visible(False)
ax0.spines['right'].set_visible(False)
ax0.spines['bottom'].set_visible(False)
ax0.spines['left'].set_visible(False)
ax0.set_yticks([ymin, ymax])
ax0.set_yticklabels([f'$\\bf{{f_{{ion}}}} = {round(ymin2, 2)}$', f'$f_{{max}}$ = {round(ymax2,2)}'])
plt.tight_layout()
Output:

Matplotlib automatically scale vertical height of subplots for shared x-axis figure

I want to automatically scale the vertical height of subplots for shared x-axis figures based on their data span! I want to compare the relative intensity of the displayed data. If i use the sharey=True kwarg for the subbplots the data is displayed in a way that the relative intensity is recognizable:
import matplotlib.pyplot as plt
from matplotlib import gridspec
import numpy as np
SIZE = (12, 8) #desired overall figure size
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
y2 = 2*(np.sin(x ** 2))
y3 = 3*(np.sin(x ** 2))
fig, ax = plt.subplots(3,ncols=1, sharex=True, sharey=True)
fig.set_size_inches(SIZE[1], SIZE[0])
fig.subplots_adjust(hspace=0.001)
ax[0].plot(x, y)
ax[1].plot(x, y2)
ax[2].plot(x, y3)
plt.show()
All subplots have the same height now and the data span in the y-Axis is recognizable as the data is displayed with the correct relative proportion.
What i would like to achieve is that the scales of each plot end where the data ends. Essentially eliminating the not used white space. The size of the subplot would than represent the relative height ratios of the data. They should still have the same scaling on the Y axis in order for the viewer to estimate the relative data height ( which cold be a countrate for example).
I found the following links to similar problems but none really helped me to solve my issue:
Link1 Link2
Here an example that determines the ratio for you and creates the subplots accordingly:
import matplotlib.pyplot as plt
from matplotlib import gridspec
import numpy as np
SIZE = (12, 8) #desired overall figure size
# Simple data to display in various forms
x = np.linspace(0, 2 * np.pi, 400)
# the maximum multiplier for the function
N = 3
# the y-ranges:
ys = [i * np.sin(x**2) for i in range(1,N+1)]
# the maximum extent of the plot in y-direction (cast as int)
hs = [int(np.ceil(np.max(np.abs(y)))) for y in ys]
# determining the size of the GridSpec:
gs_size = np.sum(hs)
gs = gridspec.GridSpec(gs_size,1)
# the figure
fig = plt.figure(figsize = SIZE)
# creating the subplots
base = 0
ax = []
for y,h in zip(ys,hs):
ax.append(fig.add_subplot(gs[base:h+base,:]))
base += h
ax[-1].plot(x,y)
##fig, ax = plt.subplots(3,ncols=1, sharex=True, sharey=True)
##fig.set_size_inches(SIZE[1], SIZE[0])
fig.subplots_adjust(hspace=0.001)
##ax[0].plot(x, ys[0])
##ax[1].plot(x, ys[1])
##ax[2].plot(x, ys[2])
plt.show()
The code determines the maximum y-extend for each set of data, casts it into an integer and then divides the figure into subplots using the sum of these extends as scale for the GridSpec.
The resulting figure looks like this:
Tested on Python 3.5
EDIT:
If the maximum and minimum extents of your data are not comparable, it may be better to change the way hs is calculated into
hs = [int(np.ceil(np.max(y))) - int(np.floor(np.min(y))) for y in ys]

Matplotlib - set an axis label where there are no ticks

Hello Python/Matplotlib gurus,
I would like to label the y-axis at a random point where a particular horizontal line is drawn.
My Y-axis should not have any values, and only show major ticks.
To illustrate my request clearly, I will use some screenshots.
What I have currently:
What I want:
As you can see, E1 and E2 are not exactly at the major tick mark. Actually, I know the y-axis values (although they should be hidden, since it's a model graph). I also know the values of E1 and E2.
I would appreciate some help.
Let my code snippet be as follows:
ax3.axis([0,800,0,2500) #You can see that the major YTick-marks will be at 500 intervals
ax3.plot(x,y) #plot my lines
E1 = 1447
E2 = 2456
all_ticks = ax3.yaxis.get_all_ticks() #method that does not exist. If it did, I would be able to bind labels E1 and E2 to the respective values.
Thank you for the help!
Edit:
For another graph, I use this code to have various colors for the labels. This works nicely. energy_range, labels_energy, colors_energy are numpy arrays as large as my y-axis, in my case, 2500.
#Modify the labels and colors of the Power y-axis
for i, y in enumerate(energy_range):
if (i == int(math.floor(E1))):
labels_energy[i] = '$E_1$'
colors_energy[i] = 'blue'
elif (i == int(math.floor(E2))):
labels_energy[i] = '$E_2$'
colors_energy[i] = 'green'
else:
labels_energy.append('')
#Modify the colour of the energy y-axis ticks
for color,tick in zip(colors_energy,ax3.yaxis.get_major_ticks()):
print color, tick
if color:
print color
tick.label1.set_color(color) #set the color property
ax3.get_yaxis().set_ticklabels(labels_energy)
Edit2:
Full sample with dummy values:
#!/bin/python
import matplotlib
# matplotlib.use('Agg') #Remote, block show()
import numpy as np
import pylab as pylab
from pylab import *
import math
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
import matplotlib.font_manager as fm
from matplotlib.font_manager import FontProperties
import matplotlib.dates as mdates
from datetime import datetime
import matplotlib.cm as cm
from matplotlib.ticker import MultipleLocator, FormatStrFormatter
from scipy import interpolate
def plot_sketch():
x = np.arange(0,800,1)
energy_range = range (0,2500,1) #Power graph y-axis range
labels_energy = [''] * len(energy_range)
colors_energy = [''] * len(energy_range)
f1=4
P1=3
P2=2
P3=4
f2=2
f3=6
#Set Axes ranges
ax3.axis([0,800,0,energy_range[-1]])
#Add Energy lines; E=integral(P) dt
y=[i * P1 for i in x]
ax3.plot(x,y, color='b')
y = [i * P2 for i in x[:0.3*800]]
ax3.plot(x[:0.3*800],y, color='g')
last_val = y[-1]
y = [(i * P3 -last_val) for i in x[(0.3*800):(0.6*800)]]
ax3.plot(x[(0.3*800):(0.6*800)],y, color='g')
E1 = x[-1] * P1
E2 = (0.3 * x[-1]) * P2 + x[-1] * (0.6-0.3) * P3
#Modify the labels and colors of the Power y-axis
for i, y in enumerate(energy_range):
if (i == int(math.floor(E1))):
labels_energy[i] = '$E_1$'
colors_energy[i] = 'blue'
elif (i == int(math.floor(E2))):
labels_energy[i] = '$E_2$'
colors_energy[i] = 'green'
else:
labels_energy.append('')
#Modify the colour of the power y-axis ticks
for color,tick in zip(colors_energy,ax3.yaxis.get_major_ticks()):
if color:
tick.label1.set_color(color) #set the color property
ax3.get_yaxis().set_ticklabels(labels_energy)
ax3.axhline(energy_range[int(math.floor(E1))], xmin=0, xmax=1, linewidth=0.25, color='b', linestyle='--')
ax3.axhline(energy_range[int(math.floor(E2))], xmin=0, xmax=0.6, linewidth=0.25, color='g', linestyle='--')
#Show grid
ax3.xaxis.grid(True)
#fig = Sketch graph
fig = plt.figure(num=None, figsize=(14, 7), dpi=80, facecolor='w', edgecolor='k')
fig.canvas.set_window_title('Sketch graph')
ax3 = fig.add_subplot(111) #Energy plot
ax3.set_xlabel('Time (ms)', fontsize=12)
ax3.set_ylabel('Energy (J)', fontsize=12)
pylab.xlim(xmin=0) # start at 0
plot_sketch()
plt.subplots_adjust(hspace=0)
plt.show()
I think you're looking for the correct transform (check this out). In your case, what I think you want is to simply use the text method with the correct transform kwarg. Try adding this to your plot_sketch function after your axhline calls:
ax3.text(0, energy_range[int(math.floor(E1))],
'E1', color='g',
ha='right',
va='center',
transform=ax3.get_yaxis_transform(),
)
ax3.text(0, energy_range[int(math.floor(E2))],
'E2', color='b',
ha='right',
va='center',
transform=ax3.get_yaxis_transform(),
)
The get_yaxis_transform method returns a 'blended' transform which makes the x values input to the text call be plotted in axes units, and the y data in 'data' units. You can adjust the value of the x-data, (0) to be -0.003 or something if you want a little padding (or you could use a ScaledTranslation transform, but that's generally unnecessary if this is a one-off fix).
You'll probably also want to use the 'labelpad' option for set_ylabel, e.g.:
ax3.set_ylabel('Energy (J)', fontsize=12, labelpad=20)
I think my answer to a different post might be of help to you:
Matplotlib: Add strings as custom x-ticks but also keep existing (numeric) tick labels? Alternatives to matplotlib.pyplot.annotate?
It also works for the y-axis.Here is the result:

Updating a matplotlib bar graph?

I have a bar graph which retrieves its y values from a dict. Instead of showing several graphs with all the different values and me having to close every single one, I need it to update values on the same graph. Is there a solution for this?
Here is an example of how you can animate a bar plot.
You call plt.bar only once, save the return value rects, and then call rect.set_height to modify the bar plot.
Calling fig.canvas.draw() updates the figure.
import matplotlib
matplotlib.use('TKAgg')
import matplotlib.pyplot as plt
import numpy as np
def animated_barplot():
# http://www.scipy.org/Cookbook/Matplotlib/Animations
mu, sigma = 100, 15
N = 4
x = mu + sigma*np.random.randn(N)
rects = plt.bar(range(N), x, align = 'center')
for i in range(50):
x = mu + sigma*np.random.randn(N)
for rect, h in zip(rects, x):
rect.set_height(h)
fig.canvas.draw()
fig = plt.figure()
win = fig.canvas.manager.window
win.after(100, animated_barplot)
plt.show()
I've simplified the above excellent solution to its essentials, with more details at my blogpost:
import numpy as np
import matplotlib.pyplot as plt
numBins = 100
numEvents = 100000
file = 'datafile_100bins_100000events.histogram'
histogramSeries = np.loadtext(file)
fig, ax = plt.subplots()
rects = ax.bar(range(numBins), np.ones(numBins)*40) # 40 is upper bound of y-axis
for i in range(numEvents):
for rect,h in zip(rects,histogramSeries[i,:]):
rect.set_height(h)
fig.canvas.draw()
plt.pause(0.001)

Categories

Resources