Chapter 6: Animation Generation (generate_animation)

Welcome to the final chapter in our vidigi tutorial! In Chapter 5: Snapshot Preparation (reshape_for_animations & generate_animation_df), we saw how vidigi transforms our raw Event Log and Layout Configuration (event_position_df) into a detailed, frame-by-frame dataset. This dataset tells us exactly where each entity (like a patient) should be on the screen, with its specific icon, at every single snapshot in time.

Think of the output from Chapter 5 as a perfectly prepared film reel for a movie. Each frame on the reel shows the precise position of all actors. Now, we need the movie projector to actually display this film reel as a moving picture. That’s the job of the generate_animation function!

The Movie Projector: Turning Data into Visuals

generate_animation is the core rendering engine of vidigi. It takes the “animation-ready” DataFrame prepared in the previous step (the one with minute, patient, icon, x_final, y_final, etc.) and uses a powerful plotting library called Plotly Express to create the actual interactive animation.

Its main job is to:

  1. Draw Each Frame: For each time snapshot (minute), it plots each entity’s icon at its calculated x_final and y_final position.
  2. Create Motion: It tells Plotly how to smoothly transition the icons from their positions in one frame to their positions in the next frame. This is what makes the entities appear to move through the different stages (queues, resources).
  3. Add Controls & Polish: It adds features like a timeline slider, play/pause buttons, tooltips (text that appears when you hover over an icon), stage labels, and optional background images.

Essentially, generate_animation is the final artist that takes the detailed blueprint and brings it to life as an interactive animation.

How to Use generate_animation (Usually Indirectly)

Most of the time, you won’t call generate_animation directly. Remember the “Easy Button” from Chapter 1: Animation Facade (animate_activity_log)? That main animate_activity_log function calls generate_animation internally as its final step.

However, if you wanted very fine-grained control or were building the animation step-by-step yourself, you could call it directly. You would first need to run the preparation steps from Chapter 5 to get the required input DataFrame.

Let’s imagine we have the final DataFrame from Chapter 5, called animation_ready_df:

# --- Assume we have this from Chapter 5 ---
# animation_ready_df looks like this (simplified):
#    minute  patient icon  x_final  y_final             label  ...
# 0       0        1  '🧔🏼'     50.0    200.0          Entrance  ...
# 1       0        1  '🧔🏼'    200.0    250.0      Waiting Area  ...
# 2       5        1  '🧔🏼'    190.0    150.0    Treatment Bays  ...
# 3       5        2  '👨🏿‍🦯'    200.0    250.0      Waiting Area  ...
# ...

# --- Assume we also have these from previous chapters ---
# my_layout = pd.DataFrame(...) # From Chapter 3
# class SimpleScenario: n_cubicles = 2
# scenario_details = SimpleScenario() # From Chapter 1/3

# --- Import the function ---
from vidigi.animation import generate_animation
import pandas as pd # We'll use pandas DataFrames

# --- Call generate_animation directly ---
# (Normally done inside animate_activity_log)
final_animation = generate_animation(
    full_patient_df_plus_pos=animation_ready_df, # The prepared data!
    event_position_df=my_layout,             # Needed for stage labels, resources
    scenario=scenario_details,               # Needed for drawing resource markers
    plotly_height=600,                       # Set the height of the animation
    icon_and_text_size=20,                   # Make icons smaller
    time_display_units='dhm',                # Show time nicely
    frame_duration=500,                      # Slow down playback slightly
    add_background_image='floorplan.png'     # Optional: Add a background
)

# You can now display the animation (e.g., in a Jupyter Notebook)
# final_animation.show()

What happens when you run this?

The variable final_animation now holds a Plotly Figure object. If you displayed it (e.g., using final_animation.show() in a Jupyter notebook), you would see the complete, interactive animation:

  • Icons (like ‘🧔🏼’ and ‘👨🏿‍🦯’) representing patients moving between locations defined in my_layout.
  • Smooth transitions between the time steps recorded in animation_ready_df.
  • A slider at the bottom showing the time (formatted as Days/Hours/Minutes).
  • Play/Pause buttons.
  • Stage labels (“Entrance”, “Waiting Area”, etc.) displayed on the chart.
  • Static markers showing the available treatment bays (based on scenario_details).
  • Optionally, the ‘floorplan.png’ image in the background.

What’s Happening Under the Hood?

generate_animation relies heavily on the plotly.express.scatter function. Here’s a simplified breakdown of how it works:

  1. Core Scatter Plot: It calls px.scatter, telling it:
    • Use animation_ready_df as the data source.
    • Plot points at x="x_final" and y="y_final".
    • Use the icon column as the text marker for each point (text="icon"). This is how the emojis appear.
    • Set the animation_frame based on the time column (minute_display). This tells Plotly which rows belong to which frame of the animation.
    • Set the animation_group based on the patient column. This tells Plotly that all rows with the same patient ID represent the same object moving across frames, allowing for smooth transitions.
    • Define hover text (hover_name, hover_data) so useful information appears when you mouse over an icon.
    • Set the plot boundaries (range_x, range_y) and size (height, width).
  2. Adding Static Layers: After creating the basic animated scatter plot, generate_animation adds extra, non-moving layers:
    • Stage Labels: If display_stage_labels=True, it adds another go.Scatter trace (this time non-animated) to display the text labels from the event_position_df at their respective (x, y) coordinates.
    • Resource Markers: If a scenario object is provided, it calculates the positions for each individual resource slot (like each treatment bay) based on the event_position_df and the resource counts in scenario. It then adds another static go.Scatter trace to draw markers (like light blue circles or custom icons) at these positions.
    • Background Image: If add_background_image is specified, it uses fig.add_layout_image to place the image underneath the animation layers.
  3. Styling and Controls: Finally, it adjusts the appearance:
    • Sets the size of the icon/text markers (icon_and_text_size).
    • Hides axes and gridlines (unless setup_mode=True).
    • Configures the animation player (play button, slider speed using frame_duration and frame_transition_duration).
    • Returns the complete Plotly Figure object.

