I have the following code which plots a time-series. I can spanselect inside the upper subplot which applies some processing when pressing the ‘z’ key and displays this new data inside the lower subplot. Moreover, it is possible to go back to the initial upper plot of the data when clicking on the ‘Home’ button in the toolbar.
I would like to add a new button to the toolbar, named “Process”, which will perform the same operation as pressing the ‘z’ key when clicked by the user. The key press functionality can be dropped if it simplifies the implementation.
Code:
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
import matplotlib.dates as mdates
if __name__ == '__main__':
idx = pd.date_range("2018-01-01", "2018-01-03", freq="10s")
df = pd.DataFrame(range(len(idx)), index=idx, columns=['val_phys'])
date_min = df.index.values[0]
date_max = df.index.values[len(df) - 1]
# Display non-overlapping layout
plt.rcParams['figure.constrained_layout.use'] = True
# Use concise dates for labels
plt.rcParams['date.converter'] = 'concise'
# Display grid
plt.rcParams['axes.grid'] = True
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
tb = fig.canvas.toolbar
ax1.plot(df)
# We scale the x-axis to the scale of the data (for Windows)
ax1.set_xlim([mdates.date2num(date_min), mdates.date2num(date_max)])
# For aesthetics we set the second plot axes
ax2.set_xlim(ax1.get_xlim())
# We push this scale onto the stack of the toolbar
tb.push_current()
def on_select(xmin, xmax):
global date_min, date_max
date_min = mdates.num2date(xmin).replace(tzinfo = None)
date_max = mdates.num2date(xmax).replace(tzinfo = None)
ax1.set_xlim([xmin, xmax])
visible_y = df.loc[(df.index >= date_min) & (df.index <= date_max)].val_phys
if len(visible_y):
ax1.set_ylim(min(visible_y), max(visible_y))
ax2.set_xlim(ax1.get_xlim())
tb.push_current()
plt.draw()
def on_key(event):
#print('you pressed', event.key, event.xdata, event.ydata)
if event.key == 'z':
# We filter dataframe to selected date range by mouse
df_selected = df.loc[(df.index >= date_min) & (df.index <= date_max)]
# We only keep one sample every 5 minutes (5*6=30 10-seconds samples)
df_selected = df_selected.iloc[::30]
ax2.clear()
ax2.set_xlim(ax1.get_xlim())
ax2.plot(df_selected)
ax2.autoscale(enable=True, axis="y", tight=False)
plt.draw()
span_selector = SpanSelector(ax1, on_select, direction='horizontal', useblit=True)
cid = fig.canvas.mpl_connect('key_press_event', on_key)
plt.show()
I have tried to adapt the examples given in the Tool Manager documentation without success.
OK I found a solution in the following code.
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
from matplotlib.backend_tools import ToolBase
import matplotlib.dates as mdates
matplotlib.rcParams["toolbar"] = "toolmanager"
if __name__ == '__main__':
idx = pd.date_range("2018-01-01", "2018-01-03", freq="10s")
df = pd.DataFrame(range(len(idx)), index=idx, columns=['val_phys'])
class Process(ToolBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.toggle_flag = False # Initial state
def trigger(self, sender, event, data=None):
self.toggle_flag = True # Toggle state
if self.toggle_flag:
# We filter dataframe to selected date range by mouse
df_selected = df.loc[(df.index >= date_min) & (df.index <= date_max)]
# We only keep one sample every 5 minutes (5*6=30 10-seconds samples)
df_selected = df_selected.iloc[::30]
ax2.clear()
ax2.set_xlim(ax1.get_xlim())
ax2.plot(df_selected.drop(columns=['cq']))
ax2.set_ylabel(labels[meta['unite_phys']])
ax2.autoscale(enable=True, axis="y", tight=False)
plt.draw()
self.toggle_flag = False
date_min = df.index.values[0]
date_max = df.index.values[len(df) - 1]
# Display non-overlapping layout
plt.rcParams['figure.constrained_layout.use'] = True
# Use concise dates for labels
plt.rcParams['date.converter'] = 'concise'
# Display grid
plt.rcParams['axes.grid'] = True
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
#tb = fig.canvas.toolbar
tm = fig.canvas.manager.toolmanager
tm.add_tool("Process", Process)
fig.canvas.manager.toolbar.add_tool(tm.get_tool("Process"), "toolgroup")
ax1.plot(df.drop(columns = ['cq']))
# We scale the x-axis to the scale of the data (for Windows)
ax1.set_xlim([mdates.date2num(date_min), mdates.date2num(date_max)])
ax1.set_ylabel(labels[meta['unite_phys']])
ax1.set_title(meta['ncon'])
# For aesthetics we set the second plot axes
ax2.set_xlim(ax1.get_xlim())
# We push this scale onto the stack of the toolbar
#tb.push_current()
def on_select(xmin, xmax):
global date_min, date_max
date_min = mdates.num2date(xmin).replace(tzinfo = None)
date_max = mdates.num2date(xmax).replace(tzinfo = None)
vprint(f"Selected Span: xmin={date_min}, xmax={date_max}")
ax1.set_xlim([xmin, xmax])
visible_y = df.loc[(df.index >= date_min) & (df.index <= date_max)].val_phys
if len(visible_y):
ax1.set_ylim(min(visible_y), max(visible_y))
ax2.set_xlim(ax1.get_xlim())
#tb.push_current()
plt.draw()
def on_key(event):
#print('you pressed', event.key, event.xdata, event.ydata)
if event.key == 'z':
# We filter dataframe to selected date range by mouse
df_selected = df.loc[(df.index >= date_min) & (df.index <= date_max)]
# We only keep one sample every 5 minutes (5*6=30 10-seconds samples)
df_selected = df_selected.iloc[::30]
ax2.clear()
ax2.set_xlim(ax1.get_xlim())
ax2.plot(df_selected.drop(columns=['cq']))
ax2.set_ylabel(labels[meta['unite_phys']])
ax2.autoscale(enable=True, axis="y", tight=False)
plt.draw()
span_selector = SpanSelector(ax1, on_select, direction='horizontal', useblit=True)
cid = fig.canvas.mpl_connect('key_press_event', on_key)
plt.show()
The problem lies in the fact that I had to comment calls to tb.push_current()
method and thus the ‘Home’, ‘Forward’ & ‘Backward’ buttons do not work anymore.
Any clue on how can it be fixed with Tool Manager, please ?