Visualizations¶
Requires the [plot] extra
These helpers import matplotlib at module top, so they are not part of
import mesoltm. Install with pip install "mesoltm[plot]" and import from
mesoltm.visualizations. MP4 export additionally needs ffmpeg on the PATH
(GIF falls back to Pillow).
See Visualizations and Movement animations for usage.
Plots¶
Most plots are re-exported from mesoltm.visualizations. Two —
plot_travel_time_distribution and plot_link_travel_times — are currently only
importable from the mesoltm.visualizations.plots submodule (noted below).
plot_cumulative_curves
¶
plot_cumulative_curves(sim: Simulation, link_ids: Sequence[int] | None = None, ax: Axes | None = None) -> Axes
Plot cumulative inflow (solid) and outflow (dashed) for links over time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sim
|
Simulation
|
A run :class: |
required |
link_ids
|
Sequence[int] | None
|
Link ids to plot; defaults to all links in the simulation. |
None
|
ax
|
Axes | None
|
Optional matplotlib axes to draw on; a new one is created if omitted. |
None
|
Returns:
| Type | Description |
|---|---|
Axes
|
The matplotlib axes containing the plot. |
plot_link_flow
¶
plot_link_flow(sim: Simulation, link_ids: Iterable[int], window: int = 60, window_seconds: float | None = None, ax: Axes | None = None) -> Axes
Plot the flow (veh/h) through one or a collection of links over time.
When several link ids are given their flows are summed, so this doubles as a "flow across a cut" plot (e.g. total flow leaving a region).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sim
|
Simulation
|
A run simulation. |
required |
link_ids
|
Iterable[int]
|
One or more link ids whose outflow is aggregated. |
required |
window
|
int
|
Averaging/sampling window in steps for the flow rate. |
60
|
window_seconds
|
float | None
|
Same window expressed in seconds; when given it takes
precedence over |
None
|
ax
|
Axes | None
|
Optional axes; created if omitted. |
None
|
Returns:
| Type | Description |
|---|---|
Axes
|
The matplotlib axes containing the plot. |
plot_link_flows
¶
plot_link_flows(sim: Simulation, link_ids: Iterable[int], labels: Sequence[str] | None = None, window: int = 60, window_seconds: float | None = None, ax: Axes | None = None) -> Axes
Plot flow (veh/h) over time for each link as its own labelled line.
Unlike :func:plot_link_flow (which sums links into one "cut" curve), this
draws one line per link so it is clear which link carries which flow —
handy for small scenarios (e.g. a freeway merge, parallel lanes).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sim
|
Simulation
|
A run simulation. |
required |
link_ids
|
Iterable[int]
|
The links to plot, one line each. |
required |
labels
|
Sequence[str] | None
|
Optional legend labels, one per link id (defaults to |
None
|
window
|
int
|
Averaging/sampling window in steps for the flow rate. |
60
|
window_seconds
|
float | None
|
Same window expressed in seconds; when given it takes
precedence over |
None
|
ax
|
Axes | None
|
Optional axes; created if omitted. |
None
|
Returns:
| Type | Description |
|---|---|
Axes
|
The matplotlib axes containing the plot. |
plot_link_time_series
¶
plot_link_time_series(sim: Simulation, link_ids: Sequence[int], labels: Sequence[str] | None = None, window: int = 5, axes=None)
One subplot per link: the travel time experienced on it, over time.
Each vehicle that crossed a link contributes one point (time it entered the
link, time it took to cross). Points are sorted by entry time and smoothed
with a trailing moving average, so a link where a queue builds up shows its
travel time climbing over the run — the formation of congestion made
visible per link.
Samples are taken from completed vehicles' recorded trajectories (see
:attr:mesoltm.core.vehicle.Vehicle.trajectory).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sim
|
Simulation
|
A run simulation. |
required |
link_ids
|
Sequence[int]
|
The links to plot, one subplot each (e.g. four links -> four side-by-side panels). |
required |
labels
|
Sequence[str] | None
|
Optional per-link subplot titles (defaults to |
None
|
window
|
int
|
Number of samples in the trailing moving average. |
5
|
axes
|
Optional sequence of axes (one per link); a new row of subplots is created if omitted. |
None
|
Returns:
| Type | Description |
|---|---|
|
The sequence of axes, one per link. |
plot_network
¶
plot_network(state: NetworkState, color_by: str = 'occupancy', ax: Axes | None = None, node_size: float = 260.0, annotate_links: bool = False) -> Axes
Draw the network with links coloured by a per-link quantity.
Uses the same drawing helpers as the animation (:mod:._draw), so the
static map and the video render identical arcs and node markers — only the link
colour differs (here it encodes the chosen quantity). Requires node positions
(set when building the network). Connector links are omitted; only real links are
drawn. Links that share an edge — true parallel links and the two directions of
a bidirectional edge (A->B and B->A) — are fanned onto separate arcs so
their arrows never overlap; a lone link is drawn straight. Works for arbitrary
node placements (not just grid-aligned) and any number of links between a pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
NetworkState
|
A :class: |
required |
color_by
|
str
|
Link quantity to colour by: |
'occupancy'
|
ax
|
Axes | None
|
Optional axes; created if omitted. |
None
|
node_size
|
float
|
Node marker area (matplotlib |
260.0
|
annotate_links
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
Axes
|
The matplotlib axes containing the plot. |
plot_travel_time_distribution
¶
plot_travel_time_distribution(trips: Sequence[dict], ax: Axes | None = None, bins: int = 20) -> Axes
Plot the distribution of per-vehicle overall travel times.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trips
|
Sequence[dict]
|
Trip records from :func: |
required |
ax
|
Axes | None
|
Optional axes; created if omitted. |
None
|
bins
|
int
|
Number of histogram bins. |
20
|
Returns:
| Type | Description |
|---|---|
Axes
|
The matplotlib axes containing the plot. |
plot_link_travel_times
¶
Plot the mean travel time on each link, aggregated over all trips.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trips
|
Sequence[dict]
|
Trip records from :func: |
required |
ax
|
Axes | None
|
Optional axes; created if omitted. |
None
|
Returns:
| Type | Description |
|---|---|
Axes
|
The matplotlib axes containing the plot. |
Animation¶
Render a recorded SimulationHistory to a video or per-step
frames. The high-level entry points are animate_history,
animate_from_history_file, and animate_simulation.
animate_simulation
¶
animate_simulation(sim: Simulation, out_path: str, *, classify: ClassifyFn | None = None, **kwargs) -> str
Run a simulation with history logging and render it in one call.
Convenience for the batch case (no custom stepping): enables recording, runs
sim and renders the result. For a custom step loop that injects vehicles,
compile with record_history=True and render sim.history yourself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sim
|
Simulation
|
A compiled simulation. |
required |
out_path
|
str
|
Output video path. |
required |
classify
|
ClassifyFn | None
|
Optional per-agent category classifier. |
None
|
**kwargs
|
Forwarded to :func: |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
The path the video was written to. |
animate_history
¶
animate_history(history: SimulationHistory, out_path: str, **kwargs) -> str
Render a recorded :class:SimulationHistory to a video (builds the layout).
animate_from_history_file
¶
Load a saved history file and render it, guiding the user if it is missing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
history_path
|
str
|
Path to a JSON history written by
:meth: |
required |
out_path
|
str
|
Output video path. |
required |
**kwargs
|
Forwarded to :func: |
{}
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
save_animation
¶
save_animation(frames: Sequence[Frame], layout: NetworkLayout, out_path: str, *, fps: int = 25, subsample: float = 1.0, dpi: int = 150, palette: dict[str, str] | None = None, frames_dir: str | None = None, **render_kw) -> str
Render the frames to a video (MP4 or GIF) and return the output path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frames
|
Sequence[Frame]
|
The recorded frames (a list of :class: |
required |
layout
|
NetworkLayout
|
The :class: |
required |
out_path
|
str
|
Output file; |
required |
fps
|
int
|
Frames per second (default 25). |
25
|
subsample
|
float
|
Frames shown per simulation step — |
1.0
|
dpi
|
int
|
Output resolution. |
150
|
palette
|
dict[str, str] | None
|
Optional fixed |
None
|
frames_dir
|
str | None
|
If given, also write the individual per-step PNGs there — so one call can store both the video and the separate pictures. When omitted, only the video is written. |
None
|
**render_kw
|
Forwarded to :func: |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
The path the video was written to ( |
save_frames
¶
save_frames(frames: Sequence[Frame], layout: NetworkLayout, out_dir: str, *, stride: int = 1, dpi: int = 150, prefix: str = 'frame_', palette: dict[str, str] | None = None, **render_kw) -> list[str]
Save individual per-step PNGs (the "separate pictures" option).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frames
|
Sequence[Frame]
|
The recorded frames (a list of :class: |
required |
layout
|
NetworkLayout
|
The :class: |
required |
out_dir
|
str
|
Directory to write PNGs into (created if missing). |
required |
stride
|
int
|
Write every |
1
|
dpi
|
int
|
Output resolution. |
150
|
prefix
|
str
|
Filename prefix; files are |
'frame_'
|
palette
|
dict[str, str] | None
|
Optional fixed |
None
|
**render_kw
|
Forwarded to :func: |
{}
|
Returns:
| Type | Description |
|---|---|
list[str]
|
The list of written file paths. |
render_frame
¶
render_frame(frame: Frame, layout: NetworkLayout, ax: Axes | None = None, *, palette: dict[str, str] | None = None, color_by: str | Callable[..., str] | None = DEFAULT_COLOR_BY, detail: str = 'auto', show_agent_ids: bool | None = None, show_next_link: bool | None = None, show_node_labels: bool | None = None, max_slots: int = 8, highlight_links: Iterable[int] | None = None, title: str | None = None) -> Axes
Draw a single recorded frame onto ax (created if omitted).
Marker, node and label sizes scale to the network so the frame stays readable
from a small bottleneck to a large dense grid. How much per-agent annotation is
drawn is controlled by detail (with per-annotation overrides), so dense
scenarios can stay clean while the extra detail remains available on request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
frame
|
Frame
|
The :class: |
required |
layout
|
NetworkLayout
|
The :class: |
required |
ax
|
Axes | None
|
Optional axes; a new figure/axes is created if omitted. |
None
|
palette
|
dict[str, str] | None
|
|
None
|
color_by
|
str | Callable[..., str] | None
|
What the agent dot colour encodes. |
DEFAULT_COLOR_BY
|
detail
|
str
|
|
'auto'
|
show_agent_ids
|
bool | None
|
Override — draw the id inside each agent marker. |
None
|
show_next_link
|
bool | None
|
Override — draw each agent's next-link intention arrow + label. |
None
|
show_node_labels
|
bool | None
|
Override — draw node id labels. |
None
|
max_slots
|
int
|
Maximum FIFO slots drawn per link; longer queues show a |
8
|
highlight_links
|
Iterable[int] | None
|
Optional link ids drawn dashed-red (e.g. a bottleneck). |
None
|
title
|
str | None
|
Optional title drawn on the axes. |
None
|
Returns:
| Type | Description |
|---|---|
Axes
|
The matplotlib axes containing the drawing. |
NetworkLayout
¶
NetworkLayout(positions: dict[str, tuple[float, float]], labels: dict[str, str], links: dict[str, tuple[tuple[float, float], tuple[float, float], float]], xlim: tuple[float, float], ylim: tuple[float, float], scale: float, min_link: float)
Precomputed drawing geometry for a network (positions, link curves, limits).
Built once and reused for every frame so the video does not jitter and the
per-frame cost stays low. Create it with :meth:from_state (live simulation)
or :meth:from_history (a loaded history file).
Attributes:
| Name | Type | Description |
|---|---|---|
positions |
|
|
labels |
|
|
links |
|
|
xlim |
/ ylim
|
fixed axis limits including a margin. |
scale |
A characteristic length used to size markers/offsets. |
|
min_link |
The shortest drawn link length (data units) — sets how big a marker can be before agents on a link overlap, so markers/labels shrink automatically on dense networks (e.g. a large grid). |
from_state
classmethod
¶
from_state(state: NetworkState) -> NetworkLayout
Build a layout from a live compiled :class:NetworkState.
from_history
classmethod
¶
from_history(history: SimulationHistory) -> NetworkLayout
Build a layout from a recorded :class:SimulationHistory.
resolve_palette
¶
resolve_palette(frames: Sequence[Frame], palette: dict[str, str] | None = None, color_by: str | Callable[..., str] | None = DEFAULT_COLOR_BY) -> dict[str, str]
Return a stable category -> colour map for a sequence of frames.
Categories keep any colours given in palette; the rest are assigned from
the Okabe-Ito palette in order of first appearance across all frames, so
agent colours are stable over the whole animation (no per-frame flicker). With
color_by="next_link" the categories are next-link ids (the palette cycles
when there are more than eight).
expand_frame_indices
¶
Map output-frame index -> source-frame index for a playback subsample.
subsample is frames-shown-per-step: >1 repeats each step's frame (a
slower video), <1 drops steps (a faster video), 1 is one-to-one.