Ejemplo n.º 1
0
 def test_Either(self) -> None:
     p = Either(Int, Float)
     with pytest.raises(ValueError) as e:
         p.validate("junk")
     assert matches(
         str(e.value),
         r"expected an element of either Int or Float, got 'junk'")
Ejemplo n.º 2
0
class PlotlyPlot(LayoutDOM):
    """
    A bokeh model that wraps around a plotly plot and renders it inside
    a bokeh plot.
    """

    __javascript_raw__ = [
        JS_URLS['jQuery'], 'https://cdn.plot.ly/plotly-2.10.1.min.js'
    ]

    @classproperty
    def __javascript__(cls):
        return bundled_files(cls)

    @classproperty
    def __js_skip__(cls):
        return {'Plotly': cls.__javascript__[1:]}

    __js_require__ = {
        'paths': {
            'plotly': 'https://cdn.plot.ly/plotly-2.10.1.min'
        },
        'exports': {
            'plotly': 'Plotly'
        }
    }

    data = List(Any)

    layout = Dict(String, Any)

    config = Dict(String, Any)

    data_sources = List(Instance(ColumnDataSource))

    relayout = Nullable(Dict(String, Any))

    restyle = Nullable(Dict(String, Any))

    # Callback properties
    relayout_data = Dict(String, Any)
    restyle_data = List(Any)
    click_data = Either(Dict(String, Any), Null)
    hover_data = Either(Dict(String, Any), Null)
    clickannotation_data = Either(Dict(String, Any), Null)
    selected_data = Either(Dict(String, Any), Null)
    viewport = Either(Dict(String, Any), Null)
    viewport_update_policy = Enum("mouseup", "continuous", "throttle")
    viewport_update_throttle = Int()
    visibility = Bool(True)
    _render_count = Int()
Ejemplo n.º 3
0
class SingleSelect(InputWidget):
    ''' Single-select widget.

    '''

    disabled_options = List(Any,
                            default=[],
                            help="""
    List of options to disable.
    """)

    options = List(Either(String, Tuple(String, String)),
                   help="""
    Available selection options. Options may be provided either as a list of
    possible string values, or as a list of tuples, each of the form
    ``(value, label)``. In the latter case, the visible widget text for each
    value will be corresponding given label.
    """)

    value = String(help="Initial or selected value.")

    size = Int(default=4,
               help="""
    The number of visible options in the dropdown list. (This uses the
    ``select`` HTML element's ``size`` attribute. Some browsers might not
    show less than 3 options.)
    """)
Ejemplo n.º 4
0
class CollisionModifier(HasProps):
    """Models an special type of operation that alters how glyphs interact.

    Used to handle the manipulation of glyphs for operations, such as stacking. The
    list of `CompositeGlyph`s can either be input into the `CollisionModifier` as
    keyword args, or added individually with the `add_glyph` method.
    """
    comp_glyphs = List(Instance(CompositeGlyph),
                       help="""A list of composite glyphs,
        to apply the modification to.""")
    name = String(help="""The name of the collision modifier.""")
    method_name = String(
        help="""The name of the method that will be utilized on
        the composite glyphs. This method must exist on all `comp_glyphs`.""")
    columns = Either(ColumnLabel,
                     List(ColumnLabel),
                     help="""Some collision modifiers
        might require column labels to apply the operation in relation to.""")

    def add_glyph(self, comp_glyph):
        self.comp_glyphs.append(comp_glyph)

    def apply(self, renderers=None):
        if len(self.comp_glyphs) == 0:
            self.comp_glyphs = renderers

        if len(self.comp_glyphs) > 0:
            # the first renderer's operation method is applied to the rest
            getattr(self.comp_glyphs[0], self.method_name)(self.comp_glyphs)
        else:
            raise AttributeError(
                '%s must be applied to available renderers, none found.' %
                self.__class__.__name__)
Ejemplo n.º 5
0
def test_Property_wrap() -> None:
    types = [
        Bool(),
        Int(),
        Float(),
        Complex(),
        String(),
        Enum("Some", "a", "b"),
        Color(),
        Regex("^$"),
        Seq(Any),
        Tuple(Any, Any),
        Instance(_TestModel),
        Any(),
        Interval(Float, 0, 1),
        Either(Int, String),
        DashPattern(),
        Size(),
        Percent(),
        Angle(),
        MinMaxBounds(),
    ]

    for x in types:
        for y in (0, 1, 2.3, "foo", None, (), [], {}):
            r = x.wrap(y)
            assert r == y
            assert isinstance(r, type(y))
Ejemplo n.º 6
0
class IDOM(HTMLBox):

    importSourceUrl = String()

    event = Tuple(Any, Any)

    msg = Either(Dict(String, Any), Null)
Ejemplo n.º 7
0
class DeckGLPlot(HTMLBox):
    """A Bokeh model that wraps around a DeckGL plot and renders it inside a HTMLBox"""

    __css_raw__ = ["https://api.mapbox.com/mapbox-gl-js/v1.7.0/mapbox-gl.css"]

    @classproperty
    def __css__(cls):
        return bundled_files(cls, 'css')

    __javascript_raw__ = [
        "https://cdn.jsdelivr.net/npm/[email protected]/dist.min.js",
        "https://cdn.jsdelivr.net/npm/@deck.gl/[email protected]/dist.min.js",
        "https://cdn.jsdelivr.net/npm/@loaders.gl/[email protected]/dist/dist.min.js",
        "https://cdn.jsdelivr.net/npm/@loaders.gl/[email protected]/dist/dist.min.js",
        "https://cdn.jsdelivr.net/npm/@loaders.gl/[email protected]/dist/dist.min.js",
        "https://api.mapbox.com/mapbox-gl-js/v1.7.0/mapbox-gl.js",
    ]

    @classproperty
    def __javascript__(cls):
        return bundled_files(cls)

    @classproperty
    def __js_skip__(cls):
        return {
            'deck': cls.__javascript__[:-1],
            'mapboxgl': cls.__javascript__[-1:]
        }

    __js_require__ = {
        'paths': OrderedDict([
            ("deck.gl", "https://cdn.jsdelivr.net/npm/@deck.gl/jupyter-widget@^8.1.2/dist/index"),
            ("mapbox-gl", 'https://cdn.jsdelivr.net/npm/[email protected]/dist/mapbox-gl.min'),
        ]),
        'exports': {"deck.gl": "deck", "mapbox-gl": "mapboxgl"}
    }

    data = Dict(String, Any)

    data_sources = List(Instance(ColumnDataSource))

    initialViewState = Dict(String, Any)

    layers = List(Dict(String, Any))

    mapbox_api_key = String()

    tooltip = Either(Bool, Dict(Any, Any))

    clickState = Dict(String, Any)

    hoverState = Dict(String, Any)

    viewState = Dict(String, Any)

    height = Override(default=400)

    width = Override(default=600)
Ejemplo n.º 8
0
class BinStats(Stat):
    """A set of statistical calculations for binning values.

    Bin counts using: https://en.wikipedia.org/wiki/Freedman%E2%80%93Diaconis_rule
    """
    bins = Either(Int,
                  Float,
                  List(Float),
                  default=None,
                  help="""
    If bins is an int, it defines the number of equal-width bins in the
    given range. If bins is a sequence, it defines the
    bin edges, including the rightmost edge, allowing for non-uniform
    bin widths.

    (default: None, use Freedman-Diaconis rule)
    """)
    bin_width = Float(default=None, help='Use Freedman-Diaconis rule if None.')
    q1 = Quantile(interval=0.25)
    q3 = Quantile(interval=0.75)
    labels = List(String)

    def __init__(self, values=None, column=None, **properties):
        properties['values'] = values
        properties['column'] = column or 'values'

        super(BinStats, self).__init__(**properties)

    def update(self):
        values = self.get_data()
        self.q1.set_data(values)
        self.q3.set_data(values)
        if self.bins is None:
            self.calc_num_bins(values)

    def calc_num_bins(self, values):
        """Calculate optimal number of bins using IQR.

        From: http://stats.stackexchange.com/questions/114490/optimal-bin-width-for-two-dimensional-histogram

        """
        iqr = self.q3.value - self.q1.value

        if iqr == 0:
            self.bin_width = np.sqrt(values.size)
        else:
            self.bin_width = 2 * iqr * (len(values)**-(1. / 3.))

        self.bins = int(np.ceil(
            (values.max() - values.min()) / self.bin_width))

        if self.bins <= 1:
            self.bins = 3

    def calculate(self):
        pass
Ejemplo n.º 9
0
class JSON(Markup):
    """
    A bokeh model that renders JSON as tree.
    """

    depth = Either(Int, Float, default=1, help="Depth to which the JSON tree is expanded.")

    hover_preview = Bool(default=False, help="Whether to show a hover preview for collapsed nodes.")

    theme = String(default='dark', help="Whether to expand all JSON nodes.")
