Как обновить диаграмму matplotlib в GTK3 с помощью Python и Glade - PullRequest
0 голосов
/ 12 декабря 2018

Я вставил две графики matplotlib в контейнер, используя GTK3, Python и Glade.Они отображают погоду локали, используя данные стороннего API.Я хочу, чтобы графики обновлялись при вводе новой локали и нажатии кнопки обновления.

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

  • Уничтожить родительский контейнер и затем загрузить обновленный.Следующий код вызывает ошибку сегментации.Код пытается передать объект из нового экземпляра компоновщика в старый.Я считаю, что это моя главная проблема.Я не знаю, как передать экземпляр компоновщика, с которым я работаю, в on_refresh_button_click() без необходимости переписывать все в один класс.

    def on_refresh_button_click(self, widget):
    
        parent = self.get_parent()
        grandparent = parent.get_parent()
        parent.destroy()
    
        city_name = "yerevan"
        db = "test.db"
    
        get_updated_data(city_name)
    
        builder = builder_with_signals()
        read_weather_from_db(db, builder, city_name)
        grandparent.add(parent)
    
        parent.show_all()
    
  • Использование self.get_parent() получить родительский контейнер кнопки как объект для работы.Это почти сработало, я думаю.Я мог бы remove() и destroy() контейнер, содержащий граф.И я думаю, что я также успешно добавил обновленный.Но я не мог заставить это показать.Код:

    def on_refresh_button_click(self, widget):
    
        parent = self.get_parent()
    
        city_name = "yerevan"
        db = "test.db"
    
        get_updated_data(city_name)
        fig_list = read_weather_from_db(db, city_name)
    
        for child in parent:
            try:
                for grandchild in child:
                    if Gtk.Buildable.get_name(grandchild) == "chart_past":
                        parent = child # Confusing, yes.
                        old = grandchild
                        new = FigureCanvas(fig_list[0])
    
                        props = {}
                        for key in parent.list_child_properties():
                            props[key.name] = parent.child_get_property(old, key.name)
    
                        parent.remove(old)
                        parent.add(new)
    
                        for name, value in props.items():
                            parent.child_set_property(new, name, value)
    
                        parent.show_all()
                        child.show_all()
                        grandchild.show_all()
    
                        # Try to find the newly added object
                        for item in parent:
                            print("trying to find another:", Gtk.Buildable.get_name(item))
        except:
            print(Gtk.Buildable.get_name(child))
    
  • Удаление старого контейнера / виджета и добавление нового, используя этот код из здесь :

    def replace_widget(old, new):
        parent = old.get_parent()
    
        props = {}
        for key in Gtk.ContainerClass.list_child_properties(type(parent)):
            props[key.name] = parent.child_get_property(old, key.name)
    
        parent.remove(old)
        parent.add(new)
    
        for name, value in props.iteritems():
            parent.child_set_property(new, name, value)
    
  • Уничтожение главного окна перед запуском скрипта с нуля с другим языковым стандартом:

    def on_refresh_button_click(self, widget):
        builder = setup_builder()
        add_signals(builder)
        window = builder.get_object("window1")
        window.destroy()
        display_data("yerevan")
    
  • Закрытие программы и ее перезапуск, что не вызываетсмысл даже для меня, но:

    def on_refresh_button_click(self, widget):
        Gtk.main_quit()
        display_data("yerevan")
    
  • Использование canvas.draw() из здесь , здесь и здесь .

  • Замена add_with_viewport() на add(), потому что this говорит о проблемах.

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

Большинство примеров, кажется, создают приложения с GTK3 и Python, но без Glade.Они также используют классы.Я не хочу использовать классы (на данный момент).Я хотел бы увидеть, если кто-нибудь знает решение, прежде чем я переписать все это в один класс.Я, наверное, просто неправильно понимаю или что-то упустил.

Я супер новичок в 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()

Не думаю, что вам нужен XML-файл Glade, но я не уверен, потому что я новичок в этом:

<?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>

Ответы [ 3 ]

0 голосов
/ 13 декабря 2018

Создать отдельное окно графика, как здесь, и кнопка в графическом интерфейсе вызовет обновление?https://github.com/f4iteightiz/UWR_simulator Функция непрерывного выполнения funcanimation должна предоставить вам требуемую функцию «обновления».

0 голосов
/ 13 декабря 2018

Оказывается, что использование self.parent() в качестве отправной точки было, вероятно, самым оптимальным выбором, учитывая, что я не хотел переписывать все это как класс.Но:

  • уничтожение / удаление дочернего контейнера, содержащего граф, означало, что больше не было контейнера для размещения нового графа.
  • Я предположил, что не могу удалитьграф в дочернем контейнере.Это предположение было неверным.

Удаление графика заняло немного усилий.Давайте предположим, что 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()

Сработало.

0 голосов
/ 12 декабря 2018

Я не уверен, что могу дать вам пример, который работает с вашим существующим кодом, но вот как я это сделал:

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()

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

...