Customising the animation
The appearance arguments to animate_activity_log and generate_animation
Both animation entry points return an ordinary Plotly go.Figure, so anything is adjustable after the fact. Most of the common tweaks, though, are already exposed as keyword arguments so you don’t have to reach into the figure by hand.
This page groups those arguments by what they change.
animate_activity_log is the all-in-one function. generate_animation is the last of the three step-by-step functions (reshape_for_animations → generate_animation_df → generate_animation).
The appearance arguments below are accepted by both, except for a handful that act while the event log is being reshaped into per-frame snapshots (wrap_queues_at, gap_between_entities, gap_between_queue_rows, step_snapshot_max, step_snapshot_max_overrides, custom_entity_icon_list, and the gauge arguments). Those live on animate_activity_log only — if you are using the step-by-step route, pass them to reshape_for_animations / generate_animation_df instead.
The figure is just Plotly
If something you want to change isn’t in the list below, capture the figure and edit it directly:
fig = animate_activity_log(event_log, event_position_df, scenario=scenario)
fig.update_layout(
title_text="Emergency Department — Monday",
font_family="Times New Roman",
)
fig.show()fig.update_layout, fig.update_traces, fig.update_xaxes / fig.update_yaxes and fig.add_annotation all work as normal. The frame-speed feature breakdown is an example of post-hoc editing — it rewrites the playback timings on an already-generated figure.
Background colour
Added in 2.0.0. These forward straight to fig.update_layout(...), so they accept any CSS colour string ("white", "#f5f5f5", "rgba(0,0,0,0)", …).
| Argument | Default | Effect |
|---|---|---|
plot_bgcolor |
None |
Colour of the plotting area, inside the axes. |
paper_bgcolor |
None |
Colour of the surround — behind the title, play button and timeline. |
None leaves the active Plotly template in charge, which is why the default figure has the faint blue-grey plot area you may recognise from Plotly Express.
# A plain white canvas
fig = animate_activity_log(
event_log, event_position_df, scenario=scenario,
plot_bgcolor="white",
paper_bgcolor="white",
)# Dark mode
fig = animate_activity_log(
event_log, event_position_df, scenario=scenario,
plot_bgcolor="#111111",
paper_bgcolor="#111111",
overflow_text_color="white",
stage_label_text_colour="white",
)Set both to "rgba(0,0,0,0)" when the animation will sit on a coloured page or inside a Streamlit app whose theme should show through.
Background image
| Argument | Default | Effect |
|---|---|---|
add_background_image |
None |
Path, URL or data-URI of an image drawn behind the animation (a floor plan, a process diagram, …). Local files are embedded into the figure so it stays portable. |
background_image_opacity |
0.5 |
0 fully transparent, 1 fully opaque. |
A background image pairs naturally with display_stage_labels=False when the image already carries its own stage names. Several gallery examples use one — the car wash and gas station among them.
Stage labels
| Argument | Default | Effect |
|---|---|---|
display_stage_labels |
True |
Draw the label text from event_position_df next to each stage. |
text_size |
24 |
Font size of the stage labels, in points. |
stage_label_text_colour |
"black" |
Font colour of the stage labels. |
The figure margin auto-expands to fit long stage labels, so you rarely need override_x_max just to stop them being clipped.
Icons and the text on them
| Argument | Default | Effect |
|---|---|---|
entity_icon_size |
24 |
Size of the entity (patient / customer / …) icons. |
resource_icon_size |
24 |
Size of the resource-availability icons. |
overflow_text_color |
"black" |
Colour of the text drawn to represent any additional entities that exceed step_snapshot_max. |
resource_opacity |
0.8 |
Opacity of the resource icons. |
custom_resource_icon |
None |
Emoji or short string used for every resource icon in place of the default. |
resource_icon (per event) |
None |
Field on EventPosition (or a column on event_position_df) overriding custom_resource_icon for one event, so resource stages can each have their own icon — a glyph, or an image (a URL, local path, or data: URI). See below. |
custom_entity_icon_list |
None |
List of emoji cycled through for entity icons (animate_activity_log only). |
flip_entity_icons |
False |
Mirror entity icons (and a custom_resource_icon) horizontally — useful when an emoji faces the wrong way for a layout, independently of queue_direction. Set per event instead with a flip_icons column on event_position_df. |
entity_icon_font |
None |
Render entity icons in an icon font instead of emoji — a preset name ("font-awesome", "bootstrap-icons", "material-symbols") or any CSS font-family already on the page. custom_entity_icon_list then supplies that font’s codepoints instead of emoji. Resource glyphs have their own resource_icon_font. |
entity_icon_font_weight |
None |
Overrides a preset’s default weight (Font Awesome ships Solid at 900, Regular at 400). |
resource_icon_font |
None |
Render glyph resource icons (custom_resource_icon, and text-glyph resource_icon) in an icon font — same accepted values as entity_icon_font, chosen independently of it, so entities and resources can be in different fonts (or one on emoji). Animation-wide. See below. |
resource_icon_font_weight |
None |
Overrides a preset’s default weight for resource_icon_font. |
entity_colour_by |
None |
Name of an event-log column to colour entity icons by (priority, pathway, …). Only visible together with entity_icon_font — emoji ignore colour entirely. |
entity_colour_map |
None |
Maps entity_colour_by values to specific colours, e.g. {"high": "crimson", "low": "steelblue"}. An uncovered value falls back to Plotly’s default palette. |
show_entity_legend |
True |
Show a legend for entity_colour_by. Ignored when it is not set. |
entity_annotation_by |
None |
Name of a column to draw as a second, independently-styled text trace offset below each entity’s icon (a running length-of-stay figure, a delay flag, …). Express backend only. None draws no second trace at all — see below for when to reach for this instead of just appending onto icon. |
entity_annotation_size |
14 |
Font size of the entity_annotation_by text. Independent of entity_icon_size. |
entity_annotation_color |
"black" |
Colour of the entity_annotation_by text. Independent of entity_colour_by/overflow_text_color. |
entity_annotation_offset_y |
-15 |
Vertical offset, in data units, of entity_annotation_by text below (negative) or above (positive) each entity’s icon. |
resource_image_size |
None |
Size, in data units, of an image resource_icon (see below). Defaults to resource_icon_size, kept independent of gap_between_resources so widening the spacing between resources doesn’t also inflate the image. |
entity_resource_offset_y |
-10 |
Vertical offset, in data units, of each resource icon relative to the entity using it — negative below (the default), positive above. Applies to the dot, a glyph custom_resource_icon/resource_icon, and an image resource_icon alike. Handy for lifting an entity clear of a large resource_image_size. |
Flipping is a CSS mirror, not a different icon, so it needs a small <style> block to reach the page — vidigi injects it automatically in a notebook or Streamlit app whenever any icon actually resolves to flipped. Embedding the figure another way (fig.write_html(), a hand-built page) needs it added explicitly:
from vidigi.utils import entity_icon_flip_css
with open("animation.html", "w") as f:
f.write(entity_icon_flip_css())
f.write(fig.to_html(full_html=False, include_plotlyjs="cdn"))or, in Streamlit:
import streamlit as st
from vidigi.utils import entity_icon_flip_css
st.markdown(entity_icon_flip_css(), unsafe_allow_html=True)
st.plotly_chart(fig)This does not affect a static export via fig.write_image(), which renders in its own page rather than the browser tab the CSS would otherwise reach.
Beyond emoji: icon fonts, per-entity colour, and per-resource icons
Emoji cap what an icon can look like, and — being colour fonts — ignore textfont.color entirely, so an entity can never be coloured by, say, priority. entity_icon_font switches to an icon font instead: thousands of monochrome glyphs, which entity_colour_by can then colour meaningfully.
animate_activity_log(
event_log, event_position_df,
entity_icon_font="font-awesome",
custom_entity_icon_list=["", ""], # fa-person / fa-person-dress
entity_colour_by="priority",
entity_colour_map={"high": "crimson", "low": "steelblue"},
)Presets load their CSS from a CDN — nothing is bundled with vidigi, so this needs network access at view time, and does not affect fig.write_image(), for the same reason flipping doesn’t. The CSS is injected automatically, the same way as flip_entity_icons’s; see vidigi.utils.entity_icon_font_css() / inject_icon_font_css() for embedding a figure another way.
entity_icon_font / resource_icon_font need plotly ≥ 5.23.0
An icon-font preset always resolves a numeric font weight (Font Awesome Solid is 900), and numeric textfont.weight on scatter traces only arrived in plotly.js 2.33.0, bundled from plotly.py 5.23.0. On an older plotly the call raises ValueError: Invalid value ... received for the 'weight' property. If you hit that, upgrade with pip install -U plotly. Emoji animations are unaffected — vidigi’s overall minimum is still plotly>=5.12.0.
"material-symbols" is a ligature font, so custom_entity_icon_list can use names instead of codepoints ("directions_walk" renders as the walking-person glyph directly). The + N more / ASCII-gauge overflow icon always stays on the default font — a substituted glyph in place of the ASCII art would be worse than plain text.
custom_resource_icon sets one icon for every resource. resource_icon — a field on EventPosition, or a column on a hand-built / CSV event_position_df — overrides it for a single event, so each resource stage can have its own:
create_event_position_df([
EventPosition(event="registration", x=120, y=175, resource="n_clerks",
label="Registration", resource_icon="🖥️"),
EventPosition(event="treatment_begins", x=250, y=175, resource="n_cubicles",
label="Being Treated", resource_icon="assets/bed.png"),
])The value is a text glyph (an emoji or short string, drawn as scatter text and mirrored by flip_entity_icons / a per-event flip_icons, exactly like custom_resource_icon), or an image — a URL, local path, or data: URI, recognised by an image file extension or URL scheme. An image is drawn at the resource’s position via Plotly’s layout.images rather than as text, sized by resource_image_size; it is static across frames, but cannot be mirrored (Plotly has no per-image transform), so supply it pre-flipped if the layout needs it.
A glyph resource_icon (and custom_resource_icon) renders in the page default font unless you set resource_icon_font — the resource-side counterpart to entity_icon_font, taking the same preset names or CSS families and chosen independently, so entities and resources can be in different icon fonts, or one in a font while the other stays on emoji. Put the codepoint or ligature straight into custom_resource_icon / resource_icon; there is no list argument like custom_entity_icon_list. It is animation-wide — every glyph resource stage shares it — and does nothing to an image resource_icon.
The custom icons feature breakdown notebook walks through all three together.
Annotating an icon with extra text
Sooner or later you’ll want to show something about an entity next to its icon — a running length-of-stay figure, a delay flag, a surgery type. There are two ways to do it, and they aren’t interchangeable.
The default: append it onto the icon. vidigi has always drawn one point of text per entity, so the cheapest way to add more is to bake it into that same string — typically by post-processing the icon column of the dataframe produced by reshape_for_animations()/generate_animation_df() before passing it to generate_animation():
full_entity_df["icon"] = full_entity_df["icon"] + "<br>" + full_entity_df["los"].astype(str)This is exactly what example_13’s synchronised-traces walkthrough does to show a running length-of-stay and delayed-discharge flag next to each patient. Prefer this whenever you can — it’s one point of text per entity, same as vidigi has always drawn, so it costs nothing extra.
Where it breaks: flip_entity_icons and entity_icon_font. Both act on the icon’s rendered SVG <text> node — flipping mirrors the whole node, re-fonting sets one font-family for the whole node — and Plotly gives a single <text> node exactly one transform and one font, with no equivalent of HTML’s independently-stylable <tspan>. Text appended into the same string as the icon shares that node, so it gets mirrored or re-fonted right along with the icon: mirrored digits and reversed multi-digit order for a length-of-stay number, tofu or a broken ligature for text sharing a node with a "material-symbols" glyph. This is a genuine Plotly/SVG ceiling, not something vidigi can parse around from the Python side.
entity_annotation_by when you need both. Combining annotated icons with flip_entity_icons or entity_icon_font needs the annotation drawn as a separate scatter trace — offset below the icon, moving in lockstep with it, but never touched by flip or font logic because it never shares a text node with the icon at all:
generate_animation(
full_entity_df_plus_pos, event_position_df,
entity_icon_font="font-awesome",
flip_entity_icons=True,
entity_annotation_by="los", # a column on full_entity_df_plus_pos
entity_annotation_offset_y=-15,
)The trade-off is real, not cosmetic: this is a second, fully-animated trace with its own point per entity per frame, roughly doubling the per-frame text/point payload for the whole animation, against the appending method’s one point per entity. Reach for it specifically when flip/icon-font are in play for this animation; keep appending onto icon otherwise. feat_custom_icons.ipynb has a worked example combining all three.
Size, spacing and wrapping
| Argument | Default | Effect |
|---|---|---|
plotly_height |
900 |
Figure height in pixels. |
plotly_width |
None |
Figure width in pixels; None lets the figure size itself. |
gap_between_entities |
10 |
Horizontal gap between entities in a queue, in pixels (animate_activity_log only). |
gap_between_queue_rows |
30 |
Vertical gap between wrapped queue rows (animate_activity_log only). |
gap_between_resources |
10 |
Horizontal gap between resource icons. |
stage_label_offset |
10 |
Gap, in data units, between a stage label and the front of its queue/resources. Increase for larger icon sizes so labels don’t crowd the icons. |
gap_between_resource_rows |
30 |
Vertical gap between wrapped resource rows. |
wrap_queues_at |
20 |
Entities per row before a queue wraps onto a new line (animate_activity_log only). |
wrap_resources_at |
20 |
Resource icons per row before wrapping. Keep this equal to wrap_queues_at so the icons line up with the entities using them. |
queue_direction |
"left" |
Which way queues (and rows of resources) build out from their anchor. "right" mirrors the layout so the anchor is the bottom-left corner — reads better with right-facing entity emojis. Set per event instead with a direction column on event_position_df. If using the step-by-step route, pass it to generate_animation_df and generate_animation. |
override_x_max |
None |
Force the x-axis upper bound. Only needed when the auto-sizing gets a layout wrong. |
override_y_max |
None |
Force the y-axis upper bound. |
Playback
| Argument | Default | Effect |
|---|---|---|
frame_duration |
400 |
Milliseconds each frame is held. |
frame_transition_duration |
600 |
Milliseconds entities take to glide to their next position. |
include_play_button |
True |
Show the play/pause control and timeline slider. |
These can also be changed after generating the figure — see the frame-speed feature breakdown.
Crowded steps
When more entities are at a step than will fit, vidigi shows a + n more label. These arguments control that fallback.
| Argument | Default | Effect |
|---|---|---|
step_snapshot_max |
60 |
Maximum entity icons drawn per step before the + n more label appears (animate_activity_log only). |
step_snapshot_max_overrides |
None |
A {event: cap} dict setting a per-event step_snapshot_max — e.g. {"waiting_for_bed": 250} to show one long bottleneck queue in full while every other step stays capped at step_snapshot_max (animate_activity_log only). |
step_snapshot_limit_gauges |
False |
Replace the + n more text with a segmented gauge (animate_activity_log only). |
gauge_segments |
10 |
Number of segments in that gauge — higher is finer-grained. |
gauge_max_override |
None |
Fix the gauge’s upper bound instead of taking it from the busiest observed step. |
See the gauge-only animations example.
Where entities enter and leave
An exit looks deliberate: give event_position_df a depart row and every entity is seen to move to that point before it disappears. Arrivals now behave the same way — a new entity glides in from the arrival anchor rather than flying in from the plot’s top-left corner (a Plotly quirk: a point new to a frame has no previous position to move from).
| Argument | Default | Effect |
|---|---|---|
spawn_in_from_arrival |
True |
Slide a new entity in from the EventPosition(event="arrival", …) anchor — the arrival-side mirror of depart. Put that anchor off the edge of the plot for a “fly in from off-screen” look, or on a visible entrance. Pass False for the old top-left fly-in. |
The anchor is the "arrival" row you almost certainly already have; a layout that never positioned "arrival" is unaffected. Only entities that arrive at least two snapshots (2 × every_x_time_units) after the animation starts glide in — an entity already in the system when the animation opens has no earlier frame to enter from and still flies in from the corner. This is independent of step_snapshot_reveal_pop_in, which does the same job for entities re-emerging from behind a + n more label.
Setup mode
| Argument | Default | Effect |
|---|---|---|
setup_mode |
False |
Show the axes, gridlines and tick marks. Useful while you are working out the x / y coordinates for event_position_df; turn it off for the finished animation. |
Time display
How the timeline is labelled — clock times, dates, simulation-relative days — is controlled by time_display_units, start_date and start_time. That is covered separately in the changing simulation time example.