-
-
Notifications
You must be signed in to change notification settings - Fork 402
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement support for subcoordinate systems in the y-axis (#5840)
Co-authored-by: Simon Høxbro Hansen <[email protected]> Co-authored-by: maximlt <[email protected]> Co-authored-by: Demetris Roumis <[email protected]>
- Loading branch information
1 parent
9d208fb
commit 38e0b39
Showing
4 changed files
with
515 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"id": "549b47a4", | ||
"metadata": {}, | ||
"source": [ | ||
"This example demonstrates advanced visualization techniques using HoloViews with the Bokeh plotting backend. You'll learn how to:\n", | ||
"\n", | ||
"1. Display multiple timeseries in a single plot using `subcoordinate_y`.\n", | ||
"2. Create and link a minimap to the main plot with `RangeToolLink`.\n", | ||
"\n", | ||
"Specifically, we'll simulate [Electroencephalography](https://en.wikipedia.org/wiki/Electroencephalography) (EEG) data, plot it, and then create a minimap based on the [z-score](https://en.wikipedia.org/wiki/Standard_score) of the data for easier navigation." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "8109537b-5fba-4f07-aba4-91a56f7e95c7", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import numpy as np\n", | ||
"import holoviews as hv\n", | ||
"from bokeh.models import HoverTool\n", | ||
"from holoviews.plotting.links import RangeToolLink\n", | ||
"from scipy.stats import zscore\n", | ||
"\n", | ||
"hv.extension('bokeh')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "1c95f241-2314-42b0-b6cb-2c0baf332686", | ||
"metadata": {}, | ||
"source": [ | ||
"## Generating EEG data\n", | ||
"\n", | ||
"Let's start by simulating some EEG data. We'll create a timeseries for each channel using sine waves with varying frequencies." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "5f4a9dbe", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"\n", | ||
"N_CHANNELS = 10\n", | ||
"N_SECONDS = 5\n", | ||
"SAMPLING_RATE = 200\n", | ||
"INIT_FREQ = 2 # Initial frequency in Hz\n", | ||
"FREQ_INC = 5 # Frequency increment\n", | ||
"AMPLITUDE = 1\n", | ||
"\n", | ||
"# Generate time and channel labels\n", | ||
"total_samples = N_SECONDS * SAMPLING_RATE\n", | ||
"time = np.linspace(0, N_SECONDS, total_samples)\n", | ||
"channels = [f'EEG {i}' for i in range(N_CHANNELS)]\n", | ||
"\n", | ||
"# Generate sine wave data\n", | ||
"data = np.array([AMPLITUDE * np.sin(2 * np.pi * (INIT_FREQ + i * FREQ_INC) * time)\n", | ||
" for i in range(N_CHANNELS)])" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "ec9e71b8-a995-4c0f-bdbb-5d148d8fa138", | ||
"metadata": {}, | ||
"source": [ | ||
"## Visualizing EEG Data\n", | ||
"\n", | ||
"Next, let's dive into visualizing the EEG data. We construct each timeseries using a `Curve` element, assigning it a `label` and setting `subcoordinate_y=True`. All these curves are then aggregated into a list, which serves as the input for an `Overlay` element. Rendering this `Overlay` produces a plot where the timeseries are stacked vertically.\n", | ||
"\n", | ||
"Additionally, we'll enhance user interaction by implementing a custom hover tool. This will display key information—channel, time, and amplitude—when you hover over any of the curves." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "9476769f-3935-4236-b010-1511d1a1e77f", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"hover = HoverTool(tooltips=[\n", | ||
" (\"Channel\", \"@channel\"),\n", | ||
" (\"Time\", \"$x s\"),\n", | ||
" (\"Amplitude\", \"$y µV\")\n", | ||
"])\n", | ||
"\n", | ||
"channel_curves = []\n", | ||
"for channel, channel_data in zip(channels, data):\n", | ||
" ds = hv.Dataset((time, channel_data, channel), [\"Time\", \"Amplitude\", \"channel\"])\n", | ||
" curve = hv.Curve(ds, \"Time\", [\"Amplitude\", \"channel\"], label=channel)\n", | ||
" curve.opts(\n", | ||
" subcoordinate_y=True, color=\"black\", line_width=1, tools=[hover],\n", | ||
" )\n", | ||
" channel_curves.append(curve)\n", | ||
"\n", | ||
"eeg = hv.Overlay(channel_curves, kdims=\"Channel\").opts(\n", | ||
" xlabel=\"Time (s)\", ylabel=\"Channel\", show_legend=False, aspect=3, responsive=True,\n", | ||
")\n", | ||
"eeg" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "b4f603e2-039d-421a-ba9a-ed9e77efab99", | ||
"metadata": {}, | ||
"source": [ | ||
"## Creating the Minimap\n", | ||
"\n", | ||
"A minimap can provide a quick overview of the data and help you navigate through it. We'll compute the z-score for each channel and represent it as an image; the z-score will normalize the data and bring out the patterns more clearly. To enable linking in the next step between the EEG `Overlay` and the minimap `Image`, we ensure they share the same y-axis range." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "40fa2198-c3b5-41e1-944f-f8b812612168", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"y_positions = range(N_CHANNELS)\n", | ||
"yticks = [(i , ich) for i, ich in enumerate(channels)]\n", | ||
"\n", | ||
"z_data = zscore(data, axis=1)\n", | ||
"\n", | ||
"minimap = hv.Image((time, y_positions , z_data), [\"Time (s)\", \"Channel\"], \"Amplitude (uV)\")\n", | ||
"minimap = minimap.opts(\n", | ||
" cmap=\"RdBu_r\", xlabel='Time (s)', alpha=.5, yticks=[yticks[0], yticks[-1]],\n", | ||
" height=150, responsive=True, default_tools=[], clim=(-z_data.std(), z_data.std())\n", | ||
")\n", | ||
"minimap" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "a5b77970-342f-4428-bd1c-4dbef1e6a2b5", | ||
"metadata": {}, | ||
"source": [ | ||
"## Building the dashboard\n", | ||
"\n", | ||
"Finally, we use [`RangeToolLink`](../../../user_guide/Linking_Plots.ipynb) to connect the minimap `Image` and the EEG `Overlay`, setting bounds for the initial viewable area. Once the plots are linked and assembled into a unified dashboard, you can interact with it. Experiment by dragging the selection box on the minimap or resizing it by clicking and dragging its edges." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "260489eb-2dbf-4c88-ba83-dd1cba0e547b", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"RangeToolLink(\n", | ||
" minimap, eeg, axes=[\"x\", \"y\"],\n", | ||
" boundsx=(None, 2), boundsy=(None, 6.5)\n", | ||
")\n", | ||
"\n", | ||
"dashboard = (eeg + minimap).opts(merge_tools=False).cols(1)\n", | ||
"dashboard" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"language_info": { | ||
"name": "python", | ||
"pygments_lexer": "ipython3" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.