Donut count labels use the same overlap and collision-placement rules as their icons, preventing collision-hidden numbers inside visible donuts for native and precomputed clusters. Ordinary circle-cluster labels are unchanged.
Interactive categorical legends preserve hexagon and custom SVG patch shapes when toggling categories or resetting filters.
Clusters can be drawn as donut charts showing each cluster's category mix with cluster_options(donut_column = ), for live GeoJSON clustering and pre-clustered vector tiles.
add_layer() gains a metadata argument.
Abbreviated cluster count labels now switch to millions and billions (e.g. "1.7M" or "1.5B" instead of "1735k" or "1500M").
Clustered layers now render in compare() views, and cluster circle paint options (circle_opacity, strokes, blur) now reach maps updated through a Shiny proxy.
Update Mapbox GL JS to v3.31.0, which brings a much smaller memory footprint for large GeoJSON sources.
Raster PMTiles sources on Mapbox maps now use Mapbox GL JS's native PMTiles support. The custom pmtile-source plugin, which stopped loading on Mapbox GL JS v3.29+, has been removed.
Popups and tooltips support conditional logic evaluated per feature at render time, with new builders if_else_expr(), case_expr(), coalesce_expr(), has_column(), is_blank(), and html_escape_expr() that compose with concat(), get_column(), and number_format(). Useful for remote sources like PMTiles where popup columns can't be precomputed. See ?conditional_expressions.
The popup/tooltip expression evaluator supports a broad set of GL-style conditional, comparison, boolean, lookup, math, ramp, and string operators, so match_expr() and step_expr() output also works as popup/tooltip content. Unknown or failing operators render as an empty string with a console warning.
Legends gain optional min_zoom and max_zoom arguments to show or hide a legend on zoom, with the same semantics as the layer arguments of the same names.
Legends sharing a corner position now stack automatically instead of overlapping, and reflow as legends are shown or hidden. Legends with explicit margin_* values or that have been dragged are left alone.
Adding a legend with the default add = FALSE to a map that already has one now messages that the existing legend is being replaced.
add_legend(draggable = TRUE) now works in compare().
add_draw_control() gains a provider argument that can be set to "terra-draw" to use the Terra Draw drawing engine as an alternative to mapbox-gl-draw (which remains the default; existing code is unaffected). Terra Draw works identically on Mapbox and MapLibre maps, standalone and in compare(), and is fully integrated with the existing drawing workflow: get_drawn_features(), add_features_to_draw(), clear_drawn_features(), clear_controls("draw"), attribute editing via attributes, live measurements via show_measurements, the download button, and the styling arguments all work unchanged. (As with the default provider, attribute editing and measurements are available on standalone widgets only, not in compare().) Details:
modes argument selects the toolbar tools: "point", "linestring", "polygon", "rectangle", "circle", "freehand", "freehand-linestring", "angled-rectangle", "sector", "sensor", "curve", "curve-linestring", and "select" — Terra Draw's select mode supports dragging features, dragging/deleting vertices, midpoint insertion, and optional rotate/scale/resize and snapping."curve" and "curve-linestring" modes are mapgl-authored pen-tool drawing modes for shapes mixing straight and curved (cubic Bezier) edges — e.g. a basketball key or a river trace. Click places a corner; click-and-drag places an anchor and pulls out curve handles; click the first point or press Enter to finish, Escape to cancel, Backspace to remove the last point. Output is the rendered curved geometry (so measurements, downloads, and get_drawn_features() work unchanged) with the control points preserved in a curveNodes JSON-string column; finished curves can be moved (control points follow) but not vertex-edited, and map panning is suspended while a curve tool is active.terradraw_options() helper configures advanced behavior (select-mode editing flags, snapping, drawing interaction style, per-mode overrides) via the new options argument.add_terradraw_control() is an equivalent convenience wrapper for add_draw_control(provider = "terra-draw") whose signature contains only the arguments that apply to the Terra Draw engine.get_drawn_features() include a mode column recording which tool created each feature; circles additionally carry a radiusKilometers property.clear_drawn_features() to remove everything); when the mode set includes "select", finishing a shape returns to the select tool with the new feature selected — set terradraw_options(keep_mode_active = TRUE) to stay in the drawing mode instead.source or add_features_to_draw() are adapted to Terra Draw's constraints: Multi* geometries are split into single-part features, coordinates are rounded to Terra Draw's 9-decimal-place precision limit (~0.1 mm), and polygon holes are removed. Colors are coerced to the 6-digit hex form Terra Draw requires.The layers control from add_layers_control() is now a first-class map control: it is added through the GL addControl() API and stacks with the other controls in its corner (in call order) instead of floating over them, so it no longer collides with navigation, fullscreen, and similar controls. Related changes:
active_color = "#4a90e2" and active_text_color = "#ffffff".margin_* arguments are no longer applied by default (the native stack handles spacing) but are still honored when explicitly set.compare(), each side's layers control now lives in that side's own control stack (previously both sides rendered into the same overlay space and overlapped), custom colors are now honored, layer-linked legends now show/hide with their layers, and clear_controls("layers") now works (previously it errored or did nothing).map_side (previously it toggled every side at once).layers, the control now lists the map's non-basemap style layers instead of rendering empty. Note this covers regular GL layers only (flowmap layers are managed outside the style), and the basemap filter reflects the initially loaded style.set_style(), the control now reflects each layer's restored visibility instead of resetting every entry to active.add_layers_control() gains a mode argument. The default "multiple" keeps the current independent toggles; mode = "single" shows one entry at a time — activating an entry turns the others (and their linked legends) off, which suits flipping through alternative analytical layers or raster imagery. To mix both behaviors on one map, add a "single"-mode control alongside a "multiple"-mode control; they stack cleanly in the same corner.
Various bug fixes and performance improvements (#30, #131, #150, #212, #215, #216, #217, #219).
Tooltips and popups now accept {brace} templates package-wide. Any layer's tooltip/popup (and set_tooltip()/set_popup()) can take a glue-style template such as "{name}: {population}", in addition to the existing column name and concat()/number_format() expression forms; substituted values are HTML-escaped.
New tooltip_style() / popup_style() helpers theme tooltips and popups without hand-written HTML/CSS, mirroring legend_style(). Pass a preset ("light"/"dark") or custom appearance (background, border, radius, font, padding, shadow) to the new tooltip_style/popup_style argument on layer functions (and set_tooltip()/set_popup()). Tooltips are unstyled by default, so existing maps are unchanged.
New add_flowmap() draws animated origin-destination flow maps powered by Flowmap.gl (deck.gl). It supports temporal flows, location clustering, customizable color schemes (flowmap_color_schemes()), CSS blend modes, and themeable tooltips and popups (per-object-type content via list(location = , flow = ), styled with tooltip_style()/popup_style()). Flow filters and settings can be updated reactively in Shiny with set_flowmap_filter() and set_flowmap_settings(), and a "window"-mode add_slider_control() can drive a flowmap's temporal range. The heavy flow-mapping libraries load on demand, so maps without a flowmap are unaffected. Adapted from Egor Kotov's contribution in #205, with the bundled bixi_locations and bixi_flows datasets as a ready-to-use example.
New add_slider_control() adds an interactive slider that filters and/or animates one or more layers by a numeric feature property. It composes with a layer's initial filter, later set_filter() calls, and interactive legends (intersecting via ["all", ...]) rather than replacing them. Modes are "sequential" (one value), "cumulative" (everything through a value), and "window" (a selected range that can also drive flowmap time ranges; window_behavior chooses a "resizable" two-edge range or a "fixed"-width band you pan). Two presentations: the default "compact" slider, or presentation = "timeline" — a prominent, brushable histogram (drag the selected window across the bars, drag its edges to resize) modeled on Egor Kotov's FlowMapBlue time control (#205). Supports paint-property animation, an optional play button, and an optional density histogram (histogram/histogram_data/counts, drawn with d3 loaded on demand). New slider_style() presets and overrides control the container, play button, track, thumb, and histogram appearance, and draggable = TRUE lets the user reposition the panel anywhere on the map (as with add_legend()). Companions update_slider_control() (for Shiny proxies) and as_time_property() (coerce Date/POSIXct to a numeric filter property) round out the feature.
add_categorical_legend() and add_legend() gain a patch_spacing argument ("uniform"/"proportional"). With "proportional", each legend row's height tracks its own symbol size, giving proportional vertical spacing for graduated-symbol legends; the default "uniform" preserves existing behavior (#206, thanks to @mtennekes).
New add_h3t_source() adds a tiled H3 (h3t) source for MapLibre maps, fetching only the H3 cells in the current viewport from a {z}/{x}/{y} tile endpoint via the h3tiles:// protocol — a scalable alternative to add_h3j_source() for large datasets. Works with multiple sources per map and on both sides of compare(). Bundled h3j-h3t library updated to 0.9.7 (#199, thanks to @bbest).
Update MapLibre GL JS to v5.24.0 and Mapbox GL JS to v3.24.0.
Fixed add_legend() silently ignoring the target argument for MapLibre compare widgets: the compare dispatch checked for class "maplibre_compare" while the widget class is "maplibregl_compare", so legends were attached as regular map legends instead of compare-level legends.
compare() now supports synchronizing more than two maps (#204). Pass additional maps after map1 and map2 with mode = "sync", and control the grid layout with the new ncol argument. In Shiny, maps in multi-map widgets are addressed as "map1" through "mapN" via map_side in the compare proxy functions (integers also accepted, e.g. map_side = 3), and emit input values like input$id_map3_view. Legends can be targeted at individual grid maps with target = "map3".
New bivariate mapping support with bivariate_scale(), bivariate_palettes(), and add_bivariate_legend(). Bivariate scales use 3-by-3 palettes, support custom 3-by-3 color matrices, optional x_breaks and y_breaks for stable bins, and explicit na_color handling (#181).
Continuous legends can now expose an opt-in color-ramp picker. Use color_ramps, selected_ramp, and ramp_picker = TRUE with continuous legends to let readers switch palettes directly from the legend; named ramps display labels by default, and ramp_labels = FALSE creates a compact picker.
interpolate_palette() now carries additional scale metadata for downstream legends, including the source column, missing-value color, available color ramps, and selected ramp.
Compare maps now initialize legend interactivity assets so supported dynamic legend features can be used in compare views.
GitHub source archives are now slimmed with .gitattributes export-ignore rules for generated site and vignette assets. This preserves GitHub Pages content in the repository while avoiding large downloads during GitHub-based package installs.
New save_map() function renders a map widget to a static PNG file using headless Chrome via the chromote package. Supports options for hiding controls, including/excluding legends and scale bars, replacing the basemap with a solid color, and retina-quality output with image_scale. New print_map() function provides the same capability for use in Quarto and R Markdown documents.
Update MapLibre GL JS to v5.22.0 and Mapbox GL JS to v3.21.0.
Mapbox GL JS v3.21.0 adds native PMTiles support via the TileProvider API. add_pmtiles_source() now uses the native vector tile path for Mapbox, removing the need for the custom PMTiles source implementation for vector tiles.
Support for color tables and categorical rasters in add_image_source() (#21).
New layer properties: circle_emissive_strength, circle_pitch_alignment, circle_pitch_scale, fill_pattern_cross_fade, line_elevation_reference, line_elevation_ground_scale, line_pattern_cross_fade, fill_extrusion_emissive_strength, fill_extrusion_ambient_occlusion_intensity, fill_extrusion_ambient_occlusion_radius, fill_extrusion_cast_shadows, fill_extrusion_cutoff_fade_range, fill_extrusion_vertical_gradient, raster_color, raster_color_mix, raster_color_range, raster_emissive_strength, icon_occlusion_opacity, text_occlusion_opacity.
sfc geometry vectors are now accepted wherever sf objects are accepted, including layer functions, quickview functions, and the bounds parameter (#177).
Various bug fixes and performance improvements (see GitHub issues for full list).
Esri styles support: New esri_style() function provides access to Esri basemap styles for use with MapLibre maps, with support for ArcGIS API key authentication.
MLT format support: Updated PMTiles implementation to support the MapLibre Tiles (MLT) format.
Update Mapbox GL JS to v3.19.1 and MapLibre GL JS to v5.19.0.
Bug fixes and improvements:
add_reset_control() button to match the visual style of other navigation controls (#168)set_source() not working with maplibre_compare_proxy() and mapboxgl_compare_proxy() (#171)Update Mapbox GL JS to v3.17.0 and MapLibre GL JS to v5.15.0.
Interactive legends: New opt-in interactivity for map legends enables direct data filtering from the legend:
interactive = TRUE, filter_column, filter_values, and classification in legend functionsinteractive_legend parameter in maplibre_view() and mapboxgl_view() for quick interactive visualizationsDraggable legends: New draggable = TRUE parameter allows users to drag legends to any position on the map. Supports both mouse and touch interactions.
Screenshot control: New add_screenshot_control() function allows users to capture and download map screenshots as PNG images. Includes image_scale parameter for controlling output resolution.
Globe projection for compare views: Compare maps in MapLibre now properly respect globe projection when specified.
Bug fixes and improvements:
get_legend_labels())step_expr() to properly handle quoted column names (#148)add_image() now persist across set_style() callsbounds argument in maplibre() and mapboxgl() now accepts sf::st_bbox() output with automatic CRS transformation to EPSG:4326.set_style() is called in Shiny applications.clear_controls() not properly removing controls.projection argument to maplibre() for consistency with mapboxgl().number_format() in comparison maps.palette_to_lut() function for creating custom color lookup tables in Mapbox styles.maplibre() and maplibre_view() now defaults to the globe projection.Enhanced draw control with new drawing modes and live measurements:
measurement_units parameterAdvanced layers control enhancements:
New map style support:
openfreemap_style() function providing free, open-source map styles without API key requirementsGeneral improvements:
Selective control functionality: Enhanced control over which map controls are displayed and when, providing more granular customization options
Bug fixes and stability improvements:
move_layer() functionality outside of Shiny applicationsturf_buffer(), turf_union(), turf_intersect(), turf_difference()turf_filter() with predicates (intersects, within, contains, crosses, disjoint)turf_convex_hull(), turf_concave_hull(), turf_voronoi(), turf_centroid(), turf_center_of_mass()turf_distance() and turf_area() (Shiny-only)download_button and download_filename parameters in add_draw_control(). Users can now export drawn features as GeoJSON files directly from the draw control interface.provider parameter allows choosing between "osm" (OpenStreetMap/Nominatim) and "maptiler" providers, with maptiler_api_key parameter for MapTiler authentication.add_pmtiles_source() function.query_rendered_features() and get_queried_features() allow users to directly invoke the map.queryRenderedFeatures() method from the parent libraries in Shiny apps, with guidance for users to avoid race conditions and ensure proper synchronization. Functions query visible features in the map's viewport and which can be extracted as sf objects.mapboxgl_view() and maplibre_view() now support quick view of terra rasters. A new function, add_view(), is designed to help users quickly stack layers on top of the core view functions.Added enable_shiny_hover() function for optional hover events in Shiny applications:
_hover input for mouse coordinates and _feature_hover input for feature informationmaplibre() and mapboxgl() widgets, including compare viewscoordinates = TRUE/FALSE and features = TRUE/FALSEmaplibre() |> add_circle_layer(...) |> enable_shiny_hover()"before" or "after") in input namesComprehensive legend styling system: Major enhancement to legend functionality with extensive customization options:
legend_style() function provides user-friendly interface for legend styling without requiring CSS knowledgemap_legends documentationAdvanced data classification system: New functions for automatic choropleth mapping similar to GIS software:
step_equal_interval(), step_quantile(), and step_jenks() for automatic classification with equal interval, quantile, and Jenks natural breaks methodsinterpolate_palette() for continuous color scaling with multiple interpolation methodsget_legend_labels(), get_legend_colors(), and get_breaks() for extracting classification metadataclassInt package for robust statistical classification algorithmsEnhanced MapTiler integration: Expanded support for MapTiler mapping styles:
variant parameter in maptiler_style()Robust set_style() improvements: Fixed critical issues with dynamic style switching in Shiny applications:
set_style() now properly preserves user-added layers, sources, popups, tooltips, and other map elements when switching base stylesNew number_format() function: Comprehensive number formatting for tooltips and map content:
Enhanced draw control functionality with improved feature editing capabilities:
add_features_to_draw()Fixed hover_options for vector tile sources in MapLibre (#67):
Enhanced tooltip functionality with expression support:
get_column() to reference feature properties in tooltipsconcat() helper function for combining strings and expressionstooltip = concat("<strong>Name:</strong> ", get_column("name"), "<br>Value: ", get_column("value"))set_tooltip() in Shiny applicationsmapboxgl_view() and maplibre_view() functions for quick visualization of sf objects with automatic geometry detection and column-based styling (#102).set_rain() and set_snow() functions.add_globe_control() for MapLibre maps, allowing users to toggle between "mercator" and "globe" projections.set_style() in Shiny applications for both Mapbox and MapLibre maps (#99).get_drawn_features() for Shiny modules (#95).layer_id parameter in add_legend(). When a layer is toggled in the layers control, its associated legend will automatically show or hide.margin_top, margin_right, margin_bottom, margin_left) that allow fine-grained control over legend placement.visibility = "none" showing as active in the control.compare() plugin in Shiny applications, with new rendering and proxy functions for comparison apps in Mapbox and MapLibre.mode parameter in compare() allowing users to choose between "swipe" mode with a comparison slider, and "sync" mode which displays synchronized maps side-by-side.add_image() allows you to add your own image to the map's sprite for use as an icon / symbol layeradd_geolocate_control() adds a Geolocate control to the mapadd_globe_minimap() adds a mini globe overview map that tracks how your map moves around the globeadd = TRUEmove_layer() function that gives you more fine-grained control over layer ordering in a Shiny sessionadd_geocoder_control()add_draw_control(freehand = TRUE)add_reset_control()cluster_options() function, to be used with the cluster_options argument in add_circle_layer() and add_symbol_layer()