Repository: bokeh/bokeh

Issue:
It's possible to extend the lasso tool outside the bounds of a plot
For reference, this is version '**0.9.0rc-8f3c806-18-g08c81cb**'

![untitled](https://cloud.githubusercontent.com/assets/1417520/7637447/96059696-fa33-11e4-90ee-84f46c760595.png)



Discussion:
one solution is just to cancel/reset current tool when mouse exits inner area, but another would make the tool boundary "hug" the inner frame 


Resolving patch:
diff --git a/bokeh/models/tools.py b/bokeh/models/tools.py
--- a/bokeh/models/tools.py
+++ b/bokeh/models/tools.py
@@ -450,6 +450,14 @@ class LassoSelectTool(Tool):
     event, or only once, when the selection region is completed. Default: True
     """)
 
+    callback = Instance(Callback, help="""
+    A callback to run in the browser on every selection of a lasso area.
+    The cb_data parameter that is available to the Callback code will contain
+    one LassoSelectTool-specific field:
+
+    :geometry: object containing the coordinates of the lasso area
+    """)
+
     overlay = Instance(PolyAnnotation, default=DEFAULT_POLY_OVERLAY, help="""
     A shaded annotation drawn to indicate the selection region.
     """)


Test patch:
diff --git a/tests/integration/interaction/test_tools.py b/tests/integration/interaction/test_tools.py
--- a/tests/integration/interaction/test_tools.py
+++ b/tests/integration/interaction/test_tools.py
@@ -1,7 +1,7 @@
 from __future__ import absolute_import
 
 from bokeh.io import save
-from bokeh.models import BoxSelectTool, ColumnDataSource, CustomJS
+from bokeh.models import BoxSelectTool, ColumnDataSource, CustomJS, LassoSelectTool
 from bokeh.plotting import figure
 from selenium.webdriver.common.action_chains import ActionChains
 from tests.integration.utils import has_no_console_errors
@@ -9,17 +9,16 @@
 import pytest
 pytestmark = pytest.mark.integration
 
+PLOT_DIM = 600
 
-def test_box_select(output_file_url, selenium):
-    PLOT_DIM = 600
-
+def generate_plot():
     source = ColumnDataSource(dict(
         x=[1, 2, 3],
         y=[3, 2, 3],
         name=['top_left', 'middle', 'top_right'],
     ))
     # Make plot and add a taptool callback that generates an alert
-    plot = figure(tools='box_select', height=PLOT_DIM, width=PLOT_DIM, x_range=[1, 3], y_range=[1, 3])
+    plot = figure(tools='', height=PLOT_DIM, width=PLOT_DIM, x_range=[1, 3], y_range=[1, 3])
     plot.circle(x='x', y='y', radius=0.2, source=source)
 
     source.callback = CustomJS(code="""
@@ -34,6 +33,13 @@ def test_box_select(output_file_url, selenium):
         alert(selected_names);
     """)
 
+    return plot
+
+def test_box_select(output_file_url, selenium):
+    plot = generate_plot()
+
+    plot.add_tools(BoxSelectTool())
+
     # Save the plot and start the test
     save(plot)
     selenium.get(output_file_url)
@@ -52,3 +58,30 @@ def test_box_select(output_file_url, selenium):
     # Get the alert from box select and assert that the middle item is selected
     alert = selenium.switch_to_alert()
     assert alert.text == 'middle'
+
+def test_lasso_select(output_file_url, selenium):
+    plot = generate_plot()
+
+    #limit to one callback on click release
+    plot.add_tools(LassoSelectTool(select_every_mousemove=False))
+
+    # Save the plot and start the test
+    save(plot)
+    selenium.get(output_file_url)
+    assert has_no_console_errors(selenium)
+
+    # Drag a lasso selection area around middle point
+    canvas = selenium.find_element_by_tag_name('canvas')
+
+    actions = ActionChains(selenium)
+    actions.move_to_element_with_offset(canvas, PLOT_DIM * 0.25, PLOT_DIM * 0.25)
+    actions.click_and_hold()
+    actions.move_by_offset(0, PLOT_DIM * 0.5)
+    actions.move_by_offset(PLOT_DIM * 0.5, 0)
+    actions.move_by_offset(0, PLOT_DIM * -0.5)
+    actions.release()
+    actions.perform()
+
+    # Get the alert from box select and assert that the middle item is selected
+    alert = selenium.switch_to_alert()
+    assert alert.text == 'middle'