Repository: bokeh/bokeh

Issue:
Add major_label_overrides to ColorBar
axes already support the use of major_label_overrides that use a dictionary to change major tick labels.  It would be nice if ColorBar objects supported the same.  This could close out #5618


Discussion:
I have this working in a local version of bokeh, working on PR

Resolving patch:
diff --git a/bokeh/models/annotations.py b/bokeh/models/annotations.py
--- a/bokeh/models/annotations.py
+++ b/bokeh/models/annotations.py
@@ -9,7 +9,7 @@
 from ..core.enums import (AngleUnits, Dimension, FontStyle, LegendClickPolicy, LegendLocation,
                           Orientation, RenderMode, SpatialUnits, TextAlign)
 from ..core.has_props import abstract
-from ..core.properties import (Angle, AngleSpec, Auto, Bool, ColorSpec, Datetime, DistanceSpec, Either,
+from ..core.properties import (Angle, AngleSpec, Auto, Bool, ColorSpec, Datetime, Dict, DistanceSpec, Either,
                                Enum, Float, FontSizeSpec, Include, Instance, Int, List, NumberSpec, Override,
                                Seq, String, StringSpec, Tuple, value)
 from ..core.property_mixins import FillProps, LineProps, TextProps
@@ -248,6 +248,11 @@ class ColorBar(Annotation):
     A TickFormatter to use for formatting the visual appearance of ticks.
     """)
 
+    major_label_overrides = Dict(Either(Float, String), String, default={}, help="""
+    Provide explicit tick label values for specific tick locations that
+    override normal formatting.
+    """)
+
     color_mapper = Instance(ContinuousColorMapper, help="""
     A continuous color mapper containing a color palette to render.
 


Test patch:
diff --git a/bokeh/models/tests/test_annotations.py b/bokeh/models/tests/test_annotations.py
--- a/bokeh/models/tests/test_annotations.py
+++ b/bokeh/models/tests/test_annotations.py
@@ -103,7 +103,8 @@ def test_ColorBar():
         "major_tick_in",
         "major_tick_out",
         "minor_tick_in",
-        "minor_tick_out"],
+        "minor_tick_out",
+        "major_label_overrides"],
         prefix('title_', TEXT),
         prefix('major_label_', TEXT),
         prefix('major_tick_', LINE),
diff --git a/bokehjs/test/models/annotations/color_bar.coffee b/bokehjs/test/models/annotations/color_bar.coffee
--- a/bokehjs/test/models/annotations/color_bar.coffee
+++ b/bokehjs/test/models/annotations/color_bar.coffee
@@ -184,7 +184,7 @@ describe "ColorBar module", ->
             expect(image_dimensions.width).to.be.equal(480)
             expect(image_dimensions.height).to.be.equal(25)
 
-    describe "ColorBar._tick_coordinates method", ->
+    describe "ColorBar.tick_info method", ->
 
       beforeEach ->
         @plot.add_layout(@color_bar)
@@ -196,53 +196,53 @@ describe "ColorBar module", ->
         @color_bar.height = 100
         @color_bar.orientation = 'vertical'
 
-        tick_coords = @color_bar._tick_coordinates()
+        tick_coords = @color_bar.tick_info()
 
-        expect(tick_coords.major[0]).to.be.deep.equal([0, 0, 0, 0, 0, 0])
-        expect(tick_coords.major[1]).to.be.deep.equal(new Float64Array([100, 80, 60, 40, 20, 0]))
-        expect(tick_coords.major_labels).to.be.deep.equal([10, 12, 14, 16, 18, 20])
+        expect(tick_coords.coords.major[0]).to.be.deep.equal([0, 0, 0, 0, 0, 0])
+        expect(tick_coords.coords.major[1]).to.be.deep.equal(new Float64Array([100, 80, 60, 40, 20, 0]))
+        expect(tick_coords.labels.major).to.be.deep.equal(['10', '12', '14', '16', '18', '20'])
 
       it "Should correctly determine tick coords and labels for LinearColorMapperif orientation='horizontal'", ->
         @color_bar.color_mapper = new LinearColorMapper({low: 10, high: 20, palette: Viridis.Viridis10})
         @color_bar.width = 100
         @color_bar.orientation = 'horizontal'
 
-        tick_coords = @color_bar._tick_coordinates()
+        tick_coords = @color_bar.tick_info()
 
-        expect(tick_coords.major[1]).to.be.deep.equal([0, 0, 0, 0, 0, 0])
-        expect(tick_coords.major[0]).to.be.deep.equal(new Float64Array([0, 20, 40, 60, 80, 100]))
-        expect(tick_coords.major_labels).to.be.deep.equal([10, 12, 14, 16, 18, 20])
+        expect(tick_coords.coords.major[1]).to.be.deep.equal([0, 0, 0, 0, 0, 0])
+        expect(tick_coords.coords.major[0]).to.be.deep.equal(new Float64Array([0, 20, 40, 60, 80, 100]))
+        expect(tick_coords.labels.major).to.be.deep.equal(['10', '12', '14', '16', '18', '20'])
 
       it "Should correctly determine tick coords and labels for LogColorMapper if orientation='vertical'", ->
         @color_bar.color_mapper = new LogColorMapper({low: 1, high: 1000, palette: Viridis.Viridis10})
         @color_bar.height = 100
         @color_bar.orientation = 'vertical'
 
-        tick_coords = @color_bar._tick_coordinates()
+        tick_coords = @color_bar.tick_info()
 
-        expect(tick_coords.major[0]).to.be.deep.equal([0, 0, 0, 0, 0])
-        expect(tick_coords.major[1]).to.be.deep.equal(new Float64Array([23.299000144533963, 13.264666955734583, 7.394958320545214, 3.2303337669352175, 0]))
-        expect(tick_coords.major_labels).to.be.deep.equal([200, 400, 600, 800, 1000])
+        expect(tick_coords.coords.major[0]).to.be.deep.equal([0, 0, 0, 0, 0])
+        expect(tick_coords.coords.major[1]).to.be.deep.equal(new Float64Array([23.299000144533963, 13.264666955734583, 7.394958320545214, 3.2303337669352175, 0]))
+        expect(tick_coords.labels.major).to.be.deep.equal(['200', '400', '600', '800', '1000'])
 
       it "Should correctly determine tick coords and labels for LogColorMapper if orientation='horizontal'", ->
         @color_bar.color_mapper = new LogColorMapper({low: 1, high: 1000, palette: Viridis.Viridis10})
         @color_bar.width = 100
         @color_bar.orientation = 'horizontal'
 
-        tick_coords = @color_bar._tick_coordinates()
+        tick_coords = @color_bar.tick_info()
 
-        expect(tick_coords.major[1]).to.be.deep.equal([0, 0, 0, 0, 0])
-        expect(tick_coords.major[0]).to.be.deep.equal(new Float64Array([76.70099985546604, 86.73533304426542, 92.60504167945479, 96.76966623306478, 100]))
-        expect(tick_coords.major_labels).to.be.deep.equal([200, 400, 600, 800, 1000])
+        expect(tick_coords.coords.major[1]).to.be.deep.equal([0, 0, 0, 0, 0])
+        expect(tick_coords.coords.major[0]).to.be.deep.equal(new Float64Array([76.70099985546604, 86.73533304426542, 92.60504167945479, 96.76966623306478, 100]))
+        expect(tick_coords.labels.major).to.be.deep.equal(['200', '400', '600', '800', '1000'])
 
       it "Should correctly return empty tick coords and labels for LogColorMapper if log(high)/log(low) are non-numeric", ->
         @color_bar.color_mapper = new LogColorMapper({low: -1, high: 0, palette: Viridis.Viridis10})
         @color_bar.ticker = new LogTicker()
-        tick_coords = @color_bar._tick_coordinates()
+        tick_coords = @color_bar.tick_info()
 
-        expect(tick_coords.major[0]).to.be.deep.equal([])
-        expect(tick_coords.major[1]).to.be.deep.equal(new Float64Array([]))
-        expect(tick_coords.major_labels).to.be.deep.equal([])
+        expect(tick_coords.coords.major[0]).to.be.deep.equal([])
+        expect(tick_coords.coords.major[1]).to.be.deep.equal(new Float64Array([]))
+        expect(tick_coords.labels.major).to.be.deep.equal([])
 
   describe "ColorBarView", ->
 
@@ -282,7 +282,7 @@ describe "ColorBar module", ->
 
     it "ColorBarView._get_label_extent method (orientation='vertical') and no major_labels", ->
       # Handle case where scale start/end causes no ticks to exist (usually for a logticker)
-      stub = sinon.stub(@color_bar_view.model, "_tick_coordinates").returns({"major_labels": []})
+      stub = sinon.stub(@color_bar_view.model, "tick_info").returns({labels: {major: []}})
       expect(@color_bar_view._get_label_extent()).to.be.equal(0)
       stub.restore()