anywidget-vector 0.3.1__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. anywidget_vector-0.4.0/.github/workflows/ci.yml +70 -0
  2. anywidget_vector-0.4.0/.github/workflows/grafeo-latest.yml +72 -0
  3. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/.github/workflows/pypi.yml +20 -11
  4. anywidget_vector-0.4.0/CHANGELOG.md +191 -0
  5. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/PKG-INFO +129 -13
  6. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/README.md +114 -2
  7. anywidget_vector-0.4.0/assets/demo.png +0 -0
  8. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/examples/fashion-mnist-parallel-coords-6d.py +8 -11
  9. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/pyproject.toml +32 -16
  10. anywidget_vector-0.4.0/scripts/vendor/build.mjs +40 -0
  11. anywidget_vector-0.4.0/scripts/vendor/entry.mjs +70 -0
  12. anywidget_vector-0.4.0/scripts/vendor/package-lock.json +506 -0
  13. anywidget_vector-0.4.0/scripts/vendor/package.json +15 -0
  14. anywidget_vector-0.4.0/src/anywidget_vector/__init__.py +13 -0
  15. anywidget_vector-0.4.0/src/anywidget_vector/backends/grafeo/__init__.py +23 -0
  16. anywidget_vector-0.4.0/src/anywidget_vector/backends/grafeo/browser.js +64 -0
  17. anywidget_vector-0.4.0/src/anywidget_vector/backends/grafeo/client.py +53 -0
  18. anywidget_vector-0.4.0/src/anywidget_vector/backends/grafeo/converter.py +133 -0
  19. anywidget_vector-0.4.0/src/anywidget_vector/backends/grafeo/embed.js +114 -0
  20. anywidget_vector-0.4.0/src/anywidget_vector/backends/grafeo/points.js +117 -0
  21. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/demo.py +1 -1
  22. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/ui/__init__.py +113 -101
  23. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/ui/canvas.js +21 -8
  24. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/ui/properties.js +9 -4
  25. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/ui/sidebar.js +9 -4
  26. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/ui/styles.css +8 -5
  27. anywidget_vector-0.4.0/src/anywidget_vector/ui/theme.js +126 -0
  28. anywidget_vector-0.4.0/src/anywidget_vector/ui/vendor/three.LICENSES.txt +25 -0
  29. anywidget_vector-0.4.0/src/anywidget_vector/ui/vendor/three.min.js +4220 -0
  30. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/widget.py +232 -49
  31. anywidget_vector-0.4.0/tests/conftest.py +20 -0
  32. anywidget_vector-0.4.0/tests/js/wasm_smoke.mjs +111 -0
  33. anywidget_vector-0.4.0/tests/support.py +31 -0
  34. anywidget_vector-0.4.0/tests/test_grafeo_backend.py +202 -0
  35. anywidget_vector-0.4.0/tests/test_grafeo_contract.py +383 -0
  36. anywidget_vector-0.4.0/tests/test_theme.py +328 -0
  37. anywidget_vector-0.4.0/tests/test_ui_bundle.py +459 -0
  38. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/tests/test_widget.py +20 -2
  39. anywidget_vector-0.4.0/tests/test_widget_robustness.py +309 -0
  40. anywidget_vector-0.4.0/uv.lock +4226 -0
  41. anywidget_vector-0.3.1/.github/workflows/ci.yml +0 -71
  42. anywidget_vector-0.3.1/CHANGELOG.md +0 -129
  43. anywidget_vector-0.3.1/assets/demo.png +0 -0
  44. anywidget_vector-0.3.1/src/anywidget_vector/__init__.py +0 -6
  45. anywidget_vector-0.3.1/src/anywidget_vector/backends/grafeo/__init__.py +0 -20
  46. anywidget_vector-0.3.1/src/anywidget_vector/backends/grafeo/browser.js +0 -90
  47. anywidget_vector-0.3.1/src/anywidget_vector/backends/grafeo/client.py +0 -33
  48. anywidget_vector-0.3.1/src/anywidget_vector/backends/grafeo/converter.py +0 -46
  49. anywidget_vector-0.3.1/src/anywidget_vector/backends/grafeo/embed.js +0 -95
  50. anywidget_vector-0.3.1/uv.lock +0 -3122
  51. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/.gitignore +0 -0
  52. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/.python-version +0 -0
  53. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/LICENSE +0 -0
  54. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/examples/demo.py +0 -0
  55. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/examples/test_add_numpy.py +0 -0
  56. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/examples/test_projection.py +0 -0
  57. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/__init__.py +0 -0
  58. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/chroma/__init__.py +0 -0
  59. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/chroma/client.py +0 -0
  60. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/chroma/converter.py +0 -0
  61. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/lancedb/__init__.py +0 -0
  62. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/lancedb/client.py +0 -0
  63. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/lancedb/converter.py +0 -0
  64. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/pinecone/__init__.py +0 -0
  65. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/pinecone/client.js +0 -0
  66. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/pinecone/converter.py +0 -0
  67. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/qdrant/__init__.py +0 -0
  68. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/qdrant/client.js +0 -0
  69. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/qdrant/converter.py +0 -0
  70. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/weaviate/__init__.py +0 -0
  71. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/weaviate/client.js +0 -0
  72. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/backends/weaviate/converter.py +0 -0
  73. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/py.typed +0 -0
  74. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/static/icons.js +0 -0
  75. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/ui/constants.js +0 -0
  76. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/ui/settings.js +0 -0
  77. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/src/anywidget_vector/ui/toolbar.js +0 -0
  78. {anywidget_vector-0.3.1 → anywidget_vector-0.4.0}/tests/__init__.py +0 -0
