matplotlib notebook cursor coordinates on graph with double y axis - python-3.x

The issue I would like you to figure out is about the coordinantes appearence on matplotlib graph with a double y axis. First of all a code on Jupyter Notebook which draws a graph with two lines and only one y axis (for some unknown reasons I have to run it two times in order to make it working correctly)
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.mlab as mlab
from IPython.display import display
from IPython.core.display import display, HTML #display multiple output on a cell
display(HTML("<style>.container { width:100% !important; }</style>")) # improve cells horizontal size
from IPython.core.interactiveshell import InteractiveShell # It saves you having to repeatedly type "Display"
InteractiveShell.ast_node_interactivity = "all"
%matplotlib notebook
x = np.arange(0, 10, 0.01)
y1 = np.sin(np.pi*x)/(np.pi*x)
y2 = abs(np.tan(0.1*np.pi*x))
plt.figure()
plt.plot(x, y1)
plt.plot(x, y2)
plt.ylim(0, 3)
plt.grid()
plt.show()
The present figure provides the two lines with cursor coordinates on the right bottom part of the graph.
The following code
import pandas as pd
import os
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.mlab as mlab
from IPython.display import display
from IPython.core.display import display, HTML #display multiple output on a cell
display(HTML("<style>.container { width:100% !important; }</style>")) # improve cells horizontal size
from IPython.core.interactiveshell import InteractiveShell # It saves you having to repeatedly type "Display"
InteractiveShell.ast_node_interactivity = "all"
%matplotlib notebook
x = np.arange(0, 10, 0.01)
y1 = np.sin(np.pi*x)/(np.pi*x)
y2 = abs(np.tan(0.1*np.pi*x))
# Create some mock data
fig, ax1 = plt.subplots()
plt.grid()
color = 'tab:red'
ax1.set_xlabel('Time (days from 24 February)')
ax1.set_ylabel('Death cases/Intensive care', color=color)
#ax1.set_xlim(0, 15)
#ax1.set_ylim(0, 900)
ax1.plot(x, y1, '-', color=color, label = 'Left hand scale')
ax1.tick_params(axis='y', labelcolor=color)
ax1.legend(loc = 'upper left')
ax2 = ax1.twinx()
color = 'tab:blue'
ax2.set_ylabel('Total cases/currently positive', color=color) # we already handled the x-label with ax1
ax2.plot(x, y2, '-', color=color, label = 'Right hand scale')
ax2.set_ylim(0, 20)
ax2.tick_params(axis='y', labelcolor=color)
ax2.legend(loc = 'lower right')
fig.tight_layout()
plt.show()
Shows the following graph
Which shows a graph with TWO y scales, one red on the left side and one blue on the right side. The problem here is that in the left bottom side of the picture there are the cursor coordinates related to the right scale and nothing about the left one. Is there a way to show up both the two scales?

Depending on your precise needs, mplcursors seems helpful. Mplcursors allows a lot of ways to customize, for example you can show both y-values together with the current x. Or you could suppress the annotation and only write in the status bar.
Setting hover=True constantly displays the plotted values when the mouse hovers over a curve. Default, the values would only be displayed when clicking.
import matplotlib.pyplot as plt
import numpy as np
import mplcursors
# Create some test data
x = np.arange(0, 10, 0.01)
y1 = np.sin(np.pi * x) / (np.pi * x)
y2 = abs(np.tan(0.1 * np.pi * x))
fig, ax1 = plt.subplots()
plt.grid()
color = 'tab:red'
ax1.set_xlabel('Time (days from 24 February)')
ax1.set_ylabel('Death cases/Intensive care', color=color)
lines1 = ax1.plot(x, y1, '-', color=color, label='Left hand scale')
ax1.tick_params(axis='y', labelcolor=color)
ax1.legend(loc='upper left')
ax2 = ax1.twinx()
color = 'tab:blue'
ax2.set_ylabel('Total cases/currently positive', color=color) # we already handled the x-label with ax1
lines2 = ax2.plot(x, y2, '-', color=color, label='Right hand scale')
ax2.set_ylim(0, 20)
ax2.tick_params(axis='y', labelcolor=color)
ax2.legend(loc='lower right')
cursor1 = mplcursors.cursor(lines1, hover=True)
cursor2 = mplcursors.cursor(lines2, hover=True)
fig.tight_layout()
plt.show()

Related

How do I get matplotlib subplot and GridSpec to position and size subplots as I want them?

