Chapter 6: Action! Making the Movie with generate_animation

Great Scott! In Chapter 5: Prepare for Snapshots, McFly! (reshape_for_animations & generate_animation_df), we saw how vidigi acts like a skilled film crew, taking our raw Event Log (the script) and turning it into a detailed storyboard (full_patient_df_plus_pos). This storyboard tells us exactly who (Maverick, Goose) is doing what (‘wait_for_simulator’, ‘start_simulator’), when (minute 10, 15, etc.), and precisely where on the screen (‘x_final’, ‘y_final’) they should be, complete with their assigned icon (like giving Johnny 5 his signature look!).

But a storyboard isn’t a movie! We need to actually project these frames onto the screen, make them move smoothly, add the background scenery, and get the VCR (or maybe the Betamax?) rolling. We need the final rendering engine, the movie projector, the director yelling “Action!”

That’s the role of the generate_animation function! It takes the perfectly prepared snapshot data and uses the power of Plotly Express (think of it as the special effects department, maybe ILM back in the 80s) to create the final, interactive animation. It’s time to bring your data to life!

The Mission: Rolling Film!

Our mission, should we choose to accept it, is to take the full_patient_df_plus_pos DataFrame – our meticulously prepared sequence of snapshots with precise coordinates – and turn it into a slick, animated visualization. We want to see Maverick and Goose (represented by cool 80s-style emoji icons, naturally) move through the ‘Hangar Deck’, queue in the ‘Ready Room’, use the ‘Simulator Bays’, and finally head to ‘Debriefing’, all smoothly animated over time with a playable timeline. Cue the Top Gun theme!

The Projector: How generate_animation Works

Think of generate_animation as the high-tech projector in the back of the cinema. It knows how to take the film reel (full_patient_df_plus_pos) and display it frame by frame, creating the illusion of motion.

Its core mechanism relies on plotly.express.scatter:

  1. Input Data: It receives the full_patient_df_plus_pos DataFrame, which has columns like minute (or minute_display for formatted time), patient, x_final, y_final, and icon. This is the complete set of instructions for where each actor (icon) should be in every frame (minute).
  2. Plotly Magic: It calls plotly.express.scatter, telling it:
    • Use x_final and y_final for the position of each point.
    • Use icon as the text label for each point (making the points themselves invisible, so we only see the emoji!).
    • Use minute_display as the animation_frame. This tells Plotly to create a separate frame for each unique value in this column and add a slider to control time.
    • Use patient as the animation_group. This tells Plotly that rows with the same patient ID across different frames represent the same object moving, allowing Plotly to create smooth transitions between positions. It’s like telling the computer that the ‘Maverick’ icon in frame 10 is the same dude as the ‘Maverick’ icon in frame 11, just potentially in a different spot.
  3. Adding Static Layers: After Plotly creates the basic animated scatter plot, generate_animation adds extra, non-moving elements:
    • Stage Labels: If display_stage_labels=True, it adds text labels (from the Layout Configuration (event_position_df)) at the specified base coordinates, like putting up signs for “Hangar Deck” or “Simulator Bays”.
    • Resource Placeholders: If a scenario object is provided, it uses the resource counts (e.g., g.n_simulators) and the layout information to draw placeholder icons (often light blue circles, but customizable!) for all available resource slots (like ‘Sim_1’, ‘Sim_2’). This lets you see empty bays waiting for pilots. It’s like showing the empty parking spots in the FLAG Mobile Unit for K.I.T.T. and K.A.R.R.
    • Background Image: If you provide add_background_image, it stretches your image across the plot area, like projecting a cool background matte painting from Labyrinth or the digital grid from Tron.
  4. Final Touches: It configures the plot’s appearance (hiding axes, setting height/width, adjusting animation speed) and returns the final Plotly Figure object.

Using the Projector: Code Example

You usually won’t call generate_animation directly. The main animate_activity_log function calls it for you after preparing the data with reshape_for_animations and generate_animation_df. However, understanding its signature helps you know what customization options are passed through.

Here’s a conceptual call, assuming data_ready_for_animation (the output from generate_animation_df) and layout_df (our event_position_df) exist:

import plotly.graph_objects as go
from vidigi.animation import generate_animation # Import the function
# Assume 'data_ready_for_animation' DataFrame exists (output of generate_animation_df)
# Assume 'layout_df' DataFrame exists (our event_position_df)
# Assume 'g' scenario object exists with g.n_simulators = 2

