Skip to main content

reflex-mapcn

mapcn map components for Reflex.

mapcn is a set of beautifully designed, MapLibre GL powered map components (map, markers, popups, controls, routes, arcs, GeoJSON layers and clusters) originally distributed as a shadcn/ui registry item. reflex-mapcn ships a self-contained port of that component and exposes every piece as a Reflex component, so you can build interactive maps in pure Python.

  • Free CARTO basemap that follows Reflex's light/dark color mode automatically
  • Markers with content, tooltips, popups, labels and dragging
  • Standalone popups, zoom / compass / locate / fullscreen controls
  • Routes with progress tracking and route-anchored markers
  • Curved arcs, GeoJSON fill/outline layers with hover state, native clustering
  • Controlled viewport, globe projection, custom styles and a blank basemap
  • Every callback delivers JSON-serialisable payloads for Reflex event handlers
  • Extras for Reflex: map_camera (flyTo / easeTo / fitBounds from state), on_click, on_move_end and on_load on the map

Installation

pip install reflex-mapcn

The only npm dependency (maplibre-gl) is installed automatically by Reflex the first time the app compiles.

Quick start

import reflex as rx
import reflex_mapcn as mapcn


def index() -> rx.Component:
    return rx.box(
        mapcn.map(
            mapcn.map_controls(position="top-right", show_compass=True),
            mapcn.map_marker(
                mapcn.marker_content(),          # default blue dot
                mapcn.marker_tooltip("Caracas"),
                mapcn.marker_popup(rx.text("Hello from Caracas"), close_button=True),
                longitude=-66.9036,
                latitude=10.4806,
            ),
            center=[-66.9036, 10.4806],
            zoom=11,
        ),
        height="480px",
    )


app = rx.App()
app.add_page(index)

The map fills its parent, so give the parent (or the map itself via height and width) a size.

Components

Reflex factory mapcn component Notes
mapcn.map Map Root container. Accepts MapLibre MapOptions as props.
mapcn.map_marker MapMarker longitude, latitude, draggable, drag/click/hover events
mapcn.marker_content MarkerContent Visual of the marker (defaults to a dot)
mapcn.marker_popup MarkerPopup Click popup attached to the marker
mapcn.marker_tooltip MarkerTooltip Hover tooltip
mapcn.marker_label MarkerLabel Text above/below the marker (position)
mapcn.map_popup MapPopup Standalone popup at a coordinate, on_close
mapcn.map_controls MapControls position, show_zoom, show_compass, show_locate, show_fullscreen, on_locate
mapcn.map_route MapRoute coordinates, color, width, progress, active*, dash_array
mapcn.route_progress RouteProgress Traveled part of the parent route
mapcn.route_marker RouteMarker Marker at "start", "end", "progress" or a 0-1 fraction
mapcn.map_arc MapArc Curved arcs, paint, hover_paint, on_hover, on_click
mapcn.map_geojson MapGeoJSON Fill + outline layers, promote_id, fill_hover_paint, events
mapcn.map_cluster_layer MapClusterLayer Native clustering, on_point_click, on_cluster_click
mapcn.map_camera (Reflex extra) Runs a camera command dict from state

A mapcn.mapcn namespace mirrors the same factories with shorter names (mapcn.mapcn.marker, mapcn.mapcn.geojson, ...).

Props use snake_case and are converted to the camelCase props of the React component (show_zoomshowZoom, fill_paintfillPaint).

Events

All callbacks receive plain dictionaries / lists:

Event Payload
map.on_viewport_change, on_move_end, on_load {"center": [lng, lat], "zoom", "bearing", "pitch"}
map.on_click {"lng", "lat", "point": {"x", "y"}}
map_marker.on_click / on_mouse_enter / on_mouse_leave / on_drag* {"lng", "lat"}
map_controls.on_locate {"longitude", "latitude"}
map_route.on_click / on_mouse_enter {"lng", "lat"}
map_arc.on_click / on_hover {"arc": {...datum}, "longitude", "latitude"} (None on hover leave)
map_geojson.on_click / on_hover {"feature": {"id", "properties", "geometry"}, "longitude", "latitude"} (None on hover leave)
map_cluster_layer.on_point_click (feature, [lng, lat])
map_cluster_layer.on_cluster_click (cluster_id, [lng, lat], point_count)
map_popup.on_close no arguments

on_viewport_change fires continuously while the map moves; throttle it (State.handler.throttle(100)) or prefer on_move_end.

class MapState(rx.State):
    viewport: dict = {"center": [-66.9, 10.48], "zoom": 11, "bearing": 0, "pitch": 0}

    @rx.event
    def set_viewport(self, viewport: dict):
        self.viewport = viewport


mapcn.map(
    viewport=MapState.viewport,                              # controlled mode
    on_viewport_change=MapState.set_viewport.throttle(100),
)

