Python Matplotlib Twinx() cursor values - python

I want to use the cursor (x,y values get displayed at the bottom left of figure) to measure the y and x distance between two points, however this only works for the data plotted on the second axis.
Is there a way to switch back an forth between the second axis and first y-axis?
Please note: I do not want a programmatic way of getting distance between points, just to use the cursor when I am viewing data in the figure plot.
Not sure if this helps but my code is literally the example for plotting two axes from the matplotlib page:
fig, ax1 = plt.subplots()
ax1.plot(sensor1, 'b-')
ax1.set_xlabel('(time)')
# Make the y-axis label and tick labels match the line color.
ax1.set_ylabel('Sensor 1', color='b')
for tl in ax1.get_yticklabels():
tl.set_color('b')
ax2 = ax1.twinx()
ax2.plot(sensor2, 'r.')
ax2.set_ylabel('Sensor 2', color='r')
for tl in ax2.get_yticklabels():
tl.set_color('r')
plt.show()

You can use the excellent answer here to get both coordinates displayed at the same time. In order to get distance between two points, you can then combine this idea with ginput to map from one to the other and add the result as a title,
import matplotlib.pyplot as plt
import numpy as np
#Provide other axis
def get_othercoords(x,y,current,other):
display_coord = current.transData.transform((x,y))
inv = other.transData.inverted()
ax_coord = inv.transform(display_coord)
return ax_coord
#Plot the data
fig, ax1 = plt.subplots()
t = np.linspace(0,2*np.pi,100)
ax1.plot(t, np.sin(t),'b-')
ax1.set_xlabel('(time)')
ax1.set_ylabel('Sensor 1', color='b')
for tl in ax1.get_yticklabels():
tl.set_color('b')
ax2 = ax1.twinx()
ax2.plot(t,3.*np.cos(t),'r-')
ax2.set_ylabel('Sensor 2', color='r')
for tl in ax2.get_yticklabels():
tl.set_color('r')
#Get user input
out = plt.ginput(2)
#2nd axis from input
x2b, x2t = out[0][0], out[1][0]
y2b, y2t = out[0][1], out[1][1]
#Draw line
ax2.plot([x2b, x2t],[y2b, y2t],'k-',lw=3)
#1st axis from transform
x1b, y1b = get_othercoords(x2b,y2b,ax2,ax1)
x1t, y1t = get_othercoords(x2t,y2t,ax2,ax1)
plt.title("Distance x1 = " + str(x1t-x1b) + " y1 = " + str(y1t-y1b) + "\n"
"Distance x2 = " + str(x2t-x2b) + " y2 = " + str(y2t-y2b))
plt.draw()
plt.show()
which gives something like,

Related

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:

Make legend stay at bottom matplotlib python

I have a graph with about 30 different curves and I want to show them all in the legend. But it gets too long and they don't all fit. I want to put them down the bottom, how do I do this?
fig = plt.figure()
for i in range(len(data)):
print(i)
x1 = data.loc[0][5:-4]
y1 = data.loc[i][5:-4]
y1.replace(' ',100.0,inplace=True)
x = list(reversed(x1))
y = list(reversed(y1))
report_num = data.loc[i,'Report No']
plt.plot(x, y, label = report_num)
plt.xscale('log')
plt.grid()
plt.yticks(yints)
plt.xticks(x,x,rotation=40)
plt.title('Particle Size Distribution Curve - %s'%(report_num))
plt.legend(loc=1)

Embed a small radarchart into matplotlib plot

Right now I can create a radarchart as follows. Note that I made it a function just so that I can simply insert the function into my larger scatterplot more cleanly.
Radar Chart
def radarChart(PlayerLastName):
playerdf = dg.loc[dg['Player Name'] == name].index.tolist()[0]
#print(playerdf)
labels=np.array(['SOG', 'SH', 'G', 'A'])
stats=dg.loc[playerdf,labels].values
#print(stats)
# Set the angle of polar axis.
# And here we need to use the np.concatenate to draw a closed plot in radar chart.
angles=np.linspace(0, 2*np.pi, len(labels), endpoint=False)
# close the plot
stats=np.concatenate((stats,[stats[0]]))
angles=np.concatenate((angles,[angles[0]]))
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
ax.plot(angles, stats, 'o-', linewidth=1)
ax.fill(angles, stats, alpha=0.3)
ax.set_thetagrids(angles * 180/np.pi, labels)
#plt.title(PlayerLastName + ' vs. ' + namegame)
ax.grid(True)
return
I then want to put this figure in the bottom right of my scatter plot I have elsewhere. This other article does not provide me with any way to do this since my plot is circular. Any help would be great!
When I call radarChart('someones name') I get
I would really like to not have to save it as an image first and then put it in the plot.
I am not sure, what your desired output is. You should always provide a Minimal, Complete, and Verifiable example. Apart from this, I don't know, why a polar plot would be different from any other plot to create an inset:
import matplotlib.pyplot as plt
import numpy as np
#function for the polar plot
def radarChart(Player = "SOG", left = .3, bottom = .6, width = .2, height = .2):
#labels and positions
labels = np.array(['SOG', 'SH', 'G', 'A'])
angles = np.linspace(0, 360, len(labels), endpoint = False)
#inset position
ax = plt.axes([left, bottom, width, height], facecolor = "lightblue", polar = True)
#label polar chart
ax.set_thetagrids(angles, labels)
#polar chart title
plt.title(Player, loc = "left")
return ax
#main figure
x = np.linspace (-3, 1, 1000)
y = 2 * np.exp(3 - x) - 1
plt.plot(x, y)
plt.xlabel("x values")
plt.ylabel("y values")
plt.title("figure with polar insets")
#inset 1
ax = radarChart(Player = "A")
plt.scatter(x[::50], y[::50])
#inset 2
ax = radarChart(left = .6, bottom = .4, width = .2, height = .2)
plt.plot(x, y)
plt.show()
Sample output:

