Задать вопрос
@mr_secret

Как разместить в tkinter окне matplotlib-фигуру, выходящую за границы экрана?

Ситуация №1: Нужно оконное приложение, написанное на библиотеке tkinter, способное отображать как обычный текст, так и математические формулы, переданные в виде latex-кода.
Решение №1: написать latex-виджет, способный на это.

Ситуация №2: так как отображаемые данным приложением математические формулы могут занимать несколько строк или быть слишком длинными, места на экране может не хватить.
Решение №2:
1) Установить достаточно большие размеры latex-виджета;
2) Использовать полосы прокрутки.

Ситуация №3: при использовании полос прокрутки часть математических формул не видна, так как вместо них отображается серый фон.
Решение №3: ???

import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg


matplotlib.use('TkAgg')
plt.rcParams['text.usetex'] = True  # https://stackoverflow.com/questions/71250205/matplotlib-runtime-error-latex-was-not-able-to-process-the-following-string
plt.rc('text.latex', preamble=r'''\usepackage{amsmath} \usepackage[english,russian]{babel} \usepackage{amssymb}''')


class Main_App(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)

        # region Развернуть окно
        self.state('zoomed')
        # endregion

        #  region Создаём внутри окна Глобальный холст self.global_canvas (и размещаем его слева)
        self.global_canvas = tk.Canvas(master=self)
        # self.global_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        # endregion

        # region Создаём self.frm внутри self.global_canvas; На нём будет размещаться всё
        self.frm = tk.Frame(master=self.global_canvas, bg='orange')
        self.frm.grid()
        # endregion

        # region Создаём Scrollbar внутри окна (и размещаем его справа)
        self.vertical_scrollbar = tk.Scrollbar(master=self, command=self.global_canvas.yview)
        self.vertical_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        # endregion
        # region Настройка Scrollbar-а. Она там как-то внутри работает. НЕХОРОШО, что не разбираюсь в этом полностью
        self.global_canvas.configure(yscrollcommand=self.vertical_scrollbar.set)
        for event in ('Configure', 'MouseWheel'):
            self.frm.bind(f'<{event}>', lambda event: self.global_canvas.configure(scrollregion=self.global_canvas.bbox('all')))
        self.global_canvas.create_window((10, 10), window=self.frm, anchor='nw')
        # endregion

        # region Создаём Scrollbar внутри окна (и размещаем его снизу)
        self.horizontal_scrollbar = tk.Scrollbar(master=self, orient=tk.HORIZONTAL, command=self.global_canvas.xview)
        self.horizontal_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
        # endregion
        # region Настройка Scrollbar-а. Она там как-то внутри работает. НЕХОРОШО, что не разбираюсь в этом полностью
        self.global_canvas.configure(xscrollcommand=self.horizontal_scrollbar.set)
        for event in ('Configure', 'MouseWheel'):
            self.frm.bind(f'<{event}>', lambda event: self.global_canvas.configure(scrollregion=self.global_canvas.bbox('all')))
        self.global_canvas.create_window((10, 10), window=self.frm, anchor='nw')
        # endregion


        self.global_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)  # важно именно здесь!


class LateXLbl(tk.Label):
    """link = 'https://stackoverflow.com/questions/36636185/is-it-possible-for-python-to-display-latex-in-real-time-in-a-text-box'"""
    def __init__(
            self,
            sides_size: tuple[int | float, int | float],
            dpi: int,
            fontsize: int,
            x_indent, y_indent,
            *args, **kwargs
    ) -> None:
        """dpi - увеличение"""
        super().__init__(*args, **kwargs)

        # region Инициализация полей класса
        for attr_name in ('fontsize', 'x_indent', 'y_indent'):
            setattr(self, attr_name, eval(attr_name))
        self._cur_latex_code: str = ''
        # endregion

        self.fig = matplotlib.figure.Figure(figsize=sides_size, dpi=dpi)
        self.ax = self.fig.add_subplot(111)

        self.canvas = FigureCanvasTkAgg(self.fig, master=self)
        self.canvas.get_tk_widget().pack(side="top", fill="both", expand=True)
        self.canvas._tkcanvas.pack(side="top", fill="both", expand=True)

        # region Отключаем прорисовку осей
        self.ax.get_xaxis().set_visible(False)
        self.ax.get_yaxis().set_visible(False)

        self.ax.spines['top'].set_visible(False)
        self.ax.spines['right'].set_visible(False)
        self.ax.spines['bottom'].set_visible(False)
        self.ax.spines['left'].set_visible(False)
        # endregion

    def write(self, tmptext: str) -> None:
        self._cur_latex_code: str = tmptext
        self.ax.text(self.x_indent, self.y_indent, tmptext, fontsize=self.fontsize)
        self.canvas.draw()

    def _clear(self) -> None:
        self.ax.clear()

    def clear(self) -> None:
        self._cur_latex_code: str = ''
        self._clear()

    text = property(fget=lambda self: self._cur_latex_code, fset=write)


root = Main_App()

ltx = LateXLbl(master=root.frm, sides_size=(20, 20), dpi=100, fontsize=100, x_indent=0.1, y_indent=0.1)
ltx.pack()

ltx.text = '$\\sum_{i=0}^5(x_i); $' * 10
root.mainloop()


678c401f16aae581320571.png
  • Вопрос задан
  • 77 просмотров
Подписаться 1 Средний Комментировать
Решения вопроса 1
@dim5x
ЗИ, ИБ. Помогли? Поблагодарите. Отметьте ответом.
Код
import tkinter as tk

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from PIL import Image, ImageTk


