Интерактивный сюжет с осью категории с Matplotlib - PullRequest
0 голосов
/ 07 октября 2018

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

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import time
import random

# Mock categories
categories = ["cat1", "cat2", "cat3", "cat4"]

counter = 0

# Plot
x_data = []
y_data = []

plt.ion()
fig = plt.figure()

subplt = fig.add_subplot(312)
subplt_line, = subplt.plot(x_data, y_data, 'b.')

while True:

    time.sleep(0.1)  # simulate some delay that occurs in the actual application

    x_data.append(counter)
    subplt_line.set_xdata(x_data)

    counter += 1

    # y_data.append(random.randrange(1, 15))  # This works fine (except the scaling)
    y_data.append(random.choice(categories))  # This will end in an exception
    subplt_line.set_ydata(y_data)

    # Update the plot
    fig.canvas.draw()
    fig.canvas.flush_events()

Это закончится следующим исключением:

Traceback (most recent call last):
  File "test.py", line 36, in <module>
    fig.canvas.draw()
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/backends/backend_tkagg.py", line 12, in draw
    super(FigureCanvasTkAgg, self).draw()
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/backends/backend_agg.py", line 437, in draw
    self.figure.draw(self.renderer)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/figure.py", line 1493, in draw
    renderer, self, artists, self.suppressComposite)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/image.py", line 141, in _draw_list_compositing_images
    a.draw(renderer)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/axes/_base.py", line 2635, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/image.py", line 141, in _draw_list_compositing_images
    a.draw(renderer)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/artist.py", line 55, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/lines.py", line 738, in draw
    self.recache()
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/lines.py", line 656, in recache
    yconv = self.convert_yunits(self._yorig)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/artist.py", line 200, in convert_yunits
    return ax.yaxis.convert_units(y)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/axis.py", line 1526, in convert_units
    ret = self.converter.convert(x, self.units, self)
  File "/anaconda3/envs/xxx/lib/python2.7/site-packages/matplotlib/category.py", line 65, in convert
    unit.update(values)
AttributeError: 'NoneType' object has no attribute 'update'

Мне кажется, что комбинация категориальных данных yи интерактивность вызывает это.При использовании числовых значений он работает нормально, а при использовании неинтерактивной функции он работает хорошо даже с категориальной осью.

Другой проблемой является автоматическое масштабирование оси y.Новые значения, добавленные через set_y_data() точка, похоже, вызывают это.

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

1 Ответ

0 голосов
/ 07 октября 2018

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

Я считаю, что ваша проблема с инициализацией объекта Line2D.Когда вы передаете любой пустой массив y, matplotlib, похоже, предполагает, что вы будете использовать числовые, а не категориальные значения.Инициализация строки со строкой в ​​виде значения y, кажется, делает свое дело.Вам придется настроить код так, чтобы первая созданная точка имела смысл для ваших данных, но это должно быть лишь незначительным раздражением.

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

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
import random

# Mock categories
categories = ["cat1", "cat2", "cat3", "cat4"]

counter = 0

# Plot
x_data = [0]
y_data = ['cat1']

fig = plt.figure()

subplt = fig.add_subplot(312)
subplt_line, = subplt.plot(x_data, y_data, 'b.-')
debug_text = fig.text(0, 1, "TEXT", va='top')  # for debugging

def init():
    subplt_line.set_data([0],['cat1'])

def animate(num, ax):
    new_x, new_y = num, random.choice(categories)
    debug_text.set_text('{:d} {:s}'.format(num, new_y))
    x, y = subplt_line.get_data()
    x = np.append(x, new_x)
    y = np.append(y, new_y)
    subplt_line.set_data(x,y)
    ax.set_xlim(min(x),max(x))
    ax.set_ylim(0,len(np.unique(y))-1)
    return subplt_line,debug_text

ani = animation.FuncAnimation(fig, animate, fargs=[subplt], init_func=init, frames=20, blit=False, repeat=False)
plt.show()

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...