1#-----------------------------------------------------------------------------
2# Copyright (c) 2012 - 2021, Anaconda, Inc., and Bokeh Contributors.
3# All rights reserved.
4#
5# The full license is in the file LICENSE.txt, distributed with this software.
6#-----------------------------------------------------------------------------
7''' Bokeh comes with a number of interactive tools.
8
9There are five types of tool interactions:
10
11.. hlist::
12    :columns: 5
13
14    * Pan/Drag
15    * Click/Tap
16    * Scroll/Pinch
17    * Actions
18    * Inspectors
19
20For the first three comprise the category of gesture tools, and only
21one tool for each gesture can be active at any given time. The active
22tool is indicated on the toolbar by a highlight next to the tool.
23Actions are immediate or modal operations that are only activated when
24their button in the toolbar is pressed. Inspectors are passive tools that
25merely report information or annotate the plot in some way, and may
26always be active regardless of what other tools are currently active.
27
28'''
29
30#-----------------------------------------------------------------------------
31# Boilerplate
32#-----------------------------------------------------------------------------
33import logging # isort:skip
34log = logging.getLogger(__name__)
35
36#-----------------------------------------------------------------------------
37# Imports
38#-----------------------------------------------------------------------------
39
40# Standard library imports
41import difflib
42import typing as tp
43from typing_extensions import Literal
44
45# Bokeh imports
46from ..core.enums import (
47    Anchor,
48    Dimension,
49    Dimensions,
50    Location,
51    SelectionMode,
52    TooltipAttachment,
53    TooltipFieldFormatter,
54)
55from ..core.has_props import abstract
56from ..core.properties import (
57    Alpha,
58    Auto,
59    Bool,
60    Color,
61    Date,
62    Datetime,
63    Dict,
64    Either,
65    Enum,
66    Float,
67    Image,
68    Instance,
69    Int,
70    List,
71    NonNullable,
72    Null,
73    Nullable,
74    Override,
75    Percent,
76    Seq,
77    String,
78    Tuple,
79)
80from ..core.validation import error
81from ..core.validation.errors import (
82    INCOMPATIBLE_BOX_EDIT_RENDERER,
83    INCOMPATIBLE_LINE_EDIT_INTERSECTION_RENDERER,
84    INCOMPATIBLE_LINE_EDIT_RENDERER,
85    INCOMPATIBLE_POINT_DRAW_RENDERER,
86    INCOMPATIBLE_POLY_DRAW_RENDERER,
87    INCOMPATIBLE_POLY_EDIT_RENDERER,
88    INCOMPATIBLE_POLY_EDIT_VERTEX_RENDERER,
89    NO_RANGE_TOOL_RANGES,
90)
91from ..model import Model
92from ..util.deprecation import deprecated
93from ..util.string import nice_join
94from .annotations import BoxAnnotation, PolyAnnotation
95from .callbacks import Callback
96from .glyphs import Line, LineGlyph, MultiLine, Patches, Rect, XYGlyph
97from .layouts import LayoutDOM
98from .ranges import Range1d
99from .renderers import DataRenderer, GlyphRenderer
100
101#-----------------------------------------------------------------------------
102# Globals and constants
103#-----------------------------------------------------------------------------
104
105__all__ = (
106    'Action',
107    'ActionTool',
108    'BoxEditTool',
109    'BoxSelectTool',
110    'BoxZoomTool',
111    'CrosshairTool',
112    'CustomAction',
113    'CustomJSHover',
114    'Drag',
115    'EditTool',
116    'FreehandDrawTool',
117    'HelpTool',
118    'HoverTool',
119    'Inspection',
120    'InspectTool',
121    'Gesture',
122    'GestureTool',
123    'LassoSelectTool',
124    'LineEditTool',
125    'PanTool',
126    'PointDrawTool',
127    'PolyDrawTool',
128    'PolyEditTool',
129    'PolySelectTool',
130    'ProxyToolbar',
131    'RangeTool',
132    'RedoTool',
133    'ResetTool',
134    'SaveTool',
135    'Scroll',
136    'Tap',
137    'TapTool',
138    'Tool',
139    'Toolbar',
140    'ToolbarBase',
141    'ToolbarBox',
142    'UndoTool',
143    'WheelPanTool',
144    'WheelZoomTool',
145    'ZoomInTool',
146    'ZoomOutTool',
147)
148
149#-----------------------------------------------------------------------------
150# General API
151#-----------------------------------------------------------------------------
152
153@abstract
154class Tool(Model):
155    ''' A base class for all interactive tool types.
156
157    '''
158
159    description = Nullable(String, help="""
160    A string describing the purpose of this tool. If not defined, an auto-generated
161    description will be used. This description will be typically presented in the
162    user interface as a tooltip.
163    """)
164
165    _known_aliases: tp.ClassVar[tp.Dict[str, tp.Callable[[], "Tool"]]] = {}
166
167    @classmethod
168    def from_string(cls, name: str) -> "Tool":
169        """ Takes a string and returns a corresponding `Tool` instance. """
170        constructor = cls._known_aliases.get(name)
171        if constructor is not None:
172            return constructor()
173        else:
174            known_names = cls._known_aliases.keys()
175            matches, text = difflib.get_close_matches(name.lower(), known_names), "similar"
176            if not matches:
177                matches, text = known_names, "possible"
178            raise ValueError(f"unexpected tool name '{name}', {text} tools are {nice_join(matches)}")
179
180    @classmethod
181    def register_alias(cls, name: str, constructor: tp.Callable[[], "Tool"]) -> None:
182        cls._known_aliases[name] = constructor
183
184@abstract
185class ActionTool(Tool):
186    ''' A base class for tools that are buttons in the toolbar.
187
188    '''
189    pass
190
191# TODO: deprecated, remove at bokeh 3.0
192Action = ActionTool
193
194@abstract
195class GestureTool(Tool):
196    ''' A base class for tools that respond to drag events.
197
198    '''
199    pass
200
201# TODO: deprecated, remove at bokeh 3.0
202Gesture = GestureTool
203
204@abstract
205class Drag(GestureTool):
206    ''' A base class for tools that respond to drag events.
207
208    '''
209    pass
210
211@abstract
212class Scroll(GestureTool):
213    ''' A base class for tools that respond to scroll events.
214
215    '''
216    pass
217
218@abstract
219class Tap(GestureTool):
220    ''' A base class for tools that respond to tap/click events.
221
222    '''
223    pass
224
225@abstract
226class SelectTool(GestureTool):
227    ''' A base class for tools that perfrom "selections", e.g. ``BoxSelectTool``.
228
229    '''
230
231    names = List(String, help="""
232    A list of names to query for. If set, only renderers that have a matching
233    value for their ``name`` attribute will be used.
234
235    .. note:
236        This property is deprecated and will be removed in bokeh 3.0.
237
238    """)
239
240    renderers = Either(Auto, List(Instance(DataRenderer)), default="auto", help="""
241    An explicit list of renderers to hit test against. If unset, defaults to
242    all renderers on a plot.
243    """)
244
245    mode = Enum(SelectionMode, default="replace", help="""
246    Defines what should happen when a new selection is made. The default
247    is to replace the existing selection. Other options are to append to
248    the selection, intersect with it or subtract from it.
249    """)
250
251@abstract
252class InspectTool(GestureTool):
253    ''' A base class for tools that perform "inspections", e.g. ``HoverTool``.
254
255    '''
256    toggleable = Bool(True, help="""
257    Whether an on/off toggle button should appear in the toolbar for this
258    inspection tool. If ``False``, the viewers of a plot will not be able to
259    toggle the inspector on or off using the toolbar.
260    """)
261
262# TODO: deprecated, remove at bokeh 3.0
263Inspection = InspectTool
264
265@abstract
266class ToolbarBase(Model):
267    ''' A base class for different toolbars.
268
269    '''
270
271    logo = Nullable(Enum("normal", "grey"), default="normal", help="""
272    What version of the Bokeh logo to display on the toolbar. If
273    set to None, no logo will be displayed.
274    """)
275
276    autohide = Bool(default=False, help="""
277    Whether the toolbar will be hidden by default. Default: False.
278    If True, hides toolbar when cursor is not in canvas.
279    """)
280
281    tools = List(Instance(Tool), help="""
282    A list of tools to add to the plot.
283    """)
284
285class Toolbar(ToolbarBase):
286    ''' Collect tools to display for a single plot.
287
288    '''
289
290    active_drag: tp.Union[Literal["auto"], Drag, None] = Either(Null, Auto, Instance(Drag), default="auto", help="""
291    Specify a drag tool to be active when the plot is displayed.
292    """)
293
294    active_inspect: tp.Union[Literal["auto"], InspectTool, tp.Sequence[InspectTool], None] = \
295        Either(Null, Auto, Instance(InspectTool), Seq(Instance(InspectTool)), default="auto", help="""
296    Specify an inspection tool or sequence of inspection tools to be active when
297    the plot is displayed.
298    """)
299
300    active_scroll: tp.Union[Literal["auto"], Scroll, None] = Either(Null, Auto, Instance(Scroll), default="auto", help="""
301    Specify a scroll/pinch tool to be active when the plot is displayed.
302    """)
303
304    active_tap: tp.Union[Literal["auto"], Tap, None] = Either(Null, Auto, Instance(Tap), default="auto", help="""
305    Specify a tap/click tool to be active when the plot is displayed.
306    """)
307
308    active_multi: tp.Union[Literal["auto"], GestureTool, None] = Nullable(Instance(GestureTool), help="""
309    Specify an active multi-gesture tool, for instance an edit tool or a range
310    tool.
311
312    Note that activating a multi-gesture tool will deactivate any other gesture
313    tools as appropriate. For example, if a pan tool is set as the active drag,
314    and this property is set to a ``BoxEditTool`` instance, the pan tool will
315    be deactivated (i.e. the multi-gesture tool will take precedence).
316    """)
317
318class ProxyToolbar(ToolbarBase):
319    ''' A toolbar that allow to merge and proxy tools of toolbars in multiple
320    plots.
321
322    '''
323
324    toolbars = List(Instance(Toolbar), help="""
325    """)
326
327class ToolbarBox(LayoutDOM):
328    ''' A layoutable toolbar that can accept the tools of multiple plots, and
329    can merge the tools into a single button for convenience.
330
331    '''
332
333    toolbar = Instance(ToolbarBase, help="""
334    A toolbar associated with a plot which holds all its tools.
335    """)
336
337    toolbar_location = Enum(Location, default="right")
338
339class PanTool(Drag):
340    ''' *toolbar icon*: |pan_icon|
341
342    The pan tool allows the user to pan a Plot by left-dragging a mouse, or on
343    touch devices by dragging a finger or stylus, across the plot region.
344
345    The pan tool also activates the border regions of a Plot for "single axis"
346    panning. For instance, dragging in the vertical border or axis will effect
347    a pan in the vertical direction only, with horizontal dimension kept fixed.
348
349    .. |pan_icon| image:: /_images/icons/Pan.png
350        :height: 24px
351
352    '''
353
354    dimensions = Enum(Dimensions, default="both", help="""
355    Which dimensions the pan tool is constrained to act in. By default
356    the pan tool will pan in any dimension, but can be configured to only
357    pan horizontally across the width of the plot, or vertically across the
358    height of the plot.
359    """)
360
361DEFAULT_RANGE_OVERLAY = lambda: BoxAnnotation(
362    syncable=False,
363    level="overlay",
364    fill_color="lightgrey",
365    fill_alpha=0.5,
366    line_color="black",
367    line_alpha=1.0,
368    line_width=0.5,
369    line_dash=[2,2],
370)
371
372class RangeTool(Drag):
373    ''' *toolbar icon*: |range_icon|
374
375    The range tool allows the user to update range objects for either or both
376    of the x- or y-dimensions by dragging a corresponding shaded annotation to
377    move it or change its boundaries.
378
379    A common use case is to add this tool to a plot with a large fixed range,
380    but to configure the tool range from a different plot. When the user
381    manipulates the overlay, the range of the second plot will be updated
382    automatically.
383
384    .. |range_icon| image:: /_images/icons/Range.png
385        :height: 24px
386
387    '''
388
389    x_range = Nullable(Instance(Range1d), help="""
390    A range synchronized to the x-dimension of the overlay. If None, the overlay
391    will span the entire x-dimension.
392    """)
393
394    x_interaction = Bool(default=True, help="""
395    Whether to respond to horizontal pan motions when an ``x_range`` is present.
396
397    By default, when an ``x_range`` is specified, it is possible to adjust the
398    horizontal position of the range box by panning horizontally inside the
399    box, or along the top or bottom edge of the box. To disable this, and fix
400    the  range box in place horizontally, set to False. (The box will still
401    update if the ``x_range`` is updated programmatically.)
402    """)
403
404    y_range = Nullable(Instance(Range1d), help="""
405    A range synchronized to the y-dimension of the overlay. If None, the overlay
406    will span the entire y-dimension.
407    """)
408
409    y_interaction = Bool(default=True, help="""
410    Whether to respond to vertical pan motions when a ``y_range`` is present.
411
412    By default, when a ``y_range`` is specified, it is possible to adjust the
413    vertical position of the range box by panning vertically inside the box, or
414    along the top or bottom edge of the box. To disable this, and fix the range
415    box in place vertically, set to False. (The box will still update if the
416    ``y_range`` is updated programmatically.)
417    """)
418
419    overlay = Instance(BoxAnnotation, default=DEFAULT_RANGE_OVERLAY, help="""
420    A shaded annotation drawn to indicate the configured ranges.
421    """)
422
423    @error(NO_RANGE_TOOL_RANGES)
424    def _check_no_range_tool_ranges(self):
425        if self.x_range is None and self.y_range is None:
426            return "At least one of RangeTool.x_range or RangeTool.y_range must be configured"
427
428class WheelPanTool(Scroll):
429    ''' *toolbar icon*: |wheel_pan_icon|
430
431    The wheel pan tool allows the user to pan the plot along the configured
432    dimension using the scroll wheel.
433
434    .. |wheel_pan_icon| image:: /_images/icons/WheelPan.png
435        :height: 24px
436
437    '''
438
439    dimension = Enum(Dimension, default="width", help="""
440    Which dimension the wheel pan tool is constrained to act in. By default the
441    wheel pan tool will pan the plot along the x-axis.
442    """)
443
444class WheelZoomTool(Scroll):
445    ''' *toolbar icon*: |wheel_zoom_icon|
446
447    The wheel zoom tool will zoom the plot in and out, centered on the
448    current mouse location.
449
450    The wheel zoom tool also activates the border regions of a Plot for
451    "single axis" zooming. For instance, zooming in the vertical border or
452    axis will effect a zoom in the vertical direction only, with the
453    horizontal dimension kept fixed.
454
455    .. |wheel_zoom_icon| image:: /_images/icons/WheelZoom.png
456        :height: 24px
457
458    '''
459
460    dimensions = Enum(Dimensions, default="both", help="""
461    Which dimensions the wheel zoom tool is constrained to act in. By default
462    the wheel zoom tool will zoom in any dimension, but can be configured to
463    only zoom horizontally across the width of the plot, or vertically across
464    the height of the plot.
465    """)
466
467    maintain_focus = Bool(default=True, help="""
468    Whether or not zooming tool maintains its focus position. Setting to False
469    results in a more "gliding" behavior, allowing one to zoom out more
470    smoothly, at the cost of losing the focus position.
471    """)
472
473    zoom_on_axis = Bool(default=True, help="""
474    Whether scrolling on an axis (outside the central plot area) should zoom
475    that dimension.
476    """)
477
478    speed = Float(default=1/600, help="""
479    Speed at which the wheel zooms. Default is 1/600. Optimal range is between
480    0.001 and 0.09. High values will be clipped. Speed may very between browsers.
481    """)
482
483class CustomAction(ActionTool):
484    ''' Execute a custom action, e.g. ``CustomJS`` callback when a toolbar
485    icon is activated.
486
487    Example:
488
489        .. code-block:: python
490
491            tool = CustomAction(icon="icon.png",
492                                callback=CustomJS(code='alert("foo")'))
493
494            plot.add_tools(tool)
495
496    '''
497
498    def __init__(self, *args, **kwargs):
499        action_tooltip = kwargs.pop("action_tooltip", None)
500        if action_tooltip is not None:
501            deprecated((2, 3, 0), "CustomAction.action_tooltip", "CustomAction.description")
502            kwargs["description"] = action_tooltip
503        super().__init__(*args, **kwargs)
504
505    @property
506    def action_tooltip(self):
507        deprecated((2, 3, 0), "CustomAction.action_tooltip", "CustomAction.description")
508        return self.description
509    @action_tooltip.setter
510    def action_tooltip(self, description):
511        deprecated((2, 3, 0), "CustomAction.action_tooltip", "CustomAction.description")
512        self.description = description
513
514    description = Override(default="Perform a Custom Action")
515
516    callback = Nullable(Instance(Callback), help="""
517    A Bokeh callback to execute when the custom action icon is activated.
518    """)
519
520    icon = Image(help="""
521    An icon to display in the toolbar.
522
523    The icon can provided as a string filename for an image, a PIL ``Image``
524    object, or an RGB(A) NumPy array.
525    """)
526
527class SaveTool(ActionTool):
528    ''' *toolbar icon*: |save_icon|
529
530    The save tool is an action. When activated, the tool opens a download dialog
531    which allows to save an image reproduction of the plot in PNG format. If
532    automatic download is not support by a web browser, the tool falls back to
533    opening the generated image in a new tab or window. User then can manually
534    save it by right clicking on the image and choosing "Save As" (or similar)
535    menu item.
536
537    .. |save_icon| image:: /_images/icons/Save.png
538        :height: 24px
539
540    '''
541
542class ResetTool(ActionTool):
543    ''' *toolbar icon*: |reset_icon|
544
545    The reset tool is an action. When activated in the toolbar, the tool resets
546    the data bounds of the plot to their values when the plot was initially
547    created.
548
549    .. |reset_icon| image:: /_images/icons/Reset.png
550        :height: 24px
551
552    '''
553
554    pass
555
556class TapTool(Tap, SelectTool):
557    ''' *toolbar icon*: |tap_icon|
558
559    The tap selection tool allows the user to select at single points by
560    left-clicking a mouse, or tapping with a finger.
561
562    See :ref:`userguide_styling_selected_unselected_glyphs` for information
563    on styling selected and unselected glyphs.
564
565    .. |tap_icon| image:: /_images/icons/Tap.png
566        :height: 24px
567
568    .. note::
569        Selections can be comprised of multiple regions, even those
570        made by different selection tools. Hold down the <<shift>> key
571        while making a selection to append the new selection to any
572        previous selection that might exist.
573
574    '''
575
576    behavior = Enum("select", "inspect", default="select", help="""
577    This tool can be configured to either make selections or inspections
578    on associated data sources. The difference is that selection changes
579    propagate across bokeh and other components (e.g. selection glyph)
580    will be notified. Inspections don't act like this, so it's useful to
581    configure `callback` when setting `behavior='inspect'`.
582    """)
583
584    gesture = Enum("tap", "doubletap", default="tap", help="""
585    Specifies which kind of gesture will be used to trigger the tool,
586    either a single or double tap.
587    """)
588
589    callback = Nullable(Instance(Callback), help="""
590    A callback to execute *whenever a glyph is "hit"* by a mouse click
591    or tap.
592
593    This is often useful with the  :class:`~bokeh.models.callbacks.OpenURL`
594    model to open URLs based on a user clicking or tapping a specific glyph.
595
596    However, it may also be a :class:`~bokeh.models.callbacks.CustomJS`
597    which can execute arbitrary JavaScript code in response to clicking or
598    tapping glyphs. The callback will be executed for each individual glyph
599    that is it hit by a click or tap, and will receive the ``TapTool`` model
600    as  ``cb_obj``. The optional ``cb_data`` will have the data source as
601    its ``.source`` attribute and the selection geometry as its
602    ``.geometries`` attribute.
603
604    The ``.geometries`` attribute has 5 members.
605    ``.type`` is the geometry type, which always a ``.point`` for a tap event.
606    ``.sx`` and ``.sy`` are the screen X and Y coordinates where the tap occurred.
607    ``.x`` and ``.y`` are the converted data coordinates for the item that has
608    been selected. The ``.x`` and ``.y`` values are based on the axis assigned
609    to that glyph.
610
611    .. note::
612        This callback does *not* execute on every tap, only when a glyph is
613        "hit". If you would like to execute a callback on every mouse tap,
614        please see :ref:`userguide_interaction_jscallbacks_customjs_interactions`.
615
616    """)
617
618class CrosshairTool(InspectTool):
619    ''' *toolbar icon*: |crosshair_icon|
620
621    The crosshair tool is a passive inspector tool. It is generally on at all
622    times, but can be configured in the inspector's menu associated with the
623    *toolbar icon* shown above.
624
625    The crosshair tool draws a crosshair annotation over the plot, centered on
626    the current mouse position. The crosshair tool may be configured to draw
627    across only one dimension by setting the ``dimension`` property to only
628    ``width`` or ``height``.
629
630    .. |crosshair_icon| image:: /_images/icons/Crosshair.png
631        :height: 24px
632
633    '''
634
635    dimensions = Enum(Dimensions, default="both", help="""
636    Which dimensions the crosshair tool is to track. By default, both vertical
637    and horizontal lines will be drawn. If only "width" is supplied, only a
638    horizontal line will be drawn. If only "height" is supplied, only a
639    vertical line will be drawn.
640    """)
641
642    line_color = Color(default="black", help="""
643    A color to use to stroke paths with.
644    """)
645
646    line_alpha = Alpha(help="""
647    An alpha value to use to stroke paths with.
648    """)
649
650    line_width = Float(default=1, help="""
651    Stroke width in units of pixels.
652    """)
653
654DEFAULT_BOX_OVERLAY = lambda: BoxAnnotation(
655    syncable=False,
656    level="overlay",
657    top_units="screen",
658    left_units="screen",
659    bottom_units="screen",
660    right_units="screen",
661    fill_color="lightgrey",
662    fill_alpha=0.5,
663    line_color="black",
664    line_alpha=1.0,
665    line_width=2,
666    line_dash=[4, 4],
667)
668
669class BoxZoomTool(Drag):
670    ''' *toolbar icon*: |box_zoom_icon|
671
672    The box zoom tool allows users to define a rectangular egion of a Plot to
673    zoom to by dragging he mouse or a finger over the plot region. The end of
674    the drag event indicates the selection region is ready.
675
676    .. |box_zoom_icon| image:: /_images/icons/BoxZoom.png
677        :height: 24px
678
679    .. note::
680        ``BoxZoomTool`` is incompatible with ``GMapPlot`` due to the manner in
681        which Google Maps exert explicit control over aspect ratios. Adding
682        this tool to a ``GMapPlot`` will have no effect.
683
684    '''
685
686    dimensions = Enum(Dimensions, default="both", help="""
687    Which dimensions the zoom box is to be free in. By default, users may
688    freely draw zoom boxes with any dimensions. If only "width" is supplied,
689    the box will be constrained to span the entire vertical space of the plot,
690    only the horizontal dimension can be controlled. If only "height" is
691    supplied, the box will be constrained to span the entire horizontal space
692    of the plot, and the vertical dimension can be controlled.
693    """)
694
695    overlay = Instance(BoxAnnotation, default=DEFAULT_BOX_OVERLAY, help="""
696    A shaded annotation drawn to indicate the selection region.
697    """)
698
699    match_aspect = Bool(default=False, help="""
700    Whether the box zoom region should be restricted to have the same
701    aspect ratio as the plot region.
702
703    .. note::
704        If the tool is restricted to one dimension, this value has
705        no effect.
706
707    """)
708
709    origin = Enum("corner", "center", default="corner", help="""
710    Indicates whether the rectangular zoom area should originate from a corner
711    (top-left or bottom-right depending on direction) or the center of the box.
712    """)
713
714class ZoomInTool(ActionTool):
715    ''' *toolbar icon*: |zoom_in_icon|
716
717    The zoom-in tool allows users to click a button to zoom in
718    by a fixed amount.
719
720    .. |zoom_in_icon| image:: /_images/icons/ZoomIn.png
721        :height: 24px
722
723    '''
724    # TODO ZoomInTool dimensions should probably be constrained to be the same as ZoomOutTool
725    dimensions = Enum(Dimensions, default="both", help="""
726    Which dimensions the zoom-in tool is constrained to act in. By default the
727    zoom-in zoom tool will zoom in any dimension, but can be configured to only
728    zoom horizontally across the width of the plot, or vertically across the
729    height of the plot.
730    """)
731
732    factor = Percent(default=0.1, help="""
733    Percentage to zoom for each click of the zoom-in tool.
734    """)
735
736class ZoomOutTool(ActionTool):
737    ''' *toolbar icon*: |zoom_out_icon|
738
739    The zoom-out tool allows users to click a button to zoom out
740    by a fixed amount.
741
742    .. |zoom_out_icon| image:: /_images/icons/ZoomOut.png
743        :height: 24px
744
745    '''
746    dimensions = Enum(Dimensions, default="both", help="""
747    Which dimensions the zoom-out tool is constrained to act in. By default the
748    zoom-out tool will zoom in any dimension, but can be configured to only
749    zoom horizontally across the width of the plot, or vertically across the
750    height of the plot.
751    """)
752
753    factor = Percent(default=0.1, help="""
754    Percentage to zoom for each click of the zoom-in tool.
755    """)
756
757class BoxSelectTool(Drag, SelectTool):
758    ''' *toolbar icon*: |box_select_icon|
759
760    The box selection tool allows users to make selections on a Plot by showing
761    a rectangular region by dragging the mouse or a finger over the plot area.
762    The end of the drag event indicates the selection region is ready.
763
764    See :ref:`userguide_styling_selected_unselected_glyphs` for information
765    on styling selected and unselected glyphs.
766
767
768    .. |box_select_icon| image:: /_images/icons/BoxSelect.png
769        :height: 24px
770
771    '''
772
773    select_every_mousemove = Bool(False, help="""
774    Whether a selection computation should happen on every mouse event, or only
775    once, when the selection region is completed. Default: False
776    """)
777
778    dimensions = Enum(Dimensions, default="both", help="""
779    Which dimensions the box selection is to be free in. By default, users may
780    freely draw selections boxes with any dimensions. If only "width" is set,
781    the box will be constrained to span the entire vertical space of the plot,
782    only the horizontal dimension can be controlled. If only "height" is set,
783    the box will be constrained to span the entire horizontal space of the
784    plot, and the vertical dimension can be controlled.
785    """)
786
787    overlay = Instance(BoxAnnotation, default=DEFAULT_BOX_OVERLAY, help="""
788    A shaded annotation drawn to indicate the selection region.
789    """)
790
791    origin = Enum("corner", "center", default="corner", help="""
792    Indicates whether the rectangular selection area should originate from a corner
793    (top-left or bottom-right depending on direction) or the center of the box.
794    """)
795
796DEFAULT_POLY_OVERLAY = lambda: PolyAnnotation(
797    syncable=False,
798    level="overlay",
799    xs_units="screen",
800    ys_units="screen",
801    fill_color="lightgrey",
802    fill_alpha=0.5,
803    line_color="black",
804    line_alpha=1.0,
805    line_width=2,
806    line_dash=[4, 4]
807)
808
809class LassoSelectTool(Drag, SelectTool):
810    ''' *toolbar icon*: |lasso_select_icon|
811
812    The lasso selection tool allows users to make selections on a Plot by
813    indicating a free-drawn "lasso" region by dragging the mouse or a finger
814    over the plot region. The end of the drag event indicates the selection
815    region is ready.
816
817    See :ref:`userguide_styling_selected_unselected_glyphs` for information
818    on styling selected and unselected glyphs.
819
820    .. note::
821        Selections can be comprised of multiple regions, even those made by
822        different selection tools. Hold down the <<shift>> key while making a
823        selection to append the new selection to any previous selection that
824        might exist.
825
826    .. |lasso_select_icon| image:: /_images/icons/LassoSelect.png
827        :height: 24px
828
829    '''
830
831    select_every_mousemove = Bool(True, help="""
832    Whether a selection computation should happen on every mouse event, or only
833    once, when the selection region is completed.
834    """)
835
836    overlay = Instance(PolyAnnotation, default=DEFAULT_POLY_OVERLAY, help="""
837    A shaded annotation drawn to indicate the selection region.
838    """)
839
840class PolySelectTool(Tap, SelectTool):
841    ''' *toolbar icon*: |poly_select_icon|
842
843    The polygon selection tool allows users to make selections on a
844    Plot by indicating a polygonal region with mouse clicks. single
845    clicks (or taps) add successive points to the definition of the
846    polygon, and a double click (or tap) indicates the selection
847    region is ready.
848
849    See :ref:`userguide_styling_selected_unselected_glyphs` for information
850    on styling selected and unselected glyphs.
851
852    .. note::
853        Selections can be comprised of multiple regions, even those
854        made by different selection tools. Hold down the <<shift>> key
855        while making a selection to append the new selection to any
856        previous selection that might exist.
857
858    .. |poly_select_icon| image:: /_images/icons/PolygonSelect.png
859        :height: 24px
860
861    '''
862
863    overlay = Instance(PolyAnnotation, default=DEFAULT_POLY_OVERLAY, help="""
864    A shaded annotation drawn to indicate the selection region.
865    """)
866
867class CustomJSHover(Model):
868    ''' Define a custom formatter to apply to a hover tool field.
869
870    This model can be configured with JavaScript code to format hover tooltips.
871    The JavaScript code has access to the current value to format, some special
872    variables, and any format configured on the tooltip. The variable ``value``
873    will contain the untransformed value. The variable ``special_vars`` will
874    provide a dict with the following contents:
875
876    * ``x`` data-space x-coordinate of the mouse
877    * ``y`` data-space y-coordinate of the mouse
878    * ``sx`` screen-space x-coordinate of the mouse
879    * ``sy`` screen-space y-coordinate of the mouse
880    * ``data_x`` data-space x-coordinate of the hovered glyph
881    * ``data_y`` data-space y-coordinate of the hovered glyph
882    * ``indices`` column indices of all currently hovered glyphs
883    * ``name`` value of the ``name`` property of the hovered glyph renderer
884
885    If the hover is over a "multi" glyph such as ``Patches`` or ``MultiLine``
886    then a ``segment_index`` key will also be present.
887
888    Finally, the value of the format passed in the tooltip specification is
889    available as the ``format`` variable.
890
891    Example:
892
893        As an example, the following code adds a custom formatter to format
894        WebMercator northing coordinates (in meters) as a latitude:
895
896        .. code-block:: python
897
898            lat_custom = CustomJSHover(code="""
899                var projections = Bokeh.require("core/util/projections");
900                var x = special_vars.x
901                var y = special_vars.y
902                var coords = projections.wgs84_mercator.invert(x, y)
903                return "" + coords[1]
904            """)
905
906            p.add_tools(HoverTool(
907                tooltips=[( 'lat','@y{custom}' )],
908                formatters={'@y':lat_custom}
909            ))
910
911    .. warning::
912        The explicit purpose of this Bokeh Model is to embed *raw JavaScript
913        code* for a browser to execute. If any part of the code is derived
914        from untrusted user inputs, then you must take appropriate care to
915        sanitize the user input prior to passing to Bokeh.
916
917    '''
918
919    args = Dict(String, Instance(Model), help="""
920    A mapping of names to Bokeh plot objects. These objects are made available
921    to the callback code snippet as the values of named parameters to the
922    callback.
923    """)
924
925    code = String(default="", help="""
926    A snippet of JavaScript code to transform a single value. The variable
927    ``value`` will contain the untransformed value and can be expected to be
928    present in the function namespace at render time. Additionally, the
929    variable ``special_vars`` will be available, and will provide a dict
930    with the following contents:
931
932    * ``x`` data-space x-coordinate of the mouse
933    * ``y`` data-space y-coordinate of the mouse
934    * ``sx`` screen-space x-coordinate of the mouse
935    * ``sy`` screen-space y-coordinate of the mouse
936    * ``data_x`` data-space x-coordinate of the hovered glyph
937    * ``data_y`` data-space y-coordinate of the hovered glyph
938    * ``indices`` column indices of all currently hovered glyphs
939
940    If the hover is over a "multi" glyph such as ``Patches`` or ``MultiLine``
941    then a ``segment_index`` key will also be present.
942
943    Finally, the value of the format passed in the tooltip specification is
944    available as the ``format`` variable.
945
946    The snippet will be made into the body of a function and therefore requires
947    a return statement.
948
949    Example:
950
951        .. code-block:: javascript
952
953            code = '''
954            return value + " total"
955            '''
956    """)
957
958class HoverTool(InspectTool):
959    ''' *toolbar icon*: |hover_icon|
960
961    The hover tool is a passive inspector tool. It is generally on at all
962    times, but can be configured in the inspector's menu associated with the
963    *toolbar icon* shown above.
964
965    By default, the hover tool displays informational tooltips whenever the
966    cursor is directly over a glyph. The data to show comes from the glyph's
967    data source, and what to display is configurable with the ``tooltips``
968    property that maps display names to columns in the data source, or to
969    special known variables.
970
971    Here is an example of how to configure and use the hover tool::
972
973        # Add tooltip (name, field) pairs to the tool. See below for a
974        # description of possible field values.
975        hover.tooltips = [
976            ("index", "$index"),
977            ("(x,y)", "($x, $y)"),
978            ("radius", "@radius"),
979            ("fill color", "$color[hex, swatch]:fill_color"),
980            ("fill color", "$color[hex]:fill_color"),
981            ("fill color", "$color:fill_color"),
982            ("fill color", "$swatch:fill_color"),
983            ("foo", "@foo"),
984            ("bar", "@bar"),
985            ("baz", "@baz{safe}"),
986            ("total", "@total{$0,0.00}"
987        ]
988
989    You can also supply a ``Callback`` to the ``HoverTool``, to build custom
990    interactions on hover. In this case you may want to turn the tooltips
991    off by setting ``tooltips=None``.
992
993    .. warning::
994        When supplying a callback or custom template, the explicit intent
995        of this Bokeh Model is to embed *raw HTML and  JavaScript code* for
996        a browser to execute. If any part of the code is derived from untrusted
997        user inputs, then you must take appropriate care to sanitize the user
998        input prior to passing to Bokeh.
999
1000    Hover tool does not currently work with the following glyphs:
1001
1002        .. hlist::
1003            :columns: 3
1004
1005            * annulus
1006            * arc
1007            * bezier
1008            * image_url
1009            * oval
1010            * patch
1011            * quadratic
1012            * ray
1013            * step
1014            * text
1015
1016    .. |hover_icon| image:: /_images/icons/Hover.png
1017        :height: 24px
1018
1019    '''
1020
1021    names = List(String, help="""
1022    A list of names to query for. If set, only renderers that have a matching
1023    value for their ``name`` attribute will be used.
1024
1025    .. note:
1026        This property is deprecated and will be removed in bokeh 3.0.
1027
1028    """)
1029
1030    renderers = Either(Auto, List(Instance(DataRenderer)), default="auto", help="""
1031    An explicit list of renderers to hit test against. If unset, defaults to
1032    all renderers on a plot.
1033    """)
1034
1035    callback = Nullable(Instance(Callback), help="""
1036    A callback to run in the browser whenever the input's value changes. The
1037    ``cb_data`` parameter that is available to the Callback code will contain two
1038    ``HoverTool`` specific fields:
1039
1040    :index: object containing the indices of the hovered points in the data source
1041    :geometry: object containing the coordinates of the hover cursor
1042    """)
1043
1044    tooltips = Either(Null, String, List(Tuple(String, String)),
1045            default=[
1046                ("index","$index"),
1047                ("data (x, y)","($x, $y)"),
1048                ("screen (x, y)","($sx, $sy)"),
1049            ], help="""
1050    The (name, field) pairs describing what the hover tool should
1051    display when there is a hit.
1052
1053    Field names starting with "@" are interpreted as columns on the
1054    data source. For instance, "@temp" would look up values to display
1055    from the "temp" column of the data source.
1056
1057    Field names starting with "$" are special, known fields:
1058
1059    :$index: index of hovered point in the data source
1060    :$name: value of the ``name`` property of the hovered glyph renderer
1061    :$x: x-coordinate under the cursor in data space
1062    :$y: y-coordinate under the cursor in data space
1063    :$sx: x-coordinate under the cursor in screen (canvas) space
1064    :$sy: y-coordinate under the cursor in screen (canvas) space
1065    :$color: color data from data source, with the syntax:
1066        ``$color[options]:field_name``. The available options
1067        are: ``hex`` (to display the color as a hex value), ``swatch``
1068        (color data from data source displayed as a small color box)
1069    :$swatch: color data from data source displayed as a small color box
1070
1071    Field names that begin with ``@`` are associated with columns in a
1072    ``ColumnDataSource``. For instance the field name ``"@price"`` will
1073    display values from the ``"price"`` column whenever a hover is triggered.
1074    If the hover is for the 17th glyph, then the hover tooltip will
1075    correspondingly display the 17th price value.
1076
1077    Note that if a column name contains spaces, the it must be supplied by
1078    surrounding it in curly braces, e.g. ``@{adjusted close}`` will display
1079    values from a column named ``"adjusted close"``.
1080
1081    Sometimes (especially with stacked charts) it is desirable to allow the
1082    name of the column be specified indirectly. The field name ``@$name`` is
1083    distinguished in that it will look up the ``name`` field on the hovered
1084    glyph renderer, and use that value as the column name. For instance, if
1085    a user hovers with the name ``"US East"``, then ``@$name`` is equivalent to
1086    ``@{US East}``.
1087
1088    By default, values for fields (e.g. ``@foo``) are displayed in a basic
1089    numeric format. However it is possible to control the formatting of values
1090    more precisely. Fields can be modified by appending a format specified to
1091    the end in curly braces. Some examples are below.
1092
1093    .. code-block:: python
1094
1095        "@foo{0,0.000}"    # formats 10000.1234 as: 10,000.123
1096
1097        "@foo{(.00)}"      # formats -10000.1234 as: (10000.123)
1098
1099        "@foo{($ 0.00 a)}" # formats 1230974 as: $ 1.23 m
1100
1101    Specifying a format ``{safe}`` after a field name will override automatic
1102    escaping of the tooltip data source. Any HTML tags in the data tags will
1103    be rendered as HTML in the resulting HoverTool output. See
1104    :ref:`custom_hover_tooltip` for a more detailed example.
1105
1106    ``None`` is also a valid value for tooltips. This turns off the
1107    rendering of tooltips. This is mostly useful when supplying other
1108    actions on hover via the callback property.
1109
1110    .. note::
1111        The tooltips attribute can also be configured with a mapping type,
1112        e.g. ``dict`` or ``OrderedDict``. However, if a ``dict`` is used,
1113        the visual presentation order is unspecified.
1114
1115    """).accepts(Dict(String, String), lambda d: list(d.items()))
1116
1117    formatters = Dict(String, Either(Enum(TooltipFieldFormatter), Instance(CustomJSHover)), default=lambda: dict(), help="""
1118    Specify the formatting scheme for data source columns, e.g.
1119
1120    .. code-block:: python
1121
1122        tool.formatters = {"@date": "datetime"}
1123
1124    will cause format specifications for the "date" column to be interpreted
1125    according to the "datetime" formatting scheme. The following schemes are
1126    available:
1127
1128    :``"numeral"``:
1129        Provides a wide variety of formats for numbers, currency, bytes, times,
1130        and percentages. The full set of formats can be found in the
1131        |NumeralTickFormatter| reference documentation.
1132
1133    :``"datetime"``:
1134        Provides formats for date and time values. The full set of formats is
1135        listed in the |DatetimeTickFormatter| reference documentation.
1136
1137    :``"printf"``:
1138        Provides formats similar to C-style "printf" type specifiers. See the
1139        |PrintfTickFormatter| reference documentation for complete details.
1140
1141    If no formatter is specified for a column name, the default ``"numeral"``
1142    formatter is assumed.
1143
1144    .. |NumeralTickFormatter| replace:: :class:`~bokeh.models.formatters.NumeralTickFormatter`
1145    .. |DatetimeTickFormatter| replace:: :class:`~bokeh.models.formatters.DatetimeTickFormatter`
1146    .. |PrintfTickFormatter| replace:: :class:`~bokeh.models.formatters.PrintfTickFormatter`
1147
1148    """)
1149
1150    mode = Enum("mouse", "hline", "vline", help="""
1151    Whether to consider hover pointer as a point (x/y values), or a
1152    span on h or v directions.
1153    """)
1154
1155    muted_policy = Enum("show", "ignore",
1156                        default="show", help="""
1157    Whether to avoid showing tooltips on muted glyphs.
1158    """)
1159
1160    point_policy = Enum("snap_to_data", "follow_mouse", "none", help="""
1161    Whether the tooltip position should snap to the "center" (or other anchor)
1162    position of the associated glyph, or always follow the current mouse cursor
1163    position.
1164    """)
1165
1166    line_policy = Enum("prev", "next", "nearest", "interp", "none",
1167                       default="nearest", help="""
1168    When showing tooltips for lines, designates whether the tooltip position
1169    should be the "previous" or "next" points on the line, the "nearest" point
1170    to the current mouse position, or "interpolate" along the line to the
1171    current mouse position.
1172    """)
1173
1174    anchor = Enum(Anchor, default="center", help="""
1175    If point policy is set to `"snap_to_data"`, `anchor` defines the attachment
1176    point of a tooltip. The default is to attach to the center of a glyph.
1177    """)
1178
1179    attachment = Enum(TooltipAttachment, help="""
1180    Whether the tooltip should be displayed to the left or right of the cursor
1181    position or above or below it, or if it should be automatically placed
1182    in the horizontal or vertical dimension.
1183    """)
1184
1185    show_arrow = Bool(default=True, help="""
1186    Whether tooltip's arrow should be shown.
1187    """)
1188
1189DEFAULT_HELP_TIP = "Click the question mark to learn more about Bokeh plot tools."
1190DEFAULT_HELP_URL = "https://docs.bokeh.org/en/latest/docs/user_guide/tools.html"
1191
1192class HelpTool(ActionTool):
1193    ''' A button tool to provide a "help" link to users.
1194
1195    The hover text can be customized through the ``help_tooltip`` attribute
1196    and the redirect site overridden as well.
1197
1198    '''
1199
1200    def __init__(self, *args, **kwargs):
1201        help_tooltip = kwargs.pop("help_tooltip", None)
1202        if help_tooltip is not None:
1203            deprecated((2, 3, 0), "HelpTool.help_tooltip", "HelpTool.description")
1204            kwargs["description"] = help_tooltip
1205        super().__init__(*args, **kwargs)
1206
1207    @property
1208    def help_tooltip(self):
1209        deprecated((2, 3, 0), "HelpTool.help_tooltip", "HelpTool.description")
1210        return self.description
1211    @help_tooltip.setter
1212    def help_tooltip(self, description):
1213        deprecated((2, 3, 0), "HelpTool.help_tooltip", "HelpTool.description")
1214        self.description = description
1215
1216    description = Override(default=DEFAULT_HELP_TIP)
1217
1218    redirect = String(default=DEFAULT_HELP_URL, help="""
1219    Site to be redirected through upon click.
1220    """)
1221
1222class UndoTool(ActionTool):
1223    ''' *toolbar icon*: |undo_icon|
1224
1225    Undo tool allows to restore previous state of the plot.
1226
1227    .. |undo_icon| image:: /_images/icons/Undo.png
1228        :height: 24px
1229
1230    '''
1231
1232class RedoTool(ActionTool):
1233    ''' *toolbar icon*: |redo_icon|
1234
1235    Redo tool reverses the last action performed by undo tool.
1236
1237    .. |redo_icon| image:: /_images/icons/Redo.png
1238        :height: 24px
1239
1240    '''
1241
1242@abstract
1243class EditTool(GestureTool):
1244    ''' A base class for all interactive draw tool types.
1245
1246    '''
1247
1248    def __init__(self, *args, **kwargs):
1249        custom_tooltip = kwargs.pop("custom_tooltip", None)
1250        if custom_tooltip is not None:
1251            deprecated((2, 3, 0), "EditTool.custom_tooltip", "EditTool.description")
1252            kwargs["description"] = custom_tooltip
1253        super().__init__(*args, **kwargs)
1254
1255    @property
1256    def custom_tooltip(self):
1257        deprecated((2, 3, 0), "EditTool.custom_tooltip", "EditTool.description")
1258        return self.description
1259    @custom_tooltip.setter
1260    def custom_tooltip(self, description):
1261        deprecated((2, 3, 0), "EditTool.custom_tooltip", "EditTool.description")
1262        self.description = description
1263
1264    empty_value = NonNullable(Either(Bool, Int, Float, Date, Datetime, Color, String), help="""
1265    Defines the value to insert on non-coordinate columns when a new
1266    glyph is inserted into the ``ColumnDataSource`` columns, e.g. when a
1267    circle glyph defines 'x', 'y' and 'color' columns, adding a new
1268    point will add the x and y-coordinates to 'x' and 'y' columns and
1269    the color column will be filled with the defined empty value.
1270    """)
1271
1272    custom_icon = Nullable(Image, help="""
1273    An icon to display in the toolbar.
1274
1275    The icon can provided as a string filename for an image, a PIL ``Image``
1276    object, or an RGB(A) NumPy array.
1277    """)
1278
1279    renderers = List(Instance(GlyphRenderer), help="""
1280    An explicit list of renderers corresponding to scatter glyphs that may
1281    be edited.
1282    """)
1283
1284@abstract
1285class PolyTool(EditTool):
1286    ''' A base class for polygon draw/edit tools. '''
1287
1288    vertex_renderer = Nullable(Instance(GlyphRenderer), help="""
1289    The renderer used to render the vertices of a selected line or polygon.
1290    """)
1291
1292    @error(INCOMPATIBLE_POLY_EDIT_VERTEX_RENDERER)
1293    def _check_compatible_vertex_renderer(self):
1294        if self.vertex_renderer is None:
1295            return
1296        glyph = self.vertex_renderer.glyph
1297        if not isinstance(glyph, XYGlyph):
1298            return "glyph type %s found." % type(glyph).__name__
1299
1300class BoxEditTool(EditTool, Drag, Tap):
1301    ''' *toolbar icon*: |box_edit_icon|
1302
1303    Allows drawing, dragging and deleting ``Rect`` glyphs on one or more
1304    renderers by editing the underlying ``ColumnDataSource`` data. Like other
1305    drawing tools, the renderers that are to be edited must be supplied
1306    explicitly as a list. When drawing a new box the data will always be added
1307    to the ``ColumnDataSource`` on the first supplied renderer.
1308
1309    The tool will modify the columns on the data source corresponding to the
1310    ``x``, ``y``, ``width`` and ``height`` values of the glyph. Any additional
1311    columns in the data source will be padded with ``empty_value``, when adding
1312    a new box.
1313
1314    The supported actions include:
1315
1316    * Add box: Hold shift then click and drag anywhere on the plot or double
1317      tap once to start drawing, move the mouse and double tap again to finish
1318      drawing.
1319
1320    * Move box: Click and drag an existing box, the box will be dropped once
1321      you let go of the mouse button.
1322
1323    * Delete box: Tap a box to select it then press <<backspace>> key while the
1324      mouse is within the plot area.
1325
1326    To **Move** or **Delete** multiple boxes at once:
1327
1328    * Move selection: Select box(es) with <<shift>>+tap (or another selection
1329      tool) then drag anywhere on the plot. Selecting and then dragging on a
1330      specific box will move both.
1331
1332    * Delete selection: Select box(es) with <<shift>>+tap (or another selection
1333      tool) then press <<backspace>> while the mouse is within the plot area.
1334
1335    .. |box_edit_icon| image:: /_images/icons/BoxEdit.png
1336        :height: 24px
1337    '''
1338
1339    dimensions = Enum(Dimensions, default="both", help="""
1340    Which dimensions the box drawing is to be free in. By default, users may
1341    freely draw boxes with any dimensions. If only "width" is set, the box will
1342    be constrained to span the entire vertical space of the plot, only the
1343    horizontal dimension can be controlled. If only "height" is set, the box
1344    will be constrained to span the entire horizontal space of the plot, and the
1345    vertical dimension can be controlled.
1346    """)
1347
1348    num_objects = Int(default=0, help="""
1349    Defines a limit on the number of boxes that can be drawn. By default there
1350    is no limit on the number of objects, but if enabled the oldest drawn box
1351    will be dropped to make space for the new box being added.
1352    """)
1353
1354    @error(INCOMPATIBLE_BOX_EDIT_RENDERER)
1355    def _check_compatible_renderers(self):
1356        incompatible_renderers = []
1357        for renderer in self.renderers:
1358            if not isinstance(renderer.glyph, Rect):
1359                incompatible_renderers.append(renderer)
1360        if incompatible_renderers:
1361            glyph_types = ', '.join(type(renderer.glyph).__name__ for renderer in incompatible_renderers)
1362            return "%s glyph type(s) found." % glyph_types
1363
1364class PointDrawTool(EditTool, Drag, Tap):
1365    ''' *toolbar icon*: |point_draw_icon|
1366
1367    The PointDrawTool allows adding, dragging and deleting point-like glyphs
1368    (i.e subclasses of``XYGlyph``) on one or more renderers by editing the
1369    underlying ``ColumnDataSource`` data. Like other drawing tools, the
1370    renderers that are to be edited must be supplied explicitly as a list. Any
1371    newly added points will be inserted on the ``ColumnDataSource`` of the
1372    first supplied renderer.
1373
1374    The tool will modify the columns on the data source corresponding to the
1375    ``x`` and ``y`` values of the glyph. Any additional columns in the data
1376    source will be padded with the given ``empty_value`` when adding a new
1377    point.
1378
1379    .. note::
1380        The data source updates will trigger data change events continuously
1381        throughout the edit operations on the BokehJS side. In Bokeh server
1382        apps, the data source will only be synced once, when the edit operation
1383        finishes.
1384
1385    The supported actions include:
1386
1387    * Add point: Tap anywhere on the plot
1388
1389    * Move point: Tap and drag an existing point, the point will be
1390      dropped once you let go of the mouse button.
1391
1392    * Delete point: Tap a point to select it then press <<backspace>>
1393      key while the mouse is within the plot area.
1394
1395    .. |point_draw_icon| image:: /_images/icons/PointDraw.png
1396        :height: 24px
1397    '''
1398
1399    add = Bool(default=True, help="""
1400    Enables adding of new points on tap events.
1401    """)
1402
1403    drag = Bool(default=True, help="""
1404    Enables dragging of existing points on pan events.
1405    """)
1406
1407    num_objects = Int(default=0, help="""
1408    Defines a limit on the number of points that can be drawn. By default there
1409    is no limit on the number of objects, but if enabled the oldest drawn point
1410    will be dropped to make space for the new point.
1411    """)
1412
1413    @error(INCOMPATIBLE_POINT_DRAW_RENDERER)
1414    def _check_compatible_renderers(self):
1415        incompatible_renderers = []
1416        for renderer in self.renderers:
1417            if not isinstance(renderer.glyph, XYGlyph):
1418                incompatible_renderers.append(renderer)
1419        if incompatible_renderers:
1420            glyph_types = ', '.join(type(renderer.glyph).__name__ for renderer in incompatible_renderers)
1421            return "%s glyph type(s) found." % glyph_types
1422
1423class PolyDrawTool(PolyTool, Drag, Tap):
1424    ''' *toolbar icon*: |poly_draw_icon|
1425
1426    The PolyDrawTool allows drawing, selecting and deleting ``Patches`` and
1427    ``MultiLine`` glyphs on one or more renderers by editing the underlying
1428    ``ColumnDataSource`` data. Like other drawing tools, the renderers that
1429    are to be edited must be supplied explicitly.
1430
1431    The tool will modify the columns on the data source corresponding to the
1432    ``xs`` and ``ys`` values of the glyph. Any additional columns in the data
1433    source will be padded with the declared ``empty_value``, when adding a new
1434    point.
1435
1436    If a ``vertex_renderer`` with an point-like glyph is supplied then the
1437    ``PolyDrawTool`` will use it to display the vertices of the multi-lines or
1438    patches on all supplied renderers. This also enables the ability to snap
1439    to existing vertices while drawing.
1440
1441    The supported actions include:
1442
1443    * Add patch or multi-line: Double tap to add the first vertex, then use tap
1444      to add each subsequent vertex, to finalize the draw action double tap to
1445      insert the final vertex or press the <<esc> key.
1446
1447    * Move patch or ulti-line: Tap and drag an existing patch/multi-line, the
1448      point will be dropped once you let go of the mouse button.
1449
1450    * Delete patch or multi-line: Tap a patch/multi-line to select it then
1451      press <<backspace>> key while the mouse is within the plot area.
1452
1453    .. |poly_draw_icon| image:: /_images/icons/PolyDraw.png
1454        :height: 24px
1455    '''
1456
1457    drag = Bool(default=True, help="""
1458    Enables dragging of existing patches and multi-lines on pan events.
1459    """)
1460
1461    num_objects = Int(default=0, help="""
1462    Defines a limit on the number of patches or multi-lines that can be drawn.
1463    By default there is no limit on the number of objects, but if enabled the
1464    oldest drawn patch or multi-line will be dropped to make space for the new
1465    patch or multi-line.
1466    """)
1467
1468    @error(INCOMPATIBLE_POLY_DRAW_RENDERER)
1469    def _check_compatible_renderers(self):
1470        incompatible_renderers = []
1471        for renderer in self.renderers:
1472            if not isinstance(renderer.glyph, (MultiLine, Patches)):
1473                incompatible_renderers.append(renderer)
1474        if incompatible_renderers:
1475            glyph_types = ', '.join(type(renderer.glyph).__name__ for renderer in incompatible_renderers)
1476            return "%s glyph type(s) found." % glyph_types
1477
1478class FreehandDrawTool(EditTool, Drag, Tap):
1479    ''' *toolbar icon*: |freehand_draw_icon|
1480
1481    Allows freehand drawing of ``Patches`` and ``MultiLine`` glyphs. The glyph
1482    to draw may be defined via the ``renderers`` property.
1483
1484    The tool will modify the columns on the data source corresponding to the
1485    ``xs`` and ``ys`` values of the glyph. Any additional columns in the data
1486    source will be padded with the declared ``empty_value``, when adding a new
1487    point.
1488
1489    The supported actions include:
1490
1491    * Draw vertices: Click and drag to draw a line
1492
1493    * Delete patch/multi-line: Tap a patch/multi-line to select it then press
1494      <<backspace>> key while the mouse is within the plot area.
1495
1496    .. |freehand_draw_icon| image:: /_images/icons/FreehandDraw.png
1497        :height: 24px
1498    '''
1499
1500    num_objects = Int(default=0, help="""
1501    Defines a limit on the number of patches or multi-lines that can be drawn.
1502    By default there is no limit on the number of objects, but if enabled the
1503    oldest drawn patch or multi-line will be overwritten when the limit is
1504    reached.
1505    """)
1506
1507    @error(INCOMPATIBLE_POLY_DRAW_RENDERER)
1508    def _check_compatible_renderers(self):
1509        incompatible_renderers = []
1510        for renderer in self.renderers:
1511            if not isinstance(renderer.glyph, (MultiLine, Patches)):
1512                incompatible_renderers.append(renderer)
1513        if incompatible_renderers:
1514            glyph_types = ', '.join(type(renderer.glyph).__name__ for renderer in incompatible_renderers)
1515            return "%s glyph type(s) found." % glyph_types
1516
1517class PolyEditTool(PolyTool, Drag, Tap):
1518    ''' *toolbar icon*: |poly_edit_icon|
1519
1520    The PolyEditTool allows editing the vertices of one or more ``Patches`` or
1521    ``MultiLine`` glyphs. Glyphs to be edited are defined via the ``renderers``
1522    property and a renderer for the vertices is set via the ``vertex_renderer``
1523    property (must render a point-like Glyph (a subclass of ``XYGlyph``).
1524
1525    The tool will modify the columns on the data source corresponding to the
1526    ``xs`` and ``ys`` values of the glyph. Any additional columns in the data
1527    source will be padded with the declared``empty_value``, when adding a new
1528    point.
1529
1530    The supported actions include:
1531
1532    * Show vertices: Double tap an existing patch or multi-line
1533
1534    * Add vertex: Double tap an existing vertex to select it, the tool will
1535      draw the next point, to add it tap in a new location. To finish editing
1536      and add a point double tap otherwise press the <<esc> key to cancel.
1537
1538    * Move vertex: Drag an existing vertex and let go of the mouse button to
1539      release it.
1540
1541    * Delete vertex: After selecting one or more vertices press <<backspace>>
1542      while the mouse cursor is within the plot area.
1543
1544    .. |poly_edit_icon| image:: /_images/icons/PolyEdit.png
1545        :height: 24px
1546    '''
1547
1548    @error(INCOMPATIBLE_POLY_EDIT_RENDERER)
1549    def _check_compatible_renderers(self):
1550        incompatible_renderers = []
1551        for renderer in self.renderers:
1552            if not isinstance(renderer.glyph, (MultiLine, Patches)):
1553                incompatible_renderers.append(renderer)
1554        if incompatible_renderers:
1555            glyph_types = ', '.join(type(renderer.glyph).__name__
1556                                    for renderer in incompatible_renderers)
1557            return "%s glyph type(s) found." % glyph_types
1558
1559
1560class LineEditTool(EditTool, Drag, Tap):
1561    ''' *toolbar icon*: |line_edit_icon|
1562
1563    The LineEditTool allows editing the intersection points of one or more ``Line`` glyphs.
1564    Glyphs to be edited are defined via the ``renderers``
1565    property and a renderer for the intersections is set via the ``intersection_renderer``
1566    property (must render a point-like Glyph (a subclass of ``XYGlyph``).
1567
1568    The tool will modify the columns on the data source corresponding to the
1569    ``x`` and ``y`` values of the glyph. Any additional columns in the data
1570    source will be padded with the declared``empty_value``, when adding a new
1571    point.
1572
1573    The supported actions include:
1574
1575    * Show intersections: Double tap an existing line
1576
1577    * Move point: Drag an existing point and let go of the mouse button to
1578      release it.
1579
1580    .. |line_edit_icon| image:: /_images/icons/LineEdit.png
1581        :height: 24px
1582     '''
1583
1584    intersection_renderer = Instance(GlyphRenderer, help="""
1585    The renderer used to render the intersections of a selected line
1586    """)
1587
1588    dimensions = Enum(Dimensions, default="both", help="""
1589    Which dimensions this edit tool is constrained to act in. By default
1590    the line edit tool allows moving points in any dimension, but can be
1591    configured to only allow horizontal movement across the width of the
1592    plot, or vertical across the height of the plot.
1593    """)
1594
1595    @error(INCOMPATIBLE_LINE_EDIT_INTERSECTION_RENDERER)
1596    def _check_compatible_intersection_renderer(self):
1597        glyph = self.intersection_renderer.glyph
1598        if not isinstance(glyph, LineGlyph):
1599            return "glyph type %s found." % type(glyph).__name__
1600
1601    @error(INCOMPATIBLE_LINE_EDIT_RENDERER)
1602    def _check_compatible_renderers(self):
1603        incompatible_renderers = []
1604        for renderer in self.renderers:
1605            if not isinstance(renderer.glyph, (Line,)):
1606                incompatible_renderers.append(renderer)
1607        if incompatible_renderers:
1608            glyph_types = ', '.join(type(renderer.glyph).__name__
1609                                    for renderer in incompatible_renderers)
1610            return "%s glyph type(s) found." % glyph_types
1611
1612#
1613#-----------------------------------------------------------------------------
1614# Dev API
1615#-----------------------------------------------------------------------------
1616
1617#-----------------------------------------------------------------------------
1618# Private API
1619#-----------------------------------------------------------------------------
1620
1621#-----------------------------------------------------------------------------
1622# Code
1623#-----------------------------------------------------------------------------
1624
1625Tool.register_alias("pan", lambda: PanTool(dimensions="both"))
1626Tool.register_alias("xpan", lambda: PanTool(dimensions="width"))
1627Tool.register_alias("ypan", lambda: PanTool(dimensions="height"))
1628Tool.register_alias("xwheel_pan", lambda: WheelPanTool(dimension="width"))
1629Tool.register_alias("ywheel_pan", lambda: WheelPanTool(dimension="height"))
1630Tool.register_alias("wheel_zoom", lambda: WheelZoomTool(dimensions="both"))
1631Tool.register_alias("xwheel_zoom", lambda: WheelZoomTool(dimensions="width"))
1632Tool.register_alias("ywheel_zoom", lambda: WheelZoomTool(dimensions="height"))
1633Tool.register_alias("zoom_in", lambda: ZoomInTool(dimensions="both"))
1634Tool.register_alias("xzoom_in", lambda: ZoomInTool(dimensions="width"))
1635Tool.register_alias("yzoom_in", lambda: ZoomInTool(dimensions="height"))
1636Tool.register_alias("zoom_out", lambda: ZoomOutTool(dimensions="both"))
1637Tool.register_alias("xzoom_out", lambda: ZoomOutTool(dimensions="width"))
1638Tool.register_alias("yzoom_out", lambda: ZoomOutTool(dimensions="height"))
1639Tool.register_alias("click", lambda: TapTool(behavior="inspect"))
1640Tool.register_alias("tap", lambda: TapTool())
1641Tool.register_alias("doubletap", lambda: TapTool(gesture="doubletap"))
1642Tool.register_alias("crosshair", lambda: CrosshairTool())
1643Tool.register_alias("box_select", lambda: BoxSelectTool())
1644Tool.register_alias("xbox_select", lambda: BoxSelectTool(dimensions="width"))
1645Tool.register_alias("ybox_select", lambda: BoxSelectTool(dimensions="height"))
1646Tool.register_alias("poly_select", lambda: PolySelectTool())
1647Tool.register_alias("lasso_select", lambda: LassoSelectTool())
1648Tool.register_alias("box_zoom", lambda: BoxZoomTool(dimensions="both"))
1649Tool.register_alias("xbox_zoom", lambda: BoxZoomTool(dimensions="width"))
1650Tool.register_alias("ybox_zoom", lambda: BoxZoomTool(dimensions="height"))
1651Tool.register_alias("save", lambda: SaveTool())
1652Tool.register_alias("undo", lambda: UndoTool())
1653Tool.register_alias("redo", lambda: RedoTool())
1654Tool.register_alias("reset", lambda: ResetTool())
1655Tool.register_alias("help", lambda: HelpTool())
1656Tool.register_alias("box_edit", lambda: BoxEditTool())
1657Tool.register_alias("line_edit", lambda: LineEditTool())
1658Tool.register_alias("point_draw", lambda: PointDrawTool())
1659Tool.register_alias("poly_draw", lambda: PolyDrawTool())
1660Tool.register_alias("poly_edit", lambda: PolyEditTool())
1661Tool.register_alias("freehand_draw", lambda: FreehandDrawTool())
1662Tool.register_alias("hover", lambda: HoverTool(tooltips=[
1663    ("index", "$index"),
1664    ("data (x, y)", "($x, $y)"),
1665    ("screen (x, y)", "($sx, $sy)"),
1666]))
1667