Skip to content

keplergl module

KeplerGL map widget implementation.

KeplerGL is loaded via CDN since it's React-based and requires complex setup. This implementation provides a Python wrapper with data management capabilities.

KeplerGLMap (MapWidget)

Interactive map widget using KeplerGL.

KeplerGL is a powerful data visualization tool built on top of deck.gl. This class provides a Python interface for adding data and configuring the KeplerGL visualization.

Note: KeplerGL is loaded from CDN due to its React-based architecture.

Examples:

>>> from anymap_ts import KeplerGLMap
>>> import pandas as pd
>>> m = KeplerGLMap()
>>> df = pd.DataFrame({
...     'lat': [37.7749, 37.8044],
...     'lng': [-122.4194, -122.2712],
...     'value': [100, 200]
... })
>>> m.add_data(df, name='points')
>>> m
Source code in anymap_ts/keplergl.py
class KeplerGLMap(MapWidget):
    """Interactive map widget using KeplerGL.

    KeplerGL is a powerful data visualization tool built on top of deck.gl.
    This class provides a Python interface for adding data and configuring
    the KeplerGL visualization.

    Note: KeplerGL is loaded from CDN due to its React-based architecture.

    Example:
        >>> from anymap_ts import KeplerGLMap
        >>> import pandas as pd
        >>> m = KeplerGLMap()
        >>> df = pd.DataFrame({
        ...     'lat': [37.7749, 37.8044],
        ...     'lng': [-122.4194, -122.2712],
        ...     'value': [100, 200]
        ... })
        >>> m.add_data(df, name='points')
        >>> m
    """

    # ESM module for frontend
    _esm = STATIC_DIR / "keplergl.js"

    # KeplerGL-specific traits
    config = traitlets.Dict({}).tag(sync=True)
    datasets = traitlets.Dict({}).tag(sync=True)
    read_only = traitlets.Bool(False).tag(sync=True)
    show_data_table = traitlets.Bool(True).tag(sync=True)

    # Mapbox token for basemaps
    mapbox_token = traitlets.Unicode("").tag(sync=True)

    def __init__(
        self,
        center: Tuple[float, float] = (-122.4, 37.8),
        zoom: float = 10.0,
        width: str = "100%",
        height: str = "600px",
        config: Optional[Dict] = None,
        read_only: bool = False,
        show_data_table: bool = True,
        mapbox_token: Optional[str] = None,
        **kwargs,
    ):
        """Initialize a KeplerGL map.

        Args:
            center: Map center as (longitude, latitude).
            zoom: Initial zoom level.
            width: Widget width as CSS string.
            height: Widget height as CSS string.
            config: KeplerGL configuration dict.
            read_only: Whether the UI is read-only.
            show_data_table: Whether to show the data table panel.
            mapbox_token: Mapbox access token for basemaps.
            **kwargs: Additional widget arguments.
        """
        import os

        if mapbox_token is None:
            mapbox_token = os.environ.get("MAPBOX_TOKEN", "")

        super().__init__(
            center=list(center),
            zoom=zoom,
            width=width,
            height=height,
            config=config or {},
            read_only=read_only,
            show_data_table=show_data_table,
            mapbox_token=mapbox_token,
            **kwargs,
        )
        self.datasets = {}

    # -------------------------------------------------------------------------
    # Data Methods
    # -------------------------------------------------------------------------

    def add_data(
        self,
        data: Any,
        name: Optional[str] = None,
    ) -> None:
        """Add data to the map.

        Args:
            data: Data to add (DataFrame, GeoDataFrame, dict, or file path).
            name: Dataset name/label.
        """
        dataset_id = name or f"data_{uuid.uuid4().hex[:8]}"
        processed_data = self._process_data(data)

        self.datasets = {
            **self.datasets,
            dataset_id: {
                "info": {
                    "id": dataset_id,
                    "label": dataset_id,
                },
                "data": processed_data,
            },
        }

        self.call_js_method(
            "addData",
            dataId=dataset_id,
            data=processed_data,
        )

    def _process_data(self, data: Any) -> Dict:
        """Process data into KeplerGL format.

        Args:
            data: Input data.

        Returns:
            Processed data dict with fields and rows.
        """
        # Handle DataFrame
        if hasattr(data, "to_dict"):
            # Check if it's a GeoDataFrame
            if hasattr(data, "geometry"):
                # Convert to GeoJSON for geometry columns
                geojson = json.loads(data.to_json())
                return {
                    "type": "geojson",
                    "data": geojson,
                }
            else:
                # Regular DataFrame
                fields = []
                for col in data.columns:
                    dtype = str(data[col].dtype)
                    if "int" in dtype:
                        field_type = "integer"
                    elif "float" in dtype:
                        field_type = "real"
                    elif "datetime" in dtype:
                        field_type = "timestamp"
                    elif "bool" in dtype:
                        field_type = "boolean"
                    else:
                        field_type = "string"

                    fields.append({"name": col, "type": field_type})

                # Convert to list of lists
                rows = data.values.tolist()

                return {
                    "fields": fields,
                    "rows": rows,
                }

        # Handle dict (assume it's already GeoJSON or processed)
        if isinstance(data, dict):
            if "type" in data and data["type"] in [
                "FeatureCollection",
                "Feature",
                "Point",
                "LineString",
                "Polygon",
                "MultiPoint",
                "MultiLineString",
                "MultiPolygon",
            ]:
                return {"type": "geojson", "data": data}
            return data

        # Handle file path
        if isinstance(data, (str, Path)):
            path = Path(data)
            if path.exists():
                if path.suffix.lower() in [".geojson", ".json"]:
                    with open(path) as f:
                        geojson = json.load(f)
                    return {"type": "geojson", "data": geojson}
                elif path.suffix.lower() == ".csv":
                    try:
                        import pandas as pd

                        df = pd.read_csv(path)
                        return self._process_data(df)
                    except ImportError:
                        raise ImportError(
                            "pandas is required to load CSV files. "
                            "Install with: pip install pandas"
                        )

        return data

    def remove_data(self, name: str) -> None:
        """Remove a dataset.

        Args:
            name: Dataset name to remove.
        """
        if name in self.datasets:
            datasets = dict(self.datasets)
            del datasets[name]
            self.datasets = datasets
        self.call_js_method("removeData", dataId=name)

    # -------------------------------------------------------------------------
    # Configuration Methods
    # -------------------------------------------------------------------------

    def set_config(self, config: Dict) -> None:
        """Set the KeplerGL configuration.

        Args:
            config: Configuration dict.
        """
        self.config = config
        self.call_js_method("setConfig", config=config)

    def get_config(self) -> Dict:
        """Get the current KeplerGL configuration.

        Returns:
            Configuration dict.
        """
        return self.config

    def save_config(self, filepath: Union[str, Path]) -> None:
        """Save configuration to a JSON file.

        Args:
            filepath: Path to save the configuration.
        """
        with open(filepath, "w") as f:
            json.dump(self.config, f, indent=2)

    def load_config(self, filepath: Union[str, Path]) -> None:
        """Load configuration from a JSON file.

        Args:
            filepath: Path to the configuration file.
        """
        with open(filepath) as f:
            config = json.load(f)
        self.set_config(config)

    # -------------------------------------------------------------------------
    # Filter Methods
    # -------------------------------------------------------------------------

    def add_filter(
        self,
        data_id: str,
        field: str,
        filter_type: str = "range",
        value: Optional[Any] = None,
    ) -> None:
        """Add a filter to the visualization.

        Args:
            data_id: Dataset ID to filter.
            field: Field name to filter on.
            filter_type: Type of filter ('range', 'select', 'time').
            value: Filter value(s).
        """
        filter_config = {
            "dataId": [data_id],
            "name": [field],
            "type": filter_type,
        }
        if value is not None:
            filter_config["value"] = value

        self.call_js_method("addFilter", filter=filter_config)

    # -------------------------------------------------------------------------
    # Layer Methods
    # -------------------------------------------------------------------------

    def add_layer(
        self,
        layer_type: str,
        data_id: str,
        columns: Dict[str, str],
        label: Optional[str] = None,
        color: Optional[List[int]] = None,
        vis_config: Optional[Dict] = None,
        **kwargs,
    ) -> None:
        """Add a layer to the visualization.

        Args:
            layer_type: Layer type ('point', 'arc', 'line', 'hexagon', 'heatmap', etc.).
            data_id: Dataset ID for the layer.
            columns: Column mapping (e.g., {'lat': 'latitude', 'lng': 'longitude'}).
            label: Layer label.
            color: Layer color as [r, g, b].
            vis_config: Visual configuration.
            **kwargs: Additional layer options.
        """
        layer_config = {
            "type": layer_type,
            "config": {
                "dataId": data_id,
                "label": label or f"{layer_type}_layer",
                "columns": columns,
                "isVisible": True,
            },
        }
        if color:
            layer_config["config"]["color"] = color
        if vis_config:
            layer_config["config"]["visConfig"] = vis_config

        layer_config["config"].update(kwargs)

        self.call_js_method("addLayer", layer=layer_config)

    # -------------------------------------------------------------------------
    # View Methods
    # -------------------------------------------------------------------------

    def fly_to(
        self,
        lng: float,
        lat: float,
        zoom: Optional[float] = None,
    ) -> None:
        """Fly to a location.

        Args:
            lng: Target longitude.
            lat: Target latitude.
            zoom: Target zoom level.
        """
        self.center = [lng, lat]
        if zoom is not None:
            self.zoom = zoom
        self.call_js_method("flyTo", lng=lng, lat=lat, zoom=zoom or self.zoom)

    # -------------------------------------------------------------------------
    # HTML Export
    # -------------------------------------------------------------------------

    def _generate_html_template(self) -> str:
        """Generate standalone HTML for KeplerGL."""
        template_path = Path(__file__).parent / "templates" / "keplergl.html"

        if template_path.exists():
            template = template_path.read_text(encoding="utf-8")
        else:
            template = self._get_default_template()

        state = {
            "center": self.center,
            "zoom": self.zoom,
            "config": self.config,
            "datasets": self.datasets,
            "read_only": self.read_only,
            "mapbox_token": self.mapbox_token,
            "width": self.width,
            "height": self.height,
            "js_calls": self._js_calls,
        }

        template = template.replace("{{state}}", json.dumps(state, indent=2))
        return template

    def _get_default_template(self) -> str:
        """Get default HTML template."""
        return """<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>KeplerGL Map</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        html, body { height: 100%; }
        #app { position: absolute; top: 0; bottom: 0; width: 100%; }
    </style>
</head>
<body>
    <div id="app"></div>
    <script>
        const state = {{state}};
        // KeplerGL requires React/Redux setup - simplified placeholder
        document.getElementById('app').innerHTML = '<p>KeplerGL visualization requires full React setup. Use Jupyter widget for interactive visualization.</p>';
    </script>
</body>
</html>"""

    def _repr_html_(self) -> str:
        """Return HTML representation for Jupyter (uses iframe with CDN KeplerGL)."""
        state = {
            "center": self.center,
            "zoom": self.zoom,
            "config": self.config,
            "datasets": self.datasets,
            "mapbox_token": self.mapbox_token,
        }

        html = f"""
        <iframe
            srcdoc='
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="utf-8">
                <script src="https://unpkg.com/react@16.8.4/umd/react.production.min.js"></script>
                <script src="https://unpkg.com/react-dom@16.8.4/umd/react-dom.production.min.js"></script>
                <script src="https://unpkg.com/kepler.gl@3.0.0/umd/keplergl.min.js"></script>
                <link href="https://unpkg.com/kepler.gl@3.0.0/umd/keplergl.min.css" rel="stylesheet" />
                <style>
                    body {{ margin: 0; padding: 0; overflow: hidden; }}
                    #app {{ width: 100vw; height: 100vh; }}
                </style>
            </head>
            <body>
                <div id="app"></div>
                <script>
                    const state = {json.dumps(state)};
                    // KeplerGL requires complex React setup
                    document.getElementById("app").innerHTML = "KeplerGL widget - use anywidget interface for full interactivity";
                </script>
            </body>
            </html>
            '
            width="{self.width}"
            height="{self.height}"
            frameborder="0"
        ></iframe>
        """
        return html