I am trying to create a figure with 2x10 subplots. I would like them all to be square with a thin white space in between them, but they are coming out as rectangles (longer in height than width). The images I'm putting in each cell of the grid are all square, but the cell itself is not square so the extra space just becomes white space which creates a giant gap between the top row and the bottom row. Here's the code that shows the rectangles:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from PIL import Image
fig = plt.figure()
gs1 = GridSpec(2, 10)
for a in range(10):
ax = plt.subplot(gs1[0, a])
ax2 = plt.subplot(gs1[1, a])
plt.show()
output from above code
Imagine this but with little to no gaps in between cells and each cell is square instead of rectangular. Thanks in advance for any help!
You can use plt.tight_layout() to clean up your subplot figure. Also, play around with plt.rcParams for the figure size:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from PIL import Image
plt.rcParams["figure.figsize"] = (20,10)
fig = plt.figure()
gs1 = GridSpec(2, 10)
for a in range(10):
ax = plt.subplot(gs1[0, a])
ax2 = plt.subplot(gs1[1, a])
plt.tight_layout()
plt.show()
Output
For more control, you can use fig,ax and turn off all the labels and ticks. Then you can remove the white space between the subplots.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from PIL import Image
plt.rcParams["figure.figsize"] = (20,4)
fig, ax = plt.subplots(2,10)
gs1 = GridSpec(2, 10)
for x in range(2):
for y in range(10):
ax[x,y].plot()
ax[x,y].tick_params(axis = 'both', bottom= False, left = False,
labelbottom = False, labelleft = False)
ax[1,0].tick_params(axis = 'both', bottom= True, left = True,
labelbottom = True, labelleft = True)
plt.subplots_adjust(wspace=0.05, hspace=0.05)
plt.show()
Output:

Change colorbar limits without changing the values of the data it represents in scatter

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()

Width of ticks in multiplot in matplotlib

Could you help me with the following script please? How to set the width of ticks in this multiplot for plotting 6 subplots?
import numpy as np
import matplotlib.pyplot as plt
from numpy import array
import matplotlib as mpl
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
fig, ax = plt.subplots(sharex=True)
plt.figure(figsize=(12, 9))
fig1 = plt.subplot(231)
plt.plot(x, y**2)
fig1.set_xlim(0e-13,2e-13)
fig1.set_ylim(-1.15e-14,0.01e-14)
fig2=plt.subplot(232)
plt.plot(x, y**2)
fig2.set_xlim(0e-13,2e-13)
fig2.set_ylim(-7.3e-15,7.3e-15)
fig3=plt.subplot(233)
plt.plot(x, y**2)
fig3.set_ylim(0e-13,1.2e-13)
fig3.set_xlim(0e-13,2e-13)
fig4=plt.subplot(234)
plt.plot(x, y**2)
fig4.set_xlim(-1.15e-14,0.01e-14)
fig4.set_ylim(-7.3e-15,7.3e-15)
fig5=plt.subplot(235)
plt.plot(x, y**2)
fig5.set_xlim(-7.3e-15,7.3e-15)
fig5.set_ylim(0e-13,1.2e-13)
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)
fig6=plt.subplot(236)
plt.plot(x, y**2)
fig6.set_xlim(-1.5e-14,0e-14)
fig6.set_ylim(0e-13,1.2e-13)
plt.show()
I tried:
ax.xaxis.set_tick_params(width=2)
ax.yaxis.set_tick_params(width=2)
and
for figures in [fig1, fig2, fig3, fig4, fig5, fig6]:
ax.xaxis.set_tick_params(width=2)
ax.yaxis.set_tick_params(width=2)
but nothing has changed and the width of ticks stayed the same.
First of all, the following
fig, ax = plt.subplots(sharex=True)
plt.figure(figsize=(12, 9))
creates two figures, which I guess you do not want.
Second, when you execute fig1 = plt.subplot(231), you do not create a Figure object but rather an Axes one. This call is redundant as it can be handled directly with plt.subplots().
Third, ax.xaxis.set_tick_params(width=2) has no effect in the figure you are interested in because ax refers to the axis created by fig, ax = plt.subplots(sharex=True) and not to any axis in the current figure you are drawing in, which was created by plt.figure(figsize=(12, 9)).
Have a look below for a cleaner version.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 400)
y = np.sin(x ** 2)
fig, axarr = plt.subplots(nrows=2, ncols=3)
for ax in axarr.flatten():
ax.plot(x, y ** 2)
ax.tick_params(width=2)
fig.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0)
plt.show()

Screen flickering with matplotlib slider update

