Создание интерфейса средствами библиотеки Matplotlib | jenyay.net

Создание интерфейса средствами библиотеки Matplotlib

Содержание

Введение

В статье Как рисовать графики с помощью Matplotlib при использовании wxPython рассказывалось о том, как можно встраивать графики Matplotlib в графические приложения, созданные на основе библиотеки wxPython. Во многих случаях это оправдано, особенно если нужно создать приложение со сложным интерфейсом и использованием многих элементов управления. Однако, если требуется создать простое приложение, интерфейс которого (помимо графика) будет содержать лишь кнопки, переключатели и / или ползунки для ввода значений, то есть смысл задуматься о том, чтобы такое приложение сделать только средствами Matplotlib, чтобы приложению не требовались дополнительные библиотеки.

Matplotlib включает в себя очень небольшое количество элементов управления, которые в терминах Matplotlib называются виджетами, которые располагаются пакете matplotlib.widgets. В этом пакете содержатся также виджеты для взаимодействия с графиками (виджеты для выделения областей), но в данной статье мы сосредоточимся только на виджетах, добавляющих элементы управления. Таких элементов всего четыре (в Matplotlib версии 2.0.0):

Все эти классы являются производными от класса matplotlib.widgets.AxesWidget, который в свою очередь является производным от matplotlib.widgets.Widget. Диаграмма классов показана на следующем рисунке:

Давайте напишем небольшое приложение, которое мы будем развивать на протяжении всей статьи. Все примеры, которые описаны в данной статье подразумевают, что вы будете их запускать с помощью Python 3.x. Если вы захотите их запустить с помощью Python 2.7, то не забудьте добавить первой строчкой указание кодировки файла (# -*- coding: UTF-8 -*-), а все строки в примерах сделать юникодными (добавить перед кавычками символ u).

Для начала наше приложение будет добавлять новую функцию Гаусса на график при нажатии на кнопку. Причем ширина функции Гаусса будет увеличиваться при каждом таком нажатии. Хотя для сути примера это не важно, напомню формулу для функции Гаусса:

В этой формуле σ отвечает за ширину графика, а μ - за смещение относительно нуля по оси X.

Сначала нарисуем график данной функции, оставив снизу место для будущих элементов управления.

import numpy

import pylab


def gauss(sigma, mu, x):
    '''Отображаемая фукнция'''
    return (1.0 / (sigma * numpy.sqrt(2.0 * numpy.pi)) *
            numpy.exp(-((x - mu) ** 2) / (2 * sigma * sigma)))


def addPlot(graph_axes, sigma, mu):
    '''Добавить график к осям'''
    x = numpy.arange(-5.0, 5.0, 0.01)
    y = gauss(sigma, mu, x)
    graph_axes.plot(x, y)


if __name__ == '__main__':
    # Начальные параметры графиков
    current_sigma = 0.2
    current_mu = 0.0

    # Создадим окно с графиком
    fig, graph_axes = pylab.subplots()
    graph_axes.grid()

    # Выделим область, которую будет занимать график
    fig.subplots_adjust(left=0.07, right=0.95, top=0.95, bottom=0.2)

    # Добавить график
    addPlot(graph_axes, current_sigma, current_mu)

    pylab.show()

В этом примере с помощью функции pylab.subplots_adjust() мы указали, какую часть окна должен занимать график. Внизу мы оставили побольше места для будущей кнопки, задав параметр bottom=0.2. Напомню, что данные координаты отсчитываются от левого нижнего угла окна.

На данный момент результат работы выглядит следующим образом:

Здесь мы используем объектно-ориентированный стиль создания графиков, чтобы явно указывать, в какие оси нужно добавлять график. Сейчас станет ясно, почему это важно (и намного удобнее).

Создание кнопок

Обзор виджетов начнем с кнопки. Давайте создадим кнопку, которая будет добавлять новую кривую на график с удвоенной σ. Для этого нам надо создать экземпляр класса matplotlib.widgets.Button, конструктор которого (как и все классы, производные от matplotlib.widgets.AxesWidget) в качестве параметра принимает экземпляр класса matplotlib.axes.Axes. То есть для кнопки надо сначала создать оси, а потом их передать конструктору класса Button. Сделаем это (восклицательными знаками в комментариях в этом и последующих примерах помечены новые строки, на которые стоит обратить внимание):

import numpy

import pylab

# !!! Импортируем класс кнопки
from matplotlib.widgets import Button


def gauss(sigma, mu, x):
    '''Отображаемая фукнция'''
    return (1.0 / (sigma * numpy.sqrt(2.0 * numpy.pi)) *
            numpy.exp(-((x - mu) ** 2) / (2 * sigma * sigma)))


def addPlot(graph_axes, sigma, mu):
    '''Добавить график к осям'''
    x = numpy.arange(-5.0, 5.0, 0.01)
    y = gauss(sigma, mu, x)
    graph_axes.plot(x, y)


if __name__ == '__main__':
    # Начальные параметры графиков
    current_sigma = 0.2
    current_mu = 0.0

    # Создадим окно с графиком
    fig, graph_axes = pylab.subplots()
    graph_axes.grid()

    # Оставим снизу от графика место для виджетов
    fig.subplots_adjust(left=0.07, right=0.95, top=0.95, bottom=0.2)

    # !!! Создадим ось для кнопки
    axes_button_add = pylab.axes([0.7, 0.05, 0.25, 0.075])

    # !!! Создадим кнопку
    button_add = Button(axes_button_add, 'Добавить')

    # Добавить график
    addPlot(graph_axes, current_sigma, current_mu)

    pylab.show()

В этом примере мы создаем новые оси с помощью функции pylab.axes(), передавая список, который описывает прямоугольник: координаты левого нижнего угла будущих осей, а затем ширину и высоту области для осей. Также как и для функции subplots_adjust(), координаты отсчитываются от левого нижнего угла окна.

Теперь результат работы программы выглядит следующим образом:

Но пока эта кнопка ничего не делает. Давайте добавим ей функциональность. Для того, чтобы подписаться на событие, возникающее при нажатии кнопки, нужно вызвать метод on_clicked(), передав ему ссылку на функцию, которая будет принимать один параметр типа matplotlib.backend_bases.MouseEvent о котором говорилось в статье Обработка событий мыши и клавиатуры на графиках Matplotlib.

Важно сохранить в скрипте ссылку на экземпляр виджета (в нашем случае в переменной axes_button_add), т.к. функции обратного вызова сохраняются с помощью слабых ссылок (weak refs), и если объект виджета будет уничтожен сборщиком мусора, то функция обратного вызова не будет вызвана.

Приведенный ниже пример не осень хорош с точки зрения архитектуры приложения из-за того, что используются глобальные переменные, в реальности лучше использовать более объектно-ориентированный подход, но пример написан таким образом, чтобы быть как можно более компактным.

import numpy

import pylab

# Импортируем класс кнопки
from matplotlib.widgets import Button


def gauss(sigma, mu, x):
    '''Отображаемая фукнция'''
    return (1.0 / (sigma * numpy.sqrt(2.0 * numpy.pi)) *
            numpy.exp(-((x - mu) ** 2) / (2 * sigma * sigma)))


def addPlot(graph_axes, sigma, mu):
    '''Добавить график к осям'''
    x = numpy.arange(-5.0, 5.0, 0.01)
    y = gauss(sigma, mu, x)
    graph_axes.plot(x, y)

    # !!! Нужно для обновления графика
    pylab.draw()


if __name__ == '__main__':
    def onButtonAddClicked(event):
        ''' !!! Обработчик события для кнопки "Добавить"'''
        global current_sigma
        global current_mu
        global graph_axes

        current_sigma *= 2
        addPlot(graph_axes, current_sigma, current_mu)

    # Начальные параметры графиков
    current_sigma = 0.2
    current_mu = 0.0

    # Создадим окно с графиком
    fig, graph_axes = pylab.subplots()
    graph_axes.grid()

    # Оставим снизу от графика место для виджетов
    fig.subplots_adjust(left=0.07, right=0.95, top=0.95, bottom=0.2)

    # Создадим ось для кнопки
    axes_button_add = pylab.axes([0.7, 0.05, 0.25, 0.075])

    # Создание кнопки
    button_add = Button(axes_button_add, 'Добавить')

    # !!! Подпишемся на событие обработки нажатия кнопки
    button_add.on_clicked(onButtonAddClicked)

    # Добавить график
    addPlot(graph_axes, current_sigma, current_mu)

    pylab.show()

Обратите внимание, что мы добавили вызов функции pylab.draw() в функцию addPlot(), чтобы перерисовывать график после добавления очередной кривой. Обработчик нажатия кнопки onButtonAddClicked лишь удваивает значение переменной current_sigma, а затем добавляет новую кривую на график.

После пары нажатия кнопки график будет выглядеть следующим образом:

Метод on_clicked() возвращает целочисленный идентификатор, который затем можно использовать, чтобы отписаться от события. Для этого предназначен метод disconnect(). В частности, если бы мы хотели после подписки на событие сразу же от него отписаться, то могли бы использовать примерно следующий код:

    # Создание кнопки
    button_add = Button(axes_button_add, 'Добавить')

    # !!! Подпишемся на событие обработки нажатия кнопки
    on_clicked_id = button_add.on_clicked(onButtonAddClicked)

    # !!! Отпишемся от события on_clicked
    button_add.disconnect(on_clicked_id)

Разумеется, в реальных приложениях отписка от событий будет находиться где-то подальше от подписки, возможно, в каком-нибудь обработчике события, а во многих случаях явно отписываться от событий не придется.

Создание слайдеров

Давайте теперь добавим два слайдера (ползунка), с помощью которых пользователь сможет задавать значения σ и μ для добавляемой кривой. Для каждого из них нужно будет создать свои оси, а затем создать два экземпляра класса matplotlib.widgets.Slider.

Кроме того, мы изменим поведение обработчика события нажатия кнопки таким образом, чтобы значения σ и μ брались не из переменных, а в виде значений, установленных с помощью слайдеров. Таким образом, переменные current_sigma и current_mu нам больше не понадобятся.

Конструктор класса matplotlib.widgets.Slider принимает больше обязательных и необязательных параметров. Обязательные параметры это:

  1. ax - Экземпляр класса Axes.
  2. label - текстовая метка около слайдера.
  3. valmin - минимальное значение, которое можно установить с помощью слайдера.
  4. valmax - максимальное значение, которое можно установить с помощью слайдера.

Необязательных параметров существует больше, но в данном примере мы будем использовать два из них:

  1. valinit - начальное значение для слайдера (по умолчанию устанавливается на значение 0.5). Обратите внимание на красные линии на слайдерах, это и есть обозначение для начального значения. Класс Slider имеет метод reset() для установки значения к начальному значению.
  2. valfmt - строка форматирования текущего значения, установленного с помощью слайдера (значение по умолчанию - '%1.2f'). С помощью этого параметра можно имитировать случай, когда требуется устанавливать целочисленные значения с помощью слайдера, поскольку у этого виджета нет такого понятия как дискрет перемещения, и он всегда хранит число с плавающей точкой.
import numpy

import pylab

# !!! Импортируем класс кнопки и слайдера
from matplotlib.widgets import Button, Slider


def gauss(sigma, mu, x):
    '''Отображаемая фукнция'''
    return (1.0 / (sigma * numpy.sqrt(2.0 * numpy.pi)) *
            numpy.exp(-((x - mu) ** 2) / (2 * sigma * sigma)))


def addPlot(graph_axes, sigma, mu):
    '''Добавить график к осям'''
    x = numpy.arange(-5.0, 5.0, 0.01)
    y = gauss(sigma, mu, x)
    graph_axes.plot(x, y)

    # Нужно для обновления графика
    pylab.draw()


if __name__ == '__main__':
    def onButtonAddClicked(event):
        '''Обработчик события для кнопки "Добавить"'''
        # !!! Будем использовать sigma и mu, установленные с помощью слайдеров
        global slider_sigma
        global slider_mu
        global graph_axes

        # !!! Используем атрибут val, чтобы получить значение слайдеров
        addPlot(graph_axes, slider_sigma.val, slider_mu.val)

    # Создадим окно с графиком
    fig, graph_axes = pylab.subplots()
    graph_axes.grid()

    # Оставим снизу от графика место для виджетов
    fig.subplots_adjust(left=0.07, right=0.95, top=0.95, bottom=0.4)

    # Создание кнопки
    axes_button_add = pylab.axes([0.7, 0.05, 0.25, 0.075])
    button_add = Button(axes_button_add, 'Добавить')
    button_add.on_clicked(onButtonAddClicked)

    # !!! Создание слайдера для задания sigma
    axes_slider_sigma = pylab.axes([0.05, 0.25, 0.85, 0.04])
    slider_sigma = Slider(axes_slider_sigma,
                          label='σ',
                          valmin=0.1,
                          valmax=1.0,
                          valinit=0.5,
                          valfmt='%1.2f')

    # !!! Создание слайдера для задания mu
    axes_slider_mu = pylab.axes([0.05, 0.17, 0.85, 0.04])
    slider_mu = Slider(axes_slider_mu,
                       label='μ',
                       valmin=-4.0,
                       valmax=4.0,
                       valinit=0.0,
                       valfmt='%1.2f')

    pylab.show()

Теперь мы можем произвольно менять значения σ и μ с помощью слайдеров и добавлять соответствующие графики на оси. Вот как теперь может выглядеть окно после добавления нескольких кривых:

Теперь в примере просто напрашивается добавить кнопку "Очистить", предназначенную для удаления всех кривых. Мы теперь знаем, как это сделать, и добавить ее не составит труда:

import numpy

import pylab

# Импортируем класс кнопки и слайдера
from matplotlib.widgets import Button, Slider


def gauss(sigma, mu, x):
    '''Отображаемая фукнция'''
    return (1.0 / (sigma * numpy.sqrt(2.0 * numpy.pi)) *
            numpy.exp(-((x - mu) ** 2) / (2 * sigma * sigma)))


def addPlot(graph_axes, sigma, mu):
    '''Добавить график к осям'''
    x = numpy.arange(-5.0, 5.0, 0.01)
    y = gauss(sigma, mu, x)
    graph_axes.plot(x, y)

    # Нужно для обновления графика
    pylab.draw()


if __name__ == '__main__':
    def onButtonAddClicked(event):
        '''Обработчик события для кнопки "Добавить"'''
        # Будем использовать sigma и mu, установленные с помощью слайдеров
        global slider_sigma
        global slider_mu
        global graph_axes

        # Используем атрибут val, чтобы получить значение слайдеров
        addPlot(graph_axes, slider_sigma.val, slider_mu.val)

    def onButtonClearClicked(event):
        '''!!! Обработчик события нажатия кнопки "Очистить"'''
        global graph_axes

        graph_axes.clear()
        graph_axes.grid()
        pylab.draw()

    # Создадим окно с графиком
    fig, graph_axes = pylab.subplots()
    graph_axes.grid()

    # Оставим снизу от графика место для виджетов
    fig.subplots_adjust(left=0.07, right=0.95, top=0.95, bottom=0.4)

    # Создание кнопки "Добавить"
    axes_button_add = pylab.axes([0.55, 0.05, 0.4, 0.075])
    button_add = Button(axes_button_add, 'Добавить')
    button_add.on_clicked(onButtonAddClicked)

    # !!! Создание кнопки "Очистить"
    axes_button_clear = pylab.axes([0.05, 0.05, 0.4, 0.075])
    button_clear = Button(axes_button_clear, 'Очистить')
    button_clear.on_clicked(onButtonClearClicked)

    # Создание слайдера для задания sigma
    axes_slider_sigma = pylab.axes([0.05, 0.25, 0.85, 0.04])
    slider_sigma = Slider(axes_slider_sigma,
                          label='σ',
                          valmin=0.1,
                          valmax=1.0,
                          valinit=0.5,
                          valfmt='%1.2f')

    # Создание слайдера для задания mu
    axes_slider_mu = pylab.axes([0.05, 0.17, 0.85, 0.04])
    slider_mu = Slider(axes_slider_mu,
                       label='μ',
                       valmin=-4.0,
                       valmax=4.0,
                       valinit=0.0,
                       valfmt='%1.2f')

    pylab.show()

Теперь окно программы будет выглядеть следующим образом:

У слайдеров также имеется событие on_changed, подписавшись на которое, можно получать уведомления о том, что значение слайдера изменилось. Функция-обработчик события on_changed должна принимать один параметр, который будет представлять собой новое значение, установленное на слайдере (тип параметра - numpy.float64). Никакого идентификатора объекта, который создал событие, не передается. Таким образом, если нужно различать события от разных виджетов, то вы должны подписаться на события с помощью разных функций.

Давайте теперь мы будем не добавлять несколько разных графиков с различными параметрами, а управлять параметрами одной кривой. Для этого мы уберем все кнопки, а изменение значений на слайдерах будет приводить к перерисовке одного и того же графика.

Код примера теперь выглядит таким образом:

import numpy

import pylab

# Импортируем класс слайдера
from matplotlib.widgets import Slider


def gauss(sigma, mu, x):
    '''Отображаемая фукнция'''
    return (1.0 / (sigma * numpy.sqrt(2.0 * numpy.pi)) *
            numpy.exp(-((x - mu) ** 2) / (2 * sigma * sigma)))


if __name__ == '__main__':
    def updateGraph():
        '''!!! Функция для обновления графика'''
        # Будем использовать sigma и mu, установленные с помощью слайдеров
        global slider_sigma
        global slider_mu
        global graph_axes

        # Используем атрибут val, чтобы получить значение слайдеров
        sigma = slider_sigma.val
        mu = slider_mu.val
        x = numpy.arange(-5.0, 5.0, 0.01)
        y = gauss(sigma, mu, x)

        graph_axes.clear()
        graph_axes.plot(x, y)
        pylab.draw()

    def onChangeValue(value):
        '''!!! Обработчик события изменения значений слайдеров'''
        updateGraph()

    # Создадим окно с графиком
    fig, graph_axes = pylab.subplots()
    graph_axes.grid()

    # Оставим снизу от графика место для виджетов
    fig.subplots_adjust(left=0.07, right=0.95, top=0.95, bottom=0.4)

    # Создание слайдера для задания sigma
    axes_slider_sigma = pylab.axes([0.05, 0.25, 0.85, 0.04])
    slider_sigma = Slider(axes_slider_sigma,
                          label='σ',
                          valmin=0.1,
                          valmax=1.0,
                          valinit=0.5,
                          valfmt='%1.2f')

    # !!! Подпишемся на событие при изменении значения слайдера.
    slider_sigma.on_changed(onChangeValue)

    # Создание слайдера для задания mu
    axes_slider_mu = pylab.axes([0.05, 0.17, 0.85, 0.04])
    slider_mu = Slider(axes_slider_mu,
                       label='μ',
                       valmin=-4.0,
                       valmax=4.0,
                       valinit=0.0,
                       valfmt='%1.2f')

    # !!! Подпишемся на событие при изменении значения слайдера.
    slider_mu.on_changed(onChangeValue)

    updateGraph()
    pylab.show()

Результат работы выглядит так:

При изменении значений σ и μ с помощью слайдеров кривая на графике перемещается влево-вправо и меняет свою ширину (а вместе с ней и высоту).

Создание радиокнопок

Нам осталось рассмотреть еще два виджета с элементами управления - это наборы радиокнопок (переключателей, radio buttons) и флажков (check buttons). На мой взгляд эти виджеты не очень удобные и красивые, но если хочется обойтись только средствами Matplotlib, то выбора у нас нет.

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

Группа радиокнопок создается аналогично другим виджетам: сначала надо создать оси, где они будут располагаться, а в конструктор класса RadioButtons (обратите внимание на "s" в конце имени класса) кроме созданных осей нужно передать список строковых меток - те надписи, которые будут соответствовать пунктам, один из которых будет выбран.

Класс RadioButtons также посылает событие on_clicked(), когда пользователь щелкает на них мышкой и меняет выбранное значение (или не меняет, если щелкает на уже выбранный переключатель). В отличие от одноименного события класса Button в качестве параметра функции-обработчика событий передается строковое значение, соответствующее метке выбранного переключателя.

Как ни странно, но у RadioButtons нет свойств или методов узнать номер выбранной в данный момент метки, мы можем узнать только текст метки выбранного пункта с помощью свойства value_selected.

Новый пример, который позволяет выбирать цвет для линии выглядит следующим образом:

import numpy

import pylab

# Импортируем класс слайдера
from matplotlib.widgets import Slider, RadioButtons


def gauss(sigma, mu, x):
    '''Отображаемая фукнция'''
    return (1.0 / (sigma * numpy.sqrt(2.0 * numpy.pi)) *
            numpy.exp(-((x - mu) ** 2) / (2 * sigma * sigma)))


if __name__ == '__main__':
    def updateGraph():
        '''Функция для обновления графика'''
        # Будем использовать sigma и mu, установленные с помощью слайдеров
        global slider_sigma
        global slider_mu
        global graph_axes
        global radiobuttons_color

        colors = {'Красный': 'r', 'Синий': 'b', 'Зеленый': 'g'}

        # Используем атрибут val, чтобы получить значение слайдеров
        sigma = slider_sigma.val
        mu = slider_mu.val
        x = numpy.arange(-5.0, 5.0, 0.01)
        y = gauss(sigma, mu, x)

        style = colors[radiobuttons_color.value_selected]

        graph_axes.clear()
        graph_axes.plot(x, y, style)
        graph_axes.grid()
        pylab.draw()

    def onChangeGraph(value):
        '''Обработчик события изменения значений слайдеров'''
        updateGraph()

    def onRadioButtonsClicked(label):
        '''!!! Обработчик события при клике по RadioButtons'''
        updateGraph()

    # Создадим окно с графиком
    fig, graph_axes = pylab.subplots()
    graph_axes.grid()

    # Оставим снизу от графика место для виджетов
    fig.subplots_adjust(left=0.07, right=0.95, top=0.95, bottom=0.5)

    # Создание слайдера для задания sigma
    axes_slider_sigma = pylab.axes([0.05, 0.35, 0.85, 0.04])
    slider_sigma = Slider(axes_slider_sigma,
                          label='σ',
                          valmin=0.1,
                          valmax=1.0,
                          valinit=0.5,
                          valfmt='%1.2f')

    # Подпишемся на событие при изменении значения слайдера.
    slider_sigma.on_changed(onChangeGraph)

    # Создание слайдера для задания mu
    axes_slider_mu = pylab.axes([0.05, 0.27, 0.85, 0.04])
    slider_mu = Slider(axes_slider_mu,
                       label='μ',
                       valmin=-4.0,
                       valmax=4.0,
                       valinit=0.0,
                       valfmt='%1.2f')

    # Подпишемся на событие при изменении значения слайдера.
    slider_mu.on_changed(onChangeGraph)

    # !!! Создание осей для переключателей
    axes_radiobuttons = pylab.axes([0.05, 0.05, 0.2, 0.2])

    # !!! Создание переключателя
    radiobuttons_color = RadioButtons(axes_radiobuttons,
                                      ['Красный', 'Синий', 'Зеленый'])
    radiobuttons_color.on_clicked(onRadioButtonsClicked)

    updateGraph()
    pylab.show()

Результат работы программы выглядит следующим образом:

Обратите внимание, что в этом примере для осей переключателя выбран одинаковый размер по вертикали и горизонтали:

axes_radiobuttons = pylab.axes([0.05, 0.05, '''0.2''', '''0.2'''])

Дело в том, что в противном случае у переключателей начинает растягиваться кружок-индикатор выбранного пункта. Вот, например, как будет выглядеть тот же пример, если оси растянуть по горизонтали в два раза:

axes_radiobuttons = pylab.axes([0.05, 0.05, '''0.4''', '''0.2'''])

На мой взгляд это выглядит не очень эстетично.

Создание флажков (check buttons)

И последний виджет, который нам надо рассмотреть - это набор флажков - CheckButtons (так же обратите внимание на букву "s" в конце имени класса). Этот класс создается аналогично классу RadioButtons, в конструктор CheckButtons нужно передать список меток флажков и еще один список булевых значений, которые показывают начальные состояния этих флажков: установлены они или сброшены.

ИМХО, этот класс еще менее удобный, чем RadioButtons, и вот почему. Дело в том, что у CheckButtons нет свойств или методов, чтобы узнать, установлен ли тот или иной флаг. Их изменения приходится отслеживать самостоятельно, обрабатывая событие on_clicked, в обработчик которого, как и у RadioButtons, передается метка флажка, на который кликнули. В этом событии нужно инвертировать булеву переменную, которая бы обозначала состояние флажка. Напрашивается сделать класс, производный от CheckButtons, чтобы сделать его более удобным для использования, но такая задача выходит за рамки этой статьи.

Давайте в предыдущий пример добавим флажок, который будет включать и отключать сетку на графике.

import numpy

import pylab

# Импортируем класс слайдера
from matplotlib.widgets import Slider, RadioButtons, CheckButtons


def gauss(sigma, mu, x):
    '''Отображаемая фукнция'''
    return (1.0 / (sigma * numpy.sqrt(2.0 * numpy.pi)) *
            numpy.exp(-((x - mu) ** 2) / (2 * sigma * sigma)))


if __name__ == '__main__':
    def updateGraph():
        '''Функция для обновления графика'''
        # Будем использовать sigma и mu, установленные с помощью слайдеров
        global slider_sigma
        global slider_mu
        global graph_axes
        global radiobuttons_color
        global grid_visible

        colors = {'Красный': 'r', 'Синий': 'b', 'Зеленый': 'g'}

        # Используем атрибут val, чтобы получить значение слайдеров
        sigma = slider_sigma.val
        mu = slider_mu.val
        x = numpy.arange(-5.0, 5.0, 0.01)
        y = gauss(sigma, mu, x)

        style = colors[radiobuttons_color.value_selected]

        graph_axes.clear()
        graph_axes.plot(x, y, style)

        if grid_visible:
            graph_axes.grid()
        pylab.draw()

    def onChangeGraph(value):
        '''Обработчик события изменения значений слайдеров'''
        updateGraph()

    def onRadioButtonsClicked(label):
        '''Обработчик события при клике по RadioButtons'''
        updateGraph()

    def onCheckClicked(label):
        ''' !!! Обработчик события при клике по CheckButtons'''
        global grid_visible
        if label == 'Сетка':
            # Если щелкнули на флажке "Сетка",
            # (другого у нас нет, но для общности проверку лучше оставить),
            # то инвертируем значение переменной grid_visible
            grid_visible = not grid_visible

        updateGraph()

    # Создадим окно с графиком
    fig, graph_axes = pylab.subplots()
    graph_axes.grid()

    # !!! Состояние флажка и видимость сетки
    grid_visible = True

    # Оставим снизу от графика место для виджетов
    fig.subplots_adjust(left=0.07, right=0.95, top=0.95, bottom=0.55)

    # Создание слайдера для задания sigma
    axes_slider_sigma = pylab.axes([0.05, 0.35, 0.85, 0.04])
    slider_sigma = Slider(axes_slider_sigma,
                          label='σ',
                          valmin=0.1,
                          valmax=1.0,
                          valinit=0.5,
                          valfmt='%1.2f')

    # Подпишемся на событие при изменении значения слайдера.
    slider_sigma.on_changed(onChangeGraph)

    # Создание слайдера для задания mu
    axes_slider_mu = pylab.axes([0.05, 0.27, 0.85, 0.04])
    slider_mu = Slider(axes_slider_mu,
                       label='μ',
                       valmin=-4.0,
                       valmax=4.0,
                       valinit=0.0,
                       valfmt='%1.2f')

    # Подпишемся на событие при изменении значения слайдера.
    slider_mu.on_changed(onChangeGraph)

    # Создание осей для переключателей
    axes_radiobuttons = pylab.axes([0.05, 0.05, 0.2, 0.2])

    # Создание переключателя
    radiobuttons_color = RadioButtons(axes_radiobuttons,
                                      ['Красный', 'Синий', 'Зеленый'])
    radiobuttons_color.on_clicked(onRadioButtonsClicked)

    # !!! Создание осей флажка
    axes_checkbuttons = pylab.axes([0.35, 0.15, 0.2, 0.1])

    # !!! Создание флажка
    checkbutton_grid = CheckButtons(axes_checkbuttons,
                                    ['Сетка'],
                                    [True])
    checkbutton_grid.on_clicked(onCheckClicked)

    updateGraph()
    pylab.show()

Результат выглядит следующим образом:

В этом примере значение флажка "Сетка" хранится в переменной grid_visible. В обработчике событий onCheckClicked проверяется, на каком флажке кликнули (у нас он один в группе CheckButtons, но для общности проверка имени флажка оставлена. Если кликнули на флажок "Сетка", то значение переменной grid_visible инвертируется. Эта переменная используется в функции updateGraph, чтобы проверить, нужно ли нам отображать сетку.

Заключение

Вот мы и рассмотрели те виджеты из пакета matplotlib.widgets, которые относятся к созданию элементов интерфейса. В этот пакет еще входят виджеты для различных выделений областей на графиках, но это тема для отдельной статьи. Мы научились создавать кнопки (класс Button), ползунки (слайдеры, класс Slider), группы переключателей (радиокнопок, класс RadioButtons) и группы флажков (класс CheckButtons), рассмотрели события, которые они могут посылать и параметры, которые принимают обработчики событий.

При создании виджета нужно сначала создать оси с помощью функции pylab.axes (экземпляр класса matplotlib.widgets.AxesWidget), в которых будут располагаться создаваемые виджеты.

Но если вам все же не хватает возможностей, предоставляемых пакетом matplotlib.widgets, то для создания интерфейса можно воспользоваться, например, библиотекой wxPython. На мой взгляд кнопки и слайдеры выглядят достаточно неплохо, а вот внешний вид переключателей и флажков оставляет желать лучшего.

Другие статьи про Matplotlib

Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.
5 stars

Рейтинг 4.7/5. Всего 15 голос(а, ов)



 22.03.2023 - 13:43


Подписаться на комментарии
Автор:
Тема:
 Ваш комментарий
 
 
Введите код 764