__init__(self, center=(-122.4, 37.8), zoom=10.0, width='100%', height='600px', config=None, read_only=False, show_data_table=True, mapbox_token=None, **kwargs) special

Initialize a KeplerGL map.

Parameters:

Name Type Description Default
center Tuple[float, float]

Map center as (longitude, latitude).

(-122.4, 37.8)
zoom float

Initial zoom level.

10.0
width str

Widget width as CSS string.

'100%'
height str

Widget height as CSS string.

'600px'
config Optional[Dict]

KeplerGL configuration dict.

None
read_only bool

Whether the UI is read-only.

False
show_data_table bool

Whether to show the data table panel.

True
mapbox_token Optional[str]

Mapbox access token for basemaps.

None
**kwargs

Additional widget arguments.

{}
Source code in anymap_ts/keplergl.py
def __init__(
    self,
    center: Tuple[float, float] = (-122.4, 37.8),
    zoom: float = 10.0,
    width: str = "100%",
    height: str = "600px",
    config: Optional[Dict] = None,
    read_only: bool = False,
    show_data_table: bool = True,
    mapbox_token: Optional[str] = None,
    **kwargs,
):
    """Initialize a KeplerGL map.

    Args:
        center: Map center as (longitude, latitude).
        zoom: Initial zoom level.
        width: Widget width as CSS string.
        height: Widget height as CSS string.
        config: KeplerGL configuration dict.
        read_only: Whether the UI is read-only.
        show_data_table: Whether to show the data table panel.
        mapbox_token: Mapbox access token for basemaps.
        **kwargs: Additional widget arguments.
    """
    import os

    if mapbox_token is None:
        mapbox_token = os.environ.get("MAPBOX_TOKEN", "")

    super().__init__(
        center=list(center),
        zoom=zoom,
        width=width,
        height=height,
        config=config or {},
        read_only=read_only,
        show_data_table=show_data_table,
        mapbox_token=mapbox_token,
        **kwargs,
    )
    self.datasets = {}

