Skip to content

Commit

Permalink
Adds scale bar, range tool, and subplot sections (CZI-related updates) (
Browse files Browse the repository at this point in the history
#27)

* add scale bar section to annotations

* add rangetool section to plot_tools

* add subplots section to more_plot_types

* Update 07_annotations.ipynb

Small edits

* Small edits to 08_plot_tools.ipynb

* Small edits to 09_more_plot_types.ipynb

* add pollen image

* remove Switch from subplots

---------

Co-authored-by: Timo Cornelius Metzger <[email protected]>
  • Loading branch information
droumis and tcmetzger authored Jun 28, 2024
1 parent 1916271 commit 5ecbd40
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 6 deletions.
98 changes: 96 additions & 2 deletions notebooks/07_annotations.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@
"labels = LabelSet(\n",
" x=\"temp\", # use the temp column as the x coordinates\n",
" y=\"pressure\", # use the pressure column as the y coordinates\n",
" text=\"names\", # use the strings from the name column as text\n",
" text=\"names\", # use the strings from the names column as text\n",
" text_baseline=\"middle\",\n",
" x_offset=10,\n",
" source=source, # use the ColumnDataSource as the data source for the label positions\n",
Expand Down Expand Up @@ -397,6 +397,100 @@
"show(plot)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Scale Bar"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Bokeh's `ScaleBar` allows you to add a reference measurement for scale. This is particularly useful for images, maps, time series, or any plot where you want to communicate the scale of various features."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Adding a ScaleBar to a Plot"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To demonstrate the use of a `ScaleBar`, we'll add one to a microscopy image. The `ScaleBar` requires a Range to determine the scale, which can either be the x-range or y-range of the plot. You can also specify various properties such as the `bar_length`, `label` format, and `unit` of measurement. Here, we apply the `ScaleBar` to the `x_range` and specify that one `unit` of measurement on this axis corresponds to a millimeter (`\"mm\"`). Refer to the [reference docs](https://docs.bokeh.org/en/latest/docs/reference/models/annotations.html#bokeh.models.ScaleBar) for `ScaleBar` for all customization options."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"from bokeh.models import MetricLength, ScaleBar\n",
"\n",
"TOOLS = \"pan,wheel_zoom,box_zoom,reset\"\n",
"\n",
"p = figure(\n",
" tools=TOOLS,\n",
" active_scroll=\"wheel_zoom\",\n",
")\n",
"\n",
"p.x_range.range_padding = 0\n",
"p.y_range.range_padding = 0\n",
"\n",
"p.x_range.bounds = (0, 1)\n",
"p.y_range.bounds = (0, 1)\n",
"\n",
"pollen_png = Path(\"assets/pollen.png\")\n",
"img = pollen_png.read_bytes()\n",
"\n",
"p.image_url(x=0, y=0, w=1, h=1, url=[img], anchor=\"bottom_left\")\n",
"\n",
"scale_bar = ScaleBar(\n",
" range=p.x_range,\n",
" unit=\"mm\",\n",
" dimensional=MetricLength(),\n",
" orientation=\"horizontal\",\n",
" location=\"top_right\",\n",
" label=\"@{value} @{unit}\",\n",
" label_location=\"above\",\n",
" label_align=\"center\",\n",
" bar_length=0.2,\n",
" bar_line_width=2,\n",
" background_fill_alpha=0.8,\n",
")\n",
"p.add_layout(scale_bar)\n",
"\n",
"show(p)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Go ahead and scroll over the image to activate the wheel-zoom and see the scale bar dynamically adjust."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Custom Scale Bar Units"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By default, the ScaleBar uses metric length units, but you can define custom units of measurement using the `CustomDimensional` model. This allows you to create scale bars with units such as angular measurements or any other custom units you might need. To learn more about customizing units and other advanced features of the ScaleBar, refer to the [Bokeh User Guide on Annotations](https://docs.bokeh.org/en/latest/docs/user_guide/basic/annotations.html#scale-bars)."
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -428,7 +522,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.0"
"version": "3.12.4"
},
"vscode": {
"interpreter": {
Expand Down
84 changes: 83 additions & 1 deletion notebooks/08_plot_tools.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,88 @@
"show(largest_carriers_plot)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RangeTool\n",
"\n",
"The RangeTool allows users to maintain a macroscopic perspective of their data from a 'minimap' view, while navigating through particular regions of interest in a linked plot. This dual view capability is particularly useful for very large time series, images, maps, or scatter plots, where you often need to zoom in on a specific range while keeping an overview of the entire dataset.\n",
"\n",
"### Using the RangeTool\n",
"The RangeTool enables interactive definition of a region in one plot that results in a zoomed view on another plot. The example below uses the RangeTool to help navigate through a simple line plot:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from bokeh.layouts import column\n",
"from bokeh.models import ColumnDataSource, RangeTool\n",
"from bokeh.plotting import figure, show\n",
"from bokeh.io import output_notebook\n",
"\n",
"output_notebook()\n",
"\n",
"n_points = 3000\n",
"x_values = np.linspace(0, 100, n_points)\n",
"y_values = np.random.randn(n_points).cumsum()\n",
"\n",
"source = ColumnDataSource(data=dict(x=x_values, y=y_values))\n",
"\n",
"detailed_plot = figure(\n",
" width=800,\n",
" height=300,\n",
" tools=[\"xpan\", \"xzoom_in\", \"xzoom_out\", \"reset\", \"wheel_zoom\"],\n",
" toolbar_location=\"above\",\n",
" active_scroll=\"wheel_zoom\",\n",
" background_fill_color=\"#efefef\",\n",
" x_range=(22, 30),\n",
")\n",
"\n",
"detailed_plot.line(\"x\", \"y\", source=source)\n",
"\n",
"minimap = figure(\n",
" width=detailed_plot.width,\n",
" height=150,\n",
" tools=\"\",\n",
" toolbar_location=None,\n",
" background_fill_color=detailed_plot.background_fill_color,\n",
" title=\"Drag the middle or edges of the selection box below, or double click to start a new box\"\n",
")\n",
"\n",
"minimap.line(\"x\", \"y\", source=source)\n",
"minimap.x_range.range_padding = 0\n",
"minimap.ygrid.grid_line_color = None\n",
"\n",
"range_tool = RangeTool(x_range=detailed_plot.x_range, y_range=detailed_plot.y_range, start_gesture='tap')\n",
"range_tool.overlay.fill_color = \"darkblue\"\n",
"range_tool.overlay.fill_alpha = 0.3\n",
"range_tool.overlay.inverted = True\n",
"range_tool.overlay.use_handles = True\n",
"range_tool.overlay.handles[\"all\"].hover_fill_color = \"orange\"\n",
"range_tool.overlay.handles[\"all\"].hover_fill_alpha = 0.9\n",
"range_tool.overlay.handles[\"all\"].hover_line_alpha = 0\n",
"range_tool.overlay.handles[\"all\"].fill_alpha = 0.1\n",
"range_tool.overlay.handles[\"all\"].line_alpha = 0.1\n",
"minimap.add_tools(range_tool)\n",
"\n",
"show(column(detailed_plot, minimap))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Selection of `RangeTool` features shown above:\n",
"- `start_gesture`: Set to `'tap'` to allow you to start a new box selection by double tapping.\n",
"- `inverted`: Set to `True` to reverse the highlighting, muting the non-selected regions.\n",
"- `use_handles`: Set to `True` to add customizable interaction handles for moving (middle) and resizing (edges) the selection box."
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -402,7 +484,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.0"
"version": "3.12.4"
},
"vscode": {
"interpreter": {
Expand Down
97 changes: 94 additions & 3 deletions notebooks/09_more_plot_types.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"This chapter introduces **two additional categories of plot types**:\n",
"This chapter introduces **specialized plot types**:\n",
"- [Map plots](#Map-plots)\n",
"- [Wedge plots](#Wedge-plots) (pie and donut charts)"
"- [Wedge plots](#Wedge-plots) (pie and donut charts)\n",
"- [Subplots](#Subplots)"
]
},
{
Expand Down Expand Up @@ -692,6 +693,96 @@
"show(annular_plot)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Subplots"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Subplots allow you to create complex visualizations by dividing a larger plot into smaller sections, each with its own set of coordinates. This way, you can display data on each subplot accurately and independently, while still keeping all data within a single cohesive plot. This is particularly useful when you want to compare different segments or data sets side-by-side or overlapping on a single plot. Ideally, one axis of the subplot (e.g., time on the x-axis) is shared across glyphs, while the other axis has variations in terms of units and amplitude.\n",
"\n",
"The following example demonstrates how to create a plot of subplots, utilizing enhanced zoom features that are particularly relevant in the context of subplots. \n",
"- `level`: To specify that the zoom tool should apply to subplot level `1`, compared to the typical 'base' coordinate axis at level `0`.\n",
"- `hit_test:` To ensure that the zoom tool selectively applies to the data glyph under the cursor.\n",
"- `hit_test_mode`: Set to `'hline'` in the example to configure the zoom tool to perform horizontal span hit testing.\n",
"- `hit_test_behavior`: To group renderers so that zooming affects the entire group if any renderer within the group is under the cursor. This synchronizes zooming across related data series.\n",
"\n",
"Go ahead and hover over a line while scrolling to activate subplot-level, hit-tested, group-wise zooming. Just for demonstration purposes, the lines are in alternating groups."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from bokeh.core.properties import field\n",
"from bokeh.models import (FactorRange, GroupByModels, HoverTool,\n",
" Range1d, WheelZoomTool)\n",
"from bokeh.palettes import Category10\n",
"\n",
"n_channels = 10\n",
"n_seconds = 15\n",
"total_samples = 512*n_seconds\n",
"time = np.linspace(0, n_seconds, total_samples)\n",
"data = np.random.randn(n_channels, total_samples).cumsum(axis=1)\n",
"channels = [f\"EEG {i}\" for i in range(n_channels)]\n",
"\n",
"hover = HoverTool(tooltips=[\n",
" (\"Channel\", \"$name\"),\n",
" (\"Time\", \"$x s\"),\n",
" (\"Amplitude\", \"$y μV\"),\n",
"])\n",
"\n",
"x_range = Range1d(start=time.min(), end=time.max())\n",
"y_range = FactorRange(factors=channels)\n",
"\n",
"p = figure(x_range=x_range, y_range=y_range, lod_threshold=None, tools=\"pan,reset,xcrosshair\")\n",
"\n",
"source = ColumnDataSource(data=dict(time=time))\n",
"renderers = []\n",
"\n",
"for i, channel in enumerate(channels):\n",
" xy = p.subplot(\n",
" x_source=p.x_range,\n",
" y_source=Range1d(start=data[i].min(), end=data[i].max()),\n",
" x_target=p.x_range,\n",
" y_target=Range1d(start=i, end=i + 1),\n",
" )\n",
"\n",
" source.data[channel] = data[i]\n",
" line = xy.line(field(\"time\"), field(channel), color=Category10[10][i%2], source=source, name=channel)\n",
" renderers.append(line)\n",
"\n",
"# 🔁 Try changing the level parameter to 0 and then wheel-zoom over the plot\n",
"level = 1\n",
"hit_test = False\n",
"only_hit = True\n",
"\n",
"group_by = GroupByModels(groups=[renderers[0::2], renderers[1::2]])\n",
"\n",
"ywheel_zoom = WheelZoomTool(renderers=renderers, level=level, hit_test=True, hit_test_mode=\"hline\", hit_test_behavior=group_by, dimensions=\"height\")\n",
"xwheel_zoom = WheelZoomTool(renderers=renderers, dimensions=\"width\")\n",
"\n",
"p.add_tools(ywheel_zoom, xwheel_zoom, hover)\n",
"p.toolbar.active_scroll = ywheel_zoom\n",
"\n",
"show(p)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For additional examples of `subplot`, check out [polar_subcoordinates](https://docs.bokeh.org/en/latest/docs/examples/plotting/polar_subcoordinates.html) and [ridgeplot_subcoordinates](https://docs.bokeh.org/en/latest/docs/examples/plotting/ridgeplot_subcoordinates.html)."
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -723,7 +814,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.0"
"version": "3.12.4"
}
},
"nbformat": 4,
Expand Down
Binary file added notebooks/assets/pollen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5ecbd40

Please sign in to comment.