###############################################################################
# Copyright (c), Forschungszentrum Jülich GmbH, IAS-1/PGI-1, Germany. #
# All rights reserved. #
# This file is part of the Masci-tools package. #
# (Material science tools) #
# #
# The code is hosted on GitHub at https://github.com/judftteam/masci-tools #
# For further information on the license, see the LICENSE.txt file #
# For further information please visit http://www.flapw.de or #
# #
###############################################################################
"""
Here the :py:class:`masci_tools.vis.parameters.Plotter` subclass for the bokeh plotting backend
is defined with default values and many helper methods
"""
from .parameters import Plotter, _generate_plot_parameters_table
import copy
[docs]class BokehPlotter(Plotter):
"""
Class for plotting parameters and standard code snippets for plotting with the
bokeh backend.
Kwargs in the __init__ method are forwarded to setting default values for the instance
For specific documentation about the parameter/defaults handling refer to
:py:class:`~masci_tools.vis.parameters.Plotter`.
Below the current defined default values are shown:
"""
_BOKEH_DEFAULTS = {
'figure_kwargs': {
'tools': 'pan,poly_select,tap,wheel_zoom,'
'box_zoom,redo,undo,reset,save,crosshair,zoom_out,zoom_in',
'y_axis_type': 'linear',
'x_axis_type': 'linear',
'active_inspect': None,
'toolbar_location': 'right',
},
'additional_tools': None,
'show_tooltips': True,
'global_tooltips': False,
'format_tooltips': True,
'tooltips': [('X', '@{x}'), ('Y', '@{y}')],
'additional_tooltips': None,
'axis_linewidth': 2,
'label_fontsize': '18pt',
'tick_label_fontsize': '16pt',
'background_fill_color': '#ffffff',
'x_axis_formatter': None,
'y_axis_formatter': None,
'x_ticks': None,
'x_ticklabels_overwrite': None,
'y_ticks': None,
'y_ticklabels_overwrite': None,
'x_range_padding': None,
'y_range_padding': None,
'limits': None,
#legend options
'legend_location': 'top_right',
'legend_click_policy': 'hide', # "mute"#"hide"
'legend_orientation': 'vertical',
'legend_font_size': '14pt',
'legend_outside_plot_area': False,
#plot parameters
'color_palette': None,
'color': None,
'legend_label': None,
'alpha': 1.0,
'name': None,
'line_color': None,
'line_alpha': 1.0,
'line_dash': None,
'line_width': 2.0,
'marker': 'circle',
'marker_size': 6,
'area_plot': False,
'area_vertical': False,
'fill_alpha': 1.0,
'fill_color': None,
'level': None,
'straight_lines': None,
'straight_line_options': {
'line_color': 'black',
'line_width': 1.0,
'line_dash': 'dashed'
},
'text_font_size': '10pt',
'text_font_style': 'normal',
'text_color': 'black',
'text_align': 'left',
'text_baseline': 'middle',
#output control
'save_plots': False,
'save_format': 'html',
'show': True,
}
_BOKEH_DESCRIPTIONS = {
'figure_kwargs':
'Parameters for creating the bokeh figure. '
'Includes things like axis type (x and y), tools '
'plot width/height',
'additional_tools':
'tools to add to the tools already '
'specified in ``figure_kwargs`` (Has to be in the same format)',
'show_tooltips':
'Switch whether to add hover tooltips',
'global_tooltips':
'Switch whether to add individual (for each renderer) or global hover tooltips. ',
'format_tooltips':
'Switch whether to enable the processing of formatted strings in tooltips. ',
'tooltips':
'List of tuples specifying the tooltips. '
'For more information refer to the bokeh documentation. '
"Strings can contain format specifiers with the data keys of the function e.g. ``'@{x}'``. "
'Here the ``{x}`` will be replaced by the entry for x. If there are formatting specifications '
'for bokeh they need to be escaped with double curly braces or ``format_tooltips=False``.',
'additional_tooltips':
'Tooltips to add to the already defined ``tooltips`` (See above)',
'axis_linewidth':
'Linewidth for the lines of the axis',
'label_fontsize':
'Fontsize for the labels of the axis',
'tick_label_fontsize':
'fontsize for the ticks on the axis',
'background_fill_color':
'Color of the background of the plot',
'x_axis_formatter':
'If set this formatter will be used for the ticks on the x-axis',
'y_axis_formatter':
'If set this formatter will be used for the ticks on the y-axis',
'x_ticks':
'Tick specification for the x-axis',
'x_ticklabels_overwrite':
'Overrides the labels for the ticks on the x-axis',
'y_ticks':
'Tick specification for the y-axis',
'y_ticklabels_overwrite':
'Overrides the labels for the ticks on the y-axis',
'x_range_padding':
'Specifies the amount of padding on the edges of the x-axis',
'y_range_padding':
'Specifies the amount of padding on the edges of the y-axis',
'limits':
"Dict specifying the limits of the axis, e.g {'x': (-5,5)}",
#legend options
'legend_location':
'Location of the legend inside the plot area',
'legend_click_policy':
'Policy for what happens when labels are clicked in the legend',
'legend_orientation':
'Orientation of the legend',
'legend_font_size':
'Fontsize for the labels inside the legend',
'legend_outside_plot_area':
'If True the legend will be placed outside of the plot area',
#plot parameters
'color_palette':
'Color palette to use for the plot(s)',
'color':
'Specific colors to use for the plot(s)',
'legend_label':
'Labels to use for the legend of the plot(s)',
'alpha':
'Transparency to use for the plot(s)',
'name':
'Name used for identifying elements in the plot (not shown only internally)',
'line_color':
'Color to use for line plot(s)',
'line_alpha':
'Transparency to use for line plot(s)',
'line_dash':
'Dash styles to use for line plot(s)',
'line_width':
'Line width to use for line plot(s)',
'marker':
'Type of marker to use for scatter plot(s)',
'marker_size':
'Marker size to use for scatter plot(s)',
'area_plot':
'If True h(v)area will be used to produce the plot(s)',
'area_vertical':
'Determines, whether to use harea (False) or varea (True) for area plots',
'fill_alpha':
'Transparency to use for the area in area plot(s)',
'fill_color':
'Color to use for the area in area plot(s)',
'level':
'Can be used to specified, which elements are fore- or background',
'straight_lines':
'Dict specifying straight help-lines to draw. '
"For example {'vertical': 0, 'horizontal': [-1,1]} will draw a vertical line at 0 "
'and two horizontal at -1 and 1',
'straight_line_options':
'Color, width, and more options for the help-lines',
'text_font_size':
'fontsize for the text glyphs in the plot',
'text_font_style':
'fontstyle for the text glyphs in the plot',
'text_color':
'text color for the text glyphs in the plot',
'text_align':
'text alignment for the text glyphs in the plot',
'text_baseline':
'text baseline for the text glyphs in the plot',
#output control
'save_plots':
'If True plots will be saved to file (Configuration beforehand is needed)',
'save_format':
'Formats to save the plots to, can be single or list of formats (html, png or svg)',
'show':
'If True bokeh.io.show will be called after the plotting routine',
}
_BOKEH_GENERAL_ARGS = {
'show',
'save_plots',
'save_format',
'color_palette',
'legend_location',
'legend_click_policy',
'legend_font_size',
'legend_orientation',
'legend_outside_plot_area',
'background_fill_color',
'tick_label_fontsize',
'label_fontsize',
'axis_linewidth',
'figure_kwargs',
'additional_tools',
'show_tooltips',
'global_tooltips',
'format_tooltips',
'tooltips',
'additional_tooltips',
'straight_lines',
'x_axis_formatter',
'y_axis_formatter',
'x_ticks',
'y_ticks',
'y_ticklabels_overwrite',
'x_ticklabels_overwrite',
'x_range_padding',
'y_range_padding',
}
_DEFAULT_KWARGS = {'color', 'alpha', 'legend_label', 'name'}
_TYPE_TO_KWARGS = {
'default': _DEFAULT_KWARGS,
'line': _DEFAULT_KWARGS | {'line_color', 'line_alpha', 'line_dash', 'line_width'},
'scatter': _DEFAULT_KWARGS | {'marker', 'marker_size', 'fill_alpha', 'fill_color'},
'area': _DEFAULT_KWARGS | {'fill_alpha', 'fill_color'},
'image': _DEFAULT_KWARGS | {'color', 'alpha', 'color_palette'},
'text': _DEFAULT_KWARGS | {'text_font_style', 'text_font_size', 'text_color', 'text_align', 'text_baseline'}
}
_POSTPROCESS_RENAMES = {'marker_size': 'size', 'color_palette': 'palette'}
__doc__ = __doc__ + _generate_plot_parameters_table(_BOKEH_DEFAULTS, _BOKEH_DESCRIPTIONS)
def __init__(self, **kwargs):
super().__init__(self._BOKEH_DEFAULTS,
general_keys=self._BOKEH_GENERAL_ARGS,
key_descriptions=self._BOKEH_DESCRIPTIONS,
type_kwargs_mapping=self._TYPE_TO_KWARGS,
kwargs_postprocess_rename=self._POSTPROCESS_RENAMES,
**kwargs)
[docs] def set_color_palette_by_num_plots(self):
"""
Set the colormap for the configured number of plots according to the set colormap or color
copied from https://github.com/PatrikHlobil/Pandas-Bokeh/blob/master/pandas_bokeh/plot.py
credits to PatrikHlobil
modified for use in this Plotter class
"""
from bokeh.palettes import all_palettes #pylint: disable=no-name-in-module
if self['color'] is not None:
color = self['color']
if not isinstance(self['color'], (list, tuple)):
color = [color]
color = color * int(self.num_plots / len(color) + 1)
color = color[:self.num_plots]
elif self['color_palette'] is not None:
if self['color_palette'] in all_palettes:
color = all_palettes[self['color_palette']]
max_key = max(color.keys())
if self.num_plots <= max_key:
color = color[self.num_plots]
else:
color = color[max_key]
color = color * int(self.num_plots / len(color) + 1)
color = color[:self.num_plots]
else:
raise ValueError(
f"Could not find <colormap> with name {self['color_palette']}. The following predefined colormaps are "
f'supported (see also https://bokeh.pydata.org/en/latest/docs/reference/palettes.html ): {list(all_palettes.keys())}'
)
else:
if self.num_plots <= 10:
color = all_palettes['Category10'][10][:self.num_plots]
elif self.num_plots <= 20:
color = all_palettes['Category20'][self.num_plots]
else:
color = all_palettes['Category20'][20] * int(self.num_plots / 20 + 1)
color = color[:self.num_plots]
self['color'] = color
@staticmethod
def _format_tooltips(tooltips, **kwargs):
"""
Format the strings in tooltips with the given kwargs.
:param tooltips: The tooltips list to be formatted
:param kwargs: The keywords arguments used to format the strings
:returns: list of tuples with the tooltips ready to be used for tooltips
"""
import string
for indx, (label, value) in enumerate(tooltips):
if len([val[0] for val in string.Formatter().parse(label)]) != 0:
label = label.format(**kwargs)
if len([val[0] for val in string.Formatter().parse(value)]) != 0:
value = value.format(**kwargs)
tooltips[indx] = (label, value)
return tooltips
[docs] def draw_straight_lines(self, fig):
"""
Draw horizontal and vertical lines specified in the lines argument
:param fig: bokeh figure on which to perform the operation
"""
from bokeh.models import Span
if self['straight_lines'] is not None:
added_lines = []
if 'horizontal' in self['straight_lines']:
lines = copy.deepcopy(self['straight_lines']['horizontal'])
if not isinstance(lines, list):
lines = [lines]
for line_def in lines:
options = copy.deepcopy(self['straight_line_options'])
if isinstance(line_def, dict):
positions = line_def.pop('pos')
if not isinstance(positions, list):
positions = [positions]
options.update(line_def)
elif isinstance(line_def, list):
positions = line_def
else:
positions = [line_def]
for pos in positions:
added_lines.append(Span(location=pos, dimension='width', **options))
if 'vertical' in self['straight_lines']:
lines = copy.deepcopy(self['straight_lines']['vertical'])
if not isinstance(lines, list):
lines = [lines]
for line_def in lines:
options = copy.deepcopy(self['straight_line_options'])
if isinstance(line_def, dict):
positions = line_def.pop('pos')
if not isinstance(positions, list):
positions = [positions]
options.update(line_def)
elif isinstance(line_def, list):
positions = line_def
else:
positions = [line_def]
for pos in positions:
added_lines.append(Span(location=pos, dimension='height', **options))
fig.renderers.extend(added_lines)
[docs] def set_limits(self, fig):
"""
Set limits of the figure
:param fig: bokeh figure on which to perform the operation
"""
from bokeh.models import Range1d
if self['limits'] is not None:
if 'x' in self['limits']:
xmin = self['limits']['x'][0]
xmax = self['limits']['x'][1]
fig.x_range = Range1d(xmin, xmax)
if 'y' in self['limits']:
ymin = self['limits']['y'][0]
ymax = self['limits']['y'][1]
fig.y_range = Range1d(ymin, ymax)
[docs] def set_legend(self, fig):
"""
Set legend options for the figure
:param fig: bokeh figure on which to perform the operation
"""
fig.legend.location = self['legend_location']
fig.legend.background_fill_color = self['background_fill_color']
fig.legend.click_policy = self['legend_click_policy']
fig.legend.orientation = self['legend_orientation']
fig.legend.label_text_font_size = self['legend_font_size']
if self['legend_outside_plot_area']:
fig.add_layout(fig.legend[0], 'right')
[docs] def save_plot(self, figure, saveas):
"""
Show/save the bokeh figure
:param figure: bokeh figure on which to perform the operation
"""
from bokeh.io import show, save, export_png, export_svgs
if self['show']:
show(figure)
if self['save_plots']:
if isinstance(self['save_format'], list):
formats = self['save_format']
else:
formats = [self['save_format']]
for fmt in formats:
savefilename = f'{saveas}.{fmt}'
print(f'Save plot to: {savefilename}')
if fmt == 'html':
save(figure, filename=savefilename)
elif fmt == 'png':
export_png(figure, filename=savefilename)
elif fmt == 'svg':
export_svgs(figure, filename=savefilename)