#-----------------------------------------------------------------------------# Copyright (c) Anaconda, Inc., and Bokeh Contributors.# All rights reserved.## The full license is in the file LICENSE.txt, distributed with this software.#-----------------------------------------------------------------------------""" Provide the DataSpec properties and helpers."""#-----------------------------------------------------------------------------# Boilerplate#-----------------------------------------------------------------------------from__future__importannotationsimportlogging# isort:skiplog=logging.getLogger(__name__)#-----------------------------------------------------------------------------# Imports#-----------------------------------------------------------------------------# Standard library importsfromtypingimportTYPE_CHECKING,Any# Bokeh importsfrom...util.dataclassesimportUnspecifiedfrom...util.serializationimportconvert_datetime_type,convert_timedelta_typefrom...util.stringsimportnice_joinfrom..importenumsfrom.colorimportALPHA_DEFAULT_HELP,COLOR_DEFAULT_HELP,Colorfrom.datetimeimportDatetime,TimeDeltafrom.descriptorsimportDataSpecPropertyDescriptor,UnitsSpecPropertyDescriptorfrom.eitherimportEitherfrom.enumimportEnumfrom.instanceimportInstancefrom.nothingimportNothingfrom.nullableimportNullablefrom.primitiveimport(Float,Int,Null,String,)from.serializedimportNotSerializedfrom.singletonsimportUndefinedfrom.structimportOptional,Structfrom.vectorizationimport(Expr,Field,Value,Vectorized,)from.visualimport(DashPattern,FontSize,HatchPatternType,MarkerType,)ifTYPE_CHECKING:from...core.has_propsimportHasProps#-----------------------------------------------------------------------------# Globals and constants#-----------------------------------------------------------------------------__all__=('AlphaSpec','AngleSpec','ColorSpec','DashPatternSpec','DataSpec','DistanceSpec','FontSizeSpec','FontStyleSpec','HatchPatternSpec','IntSpec','LineCapSpec','LineJoinSpec','MarkerSpec','NumberSpec','SizeSpec','StringSpec','TextAlignSpec','TextBaselineSpec',)#-----------------------------------------------------------------------------# General API#-----------------------------------------------------------------------------
[docs]classDataSpec(Either):""" Base class for properties that accept either a fixed value, or a string name that references a column in a :class:`~bokeh.models.sources.ColumnDataSource`. Many Bokeh models have properties that a user might want to set either to a single fixed value, or to have the property take values from some column in a data source. As a concrete example consider a glyph with an ``x`` property for location. We might want to set all the glyphs that get drawn to have the same location, say ``x=10``. It would be convenient to just be able to write: .. code-block:: python glyph.x = 10 Alternatively, maybe each glyph that gets drawn should have a different location, according to the "pressure" column of a data source. In this case we would like to be able to write: .. code-block:: python glyph.x = "pressure" Bokeh ``DataSpec`` properties (and subclasses) afford this ease of and consistency of expression. Ultimately, all ``DataSpec`` properties resolve to dictionary values, with either a ``"value"`` key, or a ``"field"`` key, depending on how it is set. For instance: .. code-block:: python glyph.x = 10 # => { 'value': 10 } glyph.x = "pressure" # => { 'field': 'pressure' } When these underlying dictionary values are received in the browser, BokehJS knows how to interpret them and take the correct, expected action (i.e., draw the glyph at ``x=10``, or draw the glyph with ``x`` coordinates from the "pressure" column). In this way, both use-cases may be expressed easily in python, without having to handle anything differently, from the user perspective. It is worth noting that ``DataSpec`` properties can also be set directly with properly formed dictionary values: .. code-block:: python glyph.x = { 'value': 10 } # same as glyph.x = 10 glyph.x = { 'field': 'pressure' } # same as glyph.x = "pressure" Setting the property directly as a dict can be useful in certain situations. For instance some ``DataSpec`` subclasses also add a ``"units"`` key to the dictionary. This key is often set automatically, but the dictionary format provides a direct mechanism to override as necessary. Additionally, ``DataSpec`` can have a ``"transform"`` key, that specifies a client-side transform that should be applied to any fixed or field values before they are uses. As an example, you might want to apply a ``Jitter`` transform to the ``x`` values: .. code-block:: python glyph.x = { 'value': 10, 'transform': Jitter(width=0.4) } Note that ``DataSpec`` is not normally useful on its own. Typically, a model will define properties using one of the subclasses such as :class:`~bokeh.core.properties.NumberSpec` or :class:`~bokeh.core.properties.ColorSpec`. For example, a Bokeh model with ``x``, ``y`` and ``color`` properties that can handle fixed values or columns automatically might look like: .. code-block:: python class SomeModel(Model): x = NumberSpec(default=0, help="docs for x") y = NumberSpec(default=0, help="docs for y") color = ColorSpec(help="docs for color") # defaults to None """def__init__(self,value_type,default,*,help:str|None=None)->None:super().__init__(String,value_type,Instance(Value),Instance(Field),Instance(Expr),Struct(value=value_type,transform=Optional(Instance("bokeh.models.transforms.Transform")),),Struct(field=String,transform=Optional(Instance("bokeh.models.transforms.Transform")),),Struct(expr=Instance("bokeh.models.expressions.Expression"),transform=Optional(Instance("bokeh.models.transforms.Transform")),),default=default,help=help,)self.value_type=self._validate_type_param(value_type)self.accepts(Instance("bokeh.models.expressions.Expression"),lambdaobj:Expr(obj))deftransform(self,value:Any):ifisinstance(value,dict):if"value"invalue:returnValue(**value)if"field"invalue:returnField(**value)if"expr"invalue:returnExpr(**value)returnsuper().transform(value)defmake_descriptors(self,base_name:str):""" Return a list of ``DataSpecPropertyDescriptor`` instances to install on a class, in order to delegate attribute access to this property. Args: base_name (str) : the name of the property these descriptors are for Returns: list[DataSpecPropertyDescriptor] The descriptors returned are collected by the ``MetaHasProps`` metaclass and added to ``HasProps`` subclasses during class creation. """return[DataSpecPropertyDescriptor(base_name,self)]defto_serializable(self,obj:HasProps,name:str,val:Any)->Vectorized:# Check for spec type valuetry:self.value_type.replace(String,Nothing()).validate(val,False)returnValue(val)exceptValueError:pass# Check for data source field nameifisinstance(val,str):returnField(val)returnval
[docs]classNumberSpec(DataSpec):""" A |DataSpec| property that accepts numeric and datetime fixed values. By default, date and datetime values are immediately converted to milliseconds since epoch. It is possible to disable processing of datetime values by passing ``accept_datetime=False``. By default, timedelta values are immediately converted to absolute milliseconds. It is possible to disable processing of timedelta values by passing ``accept_timedelta=False`` Timedelta values are interpreted as absolute milliseconds. .. code-block:: python m.location = 10.3 # value m.location = "foo" # field """def__init__(self,default=Undefined,*,help:str|None=None,accept_datetime=True,accept_timedelta=True)->None:super().__init__(Float,default=default,help=help)ifaccept_timedelta:self.accepts(TimeDelta,convert_timedelta_type)ifaccept_datetime:self.accepts(Datetime,convert_datetime_type)
[docs]classStringSpec(DataSpec):""" A |DataSpec| property that accepts string fixed values. Because acceptable fixed values and field names are both strings, it can be necessary explicitly to disambiguate these possibilities. By default, string values are interpreted as fields, but you can use the |value| function to specify that a string is interpreted as a value: .. code-block:: python m.title = value("foo") # value m.title = "foo" # field """def__init__(self,default,*,help:str|None=None)->None:super().__init__(String,default=default,help=help)
[docs]classFontSizeSpec(DataSpec):""" A |DataSpec| property that accepts font-size fixed values. The ``FontSizeSpec`` property attempts to first interpret string values as font sizes (i.e. valid CSS length values). Otherwise, string values are interpreted as field names. For example: .. code-block:: python m.font_size = "13px" # value m.font_size = "1.5em" # value m.font_size = "foo" # field A full list of all valid CSS length units can be found here: https://drafts.csswg.org/css-values/#lengths """def__init__(self,default,*,help:str|None=None)->None:super().__init__(FontSize,default=default,help=help)defvalidate(self,value:Any,detail:bool=True)->None:# We want to preserve existing semantics and be a little more restrictive. This# validations makes m.font_size = "" or m.font_size = "6" an errorsuper().validate(value,detail)ifisinstance(value,str):iflen(value)==0orvalue[0].isdigit()andnotFontSize._font_size_re.match(value):msg=""ifnotdetailelsef"{value!r} is not a valid font size value"raiseValueError(msg)
classFontStyleSpec(DataSpec):def__init__(self,default,*,help:str|None=None)->None:super().__init__(Enum(enums.FontStyle),default=default,help=help)classTextAlignSpec(DataSpec):def__init__(self,default,*,help:str|None=None)->None:super().__init__(Enum(enums.TextAlign),default=default,help=help)classTextBaselineSpec(DataSpec):def__init__(self,default,*,help:str|None=None)->None:super().__init__(Enum(enums.TextBaseline),default=default,help=help)classLineJoinSpec(DataSpec):def__init__(self,default,*,help:str|None=None)->None:super().__init__(Enum(enums.LineJoin),default=default,help=help)classLineCapSpec(DataSpec):def__init__(self,default,*,help:str|None=None)->None:super().__init__(Enum(enums.LineCap),default=default,help=help)classDashPatternSpec(DataSpec):def__init__(self,default,*,help:str|None=None)->None:super().__init__(DashPattern,default=default,help=help)classHatchPatternSpec(DataSpec):""" A |DataSpec| property that accepts hatch pattern types as fixed values. The ``HatchPatternSpec`` property attempts to first interpret string values as hatch pattern types. Otherwise, string values are interpreted as field names. For example: .. code-block:: python m.font_size = "." # value m.font_size = "ring" # value m.font_size = "foo" # field """def__init__(self,default,*,help:str|None=None)->None:super().__init__(Nullable(HatchPatternType),default=default,help=help)
[docs]classMarkerSpec(DataSpec):""" A |DataSpec| property that accepts marker types as fixed values. The ``MarkerSpec`` property attempts to first interpret string values as marker types. Otherwise, string values are interpreted as field names. For example: .. code-block:: python m.font_size = "circle" # value m.font_size = "square" # value m.font_size = "foo" # field """def__init__(self,default,*,help:str|None=None)->None:super().__init__(MarkerType,default=default,help=help)
[docs]classUnitsSpec(NumberSpec):""" A |DataSpec| property that accepts numeric fixed values, and also provides an associated units property to store units information. """def__init__(self,default,units_enum,units_default,*,help:str|None=None)->None:super().__init__(default=default,help=help)units_type=NotSerialized(Enum(units_enum),default=units_default,help=f""" Units to use for the associated property: {nice_join(units_enum)} """)self._units_type=self._validate_type_param(units_type,help_allowed=True)self._type_params+=[Struct(value=self.value_type,transform=Optional(Instance("bokeh.models.transforms.Transform")),units=Optional(units_type),),Struct(field=String,transform=Optional(Instance("bokeh.models.transforms.Transform")),units=Optional(units_type),),Struct(expr=Instance("bokeh.models.expressions.Expression"),transform=Optional(Instance("bokeh.models.transforms.Transform")),units=Optional(units_type),),]def__str__(self)->str:units_default=self._units_type._defaultreturnf"{self.__class__.__name__}(units_default={units_default!r})"defget_units(self,obj:HasProps,name:str)->str:returngetattr(obj,name+"_units")defmake_descriptors(self,base_name:str):""" Return a list of ``PropertyDescriptor`` instances to install on a class, in order to delegate attribute access to this property. Unlike simpler property types, ``UnitsSpec`` returns multiple descriptors to install. In particular, descriptors for the base property as well as the associated units property are returned. Args: name (str) : the name of the property these descriptors are for Returns: list[PropertyDescriptor] The descriptors returned are collected by the ``MetaHasProps`` metaclass and added to ``HasProps`` subclasses during class creation. """units_name=base_name+"_units"units_props=self._units_type.make_descriptors(units_name)return[*units_props,UnitsSpecPropertyDescriptor(base_name,self,units_props[0])]defto_serializable(self,obj:HasProps,name:str,val:Any)->Vectorized:val=super().to_serializable(obj,name,val)ifval.unitsisUnspecified:units=self.get_units(obj,name)ifunits!=self._units_type._default:val.units=units# XXX: clone and update?returnval
[docs]classAngleSpec(UnitsSpec):""" A |DataSpec| property that accepts numeric fixed values, and also provides an associated units property to store angle units. Acceptable values for units are ``"deg"``, ``"rad"``, ``"grad"`` and ``"turn"``. """def__init__(self,default=Undefined,units_default="rad",*,help:str|None=None)->None:super().__init__(default=default,units_enum=enums.AngleUnits,units_default=units_default,help=help)
[docs]classDistanceSpec(UnitsSpec):""" A |DataSpec| property that accepts numeric fixed values or strings that refer to columns in a :class:`~bokeh.models.sources.ColumnDataSource`, and also provides an associated units property to store units information. Acceptable values for units are ``"screen"`` and ``"data"``. """def__init__(self,default=Undefined,units_default="data",*,help:str|None=None)->None:super().__init__(default=default,units_enum=enums.SpatialUnits,units_default=units_default,help=help)defprepare_value(self,cls,name,value):try:ifvalue<0:raiseValueError("Distances must be positive!")exceptTypeError:passreturnsuper().prepare_value(cls,name,value)
classNullDistanceSpec(DistanceSpec):def__init__(self,default=None,units_default="data",*,help:str|None=None)->None:super().__init__(default=default,units_default=units_default,help=help)self.value_type=Nullable(Float)self._type_params=[Null(),*self._type_params]defprepare_value(self,cls,name,value):try:ifvalueisnotNoneandvalue<0:raiseValueError("Distances must be positive or None!")exceptTypeError:passreturnsuper().prepare_value(cls,name,value)
[docs]classSizeSpec(NumberSpec):""" A |DataSpec| property that accepts non-negative numeric fixed values for size values or strings that refer to columns in a :class:`~bokeh.models.sources.ColumnDataSource`. """defprepare_value(self,cls,name,value):try:ifvalue<0:raiseValueError("Screen sizes must be positive")exceptTypeError:passreturnsuper().prepare_value(cls,name,value)
[docs]classColorSpec(DataSpec):""" A |DataSpec| property that accepts |Color| fixed values. The ``ColorSpec`` property attempts to first interpret string values as colors. Otherwise, string values are interpreted as field names. For example: .. code-block:: python m.color = "#a4225f" # value (hex color string) m.color = "firebrick" # value (named CSS color string) m.color = "foo" # field (named "foo") This automatic interpretation can be override using the dict format directly, or by using the |field| function: .. code-block:: python m.color = { "field": "firebrick" } # field (named "firebrick") m.color = field("firebrick") # field (named "firebrick") """def__init__(self,default,*,help:str|None=None)->None:help=f"{helpor''}\n{COLOR_DEFAULT_HELP}"super().__init__(Nullable(Color),default=default,help=help)@classmethoddefisconst(cls,val):""" Whether the value is a string color literal. Checks for a well-formed hexadecimal color value or a named color. Args: val (str) : the value to check Returns: True, if the value is a string color literal """returnisinstance(val,str)and \
((len(val)==7andval[0]=="#")orvalinenums.NamedColor)@classmethoddefis_color_tuple_shape(cls,val):""" Whether the value is the correct shape to be a color tuple Checks for a 3 or 4-tuple of numbers Args: val (str) : the value to check Returns: True, if the value could be a color tuple """returnisinstance(val,tuple)andlen(val)in(3,4)andall(isinstance(v,float|int)forvinval)defprepare_value(self,cls,name,value):# Some explanation is in order. We want to accept tuples like# (12.0, 100.0, 52.0) i.e. that have "float" byte values. The# ColorSpec has a transform to adapt values like this to tuples# of integers, but Property validation happens before the# transform step, so values like that will fail Color validation# at this point, since Color is very strict about only accepting# tuples of (integer) bytes. This conditions tuple values to only# have integer RGB componentsifself.is_color_tuple_shape(value):value=tuple(int(v)ifi<3elsevfori,vinenumerate(value))returnsuper().prepare_value(cls,name,value)
#-----------------------------------------------------------------------------# Dev API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Private API#-----------------------------------------------------------------------------#-----------------------------------------------------------------------------# Code#-----------------------------------------------------------------------------