Source code for holoviews.plotting.bokeh.styles

"""
Defines valid style options, validation and utilities
"""

import numpy as np
from bokeh.core.properties import (
    Angle,
    Color,
    DashPattern,
    FontSize,
    MarkerType,
    Percent,
    Size,
)

try:
    from matplotlib import cm, colors
except ImportError:
    cm, colors = None, None

from ...core.options import abbreviated_exception
from ...core.util import arraylike_types
from ...util.transform import dim
from ..util import COLOR_ALIASES, RGB_HEX_REGEX, rgb2hex

# Define shared style properties for bokeh plots

property_prefixes = ['selection', 'nonselection', 'muted', 'hover']

base_properties = ['visible', 'muted']

line_properties = ['line_color', 'line_alpha', 'color', 'alpha', 'line_width',
                   'line_join', 'line_cap', 'line_dash']
line_properties += [f'{prefix}_{prop}' for prop in line_properties
                    for prefix in property_prefixes]

fill_properties = ['fill_color', 'fill_alpha']
fill_properties += [f'{prefix}_{prop}' for prop in fill_properties
                    for prefix in property_prefixes]

text_properties = ['text_font', 'text_font_size', 'text_font_style', 'text_color',
                   'text_alpha', 'text_align', 'text_baseline']

legend_dimensions = ['label_standoff', 'label_width', 'label_height', 'glyph_width',
                     'glyph_height', 'legend_padding', 'legend_spacing', 'click_policy']

# Conversion between matplotlib and bokeh markers

markers = {
    '+': {'marker': 'cross'},
    'x': {'marker': 'cross', 'angle': np.pi/4},
    's': {'marker': 'square'},
    'd': {'marker': 'diamond'},
    '^': {'marker': 'triangle', 'angle': 0},
    '>': {'marker': 'triangle', 'angle': -np.pi/2},
    'v': {'marker': 'triangle', 'angle': np.pi},
    '<': {'marker': 'triangle', 'angle': np.pi/2},
    '1': {'marker': 'triangle', 'angle': 0},
    '2': {'marker': 'triangle', 'angle': -np.pi/2},
    '3': {'marker': 'triangle', 'angle': np.pi},
    '4': {'marker': 'triangle', 'angle': np.pi/2},
    'o': {'marker': 'circle'},
    '*': {'marker': 'asterisk'},
}


[docs]def mpl_to_bokeh(properties): """ Utility to process style properties converting any matplotlib specific options to their nearest bokeh equivalent. """ new_properties = {} for k, v in properties.items(): if isinstance(v, dict): new_properties[k] = v elif k == 's': new_properties['size'] = v elif k == 'marker': new_properties.update(markers.get(v, {'marker': v})) elif (k == 'color' or k.endswith('_color')) and not isinstance(v, (dict, dim)): with abbreviated_exception(): v = COLOR_ALIASES.get(v, v) if isinstance(v, tuple): with abbreviated_exception(): v = rgb2hex(v) new_properties[k] = v else: new_properties[k] = v new_properties.pop('cmap', None) return new_properties
# Validation alpha = Percent() angle = Angle() color = Color() dash_pattern = DashPattern() font_size = FontSize() marker = MarkerType() size = Size() validators = { 'angle' : angle.is_valid, 'alpha' : alpha.is_valid, 'color' : lambda x: ( color.is_valid(x) or (isinstance(x, str) and RGB_HEX_REGEX.match(x)) ), 'font_size' : font_size.is_valid, 'line_dash' : dash_pattern.is_valid, 'marker' : lambda x: marker.is_valid(x) or x in markers, 'size' : size.is_valid, } def get_validator(style): for k, v in validators.items(): if style.endswith(k): return v
[docs]def validate(style, value, scalar=False): """ Validates a style and associated value. Arguments --------- style: str The style to validate (e.g. 'color', 'size' or 'marker') value: The style value to validate scalar: bool Returns ------- valid: boolean or None If validation is supported returns boolean, otherwise None """ validator = get_validator(style) if validator is None: return None if isinstance(value, arraylike_types+(list,)): if scalar: return False return all(validator(v) for v in value) return validator(value)
# Utilities
[docs]def rgba_tuple(rgba): """ Ensures RGB(A) tuples in the range 0-1 are scaled to 0-255. """ if isinstance(rgba, tuple): return tuple(int(c*255) if i<3 else c for i, c in enumerate(rgba)) else: return COLOR_ALIASES.get(rgba, rgba)
[docs]def expand_batched_style(style, opts, mapping, nvals): """ Computes styles applied to a batched plot by iterating over the supplied list of style options and expanding any options found in the supplied style dictionary returning a data and mapping defining the data that should be added to the ColumnDataSource. """ opts = sorted(opts, key=lambda x: x in ['color', 'alpha']) applied_styles = set(mapping) style_data, style_mapping = {}, {} for opt in opts: if 'color' in opt: alias = 'color' elif 'alpha' in opt: alias = 'alpha' else: alias = None if opt not in style or opt in mapping: continue elif opt == alias: if alias in applied_styles: continue elif 'line_'+alias in applied_styles: if 'fill_'+alias not in opts: continue opt = 'fill_'+alias val = style[alias] elif 'fill_'+alias in applied_styles: opt = 'line_'+alias val = style[alias] else: val = style[alias] else: val = style[opt] style_mapping[opt] = {'field': opt} applied_styles.add(opt) if 'color' in opt and isinstance(val, tuple): val = rgb2hex(val) style_data[opt] = [val]*nvals return style_data, style_mapping