Repository: bokeh/bokeh

Issue:
Feature Request: Add hover inspection Image glyphs
Hi, I hope this the right place to ask my question.
is it possible to implement the Hover Tool on image in such a way that when I moving my mouse on the image pixels, I get and information on the pixel below the mouse, like the pixel coordinates and pixel value.
I would be glad to get an example.
Thanks
Saar 



Discussion:
Hi @ofirsa, our issue tracker is becoming too large to manage easily. We have to reserve it for feature requests and bug reports only, to have any hope of taming it. This kind of general support question should go to the general public mailing list:

https://groups.google.com/a/continuum.io/forum/#!forum/bokeh

Sorry to revisit this old issue, but as far as I can tell it is still valid.  Sarah closed it as a support question, but to me it sounds like either a feature request (can images please support hover information like other glyphs do) or a bug report (images don't support hover information like they should), as requested.

In either case, what's the answer?  Is there some fundamental reason why Image shouldn't support hovering to reveal the pixel coordinates and the value of the image at that point?  We will need this in our work, and can start working on a PR if that's appropriate.
There's some relevant discussion on the Google Group, but nothing that indicates to me that this feature request is somehow invalid or not feasible:

https://groups.google.com/a/continuum.io/forum/#!searchin/bokeh/hover$20tool$20image|sort:relevance/bokeh/W-n34ElNlRo/O7_eut-5DwAJ

https://groups.google.com/a/continuum.io/forum/#!searchin/bokeh/Hover$20image/bokeh/fOlnivwLwvw/miZDTdA8CQAJ

Not sure why this was closed, I thought there was another issue for image hit-testing but I can't find it offhand. Doing this will required setting a "2d" selection, i.e, will want to know which `i, j` pixel in the image was hit. That's not common, but there should already be kind-of similar example code for `MultiLine`. There's also the question of what to do when the image is big and a single pixel on the screen spans more than one pixel in the image (e.g. 1000x1000 image in a 500x500 canvas). 

Certainly possible in principle, but mostly just needs someone to spend some time thinking about cases like the above, and defining and documenting the exact behaviors. Will need tests too. 
To be clear: would be a welcome addition, just needs a little upfront thought and design. 
Sounds good.  Happy for Jean-Luc to take a stab at it?
For sure. 
Hi! Any update on this feature request/bug report? This functionality can be very useful.
It's still on @jlstevens's to-do list, and is expected to be addressed soon.
Yes, I am eager to tackle this although I am currently waiting on the refactor in #7261 to be completed first.
Awesome! Thanks!!

On Wed, Jan 3, 2018 at 6:42 PM, Jean-Luc Stevens <notifications@github.com>
wrote:

> Yes, I am eager to tackle this although I am currently waiting on the
> refactor in #7261 <https://github.com/bokeh/bokeh/pull/7261> to be
> completed first.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/bokeh/bokeh/issues/3886#issuecomment-355185201>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/ASxH0y57LxNhgMW0d-NnoX84D85nrn5pks5tHDqFgaJpZM4HcJ9Y>
> .
>


Resolving patch:
diff --git a/bokeh/sphinxext/bokeh_plot.py b/bokeh/sphinxext/bokeh_plot.py
--- a/bokeh/sphinxext/bokeh_plot.py
+++ b/bokeh/sphinxext/bokeh_plot.py
@@ -127,7 +127,7 @@ def _process_script(source, filename, env, js_name, use_relative_paths=False):
                 GOOGLE_API_KEY = "MISSING_API_KEY"
             else:
                 raise SphinxError("The GOOGLE_API_KEY environment variable is not set. Set GOOGLE_API_KEY to a valid API key, "
-                                  "or set BOKEH_DOCS_MISSING_API_KEY_OK=yes to build anyway (with broken GMaps)")
+                                  "or set bokeh_missing_google_api_key_ok=True in conf.py to build anyway (with broken GMaps)")
         run_source = source.replace("GOOGLE_API_KEY", GOOGLE_API_KEY)
     else:
         run_source = source


Test patch:
diff --git a/bokehjs/test/core/util/templating.coffee b/bokehjs/test/core/util/templating.coffee
--- a/bokehjs/test/core/util/templating.coffee
+++ b/bokehjs/test/core/util/templating.coffee
@@ -1,99 +1,208 @@
 {expect} = require "chai"
 utils = require "../../utils"
 
