` into the exported file (position it at `right: 56px` or `left: 66px` so it doesn't block map controls). See [Summary Panel Overlay](skill-references/summary-panel.md).
## Supported Data Formats
@@ -148,10 +148,15 @@ For detailed per-layer-type examples with full config, see supporting files:
- [Heatmap](skill-references/heatmap.md) — Density heatmap from points
- [Hexbin Aggregation Map](skill-references/hexbin-aggregation-map.md) — Spatial binning into hexagons
- [Trip Animation Map](skill-references/trip-animation-map.md) — Animated trips along paths
+- [Summary Panel Overlay](skill-references/summary-panel.md) — Inject a SampleMapPanel-style info overlay into the exported HTML (for LISA/cluster counts, model summaries, custom legends)
## Examples
-### Point map from DataFrame (quantile + vibrant palette)
+For full config examples per layer type, see:
+- [Point Map](skill-references/point-map.md) — includes quantile color + vibrant palette config
+- [GeoJSON / Polygon Map](skill-references/geojson-polygon-map.md) — includes choropleth config with `visualChannels`
+
+### Quick start (auto-detected layers, no config needed)
```python
from keplergl import KeplerGl
@@ -164,38 +169,8 @@ df = pd.DataFrame({
'value': [15, 42, 27]
})
-config = {
- 'version': 'v1',
- 'config': {
- 'visState': {
- 'layers': [{
- 'type': 'point',
- 'config': {
- 'dataId': 'cities',
- 'label': 'Cities',
- 'isVisible': True,
- 'columns': {'lat': 'lat', 'lng': 'lng'},
- 'visConfig': {
- 'radius': 10,
- 'colorRange': {
- 'name': 'Global Warming',
- 'type': 'sequential',
- 'category': 'Uber',
- 'colors': ['#5A1846', '#900C3F', '#C70039', '#E3611C', '#F1920E', '#FFC300']
- }
- }
- },
- 'visualChannels': {
- 'colorField': {'name': 'value', 'type': 'integer'},
- 'colorScale': 'quantile'
- }
- }]
- }
- }
-}
-
-map_1 = KeplerGl(height=600, data={'cities': df}, config=config)
-map_1.save_to_html(file_name='cities_map.html')
+map_1 = KeplerGl(data={'cities': df})
+map_1.save_to_html(file_name='cities_map.html', center_map=True)
```
### GeoDataFrame from shapefile
@@ -209,70 +184,6 @@ map_1 = KeplerGl(data={'regions': gdf})
map_1.save_to_html(file_name='regions_map.html', read_only=True, center_map=True)
```
-### GeoJSON from file
-
-```python
-from keplergl import KeplerGl
-import json
-
-with open('boundaries.geojson', 'r') as f:
- geojson = json.load(f)
-map_1 = KeplerGl(data={'boundaries': geojson})
-map_1.save_to_html(file_name='boundaries_map.html', center_map=True)
-```
-
-### GeoJSON choropleth colored by a numeric attribute
-
-Load with geopandas, then put the field-to-channel mappings under `visualChannels` (not `config`):
-
-```python
-import geopandas as gpd
-from keplergl import KeplerGl
-
-gdf = gpd.read_file('natregimes.geojson')
-
-config = {
- 'version': 'v1',
- 'config': {
- 'visState': {
- 'layers': [{
- 'id': 'hr60_layer',
- 'type': 'geojson',
- 'config': {
- 'dataId': 'natregimes',
- 'label': 'HR60',
- 'columns': {'geojson': '_geojson'},
- 'isVisible': True,
- 'visConfig': {
- 'opacity': 0.8,
- 'stroked': True,
- 'filled': True,
- 'thickness': 0.2,
- 'strokeColor': [80, 80, 80],
- 'colorRange': {
- 'name': 'Global Warming',
- 'type': 'sequential',
- 'category': 'Uber',
- 'colors': ['#5A1846', '#900C3F', '#C70039',
- '#E3611C', '#F1920E', '#FFC300']
- }
- }
- },
- 'visualChannels': {
- 'colorField': {'name': 'HR60', 'type': 'real'},
- 'colorScale': 'quantile'
- }
- }]
- },
- 'mapState': {'latitude': 39.5, 'longitude': -98.35, 'zoom': 3.5}
- }
-}
-
-map_1 = KeplerGl(height=600, config=config)
-map_1.add_data(data=gdf, name='natregimes')
-map_1.save_to_html(file_name='natregimes_map.html')
-```
-
### Multiple datasets
```python
diff --git a/bindings/python/agents/assets/icon-large.svg b/bindings/python/agents/assets/icon-large.svg
new file mode 100644
index 0000000000..ab469458b3
--- /dev/null
+++ b/bindings/python/agents/assets/icon-large.svg
@@ -0,0 +1,6 @@
+
diff --git a/bindings/python/agents/assets/icon-small.svg b/bindings/python/agents/assets/icon-small.svg
new file mode 100644
index 0000000000..00d4e63f1b
--- /dev/null
+++ b/bindings/python/agents/assets/icon-small.svg
@@ -0,0 +1,6 @@
+
diff --git a/bindings/python/agents/openai.yaml b/bindings/python/agents/openai.yaml
new file mode 100644
index 0000000000..14de76a339
--- /dev/null
+++ b/bindings/python/agents/openai.yaml
@@ -0,0 +1,13 @@
+interface:
+ display_name: "kepler.gl Map"
+ short_description: "Create interactive map visualizations with kepler.gl"
+ icon_small: "./assets/icon-small.svg"
+ icon_large: "./assets/icon-large.svg"
+ brand_color: "#1FBAD6"
+ default_prompt: "Create an interactive map visualization"
+
+policy:
+ allow_implicit_invocation: true
+
+dependencies:
+ tools: []
diff --git a/bindings/python/cursor-plugin.json b/bindings/python/cursor-plugin.json
new file mode 100644
index 0000000000..fcbe464201
--- /dev/null
+++ b/bindings/python/cursor-plugin.json
@@ -0,0 +1,38 @@
+{
+ "name": "keplergl-map",
+ "displayName": "kepler.gl Map",
+ "version": "1.0.0",
+ "description": "Create interactive map visualizations and export to standalone HTML using the keplergl Python package.",
+ "publisher": "keplergl",
+ "icon": "agents/assets/icon-large.svg",
+ "categories": ["Data Science", "Visualization", "Maps"],
+ "keywords": [
+ "maps",
+ "geospatial",
+ "visualization",
+ "kepler.gl",
+ "GeoJSON",
+ "DataFrame",
+ "HTML",
+ "interactive",
+ "choropleth",
+ "heatmap"
+ ],
+ "homepage": "https://kepler.gl",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/keplergl/kepler.gl",
+ "directory": "bindings/python"
+ },
+ "license": "MIT",
+ "skills": [
+ {
+ "path": "SKILL.md",
+ "name": "keplergl-map",
+ "description": "Create interactive map visualizations and export to standalone HTML using the keplergl Python package."
+ }
+ ],
+ "engines": {
+ "cursor": ">=1.0.0"
+ }
+}
diff --git a/bindings/python/plugin.json b/bindings/python/plugin.json
new file mode 100644
index 0000000000..144e9f1483
--- /dev/null
+++ b/bindings/python/plugin.json
@@ -0,0 +1,28 @@
+{
+ "name": "keplergl-map",
+ "version": "1.0.0",
+ "description": "Create interactive map visualizations and export to standalone HTML using the keplergl Python package. Use when the user wants to create maps, visualize geospatial data, plot locations on a map, or generate HTML map files.",
+ "author": "kepler.gl",
+ "license": "MIT",
+ "homepage": "https://kepler.gl",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/keplergl/kepler.gl",
+ "directory": "bindings/python"
+ },
+ "skills": [
+ {
+ "path": "./SKILL.md"
+ }
+ ],
+ "keywords": [
+ "maps",
+ "geospatial",
+ "visualization",
+ "kepler.gl",
+ "GeoJSON",
+ "DataFrame",
+ "HTML",
+ "interactive"
+ ]
+}
diff --git a/bindings/python/skill-references/summary-panel.md b/bindings/python/skill-references/summary-panel.md
new file mode 100644
index 0000000000..28a6be1386
--- /dev/null
+++ b/bindings/python/skill-references/summary-panel.md
@@ -0,0 +1,99 @@
+# Summary Panel Overlay (SampleMapPanel-style)
+
+Add a small informational panel on top of a kepler.gl map exported via `save_to_html()` — useful for summary stats, legends of custom classifications, model metadata, or LISA / cluster counts.
+
+## Why not a real React component?
+
+The `SampleMapPanel` used in the kepler.gl demo app is part of that demo's source and is **not** exposed by the UMD bundle loaded in the standalone HTML export. The pragmatic equivalent is to inject an HTML/CSS overlay into the exported file after calling `save_to_html()`.
+
+## Positioning
+
+The map's zoom/rotate/split/legend control column sits against the right edge of the viewport and is roughly 40–50px wide. Place the panel at `right: 56px` (or `left: 66px` to sit next to the side panel toggle) so it does not block those controls.
+
+| Placement | CSS |
+|-----------|-----|
+| Top-right, clear of map controls | `top: 16px; right: 56px;` |
+| Top-left, clear of side-panel toggle | `top: 16px; left: 66px;` |
+| Bottom-left, above attribution | `bottom: 36px; left: 16px;` |
+
+## Pattern
+
+1. Call `map.save_to_html(file_name='out.html')` as usual.
+2. Build a small `
` + `
+"""
+
+with open("out.html", "r", encoding="utf-8") as f:
+ html = f.read()
+html = html.replace("