class IonRangeSlider(InputWidget): # The special class attribute ``__implementation__`` should contain a string # of JavaScript, TypeScript or CoffeeScript code that implements the web broser # side of the custom extension model or a string name of a file with the implementation. __implementation__ = 'extensions_ion_range_slider.ts' __javascript__ = ["https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/js/ion.rangeSlider.js"] __css__ = ["https://cdnjs.cloudflare.com/ajax/libs/normalize/4.2.0/normalize.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.skinFlat.min.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/img/sprite-skin-flat.png"] # Below are all the "properties" for this model. Bokeh properties are # class attributes that define the fields (and their types) that can be # communicated automatically between Python and the browser. Properties # also support type validation. More information about properties in # can be found here: # # https://bokeh.pydata.org/en/latest/docs/reference/core.html#bokeh-core-properties disable = Bool(default=True, help=""" Enable or disable the slider. """) grid = Bool(default=True, help=""" Show or hide the grid beneath the slider. """) start = Float(default=0, help=""" The minimum allowable value. """) end = Float(default=1, help=""" The maximum allowable value. """) range = Tuple(Float, Float, help=""" The start and end values for the range. """) step = Float(default=0.1, help=""" The step between consecutive values. """) callback = Instance(Callback, help=""" A callback to run in the browser whenever the current Slider value changes. """) callback_throttle = Float(default=200, help=""" Number of microseconds to pause between callback calls as the slider is moved. """) callback_policy = Enum(SliderCallbackPolicy, default="throttle", help=""" When the callback is initiated. This parameter can take on only one of three options: "continuous": the callback will be executed immediately for each movement of the slider "throttle": the callback will be executed at most every ``callback_throttle`` milliseconds. "mouseup": the callback will be executed only once when the slider is released. The `mouseup` policy is intended for scenarios in which the callback is expensive in time. """)
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.) """)
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))
def test_Tuple(self) -> None: p = Tuple(Int, Int) with pytest.raises(ValueError) as e: p.validate("junk") assert matches( str(e.value), r"expected an element of Tuple\(Int, Int\), got 'junk'")
class IDOM(HTMLBox): importSourceUrl = String() event = Tuple(Any, Any) msg = Either(Dict(String, Any), Null)
class IonRangeSlider(InputWidget): # The special class attribute ``__implementation__`` should contain a string # of JavaScript or TypeScript code that implements the web browser # side of the custom extension model or a string name of a file with the implementation. __implementation__ = 'extensions_ion_range_slider.ts' __javascript__ = [ "https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/js/ion.rangeSlider.js" ] __css__ = [ "https://cdnjs.cloudflare.com/ajax/libs/normalize/4.2.0/normalize.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.skinFlat.min.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/img/sprite-skin-flat.png" ] # Below are all the "properties" for this model. Bokeh properties are # class attributes that define the fields (and their types) that can be # communicated automatically between Python and the browser. Properties # also support type validation. More information about properties in # can be found here: # # https://docs.bokeh.org/en/latest/docs/reference/core/properties.html#bokeh-core-properties disable = Bool(default=True, help=""" Enable or disable the slider. """) grid = Bool(default=True, help=""" Show or hide the grid beneath the slider. """) start = Float(default=0, help=""" The minimum allowable value. """) end = Float(default=1, help=""" The maximum allowable value. """) range = Tuple(Float, Float, help=""" The start and end values for the range. """) step = Float(default=0.1, help=""" The step between consecutive values. """) callback = Instance(Callback, help=""" A callback to run in the browser whenever the current Slider value changes. """)
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='')
class Skymap(LayoutDOM): __javascript__ = [ "https://slowe.github.io/VirtualSky/stuquery.min.js", "https://slowe.github.io/VirtualSky/virtualsky.min.js", ] # Below are all the "properties" for this model. Bokeh properties are # class attributes that define the fields (and their types) that can be # communicated automatically between Python and the browser. Properties # also support type validation. More information about properties in # can be found here: # # https://docs.bokeh.org/en/latest/docs/reference/core/properties.html#bokeh-core-properties latlon = Tuple(Angle, Angle, default=(0, 0)) azel = Tuple(Angle, Angle, default=(0, 0)) targetAzel = Tuple(Angle, Angle) pointer_data_source = Instance(ColumnDataSource)
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. """)
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")
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")
class ActionMenuColumn(TableColumn): width = Override(default=100) menu = List(Either(String, Tuple(String, Either(String, Instance(Callback)))), help=""" Button's dropdown menu consisting of entries containing item's text and value name. Use ``None`` as a menu separator. """)
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. """)
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")
class LuminoDock(HTMLBox): children = List(Either(Tuple(String, Instance(LayoutDOM)), Tuple(String, Instance(LayoutDOM), InsertMode), Tuple(String, Instance(LayoutDOM), InsertMode, Int)), default=[])
def test_Tuple(self, detail): p = Tuple(Int, Int) with pytest.raises(ValueError) as e: p.validate("junk", detail) assert str(e).endswith("ValueError") == (not detail)
def test_Tuple(self): p = Tuple(Int, Int) with pytest.raises(ValueError) as e: p.validate("junk") assert not str(e).endswith("ValueError")
class IonRangeSlider(AbstractSlider): # The special class attribute ``__implementation__`` should contain a string # of JavaScript (or CoffeeScript) code that implements the JavaScript side # of the custom extension model or a string name of a JavaScript (or # CoffeeScript) file with the implementation. with open( os.path.join(os.path.dirname(__file__), '../bokeh-ion-rangesliderjs/src/ion_range_slider.ts') ) as file_: implementation = file_.readlines() __implementation__ = TypeScript(''.join(implementation)) __javascript__ = [ "https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/js/ion.rangeSlider.js" ] __css__ = [ "https://cdnjs.cloudflare.com/ajax/libs/normalize/4.2.0/normalize.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/css/ion.rangeSlider.skinFlat.min.css", "https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.1.4/img/sprite-skin-flat.png" ] # Below are all the "properties" for this model. Bokeh properties are # class attributes that define the fields (and their types) that can be # communicated automatically between Python and the browser. Properties # also support type validation. More information about properties in # can be found here: # # http://bokeh.pydata.org/en/latest/docs/reference/core.html#bokeh-core-properties start = Float(default=0, help=""" The minimum allowable value. """) end = Float(default=1, help=""" The maximum allowable value. """) value = Tuple(Float, Float, default=[0, 1], help=""" Initial or selected range. """) step = Float(default=0.1, help=""" The step between consecutive values. """) #format = String(default="0[.]00") Ignored right now #bar_color = Color(default="#e6e6e6", help=""" """) Overwritten default slider_type = Enum(enumeration('single', 'double'), default='single', help=""" Choose slider type, could be single - for one handle, or double for two handles. """) values = List(Any, help=""" Set up your own array of possible slider values. They could be numbers or strings. The following attributes are ignored if you supply values: min, max and step. """) grid = Bool(default=True, help=""" Show the grid beneath the slider. """) prettify_enabled = Bool(default=True, help=""" Improve readability of long numbers. 10000000 -> 10 000 000 """) prettify = Instance(Callback, help=""" Set up your own prettify function. Can be anything. For example, you can set up unix time as slider values and than transform them to cool looking dates. """) force_edges = Bool(default=False, help=""" Slider will be always inside it's container. """) prefix = String(default="", help=""" Set prefix for values. Will be set up right before the number: $100 """)
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'], )
class DataTabulator(HTMLBox): """A Bokeh Model that enables easy use of Tabulator tables See http://tabulator.info/ """ aggregators = Dict(String, String) buttons = Dict(String, String) configuration = Dict(String, Any) columns = List(Instance(TableColumn), help=""" The list of child column widgets. """) download = Bool(default=False) children = Dict(Int, Instance(LayoutDOM)) editable = Bool(default=True) expanded = List(Int) filename = String(default="table.csv") filters = List(Any) follow = Bool(True) frozen_rows = List(Int) groupby = List(String) hidden_columns = List(String) indexes = List(String) layout = Enum('fit_data', 'fit_data_fill', 'fit_data_stretch', 'fit_data_table', 'fit_columns', default="fit_data") source = Instance(ColumnDataSource) styles = Dict( String, Either( String, Dict(Int, Dict(Int, List(Either(String, Tuple(String, String))))))) pagination = Nullable(String) page = Nullable(Int) page_size = Int() max_page = Int() sorters = List(Dict(String, String)) select_mode = Any() selectable_rows = Nullable(List(Int)) theme = Enum(*TABULATOR_THEMES, default="simple") theme_url = String(default=THEME_URL) __css_raw__ = CSS_URLS @classproperty def __css__(cls): cls.__css_raw__ = [ url for url in cls.__css_raw__ if 'simple' in url or len(cls.__css_raw__) == 1 ] return bundled_files(cls, 'css') __javascript_raw__ = [JS_SRC, MOMENT_SRC] @classproperty def __javascript__(cls): return bundled_files(cls) @classproperty def __js_skip__(cls): return { 'Tabulator': cls.__javascript__[:1], 'moment': cls.__javascript__[1:] } __js_require__ = { 'paths': { 'tabulator': JS_SRC[:-3], 'moment': MOMENT_SRC[:-3] }, 'exports': { 'tabulator': 'Tabulator', 'moment': 'moment' } }
def test_Tuple(self, detail) -> None: p = Tuple(Int, Int) with pytest.raises(ValueError) as e: p.validate("junk", detail) assert (str(e.value) == "") == (not detail)
class DatetimePicker(InputWidget): ''' Calendar-based date picker widget. ''' value = String(help=""" The initial or picked date. """) min_date = Nullable(Either(Date, Datetime), help=""" Optional earliest allowable date. """) max_date = Nullable(Either(Date, Datetime), help=""" Optional latest allowable date. """) disabled_dates = List(Either(Date, Datetime, Tuple(Date, Date), Tuple(Datetime, Datetime)), default=[], help=""" A list of dates of ``(start, end)`` date ranges to make unavailable for selection. All other dates will be avalable. .. note:: Only one of ``disabled_dates`` and ``enabled_dates`` should be specified. """) enabled_dates = List(Either(Date, Datetime, Tuple(Date, Date), Tuple(Datetime, Datetime)), default=[], help=""" A list of dates of ``(start, end)`` date ranges to make available for selection. All other dates will be unavailable. .. note:: Only one of ``disabled_dates`` and ``enabled_dates`` should be specified. """) position = Enum(CalendarPosition, default="auto", help=""" Where the calendar is rendered relative to the input when ``inline`` is False. """) inline = Bool(default=False, help=""" Whether the calendar sholud be displayed inline. """) enable_time = Bool(default=True) enable_seconds = Bool(default=True) military_time = Bool(default=True) date_format = String("Y-m-d H:i:S") mode = String(default="single", help=""" Should either be "single" or "range".""")
class V(self.pObjectClass): u1 = Instance(U) u2 = List(Instance(U)) u3 = Tuple(Int, Instance(U)) u4 = Dict(String, Instance(U)) u5 = Dict(String, List(Instance(U)))
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"