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.
Files changed (67) hide show
  1. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/.gitignore +1 -0
  2. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/PKG-INFO +42 -14
  3. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/README.md +39 -12
  4. deckgl_marimo-0.3.0/examples/displacement.py +101 -0
  5. deckgl_marimo-0.3.0/examples/ellipse.py +103 -0
  6. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/pyproject.toml +3 -2
  7. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/__init__.py +9 -0
  8. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_base.py +33 -2
  9. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_map.py +2 -2
  10. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/__init__.py +7 -0
  11. deckgl_marimo-0.3.0/src/deckgl_marimo/layers/_composite.py +411 -0
  12. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/_core.py +8 -0
  13. deckgl_marimo-0.3.0/tests/test_base_layer.py +132 -0
  14. deckgl_marimo-0.3.0/tests/test_layers/test_composite.py +372 -0
  15. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/uv.lock +171 -4
  16. deckgl_marimo-0.2.0/examples/__marimo__/session/01_scatterplot.py.json +0 -126
  17. deckgl_marimo-0.2.0/examples/__marimo__/session/02_hexagon.py.json +0 -126
  18. deckgl_marimo-0.2.0/examples/__marimo__/session/03_geojson.py.json +0 -100
  19. deckgl_marimo-0.2.0/examples/__marimo__/session/04_heatmap.py.json +0 -126
  20. deckgl_marimo-0.2.0/examples/__marimo__/session/05_arc.py.json +0 -126
  21. deckgl_marimo-0.2.0/examples/__marimo__/session/06_path.py.json +0 -113
  22. deckgl_marimo-0.2.0/examples/__marimo__/session/07_polygon.py.json +0 -113
  23. deckgl_marimo-0.2.0/examples/__marimo__/session/08_column.py.json +0 -126
  24. deckgl_marimo-0.2.0/examples/__marimo__/session/09_icon.py.json +0 -113
  25. deckgl_marimo-0.2.0/examples/__marimo__/session/10_text.py.json +0 -113
  26. deckgl_marimo-0.2.0/examples/__marimo__/session/11_comprehensive.py.json +0 -217
  27. deckgl_marimo-0.2.0/examples/__marimo__/session/hexagon_example.py.json +0 -100
  28. deckgl_marimo-0.2.0/tests/test_base_layer.py +0 -70
  29. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/.github/workflows/ci.yml +0 -0
  30. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/.github/workflows/publish.yml +0 -0
  31. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/01_scatterplot.py +0 -0
  32. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/02_hexagon.py +0 -0
  33. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/03_geojson.py +0 -0
  34. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/04_heatmap.py +0 -0
  35. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/05_arc.py +0 -0
  36. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/06_path.py +0 -0
  37. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/07_polygon.py +0 -0
  38. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/08_column.py +0 -0
  39. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/09_icon.py +0 -0
  40. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/10_text.py +0 -0
  41. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/11_comprehensive.py +0 -0
  42. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/examples/hexagon_example.py +0 -0
  43. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/esbuild.config.mjs +0 -0
  44. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/package-lock.json +0 -0
  45. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/package.json +0 -0
  46. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/src/accessor-resolver.js +0 -0
  47. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/src/index.js +0 -0
  48. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/js/src/layer-factory.js +0 -0
  49. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_basemaps.py +0 -0
  50. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_data.py +0 -0
  51. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_utils.py +0 -0
  52. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/_view_state.py +0 -0
  53. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/controls/__init__.py +0 -0
  54. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/_aggregation.py +0 -0
  55. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/_geo.py +0 -0
  56. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/layers/_mesh.py +0 -0
  57. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/static/deckgl-marimo.bundle.css +0 -0
  58. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/static/deckgl-marimo.bundle.js +0 -0
  59. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/widget.css +0 -0
  60. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/widget.js +0 -0
  61. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/src/deckgl_marimo/widget.py +0 -0
  62. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_data.py +0 -0
  63. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_layers/__init__.py +0 -0
  64. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_layers/test_aggregation.py +0 -0
  65. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_layers/test_core.py +0 -0
  66. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_map.py +0 -0
  67. {deckgl_marimo-0.2.0 → deckgl_marimo-0.3.0}/tests/test_widget.py +0 -0
@@ -20,6 +20,7 @@ js/node_modules/
20
20
  .venv/
21
21
  venv/
22
22
 
23
+ __marimo__/
23
24
 
24
25
  # IDE
25
26
  .idea/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deckgl-marimo
3
- Version: 0.2.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.21.1
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
- - **33 deck.gl layer types** — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, and more
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
- You may see `h11._util.LocalProtocolError: Too little data for declared Content-Length`
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
- - **33 deck.gl layer types** — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, and more
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
- You may see `h11._util.LocalProtocolError: Too little data for declared Content-Length`
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.2.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.21.1",
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", "_props", "_map_kwargs",
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 = [layer.to_spec() for layer in self._layers]
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 = [layer.to_spec() for layer in self._layers]
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
  ]