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''' Models for controlling the text and visual formatting of tick
8labels on Bokeh plot axes.
9
10'''
11
12#-----------------------------------------------------------------------------
13# Boilerplate
14#-----------------------------------------------------------------------------
15import logging # isort:skip
16log = logging.getLogger(__name__)
17
18#-----------------------------------------------------------------------------
19# Imports
20#-----------------------------------------------------------------------------
21
22# Bokeh imports
23from ..core.enums import LatLon, NumeralLanguage, RoundingFunction
24from ..core.has_props import abstract
25from ..core.properties import (
26    AnyRef,
27    Auto,
28    Bool,
29    Dict,
30    Either,
31    Enum,
32    Instance,
33    Int,
34    List,
35    Nullable,
36    String,
37)
38from ..core.validation import error
39from ..core.validation.errors import MISSING_MERCATOR_DIMENSION
40from ..model import Model
41from ..util.string import format_docstring
42from .tickers import Ticker
43
44#-----------------------------------------------------------------------------
45# Globals and constants
46#-----------------------------------------------------------------------------
47
48__all__ = (
49    'TickFormatter',
50    'BasicTickFormatter',
51    'MercatorTickFormatter',
52    'NumeralTickFormatter',
53    'PrintfTickFormatter',
54    'LogTickFormatter',
55    'CategoricalTickFormatter',
56    'FuncTickFormatter',
57    'DatetimeTickFormatter',
58)
59
60#-----------------------------------------------------------------------------
61# Private API
62#-----------------------------------------------------------------------------
63
64def _DATETIME_TICK_FORMATTER_HELP(field):
65    return """
66    Formats for displaying datetime values in the %s range.
67
68    See the :class:`~bokeh.models.formatters.DatetimeTickFormatter` help for a list of all supported formats.
69    """ % field
70
71#-----------------------------------------------------------------------------
72# General API
73#-----------------------------------------------------------------------------
74
75@abstract
76class TickFormatter(Model):
77    ''' A base class for all tick formatter types.
78
79    '''
80    pass
81
82class BasicTickFormatter(TickFormatter):
83    ''' Display tick values from continuous ranges as "basic numbers",
84    using scientific notation when appropriate by default.
85
86    '''
87    precision = Either(Auto, Int, help="""
88    How many digits of precision to display in tick labels.
89    """)
90
91    use_scientific = Bool(True, help="""
92    Whether to ever display scientific notation. If ``True``, then
93    when to use scientific notation is controlled by ``power_limit_low``
94    and ``power_limit_high``.
95    """)
96
97    power_limit_high = Int(5, help="""
98    Limit the use of scientific notation to when::
99
100        log(x) >= power_limit_high
101
102    """)
103
104    power_limit_low = Int(-3, help="""
105    Limit the use of scientific notation to when::
106
107        log(x) <= power_limit_low
108
109    """)
110
111class MercatorTickFormatter(BasicTickFormatter):
112    ''' A ``TickFormatter`` for values in WebMercator units.
113
114    Some map plot types internally use WebMercator to describe coordinates,
115    plot bounds, etc. These units are not very human-friendly. This tick
116    formatter will convert WebMercator units into Latitude and Longitude
117    for display on axes.
118
119    '''
120
121    dimension = Nullable(Enum(LatLon), help="""
122    Specify whether to format ticks for Latitude or Longitude.
123
124    Projected coordinates are not separable, computing Latitude and Longitude
125    tick labels from Web Mercator requires considering coordinates from both
126    dimensions together. Use this property to specify which result should be
127    used for display.
128
129    Typically, if the formatter is for an x-axis, then dimension should be
130    ``"lon"`` and if the formatter is for a y-axis, then the dimension
131    should be `"lat"``.
132
133    In order to prevent hard to debug errors, there is no default value for
134    dimension. Using an un-configured ``MercatorTickFormatter`` will result in
135    a validation error and a JavaScript console error.
136    """)
137
138    @error(MISSING_MERCATOR_DIMENSION)
139    def _check_missing_dimension(self):
140        if self.dimension is None:
141            return str(self)
142
143class NumeralTickFormatter(TickFormatter):
144    ''' Tick formatter based on a human-readable format string. '''
145
146    format = String("0,0", help="""
147    The number format, as defined in the following tables:
148
149    **NUMBERS**:
150
151    ============ ============== ===============
152    Number       Format         String
153    ============ ============== ===============
154    10000        '0,0.0000'     10,000.0000
155    10000.23     '0,0'          10,000
156    10000.23     '+0,0'         +10,000
157    -10000       '0,0.0'        -10,000.0
158    10000.1234   '0.000'        10000.123
159    10000.1234   '0[.]00000'    10000.12340
160    -10000       '(0,0.0000)'   (10,000.0000)
161    -0.23        '.00'          -.23
162    -0.23        '(.00)'        (.23)
163    0.23         '0.00000'      0.23000
164    0.23         '0.0[0000]'    0.23
165    1230974      '0.0a'         1.2m
166    1460         '0 a'          1 k
167    -104000      '0a'           -104k
168    1            '0o'           1st
169    52           '0o'           52nd
170    23           '0o'           23rd
171    100          '0o'           100th
172    ============ ============== ===============
173
174    **CURRENCY**:
175
176    =========== =============== =============
177    Number      Format          String
178    =========== =============== =============
179    1000.234    '$0,0.00'       $1,000.23
180    1000.2      '0,0[.]00 $'    1,000.20 $
181    1001        '$ 0,0[.]00'    $ 1,001
182    -1000.234   '($0,0)'        ($1,000)
183    -1000.234   '$0.00'         -$1000.23
184    1230974     '($ 0.00 a)'    $ 1.23 m
185    =========== =============== =============
186
187    **BYTES**:
188
189    =============== =========== ============
190    Number          Format      String
191    =============== =========== ============
192    100             '0b'        100B
193    2048            '0 b'       2 KB
194    7884486213      '0.0b'      7.3GB
195    3467479682787   '0.000 b'   3.154 TB
196    =============== =========== ============
197
198    **PERCENTAGES**:
199
200    ============= ============= ===========
201    Number        Format        String
202    ============= ============= ===========
203    1             '0%'          100%
204    0.974878234   '0.000%'      97.488%
205    -0.43         '0 %'         -43 %
206    0.43          '(0.000 %)'   43.000 %
207    ============= ============= ===========
208
209    **TIME**:
210
211    ============ ============== ============
212    Number       Format         String
213    ============ ============== ============
214    25           '00:00:00'     0:00:25
215    238          '00:00:00'     0:03:58
216    63846        '00:00:00'     17:44:06
217    ============ ============== ============
218
219    For the complete specification, see http://numbrojs.com/format.html
220    """)
221
222    language = Enum(NumeralLanguage, default="en", help="""
223    The language to use for formatting language-specific features (e.g. thousands separator).
224    """)
225
226    rounding = Enum(RoundingFunction, help="""
227    Rounding functions (round, floor, ceil) and their synonyms (nearest, rounddown, roundup).
228    """)
229
230class PrintfTickFormatter(TickFormatter):
231    ''' Tick formatter based on a printf-style format string. '''
232
233    format = String("%s", help="""
234    The number format, as defined as follows: the placeholder in the format
235    string is marked by % and is followed by one or more of these elements,
236    in this order:
237
238    * An optional ``+`` sign
239        Causes the result to be preceded with a plus or minus sign on numeric
240        values. By default, only the ``-`` sign is used on negative numbers.
241
242    * An optional padding specifier
243        Specifies what (if any) character to use for padding. Possible values
244        are 0 or any other character preceded by a ``'`` (single quote). The
245        default is to pad with spaces.
246
247    * An optional ``-`` sign
248        Causes sprintf to left-align the result of this placeholder. The default
249        is to right-align the result.
250
251    * An optional number
252        Specifies how many characters the result should have. If the value to be
253        returned is shorter than this number, the result will be padded.
254
255    * An optional precision modifier
256        Consists of a ``.`` (dot) followed by a number, specifies how many digits
257        should be displayed for floating point numbers. When used on a string, it
258        causes the result to be truncated.
259
260    * A type specifier
261        Can be any of:
262
263        - ``%`` --- yields a literal ``%`` character
264        - ``b`` --- yields an integer as a binary number
265        - ``c`` --- yields an integer as the character with that ASCII value
266        - ``d`` or ``i`` --- yields an integer as a signed decimal number
267        - ``e`` --- yields a float using scientific notation
268        - ``u`` --- yields an integer as an unsigned decimal number
269        - ``f`` --- yields a float as is
270        - ``o`` --- yields an integer as an octal number
271        - ``s`` --- yields a string as is
272        - ``x`` --- yields an integer as a hexadecimal number (lower-case)
273        - ``X`` --- yields an integer as a hexadecimal number (upper-case)
274
275    """)
276
277class LogTickFormatter(TickFormatter):
278    ''' Display tick values from continuous ranges as powers
279    of some base.
280
281    Most often useful in conjunction with a ``LogTicker``.
282
283    '''
284    ticker = Nullable(Instance(Ticker), help="""
285    The corresponding ``LogTicker``, used to determine the correct
286    base to use. If unset, the formatter will use base 10 as a default.
287    """)
288
289class CategoricalTickFormatter(TickFormatter):
290    ''' Display tick values from categorical ranges as string
291    values.
292
293    '''
294    pass
295
296class FuncTickFormatter(TickFormatter):
297    ''' Display tick values that are formatted by a user-defined function.
298
299    .. warning::
300        The explicit purpose of this Bokeh Model is to embed *raw JavaScript
301        code* for a browser to execute. If any part of the code is derived
302        from untrusted user inputs, then you must take appropriate care to
303        sanitize the user input prior to passing to Bokeh.
304
305    '''
306
307    args = Dict(String, AnyRef, help="""
308    A mapping of names to Python objects. In particular those can be bokeh's models.
309    These objects are made available to the formatter's code snippet as the values of
310    named parameters to the callback.
311    """)
312
313    code = String(default="", help="""
314    A snippet of JavaScript code that reformats a single tick to the desired
315    format. The variable ``tick`` will contain the unformatted tick value and
316    can be expected to be present in the code snippet namespace at render time.
317
318    Additionally available variables are:
319
320      * ``ticks``, an array of all axis ticks as positioned by the ticker,
321      * ``index``, the position of ``tick`` within ``ticks``, and
322      * the keys of ``args`` mapping, if any.
323
324    Finding yourself needing to cache an expensive ``ticks``-dependent
325    computation, you can store it on the ``this`` variable.
326
327    Example:
328
329        .. code-block:: javascript
330
331            code = '''
332            this.precision = this.precision || (ticks.length > 5 ? 1 : 2);
333            return Math.floor(tick) + " + " + (tick % 1).toFixed(this.precision);
334            '''
335    """)
336
337class DatetimeTickFormatter(TickFormatter):
338    ''' A ``TickFormatter`` for displaying datetime values nicely across a
339    range of scales.
340
341    ``DatetimeTickFormatter`` has the following properties (listed together
342    with their default values) that can be used to control the formatting
343    of axis ticks at different scales scales:
344
345    .. code-block:: python
346
347        {defaults}
348
349    Each scale property can be set to format or list of formats to use for
350    formatting datetime tick values that fall in in that "time scale".
351    By default, only the first format string passed for each time scale
352    will be used. By default, all leading zeros are stripped away from
353    the formatted labels.
354
355    This list of supported `strftime`_ formats is reproduced below.
356
357    %a
358        The abbreviated name of the day of the week according to the
359        current locale.
360
361    %A
362        The full name of the day of the week according to the current
363        locale.
364
365    %b
366        The abbreviated month name according to the current locale.
367
368    %B
369        The full month name according to the current locale.
370
371    %c
372        The preferred date and time representation for the current
373        locale.
374
375    %C
376        The century number (year/100) as a 2-digit integer.
377
378    %d
379        The day of the month as a decimal number (range 01 to 31).
380
381    %D
382        Equivalent to %m/%d/%y.  (Americans should note that in many
383        other countries %d/%m/%y is rather common. This means that in
384        international context this format is ambiguous and should not
385        be used.)
386
387    %e
388        Like %d, the day of the month as a decimal number, but a
389        leading zero is replaced by a space.
390
391    %f
392        Microsecond as a decimal number, zero-padded on the left (range
393        000000-999999). This is an extension to the set of directives
394        available to `timezone`_.
395
396    %F
397        Equivalent to %Y-%m-%d (the ISO 8601 date format).
398
399    %G
400        The ISO 8601 week-based year with century as a decimal number.
401        The 4-digit year corresponding to the ISO week number (see %V).
402        This has the same format and value as %Y, except that if the
403        ISO week number belongs to the previous or next year, that year
404        is used instead.
405
406    %g
407        Like %G, but without century, that is, with a 2-digit year (00-99).
408
409    %h
410        Equivalent to %b.
411
412    %H
413        The hour as a decimal number using a 24-hour clock (range 00
414        to 23).
415
416    %I
417        The hour as a decimal number using a 12-hour clock (range 01
418        to 12).
419
420    %j
421        The day of the year as a decimal number (range 001 to 366).
422
423    %k
424        The hour (24-hour clock) as a decimal number (range 0 to 23).
425        Single digits are preceded by a blank.  (See also %H.)
426
427    %l
428        The hour (12-hour clock) as a decimal number (range 1 to 12).
429        Single digits are preceded by a blank.  (See also %I.)  (TZ)
430
431    %m
432        The month as a decimal number (range 01 to 12).
433
434    %M
435        The minute as a decimal number (range 00 to 59).
436
437    %n
438        A newline character. Bokeh text does not currently support
439        newline characters.
440
441    %N
442        Nanosecond as a decimal number, zero-padded on the left (range
443        000000000-999999999). Supports a padding width specifier, i.e.
444        %3N displays 3 leftmost digits. However, this is only accurate
445        to the millisecond level of precision due to limitations of
446        `timezone`_.
447
448    %p
449        Either "AM" or "PM" according to the given time value, or the
450        corresponding strings for the current locale.  Noon is treated
451        as "PM" and midnight as "AM".
452
453    %P
454        Like %p but in lowercase: "am" or "pm" or a corresponding
455        string for the current locale.
456
457    %r
458        The time in a.m. or p.m. notation.  In the POSIX locale this
459        is equivalent to %I:%M:%S %p.
460
461    %R
462        The time in 24-hour notation (%H:%M). For a version including
463        the seconds, see %T below.
464
465    %s
466        The number of seconds since the Epoch, 1970-01-01 00:00:00
467        +0000 (UTC).
468
469    %S
470        The second as a decimal number (range 00 to 60).  (The range
471        is up to 60 to allow for occasional leap seconds.)
472
473    %t
474        A tab character. Bokeh text does not currently support tab
475        characters.
476
477    %T
478        The time in 24-hour notation (%H:%M:%S).
479
480    %u
481        The day of the week as a decimal, range 1 to 7, Monday being 1.
482        See also %w.
483
484    %U
485        The week number of the current year as a decimal number, range
486        00 to 53, starting with the first Sunday as the first day of
487        week 01.  See also %V and %W.
488
489    %V
490        The ISO 8601 week number (see NOTES) of the current year as a
491        decimal number, range 01 to 53, where week 1 is the first week
492        that has at least 4 days in the new year.  See also %U and %W.
493
494    %w
495        The day of the week as a decimal, range 0 to 6, Sunday being 0.
496        See also %u.
497
498    %W
499        The week number of the current year as a decimal number, range
500        00 to 53, starting with the first Monday as the first day of
501        week 01.
502
503    %x
504        The preferred date representation for the current locale
505        without the time.
506
507    %X
508        The preferred time representation for the current locale
509        without the date.
510
511    %y
512        The year as a decimal number without a century (range 00 to 99).
513
514    %Y
515        The year as a decimal number including the century.
516
517    %z
518        The +hhmm or -hhmm numeric timezone (that is, the hour and
519        minute offset from UTC).
520
521    %Z
522        The timezone name or abbreviation.
523
524    %%
525        A literal '%' character.
526
527    .. warning::
528        The client library BokehJS uses the `timezone`_ library to
529        format datetimes. The inclusion of the list below is based on the
530        claim that `timezone`_ makes to support "the full compliment
531        of GNU date format specifiers." However, this claim has not
532        been tested exhaustively against this list. If you find formats
533        that do not function as expected, please submit a `github issue`_,
534        so that the documentation can be updated appropriately.
535
536    .. _strftime: http://man7.org/linux/man-pages/man3/strftime.3.html
537    .. _timezone: http://bigeasy.github.io/timezone/
538    .. _github issue: https://github.com/bokeh/bokeh/issues
539
540    '''
541    microseconds = List(String,
542                        help=_DATETIME_TICK_FORMATTER_HELP("``microseconds``"),
543                        default=['%fus']).accepts(String, lambda fmt: [fmt])
544
545    milliseconds = List(String,
546                        help=_DATETIME_TICK_FORMATTER_HELP("``milliseconds``"),
547                        default=['%3Nms', '%S.%3Ns']).accepts(String, lambda fmt: [fmt])
548
549    seconds      = List(String,
550                        help=_DATETIME_TICK_FORMATTER_HELP("``seconds``"),
551                        default=['%Ss']).accepts(String, lambda fmt: [fmt])
552
553    minsec       = List(String,
554                        help=_DATETIME_TICK_FORMATTER_HELP("``minsec`` (for combined minutes and seconds)"),
555                        default=[':%M:%S']).accepts(String, lambda fmt: [fmt])
556
557    minutes      = List(String,
558                        help=_DATETIME_TICK_FORMATTER_HELP("``minutes``"),
559                        default=[':%M', '%Mm']).accepts(String, lambda fmt: [fmt])
560
561    hourmin      = List(String,
562                        help=_DATETIME_TICK_FORMATTER_HELP("``hourmin`` (for combined hours and minutes)"),
563                        default=['%H:%M']).accepts(String, lambda fmt: [fmt])
564
565    hours        = List(String,
566                        help=_DATETIME_TICK_FORMATTER_HELP("``hours``"),
567                        default=['%Hh', '%H:%M']).accepts(String, lambda fmt: [fmt])
568
569    days         = List(String,
570                        help=_DATETIME_TICK_FORMATTER_HELP("``days``"),
571                        default=['%m/%d', '%a%d']).accepts(String, lambda fmt: [fmt])
572
573    months       = List(String,
574                        help=_DATETIME_TICK_FORMATTER_HELP("``months``"),
575                        default=['%m/%Y', '%b %Y']).accepts(String, lambda fmt: [fmt])
576
577    years        = List(String,
578                        help=_DATETIME_TICK_FORMATTER_HELP("``years``"),
579                        default=['%Y']).accepts(String, lambda fmt: [fmt])
580
581#-----------------------------------------------------------------------------
582# Dev API
583#-----------------------------------------------------------------------------
584
585#-----------------------------------------------------------------------------
586# Code
587#-----------------------------------------------------------------------------
588
589# This is to automate documentation of DatetimeTickFormatter formats and their defaults
590_df = DatetimeTickFormatter()
591_df_fields = ['microseconds', 'milliseconds', 'seconds', 'minsec', 'minutes', 'hourmin', 'hours', 'days', 'months', 'years']
592_df_defaults = _df.properties_with_values()
593_df_defaults_string = "\n\n        ".join("%s = %s" % (name, _df_defaults[name]) for name in _df_fields)
594
595DatetimeTickFormatter.__doc__ = format_docstring(DatetimeTickFormatter.__doc__, defaults=_df_defaults_string)
596del _df, _df_fields, _df_defaults, _df_defaults_string
597