Chapter 3: Layout Configuration (event_position_df
)
In the previous chapter, Chapter 2: Event Log, we explored how to structure the event_log
DataFrame to record the ‘who, what, and when’ of entity movements in your simulation. Now, we need to tell vidigi
where these events should visually occur on the animation canvas. That’s the job of the Layout Configuration, represented by the event_position_df
DataFrame.
Motivation: Drawing the Map
Imagine you’re describing the layout of our simple clinic to someone. You might say, “The entrance is on the left, then there’s a waiting area in the middle, and the treatment rooms are further to the right.” To create a meaningful animation, vidigi
needs precisely this kind of spatial information. Without it, all the entities would just pile up at the default coordinates (0,0)!
The event_position_df
acts as a blueprint or a map for your animation’s background. It defines fixed anchor points on the 2D plane for each key stage or event in your process. These anchor points are then used by vidigi
to position entities, whether they are queuing or using a resource.
The Core Concept: A DataFrame for Coordinates
The event_position_df
is, fundamentally, a pandas DataFrame. Each row defines the base visual location for a specific event name that appears in your event_log
.
Think of it as a lookup table mapping event names to coordinates and other display properties.
Required Columns
Your event_position_df
must contain the following columns:
event
: A string that matches a specific event name found in theevent
column of yourevent_log
. Critically, this DataFrame must include entries for'arrival'
and the special'exit'
event (whichvidigi
uses internally, as created byreshape_for_animations
). It should also include entries for anyevent
names you used in theevent_log
that correspond to the start of a queue (event_type='queue'
) or the start of resource use (event_type='resource_use'
) that you wish to visualise. You generally don’t need entries forevent_type='resource_use_end'
or'depart'
(as'exit'
handles the final visual state).x
: A numerical value representing the base X-coordinate for this event on the animation canvas.y
: A numerical value representing the base Y-coordinate for this event.
Optional Columns
You can add these columns for more control and clarity:
label
: A string providing a human-readable name for the stage corresponding to this event (e.g., “Waiting Area”, “Treatment Room 1”). If thedisplay_stage_labels
parameter inanimate_activity_log
isTrue
, these labels will be drawn on the animation near the(x, y)
coordinates. It’s good practice to always include this, even if you don’t display them initially.resource
: A string that links an event associated with resource usage (i.e., one where entities haveevent_type='resource_use'
in theevent_log
) to an attribute name in yourscenario
object. This attribute in thescenario
object should hold the capacity (number of available instances) of that resource. For example, if your event is'use_nurse'
and yourscenario
object has an attributescenario.n_nurses = 3
, you would put'n_nurses'
in theresource
column for the'use_nurse'
row. This tellsvidigi
how many resource ‘slots’ to visualise and allows it to correctly place entities using specificresource_id
s (like nurse 1, nurse 2, nurse 3). This column should beNone
orNaN
for non-resource-use events like'arrival'
or queue events.
Usage Example: Defining the Clinic Layout
Let’s revisit our clinic example and define its layout. Suppose we have arrival, a waiting area ('wait_nurse'
event), a treatment area ('use_nurse'
event), and the exit point. We also have a scenario
object where scenario.n_nurses
defines the number of nurses.
import pandas as pd
import numpy as np
# Example event_position_df
= pd.DataFrame([
event_position_df 'event': 'arrival', # Matches 'arrival' in event_log
{'x': 50, 'y': 200,
'label': "Arrival Area", # Human-readable label
'resource': np.nan}, # Not a resource use step
'event': 'wait_nurse', # Matches 'wait_nurse' (queue event) in event_log
{'x': 150, 'y': 250,
'label': "Waiting Area",
'resource': np.nan}, # Queue, not direct resource use
'event': 'use_nurse', # Matches 'use_nurse' (resource_use event) in event_log
{'x': 250, 'y': 150,
'label': "Treatment Bays",
'resource': 'n_nurses'}, # Links to scenario.n_nurses for capacity
'event': 'exit', # Matches the special 'exit' event
{'x': 350, 'y': 200,
'label': "Exit Point",
'resource': np.nan} # Not a resource use step
])
# --- Placeholder Scenario Object (Assume this exists elsewhere) ---
class Scenario:
= 2 # Let's say there are 2 nurses
n_nurses = Scenario()
scenario_config # --- End Placeholder ---
print(event_position_df)
Output:
event x y label resource
0 arrival 50 200 Arrival Area NaN
1 wait_nurse 150 250 Waiting Area NaN
2 use_nurse 250 150 Treatment Bays n_nurses
3 exit 350 200 Exit Point NaN
This DataFrame provides vidigi
with the necessary spatial anchors.
How vidigi
Uses the Layout
The event_position_df
is primarily used within the Chapter 5: Snapshot Preparation (reshape_for_animations
& generate_animation_df
) stage, specifically by the generate_animation_df
function, and also by the Chapter 6: Animation Generation (generate_animation
) function for drawing labels and resource placeholders.
Here’s a conceptual walkthrough of how generate_animation_df
uses it:
- Input: It receives the snapshot DataFrame (output from
reshape_for_animations
, detailing which entity is performing whichevent
at eachminute
) and theevent_position_df
. - Merge/Lookup: For each row in the snapshot DataFrame, it looks up the corresponding
event
inevent_position_df
to find the basex
andy
coordinates. - Calculate Final Position (
x_final
,y_final
):- For simple events (like
'arrival'
,'exit'
): Thex_final
,y_final
might be directly set to the basex
,y
. - For
'queue'
events: It calculates the entity’s position within the queue (based on arrival time at that queue step, using therank
column generated earlier). The queue typically extends leftwards from the basex
coordinate. So, the first person might be atx
, the second atx - gap_between_entities
, the third atx - 2 * gap_between_entities
, and so on. Ifwrap_queues_at
is set, it will wrap the queue onto new rows below the basey
coordinate. - For
'resource_use'
events: It uses theresource_id
associated with the entity (from theevent_log
) and the basex
,y
. Similar to queues, resources are typically placed extending leftwards. Ifresource='n_nurses'
, resource 1 might be atx - gap_between_resources
, resource 2 atx - 2 * gap_between_resources
, etc. The entity is placed at the coordinates corresponding to its assignedresource_id
. Wrapping viawrap_resources_at
also applies.
- For simple events (like
- Output: It returns an enhanced DataFrame (
full_patient_df_plus_pos
) which now includes the calculatedx_final
andy_final
coordinates for every entity at every time snapshot.
The generate_animation
function then uses this full_patient_df_plus_pos
to create the scatter plot. It also uses the original event_position_df
again: * To draw the stage labels (if display_stage_labels=True
) near the base x
, y
coordinates. * To draw placeholder markers for the available resources (using the resource
column to find the capacity in the scenario
object) near the base x
, y
coordinates for resource stages.
Linking Resources via the scenario
Object
The connection between event_position_df
and the scenario
object is vital for visualising resource utilisation accurately.
- In
event_position_df
, you identify a resource stage (e.g.,'use_nurse'
). - You add the
resource
column and put the name of the attribute in yourscenario
object that holds the capacity (e.g.,'n_nurses'
). - When
generate_animation
runs, it looks at this row inevent_position_df
. - It sees
resource = 'n_nurses'
. - It accesses the provided
scenario
object and retrieves the value ofgetattr(scenario, 'n_nurses')
(which might be, say, 2). - It now knows it needs to display 2 placeholder resource markers (e.g., light blue circles) near the base coordinates
(x, y)
defined for'use_nurse'
. - Simultaneously,
generate_animation_df
uses this information (and theresource_id
from the event log) to ensure Patient A using Nurse 1 is placed at the coordinates for resource slot 1, and Patient B using Nurse 2 is placed at the coordinates for resource slot 2.
Here’s a simplified snippet from vidigi.animation.generate_animation
showing how the resource count is retrieved:
# From: vidigi/animation.py (Simplified inside generate_animation)
def generate_animation(
# ... other parameters ...
event_position_df,=None,
scenario# ... other parameters ...
):# ... setup code ...
# --- Resource Placeholder Generation ---
if scenario is not None:
# 1. Filter event_position_df to rows where 'resource' is not null
= event_position_df[event_position_df['resource'].notnull()].copy()
events_with_resources
# 2. Get the count for each resource from the scenario object
# It applies getattr(scenario, resource_name) to each row
'resource_count'] = events_with_resources['resource'].apply(
events_with_resources[lambda resource_name: getattr(scenario, resource_name)
)
# 3. Calculate positions for each resource instance marker
# (Simplified - actual code handles wrapping and gaps)
= []
resource_positions for _, row in events_with_resources.iterrows():
= row['x'], row['y']
base_x, base_y = row['resource_count']
count for i in range(count):
# Example: place leftwards
= base_x - (gap_between_resources * (i + 1))
res_x = base_y # (Actual code handles wrapping in y-dimension too)
res_y 'x': res_x, 'y': res_y})
resource_positions.append({
= pd.DataFrame(resource_positions)
resource_pos_df
# 4. Add a scatter trace to the plot for these resource positions
fig.add_trace(go.Scatter(=resource_pos_df['x'],
x=resource_pos_df['y'] - 10, # Offset slightly
y="markers",
mode=dict(color='LightSkyBlue', size=15, opacity=resource_opacity),
marker='none'
hoverinfo# ... other trace parameters ...
))
# ... code to add entity traces, labels, background image etc. ...
return fig
This snippet illustrates how the resource
column acts as the bridge between the layout definition and the scenario’s parameters to determine how many resource slots to show. The actual placement of entities into these slots happens in generate_animation_df
using the resource_id
from the event_log
.
Tips for Setting Up Your Layout
Defining coordinates can sometimes involve a bit of trial and error. Here are a couple of tips:
- Use
setup_mode=True
: When callinganimate_activity_log
, setsetup_mode=True
. This will display the plot axes with grid lines and coordinate values, making it much easier to estimate appropriatex
andy
values for yourevent_position_df
. - Sketch it Out: Draw a rough sketch of your desired layout on paper first. This can help you think about the relative positioning of stages before you start coding the coordinates.
- Iterate: Don’t expect to get it perfect on the first try. Define initial coordinates, run the animation (perhaps with
limit_duration
set low for speed), see how it looks, and adjust thex
,y
values inevent_position_df
as needed. The HSMA example in the documentation (vidigi_docs/adding_vidigi_to_a_simple_simpy_model_hsma_structure.qmd
) shows this iterative process.
Conclusion
The event_position_df
is your key tool for controlling the visual layout of your vidigi
animation. By mapping event names from your simulation log to specific X and Y coordinates, providing human-readable labels, and linking resource-use events to capacities defined in a scenario
object, you create the essential blueprint that guides how entities are positioned on the screen. It translates the abstract sequence of events from your event_log
into a spatially meaningful visualisation.
We’ve seen how this DataFrame specifies base locations and how it interacts with the scenario
object for resource visualisation. However, correctly logging the resource_id
for individual resource instances relies on enhancements to standard simpy
resources. In the next chapter, we’ll look at how vidigi
uses CustomResource
objects, often managed within simpy.Store
s, to facilitate this detailed tracking.
Next: Chapter 4: Simpy Resource Enhancement (CustomResource
, Store
, populate_store
)
Generated by AI Codebase Knowledge Builder