Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
68d4bf1
moved from poetry to uv
rgerum Jan 8, 2026
0977824
publish.yml also use uv
rgerum Jan 8, 2026
4156f07
ruff check autofix
rgerum Jan 9, 2026
a064c16
added formatting with ruff format
rgerum Jan 9, 2026
102174c
fixed ruff check errors
rgerum Jan 9, 2026
92be29b
ignore example_pylustrator.py for ruff
rgerum Jan 9, 2026
6b40512
run pytest on all branches
rgerum Jan 9, 2026
c61154e
changed how x/y lims are saved
rgerum Jan 9, 2026
3d26cc8
changed how x/y labels are saved
rgerum Jan 9, 2026
b002c9f
fixed some ty errors
rgerum Jan 9, 2026
ff4835e
fixed license string in pyproject.toml
rgerum Jan 9, 2026
b553f1b
fixed license string in pyproject.toml again
rgerum Jan 9, 2026
e469e39
[tool.setuptools]
rgerum Jan 9, 2026
a2d93bd
moved ruff and ty to dev
rgerum Jan 9, 2026
0f1e837
make readthedocs use uv to build
rgerum Jan 9, 2026
083e1de
changed readthedocs group from doc to docs
rgerum Jan 9, 2026
2610bb6
added mock to docs
rgerum Jan 9, 2026
79cda35
added pylustrator.components to pyproject.toml
rgerum Jan 12, 2026
0170d89
some ty fixes
rgerum Jan 13, 2026
713c99a
silences ty errors
rgerum Jan 15, 2026
c61bfa7
renamed self.layout to self.layout_main to avoid overwriting the qt f…
rgerum Jan 15, 2026
c3e5039
commited missing type files
rgerum Jan 15, 2026
1db9652
updated pyproject.toml
rgerum Jan 15, 2026
7210444
remove ignores by importing matplotlib components directly
rgerum Jan 15, 2026
fa2bf0a
fixed ty:ignores for Signals
rgerum Jan 15, 2026
8c70102
removed more ty:ignores
rgerum Jan 15, 2026
6f43ad4
fixed types in snap.py for the points
rgerum Jan 20, 2026
e585f6f
fixed types in tree_view.py
rgerum Jan 20, 2026
a89af15
fixed ruff checks in tree_view.py
rgerum Jan 20, 2026
70937f3
fixed types in qitem_properties.py
rgerum Jan 20, 2026
d6c3a5c
fixed type errors in QtGuiDrag.py
rgerum Jan 20, 2026
42e1d22
small fixes
rgerum Jan 20, 2026
00bfa22
implemented dragable -> draggable patch
rgerum Jan 20, 2026
3010bf9
fixed ty errors in drag_helper.py
rgerum Jan 20, 2026
0f0e0ca
removed ignores in drag_helper.py
rgerum Jan 21, 2026
e31fef6
removed more ignores
rgerum Jan 21, 2026
3d4f16e
removed more ignores
rgerum Jan 21, 2026
163fd18
fixed tests
rgerum Jan 21, 2026
3df46c5
fixed ignores in QLinkableWidgets.py
rgerum Jan 26, 2026
870eb2f
removed some ty ignores in change_tracker.py
rgerum Jan 26, 2026
b6052c8
fixed remaining type errors in change_tracker.py
rgerum Jan 26, 2026
d12acb0
removed more ty:ignores
rgerum Jan 27, 2026
4646643
added some more ty:ignore
rgerum Jan 27, 2026
816a947
fixed add rectangle
rgerum Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and publish to pypi
uses: JRubics/poetry-publish@v1.13
with:
pypi_token: ${{ secrets.PYPI_TOKEN }}
- uses: actions/checkout@v6

- name: Setup uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"

- name: Build distribution
run: uv build

