{
"cells": [
{
"cell_type": "markdown",
"id": "f3f105b7",
"metadata": {},
"source": [
"# Widgets and Layout Customization\n",
"\n",
"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.\n",
"\n",
"This notebook will walk you through the widget system from the basics to a per-widget showcase, covering:\n",
"\n",
"- How the legacy parameter style still works (and is converted to widgets behind the scenes)\n",
"- How to use widget classes directly for full control over placement and configuration\n",
"- A tour of every built-in widget type with working examples\n",
"- Layout presets and JSON configuration for reusable setups\n",
"\n",
"Let's start by importing DataMapPlot and loading some sample data."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "2f4359e9",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:16.580595Z",
"iopub.status.busy": "2026-03-22T01:14:16.580475Z",
"iopub.status.idle": "2026-03-22T01:14:21.929103Z",
"shell.execute_reply": "2026-03-22T01:14:21.928649Z",
"shell.execute_reply.started": "2026-03-22T01:14:16.580581Z"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/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\n",
" from .autonotebook import tqdm as notebook_tqdm\n"
]
}
],
"source": [
"import datamapplot\n",
"import numpy as np\n",
"import pandas as pd"
]
},
{
"cell_type": "markdown",
"id": "3a48da98",
"metadata": {},
"source": [
"## Loading Sample Data\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4ea8dc3b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:21.929943Z",
"iopub.status.busy": "2026-03-22T01:14:21.929580Z",
"iopub.status.idle": "2026-03-22T01:14:23.913474Z",
"shell.execute_reply": "2026-03-22T01:14:23.913024Z",
"shell.execute_reply.started": "2026-03-22T01:14:21.929927Z"
}
},
"outputs": [],
"source": [
"newsgroup_df = pd.read_parquet(\"hf://datasets/lmcinnes/20newsgroups_topics/newsgroups_with_topics_customised.parquet\")\n",
"news_data_map = np.stack(newsgroup_df[\"map\"])\n",
"news_hover_data = np.stack(newsgroup_df[\"post\"])\n",
"truncated_news_hover_data = np.array([text[:127] + \"…\" if len(text) > 128 else text for text in news_hover_data])\n",
"news_label_layers = [newsgroup_df[f\"layer_{i}_topics\"].values for i in range(5)]"
]
},
{
"cell_type": "markdown",
"id": "42f66ed2",
"metadata": {},
"source": [
"## The Legacy Way — Parameters You Already Know\n",
"\n",
"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."
]
},
{
"cell_type": "markdown",
"id": "fa7e138b",
"metadata": {},
"source": [
"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."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "8855db19",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:23.914170Z",
"iopub.status.busy": "2026-03-22T01:14:23.914015Z",
"iopub.status.idle": "2026-03-22T01:14:24.726446Z",
"shell.execute_reply": "2026-03-22T01:14:24.725987Z",
"shell.execute_reply.started": "2026-03-22T01:14:23.914157Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Using legacy parameters (automatically converted to widgets)\n",
"plot = datamapplot.create_interactive_plot(\n",
" news_data_map,\n",
" *news_label_layers,\n",
" hover_text=truncated_news_hover_data,\n",
" font_family=\"Playfair Display SC\",\n",
" title=\"20-Newsgroups Data Map\",\n",
" sub_title=\"A data map of posts from the classic 20-Newsgroups dataset\",\n",
" logo=\"https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png\",\n",
" logo_width=180,\n",
" enable_search=True,\n",
" darkmode=True,\n",
")\n",
"plot"
]
},
{
"cell_type": "markdown",
"id": "b1a386f0",
"metadata": {},
"source": [
"## Introducing Widget Classes\n",
"\n",
"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``.\n",
"\n",
"Each widget accepts two placement parameters:\n",
"\n",
"| Parameter | Description |\n",
"|-----------|-------------|\n",
"| ``location`` | Where the widget appears. One of ``\"top-left\"``, ``\"top-right\"``, ``\"bottom-left\"``, ``\"bottom-right\"``, ``\"drawer-left\"``, or ``\"drawer-right\"``. |\n",
"| ``order`` | Stacking order within a location. Lower numbers appear first (top for corners, top of drawer for drawers). |\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a95cdb2f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:24.727225Z",
"iopub.status.busy": "2026-03-22T01:14:24.727029Z",
"iopub.status.idle": "2026-03-22T01:14:25.711343Z",
"shell.execute_reply": "2026-03-22T01:14:25.710983Z",
"shell.execute_reply.started": "2026-03-22T01:14:24.727211Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from datamapplot.widgets import TitleWidget, SearchWidget, LogoWidget, TopicTreeWidget\n",
"\n",
"widgets = [\n",
" TitleWidget(\n",
" title=\"20-Newsgroups Data Map\",\n",
" sub_title=\"A data map of posts from the classic 20-Newsgroups dataset\",\n",
" title_font_size=32,\n",
" location=\"top-left\",\n",
" order=0,\n",
" darkmode=True,\n",
" ),\n",
" SearchWidget(\n",
" search_field=\"hover_text\",\n",
" location=\"top-right\", # moved to top-right!\n",
" order=0,\n",
" ),\n",
" LogoWidget(\n",
" logo=\"https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png\",\n",
" logo_width=128,\n",
" location=\"bottom-right\",\n",
" order=0,\n",
" ),\n",
"]\n",
"\n",
"plot = datamapplot.create_interactive_plot(\n",
" news_data_map,\n",
" *news_label_layers,\n",
" hover_text=truncated_news_hover_data,\n",
" font_family=\"Playfair Display SC\",\n",
" darkmode=True,\n",
" widgets=widgets,\n",
")\n",
"plot"
]
},
{
"cell_type": "markdown",
"id": "ee633c72",
"metadata": {},
"source": [
"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.\n",
"\n",
"**Keyboard shortcuts:**\n",
"- ``Ctrl + [`` — Toggle left drawer\n",
"- ``Ctrl + ]`` — Toggle right drawer\n",
"- ``Escape`` — Close all drawers\n",
"\n",
"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)."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "67ef7527",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:25.711941Z",
"iopub.status.busy": "2026-03-22T01:14:25.711775Z",
"iopub.status.idle": "2026-03-22T01:14:26.810437Z",
"shell.execute_reply": "2026-03-22T01:14:26.810067Z",
"shell.execute_reply.started": "2026-03-22T01:14:25.711927Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"widgets = [\n",
" TitleWidget(\n",
" title=\"20-Newsgroups Data Map\",\n",
" sub_title=\"Click the left edge to explore topics\",\n",
" location=\"top-left\",\n",
" order=0,\n",
" ),\n",
" SearchWidget(\n",
" search_field=\"hover_text\",\n",
" location=\"drawer-left\",\n",
" order=0,\n",
" ),\n",
" TopicTreeWidget(\n",
" location=\"drawer-left\",\n",
" order=1,\n",
" ),\n",
" LogoWidget(\n",
" logo=\"https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png\",\n",
" logo_width=128,\n",
" location=\"bottom-right\",\n",
" order=0,\n",
" ),\n",
"]\n",
"\n",
"plot = datamapplot.create_interactive_plot(\n",
" news_data_map,\n",
" *news_label_layers,\n",
" hover_text=truncated_news_hover_data,\n",
" font_family=\"Playfair Display SC\",\n",
" widgets=widgets,\n",
")\n",
"plot"
]
},
{
"cell_type": "markdown",
"id": "20db3461",
"metadata": {},
"source": [
"## Greater Control with Many Widgets\n",
"\n",
"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.\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "8fe58738",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:26.811894Z",
"iopub.status.busy": "2026-03-22T01:14:26.811698Z",
"iopub.status.idle": "2026-03-22T01:14:33.187074Z",
"shell.execute_reply": "2026-03-22T01:14:33.186339Z",
"shell.execute_reply.started": "2026-03-22T01:14:26.811878Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from datamapplot.widgets import MiniMapWidget, LayerToggleWidget\n",
"\n",
"widgets = [\n",
" TitleWidget(\n",
" title=\"20-Newsgroups Data Map\",\n",
" sub_title=\"A fully configured data map\",\n",
" location=\"top-left\",\n",
" order=0,\n",
" darkmode=True,\n",
" ),\n",
" SearchWidget(\n",
" search_field=\"hover_text\",\n",
" location=\"drawer-left\",\n",
" order=0,\n",
" ),\n",
" TopicTreeWidget(\n",
" location=\"drawer-left\",\n",
" order=1,\n",
" ),\n",
" LayerToggleWidget(\n",
" location=\"top-right\",\n",
" darkmode=True,\n",
" order=0,\n",
" ),\n",
" MiniMapWidget(\n",
" location=\"bottom-right\",\n",
" darkmode=True,\n",
" order=1,\n",
" ),\n",
" LogoWidget(\n",
" logo=\"https://raw.githubusercontent.com/TutteInstitute/.github/refs/heads/main/profile/images/tutte_logo_redesign_horizontal_original_colours.png\",\n",
" logo_width=128,\n",
" location=\"bottom-right\",\n",
" order=0,\n",
" ),\n",
"]\n",
"\n",
"plot = datamapplot.create_interactive_plot(\n",
" news_data_map,\n",
" *news_label_layers,\n",
" hover_text=truncated_news_hover_data,\n",
" font_family=\"Playfair Display SC\",\n",
" darkmode=True,\n",
" cluster_boundary_polygons=True,\n",
" widgets=widgets,\n",
" use_widgets=True\n",
")\n",
"plot"
]
},
{
"cell_type": "markdown",
"id": "d1de268e",
"metadata": {},
"source": [
"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.\n",
"\n",
"Now let's take a closer look at each widget type so you know what's available and how to configure them.\n",
"\n",
"## Widget Showcase\n",
"\n",
"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."
]
},
{
"cell_type": "markdown",
"id": "943b1a2c",
"metadata": {},
"source": [
"### TitleWidget\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "3d56257b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:33.188045Z",
"iopub.status.busy": "2026-03-22T01:14:33.187884Z",
"iopub.status.idle": "2026-03-22T01:14:34.154118Z",
"shell.execute_reply": "2026-03-22T01:14:34.153733Z",
"shell.execute_reply.started": "2026-03-22T01:14:33.188032Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"widgets = [\n",
" TitleWidget(\n",
" title=\"20-Newsgroups Data Map\",\n",
" sub_title=\"Custom fonts and colors\",\n",
" title_font_family=\"Cinzel\",\n",
" title_font_size=28,\n",
" sub_title_font_size=14,\n",
" title_font_color=\"#1a73e8\",\n",
" sub_title_font_color=\"#888888\",\n",
" location=\"top-left\",\n",
" ),\n",
"]\n",
"\n",
"plot = datamapplot.create_interactive_plot(\n",
" news_data_map,\n",
" *news_label_layers,\n",
" hover_text=truncated_news_hover_data,\n",
" font_family=\"Playfair Display SC\",\n",
" widgets=widgets,\n",
")\n",
"plot"
]
},
{
"cell_type": "markdown",
"id": "6b158e43",
"metadata": {},
"source": [
"### SearchWidget\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "ce0f86ec",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:34.154664Z",
"iopub.status.busy": "2026-03-22T01:14:34.154528Z",
"iopub.status.idle": "2026-03-22T01:14:35.126373Z",
"shell.execute_reply": "2026-03-22T01:14:35.125894Z",
"shell.execute_reply.started": "2026-03-22T01:14:34.154651Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"widgets = [\n",
" TitleWidget(\n",
" title=\"20-Newsgroups Data Map\",\n",
" sub_title=\"Try searching for 'hockey' or 'X11'\",\n",
" location=\"top-left\",\n",
" darkmode=True,\n",
" ),\n",
" SearchWidget(\n",
" placeholder=\"🔍 Search posts…\",\n",
" search_field=\"hover_text\",\n",
" location=\"top-left\",\n",
" order=1,\n",
" ),\n",
"]\n",
"\n",
"plot = datamapplot.create_interactive_plot(\n",
" news_data_map,\n",
" *news_label_layers,\n",
" hover_text=truncated_news_hover_data,\n",
" font_family=\"Playfair Display SC\",\n",
" darkmode=True,\n",
" widgets=widgets,\n",
")\n",
"plot"
]
},