deckgl-marimo 0.2.0__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 (83) hide show
  1. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/.gitignore +4 -0
  2. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/PKG-INFO +138 -29
  3. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/README.md +133 -27
  4. deckgl_marimo-0.4.0/docs/binary-transfer-optimization-research.md +282 -0
  5. deckgl_marimo-0.4.0/examples/arc_binary_compare.py +341 -0
  6. deckgl_marimo-0.4.0/examples/column_binary_compare.py +355 -0
  7. deckgl_marimo-0.4.0/examples/displacement.py +101 -0
  8. deckgl_marimo-0.4.0/examples/ellipse.py +103 -0
  9. deckgl_marimo-0.4.0/examples/generate_1m_polygons.py +77 -0
  10. deckgl_marimo-0.4.0/examples/line_binary_compare.py +330 -0
  11. deckgl_marimo-0.4.0/examples/path_binary_compare.py +374 -0
  12. deckgl_marimo-0.4.0/examples/pointcloud_binary_compare.py +339 -0
  13. deckgl_marimo-0.4.0/examples/polygon_1m_stress.py +227 -0
  14. deckgl_marimo-0.4.0/examples/polygon_binary_compare.py +390 -0
  15. deckgl_marimo-0.4.0/examples/polygon_perf.py +296 -0
  16. deckgl_marimo-0.4.0/examples/polygon_stress.py +261 -0
  17. deckgl_marimo-0.4.0/examples/scatterplot_binary_compare.py +344 -0
  18. deckgl_marimo-0.4.0/js/src/index.js +257 -0
  19. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/js/src/layer-factory.js +68 -3
  20. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/pyproject.toml +4 -2
  21. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/__init__.py +15 -0
  22. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/_base.py +45 -3
  23. deckgl_marimo-0.4.0/src/deckgl_marimo/_binary.py +168 -0
  24. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/_map.py +42 -2
  25. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/layers/__init__.py +7 -0
  26. deckgl_marimo-0.4.0/src/deckgl_marimo/layers/_composite.py +411 -0
  27. deckgl_marimo-0.4.0/src/deckgl_marimo/layers/_core.py +1005 -0
  28. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/static/deckgl-marimo.bundle.js +165 -165
  29. deckgl_marimo-0.4.0/tests/test_base_layer.py +132 -0
  30. deckgl_marimo-0.4.0/tests/test_binary.py +320 -0
  31. deckgl_marimo-0.4.0/tests/test_layers/test_composite.py +372 -0
  32. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/uv.lock +178 -6
  33. deckgl_marimo-0.2.0/examples/__marimo__/session/01_scatterplot.py.json +0 -126
  34. deckgl_marimo-0.2.0/examples/__marimo__/session/02_hexagon.py.json +0 -126
  35. deckgl_marimo-0.2.0/examples/__marimo__/session/03_geojson.py.json +0 -100
  36. deckgl_marimo-0.2.0/examples/__marimo__/session/04_heatmap.py.json +0 -126
  37. deckgl_marimo-0.2.0/examples/__marimo__/session/05_arc.py.json +0 -126
  38. deckgl_marimo-0.2.0/examples/__marimo__/session/06_path.py.json +0 -113
  39. deckgl_marimo-0.2.0/examples/__marimo__/session/07_polygon.py.json +0 -113
  40. deckgl_marimo-0.2.0/examples/__marimo__/session/08_column.py.json +0 -126
  41. deckgl_marimo-0.2.0/examples/__marimo__/session/09_icon.py.json +0 -113
  42. deckgl_marimo-0.2.0/examples/__marimo__/session/10_text.py.json +0 -113
  43. deckgl_marimo-0.2.0/examples/__marimo__/session/11_comprehensive.py.json +0 -217
  44. deckgl_marimo-0.2.0/examples/__marimo__/session/hexagon_example.py.json +0 -100
  45. deckgl_marimo-0.2.0/js/src/index.js +0 -148
  46. deckgl_marimo-0.2.0/src/deckgl_marimo/layers/_core.py +0 -574
  47. deckgl_marimo-0.2.0/tests/test_base_layer.py +0 -70
  48. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/.github/workflows/ci.yml +0 -0
  49. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/.github/workflows/publish.yml +0 -0
  50. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/01_scatterplot.py +0 -0
  51. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/02_hexagon.py +0 -0
  52. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/03_geojson.py +0 -0
  53. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/04_heatmap.py +0 -0
  54. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/05_arc.py +0 -0
  55. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/06_path.py +0 -0
  56. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/07_polygon.py +0 -0
  57. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/08_column.py +0 -0
  58. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/09_icon.py +0 -0
  59. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/10_text.py +0 -0
  60. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/11_comprehensive.py +0 -0
  61. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/examples/hexagon_example.py +0 -0
  62. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/js/esbuild.config.mjs +0 -0
  63. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/js/package-lock.json +0 -0
  64. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/js/package.json +0 -0
  65. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/js/src/accessor-resolver.js +0 -0
  66. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/_basemaps.py +0 -0
  67. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/_data.py +0 -0
  68. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/_utils.py +0 -0
  69. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/_view_state.py +0 -0
  70. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/controls/__init__.py +0 -0
  71. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/layers/_aggregation.py +0 -0
  72. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/layers/_geo.py +0 -0
  73. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/layers/_mesh.py +0 -0
  74. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/static/deckgl-marimo.bundle.css +0 -0
  75. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/widget.css +0 -0
  76. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/widget.js +0 -0
  77. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/src/deckgl_marimo/widget.py +0 -0
  78. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/tests/test_data.py +0 -0
  79. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/tests/test_layers/__init__.py +0 -0
  80. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/tests/test_layers/test_aggregation.py +0 -0
  81. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/tests/test_layers/test_core.py +0 -0
  82. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.0}/tests/test_map.py +0 -0
  83. {deckgl_marimo-0.2.0 → deckgl_marimo-0.4.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/
@@ -39,3 +40,6 @@ htmlcov/
39
40
 
40
41
  # mypy / type checking
41
42
  .mypy_cache/
43
+
44
+ # Large test data files
45
+ examples/data/*.npz
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deckgl-marimo
3
- Version: 0.2.0
3
+ Version: 0.4.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,9 +22,12 @@ 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
29
+ Provides-Extra: binary
30
+ Requires-Dist: numpy; extra == 'binary'
28
31
  Provides-Extra: dev
29
32
  Requires-Dist: duckdb; extra == 'dev'
30
33
  Requires-Dist: geopandas; extra == 'dev'
@@ -50,11 +53,14 @@ Interactive [deck.gl](https://deck.gl) visualization library for [marimo](https:
50
53
 
51
54
  ## Features
52
55
 
53
- - **33 deck.gl layer types** — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, and more
56
+ - **12 deck.gl layer types** — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, lines, point clouds, and more
57
+ - **Binary data transfer** — bypass JSON serialization for large datasets (30x faster, 4x smaller payloads)
54
58
  - **Multi-layer maps** — compose multiple layers on a single map
55
59
  - **Standalone layers** — display any layer directly without explicit map setup
56
60
  - **Marimo-native reactivity** — bind layer properties to sliders, dropdowns, and other widgets
61
+ - **Performance metrics** — built-in FPS counter and frame time tracking via `perf_metrics` traitlet
57
62
  - **Multiple data sources** — pandas, polars, geopandas, DuckDB, GeoJSON dicts, and URLs
63
+ - **Authenticated data loading** — pass HTTP headers, API keys, or credentials for remote data sources
58
64
  - **Fully offline** — all JavaScript bundled in the package, no CDN dependencies
59
65
  - **Viewport readback** — read the current map center, zoom, pitch, and bearing from Python
60
66
  - **Click & hover events** — inspect picked objects reactively in downstream cells
@@ -160,30 +166,143 @@ rel = duckdb.sql("SELECT lon, lat, value FROM 'data.parquet' WHERE value > 100")
160
166
  layer = dgl.ScatterplotLayer(data=rel, get_position=["lon", "lat"])
161
167
  ```
162
168
 
169
+ ### Binary data for large datasets
170
+
171
+ For datasets with 100k+ rows, binary data transfer bypasses JSON serialization entirely, sending typed arrays directly to the GPU via deck.gl's native binary format.
172
+
173
+ **Using `use_binary=True` (automatic packing from list-of-dicts):**
174
+
175
+ ```python
176
+ layer = dgl.ScatterplotLayer(
177
+ data=large_df.to_dict("records"),
178
+ get_position=["lon", "lat"],
179
+ get_fill_color="color",
180
+ get_radius="radius",
181
+ use_binary=True,
182
+ )
183
+ ```
184
+
185
+ **Using pre-built numpy arrays (fastest — zero dict iteration):**
186
+
187
+ ```python
188
+ import numpy as np
189
+ from deckgl_marimo._binary import pack_binary
190
+
191
+ # Prepare arrays
192
+ positions = np.column_stack([lons, lats]).astype(np.float32)
193
+ colors = np.array(color_data, dtype=np.uint8) # (n, 4)
194
+
195
+ # Create layer spec (no data — binary provides it)
196
+ layer = dgl.ScatterplotLayer(
197
+ get_fill_color=[255, 140, 0], # constant props still go in spec
198
+ radius_min_pixels=2,
199
+ use_binary=True,
200
+ )
201
+ spec = layer.to_spec()
202
+
203
+ # Pack binary buffer
204
+ meta, buf = pack_binary(
205
+ n=len(positions),
206
+ attributes={
207
+ "getPosition": (positions, "float32", 2),
208
+ "getFillColor": (colors, "uint8", 4),
209
+ },
210
+ )
211
+ meta["id"] = spec["id"]
212
+
213
+ # Send to map
214
+ map_widget.binary_metadata = {"layers": [meta]}
215
+ map_widget.binary_data = buf
216
+ map_widget.layer_specs = [spec]
217
+ ```
218
+
219
+ **Performance at 200k polygons:**
220
+
221
+ | Mode | Serialization | Payload | Speedup |
222
+ |------|--------------|---------|---------|
223
+ | JSON | 1,167 ms | 62 MB | — |
224
+ | Binary | 39 ms | 14.5 MB | 30x faster, 4.3x smaller |
225
+
226
+ Binary data is supported on: `ScatterplotLayer`, `PolygonLayer`, `PathLayer`, `ArcLayer`, `LineLayer`, `ColumnLayer`, and `PointCloudLayer`.
227
+
228
+ ### Performance metrics
229
+
230
+ The `Map` widget includes a built-in FPS counter that reports metrics back to Python:
231
+
232
+ ```python
233
+ map_widget = dgl.Map(basemap="dark-matter", center=(0, 0), zoom=1)
234
+ widget = mo.ui.anywidget(map_widget)
235
+
236
+ # Read performance metrics (updated every 500ms)
237
+ perf = widget.value.get("perf_metrics", {})
238
+ fps = perf.get("fps") # frames per second
239
+ frame_time = perf.get("frameTimeAvg") # ms per frame
240
+ ```
241
+
242
+ ### Authenticated remote data
243
+
244
+ Any layer that loads data from a URL supports custom HTTP headers via
245
+ `fetch_headers`, or full control over the fetch request via `load_options`.
246
+
247
+ ```python
248
+ # Bearer token
249
+ layer = dgl.GeoJsonLayer(
250
+ data="https://secure-api.example.com/data.geojson",
251
+ fetch_headers={"Authorization": "Bearer my-token"},
252
+ get_fill_color=[0, 180, 230, 160],
253
+ )
254
+
255
+ # API key
256
+ layer = dgl.GeoJsonLayer(
257
+ data="https://api.example.com/features",
258
+ fetch_headers={"X-API-Key": "abc123"},
259
+ )
260
+
261
+ # Full fetch control (mTLS / CORS / custom options)
262
+ layer = dgl.GeoJsonLayer(
263
+ data="https://internal.example.com/data.geojson",
264
+ load_options={
265
+ "fetch": {
266
+ "credentials": "include",
267
+ "mode": "cors",
268
+ "headers": {"Authorization": "Bearer token"},
269
+ }
270
+ },
271
+ )
272
+ ```
273
+
274
+ Both parameters are available on all layer types via `BaseLayer`. When
275
+ `fetch_headers` and `load_options` both specify headers, the `load_options`
276
+ headers take precedence.
277
+
163
278
  ## Available layers
164
279
 
165
- ### Fully tested (10)
280
+ ### Fully tested (12)
281
+
282
+ | Layer | Use case | Binary support |
283
+ |-------|----------|----------------|
284
+ | `ScatterplotLayer` | Point data | Yes |
285
+ | `GeoJsonLayer` | Polygons, lines, points from GeoJSON/GeoDataFrame | — |
286
+ | `ArcLayer` | Origin-destination flows | Yes |
287
+ | `PathLayer` | Routes, trajectories | Yes |
288
+ | `PolygonLayer` | Filled regions | Yes |
289
+ | `IconLayer` | Marker icons | — |
290
+ | `TextLayer` | Labels | — |
291
+ | `ColumnLayer` | 3D bars on map | Yes |
292
+ | `HexagonLayer` | Hexagonal binning | — |
293
+ | `HeatmapLayer` | Density visualization | — |
294
+ | `LineLayer` | Straight lines between point pairs | Yes |
295
+ | `PointCloudLayer` | 3D point clouds (LiDAR, etc.) | Yes |
166
296
 
167
- | Layer | Use case |
168
- |-------|----------|
169
- | `ScatterplotLayer` | Point data |
170
- | `GeoJsonLayer` | Polygons, lines, points from GeoJSON/GeoDataFrame |
171
- | `ArcLayer` | Origin-destination flows |
172
- | `PathLayer` | Routes, trajectories |
173
- | `PolygonLayer` | Filled regions |
174
- | `IconLayer` | Marker icons |
175
- | `TextLayer` | Labels |
176
- | `ColumnLayer` | 3D bars on map |
177
- | `HexagonLayer` | Hexagonal binning |
178
- | `HeatmapLayer` | Density visualization |
297
+ `LineLayer` and `PointCloudLayer` are newly exported experimental layers.
179
298
 
180
- ### Experimental (23)
299
+ ### Experimental (21+)
181
300
 
182
301
  All additional deck.gl layers are available as experimental stubs via `deckgl_marimo.layers`:
183
302
 
184
303
  ```python
185
304
  from deckgl_marimo.layers import TripsLayer, MVTLayer, H3HexagonLayer, ContourLayer
186
- # ... and 19 more
305
+ # ... and more
187
306
  ```
188
307
 
189
308
  ## Basemaps
@@ -200,18 +319,8 @@ dgl.Map(basemap="https://my-tileserver.example.com/style.json")
200
319
 
201
320
  ### `Content-Length` errors in the marimo console
202
321
 
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.
206
-
207
- To suppress it, install `httptools` so uvicorn uses a faster HTTP parser, then
208
- **fully restart marimo** (kill and relaunch):
322
+ This is fixed in Marimo >= 0.22.0 please update to that.
209
323
 
210
- ```bash
211
- uv add httptools
212
- # then restart marimo
213
- uv run marimo edit your_notebook.py
214
- ```
215
324
 
216
325
  ## License
217
326
 
@@ -10,11 +10,14 @@ 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
+ - **12 deck.gl layer types** — scatter plots, hexagonal bins, heatmaps, arcs, paths, polygons, GeoJSON, 3D columns, lines, point clouds, and more
14
+ - **Binary data transfer** — bypass JSON serialization for large datasets (30x faster, 4x smaller payloads)
14
15
  - **Multi-layer maps** — compose multiple layers on a single map
15
16
  - **Standalone layers** — display any layer directly without explicit map setup
16
17
  - **Marimo-native reactivity** — bind layer properties to sliders, dropdowns, and other widgets
18
+ - **Performance metrics** — built-in FPS counter and frame time tracking via `perf_metrics` traitlet
17
19
  - **Multiple data sources** — pandas, polars, geopandas, DuckDB, GeoJSON dicts, and URLs
20
+ - **Authenticated data loading** — pass HTTP headers, API keys, or credentials for remote data sources
18
21
  - **Fully offline** — all JavaScript bundled in the package, no CDN dependencies
19
22
  - **Viewport readback** — read the current map center, zoom, pitch, and bearing from Python
20
23
  - **Click & hover events** — inspect picked objects reactively in downstream cells
@@ -120,30 +123,143 @@ rel = duckdb.sql("SELECT lon, lat, value FROM 'data.parquet' WHERE value > 100")
120
123
  layer = dgl.ScatterplotLayer(data=rel, get_position=["lon", "lat"])
121
124
  ```
122
125
 
126
+ ### Binary data for large datasets
127
+
128
+ For datasets with 100k+ rows, binary data transfer bypasses JSON serialization entirely, sending typed arrays directly to the GPU via deck.gl's native binary format.
129
+
130
+ **Using `use_binary=True` (automatic packing from list-of-dicts):**
131
+
132
+ ```python
133
+ layer = dgl.ScatterplotLayer(
134
+ data=large_df.to_dict("records"),
135
+ get_position=["lon", "lat"],
136
+ get_fill_color="color",
137
+ get_radius="radius",
138
+ use_binary=True,
139
+ )
140
+ ```
141
+
142
+ **Using pre-built numpy arrays (fastest — zero dict iteration):**
143
+
144
+ ```python
145
+ import numpy as np
146
+ from deckgl_marimo._binary import pack_binary
147
+
148
+ # Prepare arrays
149
+ positions = np.column_stack([lons, lats]).astype(np.float32)
150
+ colors = np.array(color_data, dtype=np.uint8) # (n, 4)
151
+
152
+ # Create layer spec (no data — binary provides it)
153
+ layer = dgl.ScatterplotLayer(
154
+ get_fill_color=[255, 140, 0], # constant props still go in spec
155
+ radius_min_pixels=2,
156
+ use_binary=True,
157
+ )
158
+ spec = layer.to_spec()
159
+
160
+ # Pack binary buffer
161
+ meta, buf = pack_binary(
162
+ n=len(positions),
163
+ attributes={
164
+ "getPosition": (positions, "float32", 2),
165
+ "getFillColor": (colors, "uint8", 4),
166
+ },
167
+ )
168
+ meta["id"] = spec["id"]
169
+
170
+ # Send to map
171
+ map_widget.binary_metadata = {"layers": [meta]}
172
+ map_widget.binary_data = buf
173
+ map_widget.layer_specs = [spec]
174
+ ```
175
+
176
+ **Performance at 200k polygons:**
177
+
178
+ | Mode | Serialization | Payload | Speedup |
179
+ |------|--------------|---------|---------|
180
+ | JSON | 1,167 ms | 62 MB | — |
181
+ | Binary | 39 ms | 14.5 MB | 30x faster, 4.3x smaller |
182
+
183
+ Binary data is supported on: `ScatterplotLayer`, `PolygonLayer`, `PathLayer`, `ArcLayer`, `LineLayer`, `ColumnLayer`, and `PointCloudLayer`.
184
+
185
+ ### Performance metrics
186
+
187
+ The `Map` widget includes a built-in FPS counter that reports metrics back to Python:
188
+
189
+ ```python
190
+ map_widget = dgl.Map(basemap="dark-matter", center=(0, 0), zoom=1)
191
+ widget = mo.ui.anywidget(map_widget)
192
+
193
+ # Read performance metrics (updated every 500ms)
194
+ perf = widget.value.get("perf_metrics", {})
195
+ fps = perf.get("fps") # frames per second
196
+ frame_time = perf.get("frameTimeAvg") # ms per frame
197
+ ```
198
+
199
+ ### Authenticated remote data
200
+
201
+ Any layer that loads data from a URL supports custom HTTP headers via
202
+ `fetch_headers`, or full control over the fetch request via `load_options`.
203
+
204
+ ```python
205
+ # Bearer token
206
+ layer = dgl.GeoJsonLayer(
207
+ data="https://secure-api.example.com/data.geojson",
208
+ fetch_headers={"Authorization": "Bearer my-token"},
209
+ get_fill_color=[0, 180, 230, 160],
210
+ )
211
+
212
+ # API key
213
+ layer = dgl.GeoJsonLayer(
214
+ data="https://api.example.com/features",
215
+ fetch_headers={"X-API-Key": "abc123"},
216
+ )
217
+
218
+ # Full fetch control (mTLS / CORS / custom options)
219
+ layer = dgl.GeoJsonLayer(
220
+ data="https://internal.example.com/data.geojson",
221
+ load_options={
222
+ "fetch": {
223
+ "credentials": "include",
224
+ "mode": "cors",
225
+ "headers": {"Authorization": "Bearer token"},
226
+ }
227
+ },
228
+ )
229
+ ```
230
+
231
+ Both parameters are available on all layer types via `BaseLayer`. When
232
+ `fetch_headers` and `load_options` both specify headers, the `load_options`
233
+ headers take precedence.
234
+
123
235
  ## Available layers
124
236
 
125
- ### Fully tested (10)
237
+ ### Fully tested (12)
238
+
239
+ | Layer | Use case | Binary support |
240
+ |-------|----------|----------------|
241
+ | `ScatterplotLayer` | Point data | Yes |
242
+ | `GeoJsonLayer` | Polygons, lines, points from GeoJSON/GeoDataFrame | — |
243
+ | `ArcLayer` | Origin-destination flows | Yes |
244
+ | `PathLayer` | Routes, trajectories | Yes |
245
+ | `PolygonLayer` | Filled regions | Yes |
246
+ | `IconLayer` | Marker icons | — |
247
+ | `TextLayer` | Labels | — |
248
+ | `ColumnLayer` | 3D bars on map | Yes |
249
+ | `HexagonLayer` | Hexagonal binning | — |
250
+ | `HeatmapLayer` | Density visualization | — |
251
+ | `LineLayer` | Straight lines between point pairs | Yes |
252
+ | `PointCloudLayer` | 3D point clouds (LiDAR, etc.) | Yes |
126
253
 
127
- | Layer | Use case |
128
- |-------|----------|
129
- | `ScatterplotLayer` | Point data |
130
- | `GeoJsonLayer` | Polygons, lines, points from GeoJSON/GeoDataFrame |
131
- | `ArcLayer` | Origin-destination flows |
132
- | `PathLayer` | Routes, trajectories |
133
- | `PolygonLayer` | Filled regions |
134
- | `IconLayer` | Marker icons |
135
- | `TextLayer` | Labels |
136
- | `ColumnLayer` | 3D bars on map |
137
- | `HexagonLayer` | Hexagonal binning |
138
- | `HeatmapLayer` | Density visualization |
254
+ `LineLayer` and `PointCloudLayer` are newly exported experimental layers.
139
255
 
140
- ### Experimental (23)
256
+ ### Experimental (21+)
141
257
 
142
258
  All additional deck.gl layers are available as experimental stubs via `deckgl_marimo.layers`:
143
259
 
144
260
  ```python
145
261
  from deckgl_marimo.layers import TripsLayer, MVTLayer, H3HexagonLayer, ContourLayer
146
- # ... and 19 more
262
+ # ... and more
147
263
  ```
148
264
 
149
265
  ## Basemaps
@@ -160,18 +276,8 @@ dgl.Map(basemap="https://my-tileserver.example.com/style.json")
160
276
 
161
277
  ### `Content-Length` errors in the marimo console
162
278
 
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.
166
-
167
- To suppress it, install `httptools` so uvicorn uses a faster HTTP parser, then
168
- **fully restart marimo** (kill and relaunch):
279
+ This is fixed in Marimo >= 0.22.0 please update to that.
169
280
 
170
- ```bash
171
- uv add httptools
172
- # then restart marimo
173
- uv run marimo edit your_notebook.py
174
- ```
175
281
 
176
282
  ## License
177
283