Feature Example: Flipping entity icons in place

Warning

This example has not yet been updated to reflect all of the new features and recommendations from vidigi 2.0.0, which have further simplified the process of adding vidigi to your model and accessing and modifying your animation.

For now, all of the code below will still work, but check out the getting started page for a full guide to the recommended way to use vidigi 2.0.0.

feat_queue_direction covers moving the layout so a right-facing icon reads correctly: the anchor moves from the bottom-right corner of a queue to the bottom-left, and the whole queue - wrapped rows, resource dots and all - mirrors along with it.

That’s the right fix when the layout is yours to choose. It isn’t always available - a background image or floor plan can fix a resource on a particular side, or a queue’s x/y might already be pinned by something else in the figure. flip_entity_icons solves the same visual problem a different way: it mirrors the icon glyph itself, leaving every coordinate - and queue_direction - exactly where you put it. The two are independent and can be combined.

Model setup

The same single-step clinic model feat_queue_direction uses - patients arrive, queue for a treatment cubicle, are treated, and leave. The treatment cubicles sit to the left of the queue (x=250, against the queue’s anchor at x=450), so a patient needs to walk left to reach one.

import random

import plotly.io as pio
from flip_entity_icons_model import Model, g

from vidigi.animation import animate_activity_log
from vidigi.utils import EventPosition, create_event_position_df

pio.renderers.default = "notebook"

model = Model(run_number=1)
event_log = model.run()["event_log"]
event_log.head()
patient pathway event_type event time resource_id run
0 1 Simplest arrival_departure arrival 0.000000 NaN 1
1 1 Simplest queue treatment_wait_begins 0.000000 NaN 1
2 1 Simplest resource_use treatment_begins 0.000000 1.0 1
3 2 Simplest arrival_departure arrival 12.021043 NaN 1
4 2 Simplest queue treatment_wait_begins 12.021043 NaN 1
icons = [
    "🚶‍♀️",
    "🚶🏻‍♂️",
    "🚶🏼‍♂️",
    "🚶🏽‍♂️",
    "🚶🏽‍♂️",
    "🚶🏿‍♂️",
    "🚶🏿‍♀️",
    "🚶🏾‍♀️",
    "🚶🏽‍♀️",
    "🚶🏼‍♀️",
    "🚶🏻‍♀️",
]
random.shuffle(icons)

The default layout, with icons facing the wrong way

queue_direction stays at its default of "left" - nothing about the layout is wrong here. The front of the queue sits at the anchor (x=450) and the treatment cubicles are exactly where the patients need to go next, at x=250. What’s wrong is the icon: these walking emoji face right, so every patient in the queue looks like they’re walking away from the cubicles they’re waiting for.

event_position_df = create_event_position_df(
    [
        EventPosition(event="arrival", x=50, y=300, label="Arrival"),
        EventPosition(
            event="treatment_wait_begins", x=450, y=275, label="Waiting for Treatment"
        ),
        EventPosition(
            event="treatment_begins",
            x=250,
            y=175,
            resource="n_cubicles",
            label="Being Treated",
        ),
        EventPosition(event="depart", x=170, y=70, label="Exit"),
    ]
)

animate_activity_log(
    event_log=event_log,
    event_position_df=event_position_df,
    entity_col_name="patient",
    scenario=g(),
    every_x_time_units=10,
    limit_duration=200,
    wrap_queues_at=15,
    gap_between_entities=20,
    gap_between_resources=20,
    frame_duration=1000,
    plotly_height=500,
    plotly_width=1200,
    custom_entity_icon_list=icons,
)

Mirroring the icon instead of the layout: flip_entity_icons=True

One extra argument, and every coordinate above is untouched - queue_direction is still "left", every x and y is exactly as given. flip_entity_icons=True mirrors every entity icon (and a custom_resource_icon, if one is set) horizontally, so the same right-facing emoji now walks towards the cubicles instead of away from them.

animate_activity_log(
    event_log=event_log,
    event_position_df=event_position_df,
    entity_col_name="patient",
    scenario=g(),
    every_x_time_units=10,
    limit_duration=200,
    wrap_queues_at=15,
    gap_between_entities=20,
    gap_between_resources=20,
    frame_duration=1000,
    plotly_height=500,
    plotly_width=1200,
    custom_entity_icon_list=icons,
    flip_entity_icons=True,
)

One stage at a time: the per-event flip_icons column

flip_entity_icons sets the animation-wide default; any EventPosition with its own flip_icons overrides it - useful when only one stage’s icon reads the wrong way. Here the animation-wide default is left at False, but the queue and the treatment cubicles are told to flip; arrival and departure keep the default look, since a single icon standing still doesn’t have the “which way am I walking” problem a queue or a row of resources does. A custom_resource_icon follows the same per-event setting, so the bed icon mirrors alongside the patients using it.

mixed_position_df = create_event_position_df(
    [
        EventPosition(event="arrival", x=50, y=300, label="Arrival"),
        EventPosition(
            event="treatment_wait_begins",
            x=450,
            y=275,
            label="Waiting for Treatment",
            flip_icons=True,
        ),
        EventPosition(
            event="treatment_begins",
            x=250,
            y=175,
            resource="n_cubicles",
            label="Being Treated",
            flip_icons=False,
        ),
        EventPosition(event="depart", x=170, y=70, label="Exit"),
    ]
)

mixed_position_df
event x y label resource direction flip_icons resource_icon
0 arrival 50 300 Arrival NaN None None None
1 treatment_wait_begins 450 275 Waiting for Treatment NaN None True None
2 treatment_begins 250 175 Being Treated n_cubicles None False None
3 depart 170 70 Exit NaN None None None
animate_activity_log(
    event_log=event_log,
    event_position_df=mixed_position_df,
    entity_col_name="patient",
    scenario=g(),
    every_x_time_units=10,
    limit_duration=200,
    wrap_queues_at=15,
    gap_between_entities=20,
    gap_between_resources=20,
    frame_duration=1000,
    plotly_height=500,
    plotly_width=1200,
    custom_entity_icon_list=icons,
    custom_resource_icon="🛌",
)

Getting the CSS to a page that isn’t a live notebook

Flipping is a CSS mirror applied to the icon’s text, not a different icon, so it needs a small <style> block to reach the page. In a notebook or Streamlit app this happens automatically - vidigi injects it (via IPython.display above, or streamlit.markdown in a Streamlit app) whenever any icon actually resolves to flipped, which is why nothing extra was needed for the two animations above.

Embedding a figure a different way - fig.write_html(), or a hand-built page - needs the CSS 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"))

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 - not a limitation specific to vidigi, since fig.write_image() can’t animate frames at all. See Customising the animation for more on both routes.

Notes

  • If you use the three-step pipeline (reshape_for_animations → generate_animation_df → generate_animation) rather than animate_activity_log, flip_entity_icons only needs to go to generate_animation - unlike queue_direction, it doesn’t change any position, so generate_animation_df has no use for it.
  • The + n more / ASCII-gauge overflow icon is never flipped, even when the whole animation is - mirrored text is unreadable, and the gauge string embeds the entity icon mid-string.
  • feat_queue_direction covers mirroring the layout instead of the icon.
  • v2_release_additions tours the rest of what shipped in 2.0.0.
Back to top