Skip to content
Open
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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,25 @@ To explore the full capabilities, including the property inspector and real-time
uv run examples/demo.py
```

## Development

You can run formatting, type checking and tests with [`uv`](https://github.com/astral-sh/uv).

### Running tests locally

GitHub Actions installs additional system packages for Qt-based tests (see `.github/workflows/tests.yml`).
On a fresh Linux environment you can mirror that setup by running:

```bash
sudo scripts/install_test_system_deps.sh
```

Headless environments also need the same Qt variables the CI sets:

```bash
QT_API=pyside6 QT_QPA_PLATFORM=offscreen uv run pytest
```

## License

This project is licensed under a hybrid model depending on the Qt binding used:
Expand Down
71 changes: 63 additions & 8 deletions fieldview/layers/text_layer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Optional, List, Dict, Set, Union
from typing import TYPE_CHECKING, ClassVar, Optional, List, Dict, Set, Union

if TYPE_CHECKING:
from PySide6.QtGui import QPainter, QColor, QFont, QFontMetrics, QFontDatabase
Expand Down Expand Up @@ -243,14 +243,54 @@ class ValueLayer(TextLayer):
Renders numerical values.
"""

UNIT_SUFFIXES: ClassVar[tuple[str, ...]] = (
"℃", # Degree Celsius
"℉", # Degree Fahrenheit
"%", # Percentage
"㎐", # Hertz
"㎑", # Kilohertz
"㎒", # Megahertz
"㎓", # Gigahertz
"㎂", # Microampere
"㎃", # Milliampere
"㎄", # Kiloampere
"㎎", # Milligram
"㎏", # Kilogram
"㎍", # Microgram
"㎕", # Microliter
"㎖", # Milliliter
"㎗", # Deciliter
"㎘", # Kiloliter
"㎜", # Millimeter
"㎝", # Centimeter
"㎞", # Kilometer
"㎡", # Square meter
"㎧", # Meters per second
"㎨", # Meters per second squared
"㎩", # Kilopascal
"㎪", # Megapascal
"㎫", # Gigapascal
"㎷", # Microvolt
"㎸", # Millivolt
"㎹", # Megavolt
"㎽", # Milliampere-hour
"㎾", # Kilowatt
"㎿", # Megawatt
"㏀", # Kiloohm
"㏁", # Megaohm
"㏃", # Becquerel
"㏄", # Cubic centimeter
"㏈", # Decibel
"㏊", # Hectare
"㏕", # Lux
"㏗", # pH
)

def __init__(self, data_container, parent: Optional[DataLayer] = None):
super().__init__(data_container, parent)
self._decimal_places = 2
self._suffix = ""
self._postfix = "" # Same as suffix? antigravity.md says both. Let's assume prefix/suffix or just suffix.
# antigravity.md says "Can add suffix, postfix". Maybe prefix/suffix?
# Let's implement prefix and suffix.
self._prefix = ""
self._postfix = ""

@property
def decimal_places(self) -> int:
Expand All @@ -263,11 +303,24 @@ def decimal_places(self, value: int):

@property
def suffix(self) -> str:
return self._suffix
return self._postfix

@suffix.setter
def suffix(self, value: str):
self._suffix = value
if not isinstance(value, str):
raise TypeError("Suffix must be a string")
self._postfix = value
self.update_layer()

@property
def postfix(self) -> str:
return self._postfix

@postfix.setter
def postfix(self, value: str):
if not isinstance(value, str):
raise TypeError("Postfix must be a string")
self._postfix = value
self.update_layer()

@property
Expand All @@ -276,13 +329,15 @@ def prefix(self) -> str:

@prefix.setter
def prefix(self, value: str):
if not isinstance(value, str):
raise TypeError("Prefix must be a string")
self._prefix = value
self.update_layer()

def _get_text(self, index: int, value: Optional[float], label: str) -> str:
if value is None:
return ""
return f"{self._prefix}{value:.{self._decimal_places}f}{self._suffix}"
return f"{self._prefix}{value:.{self._decimal_places}f}{self._postfix}"


class LabelLayer(TextLayer):
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fieldview"
version = "0.3.1"
version = "0.3.2"
description = "Python + Qt based 2D data visualization solution for irregular data points"
readme = "README.md"
authors = [{name = "FieldView Team", email = "dev@fieldview.org"}]
Expand Down Expand Up @@ -60,4 +60,3 @@ module = [
ignore_missing_imports = true
ignore_errors = true


26 changes: 26 additions & 0 deletions scripts/install_test_system_deps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash
set -euo pipefail

if [[ $(id -u) -ne 0 ]]; then
echo "This script must be run as root (e.g., with sudo)." >&2
exit 1
fi

apt-get update
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 \
libxcb-cursor0 \
libfontconfig1 \
libglib2.0-0 \
libgl1
37 changes: 37 additions & 0 deletions tests/test_text_layer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import TYPE_CHECKING

import pytest

if TYPE_CHECKING:
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QStyleOptionGraphicsItem
Expand Down Expand Up @@ -28,6 +30,41 @@ def test_value_layer(qtbot):
assert layer._get_text(0, 10.12345, "") == "Val: 10.123 m"


def test_value_layer_prefix_property_updates_layer(qtbot, monkeypatch):
dc = DataContainer()
dc.set_data([[0, 0]], [5.4321])
layer = ValueLayer(dc)

update_calls: list[None] = []
monkeypatch.setattr(layer, "update_layer", lambda: update_calls.append(None))

layer.prefix = "~"

assert layer.prefix == "~"
assert len(update_calls) == 1

with pytest.raises(TypeError):
layer.prefix = 123 # type: ignore[assignment]


def test_value_layer_suffix_and_postfix(qtbot):
dc = DataContainer()
dc.set_data([[0, 0]], [1.2345])
layer = ValueLayer(dc)

layer.postfix = "㎧"
assert layer._get_text(0, 1.2345, "") == "1.23㎧"

layer.prefix = "~"
assert layer._get_text(0, 1.2345, "") == "~1.23㎧"

layer.suffix = "℃"
assert layer.postfix == "℃"
assert layer._get_text(0, 1.2345, "") == "~1.23℃"

assert "℃" in ValueLayer.UNIT_SUFFIXES


def test_label_layer(qtbot):
dc = DataContainer()
dc.set_data([[0, 0]], [10], ["Test Label"])
Expand Down
4 changes: 2 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.