Source code for highcharts_maps.options.series.data.geometric

from typing import Optional, List
from decimal import Decimal

from validator_collection import validators, checkers

from highcharts_maps import constants, errors, utility_functions
from highcharts_maps.decorators import class_sensitive, validate_types
from highcharts_maps.options.series.data.base import DataCore
from highcharts_maps.utility_classes.data_labels import DataLabel
from highcharts_maps.utility_classes.geojson import Feature


[docs]class GeometricDataBase(DataCore): """Base class for representing geometric data on map charts.""" def __init__(self, **kwargs): self._data_labels = None self._drilldown = None self._geometry = None self._properties = None self.data_labels = kwargs.get('data_labels', None) self.drilldown = kwargs.get('drilldown', None) self.geometry = kwargs.get('geometry', None) self.properties = kwargs.get('properties', None) super().__init__(**kwargs) @property def data_labels(self) -> Optional[DataLabel | List[DataLabel]]: """Individual data label for the data point. .. note:: To have multiple data labels per data point, you can also supply a collection of :class:`DataLabel` configuration settings. :rtype: :class:`DataLabel`, :class:`list <python:list>` of :class:`DataLabel`, or :obj:`None <python:None>` """ return self._data_labels @data_labels.setter def data_labels(self, value): if not value: self._data_labels = None else: if checkers.is_iterable(value): self._data_labels = validate_types(value, types = DataLabel, allow_none = False, force_iterable = True) else: self._data_labels = validate_types(value, types = DataLabel, allow_none = False) @property def drilldown(self) -> Optional[str]: """The :meth:`id <SeriesBase.id>` of a series in the ``drilldown.series`` array to use as a drilldown destination for this point. Defaults to :obj:`None <python:None>`. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return self._drilldown @drilldown.setter def drilldown(self, value): self._drilldown = validators.string(value, allow_empty = True) @property def geometry(self) -> Optional[Feature]: """The :term:`geometry <map geometry>` associated with a data point, expressed as a :term:`GeoJSON` :class:`Feature <highcharts_maps.utility_classes.geojson.Feature>`. Defaults to :obj:`None <python:None>`. .. tip:: **Best practice!** To make your code easier to maintain through better separation between your visualization's structure (e.g. the rendered map) and the data visualized within that structure, it is recommended to leave ``.geometry`` empty and to use the series' :meth:`.map_data <highcharts_maps.options.series.base.MapSeriesBase.map_data>` property to define the map's geometry. :rtype: :class:`Feature <highcharts_maps.utility_classes.geojson.Feature>` """ return self._geometry @geometry.setter @class_sensitive(Feature) def geometry(self, value): self._geometry = value @property def properties(self) -> Optional[dict]: """Collection of properties associated with the geometric data point. :rtype: :class:`dict <python:dict>` or :obj:`None <python:None>` """ return self._properties @properties.setter def properties(self, value): self._properties = validators.dict(value, allow_empty = True) @classmethod def _get_kwargs_from_dict(cls, as_dict): """Convenience method which returns the keyword arguments used to initialize the class from a Highcharts Javascript-compatible :class:`dict <python:dict>` object. :param as_dict: The HighCharts JS compatible :class:`dict <python:dict>` representation of the object. :type as_dict: :class:`dict <python:dict>` :returns: The keyword arguments that would be used to initialize an instance. :rtype: :class:`dict <python:dict>` """ kwargs = { 'accessibility': as_dict.get('accessibility', None), 'class_name': as_dict.get('className', None), 'color': as_dict.get('color', None), 'color_index': as_dict.get('colorIndex', None), 'custom': as_dict.get('custom', None), 'description': as_dict.get('description', None), 'events': as_dict.get('events', None), 'id': as_dict.get('id', None), 'label_rank': as_dict.get('labelRank', None) or as_dict.get('labelrank', None), 'name': as_dict.get('name', None), 'selected': as_dict.get('selected', None), 'data_labels': as_dict.get('dataLabels', None), 'drag_drop': as_dict.get('dragDrop', None), 'drilldown': as_dict.get('drilldown', None), 'geometry': as_dict.get('geometry', None), } properties = {} if len(as_dict) > len(kwargs): for key in as_dict: if key not in kwargs: snake_key = utility_functions.to_snake_case(key) if snake_key not in kwargs: properties[snake_key] = as_dict[key] kwargs['properties'] = properties return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'dataLabels': self.data_labels, 'drilldown': self.drilldown, 'geometry': self.geometry, } if self.properties: for key in self.properties: untrimmed[key] = self.properties[key] parent_as_dict = super()._to_untrimmed_dict(in_cls = in_cls) for key in parent_as_dict: untrimmed[key] = parent_as_dict[key] return untrimmed
[docs]class GeometricData(GeometricDataBase): """Data point that can be represented on a map visualization.""" def __init__(self, **kwargs): self._middle_x = None self._middle_y = None self._path = None self._value = None self.middle_x = kwargs.get('middle_x', None) self.middle_y = kwargs.get('middle_y', None) self.path = kwargs.get('path', None) self.value = kwargs.get('value', None) super().__init__(**kwargs) @property def middle_x(self) -> Optional[int | float | Decimal]: """The horizontal mid-point of the map area corresponding to the data point (used to place the data label), expressed as a numerical value between ``0`` and ``1``. Defaults to ``0.5``. :rtype: numeric or :obj:`None <python:None>` """ return self._middle_x @middle_x.setter def middle_x(self, value): self._middle_x = validators.numeric(value, allow_empty = True, minimum = 0, maximum = 1) @property def middle_y(self) -> Optional[int | float | Decimal]: """The vertical mid-point of the map area corresponding to the data point (used to place the data label), expressed as a numerical value between ``0`` and ``1``. Defaults to ``0.5``. :rtype: numeric or :obj:`None <python:None>` """ return self._middle_y @middle_y.setter def middle_y(self, value): self._middle_y = validators.numeric(value, allow_empty = True, minimum = 0, maximum = 1) @property def path(self) -> Optional[str]: """The SVG path of the shape associated with the data point. Defaults to :obj:`None <python:None>`. .. tip:: **Best practice!** To make your code easier to maintain through better separation between your visualization's structure (e.g. the rendered map) and the data visualized within that structure, it is recommended to leave ``.geometry`` empty and to use the series' :meth:`.map_data <highcharts_maps.options.series.base.MapSeriesBase.map_data>` property to define the map's geometry. .. caution:: For compatibily with old IE, not all SVG path definitions are supported, but M, L, and C operators are supported. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return self._path @path.setter def path(self, value): self._path = validators.string(value, allow_empty = True) @property def value(self) -> Optional[int | float | Decimal | constants.EnforcedNullType]: """The ``value`` of the data point. Defaults to :obj:`None <python:None>`. :rtype: numeric or :class:`EnforcedNullType` or :obj:`None <python:None>` """ return self._value @value.setter def value(self, value_): if value_ is None or isinstance(value_, constants.EnforcedNullType): self._value = None else: self._value = validators.numeric(value_) @classmethod def from_array(cls, value): if not value: return [] elif checkers.is_string(value): try: value = validators.json(value) except (ValueError, TypeError): pass elif not checkers.is_iterable(value): value = [value] collection = [] for item in value: if checkers.is_type(item, 'GeometricData'): as_obj = item elif checkers.is_dict(item): as_obj = cls.from_dict(item) elif item is None or isinstance(item, constants.EnforcedNullType) or \ checkers.is_numeric(item): as_obj = cls(value = item) elif checkers.is_iterable(item): if len(item) != 2: raise errors.HighchartsValueError(f'data expects either a 1D or 2D ' f'collection. Collection received ' f'had {len(item)} dimensions.') as_dict = { 'name': item[0], 'value': item[1] } as_obj = cls.from_dict(as_dict) else: raise errors.HighchartsValueError(f'each data point supplied must either ' f'be a Geometric Data Point or be ' f'coercable to one. Could not coerce: ' f'{item}') collection.append(as_obj) return collection @classmethod def _get_kwargs_from_dict(cls, as_dict): """Convenience method which returns the keyword arguments used to initialize the class from a Highcharts Javascript-compatible :class:`dict <python:dict>` object. :param as_dict: The HighCharts JS compatible :class:`dict <python:dict>` representation of the object. :type as_dict: :class:`dict <python:dict>` :returns: The keyword arguments that would be used to initialize an instance. :rtype: :class:`dict <python:dict>` """ kwargs = { 'accessibility': as_dict.get('accessibility', None), 'class_name': as_dict.get('className', None), 'color': as_dict.get('color', None), 'color_index': as_dict.get('colorIndex', None), 'custom': as_dict.get('custom', None), 'description': as_dict.get('description', None), 'events': as_dict.get('events', None), 'id': as_dict.get('id', None), 'label_rank': as_dict.get('labelRank', None) or as_dict.get('labelrank', None), 'name': as_dict.get('name', None), 'selected': as_dict.get('selected', None), 'data_labels': as_dict.get('dataLabels', None), 'drag_drop': as_dict.get('dragDrop', None), 'drilldown': as_dict.get('drilldown', None), 'geometry': as_dict.get('geometry', None), 'middle_x': as_dict.get('middleX', None), 'middle_y': as_dict.get('middleY', None), 'path': as_dict.get('path', None), 'value': as_dict.get('value', None), } properties = {} if len(as_dict) > len(kwargs): for key in as_dict: if key not in kwargs: snake_key = utility_functions.to_snake_case(key) if snake_key not in kwargs: properties[snake_key] = as_dict[key] kwargs['properties'] = properties return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'middleX': self.middle_x, 'middleY': self.middle_y, 'path': self.path, 'value': self.value, } parent_as_dict = super()._to_untrimmed_dict(in_cls = in_cls) for key in parent_as_dict: untrimmed[key] = parent_as_dict[key] return untrimmed
[docs]class GeometricZData(GeometricDataBase): """Data point that can be represented on a :class:`MapBubbleSeries <highcharts_maps.options.series.mapbubble.MapBubbleSeries>` featuring a ``z`` value.""" def __init__(self, **kwargs): self._z = None self.z = kwargs.get('z', None) super().__init__(**kwargs) @property def z(self) -> Optional[int | float | Decimal | constants.EnforcedNullType]: """The ``value`` of the data point. Defaults to :obj:`None <python:None>`. :rtype: numeric or :class:`EnforcedNullType` or :obj:`None <python:None>` """ return self._z @z.setter def z(self, value): if value is None or isinstance(value, constants.EnforcedNullType): self._z = None else: self._z = validators.numeric(value) @classmethod def from_array(cls, value): if not value: return [] elif checkers.is_string(value): try: value = validators.json(value) except (ValueError, TypeError): pass elif not checkers.is_iterable(value): value = [value] collection = [] for item in value: if checkers.is_type(item, 'GeometricZData'): as_obj = item elif checkers.is_dict(item): as_obj = cls.from_dict(item) elif item is None or isinstance(item, constants.EnforcedNullType) or \ checkers.is_numeric(item): as_obj = cls(z = item) elif checkers.is_iterable(item): if len(item) != 1: raise errors.HighchartsValueError(f'data expects a 1D ' f'collection. Collection received ' f'had {len(item)} dimensions.') as_dict = { 'z': item[0] } as_obj = cls.from_dict(as_dict) else: raise errors.HighchartsValueError(f'each data point supplied must either ' f'be a Geometric Z-Data Point or be ' f'coercable to one. Could not coerce: ' f'{item}') collection.append(as_obj) return collection @classmethod def _get_kwargs_from_dict(cls, as_dict): """Convenience method which returns the keyword arguments used to initialize the class from a Highcharts Javascript-compatible :class:`dict <python:dict>` object. :param as_dict: The HighCharts JS compatible :class:`dict <python:dict>` representation of the object. :type as_dict: :class:`dict <python:dict>` :returns: The keyword arguments that would be used to initialize an instance. :rtype: :class:`dict <python:dict>` """ kwargs = { 'accessibility': as_dict.get('accessibility', None), 'class_name': as_dict.get('className', None), 'color': as_dict.get('color', None), 'color_index': as_dict.get('colorIndex', None), 'custom': as_dict.get('custom', None), 'description': as_dict.get('description', None), 'events': as_dict.get('events', None), 'id': as_dict.get('id', None), 'label_rank': as_dict.get('labelRank', None) or as_dict.get('labelrank', None), 'name': as_dict.get('name', None), 'selected': as_dict.get('selected', None), 'data_labels': as_dict.get('dataLabels', None), 'drag_drop': as_dict.get('dragDrop', None), 'drilldown': as_dict.get('drilldown', None), 'geometry': as_dict.get('geometry', None), 'z': as_dict.get('z', None), } properties = {} if len(as_dict) > len(kwargs): for key in as_dict: if key not in kwargs: snake_key = utility_functions.to_snake_case(key) if snake_key not in kwargs: properties[snake_key] = as_dict[key] kwargs['properties'] = properties return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'z': self.z } parent_as_dict = super()._to_untrimmed_dict(in_cls = in_cls) for key in parent_as_dict: untrimmed[key] = parent_as_dict[key] return untrimmed
[docs]class GeometricLatLonData(GeometricDataBase): """Data point that can be represented on a :class:`MapPointSeries <highcharts_maps.options.series.mappoint.MapPointSeries>` featuring latitude/longitude coordinates, an x-value, and a y-value.""" def __init__(self, **kwargs): self._lat = None self._lon = None self._x = None self._y = None self.lat = kwargs.get('lat', None) self.lon = kwargs.get('lon', None) self.x = kwargs.get('x', None) self.y = kwargs.get('y', None) super().__init__(**kwargs) @property def lat(self) -> Optional[int | float | Decimal]: """The latitude of the data point. Defaults to :obj:`None <python:None>`. .. warning:: Must be combined with the :meth:`.lon <highcharts_maps.options.series.data.geometric.GeometricLatLonData.lon>` to work as expected. ..warning:: Overrides the :meth:`.x <highcharts_maps.options.series.data.geometric.GeometricLatLonData.x>` value if set. :rtype: numeric or :obj:`None <python:None>` """ return self._lat @lat.setter def lat(self, value): self._lat = validators.numeric(value, allow_empty = True) @property def lon(self) -> Optional[int | float | Decimal]: """The longitude of the data point. Defaults to :obj:`None <python:None>`. .. warning:: Must be combined with the :meth:`.lat <highcharts_maps.options.series.data.geometric.GeometricLatLonData.lat>` to work as expected. .. warning:: Overrides the :meth:`.y <highcharts_maps.options.series.data.geometric.GeometricLatLonData.y>` value if set. :rtype: numeric or :obj:`None <python:None>` """ return self._lon @lon.setter def lon(self, value): self._lon = validators.numeric(value, allow_empty = True) @property def x(self) -> Optional[int | float | Decimal]: """The x-coordinate of the data point, expressed in projected units. Defaults to :obj:`None <python:None>`. :rytpe: numeric or :obj:`None <python:None>` """ return self._x @x.setter def x(self, value): self._x = validators.numeric(value, allow_empty = True) @property def y(self) -> Optional[int | float | Decimal | constants.EnforcedNullType]: """The y-coordinate of the data point, expressed in projected units. Defaults to :obj:`None <python:None>`. :rtype: numeric or :class:`EnforcedNullType` or :obj:`None <python:None>` """ return self._y @y.setter def y(self, value): if value is None or isinstance(value, constants.EnforcedNullType): self._y = None else: self._y = validators.numeric(value) @classmethod def from_array(cls, value): if not value: return [] elif checkers.is_string(value): try: value = validators.json(value) except (ValueError, TypeError): pass elif not checkers.is_iterable(value): value = [value] collection = [] for item in value: if checkers.is_type(item, 'GeometricLatLonData'): as_obj = item elif checkers.is_dict(item): as_obj = cls.from_dict(item) elif item is None or isinstance(item, constants.EnforcedNullType) or \ checkers.is_numeric(item): as_obj = cls(y = item) elif checkers.is_iterable(item): if len(item) != 2: raise errors.HighchartsValueError(f'data expects either a 1D or 2D ' f'collection. Collection received ' f'had {len(item)} dimensions.') as_dict = { 'name': item[0], 'y': item[1] } as_obj = cls.from_dict(as_dict) else: raise errors.HighchartsValueError(f'each data point supplied must either ' f'be a Geometric Lat/Lon Data Point or ' f'be coercable to one. Could not coerce:' f' {item}') collection.append(as_obj) return collection @classmethod def _get_kwargs_from_dict(cls, as_dict): """Convenience method which returns the keyword arguments used to initialize the class from a Highcharts Javascript-compatible :class:`dict <python:dict>` object. :param as_dict: The HighCharts JS compatible :class:`dict <python:dict>` representation of the object. :type as_dict: :class:`dict <python:dict>` :returns: The keyword arguments that would be used to initialize an instance. :rtype: :class:`dict <python:dict>` """ kwargs = { 'accessibility': as_dict.get('accessibility', None), 'class_name': as_dict.get('className', None), 'color': as_dict.get('color', None), 'color_index': as_dict.get('colorIndex', None), 'custom': as_dict.get('custom', None), 'description': as_dict.get('description', None), 'events': as_dict.get('events', None), 'id': as_dict.get('id', None), 'label_rank': as_dict.get('labelRank', None) or as_dict.get('labelrank', None), 'name': as_dict.get('name', None), 'selected': as_dict.get('selected', None), 'data_labels': as_dict.get('dataLabels', None), 'drag_drop': as_dict.get('dragDrop', None), 'drilldown': as_dict.get('drilldown', None), 'geometry': as_dict.get('geometry', None), 'lat': as_dict.get('lat', None), 'lon': as_dict.get('lon', None), 'x': as_dict.get('x', None), 'y': as_dict.get('y', None), } properties = {} if len(as_dict) > len(kwargs): for key in as_dict: if key not in kwargs: snake_key = utility_functions.to_snake_case(key) if snake_key not in kwargs: properties[snake_key] = as_dict[key] kwargs['properties'] = properties return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'lat': self.lat, 'lon': self.lon, 'x': self.x, 'y': self.y, } parent_as_dict = super()._to_untrimmed_dict(in_cls = in_cls) for key in parent_as_dict: untrimmed[key] = parent_as_dict[key] return untrimmed