import re
import traceback
import warnings
import bisect
from collections import defaultdict, namedtuple
import numpy as np
import param
from ..core import (
HoloMap, DynamicMap, CompositeOverlay, Layout, Overlay, GridSpace,
NdLayout, NdOverlay, AdjointLayout
)
from ..core.options import CallbackError, Cycle
from ..core.operation import Operation
from ..core.ndmapping import item_check
from ..core.spaces import get_nested_streams
from ..core.util import (
match_spec, wrap_tuple, get_overlay_spec, unique_iterator,
closest_match, is_number, isfinite, python2sort, disable_constant,
arraylike_types
)
from ..element import Points
from ..streams import LinkedStream, Params
from ..util.transform import dim
[docs]def displayable(obj):
"""
Predicate that returns whether the object is displayable or not
(i.e. whether the object obeys the nesting hierarchy)
"""
if isinstance(obj, Overlay) and any(isinstance(o, (HoloMap, GridSpace, AdjointLayout))
for o in obj):
return False
if isinstance(obj, HoloMap):
return not (obj.type in [Layout, GridSpace, NdLayout, DynamicMap])
if isinstance(obj, (GridSpace, Layout, NdLayout)):
for el in obj.values():
if not displayable(el):
return False
return True
return True
[docs]class Warning(param.Parameterized): pass
display_warning = Warning(name='Warning')
def collate(obj):
if isinstance(obj, Overlay):
nested_type = [type(o).__name__ for o in obj
if isinstance(o, (HoloMap, GridSpace, AdjointLayout))][0]
display_warning.param.warning(
"Nesting %ss within an Overlay makes it difficult to "
"access your data or control how it appears; we recommend "
"calling .collate() on the Overlay in order to follow the "
"recommended nesting structure shown in the Composing Data "
"user guide (http://goo.gl/2YS8LJ)" % nested_type)
return obj.collate()
if isinstance(obj, DynamicMap):
if obj.type in [DynamicMap, HoloMap]:
obj_name = obj.type.__name__
raise Exception("Nesting a %s inside a DynamicMap is not "
"supported. Ensure that the DynamicMap callback "
"returns an Element or (Nd)Overlay. If you have "
"applied an operation ensure it is not dynamic by "
"setting dynamic=False." % obj_name)
return obj.collate()
if isinstance(obj, HoloMap):
display_warning.param.warning(
"Nesting {0}s within a {1} makes it difficult to access "
"your data or control how it appears; we recommend "
"calling .collate() on the {1} in order to follow the "
"recommended nesting structure shown in the Composing "
"Data user guide (https://goo.gl/2YS8LJ)".format(
obj.type.__name__, type(obj).__name__))
return obj.collate()
elif isinstance(obj, (Layout, NdLayout)):
try:
display_warning.param.warning(
"Layout contains HoloMaps which are not nested in the "
"recommended format for accessing your data; calling "
".collate() on these objects will resolve any violations "
"of the recommended nesting presented in the Composing Data "
"tutorial (https://goo.gl/2YS8LJ)")
expanded = []
for el in obj.values():
if isinstance(el, HoloMap) and not displayable(el):
collated_layout = Layout(el.collate())
expanded.extend(collated_layout.values())
return Layout(expanded)
except:
raise Exception(undisplayable_info(obj))
else:
raise Exception(undisplayable_info(obj))
[docs]def isoverlay_fn(obj):
"""
Determines whether object is a DynamicMap returning (Nd)Overlay types.
"""
return isinstance(obj, DynamicMap) and (isinstance(obj.last, CompositeOverlay))
[docs]def overlay_depth(obj):
"""
Computes the depth of a DynamicMap overlay if it can be determined
otherwise return None.
"""
if isinstance(obj, DynamicMap):
if isinstance(obj.last, CompositeOverlay):
return len(obj.last)
elif obj.last is None:
return None
return 1
else:
return 1
[docs]def compute_overlayable_zorders(obj, path=[]):
"""
Traverses an overlayable composite container to determine which
objects are associated with specific (Nd)Overlay layers by
z-order, making sure to take DynamicMap Callables into
account. Returns a mapping between the zorders of each layer and a
corresponding lists of objects.
Used to determine which overlaid subplots should be linked with
Stream callbacks.
"""
path = path+[obj]
zorder_map = defaultdict(list)
# Process non-dynamic layers
if not isinstance(obj, DynamicMap):
if isinstance(obj, CompositeOverlay):
for z, o in enumerate(obj):
zorder_map[z] = [o, obj]
elif isinstance(obj, HoloMap):
for el in obj.values():
if isinstance(el, CompositeOverlay):
for k, v in compute_overlayable_zorders(el, path).items():
zorder_map[k] += v + [obj]
else:
zorder_map[0] += [obj, el]
else:
if obj not in zorder_map[0]:
zorder_map[0].append(obj)
return zorder_map
isoverlay = isinstance(obj.last, CompositeOverlay)
isdynoverlay = obj.callback._is_overlay
if obj not in zorder_map[0] and not isoverlay:
zorder_map[0].append(obj)
depth = overlay_depth(obj)
# Process the inputs of the DynamicMap callback
dmap_inputs = obj.callback.inputs if obj.callback.link_inputs else []
for z, inp in enumerate(dmap_inputs):
no_zorder_increment = False
if any(not (isoverlay_fn(p) or p.last is None) for p in path) and isoverlay_fn(inp):
# If overlay has been collapsed do not increment zorder
no_zorder_increment = True
input_depth = overlay_depth(inp)
if depth is not None and input_depth is not None and depth < input_depth:
# Skips branch of graph where the number of elements in an
# overlay has been reduced but still contains more than one layer
if depth > 1:
continue
else:
no_zorder_increment = True
# Recurse into DynamicMap.callback.inputs and update zorder_map
z = z if isdynoverlay else 0
deep_zorders = compute_overlayable_zorders(inp, path=path)
offset = max(zorder_map.keys())
for dz, objs in deep_zorders.items():
global_z = offset+z if no_zorder_increment else offset+dz+z
zorder_map[global_z] = list(unique_iterator(zorder_map[global_z]+objs))
# If object branches but does not declare inputs (e.g. user defined
# DynamicMaps returning (Nd)Overlay) add the items on the DynamicMap.last
found = any(isinstance(p, DynamicMap) and p.callback._is_overlay for p in path)
linked = any(isinstance(s, (LinkedStream, Params)) and s.linked
for s in obj.streams)
if (found or linked) and isoverlay and not isdynoverlay:
offset = max(zorder_map.keys())
for z, o in enumerate(obj.last):
if isoverlay and linked:
zorder_map[offset+z].append(obj)
if o not in zorder_map[offset+z]:
zorder_map[offset+z].append(o)
return zorder_map
[docs]def is_dynamic_overlay(dmap):
"""
Traverses a DynamicMap graph and determines if any components
were overlaid dynamically (i.e. by * on a DynamicMap).
"""
if not isinstance(dmap, DynamicMap):
return False
elif dmap.callback._is_overlay:
return True
else:
return any(is_dynamic_overlay(dm) for dm in dmap.callback.inputs)
[docs]def split_dmap_overlay(obj, depth=0):
"""
Splits a DynamicMap into the original component layers it was
constructed from by traversing the graph to search for dynamically
overlaid components (i.e. constructed by using * on a DynamicMap).
Useful for assigning subplots of an OverlayPlot the streams that
are responsible for driving their updates. Allows the OverlayPlot
to determine if a stream update should redraw a particular
subplot.
"""
layers = []
if isinstance(obj, DynamicMap):
initialize_dynamic(obj)
if issubclass(obj.type, NdOverlay) and not depth:
for v in obj.last.values():
layers.append(obj)
elif issubclass(obj.type, Overlay):
if obj.callback.inputs and is_dynamic_overlay(obj):
for inp in obj.callback.inputs:
layers += split_dmap_overlay(inp, depth+1)
else:
for v in obj.last.values():
layers.append(obj)
else:
layers.append(obj)
return layers
if isinstance(obj, Overlay):
for k, v in obj.items():
layers.append(v)
else:
layers.append(obj)
return layers
[docs]def initialize_dynamic(obj):
"""
Initializes all DynamicMap objects contained by the object
"""
dmaps = obj.traverse(lambda x: x, specs=[DynamicMap])
for dmap in dmaps:
if dmap.unbounded:
# Skip initialization until plotting code
continue
if not len(dmap):
dmap[dmap._initial_key()]
[docs]def get_plot_frame(map_obj, key_map, cached=False):
"""Returns the current frame in a mapping given a key mapping.
Args:
obj: Nested Dimensioned object
key_map: Dictionary mapping between dimensions and key value
cached: Whether to allow looking up key in cache
Returns:
The item in the mapping corresponding to the supplied key.
"""
if (map_obj.kdims and len(map_obj.kdims) == 1 and map_obj.kdims[0] == 'Frame' and
not isinstance(map_obj, DynamicMap)):
# Special handling for static plots
return map_obj.last
key = tuple(key_map[kd.name] for kd in map_obj.kdims if kd.name in key_map)
if key in map_obj.data and cached:
return map_obj.data[key]
else:
try:
return map_obj[key]
except KeyError:
return None
except (StopIteration, CallbackError) as e:
raise e
except Exception:
print(traceback.format_exc())
return None
[docs]def get_nested_plot_frame(obj, key_map, cached=False):
"""Extracts a single frame from a nested object.
Replaces any HoloMap or DynamicMap in the nested data structure,
with the item corresponding to the supplied key.
Args:
obj: Nested Dimensioned object
key_map: Dictionary mapping between dimensions and key value
cached: Whether to allow looking up key in cache
Returns:
Nested datastructure where maps are replaced with single frames
"""
clone = obj.map(lambda x: x)
# Ensure that DynamicMaps in the cloned frame have
# identical callback inputs to allow memoization to work
for it1, it2 in zip(obj.traverse(lambda x: x), clone.traverse(lambda x: x)):
if isinstance(it1, DynamicMap):
with disable_constant(it2.callback):
it2.callback.inputs = it1.callback.inputs
with item_check(False):
return clone.map(lambda x: get_plot_frame(x, key_map, cached=cached),
[DynamicMap, HoloMap], clone=False)
[docs]def undisplayable_info(obj, html=False):
"Generate helpful message regarding an undisplayable object"
collate = '<tt>collate</tt>' if html else 'collate'
info = "For more information, please consult the Composing Data tutorial (http://git.io/vtIQh)"
if isinstance(obj, HoloMap):
error = "HoloMap of %s objects cannot be displayed." % obj.type.__name__
remedy = "Please call the %s method to generate a displayable object" % collate
elif isinstance(obj, Layout):
error = "Layout containing HoloMaps of Layout or GridSpace objects cannot be displayed."
remedy = "Please call the %s method on the appropriate elements." % collate
elif isinstance(obj, GridSpace):
error = "GridSpace containing HoloMaps of Layouts cannot be displayed."
remedy = "Please call the %s method on the appropriate elements." % collate
if not html:
return '\n'.join([error, remedy, info])
else:
return "<center>{msg}</center>".format(msg=('<br>'.join(
['<b>%s</b>' % error, remedy, '<i>%s</i>' % info])))
[docs]def compute_sizes(sizes, size_fn, scaling_factor, scaling_method, base_size):
"""
Scales point sizes according to a scaling factor,
base size and size_fn, which will be applied before
scaling.
"""
if sizes.dtype.kind not in ('i', 'f'):
return None
if scaling_method == 'area':
pass
elif scaling_method == 'width':
scaling_factor = scaling_factor**2
else:
raise ValueError(
'Invalid value for argument "scaling_method": "{}". '
'Valid values are: "width", "area".'.format(scaling_method))
sizes = size_fn(sizes)
return (base_size*scaling_factor*sizes)
[docs]def get_axis_padding(padding):
"""
Process a padding value supplied as a tuple or number and returns
padding values for x-, y- and z-axis.
"""
if isinstance(padding, tuple):
if len(padding) == 2:
xpad, ypad = padding
zpad = 0
elif len(padding) == 3:
xpad, ypad, zpad = padding
else:
raise ValueError('Padding must be supplied as an number applied '
'to all axes or a length two or three tuple '
'corresponding to the x-, y- and optionally z-axis')
else:
xpad, ypad, zpad = (padding,)*3
return (xpad, ypad, zpad)
[docs]def get_minimum_span(low, high, span):
"""
If lower and high values are equal ensures they are separated by
the defined span.
"""
if is_number(low) and low == high:
if isinstance(low, np.datetime64):
span = span * np.timedelta64(1, 's')
low, high = low-span, high+span
return low, high
[docs]def get_range(element, ranges, dimension):
"""
Computes the data, soft- and hard-range along a dimension given
an element and a dictionary of ranges.
"""
if dimension and dimension != 'categorical':
if ranges and dimension.name in ranges:
drange = ranges[dimension.name]['data']
srange = ranges[dimension.name]['soft']
hrange = ranges[dimension.name]['hard']
else:
drange = element.range(dimension, dimension_range=False)
srange = dimension.soft_range
hrange = dimension.range
else:
drange = srange = hrange = (np.NaN, np.NaN)
return drange, srange, hrange
[docs]def get_sideplot_ranges(plot, element, main, ranges):
"""
Utility to find the range for an adjoined
plot given the plot, the element, the
Element the plot is adjoined to and the
dictionary of ranges.
"""
key = plot.current_key
dims = element.dimensions()
dim = dims[0] if 'frequency' in dims[1].name or 'count' in dims[1].name else dims[1]
range_item = main
if isinstance(main, HoloMap):
if issubclass(main.type, CompositeOverlay):
range_item = [hm for hm in main._split_overlays()[1]
if dim in hm.dimensions('all')][0]
else:
range_item = HoloMap({0: main}, kdims=['Frame'])
ranges = match_spec(range_item.last, ranges)
if dim.name in ranges:
main_range = ranges[dim.name]['combined']
else:
framewise = plot.lookup_options(range_item.last, 'norm').options.get('framewise')
if framewise and range_item.get(key, False):
main_range = range_item[key].range(dim)
else:
main_range = range_item.range(dim)
# If .main is an NdOverlay or a HoloMap of Overlays get the correct style
if isinstance(range_item, HoloMap):
range_item = range_item.last
if isinstance(range_item, CompositeOverlay):
range_item = [ov for ov in range_item
if dim in ov.dimensions('all')][0]
return range_item, main_range, dim
[docs]def within_range(range1, range2):
"""Checks whether range1 is within the range specified by range2."""
range1 = [r if isfinite(r) else None for r in range1]
range2 = [r if isfinite(r) else None for r in range2]
return ((range1[0] is None or range2[0] is None or range1[0] >= range2[0]) and
(range1[1] is None or range2[1] is None or range1[1] <= range2[1]))
def validate_unbounded_mode(holomaps, dynmaps):
composite = HoloMap(enumerate(holomaps), kdims=['testing_kdim'])
holomap_kdims = set(unique_iterator([kd.name for dm in holomaps for kd in dm.kdims]))
hmranges = {d: composite.range(d) for d in holomap_kdims}
if any(not set(d.name for d in dm.kdims) <= holomap_kdims
for dm in dynmaps):
raise Exception('DynamicMap that are unbounded must have key dimensions that are a '
'subset of dimensions of the HoloMap(s) defining the keys.')
elif not all(within_range(hmrange, dm.range(d)) for dm in dynmaps
for d, hmrange in hmranges.items() if d in dm.kdims):
raise Exception('HoloMap(s) have keys outside the ranges specified on '
'the DynamicMap(s).')
[docs]def get_dynamic_mode(composite):
"Returns the common mode of the dynamic maps in given composite object"
dynmaps = composite.traverse(lambda x: x, [DynamicMap])
holomaps = composite.traverse(lambda x: x, ['HoloMap'])
dynamic_unbounded = any(m.unbounded for m in dynmaps)
if holomaps:
validate_unbounded_mode(holomaps, dynmaps)
elif dynamic_unbounded and not holomaps:
raise Exception("DynamicMaps in unbounded mode must be displayed alongside "
"a HoloMap to define the sampling.")
return dynmaps and not holomaps, dynamic_unbounded
[docs]def initialize_unbounded(obj, dimensions, key):
"""
Initializes any DynamicMaps in unbounded mode.
"""
select = dict(zip([d.name for d in dimensions], key))
try:
obj.select(selection_specs=[DynamicMap], **select)
except KeyError:
pass
[docs]def dynamic_update(plot, subplot, key, overlay, items):
"""
Given a plot, subplot and dynamically generated (Nd)Overlay
find the closest matching Element for that plot.
"""
match_spec = get_overlay_spec(overlay,
wrap_tuple(key),
subplot.current_frame)
specs = [(i, get_overlay_spec(overlay, wrap_tuple(k), el))
for i, (k, el) in enumerate(items)]
closest = closest_match(match_spec, specs)
if closest is None:
return closest, None, False
matched = specs[closest][1]
return closest, matched, match_spec == matched
[docs]def map_colors(arr, crange, cmap, hex=True):
"""
Maps an array of values to RGB hex strings, given
a color range and colormap.
"""
if isinstance(crange, arraylike_types):
xsorted = np.argsort(crange)
ypos = np.searchsorted(crange, arr)
arr = xsorted[ypos]
else:
if isinstance(crange, tuple):
cmin, cmax = crange
else:
cmin, cmax = np.nanmin(arr), np.nanmax(arr)
arr = (arr - cmin) / (cmax-cmin)
arr = np.ma.array(arr, mask=np.logical_not(np.isfinite(arr)))
arr = cmap(arr)
if hex:
return rgb2hex(arr)
else:
return arr
[docs]def resample_palette(palette, ncolors, categorical, cmap_categorical):
"""
Resample the number of colors in a palette to the selected number.
"""
if len(palette) != ncolors:
if categorical and cmap_categorical:
palette = [palette[i%len(palette)] for i in range(ncolors)]
else:
lpad, rpad = -0.5, 0.49999999999
indexes = np.linspace(lpad, (len(palette)-1)+rpad, ncolors)
palette = [palette[int(np.round(v))] for v in indexes]
return palette
[docs]def mplcmap_to_palette(cmap, ncolors=None, categorical=False):
"""
Converts a matplotlib colormap to palette of RGB hex strings."
"""
from matplotlib.colors import Colormap, ListedColormap
ncolors = ncolors or 256
if not isinstance(cmap, Colormap):
import matplotlib.cm as cm
# Alias bokeh Category cmaps with mpl tab cmaps
if cmap.startswith('Category'):
cmap = cmap.replace('Category', 'tab')
try:
cmap = cm.get_cmap(cmap)
except:
cmap = cm.get_cmap(cmap.lower())
if isinstance(cmap, ListedColormap):
if categorical:
palette = [rgb2hex(cmap.colors[i%cmap.N]) for i in range(ncolors)]
return palette
elif cmap.N > ncolors:
palette = [rgb2hex(c) for c in cmap(np.arange(cmap.N))]
if len(palette) != ncolors:
palette = [palette[int(v)] for v in np.linspace(0, len(palette)-1, ncolors)]
return palette
return [rgb2hex(c) for c in cmap(np.linspace(0, 1, ncolors))]
def colorcet_cmap_to_palette(cmap, ncolors=None, categorical=False):
from colorcet import palette
categories = ['glasbey']
ncolors = ncolors or 256
cmap_categorical = any(c in cmap for c in categories)
if cmap.endswith('_r'):
palette = list(reversed(palette[cmap[:-2]]))
else:
palette = palette[cmap]
return resample_palette(palette, ncolors, categorical, cmap_categorical)
def bokeh_palette_to_palette(cmap, ncolors=None, categorical=False):
from bokeh import palettes
# Handle categorical colormaps to avoid interpolation
categories = ['accent', 'category', 'dark', 'colorblind', 'pastel',
'set1', 'set2', 'set3', 'paired']
cmap_categorical = any(cat in cmap.lower() for cat in categories)
reverse = False
if cmap.endswith('_r'):
cmap = cmap[:-2]
reverse = True
# Some colormaps are inverted compared to matplotlib
inverted = (not cmap_categorical and not cmap.capitalize() in palettes.mpl
and not cmap.startswith('fire'))
if inverted:
reverse=not reverse
ncolors = ncolors or 256
# Alias mpl tab cmaps with bokeh Category cmaps
if cmap.startswith('tab'):
cmap = cmap.replace('tab', 'Category')
# Process as bokeh palette
if cmap in palettes.all_palettes:
palette = palettes.all_palettes[cmap]
else:
palette = getattr(palettes, cmap, getattr(palettes, cmap.capitalize(), None))
if palette is None:
raise ValueError("Supplied palette %s not found among bokeh palettes" % cmap)
elif isinstance(palette, dict) and (cmap in palette or cmap.capitalize() in palette):
# Some bokeh palettes are doubly nested
palette = palette.get(cmap, palette.get(cmap.capitalize()))
if isinstance(palette, dict):
palette = palette[max(palette)]
if not cmap_categorical:
if len(palette) < ncolors:
palette = polylinear_gradient(palette, ncolors)
elif callable(palette):
palette = palette(ncolors)
if reverse: palette = palette[::-1]
return list(resample_palette(palette, ncolors, categorical, cmap_categorical))
[docs]def linear_gradient(start_hex, finish_hex, n=10):
"""
Interpolates the color gradient between to hex colors
"""
s = hex2rgb(start_hex)
f = hex2rgb(finish_hex)
gradient = [s]
for t in range(1, n):
curr_vector = [int(s[j] + (float(t)/(n-1))*(f[j]-s[j])) for j in range(3)]
gradient.append(curr_vector)
return [rgb2hex([c/255. for c in rgb]) for rgb in gradient]
[docs]def polylinear_gradient(colors, n):
"""
Interpolates the color gradients between a list of hex colors.
"""
n_out = int(float(n) / (len(colors)-1))
gradient = linear_gradient(colors[0], colors[1], n_out)
if len(colors) == len(gradient):
return gradient
for col in range(1, len(colors) - 1):
next_colors = linear_gradient(colors[col], colors[col+1], n_out+1)
gradient += next_colors[1:] if len(next_colors) > 1 else next_colors
return gradient
cmap_info=[]
CMapInfo=namedtuple('CMapInfo',['name','provider','category','source','bg'])
providers = ['matplotlib', 'bokeh', 'colorcet']
def _list_cmaps(provider=None, records=False):
"""
List available colormaps by combining matplotlib, bokeh, and
colorcet colormaps or palettes if available. May also be
narrowed down to a particular provider or list of providers.
"""
if provider is None:
provider = providers
elif isinstance(provider, str):
if provider not in providers:
raise ValueError('Colormap provider %r not recognized, must '
'be one of %r' % (provider, providers))
provider = [provider]
cmaps = []
def info(provider,names):
return [CMapInfo(name=n,provider=provider,category=None,source=None,bg=None) for n in names] \
if records else list(names)
if 'matplotlib' in provider:
try:
import matplotlib.cm as cm
if hasattr(cm, '_cmap_registry'):
mpl_cmaps = list(cm._cmap_registry)
else:
mpl_cmaps = list(cm.cmaps_listed)+list(cm.datad)
cmaps += info('matplotlib', mpl_cmaps)
cmaps += info('matplotlib', [cmap+'_r' for cmap in mpl_cmaps
if not cmap.endswith('_r')])
except:
pass
if 'bokeh' in provider:
try:
from bokeh import palettes
cmaps += info('bokeh', palettes.all_palettes)
cmaps += info('bokeh', [p+'_r' for p in palettes.all_palettes
if not p.endswith('_r')])
except:
pass
if 'colorcet' in provider:
try:
from colorcet import palette_n, glasbey_hv
cet_maps = palette_n.copy()
cet_maps['glasbey_hv'] = glasbey_hv # Add special hv-specific map
cmaps += info('colorcet', cet_maps)
cmaps += info('colorcet', [p+'_r' for p in cet_maps if not p.endswith('_r')])
except:
pass
return sorted(unique_iterator(cmaps))
[docs]def register_cmaps(category, provider, source, bg, names):
"""
Maintain descriptions of colormaps that include the following information:
name - string name for the colormap
category - intended use or purpose, mostly following matplotlib
provider - package providing the colormap directly
source - original source or creator of the colormaps
bg - base/background color expected for the map
('light','dark','medium','any' (unknown or N/A))
"""
for name in names:
bisect.insort(cmap_info, CMapInfo(name=name, provider=provider,
category=category, source=source,
bg=bg))
[docs]def list_cmaps(provider=None, records=False, name=None, category=None, source=None,
bg=None, reverse=None):
"""
Return colormap names matching the specified filters.
"""
# Only uses names actually imported and currently available
available = _list_cmaps(provider=provider, records=True)
matches = set()
for avail in available:
aname=avail.name
matched=False
basename=aname[:-2] if aname.endswith('_r') else aname
if (reverse is None or
(reverse==True and aname.endswith('_r')) or
(reverse==False and not aname.endswith('_r'))):
for r in cmap_info:
if (r.name==basename):
matched=True
# cmap_info stores only non-reversed info, so construct
# suitable values for reversed version if appropriate
r=r._replace(name=aname)
if aname.endswith('_r') and (r.category != 'Diverging'):
if r.bg=='light':
r=r._replace(bg='dark')
elif r.bg=='dark':
r=r._replace(bg='light')
if (( name is None or name in r.name) and
(provider is None or provider in r.provider) and
(category is None or category in r.category) and
( source is None or source in r.source) and
( bg is None or bg in r.bg)):
matches.add(r)
if not matched and (category is None or category=='Miscellaneous'):
# Return colormaps that exist but are not found in cmap_info
# under the 'Miscellaneous' category, with no source or bg
r = CMapInfo(aname,provider=avail.provider,category='Miscellaneous',source=None,bg=None)
matches.add(r)
# Return results sorted by category if category information is provided
if records:
return list(unique_iterator(python2sort(matches,
key=lambda r: (r.category.split(" ")[-1],r.bg,r.name.lower(),r.provider,r.source))))
else:
return list(unique_iterator(sorted([rec.name for rec in matches], key=lambda n:n.lower())))
register_cmaps('Uniform Sequential', 'matplotlib', 'bids', 'dark',
['viridis', 'plasma', 'inferno', 'magma', 'cividis'])
register_cmaps('Mono Sequential', 'matplotlib', 'colorbrewer', 'light',
['Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds',
'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu',
'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn'])
register_cmaps('Other Sequential', 'matplotlib', 'misc', 'light',
['gist_yarg', 'binary'])
register_cmaps('Other Sequential', 'matplotlib', 'misc', 'dark',
['afmhot', 'gray', 'bone', 'gist_gray', 'gist_heat',
'hot', 'pink'])
register_cmaps('Other Sequential', 'matplotlib', 'misc', 'any',
['copper', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia'])
register_cmaps('Diverging', 'matplotlib', 'colorbrewer', 'light',
['BrBG', 'PiYG', 'PRGn', 'PuOr', 'RdBu', 'RdGy',
'RdYlBu', 'RdYlGn', 'Spectral'])
register_cmaps('Diverging', 'matplotlib', 'misc', 'light',
['coolwarm', 'bwr', 'seismic'])
register_cmaps('Categorical', 'matplotlib', 'colorbrewer', 'any',
['Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2',
'Set1', 'Set2', 'Set3'])
register_cmaps('Categorical', 'matplotlib', 'd3', 'any',
['tab10', 'tab20', 'tab20b', 'tab20c'])
register_cmaps('Rainbow', 'matplotlib', 'misc', 'dark',
['nipy_spectral', 'gist_ncar'])
register_cmaps('Rainbow', 'matplotlib', 'misc', 'any',
['brg', 'hsv', 'gist_rainbow', 'rainbow', 'jet'])
register_cmaps('Miscellaneous', 'matplotlib', 'misc', 'dark',
['CMRmap', 'cubehelix', 'gist_earth', 'gist_stern',
'gnuplot', 'gnuplot2', 'ocean', 'terrain'])
register_cmaps('Miscellaneous', 'matplotlib', 'misc', 'any',
['flag', 'prism'])
register_cmaps('Uniform Sequential', 'colorcet', 'cet', 'dark',
['bgyw', 'bgy', 'kbc', 'bmw', 'bmy', 'kgy', 'gray',
'dimgray', 'fire'])
register_cmaps('Uniform Sequential', 'colorcet', 'cet', 'any',
['blues', 'kr', 'kg', 'kb'])
register_cmaps('Uniform Diverging', 'colorcet', 'cet', 'light',
['coolwarm', 'gwv', 'bwy', 'cwr'])
register_cmaps('Uniform Diverging', 'colorcet', 'cet', 'dark',
['bkr', 'bky'])
register_cmaps('Uniform Diverging', 'colorcet', 'cet', 'medium',
['bjy'])
register_cmaps('Uniform Rainbow', 'colorcet', 'cet', 'any',
['rainbow', 'colorwheel','isolum'])
register_cmaps('Uniform Sequential', 'bokeh', 'bids', 'dark',
['Viridis', 'Plasma', 'Inferno', 'Magma'])
register_cmaps('Mono Sequential', 'bokeh', 'colorbrewer', 'light',
['Blues', 'BuGn', 'BuPu', 'GnBu', 'Greens', 'Greys',
'OrRd', 'Oranges', 'PuBu', 'PuBuGn', 'PuRd', 'Purples',
'RdPu', 'Reds', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd'])
register_cmaps('Diverging', 'bokeh', 'colorbrewer', 'light',
['BrBG', 'PiYG', 'PRGn', 'PuOr', 'RdBu', 'RdGy',
'RdYlBu', 'RdYlGn', 'Spectral'])
register_cmaps('Categorical', 'bokeh', 'd3', 'any',
['Category10', 'Category20', 'Category20b', 'Category20c'])
register_cmaps('Categorical', 'bokeh', 'colorbrewer', 'any',
['Accent', 'Dark2', 'Paired', 'Pastel1', 'Pastel2',
'Set1', 'Set2', 'Set3'])
register_cmaps('Categorical', 'bokeh', 'misc', 'any',
['Colorblind'])
register_cmaps('Uniform Categorical', 'colorcet', 'cet', 'any',
['glasbey', 'glasbey_cool', 'glasbey_warm', 'glasbey_hv'])
register_cmaps('Uniform Categorical', 'colorcet', 'cet', 'dark',
['glasbey_light'])
register_cmaps('Uniform Categorical', 'colorcet', 'cet', 'light',
['glasbey_dark'])
[docs]def process_cmap(cmap, ncolors=None, provider=None, categorical=False):
"""
Convert valid colormap specifications to a list of colors.
"""
providers_checked="matplotlib, bokeh, or colorcet" if provider is None else provider
if isinstance(cmap, Cycle):
palette = [rgb2hex(c) if isinstance(c, tuple) else c for c in cmap.values]
elif isinstance(cmap, tuple):
palette = list(cmap)
elif isinstance(cmap, list):
palette = cmap
elif isinstance(cmap, str):
mpl_cmaps = _list_cmaps('matplotlib')
bk_cmaps = _list_cmaps('bokeh')
cet_cmaps = _list_cmaps('colorcet')
if provider == 'matplotlib' or (provider is None and (cmap in mpl_cmaps or cmap.lower() in mpl_cmaps)):
palette = mplcmap_to_palette(cmap, ncolors, categorical)
elif provider == 'bokeh' or (provider is None and (cmap in bk_cmaps or cmap.capitalize() in bk_cmaps)):
palette = bokeh_palette_to_palette(cmap, ncolors, categorical)
elif provider == 'colorcet' or (provider is None and cmap in cet_cmaps):
palette = colorcet_cmap_to_palette(cmap, ncolors, categorical)
else:
raise ValueError("Supplied cmap %s not found among %s colormaps." %
(cmap,providers_checked))
else:
try:
# Try processing as matplotlib colormap
palette = mplcmap_to_palette(cmap, ncolors)
except:
palette = None
if not isinstance(palette, list):
raise TypeError("cmap argument %s expects a list, Cycle or valid %s colormap or palette."
% (cmap,providers_checked))
if ncolors and len(palette) != ncolors:
return [palette[i%len(palette)] for i in range(ncolors)]
return palette
[docs]def color_intervals(colors, levels, clip=None, N=255):
"""
Maps the supplied colors into bins defined by the supplied levels.
If a clip tuple is defined the bins are clipped to the defined
range otherwise the range is computed from the levels and returned.
Arguments
---------
colors: list
List of colors (usually hex string or named colors)
levels: list or array_like
Levels specifying the bins to map the colors to
clip: tuple (optional)
Lower and upper limits of the color range
N: int
Number of discrete colors to map the range onto
Returns
-------
cmap: list
List of colors
clip: tuple
Lower and upper bounds of the color range
"""
if len(colors) != len(levels)-1:
raise ValueError('The number of colors in the colormap '
'must match the intervals defined in the '
'color_levels, expected %d colors found %d.'
% (N, len(colors)))
intervals = np.diff(levels)
cmin, cmax = min(levels), max(levels)
interval = cmax-cmin
cmap = []
for intv, c in zip(intervals, colors):
cmap += [c]*int(round(N*(intv/interval)))
if clip is not None:
clmin, clmax = clip
lidx = int(round(N*((clmin-cmin)/interval)))
uidx = int(round(N*((cmax-clmax)/interval)))
uidx = N-uidx
if lidx == uidx:
uidx = lidx+1
cmap = cmap[lidx:uidx]
if clmin == clmax:
idx = np.argmin(np.abs(np.array(levels)-clmin))
clip = levels[idx: idx+2] if len(levels) > idx+2 else levels[idx-1: idx+1]
return cmap, clip
[docs]def dim_axis_label(dimensions, separator=', '):
"""
Returns an axis label for one or more dimensions.
"""
if not isinstance(dimensions, list): dimensions = [dimensions]
return separator.join([d.pprint_label for d in dimensions])
[docs]def scale_fontsize(size, scaling):
"""
Scales a numeric or string font size.
"""
ext = None
if isinstance(size, str):
match = re.match(r"[-+]?\d*\.\d+|\d+", size)
if match:
value = match.group()
ext = size.replace(value, '')
size = float(value)
else:
return size
if scaling:
size = size * scaling
if ext is not None:
size = ('%.3f' % size).rstrip('0').rstrip('.') + ext
return size
[docs]def attach_streams(plot, obj, precedence=1.1):
"""
Attaches plot refresh to all streams on the object.
"""
def append_refresh(dmap):
for stream in get_nested_streams(dmap):
if plot.refresh not in stream._subscribers:
stream.add_subscriber(plot.refresh, precedence)
return obj.traverse(append_refresh, [DynamicMap])
[docs]def traverse_setter(obj, attribute, value):
"""
Traverses the object and sets the supplied attribute on the
object. Supports Dimensioned and DimensionedPlot types.
"""
obj.traverse(lambda x: setattr(x, attribute, value))
def _get_min_distance_numpy(element):
"""
NumPy based implementation of get_min_distance
"""
xys = element.array([0, 1])
with warnings.catch_warnings():
warnings.filterwarnings('ignore', r'invalid value encountered in')
xys = xys.astype('float32').view(np.complex64)
distances = np.abs(xys.T-xys)
np.fill_diagonal(distances, np.inf)
distances = distances[distances>0]
if len(distances):
return distances.min()
return 0
[docs]def get_min_distance(element):
"""
Gets the minimum sampling distance of the x- and y-coordinates
in a grid.
"""
try:
from scipy.spatial.distance import pdist
return pdist(element.array([0, 1])).min()
except:
return _get_min_distance_numpy(element)
[docs]def get_directed_graph_paths(element, arrow_length):
"""
Computes paths for a directed path which include an arrow to
indicate the directionality of each edge.
"""
edgepaths = element._split_edgepaths
edges = edgepaths.split(datatype='array', dimensions=edgepaths.kdims)
arrows = []
for e in edges:
sx, sy = e[0]
ex, ey = e[1]
rad = np.arctan2(ey-sy, ex-sx)
xa0 = ex - np.cos(rad+np.pi/8)*arrow_length
ya0 = ey - np.sin(rad+np.pi/8)*arrow_length
xa1 = ex - np.cos(rad-np.pi/8)*arrow_length
ya1 = ey - np.sin(rad-np.pi/8)*arrow_length
arrow = np.array([(sx, sy), (ex, ey), (np.nan, np.nan),
(xa0, ya0), (ex, ey), (xa1, ya1)])
arrows.append(arrow)
return arrows
[docs]def rgb2hex(rgb):
"""
Convert RGB(A) tuple to hex.
"""
if len(rgb) > 3:
rgb = rgb[:-1]
return "#{0:02x}{1:02x}{2:02x}".format(*(int(v*255) for v in rgb))
[docs]def dim_range_key(eldim):
"""
Returns the key to look up a dimension range.
"""
if isinstance(eldim, dim):
dim_name = repr(eldim)
if dim_name.startswith("dim('") and dim_name.endswith("')"):
dim_name = dim_name[5:-2]
else:
dim_name = eldim.name
return dim_name
[docs]def hex2rgb(hex):
''' "#FFFFFF" -> [255,255,255] '''
# Pass 16 to the integer function for change of base
return [int(hex[i:i+2], 16) for i in range(1,6,2)]
[docs]class apply_nodata(Operation):
nodata = param.Integer(default=None, doc="""
Optional missing-data value for integer data.
If non-None, data with this value will be replaced with NaN so
that it is transparent (by default) when plotted.""")
def _replace_value(self, data):
"Replace `nodata` value in data with NaN, if specified in opts"
data = data.astype('float64')
mask = data!=self.p.nodata
if hasattr(data, 'where'):
return data.where(mask, np.NaN)
return np.where(mask, data, np.NaN)
def _process(self, element, key=None):
if self.p.nodata is None:
return element
if hasattr(element, 'interface'):
vdim = element.vdims[0]
dtype = element.interface.dtype(element, vdim)
if dtype.kind not in 'iu':
return element
transform = dim(vdim, self._replace_value)
return element.transform(**{vdim.name: transform})
else:
array = element.dimension_values(2, flat=False).T
if array.dtype.kind not in 'iu':
return element
array = array.astype('float64')
return element.clone(self._replace_value(array))
RGB_HEX_REGEX = re.compile(r'^#(?:[0-9a-fA-F]{3}){1,2}$')
COLOR_ALIASES = {
'b': (0, 0, 1),
'c': (0, 0.75, 0.75),
'g': (0, 0.5, 0),
'k': (0, 0, 0),
'm': (0.75, 0, 0.75),
'r': (1, 0, 0),
'w': (1, 1, 1),
'y': (0.75, 0.75, 0),
'transparent': (0, 0, 0, 0)
}
# linear_kryw_0_100_c71 (aka "fire"):
# A perceptually uniform equivalent of matplotlib's "hot" colormap, from
# http://peterkovesi.com/projects/colourmaps
fire_colors = linear_kryw_0_100_c71 = [\
[0, 0, 0 ], [0.027065, 2.143e-05, 0 ],
[0.052054, 7.4728e-05, 0 ], [0.071511, 0.00013914, 0 ],
[0.08742, 0.0002088, 0 ], [0.10109, 0.00028141, 0 ],
[0.11337, 0.000356, 2.4266e-17], [0.12439, 0.00043134, 3.3615e-17],
[0.13463, 0.00050796, 2.1604e-17], [0.14411, 0.0005856, 0 ],
[0.15292, 0.00070304, 0 ], [0.16073, 0.0013432, 0 ],
[0.16871, 0.0014516, 0 ], [0.17657, 0.0012408, 0 ],
[0.18364, 0.0015336, 0 ], [0.19052, 0.0017515, 0 ],
[0.19751, 0.0015146, 0 ], [0.20401, 0.0015249, 0 ],
[0.20994, 0.0019639, 0 ], [0.21605, 0.002031, 0 ],
[0.22215, 0.0017559, 0 ], [0.22808, 0.001546, 1.8755e-05],
[0.23378, 0.0016315, 3.5012e-05], [0.23955, 0.0017194, 3.3352e-05],
[0.24531, 0.0018097, 1.8559e-05], [0.25113, 0.0019038, 1.9139e-05],
[0.25694, 0.0020015, 3.5308e-05], [0.26278, 0.0021017, 3.2613e-05],
[0.26864, 0.0022048, 2.0338e-05], [0.27451, 0.0023119, 2.2453e-05],
[0.28041, 0.0024227, 3.6003e-05], [0.28633, 0.0025363, 2.9817e-05],
[0.29229, 0.0026532, 1.9559e-05], [0.29824, 0.0027747, 2.7666e-05],
[0.30423, 0.0028999, 3.5752e-05], [0.31026, 0.0030279, 2.3231e-05],
[0.31628, 0.0031599, 1.2902e-05], [0.32232, 0.0032974, 3.2915e-05],
[0.32838, 0.0034379, 3.2803e-05], [0.33447, 0.0035819, 2.0757e-05],
[0.34057, 0.003731, 2.3831e-05], [0.34668, 0.0038848, 3.502e-05 ],
[0.35283, 0.0040418, 2.4468e-05], [0.35897, 0.0042032, 1.1444e-05],
[0.36515, 0.0043708, 3.2793e-05], [0.37134, 0.0045418, 3.012e-05 ],
[0.37756, 0.0047169, 1.4846e-05], [0.38379, 0.0048986, 2.796e-05 ],
[0.39003, 0.0050848, 3.2782e-05], [0.3963, 0.0052751, 1.9244e-05],
[0.40258, 0.0054715, 2.2667e-05], [0.40888, 0.0056736, 3.3223e-05],
[0.41519, 0.0058798, 2.159e-05 ], [0.42152, 0.0060922, 1.8214e-05],
[0.42788, 0.0063116, 3.2525e-05], [0.43424, 0.0065353, 2.2247e-05],
[0.44062, 0.006765, 1.5852e-05], [0.44702, 0.0070024, 3.1769e-05],
[0.45344, 0.0072442, 2.1245e-05], [0.45987, 0.0074929, 1.5726e-05],
[0.46631, 0.0077499, 3.0976e-05], [0.47277, 0.0080108, 1.8722e-05],
[0.47926, 0.0082789, 1.9285e-05], [0.48574, 0.0085553, 3.0063e-05],
[0.49225, 0.0088392, 1.4313e-05], [0.49878, 0.0091356, 2.3404e-05],
[0.50531, 0.0094374, 2.8099e-05], [0.51187, 0.0097365, 6.4695e-06],
[0.51844, 0.010039, 2.5791e-05], [0.52501, 0.010354, 2.4393e-05],
[0.53162, 0.010689, 1.6037e-05], [0.53825, 0.011031, 2.7295e-05],
[0.54489, 0.011393, 1.5848e-05], [0.55154, 0.011789, 2.3111e-05],
[0.55818, 0.012159, 2.5416e-05], [0.56485, 0.012508, 1.5064e-05],
[0.57154, 0.012881, 2.541e-05 ], [0.57823, 0.013283, 1.6166e-05],
[0.58494, 0.013701, 2.263e-05 ], [0.59166, 0.014122, 2.3316e-05],
[0.59839, 0.014551, 1.9432e-05], [0.60514, 0.014994, 2.4323e-05],
[0.6119, 0.01545, 1.3929e-05], [0.61868, 0.01592, 2.1615e-05],
[0.62546, 0.016401, 1.5846e-05], [0.63226, 0.016897, 2.0838e-05],
[0.63907, 0.017407, 1.9549e-05], [0.64589, 0.017931, 2.0961e-05],
[0.65273, 0.018471, 2.0737e-05], [0.65958, 0.019026, 2.0621e-05],
[0.66644, 0.019598, 2.0675e-05], [0.67332, 0.020187, 2.0301e-05],
[0.68019, 0.020793, 2.0029e-05], [0.68709, 0.021418, 2.0088e-05],
[0.69399, 0.022062, 1.9102e-05], [0.70092, 0.022727, 1.9662e-05],
[0.70784, 0.023412, 1.7757e-05], [0.71478, 0.024121, 1.8236e-05],
[0.72173, 0.024852, 1.4944e-05], [0.7287, 0.025608, 2.0245e-06],
[0.73567, 0.02639, 1.5013e-07], [0.74266, 0.027199, 0 ],
[0.74964, 0.028038, 0 ], [0.75665, 0.028906, 0 ],
[0.76365, 0.029806, 0 ], [0.77068, 0.030743, 0 ],
[0.77771, 0.031711, 0 ], [0.78474, 0.032732, 0 ],
[0.79179, 0.033741, 0 ], [0.79886, 0.034936, 0 ],
[0.80593, 0.036031, 0 ], [0.81299, 0.03723, 0 ],
[0.82007, 0.038493, 0 ], [0.82715, 0.039819, 0 ],
[0.83423, 0.041236, 0 ], [0.84131, 0.042647, 0 ],
[0.84838, 0.044235, 0 ], [0.85545, 0.045857, 0 ],
[0.86252, 0.047645, 0 ], [0.86958, 0.049578, 0 ],
[0.87661, 0.051541, 0 ], [0.88365, 0.053735, 0 ],
[0.89064, 0.056168, 0 ], [0.89761, 0.058852, 0 ],
[0.90451, 0.061777, 0 ], [0.91131, 0.065281, 0 ],
[0.91796, 0.069448, 0 ], [0.92445, 0.074684, 0 ],
[0.93061, 0.08131, 0 ], [0.93648, 0.088878, 0 ],
[0.94205, 0.097336, 0 ], [0.9473, 0.10665, 0 ],
[0.9522, 0.1166, 0 ], [0.95674, 0.12716, 0 ],
[0.96094, 0.13824, 0 ], [0.96479, 0.14963, 0 ],
[0.96829, 0.16128, 0 ], [0.97147, 0.17303, 0 ],
[0.97436, 0.18489, 0 ], [0.97698, 0.19672, 0 ],
[0.97934, 0.20846, 0 ], [0.98148, 0.22013, 0 ],
[0.9834, 0.23167, 0 ], [0.98515, 0.24301, 0 ],
[0.98672, 0.25425, 0 ], [0.98815, 0.26525, 0 ],
[0.98944, 0.27614, 0 ], [0.99061, 0.28679, 0 ],
[0.99167, 0.29731, 0 ], [0.99263, 0.30764, 0 ],
[0.9935, 0.31781, 0 ], [0.99428, 0.3278, 0 ],
[0.995, 0.33764, 0 ], [0.99564, 0.34735, 0 ],
[0.99623, 0.35689, 0 ], [0.99675, 0.3663, 0 ],
[0.99722, 0.37556, 0 ], [0.99765, 0.38471, 0 ],
[0.99803, 0.39374, 0 ], [0.99836, 0.40265, 0 ],
[0.99866, 0.41145, 0 ], [0.99892, 0.42015, 0 ],
[0.99915, 0.42874, 0 ], [0.99935, 0.43724, 0 ],
[0.99952, 0.44563, 0 ], [0.99966, 0.45395, 0 ],
[0.99977, 0.46217, 0 ], [0.99986, 0.47032, 0 ],
[0.99993, 0.47838, 0 ], [0.99997, 0.48638, 0 ],
[1, 0.4943, 0 ], [1, 0.50214, 0 ],
[1, 0.50991, 1.2756e-05], [1, 0.51761, 4.5388e-05],
[1, 0.52523, 9.6977e-05], [1, 0.5328, 0.00016858],
[1, 0.54028, 0.0002582 ], [1, 0.54771, 0.00036528],
[1, 0.55508, 0.00049276], [1, 0.5624, 0.00063955],
[1, 0.56965, 0.00080443], [1, 0.57687, 0.00098902],
[1, 0.58402, 0.0011943 ], [1, 0.59113, 0.0014189 ],
[1, 0.59819, 0.0016626 ], [1, 0.60521, 0.0019281 ],
[1, 0.61219, 0.0022145 ], [1, 0.61914, 0.0025213 ],
[1, 0.62603, 0.0028496 ], [1, 0.6329, 0.0032006 ],
[1, 0.63972, 0.0035741 ], [1, 0.64651, 0.0039701 ],
[1, 0.65327, 0.0043898 ], [1, 0.66, 0.0048341 ],
[1, 0.66669, 0.005303 ], [1, 0.67336, 0.0057969 ],
[1, 0.67999, 0.006317 ], [1, 0.68661, 0.0068648 ],
[1, 0.69319, 0.0074406 ], [1, 0.69974, 0.0080433 ],
[1, 0.70628, 0.0086756 ], [1, 0.71278, 0.0093486 ],
[1, 0.71927, 0.010023 ], [1, 0.72573, 0.010724 ],
[1, 0.73217, 0.011565 ], [1, 0.73859, 0.012339 ],
[1, 0.74499, 0.01316 ], [1, 0.75137, 0.014042 ],
[1, 0.75772, 0.014955 ], [1, 0.76406, 0.015913 ],
[1, 0.77039, 0.016915 ], [1, 0.77669, 0.017964 ],
[1, 0.78298, 0.019062 ], [1, 0.78925, 0.020212 ],
[1, 0.7955, 0.021417 ], [1, 0.80174, 0.02268 ],
[1, 0.80797, 0.024005 ], [1, 0.81418, 0.025396 ],
[1, 0.82038, 0.026858 ], [1, 0.82656, 0.028394 ],
[1, 0.83273, 0.030013 ], [1, 0.83889, 0.031717 ],
[1, 0.84503, 0.03348 ], [1, 0.85116, 0.035488 ],
[1, 0.85728, 0.037452 ], [1, 0.8634, 0.039592 ],
[1, 0.86949, 0.041898 ], [1, 0.87557, 0.044392 ],
[1, 0.88165, 0.046958 ], [1, 0.88771, 0.04977 ],
[1, 0.89376, 0.052828 ], [1, 0.8998, 0.056209 ],
[1, 0.90584, 0.059919 ], [1, 0.91185, 0.063925 ],
[1, 0.91783, 0.068579 ], [1, 0.92384, 0.073948 ],
[1, 0.92981, 0.080899 ], [1, 0.93576, 0.090648 ],
[1, 0.94166, 0.10377 ], [1, 0.94752, 0.12051 ],
[1, 0.9533, 0.14149 ], [1, 0.959, 0.1672 ],
[1, 0.96456, 0.19823 ], [1, 0.96995, 0.23514 ],
[1, 0.9751, 0.2786 ], [1, 0.97992, 0.32883 ],
[1, 0.98432, 0.38571 ], [1, 0.9882, 0.44866 ],
[1, 0.9915, 0.51653 ], [1, 0.99417, 0.58754 ],
[1, 0.99625, 0.65985 ], [1, 0.99778, 0.73194 ],
[1, 0.99885, 0.80259 ], [1, 0.99953, 0.87115 ],
[1, 0.99989, 0.93683 ], [1, 1, 1 ]]
# Bokeh palette
fire = [str('#{0:02x}{1:02x}{2:02x}'.format(int(r*255),int(g*255),int(b*255)))
for r,g,b in fire_colors]
[docs]class categorical_legend(Operation):
"""
Generates a Points element which contains information for generating
a legend by inspecting the pipeline of a datashaded RGB element.
"""
backend = param.String()
def _process(self, element, key=None):
import datashader as ds
from ..operation.datashader import shade, rasterize, datashade
rasterize_op = element.pipeline.find(rasterize, skip_nonlinked=False)
if isinstance(rasterize_op, datashade):
shade_op = rasterize_op
else:
shade_op = element.pipeline.find(shade, skip_nonlinked=False)
if None in (shade_op, rasterize_op):
return None
hvds = element.dataset
input_el = element.pipeline.operations[0](hvds)
agg = rasterize_op._get_aggregator(input_el, rasterize_op.aggregator)
if not isinstance(agg, (ds.count_cat, ds.by)):
return
column = agg.column
if hasattr(hvds.data, 'dtypes'):
cats = list(hvds.data.dtypes[column].categories)
if cats == ['__UNKNOWN_CATEGORIES__']:
cats = list(hvds.data[column].cat.as_known().categories)
else:
cats = list(hvds.dimension_values(column, expanded=False))
colors = shade_op.color_key or ds.colors.Sets1to3
color_data = [(0, 0, cat) for cat in cats]
if isinstance(colors, list):
cat_colors = {cat: colors[i] for i, cat in enumerate(cats)}
else:
cat_colors = {cat: colors[cat] for cat in cats}
cmap = {}
for cat, color in cat_colors.items():
if isinstance(color, tuple):
color = rgb2hex([v/256 for v in color[:3]])
cmap[cat] = color
return Points(color_data, vdims=['category']).opts(
cmap=cmap, color='category', show_legend=True,
backend=self.p.backend, visible=False)