- name: Publish to PyPI
run: uv publish
31 changes: 19 additions & 12 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ name: PyTest
on:
# Triggers the workflow on push or pull request events but only for the "master" branch
push:
branches: [ "master" ]
branches: [ "**" ]
pull_request:
branches: [ "master" ]
branches: [ "**" ]
types: [ opened, synchronize, reopened ]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand All @@ -17,13 +18,19 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: pipx install poetry
- uses: actions/setup-python@v4
with:
python-version: '3.8'
cache: 'poetry'
- run: poetry install --with test
- run: poetry run pytest
env:
QT_QPA_PLATFORM: "offscreen"
- uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true

- name: Set up Python
run: uv python install

- name: Install the project
run: uv sync --locked --all-extras --dev

- run: uv run pytest
env:
QT_QPA_PLATFORM: "offscreen"
31 changes: 31 additions & 0 deletions .github/workflows/ruff.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: ruff

on:
push:
branches: [ '**' ]
pull_request:
branches: [ '**' ]
types: [ opened, synchronize, reopened ]

jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true

- name: Set up Python
run: uv python install

- name: Install the project
run: uv sync --locked --all-extras --dev

- name: Run Ruff
run: uvx ruff check --output-format=github .

- name: Run Ty
run: uvx ty check
30 changes: 13 additions & 17 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

build:
os: ubuntu-22.04
tools:
python: "3.12"

# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py

# Optionally build your docs in additional formats such as PDF and ePub
formats: all

# Optionally set the version of Python and requirements required to build your docs
python:
install:
- requirements: docs/requirements.readthedocs.txt
build:
os: ubuntu-24.04
tools:
python: "3.13"
jobs:
pre_create_environment:
- asdf plugin add uv
- asdf install uv latest
- asdf global uv latest
create_environment:
- uv venv "${READTHEDOCS_VIRTUALENV_PATH}"
install:
- UV_PROJECT_ENVIRONMENT="${READTHEDOCS_VIRTUALENV_PATH}" uv sync --frozen --group docs
103 changes: 103 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Pylustrator is an interactive Python tool for reproducible figure creation in scientific publications. It provides a GUI integrated with Matplotlib that allows users to interactively drag/resize plot elements, adjust formatting, and automatically generate Python code that reproduces all modifications.

## Build & Development Commands

```bash
# Install dependencies
uv sync

# Install with dev dependencies (includes ruff linter)
uv sync --extra dev

# Install with test dependencies
uv sync --extra test

# Run tests
uv run pytest

# Run tests in headless mode (required for CI/Linux)
QT_QPA_PLATFORM=offscreen uv run pytest

# Run a single test
uv run pytest tests/test_axes.py::TestAxes::test_method_name

# Lint with ruff
uv run ruff check .

# Build documentation
uv sync --group docs
uv run sphinx-build -b html docs docs/_build
```

## Architecture

### Core Flow
```
pylustrator.start() → patches plt.figure/plt.show
PlotWindow (Qt GUI) created for each figure
DragManager handles interactions → Selection manages multi-element transforms
ChangeTracker monitors modifications → generates Python code
Code inserted into source file before plt.show()
```

### Key Modules

| Module | Purpose |
|--------|---------|
| `QtGuiDrag.py` | Entry point; patches matplotlib, creates PlotWindow |
| `change_tracker.py` | Tracks modifications, generates reproducible Python code, manages undo/redo |
| `drag_helper.py` | Mouse interactions, object selection, grabber classes |
| `snap.py` | Alignment snapping; `TargetWrapper` provides unified interface for matplotlib objects |
| `QLinkableWidgets.py` | `Linkable` mixin binds Qt widgets to matplotlib artist properties bidirectionally |
| `components/plot_layout.py` | Graphics scene/view management, DPI awareness |
| `components/qitem_properties.py` | Property editor panel (largest component) |
| `helper_functions.py` | Utility functions like `fig_text()`, `add_axes()`, `changeFigureSize()` |

### Key Patterns

1. **Matplotlib Monkey-Patching**: `initialize()` patches `plt.figure()` and `plt.show()` to create GUI windows

