-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
tools.py
1905 lines (1489 loc) · 69.9 KB
/
tools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2024, Anaconda, Inc., and Bokeh Contributors.
# All rights reserved.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------
''' Bokeh comes with a number of interactive tools.
There are five types of tool interactions:
.. hlist::
:columns: 5
* Pan/Drag
* Click/Tap
* Scroll/Pinch
* Actions
* Inspectors
For the first three comprise the category of gesture tools, and only
one tool for each gesture can be active at any given time. The active
tool is indicated on the toolbar by a highlight next to the tool.
Actions are immediate or modal operations that are only activated when
their button in the toolbar is pressed. Inspectors are passive tools that
merely report information or annotate the plot in some way, and may
always be active regardless of what other tools are currently active.
'''
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import annotations
import logging # isort:skip
log = logging.getLogger(__name__)
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Standard library imports
import difflib
import typing as tp
from math import nan
from typing import Literal
# Bokeh imports
from ..core.enums import (
Anchor,
Dimension,
Dimensions,
KeyModifier,
SelectionMode,
ToolIcon,
TooltipAttachment,
TooltipFieldFormatter,
)
from ..core.has_props import abstract
from ..core.properties import (
Alpha,
AnyRef,
Auto,
Bool,
Color,
Date,
Datetime,
DeprecatedAlias,
Dict,
Either,
Enum,
Float,
Image,
Instance,
InstanceDefault,
Int,
List,
NonNegative,
Null,
Nullable,
Override,
Percent,
Regex,
Seq,
String,
Struct,
Tuple,
TypeOfAttr,
)
from ..core.property.struct import Optional
from ..core.validation import error
from ..core.validation.errors import NO_RANGE_TOOL_RANGES
from ..model import Model
from ..util.strings import nice_join
from .annotations import BoxAnnotation, PolyAnnotation, Span
from .callbacks import Callback
from .coordinates import (
FrameBottom,
FrameLeft,
FrameRight,
FrameTop,
)
from .dom import Template
from .glyphs import (
HStrip,
Line,
LineGlyph,
LRTBGlyph,
MultiLine,
Patches,
Rect,
VStrip,
XYGlyph,
)
from .ranges import Range
from .renderers import DataRenderer, GlyphRenderer
from .ui import UIElement
#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------
__all__ = (
'ActionTool',
'BoxEditTool',
'BoxSelectTool',
'BoxZoomTool',
'CopyTool',
'CrosshairTool',
'CustomAction',
'CustomJSHover',
'Drag',
'EditTool',
'FreehandDrawTool',
'FullscreenTool',
'HelpTool',
'HoverTool',
'InspectTool',
'GestureTool',
'LassoSelectTool',
'LineEditTool',
'PanTool',
'PointDrawTool',
'PolyDrawTool',
'PolyEditTool',
'PolySelectTool',
'RangeTool',
'RedoTool',
'ResetTool',
'SaveTool',
'Scroll',
'ExamineTool',
'Tap',
'TapTool',
'Tool',
'ToolProxy',
'Toolbar',
'UndoTool',
'WheelPanTool',
'WheelZoomTool',
'ZoomInTool',
'ZoomOutTool',
)
#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------
def GlyphRendererOf(*types: type[Model]):
""" Constraints ``GlyphRenderer.glyph`` to the given type or types. """
return TypeOfAttr(Instance(GlyphRenderer), "glyph", Either(*(Instance(tp) for tp in types)))
@abstract
class Tool(Model):
''' A base class for all interactive tool types.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
#Image has to be first! see #12775, temporary fix
icon = Nullable(Either(Image, Enum(ToolIcon), Regex(r"^\.")), help="""
An icon to display in the toolbar.
The icon can provided as well known tool icon name, a CSS class selector,
a data URI with an ``image/*`` MIME, a path to an image, a PIL ``Image``
object, or an RGB(A) NumPy array. If ``None``, then the intrinsic icon
will be used (may depend on tool's configuration).
""")
description = Nullable(String, help="""
A string describing the purpose of this tool. If not defined, an auto-generated
description will be used. This description will be typically presented in the
user interface as a tooltip.
""")
_known_aliases: tp.ClassVar[dict[str, tp.Callable[[], Tool]]] = {}
@classmethod
def from_string(cls, name: str) -> Tool:
""" Takes a string and returns a corresponding `Tool` instance. """
constructor = cls._known_aliases.get(name)
if constructor is not None:
return constructor()
else:
known_names = cls._known_aliases.keys()
matches, text = difflib.get_close_matches(name.lower(), known_names), "similar"
if not matches:
matches, text = known_names, "possible"
raise ValueError(f"unexpected tool name '{name}', {text} tools are {nice_join(matches)}")
@classmethod
def register_alias(cls, name: str, constructor: tp.Callable[[], Tool]) -> None:
cls._known_aliases[name] = constructor
class ToolProxy(Model):
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
tools = List(Instance(Tool))
active = Bool(default=False)
disabled = Bool(default=False)
@abstract
class ActionTool(Tool):
''' A base class for tools that are buttons in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@abstract
class PlotActionTool(ActionTool):
''' A base class action tools acting on plots.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@abstract
class GestureTool(Tool):
''' A base class for tools that respond to drag events.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@abstract
class Drag(GestureTool):
''' A base class for tools that respond to drag events.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@abstract
class Scroll(GestureTool):
''' A base class for tools that respond to scroll events.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@abstract
class Tap(GestureTool):
''' A base class for tools that respond to tap/click events.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
@abstract
class SelectTool(GestureTool):
''' A base class for tools that perform "selections", e.g. ``BoxSelectTool``.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
renderers = Either(Auto, List(Instance(DataRenderer)), default="auto", help="""
A list of renderers to hit test against. If unset, defaults to
all renderers on a plot.
""")
mode = Enum(SelectionMode, default="replace", help="""
Defines what should happen when a new selection is made. The default
is to replace the existing selection. Other options are to append to
the selection, intersect with it or subtract from it.
""")
@abstract
class RegionSelectTool(SelectTool):
''' Base class for region selection tools (e.g. box, polygon, lasso).
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
continuous = Bool(False, help="""
Whether a selection computation should happen continuously during selection
gestures, or only once when the selection region is completed.
""")
select_every_mousemove = DeprecatedAlias("continuous", since=(3, 1, 0))
persistent = Bool(default=False, help="""
Whether the selection overlay should persist after selection gesture
is completed. This can be paired with setting ``editable = True`` on
the annotation, to allow to modify the selection.
""")
greedy = Bool(default=False, help="""
Defines whether a hit against a glyph requires full enclosure within
the selection region (non-greedy) or only an intersection (greedy)
(i.e. at least one point within the region).
""")
@abstract
class InspectTool(GestureTool):
''' A base class for tools that perform "inspections", e.g. ``HoverTool``.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
toggleable = Bool(True, help="""
Whether an on/off toggle button should appear in the toolbar for this
inspection tool. If ``False``, the viewers of a plot will not be able to
toggle the inspector on or off using the toolbar.
""")
class Toolbar(UIElement):
''' Collect tools to display for a single plot.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
logo = Nullable(Enum("normal", "grey"), default="normal", help="""
What version of the Bokeh logo to display on the toolbar. If
set to None, no logo will be displayed.
""")
autohide = Bool(default=False, help="""
Whether the toolbar will be hidden by default. Default: False.
If True, hides toolbar when cursor is not in canvas.
""")
tools = List(Either(Instance(Tool), Instance(ToolProxy)), help="""
A list of tools to add to the plot.
""")
active_drag: Literal["auto"] | Drag | None = Either(Null, Auto, Instance(Drag), default="auto", help="""
Specify a drag tool to be active when the plot is displayed.
""")
active_inspect: Literal["auto"] | InspectTool | tp.Sequence[InspectTool] | None = \
Either(Null, Auto, Instance(InspectTool), Seq(Instance(InspectTool)), default="auto", help="""
Specify an inspection tool or sequence of inspection tools to be active when
the plot is displayed.
""")
active_scroll: Literal["auto"] | Scroll | None = Either(Null, Auto, Instance(Scroll), default="auto", help="""
Specify a scroll/pinch tool to be active when the plot is displayed.
""")
active_tap: Literal["auto"] | Tap | None = Either(Null, Auto, Instance(Tap), default="auto", help="""
Specify a tap/click tool to be active when the plot is displayed.
""")
active_multi: Literal["auto"] | GestureTool | None = Either(Null, Auto, Instance(GestureTool), default="auto", help="""
Specify an active multi-gesture tool, for instance an edit tool or a range
tool.
Note that activating a multi-gesture tool will deactivate any other gesture
tools as appropriate. For example, if a pan tool is set as the active drag,
and this property is set to a ``BoxEditTool`` instance, the pan tool will
be deactivated (i.e. the multi-gesture tool will take precedence).
""")
class PanTool(Drag):
''' *toolbar icon*: |pan_icon|
The pan tool allows the user to pan a Plot by left-dragging a mouse, or on
touch devices by dragging a finger or stylus, across the plot region.
The pan tool also activates the border regions of a Plot for "single axis"
panning. For instance, dragging in the vertical border or axis will effect
a pan in the vertical direction only, with horizontal dimension kept fixed.
.. |pan_icon| image:: /_images/icons/Pan.png
:height: 24px
:alt: Icon of four arrows meeting in a plus shape representing the pan tool in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
dimensions = Enum(Dimensions, default="both", help="""
Which dimensions the pan tool is constrained to act in. By default
the pan tool will pan in any dimension, but can be configured to only
pan horizontally across the width of the plot, or vertically across the
height of the plot.
""")
# TODO InstanceDefault() doesn't allow for lazy argument evaluation
# DEFAULT_RANGE_OVERLAY = InstanceDefault(BoxAnnotation,
DEFAULT_RANGE_OVERLAY = lambda: BoxAnnotation(
syncable=False,
level="overlay",
visible=True,
editable=True,
propagate_hover=True,
left=nan,
right=nan,
top=nan,
bottom=nan,
left_limit=FrameLeft(),
right_limit=FrameRight(),
top_limit=FrameTop(),
bottom_limit=FrameBottom(),
fill_color="lightgrey",
fill_alpha=0.5,
line_color="black",
line_alpha=1.0,
line_width=0.5,
line_dash=[2, 2],
)
class RangeTool(Tool):
''' *toolbar icon*: |range_icon|
The range tool allows the user to update range objects for either or both
of the x- or y-dimensions by dragging a corresponding shaded annotation to
move it or change its boundaries.
A common use case is to add this tool to a plot with a large fixed range,
but to configure the tool range from a different plot. When the user
manipulates the overlay, the range of the second plot will be updated
automatically.
.. |range_icon| image:: /_images/icons/Range.png
:height: 24px
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
x_range = Nullable(Instance(Range), help="""
A range synchronized to the x-dimension of the overlay. If None, the overlay
will span the entire x-dimension.
""")
y_range = Nullable(Instance(Range), help="""
A range synchronized to the y-dimension of the overlay. If None, the overlay
will span the entire y-dimension.
""")
x_interaction = Bool(default=True, help="""
Whether to respond to horizontal pan motions when an ``x_range`` is present.
By default, when an ``x_range`` is specified, it is possible to adjust the
horizontal position of the range box by panning horizontally inside the
box, or along the top or bottom edge of the box. To disable this, and fix
the range box in place horizontally, set to False. (The box will still
update if the ``x_range`` is updated programmatically.)
""")
y_interaction = Bool(default=True, help="""
Whether to respond to vertical pan motions when a ``y_range`` is present.
By default, when a ``y_range`` is specified, it is possible to adjust the
vertical position of the range box by panning vertically inside the box, or
along the top or bottom edge of the box. To disable this, and fix the range
box in place vertically, set to False. (The box will still update if the
``y_range`` is updated programmatically.)
""")
overlay = Instance(BoxAnnotation, default=DEFAULT_RANGE_OVERLAY, help="""
A shaded annotation drawn to indicate the configured ranges.
""")
@error(NO_RANGE_TOOL_RANGES)
def _check_no_range_tool_ranges(self):
if self.x_range is None and self.y_range is None:
return "At least one of RangeTool.x_range or RangeTool.y_range must be configured"
class WheelPanTool(Scroll):
''' *toolbar icon*: |wheel_pan_icon|
The wheel pan tool allows the user to pan the plot along the configured
dimension using the scroll wheel.
.. |wheel_pan_icon| image:: /_images/icons/WheelPan.png
:height: 24px
:alt: Icon of a mouse shape next to crossed arrows representing the wheel-pan tool in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
dimension = Enum(Dimension, default="width", help="""
Which dimension the wheel pan tool is constrained to act in. By default the
wheel pan tool will pan the plot along the x-axis.
""")
class WheelZoomTool(Scroll):
''' *toolbar icon*: |wheel_zoom_icon|
The wheel zoom tool will zoom the plot in and out, centered on the
current mouse location.
The wheel zoom tool also activates the border regions of a Plot for
"single axis" zooming. For instance, zooming in the vertical border or
axis will effect a zoom in the vertical direction only, with the
horizontal dimension kept fixed.
.. |wheel_zoom_icon| image:: /_images/icons/WheelZoom.png
:height: 24px
:alt: Icon of a mouse shape next to an hourglass representing the wheel-zoom tool in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# ZoomBaseTool common {
dimensions = Enum(Dimensions, default="both", help="""
Which dimensions the wheel zoom tool is constrained to act in. By default
the wheel zoom tool will zoom in any dimension, but can be configured to
only zoom horizontally across the width of the plot, or vertically across
the height of the plot.
""")
renderers = Either(Auto, List(Instance(DataRenderer)), default="auto", help="""
Restrict zoom to ranges used by the provided data renderers. If ``"auto"``
then all ranges provided by the cartesian frame will be used.
""")
level = NonNegative(Int, default=0, help="""
When working with composite scales (sub-coordinates), this property
allows to configure which set of ranges to scale. The default is to
scale top-level (frame) ranges.
""")
# }
maintain_focus = Bool(default=True, help="""
If True, then hitting a range bound in any one dimension will prevent all
further zooming all dimensions. If False, zooming can continue
independently in any dimension that has not yet reached its bounds, even if
that causes overall focus or aspect ratio to change.
""")
zoom_on_axis = Bool(default=True, help="""
Whether scrolling on an axis (outside the central plot area) should zoom
that dimension. If enabled, the behavior of this feature can be configured
with ``zoom_together`` property.
""")
zoom_together = Enum("none", "cross", "all", default="all", help="""
Defines the behavior of the tool when zooming on an axis:
- ``"none"``
zoom only the axis that's being interacted with. Any cross
axes nor any other axes in the dimension of this axis will be affected.
- ``"cross"``
zoom the axis that's being interacted with and its cross
axis, if configured. No other axes in this or cross dimension will be
affected.
- ``"all"``
zoom all axes in the dimension of the axis that's being
interacted with. All cross axes will be unaffected.
""")
speed = Float(default=1/600, help="""
Speed at which the wheel zooms. Default is 1/600. Optimal range is between
0.001 and 0.09. High values will be clipped. Speed may very between browsers.
""")
class CustomAction(ActionTool):
''' Execute a custom action, e.g. ``CustomJS`` callback when a toolbar
icon is activated.
Example:
.. code-block:: python
tool = CustomAction(icon="icon.png",
callback=CustomJS(code='alert("foo")'))
plot.add_tools(tool)
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
description = Override(default="Perform a Custom Action")
callback = Nullable(Instance(Callback), help="""
A Bokeh callback to execute when the custom action icon is activated.
""")
class SaveTool(ActionTool):
''' *toolbar icon*: |save_icon|
The save tool is an action. When activated, the tool opens a download dialog
which allows to save an image reproduction of the plot in PNG format. If
automatic download is not support by a web browser, the tool falls back to
opening the generated image in a new tab or window. User then can manually
save it by right clicking on the image and choosing "Save As" (or similar)
menu item.
.. |save_icon| image:: /_images/icons/Save.png
:height: 24px
:alt: Icon of a floppy disk representing the save tool in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
filename = Nullable(String, help="""
Optional string specifying the filename of the saved image (extension not
needed). If a filename is not provided or set to None, the user is prompted
for a filename at save time.
""")
class CopyTool(ActionTool):
''' *toolbar icon*: |copy_icon|
The copy tool is an action tool, that allows copying the rendererd contents of
a plot or a collection of plots to system's clipboard. This tools is browser
dependent and may not function in certain browsers, or require additional
permissions to be granted to the web page.
.. |copy_icon| image:: /_images/icons/Copy.png
:height: 24px
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
class ResetTool(PlotActionTool):
''' *toolbar icon*: |reset_icon|
The reset tool is an action. When activated in the toolbar, the tool resets
the data bounds of the plot to their values when the plot was initially
created.
.. |reset_icon| image:: /_images/icons/Reset.png
:height: 24px
:alt: Icon of two arrows on a circular arc forming a circle representing the reset tool in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
class TapTool(Tap, SelectTool):
''' *toolbar icon*: |tap_icon|
The tap selection tool allows the user to select at single points by
left-clicking a mouse, or tapping with a finger.
See :ref:`ug_styling_plots_selected_unselected_glyphs` for information
on styling selected and unselected glyphs.
.. |tap_icon| image:: /_images/icons/Tap.png
:height: 24px
:alt: Icon of two concentric circles with a + in the lower right representing the tap tool in the toolbar.
.. note::
Selections can be comprised of multiple regions, even those
made by different selection tools. Hold down the SHIFT key
while making a selection to append the new selection to any
previous selection that might exist.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
behavior = Enum("select", "inspect", default="select", help="""
This tool can be configured to either make selections or inspections
on associated data sources. The difference is that selection changes
propagate across bokeh and other components (e.g. selection glyph)
will be notified. Inspections don't act like this, so it's useful to
configure `callback` when setting `behavior='inspect'`.
""")
gesture = Enum("tap", "doubletap", default="tap", help="""
Specifies which kind of gesture will be used to trigger the tool,
either a single or double tap.
""")
modifiers = Struct(shift=Optional(Bool), ctrl=Optional(Bool), alt=Optional(Bool), default={}, help="""
Allows to configure a combination of modifier keys, which need to
be pressed during the selected gesture for this tool to trigger.
.. warning::
Configuring modifiers is a platform dependent feature and
can make this tool unsable for example on mobile devices.
""").accepts(Enum(KeyModifier), lambda key_mod: {key_mod: True})
callback = Nullable(Instance(Callback), help="""
A callback to execute *whenever a glyph is "hit"* by a mouse click
or tap.
This is often useful with the :class:`~bokeh.models.callbacks.OpenURL`
model to open URLs based on a user clicking or tapping a specific glyph.
However, it may also be a :class:`~bokeh.models.callbacks.CustomJS`
which can execute arbitrary JavaScript code in response to clicking or
tapping glyphs. The callback will be executed for each individual glyph
that is it hit by a click or tap, and will receive the ``TapTool`` model
as ``cb_obj``. The optional ``cb_data`` will have the data source as
its ``.source`` attribute and the selection geometry as its
``.geometries`` attribute.
The ``.geometries`` attribute has 5 members.
``.type`` is the geometry type, which always a ``.point`` for a tap event.
``.sx`` and ``.sy`` are the screen X and Y coordinates where the tap occurred.
``.x`` and ``.y`` are the converted data coordinates for the item that has
been selected. The ``.x`` and ``.y`` values are based on the axis assigned
to that glyph.
.. note::
This callback does *not* execute on every tap, only when a glyph is
"hit". If you would like to execute a callback on every mouse tap,
please see :ref:`ug_interaction_js_callbacks_customjs_js_on_event`.
""")
class CrosshairTool(InspectTool):
''' *toolbar icon*: |crosshair_icon|
The crosshair tool is a passive inspector tool. It is generally on at all
times, but can be configured in the inspector's menu associated with the
*toolbar icon* shown above.
The crosshair tool draws a crosshair annotation over the plot, centered on
the current mouse position. The crosshair tool may be configured to draw
across only one dimension by setting the ``dimension`` property to only
``width`` or ``height``.
.. |crosshair_icon| image:: /_images/icons/Crosshair.png
:height: 24px
:alt: Icon of circle with aiming reticle marks representing the crosshair tool in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
overlay = Either(
Auto,
Instance(Span),
Tuple(Instance(Span), Instance(Span)), default="auto", help="""
An annotation drawn to indicate the crosshair.
If ``"auto"``, this will create spans depending on the ``dimensions``
property, which based on its value, will result in either one span
(horizontal or vertical) or two spans (horizontal and vertical).
Alternatively the user can provide one ``Span`` instance, where the
dimension is indicated by the ``dimension`` property of the ``Span``.
Also two ``Span`` instances can be provided. Providing explicit
``Span`` instances allows for constructing linked crosshair, when
those instances are shared between crosshair tools of different plots.
.. note::
This property is experimental and may change at any point. In
particular in future this will allow using other annotations
than ``Span`` and annotation groups.
""")
dimensions = Enum(Dimensions, default="both", help="""
Which dimensions the crosshair tool is to track. By default, both vertical
and horizontal lines will be drawn. If only "width" is supplied, only a
horizontal line will be drawn. If only "height" is supplied, only a
vertical line will be drawn.
""")
line_color = Color(default="black", help="""
A color to use to stroke paths with.
""")
line_alpha = Alpha(help="""
An alpha value to use to stroke paths with.
""")
line_width = Float(default=1, help="""
Stroke width in units of pixels.
""")
DEFAULT_BOX_ZOOM_OVERLAY = InstanceDefault(BoxAnnotation,
syncable=False,
level="overlay",
visible=False,
editable=False,
left=nan,
right=nan,
top=nan,
bottom=nan,
top_units="canvas",
left_units="canvas",
bottom_units="canvas",
right_units="canvas",
fill_color="lightgrey",
fill_alpha=0.5,
line_color="black",
line_alpha=1.0,
line_width=2,
line_dash=[4, 4],
)
DEFAULT_BOX_SELECT_OVERLAY = InstanceDefault(BoxAnnotation,
syncable=False,
level="overlay",
visible=False,
editable=True,
left=nan,
right=nan,
top=nan,
bottom=nan,
top_units="data",
left_units="data",
bottom_units="data",
right_units="data",
fill_color="lightgrey",
fill_alpha=0.5,
line_color="black",
line_alpha=1.0,
line_width=2,
line_dash=[4, 4],
)
class BoxZoomTool(Drag):
''' *toolbar icon*: |box_zoom_icon|
The box zoom tool allows users to define a rectangular region of a Plot to
zoom to by dragging he mouse or a finger over the plot region. The end of
the drag event indicates the selection region is ready.
.. |box_zoom_icon| image:: /_images/icons/BoxZoom.png
:height: 24px
:alt: Icon of a dashed box with a magnifying glass in the upper right representing the box-zoom tool in the toolbar.
.. note::
``BoxZoomTool`` is incompatible with ``GMapPlot`` due to the manner in
which Google Maps exert explicit control over aspect ratios. Adding
this tool to a ``GMapPlot`` will have no effect.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
dimensions = Either(Enum(Dimensions), Auto, default="both", help="""
Which dimensions the zoom box is to be free in. By default, users may
freely draw zoom boxes with any dimensions. If only "width" is supplied,
the box will be constrained to span the entire vertical space of the plot,
only the horizontal dimension can be controlled. If only "height" is
supplied, the box will be constrained to span the entire horizontal space
of the plot, and the vertical dimension can be controlled.
""")
overlay = Instance(BoxAnnotation, default=DEFAULT_BOX_ZOOM_OVERLAY, help="""
A shaded annotation drawn to indicate the selection region.
""")
match_aspect = Bool(default=False, help="""
Whether the box zoom region should be restricted to have the same
aspect ratio as the plot region.
.. note::
If the tool is restricted to one dimension, this value has
no effect.
""")
origin = Enum("corner", "center", default="corner", help="""
Indicates whether the rectangular zoom area should originate from a corner
(top-left or bottom-right depending on direction) or the center of the box.
""")
@abstract
class ZoomBaseTool(PlotActionTool):
""" Abstract base class for zoom action tools. """
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
renderers = Either(Auto, List(Instance(DataRenderer)), default="auto", help="""
Restrict zoom to ranges used by the provided data renderers. If ``"auto"``
then all ranges provided by the cartesian frame will be used.
""")
# TODO ZoomInTool dimensions should probably be constrained to be the same as ZoomOutTool
dimensions = Enum(Dimensions, default="both", help="""
Which dimensions the zoom tool is constrained to act in. By default
the tool will zoom in any dimension, but can be configured to only
zoom horizontally across the width of the plot, or vertically across
the height of the plot.
""")
factor = Percent(default=0.1, help="""
Percentage of the range to zoom for each usage of the tool.
""")
level = NonNegative(Int, default=0, help="""
When working with composite scales (sub-coordinates), this property
allows to configure which set of ranges to scale. The default is to
scale top-level (frame) ranges.
""")
class ZoomInTool(ZoomBaseTool):
''' *toolbar icon*: |zoom_in_icon|
The zoom-in tool allows users to click a button to zoom in
by a fixed amount.
.. |zoom_in_icon| image:: /_images/icons/ZoomIn.png
:height: 24px
:alt: Icon of a plus sign next to a magnifying glass representing the zoom-in tool in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
class ZoomOutTool(ZoomBaseTool):
''' *toolbar icon*: |zoom_out_icon|
The zoom-out tool allows users to click a button to zoom out
by a fixed amount.
.. |zoom_out_icon| image:: /_images/icons/ZoomOut.png
:height: 24px
:alt: Icon of a minus sign next to a magnifying glass representing the zoom-out tool in the toolbar.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
maintain_focus = Bool(default=True, help="""
If ``True``, then hitting a range bound in any one dimension will prevent
all further zooming in all dimensions. If ``False``, zooming can continue
independently in any dimension that has not yet reached its bounds, even
if that causes overall focus or aspect ratio to change.
""")
class BoxSelectTool(Drag, RegionSelectTool):
''' *toolbar icon*: |box_select_icon|