@@ -0,0 +1,70 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, "release/**", "support/**"]
6
+ pull_request:
7
+ branches: [main]
8
+ # The release workflow runs the full suite before it publishes
9
+ workflow_call:
10
+
11
+ jobs:
12
+ lint:
13
+ name: Lint
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
20
+
21
+ - name: Check that uv.lock matches pyproject.toml
22
+ run: uv lock --check
23
+
24
+ - name: Run ruff check
25
+ run: uv run --locked --only-group dev ruff check .
26
+
27
+ - name: Run ruff format check
28
+ run: uv run --locked --only-group dev ruff format --check .
29
+
30
+ test:
31
+ name: Test (Python ${{ matrix.python-version }})
32
+ runs-on: ubuntu-latest
33
+ strategy:
34
+ matrix:
35
+ python-version: ["3.12", "3.13", "3.14", "3.15"]
36
+ steps:
37
+ - uses: actions/checkout@v7
38
+
39
+ # Sets UV_PYTHON, so uv uses this version instead of .python-version
40
+ - name: Install uv and Python ${{ matrix.python-version }}
41
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
42
+ with:
43
+ python-version: ${{ matrix.python-version }}
44
+
45
+ # The ESM bundle tests parse and run the widget's JavaScript with Node
46
+ - name: Set up Node.js
47
+ uses: actions/setup-node@v7
48
+ with:
49
+ node-version: "22"
50
+
51
+ - name: Install dependencies
52
+ run: uv sync --locked --extra dev
53
+
54
+ - name: Run tests
55
+ run: uv run --locked --extra dev pytest
56
+
57
+ build:
58
+ name: Build
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@v7
62
+
63
+ - name: Install uv
64
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
65
+
66
+ - name: Build package
67
+ run: uv build
68
+
69
+ - name: Check package
70
+ run: uvx twine check dist/*
@@ -0,0 +1,72 @@
1
+ name: Grafeo Latest
2
+
3
+ # Early warning for engine changes: runs the suite against the newest grafeo on
4
+ # PyPI (pre-releases included, even outside the pinned range) and the browser
5
+ # backend against the pinned and the newest @grafeo-db/wasm on npm.
6
+
7
+ on:
8
+ schedule:
9
+ - cron: "0 6 * * 1" # Mondays 06:00 UTC
10
+ workflow_dispatch:
11
+
12
+ jobs:
13
+ python:
14
+ name: Test with newest grafeo (Python ${{ matrix.python-version }})
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.12", "3.13", "3.14", "3.15"]
20
+ steps:
21
+ - uses: actions/checkout@v7
22
+
23
+ - name: Install uv and Python ${{ matrix.python-version }}
24
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+
28
+ - name: Install dependencies
29
+ run: |
30
+ uv sync --extra dev
31
+ uv pip install --upgrade --prerelease allow grafeo
32
+
33
+ - name: Show grafeo build
34
+ run: uv run --no-sync python -c "import grafeo; print(grafeo.__version__); print(grafeo.build_info())"
35
+
36
+ - name: Run tests
37
+ run: uv run --no-sync pytest
38
+
39
+ wasm:
40
+ name: WASM smoke test (${{ matrix.release }} @grafeo-db/wasm)
41
+ runs-on: ubuntu-latest
42
+ strategy:
43
+ fail-fast: false
44
+ matrix:
45
+ release: [pinned, newest]
46
+ steps:
47
+ - uses: actions/checkout@v7
48
+
49
+ - name: Set up Node.js
50
+ uses: actions/setup-node@v7
51
+ with:
52
+ node-version: "22"
53
+
54
+ - name: Resolve version
55
+ id: version
56
+ run: |
57
+ if [ "${{ matrix.release }}" = "pinned" ]; then
58
+ version=$(sed -n 's/^export const GRAFEO_WASM_VERSION = "\(.*\)";$/\1/p' src/anywidget_vector/backends/grafeo/embed.js)
59
+ else
60
+ version=$(npm view @grafeo-db/wasm versions --json | node -e 'const v = JSON.parse(require("fs").readFileSync(0, "utf8")); console.log([].concat(v).pop())')
61
+ fi
62
+ echo "version=$version" >> "$GITHUB_OUTPUT"
63
+
64
+ - name: Download @grafeo-db/wasm@${{ steps.version.outputs.version }}
65
+ run: |
66
+ mkdir -p "$RUNNER_TEMP/wasm"
67
+ cd "$RUNNER_TEMP/wasm"
68
+ npm pack "@grafeo-db/wasm@${{ steps.version.outputs.version }}"
69
+ tar -xzf grafeo-db-wasm-*.tgz
70
+
71
+ - name: Run smoke test
72
+ run: node tests/js/wasm_smoke.mjs "$RUNNER_TEMP/wasm/package"
@@ -15,25 +15,34 @@ on:
15
15
  - pypi
16
16
 
17
17
  jobs:
18
+ test:
19
+ name: Test
20
+ uses: ./.github/workflows/ci.yml
21
+
18
22
  build:
19
23
  name: Build distribution
24
+ needs: test
20
25
  runs-on: ubuntu-latest
21
26
  steps:
22
- - uses: actions/checkout@v4
27
+ - uses: actions/checkout@v7
23
28
 
24
- - name: Set up Python
25
- uses: actions/setup-python@v5
26
- with:
27
- python-version: "3.12"
29
+ - name: Check the release tag matches the package version
30
+ if: github.event_name == 'release'
31
+ run: |
32
+ version=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
33
+ if [ "$GITHUB_REF_NAME" != "v$version" ]; then
34
+ echo "Release tag $GITHUB_REF_NAME does not match the package version $version"
35
+ exit 1
36
+ fi
28
37
 
29
- - name: Install build dependencies
30
- run: pip install build
38
+ - name: Install uv
39
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
31
40
 
32
41
  - name: Build package
33
- run: python -m build
42
+ run: uv build
34
43
 
35
44
  - name: Upload distribution artifacts
36
- uses: actions/upload-artifact@v4
45
+ uses: actions/upload-artifact@v7
37
46
  with:
38
47
  name: dist
39
48
  path: dist/
@@ -48,7 +57,7 @@ jobs:
48
57
  id-token: write
49
58
  steps:
50
59
  - name: Download distribution artifacts
51
- uses: actions/download-artifact@v4
60
+ uses: actions/download-artifact@v8
52
61
  with:
53
62
  name: dist
54
63
  path: dist/
@@ -68,7 +77,7 @@ jobs:
68
77
  id-token: write
69
78
  steps:
70
79
  - name: Download distribution artifacts
71
- uses: actions/download-artifact@v4
80
+ uses: actions/download-artifact@v8
72
81
  with:
73
82
  name: dist
74
83
  path: dist/
@@ -0,0 +1,191 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0 (2026-10-28)
4
+
5
+ Requires Python 3.12 or later; the Grafeo backend requires grafeo 0.6. See "Upgrading to 0.4" in the README.
6
+
7
+ ### Breaking
8
+
9
+ - **Grafeo 0.6**: The `grafeo` extra requires `grafeo>=0.6.0,<0.7`; grafeo 0.5.x is no longer supported. Grafeo 0.6 changes query results and files, and these reach the widget's Grafeo backend:
10
+ - Every database is a single file. The first read-write open of a 0.5.x database migrates it and keeps the old file as `<file>.pre-0.6`, which 0.5.x needs to go back; opening a directory that is not a 0.5.x database fails
11
+ - A GQL number with a point is an exact decimal, so `0.0/0.0` and `1/0` fail the query (division by zero) where they gave NaN or null, and so does integer overflow. Use exponent literals (`0.0e0/0.0e0`) for IEEE floats
12
+ - A `WHERE` right after `INSERT`, `CREATE`, `MERGE` or `DELETE` is an error, and an `INSERT` alone returns no rows, so running one from the toolbar clears the scene
13
+ - GQL `ORDER BY` puts nulls last in both directions
14
+ - **Theme follows the page until you choose**: `dark_mode` reports the theme in use. The widget follows the host page until a theme is chosen (`VectorSpace(dark_mode=...)`, `widget.dark_mode = ...` before or after display, even to the value it already reports, or the settings toggle) and then keeps the choice, also in later views; before, the page theme overwrote `dark_mode` passed from Python, and the widget stopped following the page after its first automatic change. The canvas `background` follows the theme unless chosen the same way; before, the theme overwrote it. Same contract as anywidget-graph and anywidget-archimate (`_theme_explicit`, `_background_explicit`)
15
+ - **Compact toolbar in a narrow widget**: The toolbar tightens when the widget itself is narrower than 600 px (a container query), not only when the window is narrow, as in anywidget-graph. Panels over the canvas (window narrower than 768 px) now cover the canvas's zoom and selection controls instead of sitting under them
16
+ - **WASM engine 0.6**: The browser-side Grafeo backend loads `@grafeo-db/wasm` 0.6.0 (was 0.5.0 in 0.3.2) from jsDelivr. The version is set once, `GRAFEO_WASM_VERSION` in `backends/grafeo/embed.js`, and a test keeps it in lockstep with the Python pin (npm and PyPI spellings of a pre-release, such as `0.6.1-rc.2` and `0.6.1rc2`, compare as the same version)
17
+
18
+ ### Improvements
19
+
20
+ - **Decimals become numbers**: Grafeo 0.6 returns decimals (`decimal.Decimal` in Python, `{"$decimal": "1.50"}` in WASM). The widget turns them into floats in every Grafeo mode, also inside lists, maps and node properties, so `RETURN 0.5 AS score` maps to color and size, `1.5 AS x` places a point, and a plain list `[1.0, 0.0, 0.0]` still gives a vector. Without this, Python points could not be sent to the browser as JSON and WASM points landed at the origin
21
+ - **Vector search errors**: A query vector of the wrong size or with NaN is a regular `grafeo.GrafeoError` in Grafeo 0.6 (0.5.44 panicked, and its WASM build trapped), shown in the widget with the sizes it names. The panic guard stays for any other engine bug
22
+ - **Three.js 0.186.1, bundled**: The widget and `to_html()` use Three.js 0.186.1 (was 0.160.0 from esm.sh), bundled with the package (`ui/vendor/three.min.js` with its license, built by `scripts/vendor`), so both work offline and under a strict Content Security Policy. Only the `wasm` Grafeo mode loads code from a CDN. Three.js dropped WebGL 1 in 0.163, so a browser needs WebGL 2
23
+ - **Theme detection**: A `data-theme="dark"` or `"light"` on the widget's closest ancestor wins, then on `<html>` and `<body>`, then a `dark` or `light` class, then the operating system. `data-theme="light"` no longer loses to a dark operating system, and other `data-theme` values (design-system names) are ignored
24
+ - **Release safety**: The PyPI workflow runs the full CI suite first and stops when the release tag is not `v` plus the `pyproject.toml` version. CI also runs on `release/**` and `support/**` branches
25
+ - **Credentials documented**: The README says where backend credentials end up (the browser and saved notebook widget state) and how to limit that
26
+ - **Dependencies**: Requires anywidget 0.11 or later (was 0.9.21). The `chroma` and `all` extras pin `chromadb==1.5.8`, because 1.5.9 has security issues. All locked dependencies refreshed (`uv lock --upgrade`), and the dev tools need current releases (pytest 9.2, ruff 0.17.3, ty 0.0.94, prek 0.5.7, marimo 0.25.2); the `dev` extra includes grafeo, numpy and packaging so the full test suite runs everywhere. CI and the release workflow use uv (`uv lock --check`, `uv sync --locked`, `uv build`) and current GitHub Actions (checkout v7, setup-uv v10.3.3 pinned by commit since setup-uv publishes no moving major tag, setup-node v7, upload-artifact v7, download-artifact v8). The `tool.uv.dev-dependencies` table moved to `[dependency-groups]`, and ruff leaves Markdown files alone
27
+ - **Engine contract tests**: The tests that pin Grafeo behavior now cover 0.6: error classes and messages for unknown metrics, zero dimensions, wrong-size and NaN query vectors and NaN in an indexed vector, filter operators (`$in`, `$ne`), tied distances, decimal literals and an `INSERT` without rows. `tests/js/wasm_smoke.mjs` checks decimals and the wrong-size error in WASM
28
+ - **Same points in every Grafeo mode**: Embedded, server and WASM results go through the same rules: node values (`RETURN n`) contribute their properties, labels and id, unaliased columns such as `n.embedding` or `id(n)` are recognized, and explicit `x`/`y`/`z` win over the first vector components
29
+ - **Server errors**: grafeo-server error responses show their `detail` text instead of raw JSON
30
+ - **Scheduled engine check**: A weekly workflow runs the suite against the newest grafeo on PyPI (pre-releases included) and a smoke test against the pinned and the newest `@grafeo-db/wasm`
31
+ - **Python 3.15**: Tested on Python 3.15; CI runs the suite on 3.12 to 3.15 and the classifiers list 3.12 to 3.15
32
+
33
+ ### Bug Fixes
34
+
35
+ - **Embedded Grafeo queries**: Queries failed with "'QueryBuilder' object is not iterable" because the client called `GrafeoDB.query()`, which returns a query builder. It now calls `execute()`
36
+ - **WASM mode**: `@grafeo-db/wasm` is published for bundlers since 0.5.x, so importing it from esm.sh no longer yields a working `Database`. The loader now imports the JS glue and instantiates the binary itself. WASM vectors (`Float32Array`) are converted to arrays, so they place points and sync to Python
37
+ - **Engine panics**: A Grafeo panic (for example a vector search with the wrong number of dimensions) escaped the query callback because pyo3 raises it as a `BaseException`. It is now reported as a query error
38
+ - **Query status**: A Python-side query with an empty result left the status at "connecting" and kept the old points. The result now replaces the points and the status becomes "connected"; a new query clears the previous error
39
+ - **NaN and infinity in points**: These values are not valid JSON and one NaN coordinate broke camera framing for the whole scene. Top-level non-finite values are now replaced with `None` (with a `RuntimeWarning`), and distance and focus methods treat `None` coordinates as 0
40
+ - **HTML injection**: Point labels and values from a database were inserted as HTML in the tooltip, the properties panel and the exported HTML page. They are now set as text. `to_html()` also escapes the title and keeps a `</script>` inside the data from ending the script
41
+ - **Vector edge cases**: `add_numpy()` with a single high-dimensional row, a 1-D or one-column array, or ids/labels/metadata of the wrong length raised an unrelated `IndexError`; it now works (single row) or raises a clear `ValueError`. `project()` handles a single point and reports ragged or non-finite vectors clearly. `compute_distances(vector_field=...)` leaves out points without that vector instead of returning infinity, and raises on vectors of different lengths instead of truncating them
42
+ - **Version**: `anywidget_vector.__version__` reported 0.2.5; it now reads the installed package version
43
+ - **Removed views kept running**: A removed or re-rendered widget kept its model listeners, its theme observers and its dark-mode media listener, so old views went on updating `dark_mode` and `background`. Removing a view now detaches all of them
44
+ - **WASM engine loaded twice**: Two copies of the widget module on one page (a cell run again, a marimo reconnect, anywidget-graph next to it) each instantiated the cached `@grafeo-db/wasm` glue, which broke the databases of the first copy. The loading promise now lives on `globalThis`, keyed by the glue URL
45
+ - **Stale query results**: A slower, older browser-side query could overwrite the result of a newer one. Only the newest query per widget now sets points, errors and the loading state
46
+ - **HTML export sizes**: `to_html(width=..., height=...)` placed the values in the page's `<style>` unchecked; a value that is not a plain CSS size now raises `ValueError`
47
+ - **Source distribution**: A local `uv build` packed the `.claude/` folder (agent notes and local settings) into the sdist; it is now excluded
48
+
49
+ ## 0.3.2 (2026-03-16)
50
+
51
+ ### Improvements
52
+
53
+ - **Marimo reactivity fix**: Removed camera and hover JS-to-Python sync to prevent continuous cell re-execution. Only user-initiated actions (selection, mode change) now trigger marimo reactive updates
54
+ - **Custom scroll zoom**: Replaced OrbitControls zoom with a custom wheel handler for much finer granularity
55
+ - **Selection hides completely**: Lasso/box selection now fully hides non-selected points (scale-to-zero for instanced meshes) instead of dimming
56
+ - **Auto zoom-to-fit on selection**: Camera automatically frames the selected points after lasso/box selection
57
+ - **Search filter modes**: Typing in the search bar dims non-matching points as a preview, pressing Run/Enter hides them completely
58
+ - **Hover via callback**: Properties panel hover now uses a direct JS callback instead of model sync, eliminating unnecessary Python round-trips
59
+
60
+ ### Bug Fixes
61
+
62
+ - **setSelection scoping**: Moved `setSelection` helper to `createCanvas` scope so lasso/box/click handlers can access it
63
+ - **Camera debounce variable**: Fixed `_cameraDebounce` lexical declaration error by moving it to the correct scope
64
+
65
+ ## 0.3.1 (2026-03-16)
66
+
67
+ ### New Features
68
+
69
+ - **Lasso selection**: New lasso mode for freehand point selection in the 3D canvas, with point-in-polygon hit testing
70
+ - **Selection highlighting**: Selected points show wireframe highlight rings and non-selected points are dimmed to 15% brightness
71
+ - **Auto-sizing**: Width defaults to 100% (fill container), height defaults to 1:1 aspect ratio. Both can still be set to fixed pixel values
72
+
73
+ ### Bug Fixes
74
+
75
+ - **Properties panel**: Panel is now always created (previously skipped when `show_properties=False`), so the toolbar button always works. Starts closed unless `show_properties=True`
76
+ - **Distance metric feedback**: Changing the metric now auto-enables k=5 connections when a point is selected, giving immediate visual feedback
77
+
78
+ ### Improvements
79
+
80
+ - Reduced scroll zoom sensitivity (`zoomSpeed` halved to 0.5)
81
+ - Search filter dimming increased from 12% to 6% opacity for clearer visual contrast
82
+ - Camera no longer resets on every point update (only on initial load)
83
+ - Cross-widget filtering: example updated so VectorSpace lasso selection feeds back into ParallelCoordinates dimming via marimo reactivity
84
+
85
+ ## 0.3.0 (2026-03-16)
86
+
87
+ ### New Features
88
+
89
+ - **Distance metrics sidebar**: Distance section with metric selector (euclidean, cosine, manhattan, dot product), configurable k-neighbors (0-50) and live nearest-neighbor display for the selected point
90
+ - **Dimension mapping display**: Sidebar now shows active visual mapping fields: color field, size range, shape field and unique shape count
91
+
92
+ ### Improvements
93
+
94
+ - Improved sidebar styling and layout
95
+
96
+ ## 0.2.8 (2026-03-15)
97
+
98
+ ### New Features
99
+
100
+ - **6D parallel coordinates example**: Fashion-MNIST demo with PCA across 6+ dimensions, interactive axis mapping and shape mapping to categorical labels
101
+
102
+ ## 0.2.7 (2026-03-15)
103
+
104
+ ### New Features
105
+
106
+ - **Dark/light mode toggle**: `dark_mode` trait with real-time CSS color scheme switching and theme-aware styling across all panels
107
+
108
+ ## 0.2.6 (2026-03-15)
109
+
110
+ ### New Features
111
+
112
+ - **NumPy array support**: `add_numpy()` method for adding points from arrays with automatic PCA projection for high-dimensional data (D > 3)
113
+ - **Dimensionality reduction**: `project()` method with PCA, t-SNE and UMAP support; `set_vectors()` for storing high-dimensional vectors separately
114
+ - **Example scripts**: Projection demo and numpy integration examples
115
+
116
+ ### Improvements
117
+
118
+ - High-dimensional vector storage without syncing to JavaScript
119
+ - Coordinate normalization after projection
120
+ - Added demo screenshot to README
121
+
122
+ ## 0.2.5 (2026-03-04)
123
+
124
+ ### Bug Fixes
125
+
126
+ - Fixed lasso selection stability issues
127
+ - Removed problematic custom traitlets implementation
128
+
129
+ ### Improvements
130
+
131
+ - Added keyboard shortcuts: F to fit view, Escape to clear selection
132
+ - Better visual feedback for selection modes
133
+ - Cleaner toolbar layout with integrated mode controls
134
+
135
+ ## 0.2.4 (2026-03-02)
136
+
137
+ ### New Features
138
+
139
+ - **Zoom and pan controls**: Zoom in, zoom out and fit-to-view buttons in the toolbar
140
+ - **Box selection mode**: New selection mode alongside click and multi-select, with visual feedback during selection
141
+ - **HTML export**: `to_html()` and `save_html()` methods for self-contained HTML output with embedded data and Three.js
142
+
143
+ ### Improvements
144
+
145
+ - Improved camera fitting algorithm for better initial view framing
146
+ - Updated README with comprehensive usage examples
147
+ - Updated dependencies
148
+
149
+ ## 0.2.3 (2026-02-12)
150
+
151
+ ### New Features
152
+
153
+ - **Sidebar explorer**: Three collapsible sections: collections browser, dimensions explorer (point count, dimensionality, axis ranges) and clusters explorer (grouped by color field with sizes)
154
+ - **Browser-side Grafeo backend**: JavaScript client for Grafeo server and WASM-embedded queries
155
+ - **Demo module**: Reference implementation showing various widget features
156
+
157
+ ### Improvements
158
+
159
+ - Consistency pass across all backend modules
160
+ - Ruff configuration tuned for marimo compatibility
161
+
162
+ ## 0.2.2 (2026-02-07)
163
+
164
+ ### New Features
165
+
166
+ - **Streaming factory methods**: `from_numpy()`, `from_umap()` and `from_arrays()` constructors for various data formats
167
+
168
+ ### Improvements
169
+
170
+ - Added Apache 2.0 license
171
+ - Expanded test suite with 194 additional test cases
172
+ - ESM bundle aggregation for widget JavaScript
173
+
174
+ ## 0.2.1 (2026-02-03)
175
+
176
+ ### New Features
177
+
178
+ - **Multi-backend architecture**: Six backends: Qdrant, Pinecone, Weaviate (browser-side REST), Chroma, LanceDB, Grafeo (Python-side)
179
+ - **Modular UI**: Separated rendering into canvas, settings, properties and toolbar components
180
+ - **Backend factory methods**: `from_qdrant()`, `from_pinecone()`, `from_weaviate()`, `from_chroma()`, `from_lancedb()`, `from_grafeo()`
181
+ - **Event system**: `on_click()`, `on_hover()` and `on_selection()` decorators with callback signatures
182
+ - **CI/CD**: GitHub Actions pipelines for testing and PyPI publishing
183
+
184
+ ### Improvements
185
+
186
+ - Refactored monolithic widget into modular component architecture
187
+ - Added README with usage examples
188
+
189
+ ## 0.1.0 (2026-02-03)
190
+
191
+ - Initial release: Three.js-based interactive 3D vector visualization with instanced rendering, six shape types, continuous and categorical color mapping, orbit controls, hover tooltips, axes/grid display and raycast-based selection
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: anywidget-vector
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Interactive 3D vector visualization with query UI for vector databases
5
5
  Project-URL: Homepage, https://grafeo.dev/
6
6
  Project-URL: Repository, https://github.com/GrafeoDB/anywidget-vector
@@ -17,11 +17,12 @@ Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.15
20
21
  Classifier: Topic :: Scientific/Engineering :: Visualization
21
22
  Requires-Python: >=3.12
22
- Requires-Dist: anywidget>=0.9.21
23
+ Requires-Dist: anywidget>=0.11.0
23
24
  Provides-Extra: all
24
- Requires-Dist: chromadb>=1.5; extra == 'all'
25
+ Requires-Dist: chromadb==1.5.8; extra == 'all'
25
26
  Requires-Dist: lancedb>=0.29.2; extra == 'all'
26
27
  Requires-Dist: numpy>=2.4; extra == 'all'
27
28
  Requires-Dist: pandas>=3; extra == 'all'
@@ -29,17 +30,20 @@ Requires-Dist: pinecone-client>=6.0; extra == 'all'
29
30
  Requires-Dist: qdrant-client>=1; extra == 'all'
30
31
  Requires-Dist: weaviate-client>=4.20; extra == 'all'
31
32
  Provides-Extra: chroma
32
- Requires-Dist: chromadb>=0.4; extra == 'chroma'
33
+ Requires-Dist: chromadb==1.5.8; extra == 'chroma'
33
34
  Provides-Extra: demo
34
35
  Provides-Extra: dev
35
- Requires-Dist: marimo>=0.20; extra == 'dev'
36
- Requires-Dist: prek>=0.3; extra == 'dev'
37
- Requires-Dist: pytest>=9.0.2; extra == 'dev'
38
- Requires-Dist: ruff>=0.15; extra == 'dev'
39
- Requires-Dist: ty>=0.0; extra == 'dev'
36
+ Requires-Dist: grafeo<0.7,>=0.6.0; extra == 'dev'
37
+ Requires-Dist: marimo>=0.25.2; extra == 'dev'
38
+ Requires-Dist: numpy>=1.24; extra == 'dev'
39
+ Requires-Dist: packaging>=24; extra == 'dev'
40
+ Requires-Dist: prek>=0.5.7; extra == 'dev'
41
+ Requires-Dist: pytest>=9.2; extra == 'dev'
42
+ Requires-Dist: ruff>=0.17.3; extra == 'dev'
43
+ Requires-Dist: ty>=0.0.94; extra == 'dev'
40
44
  Provides-Extra: embed
41
45
  Provides-Extra: grafeo
42
- Requires-Dist: grafeo>=0.5; extra == 'grafeo'
46
+ Requires-Dist: grafeo<0.7,>=0.6.0; extra == 'grafeo'
43
47
  Provides-Extra: lancedb
44
48
  Requires-Dist: lancedb>=0.1; extra == 'lancedb'
45
49
  Provides-Extra: numpy
@@ -69,7 +73,7 @@ Works with Marimo, Jupyter, VS Code, Colab, anywhere [anywidget](https://anywidg
69
73
 
70
74
  - **Universal**: One widget, every notebook environment
71
75
  - **6D Visualization**: X, Y, Z position + Color, Shape, Size encoding
72
- - **Backend-agnostic**: NumPy, pandas, Qdrant, Chroma, Pinecone, Weaviate, LanceDB, or raw dicts
76
+ - **Backend-agnostic**: NumPy, pandas, Grafeo, Qdrant, Chroma, Pinecone, Weaviate, LanceDB, or raw dicts
73
77
  - **Interactive**: Orbit, pan, zoom, click, hover, box select
74
78
  - **Customizable**: Color scales, shapes, sizes, themes
75
79
  - **Performant**: Instanced rendering for large point clouds
@@ -78,8 +82,12 @@ Works with Marimo, Jupyter, VS Code, Colab, anywhere [anywidget](https://anywidg
78
82
 
79
83
  ```bash
80
84
  uv add anywidget-vector
85
+ uv add "anywidget-vector[grafeo]" # with the embedded Grafeo backend (grafeo 0.6)
81
86
  ```
82
87
 
88
+ Requires Python 3.12 or later. The `grafeo` extra installs grafeo 0.6 (`grafeo>=0.6.0,<0.7`); the browser-side WASM mode loads the matching `@grafeo-db/wasm` from a CDN. Upgrading from 0.3 with Grafeo:
89
+ see [Upgrading to 0.4](#upgrading-to-04).
90
+
83
91
  ## Quick Start
84
92
 
85
93
  ```python
@@ -347,7 +355,8 @@ widget = VectorSpace(
347
355
  points=data,
348
356
  width=1000,
349
357
  height=700,
350
- background="#1a1a2e", # Dark theme default
358
+ dark_mode=None, # None (default) follows the page theme; True or False fixes it
359
+ background=None, # None (default) uses the theme's canvas color
351
360
  show_axes=True,
352
361
  show_grid=True,
353
362
  axis_labels={"x": "PC1", "y": "PC2", "z": "PC3"},
@@ -358,6 +367,16 @@ widget = VectorSpace(
358
367
  )
359
368
  ```
360
369
 
370
+ ### Theme
371
+
372
+ The widget follows the theme of the page it is on: a `data-theme="dark"` or `"light"` on the widget's closest ancestor,
373
+ then on `<html>` and `<body>`, then a `dark` or `light` class on `<html>` or `<body>`, then the operating system's
374
+ dark-mode setting. It keeps following as the page changes. `dark_mode` reports the theme in use.
375
+
376
+ A theme you choose stays: `VectorSpace(dark_mode=False)`, `widget.dark_mode = ...` before or after the widget is shown
377
+ (also to the value it already reports), or the toggle in the settings panel. The canvas color follows the theme unless
378
+ you choose a `background` the same way. anywidget-graph and anywidget-archimate behave the same.
379
+
361
380
  ## Backends
362
381
 
363
382
  Configure a backend for interactive querying:
@@ -368,6 +387,96 @@ widget.set_backend("lancedb", client=table)
368
387
  widget.set_backend("grafeo", client=db)
369
388
  ```
370
389
 
390
+ Type a query in the toolbar and press Run (or Enter). An empty result clears the scene; errors show in
391
+ the settings panel.
392
+
393
+ ### Credentials
394
+
395
+ Connection settings, including `apiKey`, `authToken`, `username` and `password` in `set_backend(...)` and the
396
+ embedding key in the settings panel, are widget state: they reach the browser, which needs them for the browser-side
397
+ backends (Qdrant, Pinecone, Weaviate, grafeo-server), and Jupyter saves them in the notebook file when it saves widget
398
+ state. Use a read-only key with the narrowest scope the backend offers, keep it out of notebooks you share, or query
399
+ from Python (`from_qdrant(...)`, `set_backend("chroma", client=...)` and the other Python-side clients keep their
400
+ credentials in the kernel).
401
+
402
+ ### Grafeo
403
+
404
+ [Grafeo](https://grafeo.dev/) is the default backend. It runs in three modes, set with
405
+ `grafeo_connection_mode`:
406
+
407
+ | Mode | Where queries run | Setup |
408
+ |------|-------------------|-------|
409
+ | `embedded` (default) | In Python, on a `grafeo.GrafeoDB` | `uv add "anywidget-vector[grafeo]"` and `set_backend("grafeo", client=db)` |
410
+ | `server` | Over HTTP, `POST /query` on [grafeo-server](https://github.com/GrafeoDB/grafeo-server) | `grafeo_server_url="http://localhost:7474"`; start the server with `--cors-origins` so the notebook's origin may call it |
411
+ | `wasm` | In the browser, in-memory | Nothing to install: `@grafeo-db/wasm` 0.6.0 loads from the jsDelivr CDN on the first query (the only code the widget loads from a CDN) |
412
+
413
+ ```python
414
+ import grafeo
415
+ from anywidget_vector import VectorSpace
416
+
417
+ db = grafeo.GrafeoDB()
418
+ db.create_node(["Doc"], {"title": "Graphs", "embedding": [0.9, 0.1, 0.0]})
419
+ db.create_node(["Doc"], {"title": "Vectors", "embedding": [0.1, 0.9, 0.2]})
420
+ db.create_vector_index("Doc", "embedding", metric="cosine")
421
+
422
+ widget = VectorSpace(color_field="distance")
423
+ widget.set_backend("grafeo", client=db)
424
+ widget
425
+ ```
426
+
427
+ A k-nearest-neighbor query for the toolbar (GQL, one statement per query):
428
+
429
+ ```
430
+ CALL grafeo.search.vector('Doc', 'embedding', [1.0, 0.0, 0.0], 10) YIELD node_id, distance
431
+ NEXT MATCH (n:Doc) WHERE id(n) = node_id RETURN n, distance ORDER BY distance
432
+ ```
433
+
434
+ How results become points, the same in every mode:
435
+
436
+ - A node (`RETURN n`) contributes its properties, its labels (`labels`) and its id (`id`).
437
+ - An unaliased column is named after its expression (`n.embedding`), and the widget reads it as `embedding`.
438
+ - The first of `vector`, `embedding` or `embeddings` becomes the point's `vector`; its first three components
439
+ are the coordinates unless the row also has `x`, `y` and `z`.
440
+ - A GQL number with a point (`0.5`) is an exact decimal in Grafeo 0.6; the widget turns it into a float, also inside
441
+ lists and maps, so `RETURN 0.5 AS score` colors and sizes points like any other number.
442
+
443
+ In `wasm` mode the database starts empty, so load data with one `INSERT` per query and create the index with
444
+ `CREATE VECTOR INDEX doc_embedding ON :Doc(embedding) DIMENSION 3 METRIC 'cosine'`. Store embeddings
445
+ as `vector([...])` there: a plain list is not indexed. An `INSERT` returns no rows, so it clears the scene; run a
446
+ `MATCH` or a search afterwards to see the data.
447
+
448
+ A query vector with another number of dimensions than the index, or with NaN, is a query error that names the
449
+ sizes (for example "the query vector has 2 dimensions; the index on :Doc(embedding) expects 3"); the points stay
450
+ as they were.
451
+
452
+ The distance metrics of a Grafeo vector index (`cosine`, `euclidean`, `dot_product`, `manhattan`) give the
453
+ same values as the widget's `distance_metric` and `compute_distances()`.
454
+
455
+ The browser engine version is set in one place, `GRAFEO_WASM_VERSION` in
456
+ `src/anywidget_vector/backends/grafeo/embed.js`, and moves in lockstep with the `grafeo` extra. Before a bump,
457
+ run `node tests/js/wasm_smoke.mjs <unpacked @grafeo-db/wasm package>`.
458
+
459
+ ### Upgrading to 0.4
460
+
461
+ 0.4 moves the Grafeo backend to Grafeo 0.6, which changes some query results. What to check:
462
+
463
+ - **Install**: the `grafeo` extra needs grafeo 0.6; `uv add "anywidget-vector[grafeo]"` upgrades it. The `wasm` mode
464
+ loads `@grafeo-db/wasm` 0.6 from the CDN by itself.
465
+ - **Database files**: Grafeo 0.6 keeps every database in a single file. The first read-write open of a 0.5.x
466
+ database migrates it and keeps the old file as `<file>.pre-0.6`; 0.5.x cannot open the migrated file, so stop
467
+ every 0.5.x process first and keep the `.pre-0.6` copy until you no longer need to go back. Opening a directory that
468
+ is not a 0.5.x database fails: pass a file path, such as `data/vectors.grafeo`.
469
+ - **Numbers with a point**: `0.1` is an exact decimal in GQL, so `0.0/0.0` and `1/0` now fail the query (division by
470
+ zero) instead of giving NaN or null, and integer overflow fails too. Write float literals with an exponent
471
+ (`0.0e0 / 0.0e0`) where you want IEEE results.
472
+ - **Writes**: a `WHERE` right after `INSERT`, `CREATE`, `MERGE` or `DELETE` is an error; put the condition before the
473
+ write. An `INSERT` alone returns no rows.
474
+ - **Ordering**: GQL `ORDER BY` puts nulls last in both directions; add `NULLS FIRST` for the old descending order.
475
+ - **Errors**: a vector search with a query of the wrong size raises `grafeo.GrafeoError` (Grafeo 0.5.44 panicked);
476
+ the widget shows it as a query error either way.
477
+
478
+ See Grafeo's [Upgrading to 0.6](https://grafeo.dev/upgrading/0.6/) for the full list.
479
+
371
480
  ## Export
372
481
 
373
482
  ```python
@@ -377,6 +486,13 @@ widget.to_html(title="My Vectors") # Custom title
377
486
  widget.save_html("vectors.html") # Write HTML to file
378
487
  ```
379
488
 
489
+ Three.js is bundled with the package (`ui/vendor/three.min.js`, built by `scripts/vendor`), so the widget and its
490
+ HTML export work offline and under a strict Content Security Policy. Only the `wasm` Grafeo mode loads code from a
491
+ CDN.
492
+
493
+ NaN and infinity cannot travel as JSON, so the widget replaces them with `None` in point values (with a
494
+ `RuntimeWarning`); a missing coordinate is drawn at 0.
495
+
380
496
  ## Environment Support
381
497
 
382
498
  | Environment | Supported |