Have tried to implement multiple plots on shared x axis with a common slider . On slider update , there is too much screen flicker . How can this be avoided . Here is the code sample i have used.
%matplotlib inline
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np
''' 30% window size on the selected time on slider'''
data_size=round(M.Timestamp.size*0.30)
plt.close('all')
def f(m):
plt.figure(2)
x=M['Timestamp']
y1=M['Value']
'''define boundary limits for both axis'''
min_x=0 if m-data_size < 0 else m-data_size
max_x=M.Timestamp.size if m+data_size > M.Timestamp.size else m+data_size
f, (ax1, ax2, ax3) = plt.subplots(3, sharex=True, sharey=True)
ax1.plot(x[min_x:max_x],y1[min_x:max_x],color='r')
ax1.set_title('Sharing both axes')
ax2.plot(x[min_x:max_x],y1[min_x:max_x],color='b')
ax3.plot(x[min_x:max_x],y1[min_x:max_x],color='g')
plt.xticks(rotation=30)
interactive(f, m=(0, M.Timestamp.size))
When tried to update the xlimit on slider movement the graph is blank , hence used the subset of data to update on plots
Solved the issue with following settings.
Used a selection slider with continuous_update =False
on startup load the graph and manipulate only the xlim with plt.xlim(min_x,max_x) with the slider functionality
snippet of the implementation below.
selection_range_slider = widgets.SelectionRangeSlider(
options=options,
index=index,
description='Time slider',
orientation='horizontal',
layout={'width': '1000px'},
continuous_update=False
)
#selection_range_slider
def print_date_range(date_range):
print(date_range)
plt.figure(num=None, figsize=(15, 4), dpi=80, facecolor='w', edgecolor='k')
min_x=date_range[0]
max_x=date_range[1]
ax1 = plt.subplot(311)
plt.plot(Data_1.Timestamp,Data_1.value,'r')
plt.setp(ax1.get_xticklabels(), fontsize=6,visible=False)
plt.xlabel('Data_1')
ax1.xaxis.set_label_coords(1.05, 0.5)
# share x only
ax2 = plt.subplot(312, sharex=ax1)
plt.plot(Data_2.Timestamp,Data_2.value,'b')
# make these tick labels invisible
plt.setp(ax2.get_xticklabels(), visible=False)
plt.xlabel('Data_2')
ax2.xaxis.set_label_coords(1.05, 0.5)
# share x and y
ax3 = plt.subplot(313, sharex=ax1)
plt.plot(Data_3.Timestamp,Data_3.value,'g')
ax3.xaxis.set_label_coords(1.05, 0.5)
#plt.xlim(0.01, 5.0)
plt.xlim(min_x,max_x)
plt.show()
#plt.xlabel('Data_3')
widgets.interact(
print_date_range,
date_range=selection_range_slider
);

How to create multiple plots with scrollable x axis with a single slider

I intend to create 2-3 plots shared on a common time axis that can interactively scrolled with a single slider . Also there is one constraint where in sampling frequency is different for each variable but have the same time window.
x - time
y1 - values sampled every 1 second
y2 - values sampled every 10 seconds
y3 - values sampled every 100 seconds
How can we do this .
Have tried this sample code
import matplotlib.pyplot as plt
from ipywidgets import interact
%matplotlib inline
def f(n):
plt.plot([0,1,2],[0,1,n])
plt.show()
interact(f,n=(0,10))
I want something similar to this , the only change being x and y axis data is constant and the slider widget is used to scroll the graph left and right (x axis here) with a certain time window on the graph display
Solved the problem partially for the interactivity bit of it.
X axis is scrollable with the slider movement.
%matplotlib inline
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np
def f(m):
plt.figure(2)
x = np.linspace(-10, 10, num=1000)
plt.plot(x,x)
#plt.plot(x, m * x)
plt.xlim(m+2, m-2)
plt.show()
interactive(f, m=(-2.0, 2.0))
Snippet of the implementation below.
Load all the graphs and manipulate only the xlim with plt.xlim(min_x,max_x) with the slider functionality
selection_range_slider = widgets.SelectionRangeSlider(
options=options,
index=index,
description='Time slider',
orientation='horizontal',
layout={'width': '1000px'},
continuous_update=False
)
#selection_range_slider
def print_date_range(date_range):
print(date_range)
plt.figure(num=None, figsize=(15, 4), dpi=80, facecolor='w',
edgecolor='k')
min_x=date_range[0]
max_x=date_range[1]
ax1 = plt.subplot(311)
plt.plot(Data_1.Timestamp,Data_1.value,'r')
plt.setp(ax1.get_xticklabels(), fontsize=6,visible=False)
plt.xlabel('Data_1')
ax1.xaxis.set_label_coords(1.05, 0.5)
# share x only
ax2 = plt.subplot(312, sharex=ax1)
plt.plot(Data_2.Timestamp,Data_2.value,'b')
# make these tick labels invisible
plt.setp(ax2.get_xticklabels(), visible=False)
plt.xlabel('Data_2')
ax2.xaxis.set_label_coords(1.05, 0.5)
# share x and y
ax3 = plt.subplot(313, sharex=ax1)
plt.plot(Data_3.Timestamp,Data_3.value,'g')
ax3.xaxis.set_label_coords(1.05, 0.5)
#plt.xlim(0.01, 5.0)
plt.xlim(min_x,max_x)
plt.show()
#plt.xlabel('Data_3')
widgets.interact(
print_date_range,
date_range=selection_range_slider
);

Resources