使用 Gtk+ 按钮事件更新 matplotlib 图 [英] Update matplotlib plot with Gtk+ button event

查看:54
本文介绍了使用 Gtk+ 按钮事件更新 matplotlib 图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 Gtk+ 窗口中封装了一个 matplotlib 图,我试图在单击按钮时更新该图(这是高斯圆问题).问题是,我不确定如何让情节随事件更新.到目前为止,我有以下内容.

I've encapsulated a matplotlib plot in a Gtk+ window and I'm trying to update that plot when a button is clicked (it's Gauss' circle problem). Trouble is, I'm not exactly sure how to get the plot to update with an event. So far I have the following.

#! /usr/bin/env python3.4
# -*- coding: utf-8 -*-

""" Main application--embed Matplotlib figure in window with UI """


import gi
gi.require_version('Gtk', '3.0')

import numpy as np
from gi.repository import Gtk, GObject
from matplotlib.figure import Figure

# make sure cairocffi is installed, pycairo doesn't support FigureCanvasGTK3Agg
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg \
    as FigureCanvas

from matplotlib.patches import Ellipse
from typing import List, Tuple
from math import sqrt


class Main(Gtk.Window):
    """ Main window UI """
    SIGMA = 10

    def __init__(self):
        Gtk.Window.__init__(self, title='Gauss\' Circle Problem')
        self.connect('destroy', lambda _: Gtk.main_quit())
        self.set_border_width(10)
        self.set_default_size(600, 450)

        # Set up the l/r box layout
        self.box = Gtk.Box(spacing=10)
        self.add(self.box)

        # Set up the right column
        self.rcolumn = Gtk.Grid()
        self.box.pack_end(self.rcolumn, False, False, 1)

        # Set up spin button
        adjustment = Gtk.Adjustment(10, 3, 100, 1, 0, 0)
        self.spinbutton = Gtk.SpinButton()
        self.spinbutton.set_adjustment(adjustment)
        self.rcolumn.attach(self.spinbutton, 0, 0, 1, 1)

        # Set up update button
        self.update_plot_button = Gtk.Button(label='Update')
        self.update_plot_button.connect('clicked', self.update_sigma_event)
        self.rcolumn.attach_next_to(self.update_plot_button, 
            self.spinbutton, Gtk.PackDirection.BTT, 1, 1)

        self._add_plot()

    def update_sigma_event(self, button) -> None:
        """ Update sigma and replot """
        self.SIGMA = self.spinbutton.get_value()
        self._add_plot()

    def _add_plot(self) -> None:
        """ Add the plot to the window """
        fig = Figure(figsize=(5, 4))
        ax = fig.add_subplot(111, aspect='equal')

        arr = np.zeros([self.SIGMA * 2 + 1] * 2)

        points = self.collect(int(self.SIGMA), int(self.SIGMA), self.SIGMA)

        # flip pixel value if it lies inside (or on) the circle
        for p in points:
            arr[p] = 1

        # plot ellipse on top of boxes to show their centroids lie inside
        circ = Ellipse(\
            xy=(int(self.SIGMA), int(self.SIGMA)), 
            width=2 * self.SIGMA,
            height=2 * self.SIGMA,
            angle=0.0
        )

        ax.add_artist(circ)
        circ.set_clip_box(ax.bbox)
        circ.set_alpha(0.2)
        circ.set_facecolor((1, 1, 1))
        ax.set_xlim(-0.5, 2 * self.SIGMA + 0.5)
        ax.set_ylim(-0.5, 2 * self.SIGMA + 0.5)

        # Plot the pixel centers
        ax.scatter(*zip(*points), marker='.', color='white')

        # now plot the array that's been created
        ax.imshow(-arr, interpolation='none', cmap='gray')

        # add it to the window
        canvas = FigureCanvas(fig)
        self.box.pack_start(canvas, True, True, 0)


    @staticmethod
    def collect(x: int, y: int, sigma: float =3.0) -> List[Tuple[int, int]]:
        """ create a small collection of points in a neighborhood of some 
        point 
        """
        neighborhood = []

        X = int(sigma)
        for i in range(-X, X + 1):
            Y = int(pow(sigma * sigma - i * i, 1/2))
            for j in range(-Y, Y + 1):
                neighborhood.append((x + i, y + j))

        return neighborhood


if __name__ == '__main__':
    window = Main()
    window.show_all()
    Gtk.main()

我不确定从哪里开始,我只知道更新 SpinButton 确实会调整 self.SIGMA,但我不知道如何告诉matplotlib更新窗口中的绘图.

I'm not exactly sure where to go from here, I just know that updating the SpinButton indeed adjusts self.SIGMA, but I don't know how to tell matplotlib to update the plot in the window.

此外,如果您无法运行它,这就是当前的样子(我还尝试将右列中的两个按钮小部件垂直居中:P):

Also, this is what it looks like currently if you aren't able to run it (I'm also trying to vertically center the two button widgets in the right column :P):

推荐答案

这是我发现的解决方案:

This is a solution I've found to my problem:

#! /usr/bin/env python3.4
# -*- coding: utf-8 -*-

