Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Quality

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
quality:
name: Code Quality
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libegl1 libopengl0 libxkbcommon-x11-0 libdbus-1-3 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python
run: uv python install

- name: Install dependencies
run: uv sync --dev

- name: Lint with Ruff
run: uv run ruff check .

- name: Check formatting with Ruff
run: uv run ruff format --check .

- name: Type check with Mypy
run: uv run mypy .
89 changes: 57 additions & 32 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,45 +1,70 @@
name: CI Tests
name: Tests

on:
push:
branches: [ master ]
branches: [ main, master ]
pull_request:
branches: [ master ]
branches: [ main, master ]

jobs:
tests:
name: ${{ matrix.os }} / ${{ matrix.qt-lib }} / Py ${{ matrix.python-version }}
test:
name: ${{ matrix.os }} / ${{ matrix.qt-binding }} / numpy-${{ matrix.numpy-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10", "3.12"]
qt-lib: [pyside6, pyqt6, pyqt5]
qt-binding: [pyside6, pyqt6, pyqt5]
numpy-version: ['1.24.4', '1.26.4', '2.1.0']

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'

- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libegl1 libxkbcommon-x11-0 libdbus-1-3 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 x11-utils

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install ".[${{ matrix.qt-lib }}]"
python -m pip install pytest pytest-qt

- name: Run tests
env:
QT_QPA_PLATFORM: offscreen
run: |
pytest
- uses: actions/checkout@v4

- name: Install system dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libegl1 libxkbcommon-x11-0 libdbus-1-3 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 x11-utils

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Set up Python
run: uv python install 3.10

- name: Create Virtual Environment
run: uv venv

- name: Install Base Dependencies
run: uv pip install .

- name: Install Matrix Dependencies
shell: bash
run: |
uv pip install "numpy==${{ matrix.numpy-version }}"

if [[ "${{ matrix.qt-binding }}" == "pyside6" ]]; then
uv pip install "pyside6==6.8.1"
elif [[ "${{ matrix.qt-binding }}" == "pyqt6" ]]; then
uv pip install "pyqt6==6.8.1"
elif [[ "${{ matrix.qt-binding }}" == "pyqt5" ]]; then
uv pip install pyqt5
fi

uv pip install pytest pytest-qt pandas

echo "Installed Qt Binding Version:"
uv pip show ${{ matrix.qt-binding }}



- name: Run Tests
# Set QT_API environment variable based on binding
env:
QT_API: ${{ matrix.qt-binding }}
run: |
if ( "$RUNNER_OS" == "Linux" ); then
xvfb-run -a uv run --no-sync pytest
else
uv run --no-sync pytest
fi
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,5 @@ cython_debug/

# Generated demo files
dummy_data.csv

.ruff_cache
21 changes: 0 additions & 21 deletions LICENSE

This file was deleted.

156 changes: 51 additions & 105 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,143 +1,89 @@
# FieldView

**FieldView** is a high-performance Python + Qt library for 2D data visualization, specifically designed for handling irregular data points. It uses `QtPy` to support **PySide6**, **PyQt6**, and **PyQt5**. It provides a robust rendering engine for heatmaps, markers, and text labels with minimal external dependencies.
[![Quality](https://github.com/donghoonpark/FieldView/actions/workflows/quality.yml/badge.svg)](https://github.com/donghoonpark/FieldView/actions/workflows/quality.yml)
[![Tests](https://github.com/donghoonpark/FieldView/actions/workflows/tests.yml/badge.svg)](https://github.com/donghoonpark/FieldView/actions/workflows/tests.yml)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)

<img src="assets/us_map_demo.png" alt="Quick Start" width="600">
**FieldView** is a high-performance Python library for 2D data visualization, built on top of the Qt framework. It is designed to efficiently render irregular data points using heatmaps, markers, and text labels.

FieldView leverages `QtPy` to support **PySide6**, **PyQt6**, and **PyQt5**, providing a flexible and robust solution for integrating advanced visualizations into Python desktop applications.

<img src="assets/us_map_demo.png" alt="FieldView Demo" width="800">

## Key Features

* **Fast Heatmap Rendering**: Hybrid RBF (Radial Basis Function) interpolation for high-quality visualization with real-time performance optimization.
* **Irregular Data Support**: Native handling of non-grid data points.
* **Polygon Masking**: Support for arbitrary boundary shapes (Polygon, Circle, Rectangle) to clip heatmaps.
* **Layer System**: Modular architecture with support for:
* **HeatmapLayer**: Color-based data visualization.
* **ValueLayer/LabelLayer**: Text rendering with collision avoidance.
* **PinLayer**: Marker placement.
* **SvgLayer**: Background floor plans or overlays.
* **Minimal Dependencies**: Built on `numpy`, `scipy`, and `qtpy`.
* **High-Performance Heatmaps**: Utilizes hybrid RBF (Radial Basis Function) interpolation for smooth, high-quality visualization of scattered data.
* **Irregular Data Handling**: Natively supports non-grid data points without requiring pre-processing.
* **Flexible Masking**: Supports arbitrary boundary shapes (Polygon, Circle, Rectangle) for precise clipping.
* **Modular Layer System**:
* **HeatmapLayer**: Renders interpolated data with customizable colormaps.
* **ValueLayer / LabelLayer**: Displays text with automatic collision avoidance.
* **PinLayer**: Visualizes data points with markers.
* **SvgLayer**: Renders SVG backgrounds for context (e.g., floor plans, maps).
* **Minimal Dependencies**: Core functionality relies only on `numpy`, `scipy`, and `qtpy`.

## Installation

Install FieldView with your preferred Qt binding:

```bash
pip install fieldview[pyside6] # Install with PySide6
pip install fieldview[pyside6] # Recommended
# OR
pip install fieldview[pyqt6] # Install with PyQt6
pip install fieldview[pyqt6]
# OR
pip install fieldview[pyqt5] # Install with PyQt5

pip install fieldview[pyqt5]
```

*Note: Requires Python 3.10+*
*Requires Python 3.10+*

## Quick Start

Here is a minimal example to get a heatmap up and running:
FieldView provides a high-level `FieldView` widget for easy integration.

```python
import sys
import os
import numpy as np
from qtpy.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
from qtpy.QtGui import QPolygonF
from qtpy.QtCore import Qt, QPointF
from fieldview.core.data_container import DataContainer
from fieldview.layers.heatmap_layer import HeatmapLayer
from fieldview.layers.text_layer import ValueLayer
from fieldview.layers.svg_layer import SvgLayer
from fieldview.layers.pin_layer import PinLayer
from qtpy.QtWidgets import QApplication
from fieldview import FieldView

app = QApplication(sys.argv)

# 1. Setup Data
data = DataContainer()
np.random.seed(44)

# Define rooms (x1, y1, x2, y2)
rooms = [
(-450, -250, -150, 250), # Master Bed
(-150, -250, 150, 250), # Living
(150, 0, 300, 250), # Bed 2
(300, -100, 450, 250) # Bed 4
]

points = []
values = []

for i in range(20):
room = rooms[np.random.randint(len(rooms))]
x1, y1, x2, y2 = room
margin = 20
x = np.random.uniform(x1 + margin, x2 - margin)
y = np.random.uniform(y1 + margin, y2 - margin)
points.append([x, y])
values.append(np.random.rand() * 100)

data.set_data(np.array(points), np.array(values))

# 2. Create Scene & Layers
scene = QGraphicsScene()

# SVG Layer (Background)
# Assuming floorplan_apartment.svg exists in current dir or provide path
svg_layer = SvgLayer()
svg_layer.load_svg("examples/floorplan_apartment.svg")
svg_layer.setZValue(0)
scene.addItem(svg_layer)

# Heatmap Layer
heatmap = HeatmapLayer(data)
heatmap.setOpacity(0.6)
heatmap.setZValue(1)

# Define custom boundary polygon for the apartment
polygon = QPolygonF([
QPointF(-450, -330), QPointF(-300, -330), QPointF(-300, -250),
QPointF(-150, -250), QPointF(-150, -300), QPointF(150, -300),
QPointF(150, -250), QPointF(450, -250), QPointF(450, 250),
QPointF(-450, 250)
])
heatmap.set_boundary_shape(polygon)

scene.addItem(heatmap)

# Pin Layer
pin_layer = PinLayer(data)
pin_layer.setZValue(2)
scene.addItem(pin_layer)

# Value Layer
values_layer = ValueLayer(data)
values_layer.setZValue(3)
scene.addItem(values_layer)

# 3. Setup View
view = QGraphicsView(scene)
# 1. Prepare Data
points = np.random.rand(20, 2) * 400
values = np.random.rand(20) * 100

# 2. Create FieldView
view = FieldView()
view.resize(800, 600)
view.show()
view.set_data(points, values)

# Ensure content is visible
scene.setSceneRect(scene.itemsBoundingRect())
view.fitInView(scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
# 3. Add Layers
view.add_heatmap_layer(opacity=0.6)
view.add_pin_layer()
view.add_value_layer()

# 4. Show
view.show()
view.fit_to_scene()

sys.exit(app.exec())
```

## Running the Demo
## Examples

To see all features in action, including the property editor and real-time interaction:
To explore the full capabilities, including the property inspector and real-time updates, run the included demo:

```bash
# Clone the repository
git clone https://github.com/yourusername/fieldview.git
cd fieldview

# Run the demo using uv (recommended)
# Using uv (recommended)
uv run examples/demo.py
```

<img src="assets/demo.gif" alt="Demo" width="800">

## License

MIT License
This project is licensed under a hybrid model depending on the Qt binding used:

* **LGPLv3**: When used with **PySide6**.
* **GPLv3**: When used with **PyQt6** or **PyQt5**.

Please ensure compliance with the license of the chosen Qt binding.
Empty file added examples/__init__.py
Empty file.
Loading
Loading