Шаг 75.
Библиотека Tkinter. Компоненты и вспомогательные классы. Нестилизуемые компоненты. Компонент Canvas: интерактивное построение графика

    На этом шаге мы приведем приложение для интерактивного построения графиков.

    В примере ниже показана возможность совместного использования графики matplotlib и элементов управления tkinter. В верхней части окна расположено текстовое поле (элемент Entry), предназначенное для ввода выражения относительно переменной х, график которого нужно построить. В два текстовых поля справа вводятся значения параметров а и b, задающие интервал [a,b] изменения аргумента х. Нажатие клавиши Enter или кнопки "График" строит кривую.

from tkinter import *
from numpy import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from tkinter.messagebox import showerror
import warnings


def evaluate(event):
    try:
        mystr = entry.get()
        exec('f = lambda x:' + mystr, globals())
        a = float(strA.get())
        b = float(strB.get())
        X = linspace(a, b, 300)
        Y = [f(x) for x in X]
        ax.clear()  # очистить графическую область
        ax.plot(X, Y, linewidth=2)
        ax.grid(color='b', alpha=0.5, linestyle='dashed', linewidth=0.5)
        canvasAgg.draw()  # перерисовать "составной" холст
        return
    except:  # реакция на любую ошибку
        showerror('Ошибка', "Неверное выражение или интервал [a,b].")


def evaluate2(event):  # чтобы кнопка отжималась при ошибке
    root.after(100, evaluate, event)


root = Tk()
root.wm_title("График функции")
warnings.filterwarnings("error")
frameUp = Frame(root, relief=SUNKEN, height=64)
frameUp.pack(side=TOP, fill=X)
Label(frameUp, text="Выражение: ").place(x=20, y=4, width=100, height=25)
Label(frameUp, text="Начало интервала a:").place(x=250, y=4, width=140, height=25)
Label(frameUp, text="Конец интервала b:").place(x=370, y=4, width=140, height=25)
entry = Entry(frameUp, relief=RIDGE, borderwidth=4)
entry.bind("<Return>", evaluate)
entry.place(x=6, y=30, width=250, height=25)
strA = StringVar()
strA.set(0)
entryA = Entry(frameUp, relief=RIDGE, borderwidth=4, textvariable=strA)
entryA.place(x=280, y=30, width=80, height=25)
entryA.bind("<Return>", evaluate)
strB = StringVar()
strB.set(1)
entryB = Entry(frameUp, relief=RIDGE, borderwidth=4, textvariable=strB)
entryB.place(x=400, y=30, width=80, height=25)
entryB.bind("<Return>", evaluate)
fig = Figure(figsize=(5, 4), dpi=100, facecolor='white')
ax = fig.add_subplot(111)
canvasAgg = FigureCanvasTkAgg(fig, master=root)
canvasAgg.draw()
canvas = canvasAgg.get_tk_widget()
canvas.pack(fill=BOTH, expand=1)
btn = Button(root, text='График')
btn.bind("<Button-1>", evaluate2)
btn.pack(ipady=2, pady=4, padx=10)
root.bind('<Control-z>', lambda event: root.destroy())
root.mainloop()
Архив с файлом можно взять здесь.

    Результат работы приложения приведен на рисунке 1.


Рис.1. Результат работы приложения

    Прокомментируем приведенный пример.

    Программа начинается с импортирования необходимых модулей и функций. Далее мы создаем функцию evaluate(). Она рисует график кривой и вызывается при нажатии на клавишу Enter.

def evaluate(event):
    try:
        mystr = entry.get()
        exec('f = lambda x:' + mystr, globals())
        a = float(strA.get())
        b = float(strB.get())
        X = linspace(a, b, 300)
        Y = [f(x) for x in X]
        ax.clear()  # очистить графическую область
        ax.plot(X, Y, linewidth=2)
        ax.grid(color='b', alpha=0.5, linestyle='dashed', linewidth=0.5)
        canvasAgg.draw()  # перерисовать "составной" холст
        return
    except:  # реакция на любую ошибку
        showerror('Ошибка', "Неверное выражение или интервал [a,b].")

    Инструкция

        mystr = entry.get()
читает выражение (формулу) из виджета entry в строковую переменную mystr. Функция exec() рассматривает свой первый аргумент - строку strcode как набор команд Python и выполняет их. В нашем случае строка
  'f = lambda x:' + mystr
содержит код лямбда-функции аргумента х, вычисляемой по формуле, которая содержится в строке mystr. Второй аргумент функции exec() определяет пространство имен, в котором будут располагаться переменные создаваемого кода. В результате выполнения инструкции
        exec('f = lambda x:' + mystr, globals())
функция f(x) будет размещена в пространстве имен модуля.

    С текстовыми виджетами entryA и entryB, в которые вводятся границы интервала [a, b] изменения независимой переменной, мы связали переменные слежения strA и strB (см. код далее). Две последующие инструкции преобразуют содержимое этих переменных в числовые значения а и b.

    Выражения, вводимые пользователем в текстовом виджете entry, а также текстовые поля а и bмогут содержать синтаксические или другие ошибки. Поэтому описанные выше инструкции следует помещать в конствукции try ... except, используемые для обработки исключительных ситуаций. В нашем примере реакция на любую ошибку пользователя состоит в отображении окна сообщений showerror().

    Используя значения а и b, мы создаем список X значений аргумента и список Y значений функции f. Перед последующим построением графическая область очищается от графика, нарисованного ранее. Инструкция

        ax.plot(X, Y, linewidth=2)
создает новый график, а инструкция
        ax.grid(color='b', alpha=0.5, linestyle='dashed', linewidth=0.5)
рисует координатную сетку. Графическая область ax располагается в графическом окне fig, которое находится на "составном" холсте canvasAgg (см. код далее). После перерисовки графической области ax следует обновить содержимое этого холста командой canvasAgg.draw().
def evaluate2(event):  # чтобы кнопка отжималась при ошибке
    root.after(100, evaluate, event)

    Функция evaluate2() является копией функции evaluate(), но запускается с задержкой 100 миллисекунд. Она вызывается при нажатии кнопки "График", а задержка нужна для того, чтобы процедуры перерисовки кнопки успевали ее "отжать", если в функции evaluate() сгенерирована исключительная ситуация (ошибка).

    После создания функций идет код построения окна приложения:

root = Tk()
root.wm_title("График функции")

    Следующая инструкция

warnings.filterwarnings("error")
указывает исполнительной системе Python интерпретировать предупреждения как ошибки. Без этой команды некоторые ошибки генерируют предупреждения (warnings), а не исключительные ситуации. Мы хотим все предупреждения и ошибки обрабатывать единообразно в обработчике исключительных ситуаций. Функция filterwarnings("error") находится в модуле warnings, который мы импортировали в начале программы. Иногда вам захочется игнорировать все предупреждения, тогда вы можете использовать эту функцию с аргументом "ignore".

    Далее идут инструкции создания интерфейса программы. Мы создаем рамку Frame с метками и текстовыми виджетами, предназначенными для ввода выражения и переменных а и b. К текстовым виджетам прикрепляем функцию evaluate() обработки события <Return> нажатия на клавишу Enter.

frameUp = Frame(root, relief=SUNKEN, height=64)
frameUp.pack(side=TOP, fill=X)
Label(frameUp, text="Выражение: ").place(x=20, y=4, width=100, height=25)
Label(frameUp, text="Начало интервала a:").place(x=250, y=4, width=140, height=25)
Label(frameUp, text="Конец интервала b:").place(x=370, y=4, width=140, height=25)
entry = Entry(frameUp, relief=RIDGE, borderwidth=4)
entry.bind("<Return>", evaluate)
entry.place(x=6, y=30, width=250, height=25)
strA = StringVar()
strA.set(0)
entryA = Entry(frameUp, relief=RIDGE, borderwidth=4, textvariable=strA)
entryA.place(x=280, y=30, width=80, height=25)
entryA.bind("<Return>", evaluate)
strB = StringVar()
strB.set(1)
entryB = Entry(frameUp, relief=RIDGE, borderwidth=4, textvariable=strB)
entryB.place(x=400, y=30, width=80, height=25)
entryB.bind("<Return>", evaluate)

    Для текстовых виджетов entryA и entryB также создаются переменные слежения strA и strB. После этого создается графическое окно Figure, которое помещается на "составной" холст FigureCanvasTkAgg:

fig = Figure(figsize=(5, 4), dpi=100, facecolor='white')
ax = fig.add_subplot(111)
canvasAgg = FigureCanvasTkAgg(fig, master=root)
canvasAgg.draw()
canvas = canvasAgg.get_tk_widget()
canvas.pack(fill=BOTH, expand=1)

    Затем создается кнопка Button построения графика, к которой присоединяется функция evaluate2() обработки "клика":

btn = Button(root, text='График')
btn.bind("<Button-1>", evaluate2)
btn.pack(ipady=2, pady=4, padx=10)

    Завершается код двумя стандартными инструкциями. Одна подключает процедуру destroy() завершения приложения к комбинации клавиш CTRL+Z. Другая - запускает цикл обработки сообщений:

root.bind('<Control-z>', lambda event: root.destroy())
root.mainloop()

    На следующем шаге мы рассмотрим виджет Text.




Предыдущий шаг Содержание Следующий шаг