如何使用 Python 和 Glade 在 GTK3 中刷新 matplotlib 图表

How to refresh matplotlib chart in GTK3 with Python and Glade

我已经使用 GTK3、Python 和 Glade 将两个 matplotlib 图插入到一个容器中。他们使用来自第三方 API 的数据绘制区域天气。我希望在输入新语言环境并按下刷新按钮时更新图表。

以下每个解决方案都以不同的问题结束。太多无法描述。通常,他们已经设法显示带有新图表的主 window 的另一个实例,但带有旧图表的旧实例仍然打开。我试过:

还阅读了这篇文章的不同部分 documentation 并尝试了其他一些东西,但是这两天时间太长了,所以我忘记了。

大多数示例似乎使用 GTK3 和 Python 构建应用程序,但没有使用 Glade。他们还使用 classes。我不想使用 classes(暂时)。在我将整个事情重写成一个 class 之前,我想看看是否有人知道解决方案。我可能只是误解或遗漏了什么。

我是 GTK 和 Glade 的新手,这是我的第一次尝试,所以请原谅。我省略了 SQL CRUD 代码和向 API 发送请求的代码。那些工作完美。相关代码:

# SETUP THE BUILDER
def setup_builder():
    return Gtk.Builder()

def add_signals(builder):
    builder.add_objects_from_file('weather.xml', ('window1', 'refresh_button', 'box_charts'))

    return builder.connect_signals({'on_window1_destroy': (on_window1_destroy,'window1'),
                                    'on_refresh_button_click': (on_refresh_button_click,),
                                    })
def builder_with_signals():
    builder = setup_builder()
    add_signals(builder)
    return builder


# READ DATA FROM DATABASE
def read_weather_from_db(db, builder, city_name):
    chart_future_values = read_db(db, "chart_future", city_name)
    chart_past_values = read_db(db, "chart_past", city_name)

    fig_future = embed_chart("day and time", "temp", chart_future_values["xticks"], chart_future_values["datetimes_x_axis"], chart_future_values["temps"])
    fig_past = embed_chart("day and time", "temp", chart_past_values["xticks"], chart_past_values["datetimes_x_axis"], chart_past_values["temps"])

    add_canvas(builder, "chart_future", fig_future)
    add_canvas(builder, "chart_past", fig_past)

    return builder


# EMBED THE CHARTS INTO CONTAINERS
def embed_chart(xlabel, ylabel, xticks, xticklabels, yticks):

    fig = Figure(figsize=(5, 5), dpi=100)
    chart = fig.add_subplot(111)
    chart.set_xlabel(xlabel)
    chart.set_ylabel(ylabel)
    chart.set_xticks(xticks)
    chart.set_xticklabels(xticklabels, rotation=90)
    chart.plot(xticks, yticks)
    return fig

def add_canvas(builder, chart_container, fig):
    canvas = FigureCanvas(fig)
    subbox_chart = builder.get_object(chart_container)
    subbox_chart.add(canvas)


# THIS RUNS THE SCRIPT
def display_data(city_name="isfahan"):
    get_updated_data(city_name)
    builder = builder_with_signals()
    read_weather_from_db("test.db", builder, city_name)
    show_gtk(builder)

def on_window1_destroy(self, widget):
    Gtk.main_quit()

# HERE IS THE IMPORTANT BIT
def on_refresh_button_click(self, widget):
    # I DON'T KNOW WHAT TO PUT HERE

def show_gtk(builder):
    window_main = builder.get_object('window1')
    window_main.show_all()
    Gtk.main()

我认为您不需要 Glade xml 文件,但我不确定,因为我对此很陌生:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <signal name="destroy" handler="on_window1_destroy" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox" id="box_main">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <placeholder/>
        </child>
        <child>
          <object class="GtkBox" id="box_charts">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <child>
              <object class="GtkScrolledWindow" id="chart_past">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <property name="min_content_width">500</property>
                <property name="min_content_height">500</property>
                <child>
                  <placeholder/>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">0</property>
              </packing>
            </child>
            <child>
              <object class="GtkScrolledWindow" id="chart_future">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="shadow_type">in</property>
                <property name="min_content_width">500</property>
                <property name="min_content_height">500</property>
                <child>
                  <placeholder/>
                </child>
              </object>
              <packing>
                <property name="expand">False</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="refresh_button">
            <property name="label" translatable="yes">refresh</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="button-press-event" handler="on_refresh_button_click" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

我不确定我能否为您提供一个适用于您现有代码的示例,但我是这样做的:

Figure = None
def invoice_chart_clicked (self, button):
        global Figure
        if Figure == None:
            from matplotlib.figure import Figure
            from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
            from matplotlib.pyplot import pie
            self.figure = Figure(figsize=(4, 4), dpi=100)
            canvas = FigureCanvas(self.figure)  # a Gtk.DrawingArea
            canvas.set_size_request(800, 500)
            overlay = self.builder.get_object('overlay1')
            overlay.add (canvas)
        a = self.figure.add_subplot(111)
        labels = list()
        fractions = list()
        unpaid = 0
        self.cursor.execute("SELECT SUM(amount_due), c.name FROM invoices "
                            "JOIN contacts AS c ON c.id = invoices.customer_id "
                            "WHERE (canceled, paid, posted) = "
                            "(False, False, True) GROUP BY customer_id, c.name "
                            "ORDER BY SUM(amount_due)")
        for row in self.cursor.fetchall():
            customer_total = row[0]
            customer_name = row[1]
            fractions.append(customer_total)
            labels.append(customer_name)
            unpaid += 1
        if unpaid == 0:
            labels.append("None")
            fractions.append(1.00)
        a.pie(fractions, labels=labels, autopct='%1.f%%', radius=0.7)
        window = self.builder.get_object('window1')
        window.show_all()

每次我重新加载这个函数,剧情都会重新生成。您可以找到完整的代码 here。我从来没有 运行 任何测试来查看所有内存是否已正确释放等。也许它会提供足够的空间。

像这里一样创建一个单独的图window,GUI 中的按钮会触发刷新? https://github.com/f4iteightiz/UWR_simulator 一个连续的 运行 动画应该会给你所需的 "refresh" 功能。

事实证明,使用 self.parent() 作为起点可能是最佳选择,因为我不想将整个内容重写为 class。但是:

  • destroying/removing 包含图表的子容器意味着现在没有更多的容器可以放入新图表。
  • 我假设我无法删除子容器中的图形。这个假设是错误的。

删除图表需要一些努力。假设 child 是保存容器对象的变量。打印 child.get_children() 返回 None。这是导致我做出错误假设的部分原因。

但我注意到当我尝试 add() 一个更新的图表时,它给了我这个错误:gtk_scrolled_window_add: assertion 'child_widget == NULL' failed。里面有东西

我看不到它,但是我可以删除这个 child_widget 吗?

# This successfully removes any descendent in `child`.
# It leaves the container empty, so to speak.
for grandkid in child.get_children():
    child.remove(grandkid)

# This adds and displays the updated figure.
new = FigureCanvas(fig)
child.add(new)
child.show_all()

工作。