Source code for GUIBRUSHR.GUI.WIDGET.MyFigure

import tkinter as tk
import matplotlib
matplotlib.rcParams['agg.path.chunksize'] = 10000
import os
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 screeninfo import get_monitors
from PIL import Image, ImageTk
from typing import Any, List, Optional, Tuple, Union

from GUIBRUSHR.General_Constants.FunctionsAndConstants.Constant_Variables import ConstantVariables
from GUIBRUSHR.GUI.LAYOUT.ScaleManager import ScaleManager



[docs] class MyFigure(tk.Toplevel): """ A custom figure window that can display messages, plots, or images. This class creates a Toplevel window that can be used to display various types of content: - Text messages - Matplotlib plots - Images Attributes: subplots (Tuple[int, int]): Number of subplots in the figure title_plot (List[str]): List of plot titles xlabel (List[str]): List of x-axis labels ylabel (List[str]): List of y-axis labels figure (Figure): The matplotlib figure object type_data (str): Type of data being displayed ('Message', 'Plot', or 'Image') ax (Union[Axes, List[Axes]]): The matplotlib axes object(s) """
[docs] def __init__( self, title: str, message: Optional[str] = None, type_data: str = "Message", personal_size = None, image_path: Optional[str] = None ) -> None: """ Initialize the MyFigure window. Args: title: The window title message: Text message to display when type_data is "Message" type_data: Type of content to display ("Message", "Plot", or "Image") personal_size: Optional tuple (width, height) to determine window size image_path: Path to the image file when type_data is "Image" """ super().__init__() self.title(title) self.protocol("WM_DELETE_WINDOW", self.close_figure) # Initialize attributes self.subplots = None self.title_plot = None self.xlabel = None self.ylabel = None self.figure = None self.type_data = type_data self.ax = None if type_data == "Message": self.label = tk.Label( self, text=message, anchor="w", justify="left", font=ScaleManager.get().font_label_normal if ScaleManager.get() else ("Sans", 10) ) self.label.pack() elif type_data == "Plot": # Setup window size if provided if personal_size is not None: m = get_monitors() width = int(m[0].width * personal_size[0]) height = int(m[0].height * personal_size[1]) self.geometry(f"{width}x{height}+0+0") else: self.geometry(ConstantVariables.geometry) elif type_data == "Image": if image_path: try: if personal_size is None: width = 1000 height = 800 else: width = int(personal_size[0]) height = int(personal_size[1]) # Open the image and convert it to a PhotoImage image = Image.open(image_path).resize((width, height)) photo = ImageTk.PhotoImage(image) self.image_label = tk.Label(self, image=photo) self.image_label.image = photo # Keep a reference! self.image_label.pack() except Exception as e: self.label = tk.Label( self, text=f"Error loading image: {e}", anchor="w", justify="left", font=ScaleManager.get().font_label_normal if ScaleManager.get() else ("Sans", 10) ) self.label.pack() else: self.label = tk.Label( self, text="No image path provided", anchor="w", justify="left", font=ScaleManager.get().font_label_normal if ScaleManager.get() else ("Sans", 10) ) self.label.pack() # Add a close button to the window self.close_button = tk.Button(self, text="Close", command=self.close_figure) self.close_button.pack()
[docs] def return_ax( self, title_plot: List[str], xlabel: List[str], ylabel: List[str], subplots = (1, 1), figsize: Tuple[int, int] = (8, 6), dpi: int = 100, hspace: float = 0.1, wspace: float = 0.1, height_ratios: Optional[List[float]] = None, sharex: bool = False, sharey: bool = False, ) -> Any: """ Create and return matplotlib axes for plotting. Args: title_plot: List of plot titles xlabel: List of x-axis labels ylabel: List of y-axis labels subplots: Tuple of (rows, columns) for subplot layout figsize: Figure size in inches (width, height) dpi: Dots per inch for the figure hspace: Height spacing between subplots wspace: Width spacing between subplots height_ratios: List of height ratios for subplots sharex: Whether to share x-axis between subplots sharey: Whether to share y-axis between subplots Returns: The matplotlib axes object(s) """ if height_ratios is None: height_ratios = [1] * subplots[0] self.title_plot = title_plot self.xlabel = xlabel self.ylabel = ylabel self.subplots = subplots self.figure, self.ax = plt.subplots( subplots[0], subplots[1], figsize=figsize, dpi=dpi, constrained_layout=True, gridspec_kw={ 'hspace': hspace, 'wspace': wspace, 'height_ratios': height_ratios }, sharex=sharex, sharey=sharey, ) return self.ax
[docs] def create_figure(self, path_f: str, dpi: int, tight_layout : bool = True) -> None: """ Create and save the figure with proper labels and formatting. Args: path_f: Path where to save the figure dpi: Dots per inch for the saved figure tight_layout: Whether to apply tight layout to the figure """ if len(self.xlabel) == 1: self.ax.set_xlabel(self.xlabel[0]) self.ax.set_ylabel(self.ylabel[0]) self.ax.set_title(self.title_plot[0]) elif self.subplots[0] == 1 or self.subplots[1] == 1: for i in range(max(self.subplots[0], self.subplots[1])): if self.ylabel[i] != "": self.ax[i].set_ylabel(self.ylabel[i]) if self.xlabel[i] != "": self.ax[i].set_xlabel(self.xlabel[i]) if self.title_plot[i] != "": self.ax[i].set_title(self.title_plot[i]) else: counter = 0 for i in range(self.subplots[0]): for j in range(self.subplots[1]): if counter < len(self.xlabel): if self.ylabel[counter] != "": self.ax[i, j].set_ylabel(self.ylabel[counter]) if self.xlabel[counter] != "": self.ax[i, j].set_xlabel(self.xlabel[counter]) if self.title_plot[counter] != "": self.ax[i, j].set_title(self.title_plot[counter]) plt.setp(self.ax[i, j].get_xticklabels(), rotation=45, fontsize=10) counter += 1 if tight_layout: plt.tight_layout() # Save the figure self.figure.savefig(path_f, dpi=dpi) if "jpg" in path_f: self.figure.savefig(str(path_f).replace("jpg", "pdf"), dpi=dpi) elif "png" in path_f: self.figure.savefig(str(path_f).replace("png", "pdf"), dpi=dpi) # Create the canvas and toolbar chart_type = FigureCanvasTkAgg(self.figure, self) chart_type.draw() chart_type.get_tk_widget().pack() toolbar = NavigationToolbar2Tk(chart_type, self) toolbar.update() chart_type.get_tk_widget().pack()
[docs] def close_figure(self) -> None: """ Close the figure window and clean up resources. """ try: if self.type_data == "Plot": plt.close(self.figure) except Exception: pass self.destroy()