Ejemplo n.º 10
0
class BootstrapSelect(InputWidget):
    __javascript__ = [
        "https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap-select.min.js",
        "https://cdn.jsdelivr.net/npm/[email protected]/dist/js/i18n/defaults-pl_PL.min.js"
    ]
    __css__ = [
        "https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-select.min.css"
    ]
    alt_title = String(default="")
    options = List(Tuple(String, Tuple(Either(Int, String), String)))
    value = List(Tuple(Either(Int, String), String))
    actions_box = Bool(default=False)
    live_search = Bool(default=False)
    # show_subtext = Bool(default=False)
    select_all_at_start = Bool(default=False)
    count_selected_text = String(default='')
    none_selected_text = String(default='')
    selected_text_format = String(default='')
    none_results_text = String(default='')
Ejemplo n.º 11
0
class HighTable(TableWidget):
    ''' Two dimensional grid for visualisation and editing large amounts
    of data.
    '''

    __implementation__ = "high_table.coffee"

    columns = List(Instance(TableColumn), help="""
    The list of child column widgets.
    """)

    fit_columns = Bool(True, help="""
    Whether columns should be fit to the available width. This results in no
    horizontal scrollbar showing up, but data can get unreadable if there is
    no enough space available. If set to ``True``, columns' width is
    understood as maximum width.
    """)

    sortable = Bool(True, help="""
    Allows to sort table's contents. By default natural order is preserved.
    To sort a column, click on it's header. Clicking one more time changes
    sort direction. Use Ctrl + click to return to natural order. Use
    Shift + click to sort multiple columns simultaneously.
    """)

    reorderable = Bool(True, help="""
    Allows the reordering of a tables's columns. To reorder a column,
    click and drag a table's header to the desired location in the table.
    The columns on either side will remain in their previous order.
    """)

    editable = Bool(False, help="""
    Allows to edit table's contents. Needs cell editors to be configured on
    columns that are required to be editable.
    """)

    selectable = Either(Bool(True), Enum("checkbox"), help="""
    Whether a table's rows can be selected or not. Using ``checkbox`` is
    equivalent  to ``True``, but makes selection visible through a checkbox
    for each row,  instead of highlighting rows. Multiple selection is
    allowed and can be achieved by either clicking multiple checkboxes (if
    enabled) or using Shift + click on rows.
    """)

    row_headers = Bool(True, help="""
    Enable or disable row headers, i.e. the index column.
    """)

    scroll_to_selection = Bool(True, help="""
    Whenever a selection is made on the data source, scroll the selected
    rows into the table's viewport if none of the selected rows are already
    in the viewport.
    """)

    height = Override(default=400)
Ejemplo n.º 12
0
class QuillInput(HTMLBox):
    """
    WYSIWYG text editor based on Quill.js
    """

    __css_raw__ = [
        'https://cdn.quilljs.com/1.3.6/quill.bubble.css',
        'https://cdn.quilljs.com/1.3.6/quill.snow.css'
    ]

    __javascript_raw__ = [
        'https://cdn.quilljs.com/1.3.6/quill.js',
    ]

    @classproperty
    def __javascript__(cls):
        return bundled_files(cls)

    @classproperty
    def __css__(cls):
        return bundled_files(cls, 'css')

    @classproperty
    def __js_skip__(cls):
        return {'Quill': cls.__javascript__}

    __js_require__ = {
        'paths': {
            'Quill': 'https://cdn.quilljs.com/1.3.6/quill',
        },
        'exports': {
            'Quill': 'Quill'
        }
    }

    mode = Enum("bubble", "toolbar", default='toolbar')

    placeholder = String()

    readonly = Bool(False)

    text = String()

    toolbar = Either(List(Any), Bool)
Ejemplo n.º 13
0
class DisabledSelect(InputWidget):
    ''' Single-select widget.
    '''
    __js_implementation__ = 'disabled_select.ts'

    options = List(Either(String, Tuple(String, Bool)), help="""
    Available selection options. Options may be provided either as a list of
    possible string values, or as a list of tuples, each of the form
    ``(value, disabled)``.
    """)

    value = String(default="", help="""
    Initial or selected value.
    """)

    callback = Instance(Callback, help="""
    A callback to run in the browser whenever the current Select dropdown
    value changes.
    """)
Ejemplo n.º 14
0
class PCPMultiSelect(InputWidget):
    ''' Multi-select widget.

    '''

    searchbox = Bool(default=True)

    selectall = Bool(default=True)

    options = List(Either(String, Tuple(String, String)),
                   help="""
    Available selection options. Options may be provided either as a list of
    possible string values, or as a list of tuples, each of the form
    ``(value, label)``. In the latter case, the visible widget text for each
    value will be corresponding given label.
    """)

    value = List(String, help="""
    Initial or selected values.
    """)

    theme = String(default="light")
Ejemplo n.º 15
0
 def test_Either(self, detail) -> None:
     p = Either(Int, Float)
     with pytest.raises(ValueError) as e:
         p.validate("junk", detail)
     assert (str(e.value) == "") == (not detail)
class CustomMultiSelect(InputWidget):
    ''' Custom Multi-select widget.
    '''
    __implementation__ = os.path.join("implementation_files", "multiselect.ts")
    __javascript__ = [
        "https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
        "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.bundle.min.js",
        "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.16/js/bootstrap-multiselect.min.js",
        "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/js/all.min.js",
    ]
    __css__ = [
        "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/css/bootstrap.min.css",
        "https://cdnjs.cloudflare.com/ajax/libs/bootstrap-multiselect/0.9.16/css/bootstrap-multiselect.min.css",
    ]

    options = Either(Dict(String, List(Either(String, Tuple(String, String)))),
                     List(Either(String, Tuple(String, String))),
                     help="""
    Available selection options. Options may be provided either as a list of
    possible string values, or as a list of tuples, each of the form
    ``(value, label)``. In the latter case, the visible widget text for each
    value will be corresponding given label. In order to group options, provide a dictionary where each key
    is the name of a group and its corresponding value is the group options. For example:
    {"Even": ["2", "4", "6"], "Odd": ["1", "3", "5"]}. Note! the option's value should be unique across all
    other options and not only the option's group).
    """)

    value = Nullable(List(Either(String, List(String))),
                     help="""
    Initial or selected values. Note! when the options are grouped the value is a list of tuples
    that follow the pattern: (<group>, <value>).
    """)

    include_select_all = Bool(default=False,
                              help="""
    Whether to include a "Select All" option or not. Note! in order to initialize the widget
    with the "Select All" option checked, set the "select_all" property to True.
    """)

    select_all = Bool(default=False,
                      help="""
    Whether all the options are selected or not. Note! this property is valid also when the
    "include_select_all" property is set to false.
    """)

    number_displayed = Int(default=1,
                           help="""
    Determines how many selected labels should be displayed on the toggle button.
    """)

    enable_filtering = Bool(default=False,
                            help="""
    Enable filtering options using a search box.
    """)

    enabled = Bool(default=True,
                   help="""
    Controls whether the widget is enabled (True) or disabled (False).
    Note! the "disabled" property is not supported in this widget. Use this property instead.
    """)

    non_selected_text = String(default="Select...",
                               help="""
    The text to display on the toggle button when none of the options are selected.
    """)

    is_opt_grouped = Bool(readonly=True,
                          help="""
    Indicates whether the widget contains grouped options or not.
    """)

    dropdown_closed = Bool(default=False,
                           help="""
    This property changes right before the "value" property changes.
    """)

    @classmethod
    def create(cls: Type[T], title: str) -> T:
        """This function creates a custom multi select filter with a given title.
        """
        return cls(
            include_select_all=True,
            enable_filtering=True,
            options=[],
            value=[],
            title=title,
            sizing_mode='scale_width',
            margin=[10, 10, 10, 5],
            css_classes=['custom_select', 'custom'],
        )
Ejemplo n.º 17
0
class Chart(Plot):
    """ The main Chart class, the core of the ``Bokeh.charts`` interface.

    """

    __view_model__ = "Plot"
    __subtype__ = "Chart"

    xlabel = String(None,
                    help="""
    A label for the x-axis. (default: None)
    """)

    ylabel = String(None,
                    help="""
    A label for the y-axis. (default: None)
    """)

    xscale = Either(Auto,
                    Enum(Scale),
                    help="""
    What kind of scale to use for the x-axis.
    """)

    yscale = Either(Auto,
                    Enum(Scale),
                    help="""
    What kind of scale to use for the y-axis.
    """)

    _defaults = defaults

    __deprecated_attributes__ = ('filename', 'server', 'notebook', 'width',
                                 'height', 'xgrid', 'ygrid', 'legend'
                                 'background_fill', 'border_fill', 'logo',
                                 'tools', 'title_text_baseline',
                                 'title_text_align', 'title_text_alpha',
                                 'title_text_color', 'title_text_font_style',
                                 'title_text_font_size', 'title_text_font',
                                 'title_standoff')

    _xgrid = True
    _ygrid = True
    _legend = True

    @Plot.xgrid.setter
    def xgrid(self, value):
        warnings.warn(
            "Non-functional 'xgrid' setter has been removed; use 'xgrid' keyword argument to Chart instead"
        )

    @Plot.ygrid.setter
    def ygrid(self, value):
        warnings.warn(
            "Non-functional 'ygrid' setter has been removed; use 'ygrid' keyword argument to Chart instead"
        )

    @Plot.legend.setter
    def legend(self, value):
        warnings.warn(
            "Non-functional 'legend' setter has been removed; use 'legend' keyword argument to Chart instead"
        )

    def __init__(self, *args, **kwargs):
        # pop tools as it is also a property that doesn't match the argument
        # supported types
        tools = kwargs.pop('tools', None)
        for name in ['xgrid', 'ygrid', 'legend']:
            if name in kwargs:
                kwargs["_" + name] = kwargs[name]
                del kwargs[name]

        if 'responsive' in kwargs and 'sizing_mode' in kwargs:
            raise ValueError(
                "Chart initialized with both 'responsive' and 'sizing_mode' supplied, supply only one"
            )
        if 'responsive' in kwargs:
            kwargs['sizing_mode'] = _convert_responsive(kwargs['responsive'])
            del kwargs['responsive']

        self._active_drag = kwargs.pop('active_drag', 'auto')
        self._active_inspect = kwargs.pop('active_inspect', 'auto')
        self._active_scroll = kwargs.pop('active_scroll', 'auto')
        self._active_tap = kwargs.pop('active_tap', 'auto')

        title_text = kwargs.pop("title", None)

        super(Chart, self).__init__(*args, **kwargs)

        self.title.text = title_text

        defaults.apply(self)

        if tools is not None:
            self._tools = tools

        # TODO (fpliger): we do this to still support deprecated document but
        #                 should go away when __deprecated_attributes__ is empty
        for k in self.__deprecated_attributes__:
            if k in kwargs:
                setattr(self, k, kwargs[k])

        self._glyphs = []
        self._built = False

        self._builders = []
        self._renderer_map = []
        self._ranges = defaultdict(list)
        self._labels = defaultdict(list)
        self._scales = defaultdict(list)
        self._tooltips = []

        if hasattr(self, '_tools'):
            self.create_tools(self._tools, self._active_drag,
                              self._active_inspect, self._active_scroll,
                              self._active_tap)

    def add_renderers(self, builder, renderers):
        self.renderers += renderers
        self._renderer_map.extend({r._id: builder for r in renderers})

    def add_builder(self, builder):
        self._builders.append(builder)
        builder.create(self)

    def add_ranges(self, dim, range):
        self._ranges[dim].append(range)

    def add_labels(self, dim, label):
        self._labels[dim].append(label)

    def add_scales(self, dim, scale):
        self._scales[dim].append(scale)

    def add_tooltips(self, tooltips):
        self._tooltips += tooltips

    def _get_labels(self, dim):
        if not getattr(self, dim + 'label') and len(self._labels[dim]) > 0:
            return self._labels[dim][0]
        else:
            return getattr(self, dim + 'label')

    def create_axes(self):
        self._xaxis = self.make_axis('x', "below", self._scales['x'][0],
                                     self._get_labels('x'))
        self._yaxis = self.make_axis('y', "left", self._scales['y'][0],
                                     self._get_labels('y'))

    def create_grids(self, xgrid=True, ygrid=True):
        if xgrid:
            self.make_grid(0, self._xaxis.ticker)
        if ygrid:
            self.make_grid(1, self._yaxis.ticker)

    def create_tools(self, tools, active_drag, active_inspect, active_scroll,
                     active_tap):
        """Create tools if given tools=True input.

        Only adds tools if given boolean and does not already have
        tools added to self.
        """

        if isinstance(tools, bool) and tools:
            tools = DEFAULT_TOOLS
        elif isinstance(tools, bool):
            # in case tools == False just exit
            return

        if len(self.toolbar.tools) == 0:
            # if no tools customization let's create the default tools
            tool_objs, tool_map = _process_tools_arg(self, tools)
            self.add_tools(*tool_objs)
            _process_active_tools(self.toolbar, tool_map, active_drag,
                                  active_inspect, active_scroll, active_tap)

    def start_plot(self):
        """Add the axis, grids and tools
        """
        self.create_axes()
        self.create_grids(self._xgrid, self._ygrid)

        if self.toolbar.tools:
            self.create_tools(self._tools, self._active_drag,
                              self._active_inspect, self._active_scroll,
                              self._active_tap)

        if len(self._tooltips) > 0:
            self.add_tools(HoverTool(tooltips=self._tooltips))

    def add_legend(self, chart_legends):
        """Add the legend to your plot, and the plot to a new Document.

        It also add the Document to a new Session in the case of server output.

        Args:
            legends(List(Tuple(String, List(GlyphRenderer)): A list of
                tuples that maps text labels to the legend to corresponding
                renderers that should draw sample representations for those
                labels.
        """
        location = None
        if self._legend is True:
            location = "top_left"
        else:
            location = self._legend

        items = []
        for legend in chart_legends:
            items.append(
                LegendItem(label=value(legend[0]), renderers=legend[1]))
        if location:
            legend = Legend(location=location, items=items)
            self.add_layout(legend)

    def make_axis(self, dim, location, scale, label):
        """Create linear, date or categorical axis depending on the location,
        scale and with the proper labels.

        Args:
            location(str): the space localization of the axis. It can be
                ``left``, ``right``, ``above`` or ``below``.
            scale (str): the scale on the axis. It can be ``linear``, ``datetime``
                or ``categorical``.
            label (str): the label on the axis.

        Return:
            Axis: Axis instance
        """

        # ToDo: revisit how to handle multiple ranges
        # set the last range to the chart's range
        if len(self._ranges[dim]) == 0:
            raise ValueError('Ranges must be added to derive axis type.')

        data_range = self._ranges[dim][-1]
        setattr(self, dim + '_range', data_range)

        if scale == "auto":
            if isinstance(data_range, FactorRange):
                scale = 'categorical'
            else:
                scale = 'linear'

        if scale == "linear":
            axis = LinearAxis(axis_label=label)
        elif scale == "datetime":
            axis = DatetimeAxis(axis_label=label)
        elif scale == "categorical":
            axis = CategoricalAxis(major_label_orientation=np.pi / 4,
                                   axis_label=label)
        else:
            axis = LinearAxis(axis_label=label)

        self.add_layout(axis, location)
        return axis

    def make_grid(self, dimension, ticker):
        """Create the grid just passing the axis and dimension.

        Args:
            dimension(int): the dimension of the axis, ie. xaxis=0, yaxis=1.
            ticker (obj): the axis.ticker object

        Return:
            Grid: A Grid instance
        """

        grid = Grid(dimension=dimension, ticker=ticker)
        self.add_layout(grid)

        return grid

    annular_wedge = _glyph_function(glyphs.AnnularWedge)

    annulus = _glyph_function(glyphs.Annulus)

    arc = _glyph_function(glyphs.Arc)

    asterisk = _glyph_function(markers.Asterisk)

    bezier = _glyph_function(glyphs.Bezier)

    circle = _glyph_function(markers.Circle)

    circle_cross = _glyph_function(markers.CircleCross)

    circle_x = _glyph_function(markers.CircleX)

    cross = _glyph_function(markers.Cross)

    diamond = _glyph_function(markers.Diamond)

    diamond_cross = _glyph_function(markers.DiamondCross)

    ellipse = _glyph_function(glyphs.Ellipse)

    hbar = _glyph_function(glyphs.HBar)

    image = _glyph_function(glyphs.Image)

    image_rgba = _glyph_function(glyphs.ImageRGBA)

    image_url = _glyph_function(glyphs.ImageURL)

    inverted_triangle = _glyph_function(markers.InvertedTriangle)

    line = _glyph_function(glyphs.Line)

    multi_line = _glyph_function(glyphs.MultiLine)

    oval = _glyph_function(glyphs.Oval)

    patch = _glyph_function(glyphs.Patch)

    patches = _glyph_function(glyphs.Patches)

    quad = _glyph_function(glyphs.Quad)

    quadratic = _glyph_function(glyphs.Quadratic)

    ray = _glyph_function(glyphs.Ray)

    rect = _glyph_function(glyphs.Rect)

    segment = _glyph_function(glyphs.Segment)

    square = _glyph_function(markers.Square)

    square_cross = _glyph_function(markers.SquareCross)

    square_x = _glyph_function(markers.SquareX)

    text = _glyph_function(glyphs.Text)

    triangle = _glyph_function(markers.Triangle)

    vbar = _glyph_function(glyphs.VBar)

    wedge = _glyph_function(glyphs.Wedge)

    x = _glyph_function(markers.X)
Ejemplo n.º 18
0
class Dimension(HasProps):
    """Configures valid Chart column selections.

    A dimension is Chart property that is assigned one or more columns names or indices.
    Each column can match one or more column types, which are important to charts,
    because the type of column selection can greatly affect the behavior of generalized
    Charts.

    The Dimension also provides convenient utilities for accessing information
    about the current provided configuration at the global, non-grouped level.

    The dimension configuration does not require the data, but when the data is
    added using the `set_data` method, then validation can occur of the settings
    by using the `valid` and `invalid` types identified by the selection.
    """

    name = String()
    alt_names = Either(String, List(String), default=None)
    columns = Either(ColumnLabel, List(ColumnLabel), default=None)

    valid = Either(PrimitiveProperty, List(PrimitiveProperty), default=None)
    invalid = Either(PrimitiveProperty, List(PrimitiveProperty), default=None)

    selection = Either(ColumnLabel, List(ColumnLabel), default=None)

    def __init__(self, name, **properties):
        properties['name'] = name
        super(Dimension, self).__init__(**properties)
        self._data = pd.DataFrame()
        self._chart_source = None

    def get_valid_types(self, col_data):
        """Returns all property types that are matched."""
        valid_types = list(self.valid)
        matches = []

        # validate each type on the provided column
        for valid_type in valid_types:
            prop = valid_type()

            # if valid, append to the output
            try:
                prop.validate(col_data)
                matches.append(valid_type)
            except ValueError:
                pass

        return matches

    @property
    def data(self):
        """The data selected for the Dimension.

        Returns pd.Series(1) if data is empty or no selection.
        """
        if self._data.empty or self.selection is None:
            return pd.Series(1)
        else:
            # return special column type if available
            if self.selection in list(special_columns.keys()):
                return special_columns[self.selection](self._data)

            return self._data[self.selection]

    def __len__(self):
        return len(self.data.index)

    def set_data(self, data):
        """Set data property so that builders has access to configuration metadata.

        Args:
            data (`ChartDataSource`): the data source associated with the chart
        """
        self.selection = data[self.name]
        self._chart_source = data
        self._data = data.df
        self.columns = list(self._data.columns.values)

    @property
    def min(self):
        """The minimum of one to many column selections."""
        if isinstance(self.data, pd.Series):
            return self.data.min()
        else:
            return self.data.min(axis=1).min()

    @property
    def max(self):
        """The maximum of one to many column selections."""
        if isinstance(self.data, pd.Series):
            return self.data.max()
        else:
            return self.data.max(axis=1).max()

    @property
    def dtype(self):
        if isinstance(self.data, pd.DataFrame):
            return self.data.dtypes[self.selection[0]]
        else:
            return self.data.dtype

    @property
    def computed(self):
        """Check the `ChartDataSource` to see if the selection is a derived column."""
        if self._chart_source is None:
            return False
        else:
            return self._chart_source.is_computed(self.selection)

    @property
    def selected_title(self):
        """A title formatted representation of selected columns."""
        return title_from_columns(self.selection)
Ejemplo n.º 19
0
class CategorialLegend(Annotation):
    ''' Render legend for a plot
        But can link to more renderers than used for display classs
        '''

    __implementation__ = "CategorialLegend.ts"

    items = List(Instance(CategorialLegendItem),
                 help="""
    A list of :class:`~bokeh.model.annotations.LegendItem` instances to be
    rendered in the legend.
    This can be specified explicitly, for instance:
    .. code-block:: python
        legend = Legend(items=[
            LegendItem(label="sin(x)"   , renderers=[r0, r1]),
            LegendItem(label="2*sin(x)" , renderers=[r2]),
            LegendItem(label="3*sin(x)" , renderers=[r3, r4])
        ])
    But as a convenience, can also be given more compactly as a list of tuples:
    .. code-block:: python
        legend = Legend(items=[
            ("sin(x)"   , [r0, r1]),
            ("2*sin(x)" , [r2]),
            ("3*sin(x)" , [r3, r4])
        ])
    where each tuple is of the form: *(label, renderers)*.
    """).accepts(
                     List(Tuple(List(String), Instance(GlyphRenderer))),
                     lambda items: [
                         CategorialLegendItem(labels=item[0], render=item[1])
                         for item in items
                     ])
    ''' Render informational legends for a plot.
    '''

    location = Either(Enum(LegendLocation),
                      Tuple(Float, Float),
                      default="top_right",
                      help="""
    The location where the legend should draw itself. It's either one of
    ``bokeh.core.enums.LegendLocation``'s enumerated values, or a ``(x, y)``
    tuple indicating an absolute location absolute location in screen
    coordinates (pixels from the bottom-left corner).
    """)

    orientation = Enum(Orientation,
                       default="vertical",
                       help="""
    Whether the legend entries should be placed vertically or horizontally
    when they are drawn.
    """)

    border_props = Include(LineProps,
                           help="""
    The %s for the legend border outline.
    """)

    border_line_color = Override(default="#e5e5e5")

    border_line_alpha = Override(default=0.5)

    background_props = Include(FillProps,
                               help="""
    The %s for the legend background style.
    """)

    inactive_props = Include(FillProps,
                             help="""
    The %s for the legend item style when inactive. These control an overlay
    on the item that can be used to obscure it when the corresponding glyph
    is inactive (e.g. by making it semi-transparent).
    """)

    click_policy = Enum(LegendClickPolicy,
                        default="none",
                        help="""
    Defines what happens when a lengend's item is clicked.
    """)

    background_fill_color = Override(default="#ffffff")

    background_fill_alpha = Override(default=0.95)

    inactive_fill_color = Override(default="white")

    inactive_fill_alpha = Override(default=0.7)

    label_props = Include(TextProps,
                          help="""
    The %s for the legend labels.
    """)

    label_text_baseline = Override(default='middle')

    label_text_font_size = Override(default={'value': '10pt'})

    label_standoff = Int(5,
                         help="""
    The distance (in pixels) to separate the label from its associated glyph.
    """)

    label_height = Int(20,
                       help="""
    The minimum height (in pixels) of the area that legend labels should occupy.
    """)

    label_width = Int(20,
                      help="""
    The minimum width (in pixels) of the area that legend labels should occupy.
    """)

    glyph_height = Int(20,
                       help="""
    The height (in pixels) that the rendered legend glyph should occupy.
    """)

    glyph_width = Int(20,
                      help="""
    The width (in pixels) that the rendered legend glyph should occupy.
    """)

    margin = Int(10, help="""
    Amount of margin around the legend.
    """)

    padding = Int(10,
                  help="""
    Amount of padding around the contents of the legend. Only applicable when
    when border is visible, otherwise collapses to 0.
    """)

    spacing = Int(3,
                  help="""
    Amount of spacing (in pixels) between legend entries.
    """)
Ejemplo n.º 20
0
 def test_Either(self):
     p = Either(Int, Float)
     with pytest.raises(ValueError) as e:
         p.validate("junk")
     assert not str(e).endswith("ValueError")
Ejemplo n.º 21
0
class Foo(Model):
    """ This is a Foo model. """
    index = Either(Auto, Enum('abc', 'def', 'xzy'), help="doc for index")
    value = Tuple(Float, Float, help="doc for value")
Ejemplo n.º 22
0
class Interval(AggregateGlyph):
    """A rectangle representing aggregated values.

    The interval is a rect glyph where two of the parallel sides represent a
    summary of values. Each of the two sides is derived from a separate aggregation of
    the values provided to the interval.

    .. note::
        A bar is a special case interval where one side is pinned and used to
        communicate a value relative to it.
    """

    width = Float(default=0.8)
    start_agg = Either(Instance(Stat),
                       Enum(*list(stats.keys())),
                       default=Min(),
                       help="""
        The stat used to derive the starting point of the composite glyph.""")
    end_agg = Either(Instance(Stat),
                     Enum(*list(stats.keys())),
                     default=Max(),
                     help="""
        The stat used to derive the end point of the composite glyph.""")

    start = Float(default=0.0)
    end = Float()

    def __init__(self, label, values, **kwargs):

        kwargs['label'] = label
        kwargs['values'] = values

        super(Interval, self).__init__(**kwargs)
        self.setup()

    def get_start(self):
        """Get the value for the start of the glyph."""
        if len(self.values.index) == 1:
            self.start_agg = None
            return self.values[0]
        elif isinstance(self.start_agg, str):
            self.start_agg = stats[self.start_agg]()

        self.start_agg.set_data(self.values)
        return self.start_agg.value

    def get_end(self):
        """Get the value for the end of the glyph."""
        if isinstance(self.end_agg, str):
            self.end_agg = stats[self.end_agg]()

        self.end_agg.set_data(self.values)
        return self.end_agg.value

    def get_span(self):
        """The total range between the start and end."""
        return self.end - self.start

    def build_source(self):
        # ToDo: Handle rotation
        self.start = self.get_start()
        self.end = self.get_end()
        self.span = self.get_span()

        width = [self.width]
        if self.dodge_shift is not None:
            x = [self.get_dodge_label()]
        else:
            x = [self.x_label]
        height = [self.span]
        y = [self.stack_shift + (self.span / 2.0) + self.start]
        color = [self.color]
        fill_alpha = [self.fill_alpha]
        line_color = [self.line_color]
        line_alpha = [self.line_alpha]
        label = [self.label]

        return dict(x=x,
                    y=y,
                    width=width,
                    height=height,
                    color=color,
                    fill_alpha=fill_alpha,
                    line_color=line_color,
                    line_alpha=line_alpha,
                    label=label)

    @property
    def x_max(self):
        """The maximum extent of the glyph in x.

        .. note::
            Dodging the glyph can affect the value.
        """
        return (self.dodge_shift or self.x_label_value) + (self.width / 2.0)

    @property
    def x_min(self):
        """The maximum extent of the glyph in y.

        .. note::
            Dodging the glyph can affect the value.
        """
        return (self.dodge_shift or self.x_label_value) - (self.width / 2.0)

    @property
    def y_max(self):
        """Maximum extent of all `Glyph`s.

        How much we are stacking + the height of the interval + the base of the interval

        .. note::
            the start and end of the glyph can swap between being associated with the
            min and max when the glyph end represents a negative value.
        """
        return max(self.bottom, self.top)

    @property
    def y_min(self):
        """The minimum extent of all `Glyph`s in y.

        .. note::
            the start and end of the glyph can swap between being associated with the
            min and max when the glyph end represents a negative value.
        """
        return min(self.bottom, self.top)

    @property
    def bottom(self):
        """The value associated with the start of the stacked glyph."""
        return self.stack_shift + self.start

    @property
    def top(self):
        """The value associated with the end of the stacked glyph."""
        return self.stack_shift + self.span + self.start

    def build_renderers(self):
        """Yields a `GlyphRenderer` associated with a `Rect` glyph."""
        glyph = Rect(x='x',
                     y='y',
                     width='width',
                     height='height',
                     fill_color='color',
                     fill_alpha='fill_alpha',
                     line_color='line_color')
        yield GlyphRenderer(glyph=glyph)
Ejemplo n.º 23
0
class LuminoDock(HTMLBox):
    children = List(Either(Tuple(String, Instance(LayoutDOM)),
                           Tuple(String, Instance(LayoutDOM), InsertMode),
                           Tuple(String, Instance(LayoutDOM), InsertMode,
                                 Int)),
                    default=[])
Ejemplo n.º 24
0
 def test_Either(self, detail):
     p = Either(Int, Float)
     with pytest.raises(ValueError) as e:
         p.validate("junk", detail)
     assert str(e).endswith("ValueError") == (not detail)
Ejemplo n.º 25
0
 def test_Either(self):
     p = Either(Int, Float)
     with pytest.raises(ValueError) as e:
         p.validate("junk")
     assert not str(e).endswith("ValueError")
Ejemplo n.º 26
0
class Builder(HasProps):
    """ A prototype class to inherit each new chart Builder type.

    It provides useful methods to be used by the inherited builder classes,
    in order to automate most of the charts creation tasks and leave the
    core customization to specialized builder classes. In that pattern
    inherited builders just need to provide the following methods:

    Required:

    * :meth:`~bokeh.charts.builder.Builder.yield_renderers`: yields the glyphs to be
      rendered into the plot. Here you should call the
      :meth:`~bokeh.charts.builder.Builder.add_glyph` method so that the builder can
      setup the legend for you.
    * :meth:`~bokeh.charts.builder.Builder.set_ranges`: setup the ranges for the
      glyphs. This is called after glyph creation, so you are able to inspect the
      comp_glyphs for their minimum and maximum values. See the
      :meth:`~bokeh.charts.builder.Builder.create` method for more information on
      when this is called and how the builder provides the ranges to the containing
      :class:`Chart` using the :meth:`Chart.add_ranges` method.

    Optional:

    * :meth:`~bokeh.charts.builder.Builder.setup`: provides an area
      where subclasses of builder can introspect properties, setup attributes, or change
      property values. This is called before
      :meth:`~bokeh.charts.builder.Builder.process_data`.
    * :meth:`~bokeh.charts.builder.Builder.process_data`: provides an area
      where subclasses of builder can manipulate the source data before renderers are
      created.

    """

    # Optional Inputs
    x_range = Instance(Range)
    y_range = Instance(Range)

    xlabel = String()
    ylabel = String()

    xscale = String()
    yscale = String()

    palette = List(Color,
                   help="""Optional input to override the default palette used
        by any color attribute.
        """)

    # Dimension Configuration
    """
    The dimension labels that drive the position of the
    glyphs. Subclasses should implement this so that the Builder
    base class knows which dimensions it needs to operate on.
    An example for a builder working with cartesian x and y
    coordinates would be dimensions = ['x', 'y']. You should
    then instantiate the x and y dimensions as attributes of the
    subclass of builder using the :class:`Dimension
    <bokeh.charts.properties.Dimension>` class. One for x, as x
    = Dimension(...), and one as y = Dimension(...).
    """
    dimensions = None  # None because it MUST be overridden
    """
    The dimension labels that must exist to produce the
    glyphs. This specifies what are the valid configurations for
    the chart, with the option of specifying the type of the
    columns. The
    :class:`~bokeh.charts.data_source.ChartDataSource` will
    inspect this property of your subclass of Builder and use
    this to fill in any required dimensions if no keyword
    arguments are used.
    """
    req_dimensions = []

    # Attribute Configuration
    attributes = Dict(String,
                      Instance(AttrSpec),
                      help="""
        The attribute specs used to group data. This is a mapping between the role of
        the attribute spec (e.g. 'color') and the
        :class:`~bokeh.charts.attributes.AttrSpec` class (e.g.,
        :class:`~bokeh.charts.attributes.ColorAttr`). The Builder will use this
        attributes property during runtime, which will consist of any attribute specs
        that are passed into the chart creation function (e.g.,
        :class:`~bokeh.charts.Bar`), ones that are created for the user from simple
        input types (e.g. `Bar(..., color='red')` or `Bar(..., color=<column_name>)`),
        or lastly, the attribute spec found in the default_attributes configured for
        the subclass of :class:`~bokeh.charts.builder.Builder`.
        """)
    """
    The default attribute specs used to group data. This is
    where the subclass of Builder should specify what the
    default attributes are that will yield attribute values to
    each group of data, and any specific configuration. For
    example, the :class:`ColorAttr` utilizes a default palette
    for assigning color based on groups of data. If the user
    doesn't assign a column of the data to the associated
    attribute spec, then the default attrspec is used, which
    will yield a constant color value for each group of
    data. This is by default the first color in the default
    palette, but can be customized by setting the default color
    in the ColorAttr.
    """
    default_attributes = None  # None because it MUST be overridden

    # Derived properties (created by Builder at runtime)
    attribute_columns = List(ColumnLabel,
                             help="""
        All columns used for specifying attributes for the Chart. The Builder will set
        this value on creation so that the subclasses can know the distinct set of columns
        that are being used to assign attributes.
        """)

    comp_glyphs = List(Instance(CompositeGlyph),
                       help="""
        A list of composite glyphs, where each represents a unique subset of data. The
        composite glyph is a helper class that encapsulates all low level
        :class:`~bokeh.models.glyphs.Glyph`, that represent a higher level group of
        data. For example, the :class:`BoxGlyph` is a single class that yields
        each :class:`GlyphRenderer` needed to produce a Box on a :class:`BoxPlot`. The
        single Box represents a full array of values that are aggregated, and is made
        up of multiple :class:`~bokeh.models.glyphs.Rect` and
        :class:`~bokeh.models.glyphs.Segment` glyphs.
        """)

    labels = List(
        String,
        help="""Represents the unique labels to be used for legends.""")
    """List of attributes to use for legends."""
    label_attributes = []
    """
    Used to assign columns to dimensions when no selections have been provided. The
    default behavior is provided by the :class:`OrderedAssigner`, which assigns
    a single column to each dimension available in the `Builder`'s `dims` property.
    """
    column_selector = OrderedAssigner

    comp_glyph_types = List(Instance(CompositeGlyph))

    sort_dim = Dict(String, Bool, default={})

    sort_legend = List(Tuple(String, Bool),
                       help="""
        List of tuples to use for sorting the legend, in order that they should be
        used for sorting. This sorting can be different than the sorting used for the
        rest of the chart. For example, you might want to sort only on the column
        assigned to the color attribute, or sort it descending. The order of each tuple
        is (Column, Ascending).
        """)

    legend_sort_field = String(help="""
        Attribute that should be used to sort the legend, for example: color,
        dash, maker, etc. Valid values for this property depend on the type
        of chart.
        """)

    legend_sort_direction = Enum(SortDirection,
                                 help="""
    Sort direction to apply to :attr:`~bokeh.charts.builder.Builder.sort_legend`.
    Valid values are: `ascending` or `descending`.
    """)

    source = Instance(ColumnDataSource)

    tooltips = Either(List(Tuple(String, String)),
                      List(String),
                      Bool,
                      default=None,
                      help="""
        Tells the builder to add tooltips to the chart by either using the columns
        specified to the chart attributes (True), or by generating tooltips for each
        column specified (list(str)), or by explicit specification of the tooltips
        using the valid input for the `HoverTool` tooltips kwarg.
        """)

    __deprecated_attributes__ = ('sort_legend', )

    def __init__(self, *args, **kws):
        """Common arguments to be used by all the inherited classes.

        Args:
            data (:ref:`userguide_charts_data_types`): source data for the chart
            legend (str, bool): the legend of your plot. The legend content is
                inferred from incoming input.It can be ``top_left``,
                ``top_right``, ``bottom_left``, ``bottom_right``.
                It is ``top_right`` is you set it as True.

        Attributes:
            source (obj): datasource object for your plot,
                initialized as a dummy None.
            x_range (obj): x-associated datarange object for you plot,
                initialized as a dummy None.
            y_range (obj): y-associated datarange object for you plot,
                initialized as a dummy None.
            groups (list): to be filled with the incoming groups of data.
                Useful for legend construction.
            data (dict): to be filled with the incoming data and be passed
                to the ChartDataSource for each Builder class.
            attr (list(AttrSpec)): to be filled with the new attributes created after
                loading the data dict.
        """
        data = None
        if len(args) != 0 or len(kws) != 0:

            # chart dimensions can be literal dimensions or attributes
            attrs = list(self.default_attributes.keys())
            dims = self.dimensions + attrs

            # pop the dimension inputs from kwargs
            data_args = {}
            for dim in dims:
                if dim in kws.keys():
                    data_args[dim] = kws[dim]

            # build chart data source from inputs, given the dimension configuration
            data_args['dims'] = tuple(dims)
            data_args['required_dims'] = tuple(self.req_dimensions)
            data_args['attrs'] = attrs
            data_args['column_assigner'] = self.column_selector
            data = ChartDataSource.from_data(*args, **data_args)

            # make sure that the builder dimensions have access to the chart data source
            for dim in self.dimensions:
                getattr(getattr(self, dim), 'set_data')(data)

            # handle input attrs and ensure attrs have access to data
            attributes = self._setup_attrs(data, kws)

            # remove inputs handled by dimensions and chart attributes
            for dim in dims:
                kws.pop(dim, None)
        else:
            attributes = dict()

        kws['attributes'] = attributes
        super(Builder, self).__init__(**kws)

        # collect unique columns used for attributes
        self.attribute_columns = collect_attribute_columns(**self.attributes)

        for k in self.__deprecated_attributes__:
            if k in kws:
                setattr(self, k, kws[k])

        self._data = data
        self._legends = []

    def _setup_attrs(self, data, kws):
        """Handle overridden attributes and initialize them with data.

        Makes sure that all attributes have access to the data
        source, which is used for mapping attributes to groups
        of data.

        Returns:
            None

        """
        source = ColumnDataSource(data.df)
        attr_names = self.default_attributes.keys()
        custom_palette = kws.get('palette')

        attributes = dict()

        for attr_name in attr_names:

            attr = kws.pop(attr_name, None)

            # if given an attribute use it
            if isinstance(attr, AttrSpec):
                attributes[attr_name] = attr

            # if we are given columns, use those
            elif isinstance(attr, (str, list)):
                attributes[attr_name] = self.default_attributes[
                    attr_name]._clone()

                # override palette if available
                if isinstance(attributes[attr_name], ColorAttr):
                    if custom_palette is not None:
                        attributes[attr_name].iterable = custom_palette

                attributes[attr_name].setup(data=source, columns=attr)

            else:
                # override palette if available
                if (isinstance(self.default_attributes[attr_name], ColorAttr)
                        and custom_palette is not None):
                    attributes[attr_name] = self.default_attributes[
                        attr_name]._clone()
                    attributes[attr_name].iterable = custom_palette
                else:
                    attributes[attr_name] = self.default_attributes[
                        attr_name]._clone()

        # make sure all have access to data source
        for attr_name in attr_names:
            attributes[attr_name].update_data(data=source)

        return attributes

    def setup(self):
        """Perform any initial pre-processing, attribute config.

        Returns:
            None

        """
        pass

    def process_data(self):
        """Make any global data manipulations before grouping.

        It has to be implemented by any of the inherited class
        representing each different chart type. It is the place
        where we make specific calculations for each chart.

        Returns:
            None

        """
        pass

    def yield_renderers(self):
        """ Generator that yields the glyphs to be draw on the plot

        It has to be implemented by any of the inherited class
        representing each different chart type.

        Yields:
            :class:`GlyphRenderer`
        """
        raise NotImplementedError(
            'Subclasses of %s must implement _yield_renderers.' %
            self.__class__.__name__)

    def set_ranges(self):
        """Calculate and set the x and y ranges.

        It has to be implemented by any of the subclasses of builder
        representing each different chart type, and is called after
        :meth:`yield_renderers`.

        Returns:
            None

        """
        raise NotImplementedError(
            'Subclasses of %s must implement _set_ranges.' %
            self.__class__.__name__)

    def get_dim_extents(self):
        """Helper method to retrieve maximum extents of all the renderers.

        Returns:
            a dict mapping between dimension and value for x_max, y_max, x_min, y_min

        """
        return {
            'x_max': max([renderer.x_max for renderer in self.comp_glyphs]),
            'y_max': max([renderer.y_max for renderer in self.comp_glyphs]),
            'x_min': min([renderer.x_min for renderer in self.comp_glyphs]),
            'y_min': min([renderer.y_min for renderer in self.comp_glyphs])
        }

    def add_glyph(self, group, glyph):
        """Add a composite glyph.

        Manages the legend, since the builder might not want all attribute types
        used for the legend.

        Args:
            group (:class:`DataGroup`): the data the `glyph` is associated with
            glyph (:class:`CompositeGlyph`): the glyph associated with the `group`

        Returns:
            None
        """
        if isinstance(glyph, list):
            for sub_glyph in glyph:
                self.comp_glyphs.append(sub_glyph)
        else:
            self.comp_glyphs.append(glyph)

        # handle cases where builders have specified which attributes to use for labels
        label = None
        if len(self.label_attributes) > 0:
            for attr in self.label_attributes:
                # this will get the last attribute group label for now
                if self.attributes[attr].columns is not None:
                    label = self._get_group_label(group, attr=attr)

        # if no special case for labeling, just use the group label
        if label is None:
            label = self._get_group_label(group, attr='label')

        # add to legend if new and unique label
        if str(label) not in self.labels and label is not None:
            self._legends.append((label, glyph.renderers))
            self.labels.append(label)

    def _get_group_label(self, group, attr='label'):
        """Get the label of the group by the attribute name.

        Args:
            group (:attr:`DataGroup`: the group of data
            attr (str, optional): the attribute name containing the label, defaults to
                'label'.

        Returns:
            str: the label for the group
        """
        if attr is 'label':
            label = group.label
        else:
            label = group[attr]
            if isinstance(label, dict):
                label = tuple(label.values())

        return self._get_label(label)

    @staticmethod
    def _get_label(raw_label):
        """Converts a label by string or tuple to a string representation.

        Args:
            raw_label (str or tuple(any, any)): a unique identifier for the data group

        Returns:
            str: a label that is usable in charts
        """
        # don't convert None type to string so we can test for it later
        if raw_label is None:
            return None

        if (isinstance(raw_label, tuple) or isinstance(raw_label, list)) and \
                       len(raw_label) == 1:
            raw_label = raw_label[0]
        elif isinstance(raw_label, dict):
            raw_label = label_from_index_dict(raw_label)

        return str(raw_label)

    def collect_attr_kwargs(self):
        if hasattr(super(self.__class__, self), 'default_attributes'):
            attrs = set(self.default_attributes.keys()) - set(
                (super(self.__class__, self).default_attributes or {}).keys())
        else:
            attrs = set()
        return attrs

    def get_group_kwargs(self, group, attrs):
        return {attr: group[attr] for attr in attrs}

    def create(self, chart=None):
        """Builds the renderers, adding them and other components to the chart.

        Args:
            chart (:class:`Chart`, optional): the chart that will contain the glyph
                renderers that the `Builder` produces.

        Returns:
            :class:`Chart`
        """
        # call methods that allow customized setup by subclasses
        self.setup()
        self.process_data()

        # create and add renderers to chart
        renderers = self.yield_renderers()
        if chart is None:
            chart = Chart()
        chart.add_renderers(self, renderers)

        # handle ranges after renders, since ranges depend on aggregations
        # ToDo: should reconsider where this occurs
        self.set_ranges()
        chart.add_ranges('x', self.x_range)
        chart.add_ranges('y', self.y_range)

        # sort the legend if we are told to
        self._legends = self._sort_legend(self.legend_sort_field,
                                          self.legend_sort_direction,
                                          self._legends, self.attributes)

        # always contribute legends, let Chart sort it out
        chart.add_legend(self._legends)

        chart.add_labels('x', self.xlabel)
        chart.add_labels('y', self.ylabel)

        chart.add_scales('x', self.xscale)
        chart.add_scales('y', self.yscale)

        if self.tooltips is not None:
            tooltips = build_hover_tooltips(hover_spec=self.tooltips,
                                            chart_cols=self.attribute_columns)
            chart.add_tooltips(tooltips)

        return chart

    @classmethod
    def generate_help(cls):
        help_str = ''
        for comp_glyph in cls.comp_glyph_types:
            help_str += str(comp_glyph.glyph_properties())

        return help_str

    @staticmethod
    def _sort_legend(legend_sort_field, legend_sort_direction, legends,
                     attributes):
        """Sort legends sorted by looping though sort_legend items (
        see :attr:`Builder.sort_legend` for more details)
        """
        if legend_sort_field:
            if len(attributes[legend_sort_field].columns) > 0:

                # TODO(fpliger): attributes should be consistent and not
                #               need any type checking but for
                #               the moment it is not, specially when going
                #               though a process like binning or when data
                #               is built for HeatMap, Scatter, etc...
                item_order = [
                    x[0] if isinstance(x, tuple) else x
                    for x in attributes[legend_sort_field].items
                ]

                item_order = [
                    str(x) if not isinstance(x, string_types) else x
                    for x in item_order
                ]

                def foo(leg):
                    return item_order.index(leg[0])

                reverse = legend_sort_direction == 'descending'
                return list(sorted(legends, key=foo, reverse=reverse))

        return legends

    @property
    def sort_legend(self):
        deprecated((0, 12, 0), 'Chart.sort_legend', 'Chart.legend_sort_field')
        return [(self.legend_sort_field, self.legend_sort_direction)]

    @sort_legend.setter
    def sort_legend(self, value):
        deprecated((0, 12, 0), 'Chart.sort_legend', 'Chart.legend_sort_field')
        self.legend_sort_field, direction = value[0]
        if direction:
            self.legend_sort_direction = "ascending"
        else:
            self.legend_sort_direction = "descending"
Ejemplo n.º 27
0
class AttrSpec(HasProps):
    """A container for assigning attributes to values and retrieving them as needed.

    A special function this provides is automatically handling cases where the provided
    iterator is too short compared to the distinct values provided.

    Once created as attr_spec, you can do attr_spec[data_label], where data_label must
    be a one dimensional tuple of values, representing the unique group in the data.

    See the :meth:`AttrSpec.setup` method for the primary way to provide an existing
    AttrSpec with data and column values and update all derived property values.
    """

    data = Instance(ColumnDataSource)

    iterable = List(Any, default=None)

    attrname = String(help='Name of the attribute the spec provides.')

    columns = Either(ColumnLabel, List(ColumnLabel), help="""
        The label or list of column labels that correspond to the columns that will be
        used to find all distinct values (single column) or combination of values (
        multiple columns) to then assign a unique attribute to. If not enough unique
        attribute values are found, then the attribute values will be cycled.
        """)

    default = Any(default=None, help="""
        The default value for the attribute, which is used if no column is assigned to
        the attribute for plotting. If the default value is not provided, the first
        value in the `iterable` property is used.
        """)

    attr_map = Dict(Any, Any, help="""
        Created by the attribute specification when `iterable` and `data` are
        available. The `attr_map` will include a mapping between the distinct value(s)
        found in `columns` and the attribute value that has been assigned.
        """)

    items = Any(default=None, help="""
        The attribute specification calculates this list of distinct values that are
        found in `columns` of `data`.
        """)

    sort = Bool(default=True, help="""
        A boolean flag to tell the attribute specification to sort `items`, when it is
        calculated. This affects which value of `iterable` is assigned to each distinct
        value in `items`.
        """)

    ascending = Bool(default=True, help="""
        A boolean flag to tell the attribute specification how to sort `items` if the
        `sort` property is set to `True`. The default setting for `ascending` is `True`.
        """)

    bins = Instance(Bins, help="""
        If an attribute spec is binning data, so that we can map one value in the
        `iterable` to one value in `items`, then this attribute will contain an instance
        of the Bins stat. This is used to create unique labels for each bin, which is
        then used for `items` instead of the actual unique values in `columns`.
        """)

    def __init__(self, columns=None, df=None, iterable=None, default=None,
                 items=None, **properties):
        """Create a lazy evaluated attribute specification.

        Args:
            columns: a list of column labels
            df(:class:`~pandas.DataFrame`): the data source for the attribute spec.
            iterable: an iterable of distinct attribute values
            default: a value to use as the default attribute when no columns are passed
            items: the distinct values in columns. If items is provided as input,
                then the values provided are used instead of being calculated. This can
                be used to force a specific order for assignment.
            **properties: other properties to pass to parent :class:`HasProps`
        """
        properties['columns'] = self._ensure_list(columns)

        if df is not None:
            properties['data'] = ColumnDataSource(df)

        if default is None and iterable is not None:
            default_iter = copy(iterable)
            properties['default'] = next(iter(default_iter))
        elif default is not None:
            properties['default'] = default

        if iterable is not None:
            properties['iterable'] = iterable

        if items is not None:
            properties['items'] = items

        super(AttrSpec, self).__init__(**properties)

        if self.default is None and self.iterable is not None:
            self.default = next(iter(copy(self.iterable)))

        if self.data is not None and self.columns is not None:
            if df is None:
                df = self.data.to_df()

            self._generate_items(df, columns=self.columns)

        if self.items is not None and self.iterable is not None:
            self.attr_map = self._create_attr_map()

    @staticmethod
    def _ensure_list(attr):
        """Always returns a list with the provided value. Returns the value if a list."""
        if isinstance(attr, str):
            return [attr]
        elif isinstance(attr, tuple):
            return list(attr)
        else:
            return attr

    @staticmethod
    def _ensure_tuple(attr):
        """Return tuple with the provided value. Returns the value if a tuple."""
        if not isinstance(attr, tuple):
            return (attr,)
        else:
            return attr

    def _setup_default(self):
        """Stores the first value of iterable into `default` property."""
        self.default = next(self._setup_iterable())

    def _setup_iterable(self):
        """Default behavior is to copy and cycle the provided iterable."""
        return cycle(copy(self.iterable))

    def _generate_items(self, df, columns):
        """Produce list of unique tuples that identify each item."""
        if self.sort:
            # TODO (fpliger):   this handles pandas API change so users do not experience
            #                   the related annoying deprecation warning. This is probably worth
            #                   removing when pandas deprecated version (0.16) is "old" enough
            try:
                df = df.sort_values(by=columns, ascending=self.ascending)
            except AttributeError:
                df = df.sort(columns=columns, ascending=self.ascending)

        items = df[columns].drop_duplicates()
        self.items = [tuple(x) for x in items.to_records(index=False)]

    def _create_attr_map(self, df=None, columns=None):
        """Creates map between unique values and available attributes."""

        if df is not None and columns is not None:
            self._generate_items(df, columns)

        iterable = self._setup_iterable()

        return {item: next(iterable) for item in self._item_tuples()}
    
    def _item_tuples(self):
        return [self._ensure_tuple(item) for item in self.items]

    def set_columns(self, columns):
        """Set columns property and update derived properties as needed."""
        columns = self._ensure_list(columns)

        if all([col in self.data.column_names for col in columns]):
            self.columns = columns
        else:
            # we have input values other than columns
            # assume this is now the iterable at this point
            self.iterable = columns
            self._setup_default()

    def setup(self, data=None, columns=None):
        """Set the data and update derived properties as needed."""
        if data is not None:
            self.data = data

        if columns is not None and self.data is not None:
            self.set_columns(columns)

        if self.columns is not None and self.data is not None:
            self.attr_map = self._create_attr_map(self.data.to_df(), self.columns)

    def update_data(self, data):
        self.setup(data=data, columns=self.columns)

    def __getitem__(self, item):
        """Lookup the attribute to use for the given unique group label."""

        if not self.attr_map:
            return self.default
        elif self._ensure_tuple(item) not in self.attr_map.keys():

            # make sure we have attr map
            self.setup()

        return self.attr_map[self._ensure_tuple(item)]

    @property
    def series(self):
        if not self.attr_map:
            return pd.Series()
        else:
            index = pd.MultiIndex.from_tuples(self._item_tuples(), names=self.columns)
            return pd.Series(list(self.attr_map.values()), index=index)
Ejemplo n.º 28
0
class EitherContainerDefault(hp.HasProps):
    foo = Either(List(Int), Int, default=[10])
Ejemplo n.º 29
0
 def test_Either(self, detail):
     p = Either(Int, Float)
     with pytest.raises(ValueError) as e:
         p.validate("junk", detail)
     assert str(e).endswith("ValueError") == (not detail)
Ejemplo n.º 30
0
class Bin(Stat):
    """Represents a single bin of data values and attributes of the bin."""
    label = Either(String, List(String))
    start = Either(Float, List(Float))
    stop = Either(Float, List(Float))

    start_label = String()
    stop_label = String()

    center = Either(Float, List(Float))

    stat = Instance(Stat, default=Count())
    width = Float()

    def __init__(self, bin_label, values=None, source=None, **properties):
        if isinstance(bin_label, tuple):
            bin_label = list(bin_label)
        else:
            bin_label = [bin_label]
        properties['label'] = bin_label

        bounds = self.process_bounds(bin_label)

        starts, stops = zip(*bounds)
        centers = [(start + stop) / 2.0 for start, stop in zip(starts, stops)]
        if len(starts) == 1:
            starts = starts[0]
            stops = stops[0]
            centers = centers[0]
        else:
            starts = list(starts)
            stops = list(stops)
            centers = list(centers)

        properties['start'] = starts
        properties['stop'] = stops
        properties['center'] = centers
        properties['values'] = values
        super(Bin, self).__init__(**properties)

    @staticmethod
    def binstr_to_list(bins):
        """Produce a consistent display of a bin of data."""
        value_chunks = bins.split(',')
        value_chunks = [
            val.replace('[', '').replace(']', '').replace('(',
                                                          '').replace(')', '')
            for val in value_chunks
        ]
        bin_values = [float(value) for value in value_chunks]

        return bin_values[0], bin_values[1]

    def process_bounds(self, bin_label):
        if isinstance(bin_label, list):
            return [self.binstr_to_list(dim) for dim in bin_label]
        else:
            return [self.binstr_to_list(bin_label)]

    def update(self):
        self.stat.set_data(self.values)

    def calculate(self):
        self.value = self.stat.value
Ejemplo n.º 31
0
class BoxGlyph(AggregateGlyph):
    """Summarizes the distribution with a collection of glyphs.

    A box glyph produces one "box" for a given array of vales. The box
    is made up of multiple other child composite glyphs (intervals,
    scatter) and directly produces glyph renderers for the whiskers,
    as well.
    """

    q1 = Float(help="""Derived value for 25% of all values.""")
    q2 = Float(help="""Derived value for 50% of all values.""")
    q3 = Float(help="""Derived value for 75% of all values.""")
    iqr = Float()

    w0 = Float(help='Lower whisker')
    w1 = Float(help='Upper whisker')

    q2_glyph = Instance(QuartileGlyph)
    q3_glyph = Instance(QuartileGlyph)

    whisker_glyph = Instance(GlyphRenderer)

    outliers = Either(Bool, Instance(PointGlyph))

    marker = String(default='circle')
    whisker_width = Float(default=0.3)
    whisker_line_width = Float(default=2)
    whisker_span_line_width = Float(default=2)
    whisker_color = String(default='black')

    outlier_fill_color = String(default='red')
    outlier_line_color = String(default='red')
    outlier_size = Float(default=5)

    bar_color = String(default='DimGrey')

    def __init__(self, label, values, outliers=True, **kwargs):
        width = kwargs.pop('width', None)

        bar_color = kwargs.pop('color', None) or kwargs.get(
            'bar_color') or self.lookup('bar_color').class_default()

        kwargs['outliers'] = kwargs.pop('outliers', None) or outliers
        kwargs['label'] = label
        kwargs['values'] = values

        x_label = kwargs.get('x_label')
        kwargs['q2_glyph'] = QuartileGlyph(label=label,
                                           x_label=x_label,
                                           values=values,
                                           interval1=0.25,
                                           interval2=0.5,
                                           width=width,
                                           color=bar_color)
        kwargs['q3_glyph'] = QuartileGlyph(label=label,
                                           x_label=x_label,
                                           values=values,
                                           interval1=0.5,
                                           interval2=0.75,
                                           width=width,
                                           color=bar_color)
        super(BoxGlyph, self).__init__(**kwargs)
        self.setup()

    def build_renderers(self):
        """Yields all renderers that make up the BoxGlyph."""

        self.calc_quartiles()
        outlier_values = self.values[((self.values < self.w0) |
                                      (self.values > self.w1))]

        self.whisker_glyph = GlyphRenderer(
            glyph=Segment(x0='x0s',
                          y0='y0s',
                          x1='x1s',
                          y1='y1s',
                          line_width=self.whisker_line_width,
                          line_color=self.whisker_color))

        if len(outlier_values) > 0 and self.outliers:
            self.outliers = PointGlyph(label=self.label,
                                       y=outlier_values,
                                       x=[self.get_dodge_label()] *
                                       len(outlier_values),
                                       line_color=self.outlier_line_color,
                                       fill_color=self.outlier_fill_color,
                                       size=self.outlier_size,
                                       marker=self.marker)

        for comp_glyph in self.composite_glyphs:
            for renderer in comp_glyph.renderers:
                yield renderer

        yield self.whisker_glyph

    def calc_quartiles(self):
        """Sets all derived stat properties of the BoxGlyph."""
        self.q1 = self.q2_glyph.start
        self.q2 = self.q2_glyph.end
        self.q3 = self.q3_glyph.end
        self.iqr = self.q3 - self.q1

        mx = Max()
        mx.set_data(self.values)

        mn = Min()
        mn.set_data(self.values)

        self.w0 = max(self.q1 - (1.5 * self.iqr), mn.value)
        self.w1 = min(self.q3 + (1.5 * self.iqr), mx.value)

    def build_source(self):
        """Calculate stats and builds and returns source for whiskers."""
        self.calc_quartiles()
        x_label = self.get_dodge_label()
        x_w0_label = self.get_dodge_label(shift=(self.whisker_width / 2.0))
        x_w1_label = self.get_dodge_label(shift=-(self.whisker_width / 2.0))

        # span0, whisker bar0, span1, whisker bar1
        x0s = [x_label, x_w0_label, x_label, x_w0_label]
        y0s = [self.w0, self.w0, self.q3, self.w1]
        x1s = [x_label, x_w1_label, x_label, x_w1_label]
        y1s = [self.q1, self.w0, self.w1, self.w1]

        return dict(x0s=x0s, y0s=y0s, x1s=x1s, y1s=y1s)

    def _set_sources(self):
        """Set the column data source on the whisker glyphs."""
        self.whisker_glyph.data_source = self.source

    def get_extent(self, func, prop_name):
        return func([
            getattr(renderer, prop_name) for renderer in self.composite_glyphs
        ])

    @property
    def composite_glyphs(self):
        """Returns list of composite glyphs, excluding the regular glyph renderers."""
        comp_glyphs = [self.q2_glyph, self.q3_glyph]
        if isinstance(self.outliers, PointGlyph):
            comp_glyphs.append(self.outliers)
        return comp_glyphs

    @property
    def x_max(self):
        return self.get_extent(max, 'x_max') + self.right_buffer

    @property
    def x_min(self):
        return self.get_extent(min, 'x_min') - self.left_buffer

    @property
    def y_max(self):
        return max(self.w1, self.get_extent(max, 'y_max')) + self.top_buffer

    @property
    def y_min(self):
        return min(self.w0, self.get_extent(min, 'y_min')) - self.bottom_buffer
Ejemplo n.º 32
0
class EitherSimpleDefault(hp.HasProps):
    foo = Either(List(Int), Int, default=10)