Source code for GUIBRUSHR.GUI.WIDGET.MyMultiFigure
import tkinter as tk
import os
import matplotlib
matplotlib.rcParams['agg.path.chunksize'] = 10000
if not os.environ.get('SPHINX_BUILD'):
matplotlib.use("TkAgg")
from matplotlib import pyplot as plt
from matplotlib.backends._backend_tk import NavigationToolbar2Tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from typing import List, Optional
from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import ConstantVariables
from GUIBRUSHR.core.functions.figure_pickle import load_figures
[docs]
class MyMultiFigure(tk.Toplevel):
"""
A window that displays a list of matplotlib figures as navigable pages.
Each page is embedded with an interactive NavigationToolbar2Tk (zoom, pan,
reset and a Save button that exports the current view). Prev/Next buttons
move between pages. This is the interactive counterpart of the static
multi-page PDF shown by MyPDFWindow.
"""
[docs]
def __init__(
self,
figures: List,
title: str = "Figures",
) -> None:
"""
Initialize the multi-figure window.
Args:
figures: Ordered list of matplotlib Figures, one per page.
title: The window title.
"""
super().__init__()
self.title(title)
self.protocol("WM_DELETE_WINDOW", self.close_window)
self.geometry(ConstantVariables.figure_geometry())
self.figures = list(figures)
self.index = 0
self._canvas = None
self._toolbar = None
# Navigation bar (fixed, top)
nav = tk.Frame(self)
nav.pack(side="top", fill="x")
self.prev_button = tk.Button(nav, text="< Prev", command=self.show_prev)
self.prev_button.pack(side="left")
self.page_label = tk.Label(nav, text="")
self.page_label.pack(side="left", expand=True)
self.next_button = tk.Button(nav, text="Next >", command=self.show_next)
self.next_button.pack(side="right")
# Reserve the fixed Close button (bottom) BEFORE the expanding container,
# otherwise the canvas claims all space and clips it until the user resizes.
self.close_button = tk.Button(self, text="Close", command=self.close_window)
self.close_button.pack(side="bottom", fill="x", ipady=4)
# Container that holds the current page's canvas and toolbar (expands last)
self.container = tk.Frame(self)
self.container.pack(side="top", fill="both", expand=True)
self._render_current()
[docs]
@classmethod
def from_pdf(cls, pdf_path: str, title: str = "Figures") -> "Optional[MyMultiFigure]":
"""
Build the viewer from the figures pickled next to ``pdf_path``.
Args:
pdf_path: Path of the multi-page PDF whose ``.figs.pkl`` holds the
pickled figures.
title: The window title.
Returns:
A MyMultiFigure if the pickled figures were found and loaded, else
None so the caller can fall back to the static PDF viewer.
"""
figures = load_figures(pdf_path)
if not figures:
return None
try:
return cls(figures, title=title)
except Exception:
return None
@staticmethod
def _normalize_display_dpi(figure) -> None:
"""Lower a high-dpi figure's dpi for on-screen display (font proportions)."""
try:
if figure.get_dpi() > 150:
figure.set_dpi(100)
except Exception:
pass
def _render_current(self) -> None:
"""Embed the current page's figure, replacing any previous one."""
# Tear down the previous canvas/toolbar
if self._toolbar is not None:
self._toolbar.destroy()
self._toolbar = None
if self._canvas is not None:
self._canvas.get_tk_widget().destroy()
self._canvas = None
figure = self.figures[self.index]
self._normalize_display_dpi(figure)
self._canvas = FigureCanvasTkAgg(figure, self.container)
# Toolbar packed before the expanding canvas so it stays visible.
self._toolbar = NavigationToolbar2Tk(self._canvas, self.container)
self._toolbar.update()
self._canvas.draw()
self._canvas.get_tk_widget().pack(fill="both", expand=True)
self.page_label.configure(text=f"Page {self.index + 1}/{len(self.figures)}")
self.prev_button.configure(state="normal" if self.index > 0 else "disabled")
self.next_button.configure(
state="normal" if self.index < len(self.figures) - 1 else "disabled"
)
[docs]
def show_prev(self) -> None:
"""Show the previous page, if any."""
if self.index > 0:
self.index -= 1
self._render_current()
[docs]
def show_next(self) -> None:
"""Show the next page, if any."""
if self.index < len(self.figures) - 1:
self.index += 1
self._render_current()
[docs]
def close_window(self) -> None:
"""Close the window and release all embedded figures."""
for figure in self.figures:
try:
plt.close(figure)
except Exception:
pass
self.destroy()