Widgets and Layout Customization

As of version 0.7 DataMapPlot supports a flexible widget system for adding interactive UI components to your data map visualizations. There are widgets for titles, search, topic browsing, minimaps, layer visibility toggles, annotations, histograms, colormap switching, and more — and you can place any of them in the four corners of the plot or in slide-out drawers on either side.

This notebook will walk you through the widget system from the basics to a per-widget showcase, covering:

  • How the legacy parameter style still works (and is converted to widgets behind the scenes)

  • How to use widget classes directly for full control over placement and configuration

  • A tour of every built-in widget type with working examples

  • Layout presets and JSON configuration for reusable setups

Let’s start by importing DataMapPlot and loading some sample data.

[1]:
import datamapplot
import numpy as np
import pandas as pd
/work/home/lmmcinn/.conda/envs/datamapplot/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

Loading Sample Data

We’ll use the ArXiv ML data map that appears throughout the DataMapPlot documentation. It contains 2-D coordinates for roughly 118 000 machine-learning papers, five layers of cluster labels at different levels of detail, and per-point hover text with paper titles.

[2]:
newsgroup_df = pd.read_parquet("hf://datasets/lmcinnes/20newsgroups_topics/newsgroups_with_topics_customised.parquet")
news_data_map = np.stack(newsgroup_df["map"])
news_hover_data = np.stack(newsgroup_df["post"])
truncated_news_hover_data = np.array([text[:127] + "…" if len(text) > 128 else text for text in news_hover_data])
news_label_layers = [newsgroup_df[f"layer_{i}_topics"].values for i in range(5)]

The Legacy Way — Parameters You Already Know

If you’ve been using create_interactive_plot already, nothing changes. Parameters like title, enable_search, logo, and darkmode still work exactly the way they always have — behind the scenes they are simply converted into the corresponding widget objects. Let’s start with a familiar example.

That gives us a nice looking plot with a title, a search bar, and a logo — all placed in their default locations. Under the hood DataMapPlot created a TitleWidget (top-left), a SearchWidget (top-left, below the title), and a LogoWidget (bottom-right). But what if you want the search bar somewhere else, or you need a minimap, or you want to tuck a topic tree into a slide-out drawer? That’s where widget classes come in.

[3]:
# Using legacy parameters (automatically converted to widgets)
plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    title="20-Newsgroups Data Map",
    sub_title="A data map of posts from the classic 20-Newsgroups dataset",
    logo="https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png",
    logo_width=180,
    enable_search=True,
    darkmode=True,
)
plot
[3]:

Introducing Widget Classes

Every UI element in the plot is represented by a widget class. To use them, import the ones you need from datamapplot.widgets (or directly from datamapplot), configure them, and pass a list to the widgets parameter of create_interactive_plot.

Each widget accepts two placement parameters:

Parameter

Description

location

Where the widget appears. One of "top-left", "top-right", "bottom-left", "bottom-right", "drawer-left", or "drawer-right".

order

Stacking order within a location. Lower numbers appear first (top for corners, top of drawer for drawers).

Let’s recreate the previous plot with explicit widget classes — this time placing the search bar in the top-right corner instead of its default top-left position.

[4]:
from datamapplot.widgets import TitleWidget, SearchWidget, LogoWidget, TopicTreeWidget

widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="A data map of posts from the classic 20-Newsgroups dataset",
        title_font_size=32,
        location="top-left",
        order=0,
        darkmode=True,
    ),
    SearchWidget(
        search_field="hover_text",
        location="top-right",  # moved to top-right!
        order=0,
    ),
    LogoWidget(
        logo="https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png",
        logo_width=128,
        location="bottom-right",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    widgets=widgets,
)
plot
[4]:

Moving the search bar was trivial — just change location. Now let’s go further: we can place widgets in slide-out drawers that stay hidden until the user clicks the handle on the edge of the screen (or uses a keyboard shortcut). This keeps the map view clean while giving access to heavier tools like search and topic browsing.

Keyboard shortcuts:

  • Ctrl + [ — Toggle left drawer

  • Ctrl + ] — Toggle right drawer

  • Escape — Close all drawers

Below we’ll move search and a TopicTreeWidget into the left drawer. Notice that order controls stacking within the drawer — search (order 0) appears above the topic tree (order 1).

