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_endandon_loadon 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_zoom → showZoom, fill_paint → fillPaint).
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
- mapcn by Anmol Saini (MIT)
- MapLibre GL JS
- Basemap tiles by CARTO
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2dc6e10120550e532d92e0d792828a249142bd86f528ff0ef6572807141755d7
|
|
| MD5 |
54283cf109758a7d9ee5b68c51dafe8a
|
|
| BLAKE2b-256 |
312f3c115a6890214e35d53d39c19bfb6404b45db9db11ae3df5e9d0bdbb7678
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
101e230d0d9c5d2d1638af5b30b0199d8bb4281ad0b52d9d515f127cec31962f
|
|
| MD5 |
f8f5ff4d637d17cae065e2ebf8e0ade2
|
|
| BLAKE2b-256 |
488714014d16a45f323b6eba6115ad295654d940aa689faeadcacc434278fabc
|