Source code for pyspedas.tplot_tools.MPLPlotter.ctime

import matplotlib.pyplot as plt
import matplotlib.dates as mdates

from matplotlib.lines import Line2D
from matplotlib.backend_bases import KeyEvent
from matplotlib.pyplot import rcParams

class TimeSelector:

    def add_selection_line(self,time):
        for item in self.axis_list:
            ax, y_bottom, y_top = item
            # Get the y-axis range
            vline = Line2D([time,time], [y_bottom, y_top], color='b', linestyle='--')
            self.selected_lines.append(vline)
            ax.add_line(vline)

    def clear_selection_lines(self):
        for vline in self.selected_lines:
            vline.set_color('r')
            vline.remove()
        plt.draw()
        self.selected_lines.clear()


    # Define the motion event handler
    def ctime_on_motion(self, event):
        if event.inaxes:  # Check if the mouse is over the plot's axes
            # Get the current x-position of the cursor (time)
            cursor_time = event.xdata
            # Update the vertical line position
            for vline in self.vertical_lines:
                vline.set_xdata([cursor_time, cursor_time])
            plt.draw()


    # Define the click event handler
    def ctime_on_click(self, event):

        if event.inaxes:  # Check if the click is within the plot's axes
            if event.button == 1:  # Left-click to add a timestamp, or shift-left-click to clear timestamps
                if self.shift_on:
                    self.clear_selection_lines()
                    self.selected_times.clear()
                    self.logs.append("shift left click")
                else:
                    # Convert the x-coordinate (time) to a datetime
                    self.logs.append("left click")
                    timestamp = mdates.num2date(event.xdata).timestamp()
                    self.selected_times.append(timestamp)
                    self.add_selection_line(event.xdata)
                plt.draw()
            elif event.button == 3:  # Right-click to stop
                self.logs.append("right click")
                plt.disconnect(self.cid)  # Disconnect the event handler
                plt.disconnect(self.motion_cid)  # Disconnect motion event handler
                plt.disconnect(self.kbd_on_cid)  # Disconnect kbd event handler
                plt.disconnect(self.kbd_off_cid)  # Disconnect kbd event handler
                for vline in self.vertical_lines:
                    vline.remove()  # Remove the vertical line from the plot
                plt.draw()
                self.saved_fig.canvas.stop_event_loop()

    # Define the key press event handler
    def ctime_on_key(self, event: KeyEvent):

        self.logs.append("key pressed: " + event.key)
        if event.key == 'c' or event.key == 'e':  # Check if 'c' was pressed
            #print("Pressed 'c', clearing selections")
            self.clear_selection_lines()
            self.selected_times.clear()
            plt.draw()
        elif event.key == 'shift':
            self.shift_on=True
        elif event.key == 'q':  # Check if 'q' was pressed
            #print("Pressed 'q', quitting...")
            plt.disconnect(self.cid)  # Disconnect the event handler
            plt.disconnect(self.motion_cid)  # Disconnect motion event handler
            plt.disconnect(self.kbd_on_cid)  # Disconnect kbd event handler
            plt.disconnect(self.kbd_off_cid)  # Disconnect kbd event handler
            for vline in self.vertical_lines:
                vline.remove()  # Remove the vertical line from the plot
            plt.draw()
            self.saved_fig.canvas.stop_event_loop()
            #clear_selection_lines()

    # Define the key release event handler
    def ctime_off_key(self, event: KeyEvent):
        self.logs.append("key released")
        if event.key == 'shift':
            self.shift_on=False

    def ctime_run_event_loop(self):
        # matplotlib defines some default keyboard commands for manipulating plots.  In particular,
        # 'c' is assigned to "navigate backward", and 'q' is assigned to "close the plot".  We want
        # to redefine these as "clear selections" and "quit ctime", then restore the original definitions
        # upon exit.
        #
        # This only works if the user hasn't redefined 'c' and 'q' to mean something else.
        # The correct way to do this would be to go through all the keymap.* keys, delete 'c' or 'q' from them,
        # then restore the whole map at the end.  This is probably good enough for now.
        #

        save_back = rcParams['keymap.back']
        save_quit = rcParams['keymap.quit']
        rcParams['keymap.back'] = []
        rcParams['keymap.quit'] = []

        #plt.draw()
        #plt.show(block=False)
        # plt.ioff()
        self.saved_fig.canvas.start_event_loop(-1)

        # Restore original keyboard shortcuts
        rcParams['keymap.back'] = save_back
        rcParams['keymap.quit'] = save_quit

        return self.selected_times

    def __init__(self, fig):
        """Initialize."""
        self.saved_fig = fig
        self.selected_times = []
        self.selected_lines = []
        self.vertical_lines = []
        self.axis_list = []
        self.cid = None
        self.motion_cid = None
        self.kbd_on_cid = None
        self.kbd_off_cid = None
        self.shift_on = False
        self.logs = []

        # Create vertical lines in each subplot to track the cursor position
        for ax in fig.axes:
            # Get the y-axis range
            y_bottom, y_top = ax.get_ylim()
            vline = Line2D([0, 0], [y_bottom, y_top], color='g', linestyle='--')
            self.vertical_lines.append(vline)
            ax.add_line(vline)
            self.axis_list.append((ax, y_bottom, y_top))

        # Connect the motion event to the handler
        self.motion_cid = fig.canvas.mpl_connect('motion_notify_event', self.ctime_on_motion)

        # Connect the click event to the handler
        self.cid = fig.canvas.mpl_connect('button_press_event', self.ctime_on_click)

        # Connect the key press/release events to the handler
        self.kbd_on_cid = fig.canvas.mpl_connect('key_press_event', self.ctime_on_key)
        self.kbd_off_cid = fig.canvas.mpl_connect('key_release_event', self.ctime_off_key)


[docs] def ctime(fig): """ Select time values by clicking on a plot, similar to ctime in IDL SPEDAS Left click saves the time at the current cursor position. 'c', 'e', or shift-left click clears the list of times selected. Right click or 'q' exits and returns the list of saved times (as floating point Unix times). Parameters ---------- fig A matplotlib fig object specifying the plot to be used for the time picker (returned by tplot with return_plot_objects=True) Notes ------ This feature is very platform-dependent, and should still be considered experimental. Please let the PySPEDAS developers know if you have trouble using it in your environment. As of this release, ctime seems to work in most situations, except for Jupyter notebooks using the 'ipympl' (aka 'widget') matplotlib backend. With that backend, the most common failure modes are that the ctime() call returns immediately, with no chance to interact with the plot, or the tplot call gets stuck somehow and never renders the plot. We are working on a fix for the ipympl incompatibility, but for now, the best workaround for Jupyter notebooks may be to use "%matplotlib auto" as the backend. Depending on your exact environment, this will probably render the plots outside of the Jupyter notebooks (so the plot results won't be saved in the notebook), but at least ctime should work, allowing you to continue with your desired workflow. Returns ------- list of double The selected times as floating point Unix times (in seconds) Examples -------- >>> import pyspedas >>> from pyspedas import tplot, ctime, time_string >>> pyspedas.projects.themis.state(probe='a') >>> myfig, ax = tplot('tha_pos', return_plot_objects=True) >>> saved_timestamps = ctime(myfig) >>> print(time_string(saved_timestamps)) """ ctime_obj = TimeSelector(fig) print("Use the mouse to select times. Left click to add a new time, type 'c' to clear selections, 'q' or right click to exit") selected_times = ctime_obj.ctime_run_event_loop() return selected_times