How to create multiple plots with scrollable x axis with a single slider - python-3.x

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

Related

Matplotlib - maintain plot size of uneven subplots

I've been creating uneven subplots in matplotlib based on this question. The gridspec solution (third answer) worked a little better for me as it gives a bit more flexibility for the exact sizes of the subplots.
When I add a plot of a 2D array with imshow() the affected subplot is resized to the shape of the array. Is there any way to avoid that and keep the subplot-sizes (or rather aspect-ratio) fixed?
Here's the example code and the resulting image with the subplot-sizes I'm happy with:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import gridspec
# generate data
x = np.arange(0, 10, 0.2)
y = np.sin(x)
# plot
fig = plt.figure(figsize=(12, 9))
gs = gridspec.GridSpec(20, 20)
ax1 = fig.add_subplot(gs[0:5,0:11])
ax1.plot(x, y)
ax2 = fig.add_subplot(gs[6:11,0:11])
ax2.plot(y, x)
ax3 = fig.add_subplot(gs[12:20,0:11])
ax3.plot(y, x)
ax4 = fig.add_subplot(gs[0:9,13:20])
ax4.plot(x, y)
ax5 = fig.add_subplot(gs[11:20,13:20])
ax5.plot(y, x)
plt.show()
This is what happens if I additionally plot data from a 2D array with the following lines (insert before plt.show):
2Ddata = np.arange(0, 10, 0.1).reshape(10, 10)
im = ax3.imshow(2Ddata, cmap='rainbow')
How can I restore the original size of the subplot from ax3 (lower left corner)?
Including the line ax3.set_aspect('auto') seems to have solved the issue.

matplotlib notebook cursor coordinates on graph with double y axis

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

How to change the location of the symbols/text within a legend box?

I have a subplot with a single legend entry. I am placing the legend at the bottom of the figure and using mode='expand'; however, the single legend entry is placed to the very left of the legend box. To my understanding, changing kwargs such as bbox_to_anchor changes the legend box parameters but not the parameters of the symbols/text within. Below is an example to reproduce my issue.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 21)
y = np.exp(x)
z = x **2
fig, axes = plt.subplots(nrows=1, ncols=2)
axes[0].plot(x, y, color='r', label='exponential')
axes[1].plot(x, z, color='b')
# handles, labels = axes[0].get_legend_handles_labels()
plt.subplots_adjust(bottom=0.125)
fig.legend(mode='expand', loc='lower center')
plt.show()
plt.close(fig)
This code produces . How can I change the position of the symbol and text such that they are centered in the legend box?
PS: I am aware that exponential is a bad label for this subplot since it only describes the first subfigure. But, this is just for examples-sake so that I can apply it to my actual use-case.
The legend entries are placed using a HPacker object. This does not allow to be centered. The behaviour is rather that those HPackers are "justified" (similar to the "justify" option in common word processing software).
A workaround would be to create three (or any odd number of) legend entries, such that the desired entry is in the middle. This would be accomplished via the ncol argument and the use of "dummy" entries (which might be transparent and have no associated label).
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 21)
y = np.exp(x)
z = x **2
fig, axes = plt.subplots(nrows=1, ncols=2)
fig.subplots_adjust(bottom=0.125)
l1, = axes[0].plot(x, y, color='r', label='exponential')
axes[1].plot(x, z, color='b')
dummy = plt.Line2D([],[], alpha=0)
fig.legend(handles=[dummy, l1, dummy],
mode='expand', loc='lower center', ncol=3)
plt.show()

Matplotlib get all axes artist objects for ArtistAnimation?

I am trying to make an animation using ArtistAnimation like this:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
ims = []
for i in range(60):
x = np.linspace(0,i,1000)
y = np.sin(x)
im = ax.plot(x,y, color='black')
ims.append(im)
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
plt.show()
This animates a sine wave growing across the figure. Currently I'm just adding the Lines2D object returned by ax.plot() to ims. However, I would like to potentially draw multiple overlapping plots on the Axes and adjust the title, legend and x-axis range for each frame. How do I get an object that I can add to ims after plotting and making all the changes I want for each frame?
The list you supply to ArtistAnimation should be a list of lists of artists, one list per frame.
artist_list = [[line1a, line1b, title1], [line2a, line2b, title2], ...]
where the first list is shown in the first frame, the second list in the second frame etc.
The reason your code works is that ax.plot returns a list of lines (in your case only a list of a single line).
In any case, the following might be a more understandable version of your code where an additional text is animated.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
artist_list = []
for i in range(60):
x = np.linspace(0,i,1000)
y = np.sin(x)
line, = ax.plot(x,y, color='black')
text = ax.text(i,0,i)
artist_list.append([line, text])
ani = animation.ArtistAnimation(fig, artist_list, interval=50, blit=True,
repeat_delay=1000)
plt.show()
In general, it will be hard to animate changing axes limits with ArtistAnimation, so if that is an ultimate goal consider using a FuncAnimation instead.

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

Resources