|
| 1 | +# ridgeplot Development Guide for AI Agents |
| 2 | + |
| 3 | +`ridgeplot` is a Python package that provides a simple interface for plotting beautiful and interactive ridgeline plots within the extensive Plotly ecosystem. |
| 4 | + |
| 5 | +This file provides guidance for AI assistants working with the ridgeplot codebase. |
| 6 | + |
| 7 | +## Public API Overview |
| 8 | + |
| 9 | +The library only exposes three public functions to end users. |
| 10 | + |
| 11 | +Two of these functions are toy dataset loaders (`ridgeplot.datasets.load_probly()` and |
| 12 | +`ridgeplot.datasets.load_lincoln_weather()`) but they are not the focus on the project and are only exposed for backwards compatibility reasons. |
| 13 | + |
| 14 | +The third function (`ridgeplot.ridgeplot()`) is a Plotly Figure ( |
| 15 | +`plotly.graph_objects.Figure`) factory function, and it's the focus of this project. See its docstring at |
| 16 | +`src/ridgeplot/_ridgeplot.py` for full details. |
| 17 | + |
| 18 | +## .cursor rules directory |
| 19 | + |
| 20 | +You can check |
| 21 | +`.cursor/rules` before providing recommendations or generating code. This directory contains the authoritative rules for all development aspects. |
| 22 | + |
| 23 | +To find the relevant rules: |
| 24 | + |
| 25 | +1. Find ALL .md files in `.cursor/rules` |
| 26 | +2. Read ALL relevant rules files, based on the filetypes/languages/libraries/tasks/folders involved (e.g., .tsx, .css, .java, React, |
| 27 | + `//web/src/pages/home`, etc) |
| 28 | +3. If unsure, read metadata (first 5 lines) of any .mdc file to check rule scope/description/keywords |
| 29 | + |
| 30 | +**For common tasks, you can check these specific files:** |
| 31 | + |
| 32 | +- Bazel/testing: `.cursor/rules/bazel-test-execution.mdc` |
| 33 | +- Java code: `.cursor/rules/java-conventions.mdc` |
| 34 | +- TypeScript/web: `.cursor/rules/web-conventions.mdc` |
| 35 | +- Pull requests: `.cursor/rules/pull-request-conventions.mdc` |
| 36 | + |
| 37 | +**Documentation:** https://ridgeplot.readthedocs.io/en/stable/ |
| 38 | + |
| 39 | +**Python Version**: Requires Python 3.10+ |
| 40 | + |
| 41 | +## Common Commands |
| 42 | + |
| 43 | +### Environment Setup |
| 44 | + |
| 45 | +```bash |
| 46 | +# Initialize full development environment (recommended for first-time setup) |
| 47 | +# This creates .venv, installs dependencies, and sets up pre-commit hooks |
| 48 | +make init |
| 49 | + |
| 50 | +# Use a different Python version if needed (default is python3.10) |
| 51 | +BASE_PYTHON=python3.14 make init |
| 52 | + |
| 53 | +# Offline mode (uses cached packages only) - useful when the internet is down |
| 54 | +OFFLINE=1 make init |
| 55 | + |
| 56 | +# Activate the virtual environment |
| 57 | +source .venv/bin/activate |
| 58 | +``` |
| 59 | + |
| 60 | +### Running Tests |
| 61 | + |
| 62 | +```bash |
| 63 | +# Run all test suits (unit + e2e + cicd_utils) |
| 64 | +tox -m tests |
| 65 | + |
| 66 | +# Run a specific test suite |
| 67 | +tox -e tests-unit # Unit tests with coverage |
| 68 | +tox -e tests-e2e # End-to-end tests |
| 69 | +tox -e tests-cicd_utils # CI/CD utilities tests |
| 70 | + |
| 71 | +# Run pytest directly with custom options |
| 72 | +tox -e pytest -- tests/unit/test_init.py --no-cov |
| 73 | +tox -e pytest -- -k "test_specific_function" --no-cov |
| 74 | +``` |
| 75 | + |
| 76 | +### Linting and Formatting |
| 77 | + |
| 78 | +```bash |
| 79 | +# Run the main static checks |
| 80 | +tox -m static-quick |
| 81 | + |
| 82 | +# Run the entire suite of static checks (incl. all pre-commit hooks) |
| 83 | +# If running from the main branch, you'll need |
| 84 | +# to skip the 'no-commit-to-branch' check with: |
| 85 | +SKIP='no-commit-to-branch' tox -m static |
| 86 | + |
| 87 | +# Run specific pre-commit hooks |
| 88 | +pre-commit run ruff --all-files |
| 89 | +pre-commit run ruff-format --all-files |
| 90 | + |
| 91 | +# Run type checking with pyright only |
| 92 | +tox -e typing |
| 93 | +``` |
| 94 | + |
| 95 | +### Documentation |
| 96 | + |
| 97 | +```bash |
| 98 | +# Build static documentation |
| 99 | +tox -e docs-static |
| 100 | +``` |
| 101 | + |
| 102 | +## Architecture Overview |
| 103 | + |
| 104 | +``` |
| 105 | +src/ridgeplot/ |
| 106 | +├── __init__.py # Public API exports |
| 107 | +├── _ridgeplot.py # Main ridgeplot() function |
| 108 | +├── _figure_factory.py # Plotly Figure construction |
| 109 | +├── _kde.py # Kernel Density Estimation |
| 110 | +├── _hist.py # Histogram binning |
| 111 | +├── _types.py # Type aliases and type guards |
| 112 | +├── _utils.py # Utility functions |
| 113 | +├── _missing.py # Sentinel for missing values |
| 114 | +├── _version.py # Version string (setuptools-scm) |
| 115 | +├── _color/ # Color handling |
| 116 | +│ ├── colorscale.py # Colorscale resolution |
| 117 | +│ ├── css_colors.py # CSS color parsing |
| 118 | +│ ├── interpolation.py # Color interpolation |
| 119 | +│ └── utils.py # Color utilities |
| 120 | +├── _obj/traces/ # Trace objects |
| 121 | +│ ├── area.py # Area trace (filled curves) |
| 122 | +│ ├── bar.py # Bar trace (histograms) |
| 123 | +│ └── base.py # Base trace class |
| 124 | +├── _vendor/ # Vendored dependencies |
| 125 | +└── datasets/ # Built-in datasets |
| 126 | + └── data/ # CSV data files |
| 127 | +
|
| 128 | +tests/ |
| 129 | +├── conftest.py # Shared pytest fixtures |
| 130 | +├── unit/ # Unit tests for individual modules |
| 131 | +├── e2e/ # End-to-end tests with expected outputs |
| 132 | +│ └── artifacts/ # JSON artifacts for e2e comparisons |
| 133 | +└── cicd_utils/ # Tests for CI/CD utilities |
| 134 | +
|
| 135 | +cicd_utils/ # CI/CD helper modules |
| 136 | +├── cicd/ # Scripts and test helpers |
| 137 | +└── ridgeplot_examples/ # Example implementations for docs/testing |
| 138 | +``` |
| 139 | + |
| 140 | +### Key Data Flow |
| 141 | + |
| 142 | +1. User calls `ridgeplot(samples=...)` or `ridgeplot(densities=...)` |
| 143 | +2. If samples provided → KDE (`_kde.py`) or histogram binning (`_hist.py`) produces densities |
| 144 | +3. Densities are normalised if `norm` parameter is set |
| 145 | +4. `create_ridgeplot()` in `_figure_factory.py` builds the Plotly Figure: |
| 146 | + - Resolves colorscale and computes colors for each trace |
| 147 | + - Creates trace objects (area or bar) for each density |
| 148 | + - Applies layout settings (spacing, padding, axes) |
| 149 | +5. Returns a `plotly.graph_objects.Figure` that users can further customise |
| 150 | + |
| 151 | +### Type System |
| 152 | + |
| 153 | +The codebase uses extensive type annotations. Key types in `_types.py`: |
| 154 | + |
| 155 | +- **Samples/Densities**: Ragged 3D arrays `[rows][traces_per_row][values]` |
| 156 | +- **ShallowSamples/ShallowDensities**: 2D arrays (one trace per row) |
| 157 | +- **ColorScale**: `Collection[tuple[float, Color]]` |
| 158 | +- **Type guards**: `is_shallow_samples()`, `is_densities()`, etc. |
| 159 | + |
| 160 | +## Code Style Guidelines |
| 161 | + |
| 162 | +### General |
| 163 | + |
| 164 | +- **Python version**: 3.10+ (uses modern typing features) |
| 165 | +- **Line length**: 100 characters |
| 166 | +- **Formatting**: Ruff (replaces black, isort, flake8) |
| 167 | +- **Imports**: Always include `from __future__ import annotations` |
| 168 | +- **Docstrings**: NumPy style |
| 169 | +- **Type annotations**: Use full and modern type annotations throughout with strict pyright checking |
| 170 | + |
| 171 | +### Type Annotations |
| 172 | + |
| 173 | +- All functions must be fully typed |
| 174 | +- Use `TYPE_CHECKING` blocks for import-only types |
| 175 | +- Uses **pyright** in strict mode (see `pyrightconfig.json`) |
| 176 | +- Use type guards for runtime type narrowing (see `_types.py`) |
| 177 | +- Follow this general principle for user-facing code: be contravariant in the input type and covariant in the output type |
| 178 | + |
| 179 | +## Working with This Codebase |
| 180 | + |
| 181 | +### When Adding New Features |
| 182 | + |
| 183 | +1. Start with `_ridgeplot.py` to understand the main entry point |
| 184 | +2. Add new parameters following the existing patterns (with proper deprecation handling) |
| 185 | +3. Update type annotations in `_types.py` if introducing new data structures |
| 186 | +4. Add tests in `tests/unit/` with good coverage |
| 187 | +5. Update documentation in docstrings following the existing conventions |
| 188 | + |
| 189 | +### When Fixing Bugs |
| 190 | + |
| 191 | +1. Write a failing test first |
| 192 | +2. Fix the issue with minimal changes |
| 193 | +3. Ensure all existing tests pass |
| 194 | +4. Check type annotations are correct |
| 195 | + |
| 196 | +### Common Patterns |
| 197 | + |
| 198 | +**Handling deprecated parameters:** |
| 199 | + |
| 200 | +```python |
| 201 | +if deprecated_param is not MISSING: |
| 202 | + if new_param is not None: |
| 203 | + raise ValueError("Cannot use both...") |
| 204 | + warnings.warn("...", DeprecationWarning, stacklevel=2) |
| 205 | + new_param = deprecated_param |
| 206 | +``` |
| 207 | + |
| 208 | +**Type narrowing with guards:** |
| 209 | + |
| 210 | +```python |
| 211 | +if is_shallow_samples(samples): |
| 212 | + samples = nest_shallow_collection(samples) |
| 213 | +samples = cast("Samples", samples) |
| 214 | +``` |
| 215 | + |
| 216 | +**Lazy imports for performance:** |
| 217 | + |
| 218 | +```python |
| 219 | +# Heavy imports inside functions to reduce import time |
| 220 | +def _coerce_to_densities(...): |
| 221 | + from ridgeplot._kde import estimate_densities # statsmodels is slow to import |
| 222 | +``` |
| 223 | + |
| 224 | +### Testing Tips |
| 225 | + |
| 226 | +- Use `--no-cov` flag during development for faster test runs |
| 227 | +- Run specific tests: `tox -e pytest -- tests/unit/test_ridgeplot.py -k "test_name" --no-cov` |
| 228 | +- The `tests/e2e/artifacts/` directory contains expected Plotly Figure JSON for e2e tests |
| 229 | +- `conftest.py` patches `fig.show()` to prevent browser windows during tests |
| 230 | + |
| 231 | +### Key Files to Know |
| 232 | + |
| 233 | +| File | Purpose | |
| 234 | +|------------------------------------|-----------------------------| |
| 235 | +| `src/ridgeplot/_ridgeplot.py` | Main `ridgeplot()` function | |
| 236 | +| `src/ridgeplot/_figure_factory.py` | Figure construction logic | |
| 237 | +| `src/ridgeplot/_types.py` | All type aliases and guards | |
| 238 | +| `tests/unit/test_ridgeplot.py` | Core function tests | |
| 239 | +| `cicd_utils/ridgeplot_examples/` | Example scripts for docs | |
| 240 | +| `tox.ini` | CI environment definitions | |
| 241 | +| `ruff.toml` | Linting configuration | |
| 242 | + |
| 243 | +## CI/CD Pipeline |
| 244 | + |
| 245 | +Tests run on GitHub Actions across: |
| 246 | + |
| 247 | +- Python versions: 3.10, 3.11, 3.12, 3.13, 3.14 |
| 248 | +- Operating systems: Ubuntu, macOS, Windows |
| 249 | + |
| 250 | +Coverage is uploaded to Codecov with: |
| 251 | + |
| 252 | +- Overall: 98% minimum |
| 253 | +- Diff coverage: 100% for new code |
| 254 | + |
| 255 | +## Notes for AI Assistants |
| 256 | + |
| 257 | +1. **Always run tests** after making changes: `tox -e pytest -- <path> --no-cov` |
| 258 | +2. **Check types** with: `tox -e typing` |
| 259 | +3. **Format code** with: `pre-commit run ruff-format --all-files` |
| 260 | +4. **Respect existing patterns** - this is a mature codebase with consistent style |
| 261 | +5. **Don't add unnecessary abstractions** - keep changes minimal and focused |
| 262 | +6. **Preserve deprecation warnings** - the library maintains backward compatibility |
| 263 | +7. **Update docstrings** when changing function signatures |
| 264 | +8. **Use type guards** for runtime type narrowing instead of assertions |
0 commit comments