-{replace_placeholders} = utils.require "core/util/templating"
+tmpl = utils.require "core/util/templating"
+
+{keys} = utils.require("core/util/object")
 
 {ColumnDataSource} = utils.require("models/sources/column_data_source")
 {CustomJSHover} = utils.require("models/tools/inspectors/customjs_hover")
 
 describe "templating module", ->
 
+  describe "DEFAULT_FORMATTERS", ->
+
+    it "should have 3 entries", ->
+      expect(keys(tmpl.DEFAULT_FORMATTERS).length).to.be.equal 3
+
+    it "should have a numeral formatter", ->
+      f = tmpl.DEFAULT_FORMATTERS["numeral"]
+      expect(f(3.123, "$0.00", {})).to.be.equal "$3.12"
+
+    it "should have a datetime formatter", ->
+      f = tmpl.DEFAULT_FORMATTERS["datetime"]
+      expect(f(946684800000, "%m/%d/%Y", {})).to.be.equal "01/01/2000"
+
+    it "should have a printf formatter", ->
+      f = tmpl.DEFAULT_FORMATTERS["printf"]
+      expect(f(23.123456, "%5.3f mu", {})).to.be.equal "23.123 mu"
+
+  describe "basic_formatter", ->
+
+    it "should return strings as-is", ->
+      v = tmpl.basic_formatter("foo", "junk", {})
+      expect(v).to.be.equal "foo"
+
+    it "should not format integers as integers", ->
+      v = tmpl.basic_formatter(5, "junk", {})
+      expect(v).to.be.equal "5"
+
+    it "should use %0.3f between 0.1 and 1000", ->
+      v = tmpl.basic_formatter(0.11111, "junk", {})
+      expect(v).to.be.equal "0.111"
+      v = tmpl.basic_formatter(1.11111, "junk", {})
+      expect(v).to.be.equal "1.111"
+      v = tmpl.basic_formatter(100.11111, "junk", {})
+      expect(v).to.be.equal "100.111"
+      v = tmpl.basic_formatter(999.11111, "junk", {})
+      expect(v).to.be.equal "999.111"
+
+    it "should use %0.3e outside 0.1 and 1000", ->
+      v = tmpl.basic_formatter(0.0911111, "junk", {})
+      expect(v).to.be.equal "9.111e-2"
+      v = tmpl.basic_formatter(1000.11111, "junk", {})
+      expect(v).to.be.equal "1.000e+3"
+
+  describe "get_formatter", ->
+
+    it "should return basic_formatter for null format", ->
+      f = tmpl.get_formatter("x", "@x", null, {})
+      expect(f).to.be.equal tmpl.basic_formatter
+
+    it "should return numeral formatter for specs not in formatters dict", ->
+      f = tmpl.get_formatter("x", "@x", "$0.00", {})
+      expect(f).to.be.equal tmpl.DEFAULT_FORMATTERS["numeral"]
+
+    it "should return formatter from formatters dict when raw_spec is in formatters", ->
+      f = tmpl.get_formatter("x", "@x", "$0.00", {"@x": "numeral"})
+      expect(f).to.be.equal tmpl.DEFAULT_FORMATTERS["numeral"]
+
+      f = tmpl.get_formatter("x", "@x", "%5.3f mu", {"@x": "printf"})
+      expect(f).to.be.equal tmpl.DEFAULT_FORMATTERS["printf"]
+
+      f = tmpl.get_formatter("x", "@x", "%m/%d/%Y", {"@x": "datetime"})
+      expect(f).to.be.equal tmpl.DEFAULT_FORMATTERS["datetime"]
+
+      custom = new CustomJSHover({code:"return format + ' ' + special_vars.special + ' ' + value"})
+      f = tmpl.get_formatter("x", "@x", "custom", {"@x": custom})
+      expect(f(3.123, "custom", {special: 10})).to.be.equal "custom 10 3.123"
+
+    # this should be removed ~Bokeh 2.0
+    it "should return formatter from formatters dict when name is in formatters", ->
+      f = tmpl.get_formatter("x", "@x", "$0.00", {"x": "numeral"})
+      expect(f).to.be.equal tmpl.DEFAULT_FORMATTERS["numeral"]
+
+      f = tmpl.get_formatter("x", "@x", "%5.3f mu", {"x": "printf"})
+      expect(f).to.be.equal tmpl.DEFAULT_FORMATTERS["printf"]
+
+      f = tmpl.get_formatter("x", "@x", "%m/%d/%Y", {"x": "datetime"})
+      expect(f).to.be.equal tmpl.DEFAULT_FORMATTERS["datetime"]
+
+      custom = new CustomJSHover({code:"return format + ' ' + special_vars.special + ' ' + value"})
+      f = tmpl.get_formatter("x", "@x", "custom", {"x": custom})
+      expect(f(3.123, "custom", {special: 10})).to.be.equal "custom 10 3.123"
+
+    it "should throw an error on unknown formatter type", ->
+      expect(() -> tmpl.get_formatter("x", "@x", "%5.3f mu", {"x": "junk"})).to.throw Error
+
+  describe "get_value", ->
+
+    source = new ColumnDataSource({data: {foo: [10, 1.002], bar: ["a", "<div>b</div>"], baz: [1492890671885, 1290460671885]}})
+
+    it "should return $ values from special_vars", ->
+      v = tmpl.get_value("$x", source, 0, {"x": 99999})
+      expect(v).to.be.equal 99999
+
+    it "should throw an error on unknown special vars", ->
+      expect(() -> tmpl.get_value("$x", source, 0, {}).to.throw Error)
+
+    it "should return null for missing column", ->
+      v = tmpl.get_value("x", source, 0, {})
+      expect(v).to.be.null
+
+    it "should return integer indices from columns", ->
+      v = tmpl.get_value("foo", source, 0, {})
+      expect(v).to.be.equal 10
+
+      v = tmpl.get_value("foo", source, 1, {})
+      expect(v).to.be.equal 1.002
+
   describe "replace_placeholders", ->
 
     source = new ColumnDataSource({data: {foo: [10, 1.002], bar: ["a", "<div>b</div>"], baz: [1492890671885, 1290460671885]}})
 
     it "should replace unknown field names with ???", ->
