From 515c83d41280dc959d9e3e6b615434286e6e0492 Mon Sep 17 00:00:00 2001 From: Peter McGoron Date: Mon, 16 Sep 2024 10:08:19 -0400 Subject: [PATCH] dynamic series properly updates on redraw --- dynamicseries.py | 125 +++++++++++++++++++++++++++++++---------------- test.py | 81 ------------------------------ 2 files changed, 83 insertions(+), 123 deletions(-) delete mode 100644 test.py diff --git a/dynamicseries.py b/dynamicseries.py index 5cc65ec..4dc6b70 100644 --- a/dynamicseries.py +++ b/dynamicseries.py @@ -1,58 +1,99 @@ -import matplotlib -from matplotlib.lines import Line2D +import matplotlib.pyplot as plt +import numpy as np class DynamicSeries: - """ AutomaticTimeSeries handles drawing a line in an efficient way - onto a matplotlib canvas. """ - def __init__(self, fig, ax): + def __init__(self, fig, ax, x_lim = [0,10], y_lim = [0,10]): self.fig = fig self.ax = ax - # Line associated with the figure used to draw the next line. - self.dummyLine = ax.plot([0],[0], animated=True)[0] + self.fig.show() + # 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._bg = None + self.x_lim = x_lim + self.y_lim = y_lim + self.ax.set_xlim(*self.x_lim) + self.ax.set_ylim(*self.y_lim) - # Redraw time series on reveal. - fig.canvas.mpl_connect("draw_event", self.on_draw) + # Redraw the figure when matplotlib requests a redraw. + self.fig.canvas.mpl_connect("draw_event", self.on_draw) + # Initialize data fed into the series self.x_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): - 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): + def draw_event(self, *args): 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.show() -ats = AutomaticTimeSeries(fig, ax) +d = DynamicSeries(fig, ax) + diff --git a/test.py b/test.py deleted file mode 100644 index c107894..0000000 --- a/test.py +++ /dev/null @@ -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)