Динамическое обновление графика в matplotlib

114

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

  1. Очистите график и заново начертите график со всеми точками.
  2. Анимируйте сюжет, изменяя его через определенный интервал.

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

Есть ли способ обновить график, просто добавив к нему больше точек, только когда данные получены?

Шадман Анвер
источник
2
Возможный дубликат построения графика в реальном времени в цикле while с matplotlib
Тревор Бойд Смит

Ответы:

138

Есть ли способ обновить сюжет, просто добавив к нему дополнительные точки ...

Есть несколько способов анимировать данные в matplotlib, в зависимости от вашей версии. Вы видели примеры из кулинарной книги matplotlib ? Также ознакомьтесь с более современными примерами анимации в документации matplotlib. Наконец, API анимации определяет функцию FuncAnimation, которая анимирует функцию во времени. Эта функция может быть просто функцией, которую вы используете для сбора данных.

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

Учитывая, что вы говорите, что время прибытия ваших данных не определено, лучше всего, вероятно, просто сделать что-то вроде:

import matplotlib.pyplot as plt
import numpy

hl, = plt.plot([], [])

def update_line(hl, new_data):
    hl.set_xdata(numpy.append(hl.get_xdata(), new_data))
    hl.set_ydata(numpy.append(hl.get_ydata(), new_data))
    plt.draw()

Затем, когда вы получите данные из последовательного порта, просто позвоните update_line.

Крис
источник
В заключение! Я искал ответ на этот +1 :) Как сделать автоматическое масштабирование сюжета. ax.set_autoscale_on (True), похоже, не работает.
Эдвард Ньюэлл
13
Нашел ответ: вызовите ax.relim (), затем ax.autoscale_view () после обновления данных, но перед вызовом plt.draw ()
Эдвард Ньюэлл
Ссылка на кулинарную книгу Matplotlib ( scipy.org/Cookbook/Matplotlib/Animations ) кажется неработающей (я получаю сообщение об ошибке «Запрещено»)
Дэвид Дориа
21
Поскольку вызова show () нет, график никогда не появляется на экране. Если я вызываю show (), он блокируется и не выполняет обновления. Я что-то упускаю? gist.github.com/daviddoria/027b5c158b6f200527a4
Дэвид Дориа
2
ссылка на аналогичный, но другой автономный ответ с кодом, который вы можете запустить (этот ответ имеет правильную общую идею, но пример кода не может быть запущен)
Trevor Boyd Smith
44

Чтобы сделать это без FuncAnimation (например, вы хотите выполнять другие части кода во время создания графика или вы хотите обновлять несколько графиков одновременно), drawтолько вызов не создает график (по крайней мере, с qt бэкэнд).

Для меня работает следующее:

import matplotlib.pyplot as plt
plt.ion()
class DynamicUpdate():
    #Suppose we know the x range
    min_x = 0
    max_x = 10

    def on_launch(self):
        #Set up plot
        self.figure, self.ax = plt.subplots()
        self.lines, = self.ax.plot([],[], 'o')
        #Autoscale on unknown axis and known lims on the other
        self.ax.set_autoscaley_on(True)
        self.ax.set_xlim(self.min_x, self.max_x)
        #Other stuff
        self.ax.grid()
        ...

    def on_running(self, xdata, ydata):
        #Update data (with the new _and_ the old points)
        self.lines.set_xdata(xdata)
        self.lines.set_ydata(ydata)
        #Need both of these in order to rescale
        self.ax.relim()
        self.ax.autoscale_view()
        #We need to draw *and* flush
        self.figure.canvas.draw()
        self.figure.canvas.flush_events()

    #Example
    def __call__(self):
        import numpy as np
        import time
        self.on_launch()
        xdata = []
        ydata = []
        for x in np.arange(0,10,0.5):
            xdata.append(x)
            ydata.append(np.exp(-x**2)+10*np.exp(-(x-7)**2))
            self.on_running(xdata, ydata)
            time.sleep(1)
        return xdata, ydata

d = DynamicUpdate()
d()
Zah
источник
Да! Наконец-то решение, которое работает со Spyder! Мне не хватало gcf (). Canvas.flush_events () после команды draw ().
np8
На основе этого замечательного примера я написал небольшой модуль Python, позволяющий выполнять повторяющиеся графики: github.com/lorenzschmid/dynplot
lorenzli
1
Прекрасный пример!
vvy
Ясный, лаконичный, универсальный, гибкий: это должен быть принятый ответ.
pfabri
Чтобы использовать это в Jupyter Notebook , вы должны добавить %matplotlib notebookволшебную команду после оператора импорта matplotlib.
pfabri,
3

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

import matplotlib.pyplot as plt
# generate axes object
ax = plt.axes()

# set limits
plt.xlim(0,10) 
plt.ylim(0,10)

for i in range(10):        
     # add something to axes    
     ax.scatter([i], [i]) 
     ax.plot([i], [i+1], 'rx')

     # draw the plot
     plt.draw() 
     plt.pause(0.01) #is necessary for the plot to update for some reason

     # start removing points if you don't want all shown
     if i>2:
         ax.lines[0].remove()
         ax.collections[0].remove()
НДД
источник
2

Я знаю, что опаздываю с ответом на этот вопрос, но для решения вашей проблемы вы можете изучить пакет «джойстик». Я разработал его для построения потока данных из последовательного порта, но он работает для любого потока. Он также позволяет вести интерактивный журнал текста или строить изображения (в дополнение к построению графиков). Не нужно создавать собственные циклы в отдельном потоке, пакет позаботится об этом, просто укажите желаемую частоту обновления. Плюс терминал остается доступным для контроля команд во время черчения. См. Http://www.github.com/ceyzeriat/joystick/ или https://pypi.python.org/pypi/joystick (для установки используйте джойстик pip install)

Просто замените np.random.random () вашей реальной точкой данных, считанной из последовательного порта в приведенном ниже коде:

import joystick as jk
import numpy as np
import time

class test(jk.Joystick):
    # initialize the infinite loop decorator
    _infinite_loop = jk.deco_infinite_loop()

    def _init(self, *args, **kwargs):
        """
        Function called at initialization, see the doc
        """
        self._t0 = time.time()  # initialize time
        self.xdata = np.array([self._t0])  # time x-axis
        self.ydata = np.array([0.0])  # fake data y-axis
        # create a graph frame
        self.mygraph = self.add_frame(jk.Graph(name="test", size=(500, 500), pos=(50, 50), fmt="go-", xnpts=10000, xnptsmax=10000, xylim=(None, None, 0, 1)))

    @_infinite_loop(wait_time=0.2)
    def _generate_data(self):  # function looped every 0.2 second to read or produce data
        """
        Loop starting with the simulation start, getting data and
    pushing it to the graph every 0.2 seconds
        """
        # concatenate data on the time x-axis
        self.xdata = jk.core.add_datapoint(self.xdata, time.time(), xnptsmax=self.mygraph.xnptsmax)
        # concatenate data on the fake data y-axis
        self.ydata = jk.core.add_datapoint(self.ydata, np.random.random(), xnptsmax=self.mygraph.xnptsmax)
        self.mygraph.set_xydata(t, self.ydata)

t = test()
t.start()
t.stop()
Гийом С
источник