# --- This function is usually called internally by animate_activity_log ---
final_figure = generate_animation(
    full_patient_df_plus_pos=data_ready_for_animation, # The movie reel
    event_position_df=layout_df, # Needed for labels/resource positions
    scenario=g, # Needed for resource placeholders
    plotly_height=600, # Make the screen shorter
    icon_and_text_size=30, # Bigger icons! RADICAL!
    display_stage_labels=True, # Show "Simulator Bays" etc.
    add_background_image="path/to/your/awesome_grid.png", # Optional Tron background
    frame_duration=500, # Slow down the frames a bit (milliseconds)
    frame_transition_duration=500 # Smoother transitions
)

# If you called it directly, you'd show it like this:
# final_figure.show() # Uncomment to display!

print("Plotly Figure generated! Ready to rock and roll!")

This call tells generate_animation to take the prepared data, use the layout for context, show resource placeholders based on the scenario, customize the appearance, and return the finished Plotly Figure object, ready to be displayed.

Under the Hood: Inside the Projection Booth

Let’s peek inside generate_animation without needing K.I.T.T.’s X-ray scanner.

Step-by-Step:

  1. Receive Inputs: Gets the prepared data (full_patient_df_plus_pos), layout (event_position_df), scenario, and all the customization parameters.
  2. Calculate Boundaries: Determines the X and Y axis ranges for the plot based on the layout or overrides.
  3. Format Time: If time_display_units is set (e.g., ‘dhm’), it converts the raw minute numbers into nicely formatted date/time strings for the animation slider, storing them in minute_display. It keeps the original minute column for sorting. It’s like adding subtitles to the film.
  4. Core Animation (px.scatter): This is the main event! It calls plotly.express.scatter, passing the prepared data and mapping columns:
    • x="x_final"
    • y="y_final"
    • animation_frame="minute_display"
    • animation_group="patient"
    • text="icon" (with opacity=0 for the underlying marker)
    • hover_name, hover_data for interactivity. Plotly Express does the heavy lifting here, creating the base figure with animated points and the time slider. It’s like the core rendering engine turning vectors into pixels on the Tron grid.
  5. Add Stage Labels (go.Scatter): If enabled, it iterates through the event_position_df and adds a static go.Scatter trace with mode="text" to display the label at the base x, y coordinates.
  6. Add Resource Placeholders (go.Scatter): If a scenario is provided, it calculates the positions for all resource slots (using the base x, y from the layout, gap_between_resources, gap_between_rows, and wrap_resources_at). It then adds another static go.Scatter trace (either mode="markers" for default circles or mode="markers+text" if custom_resource_icon is used) to show these placeholders. It’s like drawing the empty docking bays on Red Dwarf’s status screen.
  7. Add Background (add_layout_image): If an image path is provided, it adds the image to the layout, stretched to fit the plot area.
  8. Configure Layout: Updates the figure’s layout: sets height/width, hides axes and gridlines (unless setup_mode=True), ensures the play button is configured (or removed), and sets the animation frame/transition durations (frame_duration, frame_transition_duration).
  9. Return Figure: Returns the fully constructed and configured plotly.graph_objs.Figure object.

Sequence Diagram:

sequenceDiagram
    participant Caller as Your Code / animate_activity_log
    participant GA as generate_animation
    participant DataIn as data_with_positions (DataFrame)
    participant Layout as event_position_df (DataFrame)
    participant Scenario as Scenario Object
    participant PlotlyEx as Plotly Express
    participant PlotlyGO as Plotly Graph Objects
    participant PlotlyFig as Plotly Figure Output

    Caller->>GA: Call(data_with_positions, layout, scenario, ...)
    GA->>DataIn: Read positions, icons, minute
    GA->>Layout: Read base positions, labels
    GA->>Scenario: Read resource counts (if provided)
    GA->>PlotlyEx: px.scatter(DataIn, animation_frame='minute_display', ...)
    PlotlyEx-->>GA: Return base animated Figure
    alt display_stage_labels is True
        GA->>PlotlyGO: fig.add_trace(go.Scatter(mode='text', ...))
    end
    alt scenario is not None
        GA->>PlotlyGO: fig.add_trace(go.Scatter(mode='markers', ...)) # Resource placeholders
    end
    alt add_background_image is not None
        GA->>PlotlyFig: fig.add_layout_image(...)
    end
    GA->>PlotlyFig: fig.update_layout(axes, speed, ...)
    PlotlyFig-->>GA: Return configured Figure
    GA-->>Caller: Return final_figure