Recipes

Route with progress

mapcn.map_route(
    mapcn.route_progress(color="#3b82f6", width=5, opacity=1),
    mapcn.route_marker(mapcn.marker_content(), at="start"),
    mapcn.route_marker(mapcn.marker_content(rx.icon("car")), at="progress"),
    coordinates=[[lng, lat], ...],
    progress=State.progress,     # 0-1
    color="#94a3b8",
    dash_array=[0.5, 1.5],
)

Choropleth on a blank basemap

mapcn.map(
    mapcn.map_geojson(
        data="https://.../countries.geojson",
        promote_id="NAME_LONG",
        fill_paint={"fill-color": ["match", ["get", "NAME_LONG"], "Venezuela", "#1d4ed8", "#9ca3af"]},
        fill_hover_paint={"fill-color": "#f59e0b"},
        interactive=True,
        on_hover=State.on_country_hover,
    ),
    blank=True,
    center=[-30, 20],
    zoom=1.4,
)

Driving the camera from state

class State(rx.State):
    command: dict = {}
    _seq: int = 0

    @rx.event
    def fly_to(self, lng: float, lat: float):
        self._seq += 1
        self.command = mapcn.camera_command("flyTo", center=[lng, lat], zoom=12, seq=self._seq)


mapcn.map(mapcn.map_camera(command=State.command), ...)

Supported command types: flyTo (default), easeTo, jumpTo, fitBounds (bounds=[[west, south], [east, north]]). seq lets you re-issue an identical command.

Custom styles, globe and blank basemap

mapcn.map(styles={"light": "https://tiles.openfreemap.org/styles/bright",
                  "dark": "https://tiles.openfreemap.org/styles/dark"})
mapcn.map(projection={"type": "globe"}, zoom=1)
mapcn.map(blank=True)   # transparent, tile-less canvas for data viz

Theming

The component ships its own stylesheet (no Tailwind required). Colors default to the Radix Themes variables Reflex uses, so popups, tooltips and controls match your app in both light and dark mode. Override the --mapcn-* custom properties on .mapcn-map to re-theme:

.mapcn-map {
  --mapcn-bg: #fff;
  --mapcn-fg: #111;
  --mapcn-border: #e5e5e5;
  --mapcn-accent: #3b82f6;
  --mapcn-radius: 8px;
}

Marker content, popup bodies and labels are regular Reflex components, so style them as usual (rx.box(background_color=..., border_radius="9999px")).

Web worker and CSP

MapLibre loads its web worker from unpkg by default. To self-host it, copy node_modules/maplibre-gl/dist/maplibre-gl-worker.mjs into your app's assets/ folder and pass worker_url="/maplibre-gl-worker.mjs" to mapcn.map. With a strict CSP allow worker-src 'self' blob: and, unless self-hosting, script-src https://unpkg.com.

Demo app

The repository contains a demo app that exercises every feature:

git clone https://github.com/ecrespo/reflex-mapcn
cd reflex-mapcn
uv venv && uv pip install -e . && uv pip install -r mapcn_demo/requirements.txt
cd mapcn_demo && uv run reflex run

Pages: basic map (controlled viewport, blank basemap, custom styles), markers, popups, controls, routes (progress, OSRM alternatives), arcs, GeoJSON, clusters, advanced (camera commands, event log, globe) and a full-country Venezuela map (OpenFreeMap street-level basemap, state polygons served from assets/, capitals and main cities, state picker and camera fitting).

Development

uv venv && uv pip install -e ".[dev]"
uv run reflex component build    # generates .pyi stubs + builds dist/

Credits

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reflex_mapcn-0.1.0.tar.gz (36.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reflex_mapcn-0.1.0-py3-none-any.whl (33.9 kB view details)

Uploaded Python 3

File details

Details for the file reflex_mapcn-0.1.0.tar.gz.

File metadata

  • Download URL: reflex_mapcn-0.1.0.tar.gz
  • Upload date:
  • Size: 36.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.9

File hashes

Hashes for reflex_mapcn-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2dc6e10120550e532d92e0d792828a249142bd86f528ff0ef6572807141755d7
MD5 54283cf109758a7d9ee5b68c51dafe8a
BLAKE2b-256 312f3c115a6890214e35d53d39c19bfb6404b45db9db11ae3df5e9d0bdbb7678

See more details on using hashes here.

File details

Details for the file reflex_mapcn-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: reflex_mapcn-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 33.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.9

File hashes

Hashes for reflex_mapcn-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 101e230d0d9c5d2d1638af5b30b0199d8bb4281ad0b52d9d515f127cec31962f
MD5 f8f5ff4d637d17cae065e2ebf8e0ade2
BLAKE2b-256 488714014d16a45f323b6eba6115ad295654d940aa689faeadcacc434278fabc

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.0

2 files

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page