How to have a secondary y axis in a nested GridSpec?

I'd like to obtain this figure:
But with two plots inside each graph, like this:
Here is a sample of the code I used for the first figure
measures = ['ACE', 'SCE', 'LZs', 'LZc']
conditions = ['dark','light','flick3','flick10','switch']
outer_grid = gridspec.GridSpec(2,2)
for measure in measures:
inner_grid = gridspec.GridSpecFromSubplotSpec(5, 1, subplot_spec=outer_grid[measures.index(measure)])
ax={}
for cond in conditions:
c=conditions.index(cond)
ax[c] = plt.Subplot(fig, inner_grid[c])
if c != 0:
ax[c].get_shared_y_axes().join(ax[0], ax[c])
ax[c].plot()
ax[c+n]=ax[c].twinx()
ax[c+n].scatter()
ax[c+n].set_ylim(0,5)
fig.add_subplot(ax[c],ax[c+n])
For the second plot, it's basically the same without the first loop and GridSpec, using ax[c]=plt.subplot('51{c}') instead of ax[c]=plt.Subplot(fig, inner_grid[c]).
As you can see, when using GridSpec I still have the secondary y axis but not the scatter plot associated.
I guess the short question would be How to write fig.add_subplot(ax[c],ax[c+n]) properly?
(fig.add_subplot(ax[c]) fig.add_subplot(ax[c+n]) in two lines doesn't work.)
It is not clear from your question exactly which data you're plotting in each subplot, plus the way you're creating your subplots seems a little convoluted, which is probably why you're having problems. Here is how I would do it:
import matplotlib.gridspec as gs
measures = ['ACE', 'SCE', 'LZs', 'LZc']
conditions = ['dark','light','flick3','flick10','switch']
colors = ['g','c','b','r','grey']
Npoints = 10
data = [np.random.random((Npoints,len(measures))) for i in range(len(conditions))]
gs00 = gs.GridSpec(len(conditions), 1)
fig = plt.figure(figsize=(5,5))
for i,condition in enumerate(conditions):
ax1 = fig.add_subplot(gs00[i])
ax2 = ax1.twinx()
ax1.plot(range(Npoints), data[i][:,0], 'o-', color=colors[i], label=measures[0])
ax2.plot(range(Npoints), data[i][:,1], 'o-.', color=colors[i], label=measures[1])
ax1.set_ylim((-0.1,1.1))
ax2.set_ylim(ax1.get_ylim())
ax1.set_title(condition)
EDIT to get the same thing repeated 4 times, the logic is exactly the same, you just have to play around with the gridspec. But the only things that matters are the lines ax1 = fig.add_subplot(gs01[j]) followed by ax2 = ax1.twinx(), which will create a second axis on top of the first
import matplotlib.gridspec as gs
measures = ['ACE', 'SCE', 'LZs', 'LZc']
conditions = ['dark','light','flick3','flick10','switch']
colors = ['g','c','b','r','grey']
Npoints = 10
data = [np.random.random((Npoints,len(measures))) for i in range(len(conditions))]
gs00 = gs.GridSpec(2,2)
plt.style.use('seaborn-paper')
fig = plt.figure(figsize=(10,10))
grid_x, grid_y = np.unravel_index(range(len(measures)),(2,2))
for i,measure in enumerate(measures):
gs01 = gs.GridSpecFromSubplotSpec(len(conditions), 1, subplot_spec=gs00[grid_x[i],grid_y[i]])
for j,condition in enumerate(conditions):
ax1 = fig.add_subplot(gs01[j])
ax2 = ax1.twinx()
ax1.plot(range(Npoints), data[j][:,0], 'o-', color=colors[j], label=measures[0])
ax2.plot(range(Npoints), data[j][:,1], 'o-.', color=colors[j], label=measures[1])
ax1.set_ylim((-0.1,1.1))
ax2.set_ylim(ax1.get_ylim())
if j==0:
ax1.set_title(measure)

Laying out several plots in matplotlib + numpy

I am pretty new to python and want to plot a dataset using a histogram and a heatmap below. However, I am a bit confused about
How to put a title above both plots and
How to insert some text into bots plots
How to reference the upper and the lower plot
For my first task I used the title instruction, which inserted a caption in between both plots instead of putting it above both plots
For my second task I used the figtext instruction. However, I could not see the text anywhere in the plot. I played a bit with the x, y and fontsize parameters without any success.
Here is my code:
def drawHeatmap(xDim, yDim, plot, threshold, verbose):
global heatmapList
stableCells = 0
print("\n[I] - Plotting Heatmaps ...")
for currentHeatmap in heatmapList:
if -1 in heatmapList[currentHeatmap]:
continue
print("[I] - Plotting heatmap for PUF instance", currentHeatmap,"(",len(heatmapList[currentHeatmap])," values)")
# Convert data to ndarray
#floatMap = list(map(float, currentHeatmap[1]))
myArray = np.array(heatmapList[currentHeatmap]).reshape(xDim,yDim)
# Setup two plots per page
fig, ax = plt.subplots(2)
# Histogram
weights = np.ones_like(heatmapList[currentHeatmap]) / len(heatmapList[currentHeatmap])
hist, bins = np.histogram(heatmapList[currentHeatmap], bins=50, weights=weights)
width = 0.7 * (bins[1] - bins[0])
center = (bins[:-1] + bins[1:]) / 2
ax[0].bar(center, hist, align='center', width=width)
stableCells = calcPercentageStable(threshold, verbose)
plt.figtext(100,100,"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", fontsize=40)
heatmap = ax[1].pcolor(myArray, cmap=plt.cm.Blues, alpha=0.8, vmin=0, vmax=1)
cbar = fig.colorbar(heatmap, shrink=0.8, aspect=10, fraction=.1,pad=.01)
#cbar.ax.tick_params(labelsize=40)
for y in range(myArray.shape[0]):
for x in range(myArray.shape[1]):
plt.text(x + 0.5, y + 0.5, '%.2f' % myArray[y, x],
horizontalalignment='center',
verticalalignment='center',
fontsize=(xDim/yDim)*5
)
#fig = plt.figure()
fig = matplotlib.pyplot.gcf()
fig.set_size_inches(60.5,55.5)
plt.savefig(dataDirectory+"/"+currentHeatmap+".pdf", dpi=800, papertype="a3", format="pdf")
#plt.title("Heatmap for PUF instance "+str(currentHeatmap[0][0])+" ("+str(numberOfMeasurements)+" measurements; "+str(sizeOfMeasurements)+" bytes)")
if plot:
plt.show()
print("\t[I] - Done ...")
And here is my current output:
Perhaps this example will make things easier to understand. Things to note are:
Use fig.suptitle to add a title to the top of a figure.
Use ax[i].text(x, y, str) to add text to an Axes object
Each Axes object, ax[i] in your case, holds all the information about a single plot. Use them instead of calling plt, which only really works well with one subplot per figure or to modify all subplots at once. For example, instead of calling plt.figtext, call ax[0].text to add text to the top plot.
Try following the example code below, or at least read through it to get a better idea how to use your ax list.
import numpy as np
import matplotlib.pyplot as plt
histogram_data = np.random.rand(1000)
heatmap_data = np.random.rand(10, 100)
# Set up figure and axes
fig = plt.figure()
fig.suptitle("These are my two plots")
top_ax = fig.add_subplot(211) #2 rows, 1 col, 1st plot
bot_ax = fig.add_subplot(212) #2 rows, 1 col, 2nd plot
# This is the same as doing 'fig, (top_ax, bot_ax) = plt.subplots(2)'
# Histogram
weights = np.ones_like(histogram_data) / histogram_data.shape[0]
hist, bins = np.histogram(histogram_data, bins=50, weights=weights)
width = 0.7 * (bins[1] - bins[0])
center = (bins[:-1] + bins[1:]) / 2
# Use top_ax to modify anything with the histogram plot
top_ax.bar(center, hist, align='center', width=width)
# ax.text(x, y, str). Make sure x,y are within your plot bounds ((0, 1), (0, .5))
top_ax.text(0.5, 0.5, "Here is text on the top plot", color='r')
# Heatmap
heatmap_params = {'cmap':plt.cm.Blues, 'alpha':0.8, 'vmin':0, 'vmax':1}
# Use bot_ax to modify anything with the heatmap plot
heatmap = bot_ax.pcolor(heatmap_data, **heatmap_params)
cbar = fig.colorbar(heatmap, shrink=0.8, aspect=10, fraction=.1,pad=.01)
# See how it looks
plt.show()

Categories

Resources