Skip to content

[Bug] Unable to pickle figure with draggable legend (Windows 10, Matplotlib 3.7.0) #52

@rowan-stein

Description

@rowan-stein

User Report

[Bug]: Unable to pickle figure with draggable legend

Bug summary:

  • Unable to pickle figure with a draggable legend (same for draggable annotations).

Code for reproduction:

import matplotlib.pyplot as plt
import pickle

fig = plt.figure()
ax = fig.add_subplot(111)

time=[0,1,2,3,4]
speed=[40,43,45,47,48]

ax.plot(time,speed,label="speed")

leg=ax.legend()
leg.set_draggable(True) # pickling works after removing this line 

pickle.dumps(fig)
plt.show()

Actual outcome:

TypeError: cannot pickle 'FigureCanvasQTAgg' object

Expected outcome:

  • Pickling successful

Environment:

  • OS: Windows 10
  • Matplotlib Version: 3.7.0
  • Python: 3.10
  • Installation: pip

Specification (Research Findings)

Root cause:

  • Enabling a draggable legend sets Legend._draggable to a DraggableLegend (inherits from DraggableBase). In 3.7.0-era code, DraggableBase.__init__ stores a direct reference to the backend canvas (self.canvas = self.ref_artist.figure.canvas) and event connection IDs on that canvas. When the Figure is pickled, the _draggable helper is pickled as part of the legend, pulling in the backend-specific FigureCanvas (e.g., FigureCanvasQTAgg), which is not picklable for GUI backends.

Upstream fix:

  • Make draggable helpers “canvas-less” in their pickled state by:
    • Removing stored canvas attribute and replacing it with a computed property.
    • Using figure._canvas_callbacks and storing lightweight disconnector callables instead of persistent canvas references and connection IDs.
    • Register motion handler up-front, remove dynamic connect/disconnect that stores state.

Target changes (files/functions):

  • lib/matplotlib/offsetbox.py
    • DraggableBase.__init__: remove self.canvas assignment; use a canvas property.
    • Replace self.cids/_c1 with self._disconnectors built via figure._canvas_callbacks.
    • Update disconnect() to iterate self._disconnectors.

References:

Verification plan (local, no CI):

  • With Agg backend, pickle a figure containing a draggable legend and confirm success and that the pickle stream does not contain canvas symbols (via pickletools).
  • With Qt5Agg backend (if available), confirm that pickling succeeds after the patch whereas it fails before.

Task linkage: swev-id: matplotlib__matplotlib-25311 (ID: 277)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions