Repository: bokeh/bokeh

Issue:
Allow reset tool to only emit reset event, and skip built-in reset code
So far, `Range1d._initial_start` & `Range1d._initial_end` are private JS attributes. Because the content of a plot might change - e.g. an image which size evolves - it's necessary to be able to change the initial range limits so that the proper scale is displayed  when the user clicks on the `ResetTool` button. 


Discussion:
@mattpap Your thoughts welcome. Another possibility is to configure the reset tool explicitly with the values it should use for resets. 
Thinking about this more, I think what I would instead suggest is making it so that the `ResetTool` can suppress calling the built-in `Plot.reset` method, then users could reset to whatever coordinates they want with a JS or python callback on the "reset" event.  

Resolving patch:
diff --git a/bokeh/core/enums.py b/bokeh/core/enums.py
--- a/bokeh/core/enums.py
+++ b/bokeh/core/enums.py
@@ -117,6 +117,7 @@ class MyModel(Model):
     'Palette',
     'RenderLevel',
     'RenderMode',
+    'ResetPolicy',
     'RoundingFunction',
     'SizingMode',
     'SliderCallbackPolicy',
@@ -314,6 +315,9 @@ def enumeration(*values, **kwargs):
 #: Specify a render mode for renderers that support both Canvas or CSS rendering
 RenderMode = enumeration("canvas", "css")
 
+#: What reset actions should occur on a Plot reset
+ResetPolicy = enumeration("standard", "event_only")
+
 #: Specify a policy for  how numbers should be rounded
 RoundingFunction = enumeration("round", "nearest", "floor", "rounddown", "ceil", "roundup")
 
diff --git a/bokeh/models/plots.py b/bokeh/models/plots.py
--- a/bokeh/models/plots.py
+++ b/bokeh/models/plots.py
@@ -27,7 +27,7 @@
 from six import string_types
 
 # Bokeh imports
-from ..core.enums import Location, OutputBackend
+from ..core.enums import Location, OutputBackend, ResetPolicy
 from ..core.properties import Bool, Dict, Enum, Include, Instance, Int, List, Override, String, Float
 from ..core.property_mixins import LineProps, FillProps
 from ..core.query import find
@@ -700,6 +700,14 @@ def v_symmetry(self, _):
         This setting only takes effect if ``match_aspect`` is set to ``True``.
     """)
 
+    reset_policy = Enum(ResetPolicy, default="standard", help="""
+    How a plot should respond to being reset. By deafult, the standard actions
+    are to clear any tool state history, return plot ranges to their original
+    values, undo all selections, and emit a ``Reset`` event. If customization
+    is desired, this property may be set to ``"event_only"``, which will
+    suppress all of the actions except the Reset event.
+    """)
+
     # XXX: override LayoutDOM's definitions because of plot_{width,height}.
     @error(FIXED_SIZING_MODE)
     def _check_fixed_sizing_mode(self):


Test patch:
diff --git a/bokeh/core/tests/test_enums.py b/bokeh/core/tests/test_enums.py
--- a/bokeh/core/tests/test_enums.py
+++ b/bokeh/core/tests/test_enums.py
@@ -179,6 +179,9 @@ def test_RenderLevel(self):
     def test_RenderMode(self):
         assert tuple(bce.RenderMode) == ("canvas", "css")
 
+    def test_ResetPolicy(self):
+        assert tuple(bce.ResetPolicy) == ("standard", "event_only")
+
     def test_RoundingFunction(self):
         assert tuple(bce.RoundingFunction) == ("round", "nearest", "floor", "rounddown", "ceil", "roundup")
 
@@ -256,6 +259,7 @@ def test_enums_contents():
         'Palette',
         'RenderLevel',
         'RenderMode',
+        'ResetPolicy',
         'RoundingFunction',
         'SizingMode',
         'SizingPolicy',
diff --git a/bokehjs/test/models/plots/plot.ts b/bokehjs/test/models/plots/plot.ts
--- a/bokehjs/test/models/plots/plot.ts
+++ b/bokehjs/test/models/plots/plot.ts
@@ -28,10 +28,37 @@ describe("lib.models.plots.plot", () => {
       })
       expect(spy.called).to.be.false
     })
+
   })
 
   describe("PlotView", () => {
 
+      it("should perform standard reset actions by default", () => {
+        const view = new_plot_view()
+        const spy_state = sinon.spy(view, 'clear_state')
+        const spy_range = sinon.spy(view, 'reset_range')
+        const spy_selection = sinon.spy(view, 'reset_selection')
+        const spy_event = sinon.spy(view.model, 'trigger_event')
+        view.reset()
+        expect(spy_state.called).to.be.true
+        expect(spy_range.called).to.be.true
+        expect(spy_selection.called).to.be.true
+        expect(spy_event.called).to.be.true
+      })
+
+      it("should skip standard reset actions for event_only policy", () => {
+        const view = new_plot_view({reset_policy: "event_only"})
+        const spy_state = sinon.spy(view, 'clear_state')
+        const spy_range = sinon.spy(view, 'reset_range')
+        const spy_selection = sinon.spy(view, 'reset_selection')
+        const spy_event = sinon.spy(view.model, 'trigger_event')
+        view.reset()
+        expect(spy_state.called).to.be.false
+        expect(spy_range.called).to.be.false
+        expect(spy_selection.called).to.be.false
+        expect(spy_event.called).to.be.true
+      })
+
     it("layout should set element style correctly", () => {
       const view = new_plot_view({width: 425, height: 658})
       const expected_style = "position: relative; display: block; left: 0px; top: 0px; width: 425px; height: 658px; margin: 0px 0px 0px 0px;"