Repository: bokeh/bokeh

Issue:
Using HoverTool to display arbitrary HTML
from: https://groups.google.com/a/continuum.io/forum/#!topic/bokeh/8wcXT9vExH4 - Sarah Bird suggested I post this here:

Hi,

Upon a mouse-over event, I'd like to use the hover tool to display arbitrary HTML.  Basically, what I"m doing is this:

```
for idx in plot_df.index:
    plot_df.loc[idx, 'html_str'] = post_peer_review.loc[idx, [col1, col2]].to_html()
```

and then turning plot_df into a ColumnDataSource, and using the hovertool to display 'html_str'.  This however quotes the html, and does not display it.

Unfortunately, I cannot put the actual html into the hovertool and refer to values in the ColumnSource, because the information is coming from a separate dataframe and contains a variable number of rows.  It would of course be possible to put some clever javascript to nicely format a table depending on row numbers, but it would be much nicer to let pandas handle that for me.

Is there a way to get the hovertool to display the html without quoting it?



Discussion:
I think this would be a nice feature - I'm thinking of some kind of `safe` mark in the tooltips (similar to what is available in many templating languages).

+1. My use case is I have two glyphs that need to be presented differently in the tooltip. Where would we look in the codebase to add the "safe" flag?

Would another solution be to optionally accept an object mapping between tooltips and renderers?

> Where would we look in the codebase to add the "safe" flag?

I would start here:
https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/tool/inspectors/hover_tool.coffee#L178

It seems like all that's needed is to add an if statement for this line?:

https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/util/util.coffee#L38

I think that's probably right

What do you think is the best way to add that flag, then pass it down?

defer to @birdsarah for interface ideas

I was considering just a tooltips_safe=True flag, but I think it would be better to stick with a similar  paradigm for how we format numbers using `{}` so"

``` python
x = ['<p>foo</p>', '<p>bar</p>']
y = [1, 2]
z = [1.22233, 3.446546]
HoverTool(tooltips='@z{0.00} --- @x{safe}'). 
```

I think safe is pretty well known for those coming from web world. This should also be pretty straight forward to implement.

if anyone was working on this and wanted to also add the documentation for how to format numbers at the same time that would be great - see #2595 

two places for the documentation:
- in the HoverTool docstring, so it appears in the reference guide: http://bokeh.pydata.org/en/dev/docs/reference/models/tools.html#bokeh.models.tools.HoverTool.tooltips
- in the UserGuide:  http://bokeh.pydata.org/en/dev/docs/user_guide/tools.html#basic-tooltips 

Are you looking for number formatting docs like the one for `NumberFormatter`?
http://bokeh.pydata.org/en/0.10.0/docs/reference/models/widgets.tables.html#bokeh.models.widgets.tables.NumberFormatter

+1, I really need this to render MathML / KaTeX math expressions in labels :)

@altosaar would you have any interest in working up a PR for a `safe` flag for hover tooltips? e.g. something like

```
@foo[safe]
```

prevents the escaping that happens near the end of:

https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/util/util.coffee#L38

