|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "metadata": {}, |
| 6 | + "source": [ |
| 7 | + "# Plotly Scroll-Zoom thourgh `ipyevents`\n", |
| 8 | + "In following example mouse-scroll is zooming in and out the _Plotly_ chart. It is not as effective as native zoom, but it gets the job done. Code is broken down into steps for you to easy change/modify/customize it to solve your particular challenge." |
| 9 | + ] |
| 10 | + }, |
| 11 | + { |
| 12 | + "cell_type": "markdown", |
| 13 | + "metadata": {}, |
| 14 | + "source": [ |
| 15 | + "## Simple Example\n", |
| 16 | + "For the simplicity of understanding we are going to only zoom on X axis with right-most point fixed, so:\n", |
| 17 | + "- right-most point of the cart is fixed\n", |
| 18 | + "- left-most point is changing depending on mouse wheel movement\n", |
| 19 | + "- we're only zooming x axis" |
| 20 | + ] |
| 21 | + }, |
| 22 | + { |
| 23 | + "cell_type": "markdown", |
| 24 | + "metadata": {}, |
| 25 | + "source": [ |
| 26 | + "### get some data (use yours), but... \n", |
| 27 | + "\n", |
| 28 | + "NOTE: In following examples X axis data is of `integer` type. If you deal with time, date, or any other - perform respective conversions." |
| 29 | + ] |
| 30 | + }, |
| 31 | + { |
| 32 | + "cell_type": "code", |
| 33 | + "execution_count": 60, |
| 34 | + "metadata": {}, |
| 35 | + "outputs": [], |
| 36 | + "source": [ |
| 37 | + "import plotly.express as px\n", |
| 38 | + "df = px.data.iris()" |
| 39 | + ] |
| 40 | + }, |
| 41 | + { |
| 42 | + "cell_type": "markdown", |
| 43 | + "metadata": {}, |
| 44 | + "source": [ |
| 45 | + "### plot the data" |
| 46 | + ] |
| 47 | + }, |
| 48 | + { |
| 49 | + "cell_type": "code", |
| 50 | + "execution_count": 61, |
| 51 | + "metadata": {}, |
| 52 | + "outputs": [], |
| 53 | + "source": [ |
| 54 | + "import plotly.graph_objects as go" |
| 55 | + ] |
| 56 | + }, |
| 57 | + { |
| 58 | + "cell_type": "code", |
| 59 | + "execution_count": 62, |
| 60 | + "metadata": {}, |
| 61 | + "outputs": [ |
| 62 | + { |
| 63 | + "data": { |
| 64 | + "application/vnd.jupyter.widget-view+json": { |
| 65 | + "model_id": "46e6288fbbdc41e9bfab2008c8702ae4", |
| 66 | + "version_major": 2, |
| 67 | + "version_minor": 0 |
| 68 | + }, |
| 69 | + "text/plain": [ |
| 70 | + "FigureWidget({\n", |
| 71 | + " 'data': [{'type': 'scatter',\n", |
| 72 | + " 'uid': '519cce7e-a330-473d-af7a-f7529dc18ef9',\n", |
| 73 | + " …" |
| 74 | + ] |
| 75 | + }, |
| 76 | + "metadata": {}, |
| 77 | + "output_type": "display_data" |
| 78 | + } |
| 79 | + ], |
| 80 | + "source": [ |
| 81 | + "fig = go.FigureWidget(data=go.Scatter(\n", |
| 82 | + " x=df.index, \n", |
| 83 | + " y=df['sepal_length']))\n", |
| 84 | + "\n", |
| 85 | + "fig" |
| 86 | + ] |
| 87 | + }, |
| 88 | + { |
| 89 | + "cell_type": "markdown", |
| 90 | + "metadata": {}, |
| 91 | + "source": [ |
| 92 | + "### `fig.layout.xaxis.range`" |
| 93 | + ] |
| 94 | + }, |
| 95 | + { |
| 96 | + "cell_type": "code", |
| 97 | + "execution_count": 74, |
| 98 | + "metadata": {}, |
| 99 | + "outputs": [ |
| 100 | + { |
| 101 | + "data": { |
| 102 | + "text/plain": [ |
| 103 | + "(14, 149)" |
| 104 | + ] |
| 105 | + }, |
| 106 | + "execution_count": 74, |
| 107 | + "metadata": {}, |
| 108 | + "output_type": "execute_result" |
| 109 | + } |
| 110 | + ], |
| 111 | + "source": [ |
| 112 | + "fig.layout.xaxis.range" |
| 113 | + ] |
| 114 | + }, |
| 115 | + { |
| 116 | + "cell_type": "markdown", |
| 117 | + "metadata": {}, |
| 118 | + "source": [ |
| 119 | + "Tupple of indexes of left and right most points of the chart. This defines the view range. You can manualy set it by passing desired tupple. This is what we are going to change with scroll mouse event in next step." |
| 120 | + ] |
| 121 | + }, |
| 122 | + { |
| 123 | + "cell_type": "markdown", |
| 124 | + "metadata": {}, |
| 125 | + "source": [ |
| 126 | + "### adding mouse scroll support by `ipyevents`\n", |
| 127 | + "You can use any type of keyboard and mouse events on any widget. \n", |
| 128 | + "_**ipyevents**_: https://github.com/mwcraig/ipyevents \n", |
| 129 | + "Events list: https://developer.mozilla.org/en-US/docs/Web/Events" |
| 130 | + ] |
| 131 | + }, |
| 132 | + { |
| 133 | + "cell_type": "markdown", |
| 134 | + "metadata": {}, |
| 135 | + "source": [ |
| 136 | + "Install with conda if not installed: \n", |
| 137 | + "`conda install -c conda-forge ipyevents`" |
| 138 | + ] |
| 139 | + }, |
| 140 | + { |
| 141 | + "cell_type": "code", |
| 142 | + "execution_count": 64, |
| 143 | + "metadata": {}, |
| 144 | + "outputs": [], |
| 145 | + "source": [ |
| 146 | + "import ipyevents" |
| 147 | + ] |
| 148 | + }, |
| 149 | + { |
| 150 | + "cell_type": "markdown", |
| 151 | + "metadata": {}, |
| 152 | + "source": [ |
| 153 | + "Border conditions:" |
| 154 | + ] |
| 155 | + }, |
| 156 | + { |
| 157 | + "cell_type": "code", |
| 158 | + "execution_count": 65, |
| 159 | + "metadata": {}, |
| 160 | + "outputs": [], |
| 161 | + "source": [ |
| 162 | + "xaxis_min_range=40\n", |
| 163 | + "# (optional) ...so you cannot zoom-in to one data point, use your taste for UX\n", |
| 164 | + "\n", |
| 165 | + "xaxis_left_limit=0\n", |
| 166 | + "# (optional) ...so you cannot zoom-out into negative (there is no data there)" |
| 167 | + ] |
| 168 | + }, |
| 169 | + { |
| 170 | + "cell_type": "markdown", |
| 171 | + "metadata": {}, |
| 172 | + "source": [ |
| 173 | + "Function to be called when mouse scroll is detected:" |
| 174 | + ] |
| 175 | + }, |
| 176 | + { |
| 177 | + "cell_type": "code", |
| 178 | + "execution_count": 69, |
| 179 | + "metadata": {}, |
| 180 | + "outputs": [], |
| 181 | + "source": [ |
| 182 | + "# triggered function when mouse wheel event happens\n", |
| 183 | + "def handle_event(event):\n", |
| 184 | + " \n", |
| 185 | + " # index of left-most point of the current state of the chart\n", |
| 186 | + " xaxis_left=fig.layout.xaxis.range[0]\n", |
| 187 | + " \n", |
| 188 | + " # index of right-most point of the current state of the chart\n", |
| 189 | + " xaxis_right=fig.layout.xaxis.range[1]\n", |
| 190 | + " \n", |
| 191 | + " # setting new value by adding 'wheel movemnt`\n", |
| 192 | + " new_xaxis_left=xaxis_left+int(event['deltaY'])\n", |
| 193 | + " \n", |
| 194 | + " # respecting border conditions...\n", |
| 195 | + " if (xaxis_right-new_xaxis_left)>xaxis_min_range \\\n", |
| 196 | + " and new_xaxis_left>xaxis_left_limit:\n", |
| 197 | + " \n", |
| 198 | + " # passing new 'zoomed' range to the chart\n", |
| 199 | + " fig.layout.xaxis.range=(new_xaxis_left,xaxis_right) if xaxis_left>=0 else 0" |
| 200 | + ] |
| 201 | + }, |
| 202 | + { |
| 203 | + "cell_type": "markdown", |
| 204 | + "metadata": {}, |
| 205 | + "source": [ |
| 206 | + "Widget Event handler:" |
| 207 | + ] |
| 208 | + }, |
| 209 | + { |
| 210 | + "cell_type": "code", |
| 211 | + "execution_count": 76, |
| 212 | + "metadata": {}, |
| 213 | + "outputs": [], |
| 214 | + "source": [ |
| 215 | + "# listening to mouse events while mouse on top of widget named `fig` \n", |
| 216 | + "Event(source=fig, watched_events=['wheel']).on_dom_event(handle_event)" |
| 217 | + ] |
| 218 | + }, |
| 219 | + { |
| 220 | + "cell_type": "markdown", |
| 221 | + "metadata": {}, |
| 222 | + "source": [ |
| 223 | + "(OPTIONAL) change default mouse behaviour to `pan`, instead of _\"starnge\"_ Plotly zoom behaviour." |
| 224 | + ] |
| 225 | + }, |
| 226 | + { |
| 227 | + "cell_type": "code", |
| 228 | + "execution_count": null, |
| 229 | + "metadata": {}, |
| 230 | + "outputs": [], |
| 231 | + "source": [ |
| 232 | + "fig.layout.dragmode='pan'" |
| 233 | + ] |
| 234 | + }, |
| 235 | + { |
| 236 | + "cell_type": "markdown", |
| 237 | + "metadata": {}, |
| 238 | + "source": [ |
| 239 | + "See how it works..." |
| 240 | + ] |
| 241 | + }, |
| 242 | + { |
| 243 | + "cell_type": "code", |
| 244 | + "execution_count": 71, |
| 245 | + "metadata": {}, |
| 246 | + "outputs": [ |
| 247 | + { |
| 248 | + "data": { |
| 249 | + "application/vnd.jupyter.widget-view+json": { |
| 250 | + "model_id": "46e6288fbbdc41e9bfab2008c8702ae4", |
| 251 | + "version_major": 2, |
| 252 | + "version_minor": 0 |
| 253 | + }, |
| 254 | + "text/plain": [ |
| 255 | + "FigureWidget({\n", |
| 256 | + " 'data': [{'type': 'scatter',\n", |
| 257 | + " 'uid': '519cce7e-a330-473d-af7a-f7529dc18ef9',\n", |
| 258 | + " …" |
| 259 | + ] |
| 260 | + }, |
| 261 | + "metadata": {}, |
| 262 | + "output_type": "display_data" |
| 263 | + } |
| 264 | + ], |
| 265 | + "source": [ |
| 266 | + "fig" |
| 267 | + ] |
| 268 | + }, |
| 269 | + { |
| 270 | + "cell_type": "markdown", |
| 271 | + "metadata": {}, |
| 272 | + "source": [ |
| 273 | + "To sum-up: \n", |
| 274 | + "\n", |
| 275 | + "We are only wathcing for mouse events of `wheel` type in `Event` handler. In the function being called by event handler `handle_event()` we are only interested in `deltaY` key. `deltaY` is converted into integer type and simply added to left-most index `xaxis_left` of current chart. If border conditions are met - new tupple (with new left `new_xaxis_left` and old right `xaxis_right` value) is passed to the chart as `fig.layout.xaxis.range`." |
| 276 | + ] |
| 277 | + } |
| 278 | + ], |
| 279 | + "metadata": { |
| 280 | + "kernelspec": { |
| 281 | + "display_name": "Python 3", |
| 282 | + "language": "python", |
| 283 | + "name": "python3" |
| 284 | + }, |
| 285 | + "language_info": { |
| 286 | + "codemirror_mode": { |
| 287 | + "name": "ipython", |
| 288 | + "version": 3 |
| 289 | + }, |
| 290 | + "file_extension": ".py", |
| 291 | + "mimetype": "text/x-python", |
| 292 | + "name": "python", |
| 293 | + "nbconvert_exporter": "python", |
| 294 | + "pygments_lexer": "ipython3", |
| 295 | + "version": "3.6.7" |
| 296 | + } |
| 297 | + }, |
| 298 | + "nbformat": 4, |
| 299 | + "nbformat_minor": 4 |
| 300 | +} |
0 commit comments