-      s = replace_placeholders("stuff @junk", source, 0)
+      s = tmpl.replace_placeholders("stuff @junk", source, 0)
       expect(s).to.be.equal "stuff ???"
 
     it "should replace field names with escaped values by default", ->
-      s = replace_placeholders("stuff @foo", source, 0)
+      s = tmpl.replace_placeholders("stuff @foo", source, 0)
       expect(s).to.be.equal "stuff 10"
 
-      s = replace_placeholders("stuff @foo", source, 1)
+      s = tmpl.replace_placeholders("stuff @foo", source, 1)
       expect(s).to.be.equal "stuff 1.002"
 
-      s = replace_placeholders("stuff @bar", source, 0)
+      s = tmpl.replace_placeholders("stuff @bar", source, 0)
       expect(s).to.be.equal "stuff a"
 
-      s = replace_placeholders("stuff @bar", source, 1)
+      s = tmpl.replace_placeholders("stuff @bar", source, 1)
       expect(s).to.be.equal "stuff &lt;div&gt;b&lt;/div&gt;"
 
     it "should replace field names with values as-is with safe format", ->
-      s = replace_placeholders("stuff @foo{safe}", source, 0)
+      s = tmpl.replace_placeholders("stuff @foo{safe}", source, 0)
       expect(s).to.be.equal "stuff 10"
 
-      s = replace_placeholders("stuff @foo{safe}", source, 1)
+      s = tmpl.replace_placeholders("stuff @foo{safe}", source, 1)
       expect(s).to.be.equal "stuff 1.002"
 
-      s = replace_placeholders("stuff @bar{safe}", source, 0)
+      s = tmpl.replace_placeholders("stuff @bar{safe}", source, 0)
       expect(s).to.be.equal "stuff a"
 
-      s = replace_placeholders("stuff @bar{safe}", source, 1)
+      s = tmpl.replace_placeholders("stuff @bar{safe}", source, 1)
       expect(s).to.be.equal "stuff <div>b</div>"
 
     it "should ignore extra/unused formatters", ->
-      s = replace_placeholders("stuff @foo{(0.000 %)}", source, 0, {"quux": "numeral"})
+      s = tmpl.replace_placeholders("stuff @foo{(0.000 %)}", source, 0, {"quux": "numeral"})
       expect(s).to.be.equal "stuff 1000.000 %"
 
-      s = replace_placeholders("stuff @foo{(0.000 %)}", source, 1, {"quux": "numeral"})
+      s = tmpl.replace_placeholders("stuff @foo{(0.000 %)}", source, 1, {"quux": "numeral"})
       expect(s).to.be.equal "stuff 100.200 %"
 
     it "should throw an error on unrecognized formatters", ->