""" Main application--embed Matplotlib figure in window with UI """

import gi
gi.require_version('Gtk', '3.0')

import numpy as np
from gi.repository import Gtk, GObject
from matplotlib.figure import Figure

# make sure cairocffi is installed, pycairo doesn't support FigureCanvasGTK3Agg
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg \
    as FigureCanvas

from matplotlib.patches import Ellipse
from typing import List, Tuple, Union
from math import sqrt


class Main(Gtk.Window):
    """ Main window UI """
    SIGMA = 10
    INVERT = -1

    def __init__(self) -> None:
        Gtk.Window.__init__(self, title='Gauss\' Circle Problem')
        self.connect('destroy', lambda _: Gtk.main_quit())
        self.set_border_width(10)
        self.set_default_size(650, 500)

        # Set up the l/r box layout
        self.box = Gtk.Box(spacing=10)
        self.add(self.box)

        # Set up the right column
        self.rcolumn = Gtk.VBox(spacing=0)
        self.rcolumn.set_spacing(10)
        self.box.pack_end(self.rcolumn, False, False, 20)

        # Set up spin button
        adjustment = Gtk.Adjustment(self.SIGMA, 1, 30, 1, 0, 0)
        self.spinbutton = Gtk.SpinButton()
        self.spinbutton.set_adjustment(adjustment)
        self.rcolumn.pack_start(self.spinbutton, False, False, 0)

        # Set up invert checkbox
        self.invertbutton = Gtk.CheckButton('Invert')
        self.invertbutton.set_active(True)
        self.invertbutton.connect('toggled', self.switch_toggle_parity, 'invert')
        self.rcolumn.add(self.invertbutton)

        # Set up update button
        self.update_plot_button = Gtk.Button(label='Update')
        self.update_plot_button.connect('clicked', self.update_sigma_event)
        self.rcolumn.add(self.update_plot_button)

        self.initial_plot()

    def calculate(self) -> None:
        """ Re-calculate using the formula """
        arr = np.zeros([self.SIGMA * 2 + 1] * 2)

        points = self.collect(int(self.SIGMA), int(self.SIGMA), self.SIGMA)

        # flip pixel value if it lies inside (or on) the circle
        for p in points:
            arr[p] = 1

        # plot ellipse on top of boxes to show their centroids lie inside
        circ = Ellipse(
            xy=(int(self.SIGMA), int(self.SIGMA)),
            width=2 * self.SIGMA,
            height=2 * self.SIGMA,
            angle=0.0
        )

        self.ax.clear()
        self.ax.add_artist(circ)
        circ.set_clip_box(self.ax.bbox)
        circ.set_alpha(0.2)
        circ.set_facecolor((1, 1, 1))
        self.ax.set_xlim(-0.5, 2 * self.SIGMA + 0.5)
        self.ax.set_ylim(-0.5, 2 * self.SIGMA + 0.5)

        # Plot the pixel centers
        self.ax.scatter(*zip(*points), marker='.',
            color='white' if self.INVERT == -1 else 'black')

        # now plot the array that's been created
        self.ax.imshow(self.INVERT * arr, interpolation='none', cmap='gray')

    def initial_plot(self) -> None:
        """ Set up the initial plot; only called once """
        self.fig = Figure(figsize=(5, 4))
        self.canvas = FigureCanvas(self.fig)
        self.box.pack_start(self.canvas, True, True, 0)
        self.ax = self.fig.add_subplot(111, aspect='equal')
        self.calculate()
        self.draw_plot()

    def update_sigma_event(self, button: Union[Gtk.Button, None] =None) -> None:
        """ Update sigma and trigger a replot """
        self.SIGMA = int(self.spinbutton.get_value())
        self.calculate()
        self.draw_plot()

    def switch_toggle_parity(self, button: Union[Gtk.CheckButton, None] =None,
            name: str ='') -> None:
        """ Switch the parity of the plot before update """
        self.INVERT *= -1

    def draw_plot(self) -> None:
        """ Draw or update the current plot """
        self.fig.canvas.draw()

    @staticmethod
    def collect(x: int, y: int, sigma: float =3.0) -> List[Tuple[int, int]]:
        """ create a small collection of points in a neighborhood of some 
        point 
        """
        neighborhood = []

        X = int(sigma)
        for i in range(-X, X + 1):
            Y = int(pow(sigma * sigma - i * i, 1/2))
            for j in range(-Y, Y + 1):
                neighborhood.append((x + i, y + j))

        return neighborhood


if __name__ == '__main__':
    window = Main()
    window.show_all()
    Gtk.main()

我还添加了一个按钮,用于交换二进制图像图的奇偶校验并重新构建方法调用.

I've also added a button that swaps the parity of the binary image plot and re-structured the method calls.

这是一个缓慢/简单的开始,但我想我们所有人都必须从某个地方开始!欢迎提出意见和建议.

It's a slow/simple start, but I suppose we all have to start somewhere! Comments and suggestions welcome.

这篇关于使用 Gtk+ 按钮事件更新 matplotlib 图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