anywidget-vector 0.1.0__tar.gz → 0.2.1__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 (43) hide show
  1. anywidget_vector-0.2.1/.github/workflows/ci.yml +71 -0
  2. anywidget_vector-0.2.1/.github/workflows/pypi.yml +77 -0
  3. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/PKG-INFO +70 -3
  4. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/README.md +67 -0
  5. anywidget_vector-0.2.1/examples/demo.py +274 -0
  6. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/pyproject.toml +3 -3
  7. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/src/anywidget_vector/__init__.py +1 -1
  8. anywidget_vector-0.2.1/src/anywidget_vector/backends/__init__.py +103 -0
  9. anywidget_vector-0.2.1/src/anywidget_vector/backends/chroma/__init__.py +27 -0
  10. anywidget_vector-0.2.1/src/anywidget_vector/backends/chroma/client.py +60 -0
  11. anywidget_vector-0.2.1/src/anywidget_vector/backends/chroma/converter.py +86 -0
  12. anywidget_vector-0.2.1/src/anywidget_vector/backends/grafeo/__init__.py +20 -0
  13. anywidget_vector-0.2.1/src/anywidget_vector/backends/grafeo/client.py +33 -0
  14. anywidget_vector-0.2.1/src/anywidget_vector/backends/grafeo/converter.py +46 -0
  15. anywidget_vector-0.2.1/src/anywidget_vector/backends/lancedb/__init__.py +22 -0
  16. anywidget_vector-0.2.1/src/anywidget_vector/backends/lancedb/client.py +56 -0
  17. anywidget_vector-0.2.1/src/anywidget_vector/backends/lancedb/converter.py +71 -0
  18. anywidget_vector-0.2.1/src/anywidget_vector/backends/pinecone/__init__.py +21 -0
  19. anywidget_vector-0.2.1/src/anywidget_vector/backends/pinecone/client.js +45 -0
  20. anywidget_vector-0.2.1/src/anywidget_vector/backends/pinecone/converter.py +62 -0
  21. anywidget_vector-0.2.1/src/anywidget_vector/backends/qdrant/__init__.py +26 -0
  22. anywidget_vector-0.2.1/src/anywidget_vector/backends/qdrant/client.js +61 -0
  23. anywidget_vector-0.2.1/src/anywidget_vector/backends/qdrant/converter.py +83 -0
  24. anywidget_vector-0.2.1/src/anywidget_vector/backends/weaviate/__init__.py +33 -0
  25. anywidget_vector-0.2.1/src/anywidget_vector/backends/weaviate/client.js +50 -0
  26. anywidget_vector-0.2.1/src/anywidget_vector/backends/weaviate/converter.py +81 -0
  27. anywidget_vector-0.2.1/src/anywidget_vector/static/icons.js +14 -0
  28. anywidget_vector-0.2.1/src/anywidget_vector/traitlets.py +84 -0
  29. anywidget_vector-0.2.1/src/anywidget_vector/ui/__init__.py +206 -0
  30. anywidget_vector-0.2.1/src/anywidget_vector/ui/canvas.js +521 -0
  31. anywidget_vector-0.2.1/src/anywidget_vector/ui/constants.js +64 -0
  32. anywidget_vector-0.2.1/src/anywidget_vector/ui/properties.js +158 -0
  33. anywidget_vector-0.2.1/src/anywidget_vector/ui/settings.js +265 -0
  34. anywidget_vector-0.2.1/src/anywidget_vector/ui/styles.css +348 -0
  35. anywidget_vector-0.2.1/src/anywidget_vector/ui/toolbar.js +117 -0
  36. anywidget_vector-0.2.1/src/anywidget_vector/widget.py +315 -0
  37. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/tests/test_widget.py +125 -0
  38. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/uv.lock +23 -24
  39. anywidget_vector-0.1.0/src/anywidget_vector/widget.py +0 -978
  40. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/.gitignore +0 -0
  41. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/.python-version +0 -0
  42. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/src/anywidget_vector/py.typed +0 -0
  43. {anywidget_vector-0.1.0 → anywidget_vector-0.2.1}/tests/__init__.py +0 -0
@@ -0,0 +1,71 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ name: Lint
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Install dependencies
22
+ run: pip install ruff
23
+
24
+ - name: Run ruff check
25
+ run: ruff check .
26
+
27
+ - name: Run ruff format check
28
+ run: 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"]
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+
39
+ - name: Set up Python ${{ matrix.python-version }}
40
+ uses: actions/setup-python@v5
41
+ with:
42
+ python-version: ${{ matrix.python-version }}
43
+
44
+ - name: Install dependencies
45
+ run: |
46
+ pip install -e ".[dev]"
47
+
48
+ - name: Run tests
49
+ run: pytest
50
+
51
+ build:
52
+ name: Build
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - uses: actions/checkout@v4
56
+
57
+ - name: Set up Python
58
+ uses: actions/setup-python@v5
59
+ with:
60
+ python-version: "3.12"
61
+
62
+ - name: Install build dependencies
63
+ run: pip install build
64
+
65
+ - name: Build package
66
+ run: python -m build
67
+
68
+ - name: Check package
69
+ run: |
70
+ pip install twine
71
+ twine check dist/*
@@ -0,0 +1,77 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ target:
9
+ description: 'Publish target'
10
+ required: true
11
+ default: 'testpypi'
12
+ type: choice
13
+ options:
14
+ - testpypi
15
+ - pypi
16
+
17
+ jobs:
18
+ build:
19
+ name: Build distribution
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ - name: Install build dependencies
30
+ run: pip install build
31
+
32
+ - name: Build package
33
+ run: python -m build
34
+
35
+ - name: Upload distribution artifacts
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ publish-testpypi:
42
+ name: Publish to TestPyPI
43
+ needs: build
44
+ if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
45
+ runs-on: ubuntu-latest
46
+ environment: testpypi
47
+ permissions:
48
+ id-token: write
49
+ steps:
50
+ - name: Download distribution artifacts
51
+ uses: actions/download-artifact@v4
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+
56
+ - name: Publish to TestPyPI
57
+ uses: pypa/gh-action-pypi-publish@release/v1
58
+ with:
59
+ repository-url: https://test.pypi.org/legacy/
60
+
61
+ publish-pypi:
62
+ name: Publish to PyPI
63
+ needs: build
64
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
65
+ runs-on: ubuntu-latest
66
+ environment: pypi
67
+ permissions:
68
+ id-token: write
69
+ steps:
70
+ - name: Download distribution artifacts
71
+ uses: actions/download-artifact@v4
72
+ with:
73
+ name: dist
74
+ path: dist/
75
+
76
+ - name: Publish to PyPI
77
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anywidget-vector
3
- Version: 0.1.0
4
- Summary: Interactive vector visualization for Python notebooks using anywidget
3
+ Version: 0.2.1
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
7
7
  Author-email: "S.T. Grond" <widget@grafeo.dev>
@@ -30,7 +30,7 @@ Provides-Extra: dev
30
30
  Requires-Dist: marimo>=0.19.7; extra == 'dev'
31
31
  Requires-Dist: prek>=0.3.1; extra == 'dev'
32
32
  Requires-Dist: pytest>=9.0.2; extra == 'dev'
33
- Requires-Dist: ruff>=0.14.14; extra == 'dev'
33
+ Requires-Dist: ruff>=0.15.0; extra == 'dev'
34
34
  Requires-Dist: ty>=0.0.14; extra == 'dev'
35
35
  Provides-Extra: lancedb
36
36
  Requires-Dist: lancedb>=0.1; extra == 'lancedb'
@@ -264,6 +264,73 @@ widget = VectorSpace(
264
264
  )
265
265
  ```
266
266
 
267
+ ## Distance Metrics
268
+
269
+ Compute distances and visualize similarity relationships between points.
270
+
271
+ ### Supported Metrics
272
+
273
+ | Metric | Description |
274
+ |--------|-------------|
275
+ | `euclidean` | Straight-line distance (L2 norm) |
276
+ | `cosine` | Angle-based distance (1 - cosine similarity) |
277
+ | `manhattan` | Sum of absolute differences (L1 norm) |
278
+ | `dot_product` | Negative dot product (higher = closer) |
279
+
280
+ ### Color by Distance
281
+
282
+ ```python
283
+ # Color points by distance from a reference
284
+ widget.color_by_distance("point_a")
285
+ widget.color_by_distance("point_a", metric="cosine")
286
+ ```
287
+
288
+ ### Find Neighbors
289
+
290
+ ```python
291
+ # Find k nearest neighbors
292
+ neighbors = widget.find_neighbors("point_a", k=5)
293
+ # Returns: [("point_b", 0.1), ("point_c", 0.2), ...]
294
+
295
+ # Find neighbors within distance threshold
296
+ neighbors = widget.find_neighbors("point_a", threshold=0.5)
297
+ ```
298
+
299
+ ### Show Connections
300
+
301
+ ```python
302
+ # Draw lines to k-nearest neighbors
303
+ widget.show_neighbors("point_a", k=5)
304
+
305
+ # Draw lines to all points within threshold
306
+ widget.show_neighbors("point_a", threshold=0.3)
307
+
308
+ # Manual connection settings
309
+ widget = VectorSpace(
310
+ points=data,
311
+ show_connections=True,
312
+ k_neighbors=3,
313
+ distance_metric="cosine",
314
+ connection_color="#00ff00",
315
+ connection_opacity=0.5,
316
+ )
317
+ ```
318
+
319
+ ### Compute Distances
320
+
321
+ ```python
322
+ # Get distances from reference to all points
323
+ distances = widget.compute_distances("point_a")
324
+ # Returns: {"point_b": 0.1, "point_c": 0.5, ...}
325
+
326
+ # Use high-dimensional vectors (not just x,y,z)
327
+ distances = widget.compute_distances(
328
+ "point_a",
329
+ metric="cosine",
330
+ vector_field="embedding" # Use full embedding vector
331
+ )
332
+ ```
333
+
267
334
  ## Export
268
335
 
269
336
  ```python
@@ -216,6 +216,73 @@ widget = VectorSpace(
216
216
  )
217
217
  ```
218
218
 
219
+ ## Distance Metrics
220
+
221
+ Compute distances and visualize similarity relationships between points.
222
+
223
+ ### Supported Metrics
224
+
225
+ | Metric | Description |
226
+ |--------|-------------|
227
+ | `euclidean` | Straight-line distance (L2 norm) |
228
+ | `cosine` | Angle-based distance (1 - cosine similarity) |
229
+ | `manhattan` | Sum of absolute differences (L1 norm) |
230
+ | `dot_product` | Negative dot product (higher = closer) |
231
+
232
+ ### Color by Distance
233
+
234
+ ```python
235
+ # Color points by distance from a reference
236
+ widget.color_by_distance("point_a")
237
+ widget.color_by_distance("point_a", metric="cosine")
238
+ ```
239
+
240
+ ### Find Neighbors
241
+
242
+ ```python
243
+ # Find k nearest neighbors
244
+ neighbors = widget.find_neighbors("point_a", k=5)
245
+ # Returns: [("point_b", 0.1), ("point_c", 0.2), ...]
246
+
247
+ # Find neighbors within distance threshold
248
+ neighbors = widget.find_neighbors("point_a", threshold=0.5)
249
+ ```
250
+
251
+ ### Show Connections
252
+
253
+ ```python
254
+ # Draw lines to k-nearest neighbors
255
+ widget.show_neighbors("point_a", k=5)
256
+
257
+ # Draw lines to all points within threshold
258
+ widget.show_neighbors("point_a", threshold=0.3)
259
+
260
+ # Manual connection settings
261
+ widget = VectorSpace(
262
+ points=data,
263
+ show_connections=True,
264
+ k_neighbors=3,
265
+ distance_metric="cosine",
266
+ connection_color="#00ff00",
267
+ connection_opacity=0.5,
268
+ )
269
+ ```
270
+
271
+ ### Compute Distances
272
+
273
+ ```python
274
+ # Get distances from reference to all points
275
+ distances = widget.compute_distances("point_a")
276
+ # Returns: {"point_b": 0.1, "point_c": 0.5, ...}
277
+
278
+ # Use high-dimensional vectors (not just x,y,z)
279
+ distances = widget.compute_distances(
280
+ "point_a",
281
+ metric="cosine",
282
+ vector_field="embedding" # Use full embedding vector
283
+ )
284
+ ```
285
+
219
286
  ## Export
220
287
 
221
288
  ```python
@@ -0,0 +1,274 @@
1
+ # /// script
2
+ # requires-python = ">=3.12"
3
+ # dependencies = [
4
+ # "anywidget-vector==0.2.0",
5
+ # "marimo",
6
+ # ]
7
+ # ///
8
+ import marimo
9
+
10
+ __generated_with = "0.18.4"
11
+ app = marimo.App(width="medium")
12
+
13
+
14
+ @app.cell
15
+ def _():
16
+ import marimo as mo
17
+
18
+ return (mo,)
19
+
20
+
21
+ @app.cell
22
+ def _():
23
+ import random
24
+
25
+ from anywidget_vector import VectorSpace
26
+
27
+ return VectorSpace, random
28
+
29
+
30
+ @app.cell(hide_code=True)
31
+ def _(mo):
32
+ mo.md("""
33
+ # anywidget-vector Demo
34
+
35
+ Interactive 3D vector visualization with distance metrics and query UI.
36
+ """)
37
+ return
38
+
39
+
40
+ @app.cell
41
+ def _(VectorSpace, random):
42
+ # Generate sample clustered data
43
+ random.seed(42)
44
+
45
+ def generate_cluster(center, n=20, spread=0.15):
46
+ points = []
47
+ for i in range(n):
48
+ points.append(
49
+ {
50
+ "x": center[0] + random.gauss(0, spread),
51
+ "y": center[1] + random.gauss(0, spread),
52
+ "z": center[2] + random.gauss(0, spread),
53
+ "cluster": center[3],
54
+ }
55
+ )
56
+ return points
57
+
58
+ # Create 4 clusters
59
+ clusters = [
60
+ (0.3, 0.3, 0.3, "A"),
61
+ (0.7, 0.3, 0.7, "B"),
62
+ (0.3, 0.7, 0.7, "C"),
63
+ (0.7, 0.7, 0.3, "D"),
64
+ ]
65
+
66
+ points = []
67
+ for center in clusters:
68
+ points.extend(generate_cluster(center))
69
+
70
+ # Add IDs and labels
71
+ for i, p in enumerate(points):
72
+ p["id"] = f"point_{i}"
73
+ p["label"] = f"Point {i} ({p['cluster']})"
74
+ p["importance"] = random.random()
75
+
76
+ # Create widget with color by cluster
77
+ widget = VectorSpace(
78
+ points=points,
79
+ color_field="cluster",
80
+ size_field="importance",
81
+ size_range=[0.02, 0.06],
82
+ width=800,
83
+ height=500,
84
+ background="#0f0f1a",
85
+ )
86
+ widget
87
+ return points, widget
88
+
89
+
90
+ @app.cell(hide_code=True)
91
+ def _(mo):
92
+ mo.md("""
93
+ ## Distance Features
94
+
95
+ Click on a point above, then run the cells below to explore distance metrics.
96
+ """)
97
+ return
98
+
99
+
100
+ @app.cell
101
+ def _(widget):
102
+ # Get the currently selected point
103
+ selected = widget.selected_points
104
+ selected_id = selected[0] if selected else "point_0"
105
+ f"Selected point: {selected_id}"
106
+ return (selected_id,)
107
+
108
+
109
+ @app.cell
110
+ def _(selected_id, widget):
111
+ # Find 5 nearest neighbors using Euclidean distance
112
+ neighbors_euclidean = widget.find_neighbors(selected_id, k=5, metric="euclidean")
113
+ neighbors_euclidean
114
+ return
115
+
116
+
117
+ @app.cell
118
+ def _(selected_id, widget):
119
+ # Find 5 nearest neighbors using Cosine distance
120
+ neighbors_cosine = widget.find_neighbors(selected_id, k=5, metric="cosine")
121
+ neighbors_cosine
122
+ return
123
+
124
+
125
+ @app.cell
126
+ def _(VectorSpace, points):
127
+ # Create a second widget showing neighbor connections
128
+ widget2 = VectorSpace(
129
+ points=points,
130
+ color_field="cluster",
131
+ width=800,
132
+ height=500,
133
+ background="#0f0f1a",
134
+ # Enable k-nearest neighbor connections
135
+ show_connections=True,
136
+ k_neighbors=3,
137
+ distance_metric="euclidean",
138
+ connection_color="#44ff88",
139
+ connection_opacity=0.4,
140
+ )
141
+ widget2
142
+ return
143
+
144
+
145
+ @app.cell(hide_code=True)
146
+ def _(mo):
147
+ mo.md("""
148
+ ## Color by Distance
149
+
150
+ The widget below colors points by distance from a reference point.
151
+ """)
152
+ return
153
+
154
+
155
+ @app.cell
156
+ def _(VectorSpace, points):
157
+ # Create widget and color by distance from first point
158
+ widget3 = VectorSpace(
159
+ points=[dict(p) for p in points], # Copy points
160
+ width=800,
161
+ height=500,
162
+ background="#0f0f1a",
163
+ color_scale="plasma",
164
+ )
165
+ widget3.color_by_distance("point_0", metric="euclidean")
166
+ widget3.show_neighbors("point_0", k=5)
167
+ widget3
168
+ return
169
+
170
+
171
+ @app.cell(hide_code=True)
172
+ def _(mo):
173
+ mo.md("""
174
+ ## Different Shapes
175
+
176
+ Using shapes to encode an additional dimension.
177
+ """)
178
+ return
179
+
180
+
181
+ @app.cell
182
+ def _(VectorSpace, points):
183
+ # Create widget with shape mapping
184
+ widget4 = VectorSpace(
185
+ points=points,
186
+ color_field="cluster",
187
+ shape_field="cluster",
188
+ shape_map={
189
+ "A": "sphere",
190
+ "B": "cube",
191
+ "C": "cone",
192
+ "D": "octahedron",
193
+ },
194
+ size_range=[0.04, 0.04], # Fixed size
195
+ width=800,
196
+ height=500,
197
+ background="#0f0f1a",
198
+ )
199
+ widget4
200
+ return
201
+
202
+
203
+ @app.cell(hide_code=True)
204
+ def _(mo):
205
+ mo.md("""
206
+ ## Query UI
207
+
208
+ The widget below shows the query interface for vector database backends.
209
+ Enable `show_query_input=True` to show the toolbar.
210
+
211
+ **Supported backends:**
212
+ - Browser-side (REST API): Qdrant, Pinecone, Weaviate
213
+ - Python-side: Chroma, LanceDB, Grafeo
214
+
215
+ **Query types:**
216
+ - Text Search (requires embedding API)
217
+ - Find Similar (by vector ID)
218
+ - Raw Vector ([0.1, 0.2, ...])
219
+ - Filter (JSON filter expressions)
220
+ """)
221
+ return
222
+
223
+
224
+ @app.cell
225
+ def _(VectorSpace, points):
226
+ # Create widget with query UI enabled
227
+ widget5 = VectorSpace(
228
+ points=points,
229
+ color_field="cluster",
230
+ width=800,
231
+ height=500,
232
+ background="#0f0f1a",
233
+ # Enable query UI
234
+ show_query_input=True,
235
+ show_settings=True,
236
+ # Default to Qdrant backend
237
+ backend="qdrant",
238
+ )
239
+ widget5
240
+ return
241
+
242
+
243
+ @app.cell(hide_code=True)
244
+ def _(mo):
245
+ mo.md("""
246
+ ### Python-side Backend Example
247
+
248
+ For Python-side backends like Chroma, configure the client directly:
249
+
250
+ ```python
251
+ import chromadb
252
+
253
+ client = chromadb.Client()
254
+ collection = client.get_or_create_collection("my_vectors")
255
+
256
+ widget = VectorSpace(
257
+ show_query_input=True,
258
+ show_settings=True,
259
+ )
260
+ widget.set_backend("chroma", collection)
261
+
262
+ # Optional: set custom embedding function for text search
263
+ def my_embed(text):
264
+ # Your embedding logic here
265
+ return [0.1, 0.2, 0.3, ...]
266
+
267
+ widget.set_embedding(my_embed)
268
+ ```
269
+ """)
270
+ return
271
+
272
+
273
+ if __name__ == "__main__":
274
+ app.run()
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "anywidget-vector"
3
- version = "0.1.0"
4
- description = "Interactive vector visualization for Python notebooks using anywidget"
3
+ version = "0.2.1"
4
+ description = "Interactive 3D vector visualization with query UI for vector databases"
5
5
  readme = "README.md"
6
6
  license = { text = "Apache-2.0" }
7
7
  requires-python = ">=3.12"
@@ -28,7 +28,7 @@ dependencies = [
28
28
  dev = [
29
29
  "prek>=0.3.1",
30
30
  "pytest>=9.0.2",
31
- "ruff>=0.14.14",
31
+ "ruff>=0.15.0",
32
32
  "ty>=0.0.14",
33
33
  "marimo>=0.19.7",
34
34
  ]
@@ -3,4 +3,4 @@
3
3
  from anywidget_vector.widget import VectorSpace
4
4
 
5
5
  __all__ = ["VectorSpace"]
6
- __version__ = "0.1.0"
6
+ __version__ = "0.2.1"