
{
"cell_type": "markdown",
"id": "86ac2c87",
"metadata": {},
"source": [
"### TopicTreeWidget\n",
"\n",
"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)."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "45102f1f",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:35.127073Z",
"iopub.status.busy": "2026-03-22T01:14:35.126915Z",
"iopub.status.idle": "2026-03-22T01:14:36.185404Z",
"shell.execute_reply": "2026-03-22T01:14:36.185019Z",
"shell.execute_reply.started": "2026-03-22T01:14:35.127061Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"widgets = [\n",
" TitleWidget(\n",
" title=\"20-Newsgroups Data Map\",\n",
" sub_title=\"Open the left drawer to browse topics\",\n",
" location=\"top-left\",\n",
" darkmode=True,\n",
" ),\n",
" TopicTreeWidget(\n",
" font_size=\"11pt\",\n",
" max_height=\"50vh\",\n",
" color_bullets=True,\n",
" location=\"drawer-left\",\n",
" order=0,\n",
" ),\n",
"]\n",
"\n",
"plot = datamapplot.create_interactive_plot(\n",
" news_data_map,\n",
" *news_label_layers,\n",
" hover_text=truncated_news_hover_data,\n",
" font_family=\"Playfair Display SC\",\n",
" darkmode=True,\n",
" widgets=widgets,\n",
")\n",
"plot"
]
},
{
"cell_type": "markdown",
"id": "dfe6ea16",
"metadata": {},
"source": [
"### MiniMapWidget\n",
"\n",
"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``."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "f51ff7fc",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:36.185954Z",
"iopub.status.busy": "2026-03-22T01:14:36.185797Z",
"iopub.status.idle": "2026-03-22T01:14:37.168084Z",
"shell.execute_reply": "2026-03-22T01:14:37.167688Z",
"shell.execute_reply.started": "2026-03-22T01:14:36.185940Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"widgets = [\n",
" TitleWidget(\n",
" title=\"20-Newsgroups Data Map\",\n",
" sub_title=\"Minimap in the bottom right shows the current viewport\",\n",
" location=\"top-left\",\n",
" darkmode=True,\n",
" ),\n",
" MiniMapWidget(\n",
" width=220,\n",
" height=160,\n",
" point_color=\"#9ecae1\",\n",
" border_color=\"#2171b5\",\n",
" background_color=\"#1a1a2e\",\n",
" location=\"bottom-right\",\n",
" ),\n",
"]\n",
"\n",
"plot = datamapplot.create_interactive_plot(\n",
" news_data_map,\n",
" *news_label_layers,\n",
" hover_text=truncated_news_hover_data,\n",
" font_family=\"Playfair Display SC\",\n",
" darkmode=True,\n",
" widgets=widgets,\n",
")\n",
"plot"
]
},
{
"cell_type": "markdown",
"id": "b461e6a0",
"metadata": {},
"source": [
"### LayerToggleWidget\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "1625a848",
"metadata": {
"execution": {
"iopub.execute_input": "2026-03-22T01:14:37.168667Z",
"iopub.status.busy": "2026-03-22T01:14:37.168527Z",
"iopub.status.idle": "2026-03-22T01:14:39.058256Z",
"shell.execute_reply": "2026-03-22T01:14:39.057865Z",
"shell.execute_reply.started": "2026-03-22T01:14:37.168655Z"
}
},
"outputs": [
{
"data": {
"text/html": [
"\n",
"