[5]:
widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Click the left edge to explore topics",
        location="top-left",
        order=0,
    ),
    SearchWidget(
        search_field="hover_text",
        location="drawer-left",
        order=0,
    ),
    TopicTreeWidget(
        location="drawer-left",
        order=1,
    ),
    LogoWidget(
        logo="https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png",
        logo_width=128,
        location="bottom-right",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    widgets=widgets,
)
plot
[5]:

Greater Control with Many Widgets

Where the widget system really shines is when you have many widgets to coordinate. With explicit widget lists you can scatter UI elements across corners and drawers in whatever arrangement makes sense for your use case.

Let’s build a more feature-rich plot with five widgets across multiple locations: a title and logo in the corners, search and a topic tree in the left drawer, and a minimap in the bottom-right corner for orientation.

[6]:
from datamapplot.widgets import MiniMapWidget, LayerToggleWidget

widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="A fully configured data map",
        location="top-left",
        order=0,
        darkmode=True,
    ),
    SearchWidget(
        search_field="hover_text",
        location="drawer-left",
        order=0,
    ),
    TopicTreeWidget(
        location="drawer-left",
        order=1,
    ),
    LayerToggleWidget(
        location="top-right",
        darkmode=True,
        order=0,
    ),
    MiniMapWidget(
        location="bottom-right",
        darkmode=True,
        order=1,
    ),
    LogoWidget(
        logo="https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png",
        logo_width=128,
        location="bottom-right",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    cluster_boundary_polygons=True,
    widgets=widgets,
    use_widgets=True
)
plot
[6]:

Six widgets, four locations — and the code is straightforward. If you later decide the minimap should be in the bottom-left or the layer toggle should be in a drawer, it’s a one-line change per widget.

Now let’s take a closer look at each widget type so you know what’s available and how to configure them.

Widget Showcase

The sections below walk through each built-in widget with a working example and a few key configuration options. We won’t try to be exhaustive — check the API reference for the full list of parameters — but this should give you enough to get started with each one.

TitleWidget

The TitleWidget displays a title and optional subtitle. You can control font family, size, weight, and color — and there’s a darkmode flag that automatically flips text to light colors on dark backgrounds.

[7]:
widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Custom fonts and colors",
        title_font_family="Cinzel",
        title_font_size=28,
        sub_title_font_size=14,
        title_font_color="#1a73e8",
        sub_title_font_color="#888888",
        location="top-left",
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    widgets=widgets,
)
plot
[7]:

SearchWidget

The SearchWidget adds a text search box that highlights matching points on the map. By default it searches the "hover_text" field, but you can point it at any field in your data. The placeholder parameter controls the hint text shown in the empty search box.

[8]:
widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Try searching for 'hockey' or 'X11'",
        location="top-left",
        darkmode=True,
    ),
    SearchWidget(
        placeholder="🔍 Search posts…",
        search_field="hover_text",
        location="top-left",
        order=1,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    widgets=widgets,
)
plot
[8]:

TopicTreeWidget

The TopicTreeWidget renders a hierarchical tree of cluster topics built from the label layers. Clicking a topic zooms the map to that cluster. It works best in a drawer where it has room to expand, but it can live in a corner too. Notable configuration options include font_size, max_height, and color_bullets (which colors the bullet markers to match cluster colors).

[9]:
widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Open the left drawer to browse topics",
        location="top-left",
        darkmode=True,
    ),
    TopicTreeWidget(
        font_size="11pt",
        max_height="50vh",
        color_bullets=True,
        location="drawer-left",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    widgets=widgets,
)
plot
[9]:

MiniMapWidget

The MiniMapWidget renders a small overview map in the corner showing all the data points and a viewport indicator. You can click anywhere in the minimap to jump there, or drag the viewport rectangle to pan. Key options include width, height, point_color, and border_color.

[10]:
widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Minimap in the bottom right shows the current viewport",
        location="top-left",
        darkmode=True,
    ),
    MiniMapWidget(
        width=220,
        height=160,
        point_color="#9ecae1",
        border_color="#2171b5",
        background_color="#1a1a2e",
        location="bottom-right",
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    widgets=widgets,
)
plot
[10]:

LayerToggleWidget

