|
49 | 49 | "cell_type": "markdown",
|
50 | 50 | "metadata": {},
|
51 | 51 | "source": [
|
52 |
| - "This chapter introduces **two additional categories of plot types**:\n", |
| 52 | + "This chapter introduces **specialized plot types**:\n", |
53 | 53 | "- [Map plots](#Map-plots)\n",
|
54 |
| - "- [Wedge plots](#Wedge-plots) (pie and donut charts)" |
| 54 | + "- [Wedge plots](#Wedge-plots) (pie and donut charts)\n", |
| 55 | + "- [Subplots](#Subplots)" |
55 | 56 | ]
|
56 | 57 | },
|
57 | 58 | {
|
|
692 | 693 | "show(annular_plot)"
|
693 | 694 | ]
|
694 | 695 | },
|
| 696 | + { |
| 697 | + "cell_type": "markdown", |
| 698 | + "metadata": {}, |
| 699 | + "source": [ |
| 700 | + "## Subplots" |
| 701 | + ] |
| 702 | + }, |
| 703 | + { |
| 704 | + "cell_type": "markdown", |
| 705 | + "metadata": {}, |
| 706 | + "source": [ |
| 707 | + "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", |
| 708 | + "\n", |
| 709 | + "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", |
| 710 | + "- `level`: To specify that the zoom tool should apply to subplot level `1`, compared to the typical 'base' coordinate axis at level `0`.\n", |
| 711 | + "- `hit_test:` To ensure that the zoom tool selectively applies to the data glyph under the cursor.\n", |
| 712 | + "- `hit_test_mode`: Set to `'hline'` in the example to configure the zoom tool to perform horizontal span hit testing.\n", |
| 713 | + "- `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", |
| 714 | + "\n", |
| 715 | + "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." |
| 716 | + ] |
| 717 | + }, |
| 718 | + { |
| 719 | + "cell_type": "code", |
| 720 | + "execution_count": null, |
| 721 | + "metadata": {}, |
| 722 | + "outputs": [], |
| 723 | + "source": [ |
| 724 | + "import numpy as np\n", |
| 725 | + "from bokeh.core.properties import field\n", |
| 726 | + "from bokeh.models import (FactorRange, GroupByModels, HoverTool,\n", |
| 727 | + " Range1d, WheelZoomTool)\n", |
| 728 | + "from bokeh.palettes import Category10\n", |
| 729 | + "\n", |
| 730 | + "n_channels = 10\n", |
| 731 | + "n_seconds = 15\n", |
| 732 | + "total_samples = 512*n_seconds\n", |
| 733 | + "time = np.linspace(0, n_seconds, total_samples)\n", |
| 734 | + "data = np.random.randn(n_channels, total_samples).cumsum(axis=1)\n", |
| 735 | + "channels = [f\"EEG {i}\" for i in range(n_channels)]\n", |
| 736 | + "\n", |
| 737 | + "hover = HoverTool(tooltips=[\n", |
| 738 | + " (\"Channel\", \"$name\"),\n", |
| 739 | + " (\"Time\", \"$x s\"),\n", |
| 740 | + " (\"Amplitude\", \"$y μV\"),\n", |
| 741 | + "])\n", |
| 742 | + "\n", |
| 743 | + "x_range = Range1d(start=time.min(), end=time.max())\n", |
| 744 | + "y_range = FactorRange(factors=channels)\n", |
| 745 | + "\n", |
| 746 | + "p = figure(x_range=x_range, y_range=y_range, lod_threshold=None, tools=\"pan,reset,xcrosshair\")\n", |
| 747 | + "\n", |
| 748 | + "source = ColumnDataSource(data=dict(time=time))\n", |
| 749 | + "renderers = []\n", |
| 750 | + "\n", |
| 751 | + "for i, channel in enumerate(channels):\n", |
| 752 | + " xy = p.subplot(\n", |
| 753 | + " x_source=p.x_range,\n", |
| 754 | + " y_source=Range1d(start=data[i].min(), end=data[i].max()),\n", |
| 755 | + " x_target=p.x_range,\n", |
| 756 | + " y_target=Range1d(start=i, end=i + 1),\n", |
| 757 | + " )\n", |
| 758 | + "\n", |
| 759 | + " source.data[channel] = data[i]\n", |
| 760 | + " line = xy.line(field(\"time\"), field(channel), color=Category10[10][i%2], source=source, name=channel)\n", |
| 761 | + " renderers.append(line)\n", |
| 762 | + "\n", |
| 763 | + "# 🔁 Try changing the level parameter to 0 and then wheel-zoom over the plot\n", |
| 764 | + "level = 1\n", |
| 765 | + "hit_test = False\n", |
| 766 | + "only_hit = True\n", |
| 767 | + "\n", |
| 768 | + "group_by = GroupByModels(groups=[renderers[0::2], renderers[1::2]])\n", |
| 769 | + "\n", |
| 770 | + "ywheel_zoom = WheelZoomTool(renderers=renderers, level=level, hit_test=True, hit_test_mode=\"hline\", hit_test_behavior=group_by, dimensions=\"height\")\n", |
| 771 | + "xwheel_zoom = WheelZoomTool(renderers=renderers, dimensions=\"width\")\n", |
| 772 | + "\n", |
| 773 | + "p.add_tools(ywheel_zoom, xwheel_zoom, hover)\n", |
| 774 | + "p.toolbar.active_scroll = ywheel_zoom\n", |
| 775 | + "\n", |
| 776 | + "show(p)" |
| 777 | + ] |
| 778 | + }, |
| 779 | + { |
| 780 | + "cell_type": "markdown", |
| 781 | + "metadata": {}, |
| 782 | + "source": [ |
| 783 | + "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)." |
| 784 | + ] |
| 785 | + }, |
695 | 786 | {
|
696 | 787 | "cell_type": "markdown",
|
697 | 788 | "metadata": {},
|
|
723 | 814 | "name": "python",
|
724 | 815 | "nbconvert_exporter": "python",
|
725 | 816 | "pygments_lexer": "ipython3",
|
726 |
| - "version": "3.12.0" |
| 817 | + "version": "3.12.4" |
727 | 818 | }
|
728 | 819 | },
|
729 | 820 | "nbformat": 4,
|
|
0 commit comments