add_data(self, data, name=None)

Add data to the map.

Parameters:

Name Type Description Default
data Any

Data to add (DataFrame, GeoDataFrame, dict, or file path).

required
name Optional[str]

Dataset name/label.

None
Source code in anymap_ts/keplergl.py
def add_data(
    self,
    data: Any,
    name: Optional[str] = None,
) -> None:
    """Add data to the map.

    Args:
        data: Data to add (DataFrame, GeoDataFrame, dict, or file path).
        name: Dataset name/label.
    """
    dataset_id = name or f"data_{uuid.uuid4().hex[:8]}"
    processed_data = self._process_data(data)

    self.datasets = {
        **self.datasets,
        dataset_id: {
            "info": {
                "id": dataset_id,
                "label": dataset_id,
            },
            "data": processed_data,
        },
    }

    self.call_js_method(
        "addData",
        dataId=dataset_id,
        data=processed_data,
    )

add_filter(self, data_id, field, filter_type='range', value=None)

Add a filter to the visualization.

Parameters:

Name Type Description Default
data_id str

Dataset ID to filter.

required
field str

Field name to filter on.

required
filter_type str

Type of filter ('range', 'select', 'time').

'range'
value Optional[Any]

Filter value(s).

None
Source code in anymap_ts/keplergl.py
def add_filter(
    self,
    data_id: str,
    field: str,
    filter_type: str = "range",
    value: Optional[Any] = None,
) -> None:
    """Add a filter to the visualization.

    Args:
        data_id: Dataset ID to filter.
        field: Field name to filter on.
        filter_type: Type of filter ('range', 'select', 'time').
        value: Filter value(s).
    """
    filter_config = {
        "dataId": [data_id],
        "name": [field],
        "type": filter_type,
    }
    if value is not None:
        filter_config["value"] = value

    self.call_js_method("addFilter", filter=filter_config)

add_layer(self, layer_type, data_id, columns, label=None, color=None, vis_config=None, **kwargs)

Add a layer to the visualization.

Parameters:

Name Type Description Default
layer_type str

Layer type ('point', 'arc', 'line', 'hexagon', 'heatmap', etc.).

required
data_id str

Dataset ID for the layer.

required
columns Dict[str, str]

Column mapping (e.g., {'lat': 'latitude', 'lng': 'longitude'}).

required
label Optional[str]

Layer label.

None
color Optional[List[int]]

Layer color as [r, g, b].

None
vis_config Optional[Dict]

Visual configuration.

None
**kwargs

Additional layer options.

{}
Source code in anymap_ts/keplergl.py
def add_layer(
    self,
    layer_type: str,
    data_id: str,
    columns: Dict[str, str],
    label: Optional[str] = None,
    color: Optional[List[int]] = None,
    vis_config: Optional[Dict] = None,
    **kwargs,
) -> None:
    """Add a layer to the visualization.

    Args:
        layer_type: Layer type ('point', 'arc', 'line', 'hexagon', 'heatmap', etc.).
        data_id: Dataset ID for the layer.
        columns: Column mapping (e.g., {'lat': 'latitude', 'lng': 'longitude'}).
        label: Layer label.
        color: Layer color as [r, g, b].
        vis_config: Visual configuration.
        **kwargs: Additional layer options.
    """
    layer_config = {
        "type": layer_type,
        "config": {
            "dataId": data_id,
            "label": label or f"{layer_type}_layer",
            "columns": columns,
            "isVisible": True,
        },
    }
    if color:
        layer_config["config"]["color"] = color
    if vis_config:
        layer_config["config"]["visConfig"] = vis_config

    layer_config["config"].update(kwargs)

    self.call_js_method("addLayer", layer=layer_config)