@mattpap there appears to be some functionality that is not documented (or I don't know where it is). What is the "full" acceptable format for tooltip spec, including prefix, long name, format, etc. and what exactly do those do. 

I wish I had enough experience with JS/CoffeeScript to be of use here. I'll take a look when I can to try to understand what's going on though!

no worries, I'm just trying to ask more often when I can, just in case. The number of issues and requests is rapidly outstripping the current core contributor team size 

I git cloned the bokeh repo, commented out this line: https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/util/util.coffee#L38 

I then rebuilt bokeh, and ran `python setup.py install --build_js`

The minimal example still doesn't work though - it looks like it's still escaping the HTML. 

Should I be commenting out additional lines to understand what the `@foo[safe]` flag functionality should be?

Bokeh loads resources from CDN by default now. Try setting the environment variable `BOKEH_RESOURCES=inline` and try running again. 

Thank you - that worked! 

For posterity:

I was trying to do `export BOKEH_RESOURCES=inline` then `python setup.py install --build_js` and run the example in ipython notebook, but wasn't seeing the changes - bokeh wasn't respecting the `BOKEH_RESOURCES` environment variable.

To fix it, I had to run a `.py` script and explicitly pass the environment variable in the same line:

`BOKEH_RESOURCES=inline python example_script.py`

This prevented the HTML escaping and I can now see math (MathML in Firefox) in labels. Dopeness. This is a huge help + speedup for my research. Thanks again :dancer: 

> To fix it, I had to run a .py script and explicitly pass the environment variable in the same line:

This is how it works. `BOKEH_*` shell variables work together with a bokeh rendering script or jupyter notebook. They do not affect `setup.py`. They also sometimes work with bokeh server (unfortunately only sometimes - this needs to be fixed).

Just in case: has anyone found a workaround for this issue ?

@Zulko 

I changed bokeh-0.12.3.js as follows to enable using `@val{safe}` (where val is the datatable column name) to display raw html that is present at that column:

``` js
replace_placeholders = function(string, data_source, i, special_vars) {
  if (special_vars == null) {
    special_vars = {};
  }
  string = string.replace(/(^|[^\$])\$(\w+)/g, (function(_this) {
    return function(match, prefix, name) {
      return prefix + "@$" + name;
    };
  })(this));
  string = string.replace(/(^|[^@])@(?:(\$?\w+)|{([^{}]+)})(?:{([^{}]+)})?/g, (function(_this) {
    return function(match, prefix, name, long_name, format) {
      var ref, replacement, value;
      name = long_name != null ? long_name : name;
      value = name[0] === "$" ? special_vars[name.substring(1)] : (ref = data_source.get_column(name)) != null ? ref[i] : void 0;
      if (value == null){
          replacement = "???";
      }
      else if (format != null){
        if (format == "safe"){
          return "" + prefix + value;
        }
        else {
          replacement = Numbro.format(value, format)
        }
      } else {
        replacement = _format_number(value);
      }
      return "" + prefix + (_.escape(replacement));
    };
  })(this));
  return string;
};
```

@astraw38 perhaps you could submit a PR for this feature?

Sure! I meant to, but got swamped with other work. I'm assuming you'll need some tests for it as well? 

@astraw38 an ideal PR would:
- add a note the this "Hover Tool" section of [this users guide chapter](https://raw.githubusercontent.com/bokeh/bokeh/master/sphinx/source/docs/user_guide/tools.rst)
- add some info to [this docstring](https://github.com/bokeh/bokeh/blob/master/bokeh/models/tools.py#L619)
- add a small example (or update and existing one) to demonstrate the use
- have some kind of tests, though I note that there is currently no unit tests for anything in `core/util/templating.coffee`: https://github.com/bokeh/bokeh/tree/master/bokehjs/test/core/util

Of course, we'd be happy to help answer questions or give guidance about any or all of those, and collaborate directly on your branch, if you only have time to do some of them


Resolving patch:
diff --git a/bokeh/models/tools.py b/bokeh/models/tools.py
--- a/bokeh/models/tools.py
+++ b/bokeh/models/tools.py
@@ -640,6 +640,8 @@ class HoverTool(Inspection):
             ("fill color", "$color[hex, swatch]:fill_color"),
             ("foo", "@foo"),
             ("bar", "@bar"),
+            ("baz", "@baz{safe}"),
+            ("total", "@total{$0,0.00}"
         ]
 
     You can also supply a ``Callback`` to the HoverTool, to build custom
@@ -715,6 +717,17 @@ class HoverTool(Inspection):
         are: 'hex' (to display the color as a hex value), and
         'swatch' to also display a small color swatch.
 
+    Additional format options ``safe`` and `Numbro format codes <http://numbrojs.com/format.html>`_
+    can be included in a post-fix brace block on field names. ::
+
+        [("total", "@total{$0,0.00}"),
+         ("data", "@data{safe}")]
+
+    Including ``{safe}`` after a field name will override automatic escaping
+    of the tooltip data source. Any HTML tags in the data tags will be rendered
+    as HTML in the resulting HoverTool output. See :ref:`custom_hover_tooltip` for a
+    more detailed example.
+
     ``None`` is also a valid value for tooltips. This turns off the
     rendering of tooltips. This is mostly useful when supplying other
     actions on hover via the callback property.
diff --git a/sphinx/source/docs/user_guide/examples/tools_hover_custom_tooltip.py b/sphinx/source/docs/user_guide/examples/tools_hover_custom_tooltip.py
--- a/sphinx/source/docs/user_guide/examples/tools_hover_custom_tooltip.py
+++ b/sphinx/source/docs/user_guide/examples/tools_hover_custom_tooltip.py
@@ -14,7 +14,13 @@
                 'http://bokeh.pydata.org/static/snake3D.png',
                 'http://bokeh.pydata.org/static/snake4_TheRevenge.png',
                 'http://bokeh.pydata.org/static/snakebite.jpg'
-            ]
+            ],
+            fonts=['<i>italics</i>',
+                   '<pre>pre</pre>',
+                   '<b>bold</b>',
+                   '<small>small</small>',
+                   '<del>del</del>'
+                   ]
         )
     )
 
@@ -32,6 +38,9 @@
                 <span style="font-size: 17px; font-weight: bold;">@desc</span>
                 <span style="font-size: 15px; color: #966;">[$index]</span>
             </div>
+            <div>
+                <span>@fonts{safe}</span>
+            </div>
             <div>
                 <span style="font-size: 15px;">Location</span>
                 <span style="font-size: 10px; color: #696;">($x, $y)</span>


Test patch: