-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
legends.py
676 lines (514 loc) · 21.5 KB
/
legends.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
#-----------------------------------------------------------------------------
# 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.
#-----------------------------------------------------------------------------
'''
'''
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
from __future__ import annotations
import logging # isort:skip
log = logging.getLogger(__name__)
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Standard library imports
from typing import Any
# Bokeh imports
from ...core.enums import (
Align,
AlternationPolicy,
Anchor,
LegendClickPolicy,
LegendLocation,
Location,
Orientation,
)
from ...core.has_props import abstract
from ...core.properties import (
Auto,
Bool,
Dict,
Either,
Enum,
Float,
Include,
Instance,
InstanceDefault,
Int,
List,
NonNegative,
Nullable,
NullStringSpec,
Override,
Positive,
Seq,
String,
TextLike,
Tuple,
value,
)
from ...core.property.vectorization import Field
from ...core.property_mixins import (
ScalarFillProps,
ScalarHatchProps,
ScalarLineProps,
ScalarTextProps,
)
from ...core.validation import error
from ...core.validation.errors import (
BAD_COLUMN_NAME,
NON_MATCHING_DATA_SOURCES_ON_LEGEND_ITEM_RENDERERS,
)
from ...model import Model
from ..formatters import TickFormatter
from ..labeling import LabelingPolicy, NoOverlap
from ..mappers import ColorMapper
from ..ranges import Range
from ..renderers import GlyphRenderer
from ..tickers import FixedTicker, Ticker
from .annotation import Annotation
from .dimensional import Dimensional, MetricLength
#-----------------------------------------------------------------------------
# Globals and constants
#-----------------------------------------------------------------------------
__all__ = (
"ColorBar",
"ContourColorBar",
"Legend",
"LegendItem",
"ScaleBar",
)
#-----------------------------------------------------------------------------
# General API
#-----------------------------------------------------------------------------
@abstract
class BaseColorBar(Annotation):
''' Abstract base class for color bars.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
location = Either(Enum(Anchor), Tuple(Float, Float), default="top_right", help="""
The location where the color bar should draw itself. It's either one of
``bokeh.core.enums.Anchor``'s enumerated values, or a ``(x, y)``
tuple indicating an absolute location absolute location in screen
coordinates (pixels from the bottom-left corner).
.. warning::
If the color bar is placed in a side panel, the location will likely
have to be set to `(0,0)`.
""")
orientation = Either(Enum(Orientation), Auto, default="auto", help="""
Whether the color bar should be oriented vertically or horizontally.
""")
height = Either(Auto, Int, help="""
The height (in pixels) that the color scale should occupy.
""")
width = Either(Auto, Int, help="""
The width (in pixels) that the color scale should occupy.
""")
scale_alpha = Float(1.0, help="""
The alpha with which to render the color scale.
""")
title = Nullable(TextLike, help="""
The title text to render.
""")
title_props = Include(ScalarTextProps, prefix="title", help="""
The {prop} values for the title text.
""")
title_text_font_size = Override(default="13px")
title_text_font_style = Override(default="italic")
title_standoff = Int(2, help="""
The distance (in pixels) to separate the title from the color bar.
""")
ticker = Either(Instance(Ticker), Auto, default="auto", help="""
A Ticker to use for computing locations of axis components.
""")
formatter = Either(Instance(TickFormatter), Auto, default="auto", help="""
A ``TickFormatter`` to use for formatting the visual appearance of ticks.
""")
major_label_overrides = Dict(Either(Float, String), TextLike, default={}, help="""
Provide explicit tick label values for specific tick locations that
override normal formatting.
""")
major_label_policy = Instance(LabelingPolicy, default=InstanceDefault(NoOverlap), help="""
Allows to filter out labels, e.g. declutter labels to avoid overlap.
""")
margin = Int(30, help="""
Amount of margin (in pixels) around the outside of the color bar.
""")
padding = Int(10, help="""
Amount of padding (in pixels) between the color scale and color bar border.
""")
major_label_props = Include(ScalarTextProps, prefix="major_label", help="""
The {prop} of the major tick labels.
""")
major_label_text_font_size = Override(default="11px")
label_standoff = Int(5, help="""
The distance (in pixels) to separate the tick labels from the color bar.
""")
major_tick_props = Include(ScalarLineProps, prefix="major_tick", help="""
The {prop} of the major ticks.
""")
major_tick_line_color = Override(default="#ffffff")
major_tick_in = Int(default=5, help="""
The distance (in pixels) that major ticks should extend into the
main plot area.
""")
major_tick_out = Int(default=0, help="""
The distance (in pixels) that major ticks should extend out of the
main plot area.
""")
minor_tick_props = Include(ScalarLineProps, prefix="minor_tick", help="""
The {prop} of the minor ticks.
""")
minor_tick_line_color = Override(default=None)
minor_tick_in = Int(default=0, help="""
The distance (in pixels) that minor ticks should extend into the
main plot area.
""")
minor_tick_out = Int(default=0, help="""
The distance (in pixels) that major ticks should extend out of the
main plot area.
""")
bar_props = Include(ScalarLineProps, prefix="bar", help="""
The {prop} for the color scale bar outline.
""")
bar_line_color = Override(default=None)
border_props = Include(ScalarLineProps, prefix="border", help="""
The {prop} for the color bar border outline.
""")
border_line_color = Override(default=None)
background_props = Include(ScalarFillProps, prefix="background", help="""
The {prop} for the color bar background style.
""")
background_fill_color = Override(default="#ffffff")
background_fill_alpha = Override(default=0.95)
class ColorBar(BaseColorBar):
''' Render a color bar based on a color mapper.
See :ref:`ug_basic_annotations_color_bars` for information on plotting color bars.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
color_mapper = Instance(ColorMapper, help="""
A color mapper containing a color palette to render.
.. warning::
If the `low` and `high` attributes of the ``ColorMapper`` aren't set, ticks
and tick labels won't be rendered. Additionally, if a ``LogTicker`` is
passed to the `ticker` argument and either or both of the logarithms
of `low` and `high` values of the color_mapper are non-numeric
(i.e. `low=0`), the tick and tick labels won't be rendered.
""")
display_low = Nullable(Float, help="""
The lowest value to display in the color bar. The whole of the color entry
containing this value is shown.
""")
display_high = Nullable(Float, help="""
The highest value to display in the color bar. The whole of the color entry
containing this value is shown.
""")
class ContourColorBar(BaseColorBar):
''' Color bar used for contours.
Supports displaying hatch patterns and line styles that contour plots may
have as well as the usual fill styles.
Do not create these objects manually, instead use ``ContourRenderer.color_bar``.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
fill_renderer = Instance(GlyphRenderer, help="""
Glyph renderer used for filled contour polygons.
""")
line_renderer = Instance(GlyphRenderer, help="""
Glyph renderer used for contour lines.
""")
levels = Seq(Float, default=[], help="""
Levels at which the contours are calculated.
""")
class LegendItem(Model):
'''
'''
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if isinstance(self.label, str):
# Allow convenience of setting label as a string
self.label = value(self.label)
label = NullStringSpec(help="""
A label for this legend. Can be a string, or a column of a
ColumnDataSource. If ``label`` is a field, then it must
be in the renderers' data_source.
""")
renderers = List(Instance(GlyphRenderer), help="""
A list of the glyph renderers to draw in the legend. If ``label`` is a field,
then all data_sources of renderers must be the same.
""")
index = Nullable(Int, help="""
The column data index to use for drawing the representative items.
If None (the default), then Bokeh will automatically choose an index to
use. If the label does not refer to a data column name, this is typically
the first data point in the data source. Otherwise, if the label does
refer to a column name, the legend will have "groupby" behavior, and will
choose and display representative points from every "group" in the column.
If set to a number, Bokeh will use that number as the index in all cases.
""")
visible = Bool(default=True, help="""
Whether the legend item should be displayed. See
:ref:`ug_basic_annotations_legends_item_visibility` in the user guide.
""")
@error(NON_MATCHING_DATA_SOURCES_ON_LEGEND_ITEM_RENDERERS)
def _check_data_sources_on_renderers(self):
if isinstance(self.label, Field):
if len({r.data_source for r in self.renderers}) != 1:
return str(self)
@error(BAD_COLUMN_NAME)
def _check_field_label_on_data_source(self):
if isinstance(self.label, Field):
if len(self.renderers) < 1:
return str(self)
source = self.renderers[0].data_source
if self.label.field not in source.column_names:
return str(self)
class Legend(Annotation):
''' Render informational legends for a plot.
See :ref:`ug_basic_annotations_legends` for information on plotting legends.
'''
# explicit __init__ to support Init signatures
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
location = Either(Enum(LegendLocation), Tuple(Float, Float), default="top_right", help="""
The location where the legend should draw itself. It's either one of
:class:`~bokeh.core.enums.LegendLocation`'s enumerated values, or a ``(x, y)``
tuple indicating an absolute location absolute location in screen
coordinates (pixels from the bottom-left corner).
""")
orientation = Enum(Orientation, default="vertical", help="""
Whether the legend entries should be placed vertically or horizontally
when they are drawn.
""")
ncols = Either(Positive(Int), Auto, default="auto", help="""
The number of columns in the legend's layout. By default it's either
one column if the orientation is vertical or the number of items in
the legend otherwise. ``ncols`` takes precendence over ``nrows`` for
horizonal orientation.
""")
nrows = Either(Positive(Int), Auto, default="auto", help="""
The number of rows in the legend's layout. By default it's either
one row if the orientation is horizonal or the number of items in
the legend otherwise. ``nrows`` takes precendence over ``ncols``
for vertical orientation.
""")
title = Nullable(String, help="""
The title text to render.
""")
title_props = Include(ScalarTextProps, prefix="title", help="""
The {prop} values for the title text.
""")
title_text_font_size = Override(default="13px")
title_text_font_style = Override(default="italic")
title_location = Enum(Location, default="above", help="""
Specifies on which side of the legend the title will be located.
Titles on the left or right side will be rotated accordingly.
""")
title_standoff = Int(5, help="""
The distance (in pixels) to separate the title from the legend.
""")
border_props = Include(ScalarLineProps, prefix="border", help="""
The {prop} for the legend border outline.
""")
border_line_color = Override(default="#e5e5e5")
border_line_alpha = Override(default=0.5)
background_props = Include(ScalarFillProps, prefix="background", help="""
The {prop} for the legend background style.
""")
item_background_props = Include(ScalarFillProps, prefix="item_background", help="""
The {prop} for the legend items' background style.
""")
inactive_props = Include(ScalarFillProps, prefix="inactive", help="""
The {prop} for the legend item style when inactive. These control an overlay
on the item that can be used to obscure it when the corresponding glyph
is inactive (e.g. by making it semi-transparent).
""")
click_policy = Enum(LegendClickPolicy, default="none", help="""
Defines what happens when a lengend's item is clicked.
""")
item_background_policy = Enum(AlternationPolicy, default="none", help="""
Defines which items to style, if ``item_background_fill`` is configured.
""")
background_fill_color = Override(default="#ffffff")
background_fill_alpha = Override(default=0.95)
item_background_fill_color = Override(default="#f1f1f1")
item_background_fill_alpha = Override(default=0.8)
inactive_fill_color = Override(default="white")
inactive_fill_alpha = Override(default=0.7)
label_props = Include(ScalarTextProps, prefix="label", help="""
The {prop} for the legend labels.
""")
label_text_baseline = Override(default='middle')
label_text_font_size = Override(default='13px')
label_standoff = Int(5, help="""
The distance (in pixels) to separate the label from its associated glyph.
""")
label_height = Int(20, help="""
The minimum height (in pixels) of the area that legend labels should occupy.
""")
label_width = Int(20, help="""
The minimum width (in pixels) of the area that legend labels should occupy.
""")
glyph_height = Int(20, help="""
The height (in pixels) that the rendered legend glyph should occupy.
""")
glyph_width = Int(20, help="""
The width (in pixels) that the rendered legend glyph should occupy.
""")
margin = Int(10, help="""
Amount of margin around the legend.
""")
padding = Int(10, help="""
Amount of padding around the contents of the legend. Only applicable when
border is visible, otherwise collapses to 0.
""")
spacing = Int(3, help="""
Amount of spacing (in pixels) between legend entries.
""")
items = List(Instance(LegendItem), help="""
A list of :class:`~bokeh.model.annotations.LegendItem` instances to be
rendered in the legend.
This can be specified explicitly, for instance:
.. code-block:: python
legend = Legend(items=[
LegendItem(label="sin(x)", renderers=[r0, r1]),
LegendItem(label="2*sin(x)", renderers=[r2]),
LegendItem(label="3*sin(x)", renderers=[r3, r4])
])
But as a convenience, can also be given more compactly as a list of tuples:
.. code-block:: python
legend = Legend(items=[
("sin(x)", [r0, r1]),
("2*sin(x)", [r2]),
("3*sin(x)", [r3, r4])
])
where each tuple is of the form: *(label, renderers)*.
""").accepts(List(Tuple(String, List(Instance(GlyphRenderer)))),
lambda items: [LegendItem(label=item[0], renderers=item[1]) for item in items])
class ScaleBar(Annotation):
""" Represents a scale bar annotation.
"""
# explicit __init__ to support Init signatures
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
range = Either(Instance(Range), Auto, default="auto", help="""
The range for which to display the scale.
This can be either a range reference or ``"auto"``, in which case the
scale bar will pick the default x or y range of the frame, depending
on the orientation of the scale bar.
""")
unit = String(default="m", help="""
The unit of the ``range`` property.
""")
dimensional = Instance(Dimensional, default=InstanceDefault(MetricLength), help="""
Defines the units of measurement.
""")
orientation = Enum(Orientation, help="""
Whether the scale bar should be oriented horizontally or vertically.
""")
location = Enum(Anchor, default="top_right", help="""
Location anchor for positioning scale bar.
""")
length_sizing = Enum("adaptive", "exact", help="""
Defines how the length of the bar is interpreted.
This can either be:
* ``"adaptive"`` - the computed length is fit into a set of ticks provided
be the dimensional model. If no ticks are provided, then the behavior
is the same as if ``"exact"`` sizing was used
* ``"exact"`` - the computed length is used as-is
""")
bar_length = NonNegative(Either(Float, Int))(default=0.2, help="""
The length of the bar, either a fraction of the frame or a number of pixels.
""")
bar_line = Include(ScalarLineProps, prefix="bar", help="""
The {prop} values for the bar line style.
""")
margin = Int(default=10, help="""
Amount of margin (in pixels) around the outside of the scale bar.
""")
padding = Int(default=10, help="""
Amount of padding (in pixels) between the contents of the scale bar
and its border.
""")
label = String(default="@{value} @{unit}", help="""
The label template.
This can use special variables:
* ``@{value}`` The current value. Optionally can provide a number
formatter with e.g. ``@{value}{%.2f}``.
* ``@{unit}`` The unit of measure, by default in the short form.
Optionally can provide a format ``@{unit}{short}`` or ``@{unit}{long}``.
""")
label_text = Include(ScalarTextProps, prefix="label", help="""
The {prop} values for the label text style.
""")
label_align = Enum(Align, default="center", help="""
Specifies where to align scale bar's label along the bar.
This property effective when placing the label above or below
a horizontal scale bar, or left or right of a vertical one.
""")
label_location = Enum(Location, default="below", help="""
Specifies on which side of the scale bar the label will be located.
""")
label_standoff = Int(default=5, help="""
The distance (in pixels) to separate the label from the scale bar.
""")
title = String(default="", help="""
The title text to render.
""")
title_text = Include(ScalarTextProps, prefix="title", help="""
The {prop} values for the title text style.
""")
title_align = Enum(Align, default="center", help="""
Specifies where to align scale bar's title along the bar.
This property effective when placing the title above or below
a horizontal scale bar, or left or right of a vertical one.
""")
title_location = Enum(Location, default="above", help="""
Specifies on which side of the legend the title will be located.
""")
title_standoff = Int(default=5, help="""
The distance (in pixels) to separate the title from the scale bar.
""")
ticker = Instance(Ticker, default=InstanceDefault(FixedTicker, ticks=[]), help="""
A ticker to use for computing locations of axis components.
Note that if using the default fixed ticker with no predefined ticks,
then the appearance of the scale bar will be just a solid bar with
no additional markings.
""")
border_line = Include(ScalarLineProps, prefix="border", help="""
The {prop} for the scale bar border line style.
""")
background_fill = Include(ScalarFillProps, prefix="background", help="""
The {prop} for the scale bar background fill style.
""")
background_hatch = Include(ScalarHatchProps, prefix="background", help="""
The {prop} for the scale bar background hatch style.
""")
bar_line_width = Override(default=2)
border_line_color = Override(default="#e5e5e5")
border_line_alpha = Override(default=0.5)
border_line_width = Override(default=1)
background_fill_color = Override(default="#ffffff")
background_fill_alpha = Override(default=0.95)
label_text_font_size = Override(default="13px")
label_text_baseline = Override(default="middle")
title_text_font_size = Override(default="13px")
title_text_font_style = Override(default="italic")
#-----------------------------------------------------------------------------
# Dev API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Private API
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------