Scale, FullScreen, ImageOverlay
Three small controls / overlays that close the remaining dl1 gaps: ScaleControl, FullScreenControl, ImageOverlay.
---
.. llms_copy::Scale, FullScreen, ImageOverlay
.. toc::
Overview
Three thin wrappers around Leaflet 2 building blocks that dash-leaflet 1.x exposed and dl2 didn't yet:
| Component | Wraps | |-------------------|-------| | ScaleControl | Control.Scale — metric / imperial scale bar in any corner. | | FullScreenControl | A small Control over the browser's requestFullscreen() API. Reports fullscreen and n_clicks back to Dash. | | ImageOverlay | ImageOverlay — drape one image onto a geographic bounding box. Two-way url, bounds, opacity, zIndex. With editable it gains a TextMarker-style transform control system. |
Editable ImageOverlay (resize · rotate · move)
Set editable=True and the overlay becomes a draggable, resizable, rotatable object — the same control language as dl2.TextMarker:
- Click the image to select it (chrome + handles appear).
- Drag the body to move it (translates
bounds). - Drag the corner dot to resize — the bounds scale about the
anchor, which stays pinned.
That dot is also the white anchor marker: it sits at whichever anchor you choose (center defaults to the bottom-right corner).
- Drag the top dot to rotate. Rotation is a CSS-transform visual rotation pivoting at the
anchor — Leaflet's ImageOverlay has no native geographic rotation, so bounds stay axis-aligned and only the rendered pixels turn.
bounds, rotation, and selected round-trip back to Dash (plus an n_transforms counter).
Live demo
A single map with all three pieces: scale bar bottom-left, fullscreen button top-left, and an editable image overlay — select it, then drag to move, resize from the corner/anchor dot, and rotate from the top dot. Change the anchor pivot and watch the white dot follow.
.. exec::docs.scale-fullscreen-image.example :code: false
Source
```python
File: docs/scale-fullscreen-image/example.py
""" ScaleControl, FullScreenControl, ImageOverlay — limited working example.
A single map with the scale bar bottom-left, the fullscreen button top-left, and a sample raster ImageOverlay draped over a Honolulu, HI bounding box. Right column tweaks each piece live. """
import dash_leaflet2 as dl2 import dash_mantine_components as dmc from dash import Input, Output, callback from dl2_tiles import NATGEO, register_theme_swap from dl2_locations import HONOLULU from dl2_shared import code_panel, header, info_panel
Basemap pair for this page — dl2_tiles owns the light/dark wiring.
TILES = NATGEO
A ~17 x 20 km box centred on the city. Expressed in kilometres rather than
degrees so the overlay covers the same ground area at Honolulu's latitude as
it would anywhere else — a fixed degree box would stretch east-west near the
equator and squash near the poles.
OVERLAY_BOUNDS = HONOLULU.bounds(8.35, 9.83)
Public sample image used by Leaflet docs.
SAMPLE_IMAGE = "https://leafletjs.com/examples/crs-simple/uqm_map_full.png" SAMPLE_IMAGE_2 = "https://maps.lib.utexas.edu/maps/historical/texas_southern_1895.jpg"
ANCHORS = [ "top-left", "top", "top-right", "left", "center", "right", "bottom-left", "bottom", "bottom-right", ]
CODE = """dl2.Map(center=[21.3069, -157.8583], zoom=12, children=[ dl2.TileLayer(), dl2.ScaleControl(position="bottomleft", metric=True, imperial=True), dl2.FullScreenControl(position="topleft"),
# Editable: click to select, drag to move, corner dot resizes (about anchor), # top dot rotates. bounds / rotation / selected round-trip back to Dash. dl2.ImageOverlay( id="img", url="https://leafletjs.com/examples/crs-simple/uqm_map_full.png", bounds=[[21.23, -157.95], [21.38, -157.76]], opacity=0.85, editable=True, selected=True, anchor="center", rotation=0, ), ])
@callback(Output("out", "children"), Input("img", "bounds"), Input("img", "rotation")) def show(bounds, rotation): ..."""
component = dmc.Stack( [ header( "Scale, FullScreen, ImageOverlay", "Scale bar bottom-left (metric + imperial), fullscreen button top-left, and an " "EDITABLE ImageOverlay: click it to select, then drag to move, drag the corner " "dot (the anchor marker) to resize, and the top dot to rotate — bounds + rotation " "round-trip back to Dash. The anchor dot follows the chosen anchor.", badge="dl2.{Scale,FullScreen}Control · editable dl2.ImageOverlay", ), dmc.Grid( [ dmc.GridCol( dmc.Paper( dl2.Map( id="sfi-map", center=HONOLULU.center, zoom=11, style={"height": "60vh"}, children=[ dl2.TileLayer(id="sfi-tile", **TILES.kwargs("light")), dl2.ScaleControl( id="sfi-scale", position="bottomleft", metric=True, imperial=True, ), dl2.FullScreenControl( id="sfi-fs", position="topleft", title="Enter full screen", titleCancel="Leave full screen", ), dl2.ImageOverlay( id="sfi-image", url=SAMPLE_IMAGE, bounds=OVERLAY_BOUNDS, opacity=0.85, editable=True, selected=True, anchor="center", rotation=0, ), ], ), shadow="sm", radius="md", withBorder=True, style={"overflow": "hidden", "height": "60vh"}, ), span=8, ), dmc.GridCol( dmc.Stack( [ info_panel( "Scale bar", dmc.Stack( [ dmc.SegmentedControl( id="sfi-scale-pos", data=[ {"label": "BL", "value": "bottomleft"}, {"label": "BR", "value": "bottomright"}, {"label": "TL", "value": "topleft"}, {"label": "TR", "value": "topright"}, ], value="bottomleft", fullWidth=True, ), dmc.Switch(id="sfi-scale-metric", checked=True, label="metric"), dmc.Switch(id="sfi-scale-imperial", checked=True, label="imperial"), ], gap="xs", ), ), info_panel( "Fullscreen", dmc.Stack( [ dmc.Badge(id="sfi-fs-state", color="gray", variant="light", children="windowed"), dmc.Text(id="sfi-fs-clicks", size="xs", c="dimmed"), ], gap="xs", ), ), info_panel( "ImageOverlay opacity", dmc.Slider( id="sfi-image-opacity", min=0, max=1, step=0.05, value=0.6, ), ), info_panel( "ImageOverlay source", dmc.SegmentedControl( id="sfi-image-url", data=[ {"label": "UQM sample", "value": SAMPLE_IMAGE}, {"label": "1895 TX scan", "value": SAMPLE_IMAGE_2}, ], value=SAMPLE_IMAGE, fullWidth=True, ), ), info_panel( "Transform (anchor pivot)", dmc.Stack( [ dmc.SegmentedControl( id="sfi-anchor", data=[{"label": a, "value": a} for a in ANCHORS], value="center", orientation="vertical", fullWidth=True, size="xs", ), dmc.Text("Rotation", size="xs", c="dimmed"), dmc.Slider( id="sfi-rot", min=-180, max=180, value=0, marks=[{"value": 0, "label": "0°"}], ), dmc.Code(id="sfi-image-out", block=True, children="…"), ], gap="xs", ), ), ], gap="md", ), span=4, ), ], gutter="md", ), code_panel("Three new pieces in one map", CODE), ], gap="md", )
@callback(Output("sfi-scale", "position"), Input("sfi-scale-pos", "value")) def scale_pos(v): return v or "bottomleft"
@callback(Output("sfi-scale", "metric"), Input("sfi-scale-metric", "checked")) def scale_metric(checked): return bool(checked)
@callback(Output("sfi-scale", "imperial"), Input("sfi-scale-imperial", "checked")) def scale_imperial(checked): return bool(checked)
@callback(Output("sfi-fs-state", "children"), Output("sfi-fs-state", "color"), Input("sfi-fs", "fullscreen")) def fs_state(is_full): if is_full: return "fullscreen", "blue" return "windowed", "gray"
@callback(Output("sfi-fs-clicks", "children"), Input("sfi-fs", "n_clicks")) def fs_clicks(n): return f"button clicks: {n or 0}"
@callback(Output("sfi-image", "opacity"), Input("sfi-image-opacity", "value")) def image_opacity(v): return float(v or 0)
@callback(Output("sfi-image", "url"), Input("sfi-image-url", "value")) def image_url(v): return v or SAMPLE_IMAGE
@callback(Output("sfi-image", "anchor"), Input("sfi-anchor", "value")) def image_anchor(v): return v or "center"
@callback(Output("sfi-image", "rotation"), Input("sfi-rot", "value")) def image_rotation(v): return int(v or 0)
@callback( Output("sfi-image-out", "children"), Input("sfi-image", "bounds"), Input("sfi-image", "rotation"), Input("sfi-image", "n_transforms"), ) def image_readback(bounds, rotation, n): b = bounds or OVERLAY_BOUNDS return ( f"rotation: {rotation or 0}°\n" f"bounds : [[{b[0][0]:.4f}, {b[0][1]:.4f}],\n" f" [{b[1][0]:.4f}, {b[1][1]:.4f}]]\n" f"n_transforms: {n or 0}" )
Light/dark basemap, driven off the color-scheme store.
register_theme_swap("sfi-tile", TILES) ```
:defaultExpanded: false :withExpandedButton: true
---
*Source: /scale-fullscreen-image*
Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs:
- /scale-fullscreen-image/llms.txt — LLM-friendly documentation
- /sitemap.xml
- /robots.txt