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
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:
- Draw Each Frame: For each time snapshot (
minute), it plots each entity’siconat its calculatedx_finalandy_finalposition. - 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).
- 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:
- Core Scatter Plot: It calls
px.scatter, telling it:- Use
animation_ready_dfas the data source. - Plot points at
x="x_final"andy="y_final". - Use the
iconcolumn as the text marker for each point (text="icon"). This is how the emojis appear. - Set the
animation_framebased on the time column (minute_display). This tells Plotly which rows belong to which frame of the animation. - Set the
animation_groupbased on thepatientcolumn. This tells Plotly that all rows with the samepatientID 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).
- Use
- Adding Static Layers: After creating the basic animated scatter plot,
generate_animationadds extra, non-moving layers:- Stage Labels: If
display_stage_labels=True, it adds anothergo.Scattertrace (this time non-animated) to display the text labels from theevent_position_dfat their respective (x, y) coordinates. - Resource Markers: If a
scenarioobject is provided, it calculates the positions for each individual resource slot (like each treatment bay) based on theevent_position_dfand the resource counts inscenario. It then adds another staticgo.Scattertrace to draw markers (like light blue circles or custom icons) at these positions. - Background Image: If
add_background_imageis specified, it usesfig.add_layout_imageto place the image underneath the animation layers.
- Stage Labels: If
- 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_durationandframe_transition_duration). - Returns the complete Plotly
Figureobject.
- Sets the size of the icon/text markers (
Here’s a simplified view of the process:
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 figThis 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:
- Starting with an Event Log (the script) and a Layout Configuration (
event_position_df) (the map). - Ensuring resources are uniquely identified using the pattern from Chapter 4: Simpy Resource Enhancement (
CustomResource,Store,populate_store). - Preparing frame-by-frame snapshot data using the functions covered in Chapter 5: Snapshot Preparation (
reshape_for_animations&generate_animation_df). - Finally, rendering the animation with
generate_animation(this chapter), often orchestrated by the mainanimate_activity_logfacade.
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