Source code for dasher.layout.bootstrap.widgets

""" Widget specification and implementation of the interactive
dasher widgets based on ``dash_bootstrap_components``.

The widget specification supports the following types and generates the corresponding
interactive widgets:

* ``bool``: Radio Items
* ``str``: Input field
* ``int``: Slider, integer
* ``float``: Slider, floats
* ``tuple``: Slider
  Can be (min, max) or (min, max, step). The type of all the tuple entries
  must either be ``int`` or ``float``, which determines whether an integer or
  float slider will be generated.
* ``collections.Iterable``: Dropdown menu
  Typically a ``list`` or anything iterable.
* ``collections.Mapping``: Dropdown menu
  Typically a ``dict``. A mapping will use the keys as labels shown in the
  dropdown menu, while the values will be used as arguments to the callback
  function.
* ``dash.development.base_component.Component``: custom dash component
  Any dash component will be used as-is. This allows full customization of a
  widget if desired. The widgets ``value`` will be used as argument to
  the callback function.

"""

from abc import ABC
from collections import OrderedDict
from collections.abc import Iterable
from collections.abc import Mapping
from numbers import Integral
from numbers import Real

import dash_bootstrap_components as dbc
import dash_core_components as dcc
from dash.development.base_component import Component

from dasher.base import BaseWidget
from dasher.base import CustomWidget
from dasher.base import WidgetPassthroughMixin

from .min_max_value import get_min_max_value


[docs]class BootstrapWidget(BaseWidget, ABC): """ Abstract base class for Bootstrap widgets. Implements the default layout property, which is used by most the widgets. """ @property def layout(self): return dbc.FormGroup( [dbc.Label(self.label, html_for=self.name), self.component] )
[docs]class PassthroughWidget(BootstrapWidget, WidgetPassthroughMixin): """ Passthrough for custom dash components. """ pass
[docs]class BoolWidget(BootstrapWidget): """ RadioItems component used for booleans. Parameters ---------- name: str Name of the widget. x: tuple of int or float Tuple used to configure the slider. label: str, optional The label for the component. dependency: str, optional The attribute used for the ``dash.dependencies.Input`` dependency. Default: "checked". """ def __init__(self, name, x, label=None, dependency="checked"): super().__init__(name, x, label, dependency) @property def component(self): return dbc.Checkbox(id=self.name, checked=False, className="form-check-input") @property def layout(self): return dbc.FormGroup( [ self.component, dbc.Label(self.label, html_for=self.name, className="form-check-label"), ], check=True, )
[docs]class StringWidget(BootstrapWidget): """ Input field component used for for strings. """ @property def component(self): return dbc.Input(id=self.name, type="text", value=self.x)
[docs]class IterableWidget(BootstrapWidget): """ Dropdown component used for iterables and mappings. """ @property def component(self): if isinstance(self.x, Mapping): options = [{"label": k, "value": v} for k, v in self.x.items()] else: options = [{"label": x, "value": x} for x in self.x] if len(options) > 0: return dcc.Dropdown( id=self.name, options=options, clearable=False, value=options[0]["value"], ) else: return None
[docs]class TupleWidget(BootstrapWidget): """ Slider components used for tuples of numbers. Parameters ---------- name: str Name of the widget. x: tuple of int or float Tuple used to configure the slider. label: str, optional The label for the component. dependency: str, optional The attribute used for the ``dash.dependencies.Input`` dependency. Default: "value". slider_max_ticks: int, default 8 Maximum number of ticks to draw for the slider. slider_float_steps: int, default 60 Number of float steps to use if step is not defined explicity. """ def __init__( self, name, x, label=None, dependency="value", slider_max_ticks=8, slider_float_steps=60, ): super().__init__(name, x, label, dependency) self.slider_max_marks = slider_max_ticks self.slider_float_steps = slider_float_steps @property def component(self): step = None if len(self.x) == 1: minimum, maximum, value = get_min_max_value(None, None, x=self.x[0]) elif len(self.x) == 2: minimum, maximum, value = get_min_max_value(self.x[0], self.x[1]) elif len(self.x) == 3: step = self.x[2] if step < 0: raise ValueError("step must be >= 0") minimum, maximum, value = get_min_max_value(self.x[0], self.x[1], step=step) else: raise ValueError("tuple must be (value, ), (min, max) or (min, max, step)") if all(isinstance(i, Integral) for i in self.x): if step is None: step = 1 max_mark_step = (maximum - minimum) // self.slider_max_marks ticks = list(range(minimum, maximum + 1, max(step, max_mark_step))) marks = {i: str(i) for i in ticks} else: if step is None: step = (maximum - minimum) / (self.slider_float_steps - 1) ticks = list( minimum + step * i for i in range(0, int((maximum - minimum) / step)) ) ticks = ticks[:: max(1, len(ticks) // self.slider_max_marks)] + [maximum] marks = {int(i) if i % 1 == 0 else i: "{:.3g}".format(i) for i in ticks} return dcc.Slider( id=self.name, min=minimum, max=maximum, step=step, value=value, marks=marks )
[docs]class NumberWidget(TupleWidget): """ Widget used for numbers. """ def __init__(self, name, x, label=None, dependency="value"): super().__init__(name, (x,), label, dependency)
WIDGET_SPEC = OrderedDict( [ ((Component, CustomWidget), PassthroughWidget), (bool, BoolWidget), (str, StringWidget), ((Real, Integral), NumberWidget), (tuple, TupleWidget), (Iterable, IterableWidget), ] ) """ Widget specification. """