2. **TargetWrapper**: Unified interface for all matplotlib artists in `snap.py`:
```python
wrapper = TargetWrapper(matplotlib_artist)
position = wrapper.get_position()
wrapper.set_position([x, y, w, h])
```

3. **Linkable Mixin**: Qt widgets auto-sync with matplotlib properties in `QLinkableWidgets.py`

4. **Stack Introspection**: `ChangeTracker` uses traceback to find where to insert generated code in source files

5. **Special Attributes**: Objects use `_pylustrator_*` attributes for tracking:
- `_pylustrator_reference` - object creation location
- `_pylustrator_old_args` - initial property values
- `_pylustrator_cached_*` - property caching

### Testing

Tests use `BaseTest` class from `tests/base_test_class.py` with helpers:
- `run_plot_script()` - creates and runs test figures
- `move_element()` - simulates drag operations
- `check_line_in_file()` - verifies generated code
- `change_property2()` - tests property modifications

## Dependencies & Compatibility

- Python 3.9+
- Matplotlib 2.0+ (branching for 3.6.0+ features)
- PyQt5 5.6+ (with PyQt6/PySide6 support)
- macOS Retina/DPI awareness is actively maintained

## Current Development Focus

The `fix_mac_display` branch addresses macOS display scaling with DPR (Device Pixel Ratio) awareness in `plot_layout.py`, `drag_helper.py`, and `snap.py`.
8 changes: 5 additions & 3 deletions development/add_copyright_notice.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@
if file.endswith(".py"):
print(file)
full_filename = os.path.join(root, file)
with open(full_filename+".tmp", "w") as fp2:
with open(full_filename + ".tmp", "w") as fp2:
fp2.write(notice.format(file))
with open(full_filename, "r") as fp1:
start = False
for line in fp1.readlines():
if not start and not (line.startswith("#") or line.strip() == ""):
if not start and not (
line.startswith("#") or line.strip() == ""
):
start = True
if start:
fp2.write(line)
continue

shutil.move(full_filename+".tmp", full_filename)
shutil.move(full_filename + ".tmp", full_filename)
18 changes: 12 additions & 6 deletions development/raise_version_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,29 @@
try:
new_version = sys.argv[1]
except IndexError:
print(f"ERROR: no new version number supplied. Current {package_name} version is {current_version}", file=sys.stderr)
print(
f"ERROR: no new version number supplied. Current {package_name} version is {current_version}",
file=sys.stderr,
)
sys.exit(1)

# check if new version name differs
if current_version == new_version:
print(f"ERROR: new {package_name} version {new_version} is the same as old version {current_version}.", file=sys.stderr)
print(
f"ERROR: new {package_name} version {new_version} is the same as old version {current_version}.",
file=sys.stderr,
)
sys.exit(1)

print(f"setting {package_name} version number from {current_version} to {new_version}")

files = ["setup.py", "meta.yaml", "docs/conf.py", package_name+"/__init__.py"]
files = ["pyproject.toml", "meta.yaml", "docs/conf.py", package_name + "/__init__.py"]

# Let's go
for file in files:
if replace_version(file, current_version, new_version):
os.system(f"git add {file}")

# commit changes
os.system("git commit -m \"set version to v%s\"" % new_version)
os.system("git tag \"v%s\"" % new_version)
os.system('git commit -m "set version to v%s"' % new_version)
os.system('git tag "v%s"' % new_version)
2 changes: 1 addition & 1 deletion development/release_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def replace_version(file, version_old, version_new):
def get_setup_properties():
path = Path(__file__).parent
for i in range(3):
setup_file = path / "setup.py"
setup_file = path / "pyproject.toml"
if setup_file.exists():
os.chdir(setup_file.parent)
with setup_file.open("r") as fp:
Expand Down
63 changes: 0 additions & 63 deletions development/send_to_pypi.py

This file was deleted.

Loading
Loading