更新 xlim 时 Matplotlib 注释在动画期间消失

Matplotlib Annotations disappear during animation when updating xlim

我在使用 FuncAnimation 时遇到问题,我的注释在更新 xlim 后被删除了。下面是带有预览的代码

您可以在此处的 google 合作实验室中尝试代码 https://colab.research.google.com/drive/1NrM-ZnSQKhADccpjCbNeOC5PU8uXw-Sb?authuser=2#scrollTo=bcYtgNaTYJ3g

import os
import matplotlib.animation as ani
import matplotlib.pyplot as plt
from collections import deque
from typing import List
from IPython.display import Image
from matplotlib.offsetbox import OffsetImage, AnnotationBbox

def create_animation_for_data(qty_lists: List[List[int]], gif_path, add_horizontal_guides=True, dynamic_y_axis=True,
                              dynamic_x_axis=True):
    plt.style.use(['dark_background'])
    fruits_to_color = ["red", "orange"]
    qtys1, qty2 = qty_lists
    times = [i for i in range(len(qtys1))]
    artists = []

    zoom = 0.5
    first_image_path = 'assets/apple.png'
    first_image = plt.imread(first_image_path)
    first_offset_image = OffsetImage(first_image, zoom=zoom)

    second_image_path = 'assets/orange.png'
    second_image = plt.imread(second_image_path)
    second_offset_image = OffsetImage(second_image, zoom=zoom)


    # initializing a figure in
    # which the graph will be plotted
    my_dpi = 200
    figsize_pixels = (650, 1000)
    figsize = (int(figsize_pixels[0] / my_dpi), figsize_pixels[1] / my_dpi)

    fig = plt.figure(figsize=figsize, dpi=my_dpi)
    fig.set_tight_layout(True)

    records_per_second = 1
    seconds_show_on_screen = 30
    max_width_on_screen = records_per_second * seconds_show_on_screen
    ticks_every = 20
    graph_max_y = 100
    max_y_list = list(range(60, graph_max_y + 1, ticks_every * 2))
    max_y_index = 0
    max_y = max_y_list[max_y_index]
    highest_max_y = max_y_list[- 1]

    xlim_min = times[0]
    if dynamic_x_axis:
        xlim_max = times[max_width_on_screen - 1]
    else:
        xlim_max = times[-1]
    axes_xlim = (xlim_min, xlim_max)

    if dynamic_y_axis:
        ylim_max = max_y
    else:
        ylim_max = highest_max_y

    axes_ylim = (0, ylim_max)
    ax1 = plt.axes(xlim=axes_xlim, ylim=axes_ylim)

    # Set a title
    plt.title('Qty over time', fontsize=20)
    # Set axis labels
    plt.xlabel('Time', fontsize=18)
    plt.ylabel('Qty', fontsize=18)

    plotlays, plotcols = [2], fruits_to_color
    labels = ['Apple', 'Orange']
    lines = []
    for index in range(len(qty_lists)):
        lobj = ax1.plot([], [], lw=5, color=plotcols[index],
                        label=labels[index])[0]
        lines.append(lobj)

    # Make sure your axis ticks are large enough to be easily read.
    # You don't want your viewers squinting to read your plot.
    plt.yticks(range(0, highest_max_y + 1, ticks_every), [str(x) for x in range(0, highest_max_y + 1, ticks_every)],
               fontsize=14)
    plt.xticks(fontsize=14)

    # Provide tick lines across the plot to help your viewers trace along
    # the axis ticks. Make sure that the lines are light and small so they
    # don't obscure the primary data lines.
    if add_horizontal_guides:
        max_x = len(times)
        for y in range(0, highest_max_y + 1, ticks_every):
            plt.plot(times, [y] * max_x, "--",
                     lw=1, color="white", alpha=0.7)

    # Do this after the plotting done above
    ax1.set_ylim(*axes_ylim)

    # Remove the tick marks; they are unnecessary with the tick lines we just plotted.
    plt.tick_params(axis="both", which="both", bottom="off", top="off",
                    labelbottom="on", left="off", right="off", labelleft="on")

    # empty list to store x and y axis values
    xdata = deque()
    ydata1 = deque()
    ydata2 = deque()

    first_image_annotation = AnnotationBbox(
        first_offset_image, (times[0], 0), xycoords='data', frameon=False)
    artists.append(ax1.add_artist(first_image_annotation))

    second_image_annotation = AnnotationBbox(
        second_offset_image, (times[0], 0), xycoords='data', frameon=False)
    artists.append(ax1.add_artist(second_image_annotation))

    ann_list = [
        first_image_annotation,
        second_image_annotation,
    ]

    # animation function
    def animate(i):
        nonlocal max_y_index
        nonlocal ann_list

        # appending new points to x, y axes points list
        x1_and_x2 = times[i]
        xdata.append(x1_and_x2)

        y1 = qty_lists[0][i]
        ydata1.append(y1)

        y2 = qty_lists[1][i]
        ydata2.append(y2)

        xlist = [xdata, xdata]
        ylist = [ydata1, ydata2]

        # If we have passed our max_width_on_screen
        if len(xdata) > max_width_on_screen:
            # Delete the oldest record
            xdata.popleft()
            max_x = max(xdata)
            min_x = max_x - seconds_show_on_screen

            # Update our x axis
            if dynamic_x_axis:
                ax1.set_xlim(min_x, max_x)
            graph_max_y_data = max(ydata1[-1], ydata2[-1])
            max_y_data = max_y_list[max_y_index]
            while graph_max_y_data > max_y_data:
                max_y_index += 1
                max_y_data = max_y_list[max_y_index]

            # Update our y axis
            if dynamic_y_axis:
                ax1.set_ylim(0, max_y_data)
            ydata1.popleft()
            ydata2.popleft()

        first_image_annotation_xybox = (x1_and_x2, y1)
        first_image_annotation.xybox = first_image_annotation_xybox

        second_image_annotation_xybox = (x1_and_x2, y2)
        second_image_annotation.xybox = second_image_annotation_xybox

        for lnum, line in enumerate(lines):
            # set data for each line separately.
            line.set_data(xlist[lnum], ylist[lnum])

        return lines, ann_list

    # call the animator
    anim = ani.FuncAnimation(fig, animate, frames=len(times), interval=30, blit=False)

    # save the animation as gif file
    anim.save(gif_path, writer='imagemagick', fps=2)

    return os.path.abspath(gif_path)