class ScrollableLabel(tk.Frame):
    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)

        # Переменная для хранения LaTeX-текста
        self.latex_text = r'$\\sum_{i=0}^5(x_i); $' * 100

        # Создаем Canvas и Scrollbar (вертикальный и горизонтальный)
        self.canvas = tk.Canvas(self, bg="white")
        self.v_scrollbar = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.h_scrollbar = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)

        # Настраиваем Canvas для поддержки прокрутки
        self.scrollable_frame = tk.Frame(self.canvas, bg="white")
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(
                scrollregion=self.canvas.bbox("all")
            )
        )
        self.canvas.create_window((20, 20), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.v_scrollbar.set, xscrollcommand=self.h_scrollbar.set)

        # Размещаем Canvas и Scrollbar
        self.v_scrollbar.pack(side="right", fill="y")
        self.h_scrollbar.pack(side="bottom", fill="x")
        self.canvas.pack(side="left", fill="both", expand=True)

        # Привязываем прокрутку колесом мыши
        self.bind_mouse_wheel()

        # Создаем LaTeX-изображение
        self.create_latex_label()

    def bind_mouse_wheel(self):
        """
        Привязывает прокрутку колесом мыши к Canvas.
        """
        # Для вертикальной прокрутки (Windows/Linux)
        self.canvas.bind_all("<MouseWheel>", self._on_mouse_wheel)
        # Для горизонтальной прокрутки (Shift + колесо мыши)
        self.canvas.bind_all("<Shift-MouseWheel>", self._on_shift_mouse_wheel)

    def _on_mouse_wheel(self, event):
        """
        Обрабатывает событие вертикальной прокрутки колесом мыши.
        """
        if event.num == 4 or event.delta > 0:  # Прокрутка вверх
            self.canvas.yview_scroll(-1, "units")
        elif event.num == 5 or event.delta < 0:  # Прокрутка вниз
            self.canvas.yview_scroll(1, "units")

    def _on_shift_mouse_wheel(self, event):
        """
        Обрабатывает событие горизонтальной прокрутки колесом мыши (Shift + колесо).
        """
        if event.delta > 0:  # Прокрутка влево
            self.canvas.xview_scroll(-1, "units")
        elif event.delta < 0:  # Прокрутка вправо
            self.canvas.xview_scroll(1, "units")

    def create_latex_label(self):
        """
        Создает Label с LaTeX-изображением и добавляет его в scrollable_frame.
        """
        # Создаем фигуру matplotlib для рендеринга LaTeX
        fig = Figure(figsize=(20, 20))  # Размер фигуры
        fig.patch.set_alpha(1)  # Прозрачный фон
        ax = fig.add_subplot(111)
        ax.axis("off")  # Отключаем оси

        # Рендерим LaTeX-текст
        ax.text(0.0, 1.0, self.latex_text, fontsize=20, ha="left", va="top", usetex=True)
        # Отчекрыживаем поля
        fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
        # Преобразуем фигуру в изображение
        canvas = FigureCanvasTkAgg(fig, master=self.scrollable_frame)
        canvas.draw()

        # Получаем изображение из canvas
        width, height = fig.get_size_inches() * fig.get_dpi()  # Размеры в пикселях
        image = Image.frombytes('RGBA', (int(width), int(height)), bytes(canvas.buffer_rgba()))

        # Для версий matplotlib <= 3.8:
        # image = Image.frombytes('RGB', (int(width), int(height)), canvas.tostring_rgb())
        # image.save('lol.png')
        # Преобразуем изображение в формат, поддерживаемый Tkinter
        self.image = ImageTk.PhotoImage(image)

        # Создаем Label с изображением и добавляем его в scrollable_frame
        self.label = tk.Label(self.scrollable_frame, image=self.image, bg="white")
        self.label.pack(pady=10)

        # Сохраняем ссылку на изображение, чтобы оно не удалялось сборщиком мусора
        self.label.image = self.image

    def update_latex_text(self, new_text):
        """
        Обновляет LaTeX-текст и перерисовывает Label.
        """
        self.latex_text = new_text
        # Очищаем scrollable_frame и создаем новый Label
        for widget in self.scrollable_frame.winfo_children():
            widget.destroy()
        self.create_latex_label()


if __name__ == "__main__":
    root = tk.Tk()
    root.title("LaTeX в Tk.Label с прокруткой")

    # Создаем ScrollableLabel
    scrollable_label = ScrollableLabel(root)
    scrollable_label.pack(fill="both", expand=True, padx=10, pady=10)

    # Создаем Entry для ввода текста
    entry = tk.Entry(root, font=("Arial", 14))
    entry.pack(fill="x", padx=10, pady=5)


    # Создаем Button для обновления текста
    def on_button_click():
        new_text = entry.get() 
        scrollable_label.update_latex_text(new_text) 


    button = tk.Button(root, text="Обновить", command=on_button_click, font=("Arial", 14))
    button.pack(pady=5)

    root.mainloop()

6797dc8ec49ac263327616.png

З.Ы. Это решение отображает формулы LaTex и латиницу. Если нужна кириллица, то нужно установить XeLaTeX или LuaLaTeX через MiKTeX (если его используете) или, если вы используете стандартный LaTeX (pdfLaTeX), можно добавить пакет babel. И настроить matplotlib на их использование. Ну, и не забыть, чтобы используемый шрифт поддерживал кириллицу.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы