Динамічно оновлення сюжету в matplotlib


114

Я роблю заявку в Python, яка збирає дані з послідовного порту та розміщує графік зібраних даних на час прибуття. Час надходження даних невідомий. Я хочу, щоб сюжет оновлювався після отримання даних. Я шукав, як це зробити, і знайшов два методи:

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

Я не віддаю перевагу першому, оскільки програма працює і збирає дані протягом тривалого часу (наприклад, день), і перемальовування сюжету буде досить повільним. Другий також не є кращим, оскільки час надходження даних є невизначеним, і я хочу, щоб сюжет оновлювався лише після отримання даних.

Чи існує спосіб, яким я можу оновити сюжет, просто додавши до нього більше балів, лише коли дані отримані?


Відповіді:


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 (), він блокує та не виконує оновлення. Я щось пропускаю? gist.github.com/daviddoria/027b5c158b6f200527a4
David Doria

2
посилання на аналогічну, але іншу самостійну відповідь з кодом, який можна запустити (ця відповідь має правильне загальне уявлення, але приклад код не може бути запущений)
Тревор Бойд Сміт

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

Так! Нарешті рішення, яке працює зі Spyder! Те, що мені не вистачало, - це gcf (). Canvas.flush_events () після команди draw () -.
np8

На основі цього чудового прикладу я написав невеликий модуль Python, що дозволяє повторювати графіки
lorenzli

1
Прекрасний приклад!
vvy

Чітка, лаконічна, універсальна, гнучка: це має бути прийнята відповідь.
pfabri

Щоб використовувати це в Блокноті Юпітера , слід додати %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 для встановлення)

Просто замініть 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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.