dynamic series properly updates on redraw

This commit is contained in:
Peter McGoron 2024-09-16 10:08:19 -04:00
parent 21c2421c5e
commit 515c83d412
2 changed files with 83 additions and 123 deletions

View File

@ -1,58 +1,99 @@
import matplotlib import matplotlib.pyplot as plt
from matplotlib.lines import Line2D import numpy as np
class DynamicSeries: class DynamicSeries:
""" AutomaticTimeSeries handles drawing a line in an efficient way def __init__(self, fig, ax, x_lim = [0,10], y_lim = [0,10]):
onto a matplotlib canvas. """
def __init__(self, fig, ax):
self.fig = fig self.fig = fig
self.ax = ax self.ax = ax
# Line associated with the figure used to draw the next line. self.fig.show()
self.dummyLine = ax.plot([0],[0], animated=True)[0] # Create the one line that will be drawn onto the plot.
# ``plot`` returns a list of the things plotted, so get
# the only element of the list, the line.
self.ln = ax.plot([0,1], [0,1], 'r-', animated=True)[0]
# Bitmap of previous background self.x_lim = x_lim
self._bg = None self.y_lim = y_lim
self.ax.set_xlim(*self.x_lim)
self.ax.set_ylim(*self.y_lim)
# Redraw time series on reveal. # Redraw the figure when matplotlib requests a redraw.
fig.canvas.mpl_connect("draw_event", self.on_draw) self.fig.canvas.mpl_connect("draw_event", self.on_draw)
# Initialize data fed into the series
self.x_ax = [] self.x_ax = []
self.y_ax = [] self.y_ax = []
self.current_x_lims = 10 def set_color(self, *args, **kwargs):
self.ln.set_color(*args, **kwargs)
def insert(self, x, y): def draw_event(self, *args):
self.x_ax.append([x])
self.y_ax.append([y])
if len(self.x_ax) == 1:
return None
prev_x = self.x_ax[-1]
prev_y = self.y_ax[-1]
self.dummyLine.set_data([(prev_x, prev_y), (x, y)])
if self._bg is not None:
self.fig.canvas.restore_region(self._bg)
self.fig.draw_artist(self.dummyLine)
self.fig.canvas.blit(self.fig.bbox)
self.fig.canvas.flush_events()
self._bg = self.fig.canvas.copy_from_bbox(self.fig.bbox)
def on_invalidated(self):
""" Redraw the entire canvas on invalidation. """
self.ax.clear()
self.ax.set_xlim(0, self.current_x_lims)
self.ax.plot(self.x_ax, self.y_ax, color="blue")
def on_draw(self, event):
self.on_invalidated() self.on_invalidated()
import matplotlib.pyplot as plt def on_invalidate(self):
""" This function redraws all data to the screen whenever the
screen is "invalidated". This could mean a user resize,
or an automatic axis resize. """
self.ax.clear()
self.ax.set_xlim(*self.x_lim)
self.ax.set_ylim(*self.y_lim)
#self.fig.canvas.draw()
self.ln.set_xdata(self.x_ax)
self.ln.set_ydata(self.y_ax)
self.ax.draw_artist(self.ln)
self.fig.canvas.blit(self.fig.bbox)
def on_draw(self, *args, **kwargs):
self.on_invalidate()
def add(self, x, y):
""" Add two points to the graph and redraw it.
Returns True if the graph was redrawn. """
haveToRedraw = False
# If there were no previous points, do nothing. There is not
# enough data to draw a line.
if len(self.x_ax) == 0:
self.x_ax = [x]
self.y_ax = [y]
return False
# If there was a previous point, use it to draw a line to the
# current point. Store the current point as data.
prev_x = self.x_ax[-1]
prev_y = self.y_ax[-1]
self.x_ax.append(x)
self.y_ax.append(y)
# If the point is outside of the limits of the graph, change the
# limits and redraw the graph.
if x < self.x_lim[0]:
self.x_lim[0] = x - 0.01*x
haveToRedraw = True
elif x > self.x_lim[1]:
self.x_lim[1] = x + 0.01*x
haveToRedraw = True
if y < self.y_lim[0]:
self.y_lim[0] = y - 0.01*y
haveToRedraw = True
elif y > self.y_lim[1]:
self.y_lim[1] = y + 0.01*y
haveToRedraw = True
if haveToRedraw:
self.fig.canvas.draw()
return True
print((prev_x, x), (prev_y, y))
self.ln.set_xdata([prev_x, x])
self.ln.set_ydata([prev_y, y])
self.ax.draw_artist(self.ln)
self.fig.canvas.blit(self.fig.bbox)
self.fig.canvas.flush_events()
return False
fig, ax = plt.subplots() fig, ax = plt.subplots()
fig.show() d = DynamicSeries(fig, ax)
ats = AutomaticTimeSeries(fig, ax)

81
test.py
View File

@ -1,81 +0,0 @@
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2 * np.pi, 100)
fig, ax = plt.subplots()
ax.set_xlim(0, 100)
ax.set_ylim(0, 10000)
# animated=True tells matplotlib to only draw the artist when we
# explicitly request it
(ln,) = ax.plot([0,1], [0,1], 'r-', animated=True)
# make sure the window is raised, but the script keeps going
plt.show(block=False)
# stop to admire our empty window axes and ensure it is rendered at
# least once.
#
# We need to fully draw the figure at its final size on the screen
# before we continue on so that :
# a) we have the correctly sized and drawn background to grab
# b) we have a cached renderer so that ``ax.draw_artist`` works
# so we spin the event loop to let the backend process any pending operations
plt.pause(0.1)
ax.draw_artist(ln)
# get copy of entire figure (everything inside fig.bbox) sans animated artist
bg = fig.canvas.copy_from_bbox(fig.bbox)
# draw the animated artist, this uses a cached renderer
ax.draw_artist(ln)
# show the result to the screen, this pushes the updated RGBA buffer from the
# renderer to the GUI framework so you can see it
fig.canvas.blit(fig.bbox)
x_ax = []
y_ax = []
for j in range(100):
# reset the background back in the canvas state, screen unchanged
#fig.canvas.restore_region(bg)
# update the artist, neither the canvas state nor the screen have changed
x_ax.append(j)
y_ax.append(j*j)
ln.set_data([j,j+1], [j*j,(j+1)*(j+1)])
# re-render the artist, updating the canvas state, but not the screen
ax.draw_artist(ln)
# copy the image to the GUI state, but screen might not be changed yet
fig.canvas.blit(fig.bbox)
# flush any pending GUI events, re-painting the screen if needed
fig.canvas.flush_events()
# you can put a pause in if you want to slow things down
plt.pause(.1)
ax.clear()
ax.set_xlim(0, 200)
ax.set_ylim(0, 200*200)
(ln,) = ax.plot(x_ax, y_ax, 'r-', animated = True)
plt.pause(0.1)
ax.draw_artist(ln)
bg = fig.canvas.copy_from_bbox(fig.bbox)
fig.canvas.blit(fig.bbox)
for j in range(100, 200):
# reset the background back in the canvas state, screen unchanged
#fig.canvas.restore_region(bg)
# update the artist, neither the canvas state nor the screen have changed
x_ax.append(j)
y_ax.append(j*j)
ln.set_data([j,j+1], [j*j,(j+1)*(j+1)])
# re-render the artist, updating the canvas state, but not the screen
ax.draw_artist(ln)
# copy the image to the GUI state, but screen might not be changed yet
fig.canvas.blit(fig.bbox)
# flush any pending GUI events, re-painting the screen if needed
fig.canvas.flush_events()
# you can put a pause in if you want to slow things down
plt.pause(.1)