static_axes_gif = 'FuncAnimation-annotated-static-axes.gif'
print(static_axes_gif)
animation_path_static_axes = create_animation_for_data(data_to_plot,
                                                       static_axes_gif,
                                                       dynamic_x_axis=False,
                                                       dynamic_y_axis=False)
Image(url=animation_path_static_axes)

static_x_gif = 'FuncAnimation-annotated-static-x.gif'
print(static_x_gif)
animation_path_static_x_axis = create_animation_for_data(data_to_plot,
                                                         static_x_gif,
                                                         dynamic_x_axis=False,
                                                         dynamic_y_axis=True)
Image(url=animation_path_static_x_axis)

static_y_gif = 'FuncAnimation-annotated-static-y.gif'
print(static_y_gif)
animation_path_static_y_axis = create_animation_for_data(data_to_plot,
                                                         static_y_gif,
                                                         dynamic_x_axis=True,
                                                         dynamic_y_axis=False)
Image(url=animation_path_static_y_axis)

dynamic_axes_gif = 'FuncAnimation-annotated-dynamic-axes.gif'
print(dynamic_axes_gif)
animation_path_dynamic_axes = create_animation_for_data(data_to_plot,
                                                        dynamic_axes_gif,
                                                        dynamic_x_axis=True,
                                                        dynamic_y_axis=True)
Image(url=animation_path_dynamic_axes)

我正在制作 4 个具有可变动态轴的图表(动态 = 在胺化期间更新轴):

  1. FuncAnimation-annotated-static-axes.gif
    • xlim 和 ylim 是固定的

  1. FuncAnimation-annotated-static-x.gif
    • xlim 已修复
    • ylim 是动态的

  1. FuncAnimation-annotated-static-y.gif
    • xlim 是动态的
    • ylim 已修复

  1. FuncAnimation-注释动态-axes.gif
    • xlim 和 ylim 是动态的

我的注释在xlim更新的两种情况下消失了:

  1. FuncAnimation-annotated-static-y.gif
  2. FuncAnimation-注释-动态-axes.gif

请注意,当 xlim 为静态时,不会发生这种情况:

  1. FuncAnimation-annotated-static-axes.gif
  2. FuncAnimation-annotated-static-x.gif

有谁知道为什么会这样或者如何在不删除注释的情况下更新 xlim?

如果有什么不清楚/措辞不当的地方请告诉我,因为我真的需要解决这个问题。

所以问题出在我移动注释的方式上。 这是修复:

    # Don't do this - updating xlim will causing the annotation do disappear 
    # first_image_annotation_xybox = (x1_and_x2, y1)
    # first_image_annotation.xybox = first_image_annotation_xybox
    #
    # second_image_annotation_xybox = (x1_and_x2, y2)
    # second_image_annotation.xybox = second_image_annotation_xybox

    for lnum, line in enumerate(lines):
        # set data for each line separately.
        line.set_data(xlist[lnum], ylist[lnum])


    # Do this - Update our annotations
    for ann in ann_list:
        ann.remove()
    ann_list = []
    first_image_annotation = AnnotationBbox(
        first_offset_image, (x1_and_x2, y1), xycoords='data', frameon=False)
    ann_list.append(ax1.add_artist(first_image_annotation))

    second_image_annotation = AnnotationBbox(
        second_offset_image, (x1_and_x2, y2), xycoords='data', frameon=False)
    ann_list.append(ax1.add_artist(second_image_annotation))

    return lines, ann_list

其余代码相同。想知道为什么这发生在更新 xlim 而不是更新 ylim ¯\(ツ)