The LayerToggleWidget provides checkboxes and opacity sliders for toggling map layers on and off — points, labels, cluster boundaries, edges, and so on. By default it shows controls for all standard layers. You can pass a custom layers list to show only the layers that are relevant to your particular plot, and set show_opacity to control whether opacity sliders are displayed.

[11]:
widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Use the layer toggles in the top-right corner",
        location="top-left",
        title_font_family="Playfair",
        title_font_size=56,
        sub_title_font_size=24,
        sub_title_color="#4433ff",
        darkmode=True,
    ),
    LayerToggleWidget(
        layers=[
            {"id": "dataPointLayer", "label": "Points", "visible": True, "opacity": 1.0},
            {"id": "labelLayer", "label": "Labels", "visible": True, "opacity": 0.75},
            {"id": "boundaryLayer", "label": "Cluster Boundaries", "visible": True, "opacity": 0.4},
        ],
        show_opacity=True,
        location="top-right",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    cluster_boundary_polygons=True,
    widgets=widgets,
)
plot
[11]:

SelectionControlWidget

The SelectionControlWidget adds buttons for managing point selections: selection modes (replace, add, subtract, intersect), named selection groups you can save and recall, and a clear button. It pairs naturally with a selection source like lasso selection (enabled by passing a selection_handler to create_interactive_plot). Here we’ll combine it with a simple DisplaySample handler that shows a sample of selected point data in a drawer.

[12]:
from datamapplot.widgets import SelectionControlWidget

widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Draw a lasso to select points, then manage selections in the top-right",
        location="top-left",
        darkmode=True,
    ),
    SelectionControlWidget(
        show_modes=True,
        show_groups=True,
        show_clear=True,
        location="top-right",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    selection_handler=datamapplot.DisplaySample(),
    widgets=widgets,
)
plot
[12]:

ColormapSelectorWidget and LegendWidget

The ColormapSelectorWidget lets users switch between different color mappings at runtime. It needs per-point data to color by, supplied as a dictionary of field names to arrays via the colormaps parameter. The LegendWidget renders the corresponding color legend and should always be included alongside the colormap selector.

For this example we’ll create a quick “abstract length” field from the hover text so we have something numeric to color by.

[13]:
from datamapplot.widgets import ColormapSelectorWidget, LegendWidget

# Create a per-point numeric field: length of the hover text
post_lengths = np.array([len(str(h)) for h in news_hover_data])

widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Switch colormaps in the bottom-left",
        location="top-left",
    ),
    ColormapSelectorWidget(
        colormaps={"Post Length": post_lengths},
        location="bottom-left",
        order=0,
    ),
    LegendWidget(
        location="top-right",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    widgets=widgets,
)
plot
[13]:

HistogramWidget

The HistogramWidget displays an interactive histogram with brush selection — drag across a range of bins to highlight the corresponding points on the map. It accepts numeric, categorical, or datetime data via the histogram_data parameter (one value per point). Here we’ll reuse the abstract-length array from above.

[14]:
from datamapplot.widgets import HistogramWidget

widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Brush the histogram to filter by post length",
        location="top-left",
        darkmode=True,
    ),
    HistogramWidget(
        histogram_data=post_lengths,
        histogram_width=350,
        histogram_height=80,
        histogram_title="Abstract Length (characters)",
        histogram_bin_count=30,
        histogram_bin_fill_color="#6290C3",
        histogram_bin_selected_fill_color="#2EBFA5",
        location="bottom-left",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    widgets=widgets,
)
plot
[14]:

AnnotationWidget

The AnnotationWidget gives users tools to mark up the map with text labels, arrows, circles, and rectangles — all stored in data coordinates so they track with pan and zoom. Annotations can be exported to JSON and re-imported later via the toolbar buttons.

One thing to note: this widget requires live interaction (clicking or drawing on the canvas) to create annotations, so the plot below will look like a plain map in static documentation. Try it out live — open the left drawer, pick a tool, and draw on the map. You can configure which tools are available and set default colors and stroke widths.

[15]:
from datamapplot.widgets import AnnotationWidget

widgets = [
    TitleWidget(
        title="20-Newsgroups Data Map",
        sub_title="Open the left drawer to access annotation tools",
        location="top-left",
        darkmode=True,
    ),
    AnnotationWidget(
        allow_text=True,
        allow_arrows=True,
        allow_circles=True,
        allow_rectangles=True,
        default_text_color="#ffffff",
        default_stroke_color="#ff6b6b",
        default_stroke_width=3,
        enable_export=True,
        enable_import=True,
        location="drawer-left",
        order=0,
    ),
]

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    widgets=widgets,
)
plot
[15]:

