Skip to content

Commit 1916271

Browse files
droumistcmetzger
andauthored
Adds section on floating interfaces (popups, etc) to the widgets and interactivity notebook (#28)
* add section on floating interfaces (popups, etc) * re-enable sampledata download --------- Co-authored-by: Timo Cornelius Metzger <[email protected]>
1 parent 5a2691f commit 1916271

File tree

1 file changed

+269
-1
lines changed

1 file changed

+269
-1
lines changed

notebooks/11_widgets_interactivity.ipynb

Lines changed: 269 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,274 @@
945945
"show(largest_carriers_layout)"
946946
]
947947
},
948+
{
949+
"cell_type": "markdown",
950+
"metadata": {},
951+
"source": [
952+
"## Floating Interfaces"
953+
]
954+
},
955+
{
956+
"cell_type": "markdown",
957+
"metadata": {},
958+
"source": [
959+
"Floating interfaces provide a dynamic and interactive experience by adding contextualized actions, information, and visualizations in separate, movable windows. These interfaces enhance the user experience by allowing contextualized plots and UI elements to be accessed without cluttering the main interface."
960+
]
961+
},
962+
{
963+
"cell_type": "markdown",
964+
"metadata": {},
965+
"source": [
966+
"### Example: Popup plot in a floating window"
967+
]
968+
},
969+
{
970+
"cell_type": "markdown",
971+
"metadata": {},
972+
"source": [
973+
"In this example, we create a colorful scatter plot that can be displayed via a button in a popup dialog. This allows users to view and interact with the plot in a separate floating window within the browser tab.\n",
974+
"\n",
975+
"First, we'll create a reusable function to generate the scatter plot."
976+
]
977+
},
978+
{
979+
"cell_type": "code",
980+
"execution_count": null,
981+
"metadata": {},
982+
"outputs": [],
983+
"source": [
984+
"import numpy as np\n",
985+
"from bokeh.models import Button, CloseDialog, Column, Dialog, OpenDialog, BoxSelectTool, CustomJS, PaletteSelect, Panel, Row, ActionItem, DividerItem, Menu\n",
986+
"from bokeh.palettes import Spectral11\n",
987+
"\n",
988+
"# Function to generate a colorful scatter plot\n",
989+
"def confetti_plot(N: int):\n",
990+
" # Generate plot data\n",
991+
" x = np.random.random(size=N) * 100\n",
992+
" y = np.random.random(size=N) * 100\n",
993+
" radii = np.random.random(size=N) * 1.5\n",
994+
" colors = np.random.choice(Spectral11, size=N)\n",
995+
"\n",
996+
" # Create the plot\n",
997+
" box_select = BoxSelectTool(persistent=True)\n",
998+
" p = figure(active_scroll=\"wheel_zoom\", lod_threshold=None, title=f\"Plot with N={N} circles\")\n",
999+
" p.add_tools(box_select)\n",
1000+
" p.toolbar.active_drag = box_select\n",
1001+
" cr = p.circle(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None)\n",
1002+
"\n",
1003+
" return p, cr, box_select"
1004+
]
1005+
},
1006+
{
1007+
"cell_type": "markdown",
1008+
"metadata": {},
1009+
"source": [
1010+
" The code below integrates the plot into a `Dialog` model that can be opened and closed using buttons."
1011+
]
1012+
},
1013+
{
1014+
"cell_type": "code",
1015+
"execution_count": null,
1016+
"metadata": {},
1017+
"outputs": [],
1018+
"source": [
1019+
"def popup_dialog_ui(N: int = 1000):\n",
1020+
" plot, _, _ = confetti_plot(1000)\n",
1021+
" show_plot = Button(\n",
1022+
" label=\"Popup plot\",\n",
1023+
" button_type=\"primary\",\n",
1024+
" )\n",
1025+
" close_plot = Button(label=\"Close\", button_type=\"success\")\n",
1026+
" dialog = Dialog(\n",
1027+
" title=\"Dialog with a plot\",\n",
1028+
" content=Column(\n",
1029+
" children=[\n",
1030+
" plot,\n",
1031+
" close_plot,\n",
1032+
" ],\n",
1033+
" ),\n",
1034+
" )\n",
1035+
" show_plot.js_on_click(OpenDialog(dialog=dialog))\n",
1036+
" close_plot.js_on_click(CloseDialog(dialog=dialog))\n",
1037+
"\n",
1038+
" return show_plot\n",
1039+
"\n",
1040+
"show(popup_dialog_ui())"
1041+
]
1042+
},
1043+
{
1044+
"cell_type": "markdown",
1045+
"metadata": {},
1046+
"source": [
1047+
"### Example: Context menu for selected ranges\n",
1048+
"Another powerful feature of floating interfaces is the ability to add context menus that appear when a user right-clicks in a selected region. This example shows how to add a context menu to a scatter plot with options to delete selected points or clear the selection."
1049+
]
1050+
},
1051+
{
1052+
"cell_type": "code",
1053+
"execution_count": null,
1054+
"metadata": {},
1055+
"outputs": [],
1056+
"source": [
1057+
"# Create the plot\n",
1058+
"plot, cr, box_select = confetti_plot(1000)\n",
1059+
"\n",
1060+
"# Define context menu actions and layout\n",
1061+
"delete_selected = CustomJS(\n",
1062+
" args=dict(renderer=cr),\n",
1063+
" code=\"\"\"\n",
1064+
"export default ({renderer}) => {\n",
1065+
" const {entries} = Bokeh.require(\"core/util/object\")\n",
1066+
" const {enumerate} = Bokeh.require(\"core/util/iterator\")\n",
1067+
"\n",
1068+
" const {data, selected} = renderer.data_source\n",
1069+
" const indices = new Set(selected.indices)\n",
1070+
"\n",
1071+
" const new_data = {}\n",
1072+
" for (const [name, column] of entries(data)) {\n",
1073+
" new_data[name] = column.filter((value, i) => !indices.has(i))\n",
1074+
" }\n",
1075+
"\n",
1076+
" renderer.data_source.data = new_data\n",
1077+
" renderer.data_source.selected.indices = []\n",
1078+
"}\n",
1079+
" \"\"\",\n",
1080+
")\n",
1081+
"\n",
1082+
"clear_selection = CustomJS(\n",
1083+
" args=dict(renderer=cr, overlay=box_select.overlay),\n",
1084+
" code=\"\"\"\n",
1085+
"export default ({renderer, overlay}) => {\n",
1086+
" overlay.visible = false\n",
1087+
" renderer.data_source.selected.indices = []\n",
1088+
"}\n",
1089+
" \"\"\",\n",
1090+
")\n",
1091+
"\n",
1092+
"menu = Menu(\n",
1093+
" items=[\n",
1094+
" ActionItem(\n",
1095+
" label=\"Delete\",\n",
1096+
" shortcut=\"Alt+Shift+D\",\n",
1097+
" icon=\"delete\",\n",
1098+
" action=delete_selected,\n",
1099+
" ),\n",
1100+
" DividerItem(),\n",
1101+
" ActionItem(\n",
1102+
" icon=\"clear_selection\",\n",
1103+
" label=\"Clear selection\",\n",
1104+
" shortcut=\"Esc\",\n",
1105+
" action=clear_selection,\n",
1106+
" ),\n",
1107+
" ],\n",
1108+
")\n",
1109+
"\n",
1110+
"# Add context menu to box select\n",
1111+
"box_select.overlay.context_menu = menu\n",
1112+
"\n",
1113+
"show(plot)"
1114+
]
1115+
},
1116+
{
1117+
"cell_type": "markdown",
1118+
"metadata": {},
1119+
"source": [
1120+
"### Example: Custom toolbar for selected ranges\n",
1121+
"\n",
1122+
"For even more convenience, you can add a floating toolbar to the box selection, displaying actions directly with the selected area rather than requiring a right-click to display a context menu. We'll add an action to change the color of the enclosed points."
1123+
]
1124+
},
1125+
{
1126+
"cell_type": "code",
1127+
"execution_count": null,
1128+
"metadata": {},
1129+
"outputs": [],
1130+
"source": [
1131+
"plot, cr, box_select = confetti_plot(1000)\n",
1132+
"\n",
1133+
"common = dict(margin=0, sizing_mode=\"stretch_height\")\n",
1134+
"\n",
1135+
"delete = Button(label=\"Delete\", **common)\n",
1136+
"select = PaletteSelect(\n",
1137+
" value=Spectral11[0],\n",
1138+
" items=[(color, [color]) for color in Spectral11],\n",
1139+
" swatch_width=30,\n",
1140+
" **common,\n",
1141+
")\n",
1142+
"clear = Button(label=\"Clear\", **common)\n",
1143+
"\n",
1144+
"toolbar = Panel(\n",
1145+
" position=box_select.overlay.nodes.bottom_left,\n",
1146+
" anchor=\"top_left\",\n",
1147+
" width=box_select.overlay.nodes.width,\n",
1148+
" elements=[\n",
1149+
" Row(\n",
1150+
" children=[delete, select, clear],\n",
1151+
" spacing=5,\n",
1152+
" ),\n",
1153+
" ],\n",
1154+
" stylesheets=[\n",
1155+
" \"\"\"\n",
1156+
" :host {\n",
1157+
" background-color: rgb(221 221 221 / 0.5);\n",
1158+
" padding: 5px;\n",
1159+
" }\n",
1160+
" \"\"\",\n",
1161+
" ],\n",
1162+
")\n",
1163+
"box_select.overlay.elements.append(toolbar)\n",
1164+
"\n",
1165+
"delete.js_on_click(CustomJS(\n",
1166+
" args=dict(renderer=cr),\n",
1167+
" code=\"\"\"\n",
1168+
"export default ({renderer}) => {\n",
1169+
" const {entries} = Bokeh.require(\"core/util/object\")\n",
1170+
" const {enumerate} = Bokeh.require(\"core/util/iterator\")\n",
1171+
"\n",
1172+
" const {data, selected} = renderer.data_source\n",
1173+
" const indices = new Set(selected.indices)\n",
1174+
"\n",
1175+
" const new_data = {}\n",
1176+
" for (const [name, column] of entries(data)) {\n",
1177+
" new_data[name] = column.filter((value, i) => !indices.has(i))\n",
1178+
" }\n",
1179+
"\n",
1180+
" renderer.data_source.data = new_data\n",
1181+
" renderer.data_source.selected.indices = [] // TODO bug in ds update\n",
1182+
"}\n",
1183+
" \"\"\",\n",
1184+
"))\n",
1185+
"\n",
1186+
"select.js_on_change(\"value\", CustomJS(\n",
1187+
" args=dict(renderer=cr, select=select),\n",
1188+
" code=\"\"\"\n",
1189+
"export default ({renderer, select}) => {\n",
1190+
" const {data, selected} = renderer.data_source\n",
1191+
" const indices = new Set(selected.indices)\n",
1192+
" const selected_color = select.value\n",
1193+
"\n",
1194+
" const fill_color = [...data[\"fill_color\"]]\n",
1195+
" for (const i of indices) {\n",
1196+
" fill_color[i] = selected_color\n",
1197+
" }\n",
1198+
" renderer.data_source.data = {...data, fill_color}\n",
1199+
"}\n",
1200+
" \"\"\",\n",
1201+
"))\n",
1202+
"\n",
1203+
"clear.js_on_click(CustomJS(\n",
1204+
" args=dict(renderer=cr, overlay=box_select.overlay),\n",
1205+
" code=\"\"\"\n",
1206+
"export default ({renderer, overlay}) => {\n",
1207+
" overlay.visible = false\n",
1208+
" renderer.data_source.selected.indices = []\n",
1209+
"}\n",
1210+
" \"\"\",\n",
1211+
"))\n",
1212+
"\n",
1213+
"show(plot)"
1214+
]
1215+
},
9481216
{
9491217
"cell_type": "markdown",
9501218
"metadata": {},
@@ -976,7 +1244,7 @@
9761244
"name": "python",
9771245
"nbconvert_exporter": "python",
9781246
"pygments_lexer": "ipython3",
979-
"version": "3.12.0"
1247+
"version": "3.12.4"
9801248
},
9811249
"vscode": {
9821250
"interpreter": {

0 commit comments

Comments
 (0)