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 theeventcolumn of yourevent_log. Critically, this DataFrame must include entries for'arrival'and the special'exit'event (whichvidigiuses internally, as created byreshape_for_animations). It should also include entries for anyeventnames you used in theevent_logthat 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_labelsparameter inanimate_activity_logisTrue, 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 yourscenarioobject. This attribute in thescenarioobject should hold the capacity (number of available instances) of that resource. For example, if your event is'use_nurse'and yourscenarioobject has an attributescenario.n_nurses = 3, you would put'n_nurses'in theresourcecolumn for the'use_nurse'row. This tellsvidigihow many resource ‘slots’ to visualise and allows it to correctly place entities using specificresource_ids (like nurse 1, nurse 2, nurse 3). This column should beNoneorNaNfor 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
event_position_df = pd.DataFrame([
{'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:
n_nurses = 2 # Let's say there are 2 nurses
scenario_config = Scenario()
# --- 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 whicheventat eachminute) and theevent_position_df. - Merge/Lookup: For each row in the snapshot DataFrame, it looks up the corresponding
eventinevent_position_dfto find the basexandycoordinates. - Calculate Final Position (
x_final,y_final):- For simple events (like
'arrival','exit'): Thex_final,y_finalmight 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 therankcolumn generated earlier). The queue typically extends leftwards from the basexcoordinate. 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_atis set, it will wrap the queue onto new rows below the baseycoordinate. - For
'resource_use'events: It uses theresource_idassociated 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_atalso applies.
- For simple events (like
- Output: It returns an enhanced DataFrame (
full_patient_df_plus_pos) which now includes the calculatedx_finalandy_finalcoordinates 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
resourcecolumn and put the name of the attribute in yourscenarioobject that holds the capacity (e.g.,'n_nurses'). - When
generate_animationruns, it looks at this row inevent_position_df. - It sees
resource = 'n_nurses'. - It accesses the provided
scenarioobject 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_dfuses this information (and theresource_idfrom 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,
scenario=None,
# ... other parameters ...
):
# ... setup code ...
# --- Resource Placeholder Generation ---
if scenario is not None:
# 1. Filter event_position_df to rows where 'resource' is not null
events_with_resources = event_position_df[event_position_df['resource'].notnull()].copy()
# 2. Get the count for each resource from the scenario object
# It applies getattr(scenario, resource_name) to each row
events_with_resources['resource_count'] = events_with_resources['resource'].apply(
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():
base_x, base_y = row['x'], row['y']
count = row['resource_count']
for i in range(count):
# Example: place leftwards
res_x = base_x - (gap_between_resources * (i + 1))
res_y = base_y # (Actual code handles wrapping in y-dimension too)
resource_positions.append({'x': res_x, 'y': res_y})
resource_pos_df = pd.DataFrame(resource_positions)
# 4. Add a scatter trace to the plot for these resource positions
fig.add_trace(go.Scatter(
x=resource_pos_df['x'],
y=resource_pos_df['y'] - 10, # Offset slightly
mode="markers",
marker=dict(color='LightSkyBlue', size=15, opacity=resource_opacity),
hoverinfo='none'
# ... other trace parameters ...
))
# ... code to add entity traces, labels, background image etc. ...
return figThis 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 appropriatexandyvalues 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_durationset low for speed), see how it looks, and adjust thex,yvalues inevent_position_dfas 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.Stores, to facilitate this detailed tracking.
Next: Chapter 4: Simpy Resource Enhancement (CustomResource, Store, populate_store)
Generated by AI Codebase Knowledge Builder