Code Snippets (Simplified from vidigi/animation.py):

The core animation command:

# --- Inside generate_animation (Simplified) ---
import plotly.express as px

# (Assume data_ready_for_animation has columns:
#  'x_final', 'y_final', 'minute_display', 'patient', 'icon', 'event', ...)

fig = px.scatter(
    data_ready_for_animation.sort_values('minute'), # Sort by time!
    x="x_final",
    y="y_final",
    animation_frame="minute_display", # Tells Plotly how to make frames
    animation_group="patient",       # Tells Plotly how to connect dots
    text="icon",                    # Display the emoji icon
    hover_name="event",             # Show event name on hover
    # ... other parameters like hover_data, ranges, height ...
    opacity=0                       # Make the underlying point invisible
)
# Status: Base animation created! Like the raw footage.
# --- End Snippet ---

This single call creates the moving emojis and the time slider. Everything else builds on this foundation.

Adding static stage labels:

# --- Inside generate_animation (Simplified) ---
import plotly.graph_objects as go
# (Assume 'layout_df' has columns 'x', 'y', 'label')

if display_stage_labels:
    fig.add_trace(go.Scatter(
        x=layout_df['x'], # Base X coordinates
        y=layout_df['y'], # Base Y coordinates
        mode="text",     # We just want text, no markers
        name="",         # No legend entry needed
        text=layout_df['label'], # The labels to display
        textposition="middle right", # Position relative to x,y
        hoverinfo='none' # Don't show hover info for labels
    ))
# Status: Added signs like "Welcome to Hill Valley".
# --- End Snippet ---

Adding static resource placeholder icons:

# --- Inside generate_animation (Simplified) ---
import plotly.graph_objects as go
import pandas as pd
# (Assume 'events_with_resources' DataFrame is calculated with columns:
#  'x_final', 'y_final' for each resource slot position)

if scenario is not None:
    # (Calculation of 'events_with_resources' positions omitted for brevity)
    # ... calculates x_final, y_final for each resource slot ...

    fig.add_trace(go.Scatter(
        x=events_with_resources['x_final'],
        y=[i - 10 for i in events_with_resources['y_final']], # Slightly offset Y
        mode="markers", # Draw markers (default: circles)
        marker=dict(
            color='LightSkyBlue',
            size=15), # Appearance of markers
        opacity=resource_opacity, # Make them semi-transparent
        hoverinfo='none' # No hover needed
    ))
# Status: Added placeholders for empty simulator bays. Number 5 is... available!
# --- End Snippet ---

Adding the background image:

# --- Inside generate_animation (Simplified) ---
# (Assume 'add_background_image' holds the file path string)

if add_background_image is not None:
    fig.add_layout_image(
        dict(
            source=add_background_image, # Path to the image file
            xref="x domain", yref="y domain", # Stretch across axes
            x=1, y=1, sizex=1, sizey=1, # Cover the whole area
            xanchor="right", yanchor="top",
            sizing="stretch", # Stretch to fit
            opacity=0.5,      # Make it slightly transparent
            layer="below"     # Draw it underneath everything else
        )
    )
# Status: Laid down the background, maybe the entrance to Jareth's Labyrinth!
# --- End Snippet ---

These snippets show how generate_animation builds the final plot layer by layer, starting with the core animation from Plotly Express and adding static elements using Plotly Graph Objects.

Conclusion: That’s a Wrap!

You’ve reached the end of the production line! generate_animation is the final function in the vidigi animation pipeline, the projector that takes the meticulously prepared snapshot data (full_patient_df_plus_pos) and renders it into a dynamic, interactive Plotly animation. It leverages Plotly Express for the core animation and adds essential static layers like stage labels, resource placeholders, and background images.

This function, along with its partners reshape_for_animations and generate_animation_df, does the heavy lifting behind the scenes when you call the main animate_activity_log function. Together, they turn your raw simulation Event Log and Layout Configuration (event_position_df) into a visual story, letting you see your process unfold like never before – hopefully without disrupting the space-time continuum!

You now understand the key components and the flow of data required to create vidigi animations. Go forth and visualize your processes – make them totally awesome, to the max!


Generated by AI Codebase Knowledge Builder