使用Gtk +按钮事件更新matplotlib图

时间:2017-01-14 13:34:32

标签: python-3.x matplotlib pygtk

我已经在Gtk +窗口中封装了一个matplotlib图,我试图在点击一个按钮时更新该图(它的高斯'圆问题)。麻烦的是,我不确定如何通过事件更新情节。到目前为止,我有以下内容。

#! /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更新窗口中的情节。

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

enter image description here

2 个答案:

答案 0 :(得分:1)

这是我发现问题的解决方案:

#! /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()

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

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

答案 1 :(得分:0)

可能不完全适合你正在做的事情,但是对于高斯圆问题(使用一些Java源代码和一个丑陋但方便的插图)也有类似的简单但更快的算法:{{3} }

它比其中一个季度中的点数加上中心,加上轴上的点数快了大约3.4倍,你现在正在做,而只需再多行一行代码。

你只需要想象一个刻有铭文的正方形,只计算那个圆圈内那个正方形外面的八分之一。