matplotlib - How can I edit (e.g., remove) data tips in a python figure that has been saved and now reopened? - Stack Overflow

admin2025-04-17  2

I created a time series and assigned a cursor to each line so that I could identify the time associated with various behavior in the time series. The first time I made the figure, I selected the periods of interest so that the cursors could serve as "data tips" (this language comes from MATLAB, which is my first coding language and I'm now learning to switch over to Python).

I saved the figures. Now I am reopening them. I want to make adjustments to the data tips I previously set. I need to delete some because I think they need to be moved a bit. I am having a hard time getting the data tips I set originally to be active when I reopen the figure. When I reopen the figure, I can add new data tips and delete those, but I am having a hard time getting the original ones to be active and to delete them.

Attached is an example of a time series with the original data tips that I want to modify. Any help is super super appreciated! :)

Here is my code to reopen the figure. I am trying to get the original data tips to be active, but haven't been able to get it yet:

import os import pickle import matplotlib.pyplot as plt import mplcursors from decimal import Decimal from matplotlib.backend_bases import MouseEvent

def reload_saved_pkl_file(load_folder, file): file_path = os.path.join(load_folder, file) with open(file_path, "rb") as f: fig = pickle.load(f) # Directly load the figure

print(f"Opening: {file}")

# Store cursors for potential modifications
cursors = {}

# List to track existing annotations (annotations should still be in the figure)
existing_annotations = []

# Restore any existing annotations (those that were originally saved in the figure)
for ax in fig.axes:
    for artist in ax.texts:
        existing_annotations.append(artist)
        artist.set_visible(True)  # Ensure annotations are visible

# Function to remove an annotation on right-click
def remove_annotation(event):
    """Removes a clicked annotation, including saved ones."""
    if isinstance(event, MouseEvent) and event.button == 3:  # Right-click
        for annotation in existing_annotations:
            bbox = annotation.get_window_extent(fig.canvas.get_renderer())
            if bbox.contains(event.x, event.y):  # Check if click is on the annotation
                annotation.remove()  # Remove annotation
                existing_annotations.remove(annotation)  # Remove from list
                fig.canvas.draw_idle()  # Redraw the figure
                return  # Exit after removing one annotation

# Function to add an annotation on left-click
def add_cursor(event):
    """Adds a new cursor where the user left-clicks."""
    if isinstance(event, MouseEvent) and event.button == 1:  # Left-click
        for ax in fig.axes:
            if ax.contains(event)[0]:  # Check if the click is inside the current axis
                for line in ax.lines:
                    if line not in cursors:  # Only add a cursor if not already added
                        cursor = mplcursors.cursor(line, multiple=True, highlight=False, hover=False)
                        cursor.connect("add", lambda sel, lbl=line.get_label(): 
                                       sel.annotation.set_text(f"{lbl}\nx: {Decimal(sel.target[0]):.2f}\ny: {sel.target[1]:.2f}"))
                        # Add the annotation to existing_annotations list
                        cursor.connect("add", lambda sel: existing_annotations.append(sel.annotation))    
                        
                        cursor.connect("add", remove_annotation)  # Allow removal
                        cursors[line] = cursor  # Track the cursor
            fig.canvas.draw_idle()

# Restore interactive cursors for existing lines
for ax in fig.axes:  # Loop through all axes
    for line in ax.lines:  # Loop through all lines in each axis
        cursor = mplcursors.cursor(line, multiple=True, highlight=False, hover=False)
        cursor.connect("add", lambda sel, lbl=line.get_label(): 
                       sel.annotation.set_text(f"{lbl}\nx: {Decimal(sel.target[0]):.2f}\ny: {sel.target[1]:.2f}"))
        cursor.connect("add", remove_annotation)  # Attach right-click delete feature
        cursor.connect("add", lambda sel: existing_annotations.append(sel.annotation))  # Track new annotations
        cursors[line] = cursor  # Track cursor to prevent errors
        
# Connect mouse events for adding cursors and removing saved ones
fig.canvas.mpl_connect("button_press_event", add_cursor)

# Show the interactive figure
plt.show()

Also here is my code for saving the figures:

import os import pickle import matplotlib.pyplot as plt

def save_figure_as_pkl(fig, save_folder, file): # Ensure the folder exists os.makedirs(save_folder, exist_ok=True)

# Save to pickle file
file_path = os.path.join(save_folder, file)
with open(file_path, "wb") as f:
    pickle.dump(fig, f)
print(f"Figure saved to {file_path}")

I have been asking chatGPT for help and looking online for resources.

I did try to save different components of the figure, but I was not able to save the cursors in the pickle file because of an error that said:

TypeError: cannot pickle 'weakref.ReferenceType' object

Here was that code that I tried:

import os import pickle import matplotlib.pyplot as plt import mplcursors

def save_figure_with_annotations_and_cursors(fig, save_folder, file): # Ensure the folder exists os.makedirs(save_folder, exist_ok=True)

# Create lists to store annotations and cursors
annotations = []
cursors = []

# Loop through all axes and save annotations
for ax in fig.axes:
    print(f"Checking annotations in axis {ax}")
    for artist in ax.texts:  # annotations are stored in ax.texts
        print(f"Found annotation: {artist.get_text()}")
        annotations.append(artist)
    
    # Add cursors associated with this axis
    for line in ax.lines:
        cursor = mplcursors.cursor(line, multiple=True, highlight=False, hover=False)
        cursors.append(cursor)  # Store the cursor

# If no annotations are found, print a message
if not annotations:
    print("No annotations found.")
if not cursors:
    print("No cursors found.")

# Save the figure, annotations, and cursors together
fig_data = {
    'figure': fig,
    'annotations': annotations,
    'cursors': cursors  # Store cursors as well
}

# Save to pickle file
file_path = os.path.join(save_folder, file)
with open(file_path, "wb") as f:
    pickle.dump(fig_data, f)
print(f"Figure, annotations, and cursors saved to {file_path}")

Example of time series

转载请注明原文地址:http://anycun.com/QandA/1744889809a89065.html