activitysim_visualizer is a Panel-based dashboard for exploring and comparing ActivitySim outputs. It can:
- compare multiple model runs side by side
- build and reuse prepared and summary caches
- serve a live local dashboard
- export a standalone HTML version for offline sharing
Install dependencies with uv:
uv sync --lockedIf uv sync fails because of a hardlink issue, retry with:
uv sync --locked --link-mode=copyActivate the environment:
.\.venv\Scripts\activateCreate a project-specific config:
Copy-Item config.yaml local_config.yamlEdit local_config.yaml, then run the app with that config:
python run.py --config local_config.yamlBy default, python run.py follows pipeline.steps from the loaded config when no explicit step flags are supplied. The shipped example config defaults to summarize + dashboard, so a normal run will reuse summary caches when possible, rebuild them when needed, and then start the live dashboard on http://localhost:5006.
Dashboard pages now use one shared authoring model:
- page modules export
PAGE = DashboardPageDefinition(...) - page classes subclass
DashboardPage - page-local controls are registered with
selector(...) - refreshable regions are registered with
section(...) - live refresh and export metadata both derive from those registrations
The main shared page-helper modules live under dashboard/helpers/:
category_helpers.pygeography_helpers.pyperson_type_helpers.pytime_distance_helpers.pycomparison_helpers.py
If you are adding or refactoring a page, read docs/adding-dashboard-pages.md first. For the broader runtime picture, see docs/architecture.md.
The repo ships with config.yaml as a template. In practice, most people should:
- Copy
config.yamltolocal_config.yamlor another machine-specific file. - Update the
runssection to point at real ActivitySim output folders. - Update
skimjoin.distance_skim,zones, andfilesif your model layout differs from the defaults. - Run with
--config your_file.yaml.
The canonical config layout is organized around a few top-level sections:
root: artifacts/
log_level: INFO
pipeline:
steps: [summarize, dashboard]
dashboard_mode: live
overwrite: false
prepare: ...
summarize: ...
segment: ...
dashboard: ...
display: ...
skimjoin: ...Older keys such as processor.*, summaries.*, visualizer.*, top-level
dashboard_labels, and top-level run_colors are still supported for
compatibility, but they now emit deprecation warnings and normalize into the
canonical schema above.
The minimum useful config is usually:
root: artifacts/summary_cache
pipeline:
steps:
- summarize
- dashboard
dashboard_mode: live
runs:
- dir: path\to\run1
label: Base
- dir: path\to\run2
label: Build
skimjoin:
distance_skim:
file: path\to\skims.omx
matrix: SOV_DIST__MD
zones:
use_maz: false
maz_col: zone_id
taz_col: TAZ
files:
households: final_households
persons: final_persons
tours: final_tours
trips: final_trips
joint_tour_participants: final_joint_tour_participants
land_use: final_land_useIf runs use different raw filenames, keep files: as the default mapping and
override only the differences inside runs[*].file_map:
files:
households: final_households
persons: final_persons
tours: final_tours
trips: final_trips
runs:
- dir: path\to\run1
label: Base
file_map:
households: final_hh
trips: trip_linked
- dir: path\to\run2
label: Build
file_map:
households: household
persons: person
tours: tour
trips: tripIf a run should skip raw prepare and use externally managed canonical prepared
tables instead, point it at those files with runs[*].prepared_table_map:
runs:
- dir: path\to\raw_run
label: Raw Run
- label: Custom Prepared Run
prepared_table_map:
households: path\to\custom\households.parquet
persons: path\to\custom\persons.csv
tours: path\to\custom\tours.parquet
trips: path\to\custom\trips.csv
joint_tour_participants: path\to\custom\joint_tour_participants.parquet
land_use: path\to\custom\land_use.csvprepared_table_map is intended for canonical prepared tables that were already
skimjoined and then optionally filtered or otherwise post-processed outside this
repo. When a run uses prepared_table_map, the workflow loads those prepared
tables directly and does not rerun raw prepare or integrated skimjoin for that run.
Integrated skim enrichment can now be selected per run without forcing one shared skimjoin config for every skim structure. Keep the explicit skimjoin YAML logic in separate files, then choose the file and optional project-input overrides per run:
skimjoin:
defaults:
config_path: configs/skimjoin_default.yaml
runs:
- dir: path\to\run_a
label: Run A
skimjoin:
config_path: configs/skimjoin_odot_series15.yaml
skim_files:
- path\to\run_a\skims\*.omx
- path\to\run_a\skims\maz_stop_walk.csv
network_los_file: path\to\run_a\network_los.yaml
- dir: path\to\run_b
label: Run B
skimjoin:
config_path: configs/skimjoin_combined_walk.yaml
skim_files:
- path\to\run_b\skims\*.omxSkimjoin override rules:
runs[*].skimjoin.config_pathoverrides globalskimjoin.config_path.runs[*].skimjoin.skim_filesoverrides the selected skimjoin config'sproject.skim_files.runs[*].skimjoin.network_los_fileoverrides the selected skimjoin config'sproject.network_los_file.- If a run omits
runs[*].skimjoin, it uses the global skimjoin settings exactly as before.
Recommended rule of thumb:
- If runs differ only by skim file locations, share one skimjoin config and override
runs[*].skimjoin.skim_files. - If runs differ only by period definitions, share one skimjoin config and override
runs[*].skimjoin.network_los_file. - If runs differ by lookup logic, fallback behavior, combined vs split components, or directional semantics, use different skimjoin config files.
VOT bin preparation stays in prepare.vot_bins and remains run-aware by run label.
Skimjoin dimensions are now standardized under dimensions, while
activitysim only carries the structural trip/tour fields. The recommended
integrated-runtime pattern is:
activitysim:
trip_mode_column: trip_mode
trip_id_column: trip_id
tour_mode_column: tour_mode
tour_id_column: tour_id
outbound_column: outbound
dimensions:
PERIOD:
source_columns:
trip_source_column: depart_hour
outbound_tour_source_column: start_hour
inbound_tour_source_column: first_inbound_trip_depart
values_from_network_los: true
values:
8: AM
17: PM
VOT:
source_columns:
trip_source_column: vot_bin
outbound_tour_source_column: vot_bin
inbound_tour_source_column: vot_bin
values:
L: L
M: M
H: HPeriod behavior is directional by design:
- trips use
dimensions.PERIOD.source_columns.trip_source_column - outbound tours use
dimensions.PERIOD.source_columns.outbound_tour_source_column - inbound tours use
dimensions.PERIOD.source_columns.inbound_tour_source_column
In the standard prepare workflow, first_inbound_trip_depart is derived from
the first inbound trip on each tour before integrated skimjoin runs.
Prepared endpoint columns are also standardized before skimjoin runs:
- prepared trips and tours always include
OTAZandDTAZ - when
zones.use_maz: true, prepare also materializeso_mazandd_maz - inbound tour lookups reuse those same column names, while skimjoin swaps their logical direction in the inbound tour context
The normal prepare step can also write prepared caches as CSV when needed:
prepare:
output:
file_format: csv
validation:
relationship_checks: warnImportant path rules:
rootis resolved relative to the config file if you give a relative path.- The prepared cache is created automatically next to
rootasprepared_cache/. runs[*].dirshould point at an ActivitySim output directory.skimjoin.distance_skim.filemay be absolute, or relative to each run directory.- File entries under
filescan be bare stems likefinal_tripsor explicit filenames likefinal_trips.csv. runs[*].file_mapuses the same filename rules asfiles, but applies only to that run.runs[*].prepared_table_mapmust use explicit.parquetor.csvpaths and resolves relative paths from the config file directory.prepare.output.file_formatcontrols how standard prepared caches are written; supported values areparquetandcsv, withparquetas the default.prepare.validation.relationship_checkscontrols prepared-table foreign-key validation. Usewarnto log inconsistencies and continue,errorto fail the run, oroffto skip the checks.dashboard.export.output_path, when relative, is resolved fromroot.
These are the sections most people need to touch:
| Section | Purpose |
|---|---|
root |
Where summary caches are stored |
pipeline |
Default workflow steps, dashboard mode, and overwrite behavior |
runs |
Run directories, display labels, and optional per-run skim, raw file-map, custom prepared-table map, and weight overrides |
skimjoin.distance_skim |
Default distance skim file and matrix name used by summaries |
zones |
MAZ/TAZ settings for skim joins and zone normalization |
files |
Default ActivitySim output file stems or filenames used unless a run overrides them |
columns |
Column aliases when outputs use non-default names |
prepare.output.file_format |
On-disk format for prepared caches written by the normal prepare workflow |
prepare.validation.relationship_checks |
Whether cross-table prepared-key validation is disabled, warns, or errors |
dashboard.title |
Title used in the live dashboard and HTML export |
dashboard.live.pages |
Ordered list of live pages/groups to show |
dashboard.export |
Export-only output path, page selection, and selector-state controls |
display.run_colors |
Plot colors by run |
display.labels |
Presentation-only labels and ordering for dashboard/export |
summarize.weighting_modes |
Which cache variants to build: weighted, unweighted, or both |
summarize.geography |
Optional district/county/zone grouping |
summarize.pnr_tour_modes |
Which tour modes count as park-and-ride in summary builders |
summarize.group_*_tour_purposes |
Summary-time purpose regrouping switches |
summary_categories |
Summary-affecting category normalization/regrouping |
modes |
Optional mode ordering and grouped mode display |
person_types |
Optional display labels for ptype values |
student_types |
Optional school/university enrollment definitions for shadow pricing pages |
Weighting rules:
- If a run sets
hh_weight_col,person_weight_col, ortrip_weight_col, those are used. - Otherwise, if a
sample_ratecolumn is available, weights are derived from it. - Otherwise, weights default to
1.
Legacy config notes:
- Prefer the canonical top-level schema:
root,pipeline,dashboard,display,summarize,segment, andskimjoin. - Older keys such as
processor.root,summaries.weighting_modes,visualizer.dashboard_pages, and top-levelrun_colorsare still supported for compatibility, but now log deprecation warnings.
Geography note:
geography.enabled: falsenow disables both the older geography mapping behavior and the newergeography.aggregationsderived columns. If you want aggregation-based geography summaries,geography.enabledmust betrue.
Category config note:
- Use
summary_categorieswhen a mapping changes summary values, grouping membership, or canonical category values. - Use
display.labelswhen a change is cosmetic and should only affect dashboard/export labels or ordering.
dashboard.live.pages controls the live dashboard only. dashboard.export controls what goes into the standalone HTML export.
Current top-level page ids are:
overviewlong_term_choicesdaily_traveljoint_traveltour_summariestrip_summariesvalidationraw_trip_demo
Grouped page ids support either the whole group or specific child pages. For example:
dashboard:
live:
pages:
- overview
- long_term_choices:
- individual_choices
- mandatory_location_choice
- shadow_pricing
- daily_travel: default
- tour_summaries: all
- trip_summaries:
- trip_mode
- trip_stop_timeNotes:
defaultmeans "the group's default enabled children".allmeans every child page in the group.- A plain group id like
tour_summariesbehaves like the group's default selection. raw_trip_demois disabled by default and requests prepared trip tables, so keep it out unless you explicitly want that behavior.
For HTML export, you can further narrow the exported page set and selector states:
dashboard:
export:
dashboard:
weighting: [unweighted]
values: [percent]
exclude_groups: [validation]
pages:
long_term_choices:
shadow_pricing:
geography_level: [all]
student_type: [all]
parts:
workplace_table:
enabled: false
school_table:
enabled: falseRules worth remembering:
- If
dashboard.live.pagesis omitted, the app uses its built-in default page set. - If
dashboard.export.pagesis omitted, export mirrors the live page set. - Export selector requests accept
default,all, or a list of explicit values. dashboard.export.exclude_pagesandexclude_groupsremove pages from export without changing the live dashboard.
The CLI exposes three workflow steps:
preparesummarizedashboard
Common commands:
| Command | What it does |
|---|---|
python run.py --config local_config.yaml |
Reuse or build summaries, then start the live dashboard |
python run.py --config local_config.yaml --prepare-only |
Build prepared caches and exit |
python run.py --config local_config.yaml --summarize |
Reuse or build summary caches and exit |
python run.py --config local_config.yaml --summarize --dashboard |
Explicit form of the default live workflow |
python run.py --config local_config.yaml --dashboard |
Start the dashboard from existing summary caches for the configured runs |
python run.py --config local_config.yaml --prepare --summarize --dashboard |
Force the full prepare -> summarize -> dashboard chain in one run |
python run.py --config local_config.yaml --from-csvs |
Start the dashboard from existing summary caches only |
python run.py --config local_config.yaml --from-csvs --export-html output.html |
Build a standalone HTML export from existing summary caches |
python run.py --config local_config.yaml --summarize --write-csvs |
Rebuild summaries and write fresh cache files |
python run.py --config local_config.yaml --summarize --skip-summary-cache-write |
Build summaries for this run without writing cache updates |
python run.py --config local_config.yaml --summarize --refresh-summary-cache |
Delete and rebuild summary caches for the selected runs |
python run.py --config local_config.yaml --summarize --refresh-prepared-cache |
Rebuild summaries from freshly prepared tables instead of prepared-cache hits |
python run.py --config local_config.yaml --prepare --summarize --refresh-caches |
Delete and rebuild both prepared and summary caches for the selected runs |
Behavior details:
--from-csvsis cache-only: it will not rebuild missing summaries.--from-csvs path\to\cache1 path\to\cache2lets you point directly at specific summary cache directories.--dashboardby itself is valid when summary caches already exist for the configured runs.- During summarize, the app will reuse prepared cache when possible and rebuild from raw outputs only when needed.
--refresh-prepared-cachedeletes the selected runs' prepared-cache directories first, then disables prepared-cache reuse for that invocation.--refresh-summary-cachedeletes the selected runs' summary-cache directories first, then disables summary-cache reuse for that invocation.--refresh-cachesis shorthand for both refresh flags together.
Prepared caches are written automatically next to the summary cache root:
<summary_root_parent>/
prepared_cache/
<run_key>/
manifest.json
households.parquet|csv
persons.parquet|csv
tours.parquet|csv
trips.parquet|csv
joint_tour_participants.parquet|csv
land_use.parquet|csv
Summary caches are written under root:
<summary_root>/
<run_key>/
manifest.json
weighted/
unweighted/
Both cache layers validate manifests before reuse. Cache invalidation is driven by:
- the run inputs
- the prepare and summary config digests
- the prepared-manifest identity used to build summary caches
- per-summary summary digests inside the summary-cache manifest
That means presentation-only config changes usually do not force summary rebuilds, and adding a newly requested summary can backfill just that table instead of rebuilding the entire summary bundle.
You can override runs on the command line instead of putting them in the config:
python run.py --config local_config.yaml ^
--run C:\path\to\run1 "Base" ^
--run C:\path\to\run2 "Build"Optional per-run skim overrides can be supplied in the same order:
python run.py --config local_config.yaml ^
--run C:\path\to\run1 "Base" ^
--run C:\path\to\run2 "Build" ^
--run-skim C:\path\to\base_skims.omx C:\path\to\build_skims.omxUse null, None, or an empty string in --run-skim to fall back to the configured skimjoin.distance_skim.file.
activitysim_visualizer/
|-- run.py
|-- runtime/
| |-- workflows/
|-- runtime/
| `-- config/
|-- processor/
| |-- prepare/
| |-- summarize/
| `-- models.py
|-- dashboard/
| |-- app.py
| |-- export/
| |-- page_base.py
| |-- page_definitions.py
| |-- page_registry.py
| |-- state.py
| `-- pages/
`-- tests/
Contributor-oriented docs live under docs/:
docs/architecture.mddocs/summary-workflow.mddocs/adding-summaries.mddocs/adding-dashboard-pages.mddocs/plotting-summary-tables.mddocs/export_html_schema.mddocs/export_html_contributor_guide.md
If you are new to the codebase, start with docs/architecture.md, then docs/summary-workflow.md.
When behavior changes, update docs in the same change:
- New config key or config behavior: update this README and any affected workflow guide.
- New summary contract or registration pattern: update
docs/adding-summaries.md. - New page, selector, or export behavior: update
docs/adding-dashboard-pages.md. - New export payload/runtime behavior: update
docs/export_html_schema.mdanddocs/export_html_contributor_guide.md. - Architecture or runtime-flow changes: update
docs/architecture.mdordocs/summary-workflow.md.