So i recently strated using Python from scratch. I am trying to build a pictogram-made bar graph, where within each bar, each row appears one after another; and then each bar appears one after another. I wrote some code which i m 100%sure is quite amateur, but the animation does not work correctly I think - instead of having constant time intervals between rows and bars, the interval gets longer as the animation progresses Oh and in addition, the year labels do not appear.. Here is my code: import matplotlib matplotlib.use('TkAgg') import matplotlib.pyplot as plt import matplotlib.animation as animation from matplotlib.offsetbox import OffsetImage, AnnotationBbox import numpy as np # ========== CONFIGURATION ========== ICON_PATH = 'dot.png' # Replace with your PNG ZOOM = 0.4 # Increased zoom for visibility BAR_SPACING = 0.15 # Wider spacing between bars ICON_SPACING_X = 0.02 # More horizontal space between icons ICON_SPACING_Y = 0.02 # More vertical space between rows LABEL_Y_OFFSET = 0 # Labels placed lower BARS = [ {"total": 5 * 10, "label": "2000"}, {"total": 5 * 25, "label": "2005"}, {"total": 5 * 36 + 4, "label": "2010"}, {"total": 5 * 48 + 2, "label": "2015"}, {"total": 5 * 58 + 2, "label": "2020"}, {"total": 5 * 60, "label": "2024"}, ] COLUMNS_PER_BAR = 5 ROW_INTERVAL_MS = 50 # Affects all bars now BAR_DELAY_MS = 200 # Delay between bars (now respects ROW_INTERVAL_MS) # =================================== # Load icon (ensure it's a high-resolution PNG/SVG) icon = plt.imread(ICON_PATH) # Precompute bar data max_rows = 0 for idx, bar in enumerate(BARS): full_rows, remainder = divmod(bar["total"], COLUMNS_PER_BAR) bar["full_rows"] = full_rows bar["remainder"] = remainder bar["total_rows"] = full_rows + (1 if remainder else 0) bar["x_pos"] = 0.1+idx * BAR_SPACING max_rows = max(max_rows, bar["total_rows"]) # Set up figure with expanded limits fig, ax = plt.subplots(figsize=((len(BARS)) * BAR_SPACING+3, 12)) # Larger figure size ax.set_xlim(-0.5, (len(BARS)) * BAR_SPACING) ax.set_ylim(LABEL_Y_OFFSET - 2, max_rows * ICON_SPACING_Y + 2) ax.axis('off') def add_icon(x, y, ax): img = OffsetImage(icon, zoom=ZOOM) # Increased zoom ab = AnnotationBbox(img, (x, y), frameon=False) ax.add_artist(ab) return ab def animate(frame_data): ax.clear() ax.axis('off') current_bar_idx, current_row = frame_data for bar_idx, bar in enumerate(BARS[:current_bar_idx + 1]): # Draw up to current_row for the current bar if bar_idx < current_bar_idx: rows_to_draw = bar["total_rows"] else: rows_to_draw = current_row + 1 for row in range(rows_to_draw): cols = COLUMNS_PER_BAR if row < bar["full_rows"] else bar["remainder"] for col in range(cols): x = bar["x_pos"] + col * ICON_SPACING_X y = 0.1+row * ICON_SPACING_Y add_icon(x, y, ax) # Add label ax.text( bar["x_pos"] + (COLUMNS_PER_BAR * ICON_SPACING_X) / 2, LABEL_Y_OFFSET, bar["label"], ha='center', va='top', fontsize=14, weight='bold' ) return ax.artists # Generate frames with correct timing for all bars frames = [] for bar_idx, bar in enumerate(BARS): # Add frames for each row in this bar for row in range(bar["total_rows"]): frames.append((bar_idx, row)) # Add delay between bars using empty frames if bar_idx != len(BARS) - 1: delay_frames = int(BAR_DELAY_MS / ROW_INTERVAL_MS) for _ in range(delay_frames): frames.append((bar_idx, bar["total_rows"] - 1)) # Hold last frame # Create animation ani = animation.FuncAnimation( fig, animate, frames=frames, interval=ROW_INTERVAL_MS, blit=True ) plt.show() # ani.save('animation.gif', writer='pillow', fps=1000/ROW_INTERVAL_MS) Continue reading...