fly_to(self, lng, lat, zoom=None)

Fly to a location.

Parameters:

Name Type Description Default
lng float

Target longitude.

required
lat float

Target latitude.

required
zoom Optional[float]

Target zoom level.

None
Source code in anymap_ts/keplergl.py
def fly_to(
    self,
    lng: float,
    lat: float,
    zoom: Optional[float] = None,
) -> None:
    """Fly to a location.

    Args:
        lng: Target longitude.
        lat: Target latitude.
        zoom: Target zoom level.
    """
    self.center = [lng, lat]
    if zoom is not None:
        self.zoom = zoom
    self.call_js_method("flyTo", lng=lng, lat=lat, zoom=zoom or self.zoom)

get_config(self)

Get the current KeplerGL configuration.

Returns:

Type Description
Dict

Configuration dict.

Source code in anymap_ts/keplergl.py
def get_config(self) -> Dict:
    """Get the current KeplerGL configuration.

    Returns:
        Configuration dict.
    """
    return self.config

load_config(self, filepath)

Load configuration from a JSON file.

Parameters:

Name Type Description Default
filepath Union[str, Path]

Path to the configuration file.

required
Source code in anymap_ts/keplergl.py
def load_config(self, filepath: Union[str, Path]) -> None:
    """Load configuration from a JSON file.

    Args:
        filepath: Path to the configuration file.
    """
    with open(filepath) as f:
        config = json.load(f)
    self.set_config(config)

remove_data(self, name)

Remove a dataset.

Parameters:

Name Type Description Default
name str

Dataset name to remove.

required
Source code in anymap_ts/keplergl.py
def remove_data(self, name: str) -> None:
    """Remove a dataset.

    Args:
        name: Dataset name to remove.
    """
    if name in self.datasets:
        datasets = dict(self.datasets)
        del datasets[name]
        self.datasets = datasets
    self.call_js_method("removeData", dataId=name)

save_config(self, filepath)

Save configuration to a JSON file.

Parameters:

Name Type Description Default
filepath Union[str, Path]

Path to save the configuration.

required
Source code in anymap_ts/keplergl.py
def save_config(self, filepath: Union[str, Path]) -> None:
    """Save configuration to a JSON file.

    Args:
        filepath: Path to save the configuration.
    """
    with open(filepath, "w") as f:
        json.dump(self.config, f, indent=2)

set_config(self, config)

Set the KeplerGL configuration.

Parameters:

Name Type Description Default
config Dict

Configuration dict.

required
Source code in anymap_ts/keplergl.py
def set_config(self, config: Dict) -> None:
    """Set the KeplerGL configuration.

    Args:
        config: Configuration dict.
    """
    self.config = config
    self.call_js_method("setConfig", config=config)