Layout Presets and JSON Configuration

If you find yourself building the same widget arrangement repeatedly, DataMapPlot ships with a few built-in layout presets you can activate with a single parameter. These work with the legacy parameter style — the preset controls where each auto-generated widget ends up.

Preset

Description

"minimal"

Just title and logo — nothing else.

"default"

Title, search, topic tree in corners; colormap and histogram in bottom-left.

"analyst"

Search and topic tree in the left drawer; colormap and legend in the right drawer.

"presentation"

Clean layout for presenting — search and colormap tucked into the right drawer.

[16]:
plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    title="20-Newsgroups Data Map",
    sub_title="Using the 'analyst' layout preset",
    logo="https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png",
    default_widget_config="analyst",
)
plot
[16]:

Custom Configuration Dictionaries

Instead of a preset name you can pass a dictionary (or a JSON file path) to default_widget_config. The dictionary maps widget class names to their keyword arguments, giving you full control over the initial layout without writing individual *Widget(...) calls.

Below is the same “analyst” preset expressed as a plain dict so you can see the structure. Tweak any value and re-run to experiment.

[17]:
custom_config = {
    "title": {
        "custom_params": {
            "title": "20-Newsgroups Data Map",
            "sub_title": "Custom JSON-style config",
            "title_font_family": "Playfair Display SC",
            "sub_tile_font_family": "Playfair Display SC",
            "darkmode": True
        },
        "location": "top-right",
    },
    "search": {
        "location": "top-left",
    },
    "minimap": {
        "location": "bottom-left",
    },
}

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    darkmode=True,
    default_widget_config=custom_config,
)
plot
[17]:

Mixing Legacy Parameters with Explicit Widgets

If you pass both legacy convenience parameters (like title=...) and explicit *Widget(...) objects through extra_widgets, the explicit widgets take precedence for any overlap. This lets you start from a preset and surgically override just the pieces you care about.

# The TitleWidget below overrides the title/sub_title that would
# otherwise come from the legacy parameters.
[18]:
from datamapplot.widgets import TitleWidget, SearchWidget

plot = datamapplot.create_interactive_plot(
    news_data_map,
    *news_label_layers,
    hover_text=truncated_news_hover_data,
    font_family="Playfair Display SC",
    # Legacy params — these create default widgets behind the scenes
    title="News Data Map",
    sub_title="Legacy title that gets overridden",
    enable_search=True,
    darkmode=True,
    # Explicit widget overrides the legacy TitleWidget
    widgets=[
        TitleWidget(
            title="20-Newsgroups Data Map",
            sub_title="Overridden by explicit TitleWidget!",
            font_family="Playfair Display SC",
            darkmode=True,
            location="top-right",       # moved from default upper-left
        ),
    ],
)
plot
[18]:

Summary — Widget Quick-Reference

Widget

Purpose

Key Parameters

TitleWidget

Plot title & subtitle

title, sub_title, font_family, font_color

SearchWidget

Full-text search over hover text

query_field, font_family

LogoWidget

Corner logo image

logo_url, logo_width

TopicTreeWidget

Collapsible label hierarchy

No extra params — uses the label layers

MiniMapWidget

Zoomable overview + viewport drag

minimap_width, background_color

LayerToggleWidget

Show/hide label layers

layers (list of layer names)

SelectionControlWidget

Lasso / rectangle select with handlers

selection_handler (JS or DisplaySample)

ColormapSelectorWidget

Switch between colormaps at runtime

colormaps dict, default_colormap

LegendWidget

Interactive color legend

legend_title, extra_css

HistogramWidget

Value-distribution histogram

histogram_data (numeric array), n_bins

AnnotationWidget

Draw annotations (text, arrows, …) on the map

tools, default_styleexperimental

RESTSearchWidget

Server-side search via REST API

api_url, query_fieldfor advanced use

Locations follow the pattern "upper-left", "upper-right", "lower-left", "lower-right", (or "drawer-left" / "drawer-right", "drawer-bottom" for drawer panels).

For the full API reference see the widget API docs.