deckgl-marimo 0.2.0__tar.gz → 0.3.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.
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/.gitignore +1 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/PKG-INFO +42 -14
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/README.md +39 -12
- deckgl_marimo-0.3.0/examples/displacement.py +101 -0
- deckgl_marimo-0.3.0/examples/ellipse.py +103 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/pyproject.toml +3 -2
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/__init__.py +9 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_base.py +33 -2
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_map.py +2 -2
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/__init__.py +7 -0
- deckgl_marimo-0.3.0/src/deckgl_marimo/layers/_composite.py +411 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/_core.py +8 -0
- deckgl_marimo-0.3.0/tests/test_base_layer.py +132 -0
- deckgl_marimo-0.3.0/tests/test_layers/test_composite.py +372 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/uv.lock +171 -4
- deckgl_marimo-0.2.0/examples/__marimo__/session/01_scatterplot.py.json +0 -126
- deckgl_marimo-0.2.0/examples/__marimo__/session/02_hexagon.py.json +0 -126
- deckgl_marimo-0.2.0/examples/__marimo__/session/03_geojson.py.json +0 -100
- deckgl_marimo-0.2.0/examples/__marimo__/session/04_heatmap.py.json +0 -126
- deckgl_marimo-0.2.0/examples/__marimo__/session/05_arc.py.json +0 -126
- deckgl_marimo-0.2.0/examples/__marimo__/session/06_path.py.json +0 -113
- deckgl_marimo-0.2.0/examples/__marimo__/session/07_polygon.py.json +0 -113
- deckgl_marimo-0.2.0/examples/__marimo__/session/08_column.py.json +0 -126
- deckgl_marimo-0.2.0/examples/__marimo__/session/09_icon.py.json +0 -113
- deckgl_marimo-0.2.0/examples/__marimo__/session/10_text.py.json +0 -113
- deckgl_marimo-0.2.0/examples/__marimo__/session/11_comprehensive.py.json +0 -217
- deckgl_marimo-0.2.0/examples/__marimo__/session/hexagon_example.py.json +0 -100
- deckgl_marimo-0.2.0/tests/test_base_layer.py +0 -70
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/.github/workflows/ci.yml +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/.github/workflows/publish.yml +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/01_scatterplot.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/02_hexagon.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/03_geojson.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/04_heatmap.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/05_arc.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/06_path.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/07_polygon.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/08_column.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/09_icon.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/10_text.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/11_comprehensive.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/hexagon_example.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/esbuild.config.mjs +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/package-lock.json +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/package.json +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/src/accessor-resolver.js +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/src/index.js +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/src/layer-factory.js +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_basemaps.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_data.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_utils.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_view_state.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/controls/__init__.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/_aggregation.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/_geo.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/_mesh.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/static/deckgl-marimo.bundle.css +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/static/deckgl-marimo.bundle.js +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/widget.css +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/widget.js +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/widget.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_data.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_layers/__init__.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_layers/test_aggregation.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_layers/test_core.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_map.py +0 -0
- {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_widget.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deckgl-marimo
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: deck.gl visualization library for marimo notebooks — interactive maps with 33 layer types
|
|
5
5
|
Project-URL: Homepage, https://github.com/kihaji/deckgl-marimo
|
|
6
6
|
Project-URL: Documentation, https://kihaji.github.io/deckgl-marimo
|
|
@@ -22,8 +22,9 @@ Classifier: Topic :: Scientific/Engineering :: GIS
|
|
|
22
22
|
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: anywidget>=0.9.0
|
|
25
|
-
Requires-Dist: marimo>=0.
|
|
25
|
+
Requires-Dist: marimo>=0.22
|
|
26
26
|
Requires-Dist: narwhals>=1.0.0
|
|
27
|
+
Requires-Dist: pandas>=2.3.3
|
|
27
28
|
Requires-Dist: traitlets>=5.0.0
|
|
28
29
|
Provides-Extra: dev
|
|
29
30
|
Requires-Dist: duckdb; extra == 'dev'
|
|
@@ -50,11 +51,12 @@ Interactive [deck.gl](https://deck.gl) visualization library for [marimo](https:
|
|
|
50
51
|
|
|
51
52
|
## Features
|
|
52
53
|
|
|
53
|
-
- **
|
|
54
|
+
- **10 deck.gl layer types** — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, and more
|
|
54
55
|
- **Multi-layer maps** — compose multiple layers on a single map
|
|
55
56
|
- **Standalone layers** — display any layer directly without explicit map setup
|
|
56
57
|
- **Marimo-native reactivity** — bind layer properties to sliders, dropdowns, and other widgets
|
|
57
58
|
- **Multiple data sources** — pandas, polars, geopandas, DuckDB, GeoJSON dicts, and URLs
|
|
59
|
+
- **Authenticated data loading** — pass HTTP headers, API keys, or credentials for remote data sources
|
|
58
60
|
- **Fully offline** — all JavaScript bundled in the package, no CDN dependencies
|
|
59
61
|
- **Viewport readback** — read the current map center, zoom, pitch, and bearing from Python
|
|
60
62
|
- **Click & hover events** — inspect picked objects reactively in downstream cells
|
|
@@ -160,6 +162,42 @@ rel = duckdb.sql("SELECT lon, lat, value FROM 'data.parquet' WHERE value > 100")
|
|
|
160
162
|
layer = dgl.ScatterplotLayer(data=rel, get_position=["lon", "lat"])
|
|
161
163
|
```
|
|
162
164
|
|
|
165
|
+
### Authenticated remote data
|
|
166
|
+
|
|
167
|
+
Any layer that loads data from a URL supports custom HTTP headers via
|
|
168
|
+
`fetch_headers`, or full control over the fetch request via `load_options`.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# Bearer token
|
|
172
|
+
layer = dgl.GeoJsonLayer(
|
|
173
|
+
data="https://secure-api.example.com/data.geojson",
|
|
174
|
+
fetch_headers={"Authorization": "Bearer my-token"},
|
|
175
|
+
get_fill_color=[0, 180, 230, 160],
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# API key
|
|
179
|
+
layer = dgl.GeoJsonLayer(
|
|
180
|
+
data="https://api.example.com/features",
|
|
181
|
+
fetch_headers={"X-API-Key": "abc123"},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Full fetch control (mTLS / CORS / custom options)
|
|
185
|
+
layer = dgl.GeoJsonLayer(
|
|
186
|
+
data="https://internal.example.com/data.geojson",
|
|
187
|
+
load_options={
|
|
188
|
+
"fetch": {
|
|
189
|
+
"credentials": "include",
|
|
190
|
+
"mode": "cors",
|
|
191
|
+
"headers": {"Authorization": "Bearer token"},
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Both parameters are available on all layer types via `BaseLayer`. When
|
|
198
|
+
`fetch_headers` and `load_options` both specify headers, the `load_options`
|
|
199
|
+
headers take precedence.
|
|
200
|
+
|
|
163
201
|
## Available layers
|
|
164
202
|
|
|
165
203
|
### Fully tested (10)
|
|
@@ -200,18 +238,8 @@ dgl.Map(basemap="https://my-tileserver.example.com/style.json")
|
|
|
200
238
|
|
|
201
239
|
### `Content-Length` errors in the marimo console
|
|
202
240
|
|
|
203
|
-
|
|
204
|
-
in the server console when a notebook loads. This is a known issue with uvicorn's
|
|
205
|
-
default `h11` HTTP parser serving the large JS bundle. The map still works correctly.
|
|
241
|
+
This is fixed in Marimo >= 0.22.0 please update to that.
|
|
206
242
|
|
|
207
|
-
To suppress it, install `httptools` so uvicorn uses a faster HTTP parser, then
|
|
208
|
-
**fully restart marimo** (kill and relaunch):
|
|
209
|
-
|
|
210
|
-
```bash
|
|
211
|
-
uv add httptools
|
|
212
|
-
# then restart marimo
|
|
213
|
-
uv run marimo edit your_notebook.py
|
|
214
|
-
```
|
|
215
243
|
|
|
216
244
|
## License
|
|
217
245
|
|
|
@@ -10,11 +10,12 @@ Interactive [deck.gl](https://deck.gl) visualization library for [marimo](https:
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **
|
|
13
|
+
- **10 deck.gl layer types** — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, and more
|
|
14
14
|
- **Multi-layer maps** — compose multiple layers on a single map
|
|
15
15
|
- **Standalone layers** — display any layer directly without explicit map setup
|
|
16
16
|
- **Marimo-native reactivity** — bind layer properties to sliders, dropdowns, and other widgets
|
|
17
17
|
- **Multiple data sources** — pandas, polars, geopandas, DuckDB, GeoJSON dicts, and URLs
|
|
18
|
+
- **Authenticated data loading** — pass HTTP headers, API keys, or credentials for remote data sources
|
|
18
19
|
- **Fully offline** — all JavaScript bundled in the package, no CDN dependencies
|
|
19
20
|
- **Viewport readback** — read the current map center, zoom, pitch, and bearing from Python
|
|
20
21
|
- **Click & hover events** — inspect picked objects reactively in downstream cells
|
|
@@ -120,6 +121,42 @@ rel = duckdb.sql("SELECT lon, lat, value FROM 'data.parquet' WHERE value > 100")
|
|
|
120
121
|
layer = dgl.ScatterplotLayer(data=rel, get_position=["lon", "lat"])
|
|
121
122
|
```
|
|
122
123
|
|
|
124
|
+
### Authenticated remote data
|
|
125
|
+
|
|
126
|
+
Any layer that loads data from a URL supports custom HTTP headers via
|
|
127
|
+
`fetch_headers`, or full control over the fetch request via `load_options`.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# Bearer token
|
|
131
|
+
layer = dgl.GeoJsonLayer(
|
|
132
|
+
data="https://secure-api.example.com/data.geojson",
|
|
133
|
+
fetch_headers={"Authorization": "Bearer my-token"},
|
|
134
|
+
get_fill_color=[0, 180, 230, 160],
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# API key
|
|
138
|
+
layer = dgl.GeoJsonLayer(
|
|
139
|
+
data="https://api.example.com/features",
|
|
140
|
+
fetch_headers={"X-API-Key": "abc123"},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Full fetch control (mTLS / CORS / custom options)
|
|
144
|
+
layer = dgl.GeoJsonLayer(
|
|
145
|
+
data="https://internal.example.com/data.geojson",
|
|
146
|
+
load_options={
|
|
147
|
+
"fetch": {
|
|
148
|
+
"credentials": "include",
|
|
149
|
+
"mode": "cors",
|
|
150
|
+
"headers": {"Authorization": "Bearer token"},
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Both parameters are available on all layer types via `BaseLayer`. When
|
|
157
|
+
`fetch_headers` and `load_options` both specify headers, the `load_options`
|
|
158
|
+
headers take precedence.
|
|
159
|
+
|
|
123
160
|
## Available layers
|
|
124
161
|
|
|
125
162
|
### Fully tested (10)
|
|
@@ -160,18 +197,8 @@ dgl.Map(basemap="https://my-tileserver.example.com/style.json")
|
|
|
160
197
|
|
|
161
198
|
### `Content-Length` errors in the marimo console
|
|
162
199
|
|
|
163
|
-
|
|
164
|
-
in the server console when a notebook loads. This is a known issue with uvicorn's
|
|
165
|
-
default `h11` HTTP parser serving the large JS bundle. The map still works correctly.
|
|
200
|
+
This is fixed in Marimo >= 0.22.0 please update to that.
|
|
166
201
|
|
|
167
|
-
To suppress it, install `httptools` so uvicorn uses a faster HTTP parser, then
|
|
168
|
-
**fully restart marimo** (kill and relaunch):
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
uv add httptools
|
|
172
|
-
# then restart marimo
|
|
173
|
-
uv run marimo edit your_notebook.py
|
|
174
|
-
```
|
|
175
202
|
|
|
176
203
|
## License
|
|
177
204
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = [
|
|
4
|
+
# "marimo",
|
|
5
|
+
# "pandas",
|
|
6
|
+
# "deckgl-marimo",
|
|
7
|
+
# ]
|
|
8
|
+
#
|
|
9
|
+
# [tool.uv.sources]
|
|
10
|
+
# deckgl-marimo = { path = ".." }
|
|
11
|
+
# ///
|
|
12
|
+
|
|
13
|
+
import marimo
|
|
14
|
+
|
|
15
|
+
__generated_with = "0.19.11"
|
|
16
|
+
app = marimo.App(width="full")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.cell
|
|
20
|
+
def _():
|
|
21
|
+
import marimo as mo
|
|
22
|
+
|
|
23
|
+
return (mo,)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.cell
|
|
27
|
+
def _():
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
return (pd,)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.cell
|
|
34
|
+
def _():
|
|
35
|
+
import deckgl_marimo as dgl
|
|
36
|
+
|
|
37
|
+
return (dgl,)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.cell
|
|
41
|
+
def _(mo, pd):
|
|
42
|
+
# Simulated vessel displacement data: recorded position vs reported (AIS) position
|
|
43
|
+
vessels = pd.DataFrame(
|
|
44
|
+
[
|
|
45
|
+
{"name": "Vessel A", "rec_lon": -90.10, "rec_lat": 29.95, "rep_lon": -90.07, "rep_lat": 29.97},
|
|
46
|
+
{"name": "Vessel B", "rec_lon": -89.95, "rec_lat": 30.00, "rep_lon": -89.90, "rep_lat": 29.96},
|
|
47
|
+
{"name": "Vessel C", "rec_lon": -90.20, "rec_lat": 29.90, "rep_lon": -90.25, "rep_lat": 29.88},
|
|
48
|
+
{"name": "Vessel D", "rec_lon": -89.80, "rec_lat": 30.05, "rep_lon": -89.82, "rep_lat": 30.10},
|
|
49
|
+
{"name": "Vessel E", "rec_lon": -90.05, "rec_lat": 29.85, "rep_lon": -90.00, "rep_lat": 29.80},
|
|
50
|
+
{"name": "Vessel F", "rec_lon": -90.30, "rec_lat": 30.10, "rep_lon": -90.35, "rep_lat": 30.15},
|
|
51
|
+
{"name": "Vessel G", "rec_lon": -89.70, "rec_lat": 29.92, "rep_lon": -89.65, "rep_lat": 29.95},
|
|
52
|
+
{"name": "Vessel H", "rec_lon": -90.15, "rec_lat": 30.02, "rep_lon": -90.12, "rep_lat": 30.06},
|
|
53
|
+
]
|
|
54
|
+
)
|
|
55
|
+
mo.md(
|
|
56
|
+
f"**DisplacementLayer** — **{len(vessels)}** vessels showing recorded vs reported positions"
|
|
57
|
+
)
|
|
58
|
+
return (vessels,)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.cell
|
|
62
|
+
def _(mo):
|
|
63
|
+
show_arcs = mo.ui.switch(value=True, label="Show Arcs")
|
|
64
|
+
show_origin = mo.ui.switch(value=True, label="Show Recorded (green)")
|
|
65
|
+
show_displaced = mo.ui.switch(value=True, label="Show Reported (red)")
|
|
66
|
+
mo.hstack([show_arcs, show_origin, show_displaced], justify="start", gap=2)
|
|
67
|
+
return show_arcs, show_displaced, show_origin
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.cell
|
|
71
|
+
def _(dgl, mo):
|
|
72
|
+
map_widget = dgl.Map(basemap="dark-matter", center=(-90.0, 29.95), zoom=10)
|
|
73
|
+
widget = mo.ui.anywidget(map_widget)
|
|
74
|
+
return map_widget, widget
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@app.cell
|
|
78
|
+
def _(widget):
|
|
79
|
+
widget
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.cell
|
|
84
|
+
def _(dgl, map_widget, show_arcs, show_displaced, show_origin, vessels):
|
|
85
|
+
map_widget.layer_specs = [
|
|
86
|
+
spec
|
|
87
|
+
for spec in dgl.DisplacementLayer(
|
|
88
|
+
data=vessels.to_dict("records"),
|
|
89
|
+
origin=["rec_lon", "rec_lat"],
|
|
90
|
+
displaced=["rep_lon", "rep_lat"],
|
|
91
|
+
show_arcs=show_arcs.value,
|
|
92
|
+
show_origin_dots=show_origin.value,
|
|
93
|
+
show_displaced_dots=show_displaced.value,
|
|
94
|
+
arc_width=2,
|
|
95
|
+
).to_specs()
|
|
96
|
+
]
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
app.run()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.10"
|
|
3
|
+
# dependencies = [
|
|
4
|
+
# "marimo",
|
|
5
|
+
# "pandas",
|
|
6
|
+
# "deckgl-marimo",
|
|
7
|
+
# ]
|
|
8
|
+
#
|
|
9
|
+
# [tool.uv.sources]
|
|
10
|
+
# deckgl-marimo = { path = ".." }
|
|
11
|
+
# ///
|
|
12
|
+
|
|
13
|
+
import marimo
|
|
14
|
+
|
|
15
|
+
__generated_with = "0.19.11"
|
|
16
|
+
app = marimo.App(width="full")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.cell
|
|
20
|
+
def _():
|
|
21
|
+
import marimo as mo
|
|
22
|
+
|
|
23
|
+
return (mo,)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.cell
|
|
27
|
+
def _():
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
return (pd,)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.cell
|
|
34
|
+
def _():
|
|
35
|
+
import deckgl_marimo as dgl
|
|
36
|
+
|
|
37
|
+
return (dgl,)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.cell
|
|
41
|
+
def _(mo, pd):
|
|
42
|
+
# Sample data: storm cells with elliptical uncertainty regions
|
|
43
|
+
storms = pd.DataFrame(
|
|
44
|
+
[
|
|
45
|
+
{"name": "Cell A", "lon": -90.10, "lat": 29.95, "major": 5000, "minor": 3000, "bearing": 45},
|
|
46
|
+
{"name": "Cell B", "lon": -89.80, "lat": 30.05, "major": 8000, "minor": 4000, "bearing": 120},
|
|
47
|
+
{"name": "Cell C", "lon": -90.30, "lat": 30.10, "major": 6000, "minor": 6000, "bearing": 0},
|
|
48
|
+
{"name": "Cell D", "lon": -89.95, "lat": 29.85, "major": 10000, "minor": 3000, "bearing": 90},
|
|
49
|
+
{"name": "Cell E", "lon": -90.20, "lat": 29.90, "major": 4000, "minor": 2000, "bearing": 160},
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
mo.md(
|
|
53
|
+
f"**EllipseLayer** — **{len(storms)}** storm cells with elliptical uncertainty regions (diameters in meters, 0=north clockwise)"
|
|
54
|
+
)
|
|
55
|
+
return (storms,)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@app.cell
|
|
59
|
+
def _(mo):
|
|
60
|
+
show_dots = mo.ui.switch(value=True, label="Show Centers")
|
|
61
|
+
filled = mo.ui.switch(value=True, label="Filled")
|
|
62
|
+
stroked = mo.ui.switch(value=True, label="Stroked")
|
|
63
|
+
mo.hstack([show_dots, filled, stroked], justify="start", gap=2)
|
|
64
|
+
return filled, show_dots, stroked
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.cell
|
|
68
|
+
def _(dgl, mo):
|
|
69
|
+
map_widget = dgl.Map(basemap="dark-matter", center=(-90.05, 29.97), zoom=10)
|
|
70
|
+
widget = mo.ui.anywidget(map_widget)
|
|
71
|
+
return map_widget, widget
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.cell
|
|
75
|
+
def _(widget):
|
|
76
|
+
widget
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.cell
|
|
81
|
+
def _(dgl, filled, map_widget, show_dots, storms, stroked):
|
|
82
|
+
map_widget.layer_specs = [
|
|
83
|
+
spec
|
|
84
|
+
for spec in dgl.EllipseLayer(
|
|
85
|
+
data=storms.to_dict("records"),
|
|
86
|
+
center=["lon", "lat"],
|
|
87
|
+
major_axis="major",
|
|
88
|
+
minor_axis="minor",
|
|
89
|
+
orientation="bearing",
|
|
90
|
+
units="meters",
|
|
91
|
+
show_center_dots=show_dots.value,
|
|
92
|
+
filled=filled.value,
|
|
93
|
+
stroked=stroked.value,
|
|
94
|
+
fill_color=[0, 140, 255, 80],
|
|
95
|
+
line_color=[0, 200, 255, 255],
|
|
96
|
+
line_width=2,
|
|
97
|
+
).to_specs()
|
|
98
|
+
]
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
app.run()
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deckgl-marimo"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "deck.gl visualization library for marimo notebooks — interactive maps with 33 layer types"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -30,7 +30,8 @@ dependencies = [
|
|
|
30
30
|
"anywidget>=0.9.0",
|
|
31
31
|
"traitlets>=5.0.0",
|
|
32
32
|
"narwhals>=1.0.0",
|
|
33
|
-
"marimo>=0.
|
|
33
|
+
"marimo>=0.22",
|
|
34
|
+
"pandas>=2.3.3",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.urls]
|
|
@@ -26,6 +26,12 @@ from deckgl_marimo.layers._aggregation import (
|
|
|
26
26
|
HexagonLayer,
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
+
# Composite layers
|
|
30
|
+
from deckgl_marimo.layers._composite import (
|
|
31
|
+
DisplacementLayer,
|
|
32
|
+
EllipseLayer,
|
|
33
|
+
)
|
|
34
|
+
|
|
29
35
|
# Backward compatibility
|
|
30
36
|
from deckgl_marimo.widget import DeckGLHexagonWidget
|
|
31
37
|
|
|
@@ -46,6 +52,9 @@ __all__ = [
|
|
|
46
52
|
# Aggregation layers
|
|
47
53
|
"HeatmapLayer",
|
|
48
54
|
"HexagonLayer",
|
|
55
|
+
# Composite layers
|
|
56
|
+
"DisplacementLayer",
|
|
57
|
+
"EllipseLayer",
|
|
49
58
|
# Backward compat
|
|
50
59
|
"DeckGLHexagonWidget",
|
|
51
60
|
]
|
|
@@ -32,6 +32,16 @@ class BaseLayer:
|
|
|
32
32
|
Whether the layer responds to pointer events.
|
|
33
33
|
auto_highlight
|
|
34
34
|
Whether to highlight the picked object.
|
|
35
|
+
load_options
|
|
36
|
+
Options passed to deck.gl's loaders.gl data loading system.
|
|
37
|
+
Use this for full control over fetch configuration, e.g.
|
|
38
|
+
``{"fetch": {"credentials": "include", "headers": {...}}}``.
|
|
39
|
+
fetch_headers
|
|
40
|
+
Convenience shortcut to set HTTP headers on the data fetch
|
|
41
|
+
request. Equivalent to
|
|
42
|
+
``load_options={"fetch": {"headers": {...}}}``.
|
|
43
|
+
If both ``fetch_headers`` and ``load_options`` specify headers,
|
|
44
|
+
the ``load_options`` headers take precedence.
|
|
35
45
|
basemap
|
|
36
46
|
Basemap style for standalone display (default ``"dark-matter"``).
|
|
37
47
|
center
|
|
@@ -64,6 +74,8 @@ class BaseLayer:
|
|
|
64
74
|
opacity: float = 1.0,
|
|
65
75
|
pickable: bool = True,
|
|
66
76
|
auto_highlight: bool = False,
|
|
77
|
+
load_options: dict[str, Any] | None = None,
|
|
78
|
+
fetch_headers: dict[str, str] | None = None,
|
|
67
79
|
basemap: str = "dark-matter",
|
|
68
80
|
center: tuple[float, float] | None = None,
|
|
69
81
|
zoom: float = 1.0,
|
|
@@ -78,6 +90,8 @@ class BaseLayer:
|
|
|
78
90
|
self.opacity = opacity
|
|
79
91
|
self.pickable = pickable
|
|
80
92
|
self.auto_highlight = auto_highlight
|
|
93
|
+
self.load_options = load_options
|
|
94
|
+
self.fetch_headers = fetch_headers
|
|
81
95
|
self._props = props
|
|
82
96
|
|
|
83
97
|
# Map parameters for standalone display
|
|
@@ -119,8 +133,25 @@ class BaseLayer:
|
|
|
119
133
|
camel_key = to_camel_case(key)
|
|
120
134
|
spec[camel_key] = value
|
|
121
135
|
|
|
136
|
+
# Build loadOptions from explicit params + convenience params
|
|
137
|
+
effective_load_options = dict(self.load_options) if self.load_options else {}
|
|
138
|
+
if self.fetch_headers:
|
|
139
|
+
fetch_opts = effective_load_options.setdefault("fetch", {})
|
|
140
|
+
merged_headers = {**self.fetch_headers, **fetch_opts.get("headers", {})}
|
|
141
|
+
fetch_opts["headers"] = merged_headers
|
|
142
|
+
if effective_load_options:
|
|
143
|
+
spec["loadOptions"] = effective_load_options
|
|
144
|
+
|
|
122
145
|
return spec
|
|
123
146
|
|
|
147
|
+
def to_specs(self) -> list[dict]:
|
|
148
|
+
"""Serialize to a list of JSON-compatible specs.
|
|
149
|
+
|
|
150
|
+
Composite layers override this to return multiple specs.
|
|
151
|
+
Simple layers return a single-element list.
|
|
152
|
+
"""
|
|
153
|
+
return [self.to_spec()]
|
|
154
|
+
|
|
124
155
|
def _get_map(self) -> Any:
|
|
125
156
|
"""Get or create the backing Map widget for anywidget compatibility."""
|
|
126
157
|
if self.__map is None:
|
|
@@ -139,8 +170,8 @@ class BaseLayer:
|
|
|
139
170
|
# Avoid infinite recursion for our own attributes
|
|
140
171
|
if name.startswith("_BaseLayer") or name in (
|
|
141
172
|
"id", "data", "visible", "opacity", "pickable",
|
|
142
|
-
"auto_highlight", "
|
|
143
|
-
"LAYER_TYPE", "_MAP_KEYS",
|
|
173
|
+
"auto_highlight", "load_options", "fetch_headers",
|
|
174
|
+
"_props", "_map_kwargs", "LAYER_TYPE", "_MAP_KEYS",
|
|
144
175
|
):
|
|
145
176
|
raise AttributeError(name)
|
|
146
177
|
|
|
@@ -91,7 +91,7 @@ class Map(anywidget.AnyWidget):
|
|
|
91
91
|
**kwargs: Any,
|
|
92
92
|
) -> None:
|
|
93
93
|
self._layers: list[BaseLayer] = list(layers or [])
|
|
94
|
-
specs = [
|
|
94
|
+
specs = [spec for layer in self._layers for spec in layer.to_specs()]
|
|
95
95
|
style = Basemaps.resolve(basemap)
|
|
96
96
|
|
|
97
97
|
init_kwargs: dict[str, Any] = {
|
|
@@ -172,4 +172,4 @@ class Map(anywidget.AnyWidget):
|
|
|
172
172
|
|
|
173
173
|
def _sync_layers(self) -> None:
|
|
174
174
|
"""Re-serialize all layers and update the traitlet."""
|
|
175
|
-
self.layer_specs = [
|
|
175
|
+
self.layer_specs = [spec for layer in self._layers for spec in layer.to_specs()]
|
|
@@ -38,6 +38,10 @@ from deckgl_marimo.layers._mesh import (
|
|
|
38
38
|
ScenegraphLayer,
|
|
39
39
|
SimpleMeshLayer,
|
|
40
40
|
)
|
|
41
|
+
from deckgl_marimo.layers._composite import (
|
|
42
|
+
DisplacementLayer,
|
|
43
|
+
EllipseLayer,
|
|
44
|
+
)
|
|
41
45
|
|
|
42
46
|
__all__ = [
|
|
43
47
|
# Core
|
|
@@ -74,4 +78,7 @@ __all__ = [
|
|
|
74
78
|
# Mesh
|
|
75
79
|
"ScenegraphLayer",
|
|
76
80
|
"SimpleMeshLayer",
|
|
81
|
+
# Composite
|
|
82
|
+
"DisplacementLayer",
|
|
83
|
+
"EllipseLayer",
|
|
77
84
|
]
|