# Canopy Viewer Developer Guide

## Architecture Overview

Canopy Viewer is a NiceGUI application that provides a graphical interface for creating canopy figures. It does not generate figures itself; instead, it builds JSON configuration files from user input and delegates execution to the `canopy-tools` package (import name `canopy`), which performs the actual data loading and figure rendering.

The app presents a sidebar for field selection and figure options, and a main content area where generated figures are displayed.

## Architecture

`main.py` is the entry point. It composes the UI from `field_selector` (which uses `LocalFilePicker` for file selection) and `options_panel` (which uses `palette_picker` and `LocalFilePicker` for custom palettes). When the user clicks Run, `main.py` calls `json_builder.build_and_save_config` to produce a JSON config, then invokes the external `canopy` package to generate the figure. The canopy package reads the JSON, loads the data, and writes a PNG file that the app displays.

```
main.py
├── field_selector ──────► LocalFilePicker (data files)
├── options_panel ───────► palette_picker, LocalFilePicker (custom palette)
└── json_builder ────────► canopy package ──► PNG output
```

Data flows from the UI into a shared `state` dict (via NiceGUI's `bind_value`), then into `build_config`, which produces the JSON schema expected by canopy. The pipeline is: **User input → state dict → build_config → JSON file → canopy.run_json → PNG → display**.

## Module Reference

| File | Purpose |
|------|---------|
| [main.py](main.py) | Entry point. Defines layout, state dict, and Run handler. Uses `run.cpu_bound` to run figure generation in a separate process so the event loop stays responsive. |
| [components/field_selector.py](components/field_selector.py) | Renders field selection UI: list of file paths with Add/Remove buttons. Integrates `LocalFilePicker` for choosing data files. |
| [components/options_panel.py](components/options_panel.py) | Dynamic options panel. Figure-type toggle, preprocess options (grid type, slices, region, etc.), and figure-specific visual options (map, time_series, distribution, etc.). |
| [components/palette_picker.py](components/palette_picker.py) | Color palette dropdown with Color Brewer swatches (sequential, diverging, qualitative). Supports custom palette via file picker. |
| [components/local_file_picker.py](components/local_file_picker.py) | Local filesystem browser dialog. Used for selecting data files and custom palette files. |
| [utils/json_builder.py](utils/json_builder.py) | `build_config` and `build_and_save_config`. Maps UI state to the canopy JSON schema. Writes temp JSON and returns paths for execution. |

## Data Flow

1. **State dict** – A single `state` dict in `main.py` holds all option values (grid_type, file_format, slices, palette, figure-specific options, etc.). UI inputs use `bind_value(state, "key")` so changes update the dict automatically.

2. **Field selection** – The `fields` list holds file paths chosen via `field_selector`. When fields change, `on_fields_change` updates which figure types are enabled.

3. **Figure types and field counts** – Each figure type has min/max field requirements:
   - `map_simple`: 1 field
   - `map_diff`: 2 fields
   - `static`: 2 fields
   - `time_series`, `distribution`, `latitudinal_plot`: 1 or more fields

4. **Run flow** – On Run click:
   - `build_and_save_config(fields, figure, state)` produces a temp JSON file and output PNG path
   - `canopy.run_json(json_path)` is called via `run.cpu_bound` (separate process)
   - The resulting PNG path is displayed in the main content area

## Dependencies

- **pyproject.toml**: `nicegui`, `seaborn`, `matplotlib`, `pywebview`, `canopy-tools` (PyPI)
- **canopy-tools**: Published on PyPI; import name is `canopy`. Each canopy_viewer release pins a compatible range (e.g. `canopy-tools>=0.11.0,<0.12.0`).

### Local development

To test canopy_viewer against an editable local canopy package:

```bash
# In canopy_viewer directory
uv venv
source .venv/bin/activate
uv pip install -e . -e ../canopy   # editable install of both

# Run the app
canopy-viewer
```

## Key Design Decisions

- **`run.cpu_bound`** – Figure generation is CPU-bound. Running it in the main event loop would block the UI and cause reconnect popups. `run.cpu_bound` runs the work in a separate process, keeping the NiceGUI event loop responsive.

- **Visibility toggling vs refresh** – When switching figure types, options panels are toggled with `set_visibility` instead of destroying and recreating them. This avoids NiceGUI issue #5765 (Vue props undefined when refreshable destroys inputs in beforeUnmount).