Here’s a simplified view of the process:

sequenceDiagram
    participant AAL as animate_activity_log (Optional Caller)
    participant GA as generate_animation (The Projector)
    participant Data as animation_ready_df (The Film Reel)
    participant Layout as event_position_df (Stage Map)
    participant Scenario as scenario object (Resource Info)
    participant PX as Plotly Express
    participant PlotlyFig as Plotly Figure (The Movie)

    AAL->>GA: Call with Data, Layout, Scenario, Options
    GA->>PX: px.scatter(data=Data, x='x_final', y='y_final', text='icon', animation_frame='minute_display', animation_group='patient', ...)
    PX-->>GA: Return basic animated figure (fig)
    GA->>Layout: Get stage label positions
    GA->>PlotlyFig: fig.add_trace(go.Scatter(...)) # Add stage labels
    alt Scenario provided
        GA->>Layout: Get resource base positions & names
        GA->>Scenario: Get resource counts
        GA->>PlotlyFig: fig.add_trace(go.Scatter(...)) # Add resource markers
    end
    opt Background image provided
        GA->>PlotlyFig: fig.add_layout_image(...) # Add background
    end
    GA->>PlotlyFig: Update layout, styles, animation speed
    GA-->>AAL: Return final Plotly Figure

Code Dive (Simplified):

Looking inside the vidigi/animation.py file, the heart of generate_animation is the plotly.express.scatter call:

# Simplified from vidigi/animation.py

import plotly.express as px
import plotly.graph_objects as go
# ... other imports

def generate_animation(full_patient_df_plus_pos, event_position_df, scenario=None, ...):
    # ... (setup code for time display, plot boundaries) ...

    # === Core Animation Creation ===
    fig = px.scatter(
            full_patient_df_plus_pos.sort_values('minute'), # Use the prepared data
            x="x_final",            # Horizontal position from data
            y="y_final",            # Vertical position from data
            animation_frame="minute_display", # Column defining animation time steps
            animation_group="patient",  # Column identifying entities across frames
            text="icon",            # Column with the emoji/text to display
            hover_name="event",     # Show event name on hover
            hover_data=["patient", "time", "resource_id"], # Show other details
            # ... (ranges, height, width) ...
            opacity=0 # Make the actual scatter points invisible (we only see the text)
            )

    # === Add Stage Labels (if requested) ===
    if display_stage_labels:
        fig.add_trace(go.Scatter(
            x=event_position_df['x'] + 10, # Offset slightly
            y=event_position_df['y'],
            mode="text", # Display text, not points
            text=event_position_df['label'], # Get labels from layout
            # ... (styling) ...
            hoverinfo='none' # Don't show hover info for labels
        ))

    # === Add Resource Markers (if scenario provided) ===
    if scenario is not None:
        # ... (code to calculate positions for each resource instance) ...
        # events_with_resources = calculate_resource_positions(...)

        fig.add_trace(go.Scatter(
            x=events_with_resources['x_final'], # Calculated X for each resource spot
            y=events_with_resources['y_final'] - 10, # Place slightly below entity Y
            mode="markers", # Draw markers (e.g., circles)
            marker=dict(color='LightSkyBlue', size=15),
            opacity=resource_opacity,
            hoverinfo='none'
        ))
        # (Or use 'mode="markers+text"' if using custom_resource_icon)

    # === Add Background Image (if requested) ===
    if add_background_image is not None:
        fig.add_layout_image(
            # ... (configuration for image source, position, opacity) ...
        )

    # === Final Styling and Controls ===
    fig.update_traces(textfont_size=icon_and_text_size) # Set icon size
    fig.update_xaxes(showticklabels=False, showgrid=False) # Clean up axes
    fig.update_yaxes(showticklabels=False, showgrid=False)
    # ... (configure animation speed, play button etc.) ...

    return fig

This simplified view shows how generate_animation layers the different visual elements (animated entities, static labels, static resource markers, background) using Plotly’s capabilities to produce the final interactive figure.

Conclusion

You’ve reached the end of the vidigi core concepts tutorial! We’ve seen how generate_animation acts as the final “movie projector”. It takes the meticulously prepared frame-by-frame data (the output of generate_animation_df from Chapter 5: Snapshot Preparation (reshape_for_animations & generate_animation_df)) and uses Plotly Express to render the interactive animation. It plots entities, handles smooth transitions, adds labels, resource markers, and controls, bringing your process simulation to life visually.

While often called behind the scenes by the main animate_activity_log function, understanding generate_animation shows you how the final visualization is constructed.

You now have a complete picture of the vidigi pipeline:

  1. Starting with an Event Log (the script) and a Layout Configuration (event_position_df) (the map).
  2. Ensuring resources are uniquely identified using the pattern from Chapter 4: Simpy Resource Enhancement (CustomResource, Store, populate_store).
  3. Preparing frame-by-frame snapshot data using the functions covered in Chapter 5: Snapshot Preparation (reshape_for_animations & generate_animation_df).
  4. Finally, rendering the animation with generate_animation (this chapter), often orchestrated by the main animate_activity_log facade.

Congratulations! You’re now equipped with the knowledge to understand and use vidigi to create insightful animations of your own processes. Happy visualizing!


Generated by AI Codebase Knowledge Builder