-      fn = () -> replace_placeholders("stuff @foo{(0.000 %)}", source, 0, {"foo": "junk"})
+      fn = () -> tmpl.replace_placeholders("stuff @foo{(0.000 %)}", source, 0, {"foo": "junk"})
       expect(fn).to.throw Error
 
     it "should default to numeral formatter", ->
       # just picking a random and uniquely numbro format to test with
-      s = replace_placeholders("stuff @foo{(0.000 %)}", source, 0)
+      s = tmpl.replace_placeholders("stuff @foo{(0.000 %)}", source, 0)
       expect(s).to.be.equal "stuff 1000.000 %"
 
-      s = replace_placeholders("stuff @foo{(0.000 %)}", source, 1)
+      s = tmpl.replace_placeholders("stuff @foo{(0.000 %)}", source, 1)
       expect(s).to.be.equal "stuff 100.200 %"
 
     it "should use the numeral formatter if specified", ->
       # just picking a random and uniquely numbro format to test with
-      s = replace_placeholders("stuff @foo{(0.000 %)}", source, 0, {"foo": "numeral"})
+      s = tmpl.replace_placeholders("stuff @foo{(0.000 %)}", source, 0, {"foo": "numeral"})
       expect(s).to.be.equal "stuff 1000.000 %"
 
-      s = replace_placeholders("stuff @foo{(0.000 %)}", source, 1, {"foo": "numeral"})
+      s = tmpl.replace_placeholders("stuff @foo{(0.000 %)}", source, 1, {"foo": "numeral"})
       expect(s).to.be.equal "stuff 100.200 %"
 
     it "should use a customjs hover formatter if specified", ->
       custom = new CustomJSHover({code:"return format + ' ' + special_vars.special + ' ' + value"})
-      s = replace_placeholders("stuff @foo{custom}", source, 0, {"foo": custom}, {special: "vars"})
+      s = tmpl.replace_placeholders("stuff @foo{custom}", source, 0, {"foo": custom}, {special: "vars"})
       expect(s).to.be.equal "stuff custom vars 10"
 
     it "should replace field names with tz formatted values with datetime formatter", ->
       # just picking a random and uniquely tz format to test with
-      s = replace_placeholders("stuff @baz{%F %T}", source, 0, {"baz": "datetime"})
+      s = tmpl.replace_placeholders("stuff @baz{%F %T}", source, 0, {"baz": "datetime"})
       expect(s).to.be.equal "stuff 2017-04-22 19:51:11"
 
-      s = replace_placeholders("stuff @baz{%F %T}", source, 1, {"baz": "datetime"})
+      s = tmpl.replace_placeholders("stuff @baz{%F %T}", source, 1, {"baz": "datetime"})
       expect(s).to.be.equal "stuff 2010-11-22 21:17:51"
 
     it "should replace field names with Sprintf formatted values with printf formatter", ->
       # just picking a random and uniquely Sprintf formats to test with
-      s = replace_placeholders("stuff @foo{%x}", source, 0, {"foo": "printf"})
+      s = tmpl.replace_placeholders("stuff @foo{%x}", source, 0, {"foo": "printf"})
       expect(s).to.be.equal "stuff a"
 
-      s = replace_placeholders("stuff @foo{%0.4f}", source, 1, {"foo": "printf"})
+      s = tmpl.replace_placeholders("stuff @foo{%0.4f}", source, 1, {"foo": "printf"})
       expect(s).to.be.equal "stuff 1.0020"
 
     it "should replace special vars with supplied values", ->
-      s = replace_placeholders("stuff $foo", source, 0, {}, {"foo": "special"})
+      s = tmpl.replace_placeholders("stuff $foo", source, 0, {}, {"foo": "special"})
       expect(s).to.be.equal "stuff special"
 
     it "should replace combinations and duplicates", ->
-      s = replace_placeholders("stuff $foo @foo @foo @foo{(0.000 %)} @baz{%F %T}", source, 0, {"baz": "datetime"}, {"foo": "special"})
+      s = tmpl.replace_placeholders("stuff $foo @foo @foo @foo{(0.000 %)} @baz{%F %T}", source, 0, {"baz": "datetime"}, {"foo": "special"})
       expect(s).to.be.equal "stuff special 10 10 1000.000 % 2017-04-22 19:51:11"