{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Display hover data in Bokeh scatter plots" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "scrolled": true }, "outputs": [], "source": [ "\"\"\"\n", " Tested on bokeh 0.11.1, pandas 0.18.0\n", " \n", " First plot has no groups and correct hover data, \n", " Second plot has groups but misaligned hover data.\n", " \n", "\"\"\"\n", "from bokeh.sampledata.autompg import autompg as df\n", "from bokeh.charts import Scatter, TimeSeries, output_notebook, show\n", "from bokeh.models import HoverTool, BoxZoomTool, ResetTool, CrosshairTool, BoxSelectTool, WheelZoomTool, PreviewSaveTool\n", "\n", "from bokeh.models.renderers import GlyphRenderer\n", "import copy\n", "\n", "hover = HoverTool(\n", " tooltips = [\n", " (\"cyl\", \"@cyl\"),\n", " (\"displ\", \"@displ\"),\n", " (\"Weight\", \"@weight\"),\n", " (\"Acceleration\", \"@accel\"),\n", " ('Horsepower', '@hp'),\n", " ('MPG', '@mpg'), \n", " ('Origin', '@origin'), \n", "])\n", "\n", "tools = [hover, BoxZoomTool(), ResetTool(), CrosshairTool(), BoxSelectTool(), WheelZoomTool(), PreviewSaveTool()] \n", "scatter_sans_groups = Scatter(df, x='mpg', y='hp', title=\"Auto MPG\", xlabel=\"Miles Per Gallon\", ylabel=\"Horsepower\", \n", " tools=tools)\n" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tooltip_fields: ['cyl', 'displ', 'weight', 'accel', 'hp', 'mpg', 'origin']\n", "renderer_fields: ['x_values', 'chart_index', 'y_values']\n", "fields_to_add: ['cyl', 'displ', 'weight', 'accel', 'hp', 'mpg', 'origin']\n", "counts before adding: [('x_values', 392), ('chart_index', 392), ('y_values', 392)]\n", "counts after adding: [('origin', 392), ('mpg', 392), ('chart_index', 392), ('displ', 392), ('weight', 392), ('hp', 392), ('accel', 392), ('x_values', 392), ('y_values', 392), ('cyl', 392)]\n" ] } ], "source": [ "def patch_renderer(scatter_instance, hover_instance, data_frame):\n", " tooltip_fields = [value[1:] for label, value in hover_instance.tooltips if value[0] == '@']\n", " glyph_renderer = [r for r in scatter_instance.renderers if isinstance(r, GlyphRenderer)][0]\n", " renderer_fields = glyph_renderer.data_source.data.keys()\n", " fields_to_add = [tooltip_field for tooltip_field in tooltip_fields if tooltip_field not in renderer_fields]\n", "\n", " print 'tooltip_fields: ', tooltip_fields\n", " print 'renderer_fields: ', renderer_fields\n", " print 'fields_to_add: ', fields_to_add\n", " print 'counts before adding: ', [(key, len(glyph_renderer.data_source.data[key])) for key in glyph_renderer.data_source.data]\n", " \n", " for field in fields_to_add:\n", " glyph_renderer.data_source.data[field] = list(data_frame[field])\n", " \n", " print 'counts after adding: ', [(key, len(glyph_renderer.data_source.data[key])) for key in glyph_renderer.data_source.data]\n", " \n", "patch_renderer(scatter_sans_groups, hover, df)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " Loading BokehJS ...\n", "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "(function(global) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " if (typeof (window._bokeh_onload_callbacks) === \"undefined\") {\n", " window._bokeh_onload_callbacks = [];\n", " }\n", "\n", " function run_callbacks() {\n", " window._bokeh_onload_callbacks.forEach(function(callback) { callback() });\n", " delete window._bokeh_onload_callbacks\n", " console.info(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(js_urls, callback) {\n", " window._bokeh_onload_callbacks.push(callback);\n", " if (window._bokeh_is_loading > 0) {\n", " console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " window._bokeh_is_loading = js_urls.length;\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " var s = document.createElement('script');\n", " s.src = url;\n", " s.async = false;\n", " s.onreadystatechange = s.onload = function() {\n", " window._bokeh_is_loading--;\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: all BokehJS libraries loaded\");\n", " run_callbacks()\n", " }\n", " };\n", " s.onerror = function() {\n", " console.warn(\"failed to load library \" + url);\n", " };\n", " console.log(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " }\n", " };\n", "\n", " var js_urls = ['https://cdn.pydata.org/bokeh/release/bokeh-0.11.1.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.11.1.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-compiler-0.11.1.min.js'];\n", "\n", " var inline_js = [\n", " function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", " \n", " function(Bokeh) {\n", " Bokeh.$(\"#f6a17ec2-c3a4-4e5b-9f27-e2742d15c1f3\").text(\"BokehJS successfully loaded\");\n", " },\n", " function(Bokeh) {\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.11.1.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.11.1.min.css\");\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.11.1.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.11.1.min.css\");\n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " for (var i = 0; i < inline_js.length; i++) {\n", " inline_js[i](window.Bokeh);\n", " }\n", " }\n", "\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(js_urls, function() {\n", " console.log(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(this));" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[9]>

" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "output_notebook()\n", "show(scatter_sans_groups) # this is correct output" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Demonstrate problem when groups are introduced" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "tooltip_fields: ['cyl', 'displ', 'weight', 'accel', 'hp', 'mpg', 'origin']\n", "renderer_fields: ['origin', 'x_values', 'chart_index', 'y_values', 'cyl']\n", "fields_to_add: ['displ', 'weight', 'accel', 'hp', 'mpg']\n", "counts before adding: [('origin', 103), ('x_values', 103), ('chart_index', 103), ('y_values', 103), ('cyl', 103)]\n", "counts after adding: [('origin', 103), ('mpg', 392), ('chart_index', 103), ('displ', 392), ('weight', 392), ('hp', 392), ('accel', 392), ('x_values', 103), ('y_values', 103), ('cyl', 103)]\n" ] }, { "data": { "text/html": [ "\n", "
\n", " \n", " Loading BokehJS ...\n", "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "(function(global) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " if (typeof (window._bokeh_onload_callbacks) === \"undefined\") {\n", " window._bokeh_onload_callbacks = [];\n", " }\n", "\n", " function run_callbacks() {\n", " window._bokeh_onload_callbacks.forEach(function(callback) { callback() });\n", " delete window._bokeh_onload_callbacks\n", " console.info(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(js_urls, callback) {\n", " window._bokeh_onload_callbacks.push(callback);\n", " if (window._bokeh_is_loading > 0) {\n", " console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " window._bokeh_is_loading = js_urls.length;\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " var s = document.createElement('script');\n", " s.src = url;\n", " s.async = false;\n", " s.onreadystatechange = s.onload = function() {\n", " window._bokeh_is_loading--;\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: all BokehJS libraries loaded\");\n", " run_callbacks()\n", " }\n", " };\n", " s.onerror = function() {\n", " console.warn(\"failed to load library \" + url);\n", " };\n", " console.log(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " }\n", " };\n", "\n", " var js_urls = ['https://cdn.pydata.org/bokeh/release/bokeh-0.11.1.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.11.1.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-compiler-0.11.1.min.js'];\n", "\n", " var inline_js = [\n", " function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", " \n", " function(Bokeh) {\n", " Bokeh.$(\"#000887bd-c54f-4a13-ba40-53233306ca44\").text(\"BokehJS successfully loaded\");\n", " },\n", " function(Bokeh) {\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.11.1.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.11.1.min.css\");\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.11.1.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.11.1.min.css\");\n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " for (var i = 0; i < inline_js.length; i++) {\n", " inline_js[i](window.Bokeh);\n", " }\n", " }\n", "\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(js_urls, function() {\n", " console.log(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(this));" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "

<Bokeh Notebook handle for In[10]>

" ], "text/plain": [ "" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Not sure why hover can't be reused without throwing an error in Scatter, so create hover2\n", "hover2 = HoverTool(\n", " tooltips = [\n", " (\"cyl\", \"@cyl\"),\n", " (\"displ\", \"@displ\"),\n", " (\"Weight\", \"@weight\"),\n", " (\"Acceleration\", \"@accel\"),\n", " ('Horsepower', '@hp'),\n", " ('MPG', '@mpg'), \n", " ('Origin', '@origin'), \n", "])\n", "\n", "scatter_with_groups = Scatter(df, x='mpg', y='hp', title=\"Auto MPG\", xlabel=\"Miles Per Gallon\", ylabel=\"Horsepower\", \n", " tools=[hover2],\n", " color='cyl', marker='origin')\n", "patch_renderer(scatter_with_groups, hover2, df)\n", "output_notebook()\n", "show(scatter_with_groups)\n", "\n", "# The output plot is incorrect! Mismatch between # of unique combinations of color and marker (103) \n", "# vs. number of total rows (392) in df causes incorrect hover output" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.11" } }, "nbformat": 4, "nbformat_minor": 0 }