Source code for highcharts_core.utility_classes.clusters

from typing import Optional, List
from decimal import Decimal

from validator_collection import validators

from highcharts_core.decorators import class_sensitive
from highcharts_core.metaclasses import HighchartsMeta
from highcharts_core.utility_classes.animation import AnimationOptions
from highcharts_core.utility_classes.data_labels import DataLabel
from highcharts_core.utility_classes.events import ClusterEvents
from highcharts_core.utility_classes.markers import Marker
from highcharts_core.utility_classes.states import States
from highcharts_core.utility_classes.zones import ClusterZone


[docs]class VectorLayoutAlgorithm(HighchartsMeta): """Options for the layout algorithm to apply to the Vector chart.""" def __init__(self, **kwargs): self._distance = None self._grid_size = None self._iterations = None self._kmeans_threshold = None self._type = None self.distance = kwargs.get('distance', None) self.grid_size = kwargs.get('grid_size', None) self.iterations = kwargs.get('iterations', None) self.kmeans_threshold = kwargs.get('kmeans_threshold', None) self.type = kwargs.get('type', None) @property def distance(self) -> Optional[str | int | float | Decimal]: """When :meth:`VectorLayoutAlgorithm.type` is set to ``'kmeans'``, ``distance`` is a maximum distance between point and cluster center so that this point will be inside the cluster. Defaults to ``40``. The distance is either a number expressed in pixels or a percentage defining a percentage of the plot area width. :rtype: :class:`str <python:str>`, numeric, or :obj:`None <python:None>` """ return self._distance @distance.setter def distance(self, value): if value is None: self._distance = None else: try: value = validators.string(value) if '%' not in value: raise ValueError except ValueError: value = validators.numeric(value, minimum = 0) self._distance = value @property def grid_size(self) -> Optional[str | int | float | Decimal]: """When :meth:`VectorLayoutAlgorithm.type` is set to ``'grid'``, ``grid_size`` is is a size of a grid square element. Defaults to ``50``. The ``grid_size`` is either a number expressed in pixels or a percentage defining a percentage of the plot area width. :rtype: :class:`str <python:str>`, numeric, or :obj:`None <python:None>` """ return self._grid_size @grid_size.setter def grid_size(self, value): if value is None: self._grid_size = None else: try: value = validators.string(value) if '%' not in value: raise ValueError except (TypeError, ValueError): value = validators.numeric(value, minimum = 0) self._grid_size = value @property def iterations(self) -> Optional[int]: """When :meth:`VectorLayoutAlgorithm.type` is set to ``'kmeans'``, the ``iterations`` indicates the number of times that the algorithm will be repeated to find cluster positions. Defaults to :obj:`None <python:None>`. :rtype: :class:`int <python:int>` or :obj:`None <python:None>` """ return self._iterations @iterations.setter def iterations(self, value): self._iterations = validators.integer(value, allow_empty = True, minimum = 1) @property def kmeans_threshold(self) -> Optional[int]: """When :meth:`VectorLayoutAlgorithm.type` is set to :obj:`None <python:None>` and the number of visible points is less than or equal to ``kmeans_threshold``, the ``'kmeans'`` algorithm is used by default to find clusters. When the number of visible points exceeds the ``kmeans_threshold``, the ``'grid'`` algorithm is used. Defaults to ``100``. .. hint:: This threshold is a powerful tool to ensure good performance on large datasets and better cluster arrangemnet after zoom. :rtype: :class:`int <python:int>` or :obj:`None <python:None>` """ return self._kmeans_threshold @kmeans_threshold.setter def kmeans_threshold(self, value): self._kmeans_threshold = validators.integer(value, allow_empty = True, minimum = 0) @property def type(self) -> Optional[str]: """Type of algorithm to use to combine points into a cluster. Defaults to :obj:`None <python:None>`. There are three available algorithms: * ``'grid'`` - grid-based clustering technique. Points are assigned to squares of set size depending on their position on the plot area. Points inside the grid square are combined into a cluster. The grid size can be controlled by :meth:`VectorLayoutAlgorithm.grid_size` (grid size changes at certain zoom levels). * ``'kmeans'`` - based on K-Means clustering technique. In the first step, points are divided using the grid method (applying :meth:`VectorLayoutAlgorithm.distance` as the grid size) to find the initial amount of clusters. Next, each point is classified by computing the distance between each cluster center and that point. When the closest cluster distance is lower than :meth:`VectorLayoutAlgorithm.distance`, the point is added to this cluster otherwise is classified as noise. The algorithm is repeated until each cluster center does not change its previous position more than one pixel. This technique is more accurate but also more time consuming than the ``'grid'`` algorithm, especially for big datasets. * ``'optimizedKmeans'`` - based on K-Means clustering technique. This algorithm uses k-means algorithm only on the chart initialization or when chart extremes have greater range than on initialization. When a chart is redrawn the algorithm checks only clustered points distance from the cluster center and rebuilds it when the point is spaced enough to be outside the cluster. It provides a performance improvement and more stable clusters position so that it can be used rather on small and sparse datasets. When :obj:`None <python:None>`, the algorithm applied depends on :meth:`VectorLayoutAlgorithm.kmeans_threshold` such that if there are more visible points than the :meth:`kmeans_threshold <VectorLayoutAlgorithm.kmeans_threshold>` the ``'grid'`` algorithm is applied. If fewer, then ``'kmeans'`` is applied. A custom clustering algorithm can be applied by setting ``type`` to a JavaScript callback function. This function takes an array of ``processedXData``, ``processedYData``, ``processedXData`` indexes, and :class:`VectorLayoutAlgorithm` options as arguments and should return an object with grouped data. A custom algorithm should return an object similar to: .. code-block:: javascript { clusterId1: [{ x: 573, y: 285, index: 1 // point index in the data array }, { x: 521, y: 197, index: 2 }], clusterId2: [{ ... }] ... } Where ``clusterId*`` (in the example above - a unique id of a cluster or noise) is an array of points belonging to a cluster. If the array has only one point or fewer points than set in :class:`Cluster.minimum_cluster_size`, it won't be combined into a cluster. """ return self._type @type.setter def type(self, value): self._type = validators.string(value, allow_empty = True) @classmethod def _get_kwargs_from_dict(cls, as_dict): kwargs = { 'distance': as_dict.get('distance', None), 'grid_size': as_dict.get('gridSize', None), 'iterations': as_dict.get('iterations', None), 'kmeans_threshold': as_dict.get('kmeansThreshold', None), 'type': as_dict.get('type', None) } return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'distance': self.distance, 'gridSize': self.grid_size, 'iterations': self.iterations, 'kmeansThreshold': self.kmeans_threshold, 'type': self.type } return untrimmed
[docs]class ClusterOptions(HighchartsMeta): """Options for marker clusters, the concept of sampling the data values into larger blocks in order to ease readability and increase performance of the JavaScript charts. .. warning:: The marker clusters module does not work with ``boost`` and ``draggable-points`` modules. .. note:: The marker clusters feature requires the ``marker-clusters.js`` file to be loaded, found in the modules directory of the download package, or online at `code.highcharts.com/modules/marker-clusters.js <code.highcharts.com/modules/marker-clusters.js>`_. """ def __init__(self, **kwargs): self._allow_overlap = None self._animation = None self._data_labels = None self._drill_to_cluster = None self._enabled = None self._events = None self._layout_algorithm = None self._marker = None self._minimum_cluster_size = None self._states = None self._zones = None self.allow_overlap = kwargs.get('allow_overlap', None) self.animation = kwargs.get('animation', None) self.data_labels = kwargs.get('data_labels', None) self.drill_to_cluster = kwargs.get('drill_to_cluster', None) self.enabled = kwargs.get('enabled', None) self.events = kwargs.get('events', None) self.layout_algorithm = kwargs.get('layout_algorithm', None) self.marker = kwargs.get('marker', None) self.minimum_cluster_size = kwargs.get('minimum_cluster_size', None) self.states = kwargs.get('states', None) self.zones = kwargs.get('zones', None) @property def allow_overlap(self) -> Optional[bool]: """If ``True``, clusters are allowed to overlap. Otherwise, overlapping is prevented. Defaults to ``True``. .. warning:: Preventing overlapping only works if :meth:`layout_algorithm.type <VectorLayoutAlgorithm.type>` is set to ``'grid'``. :returns: Flag indicating whether to allow clusters to overlap. :rtype: :class:`bool <python:bool>` or :obj:`None <python:None>` """ return self._allow_overlap @allow_overlap.setter def allow_overlap(self, value): if value is None: self._allow_overlap = None else: self._allow_overlap = bool(value) @property def animation(self) -> Optional[AnimationOptions]: """Options for the cluster marker animation. :rtype: :class:`AnimationOptions` or :obj:`None <python:None>` """ return self._animation @animation.setter @class_sensitive(AnimationOptions) def animation(self, value): self._animation = value @property def data_labels(self) -> Optional[DataLabel]: """Options for the cluster data labels. :rtype: :class:`DataLabel` or :obj:`None <python:None>` """ return self._data_labels @data_labels.setter @class_sensitive(DataLabel) def data_labels(self, value): self._data_labels = value @property def drill_to_cluster(self) -> Optional[bool]: """If ``True``, zoom the plot area to the cluster points range when a cluster is clicked. Defaults to ``True``. :rtype: :class:`bool <python:bool>` or :obj:`None <python:None>` """ return self._drill_to_cluster @drill_to_cluster.setter def drill_to_cluster(self, value): if value is None: self._drill_to_cluster = None else: self._drill_to_cluster = bool(value) @property def enabled(self) -> Optional[bool]: """If ``True``, enables the marker-clusters module. Defaults to ``False``. :rtype: :class:`bool <python:bool>` or :obj:`None <python:None>` """ return self._enabled @enabled.setter def enabled(self, value): if value is None: self._enabled = None else: self._enabled = bool(value) @property def events(self) -> Optional[ClusterEvents]: """General event handlers for marker clusters. :rtype: :class:`SeriesEvents` or :obj:`None <python:None>` """ return self._events @events.setter @class_sensitive(ClusterEvents) def events(self, value): self._events = value @property def layout_algorithm(self) -> Optional[VectorLayoutAlgorithm]: """Options for the layout algorithm to apply to the Vector chart. :rtype: :class:`VectorLayoutAlgorithm` or :obj:`None <python:None>` """ return self._layout_algorithm @layout_algorithm.setter @class_sensitive(VectorLayoutAlgorithm) def layout_algorithm(self, value): self._layout_algorithm = value @property def marker(self) -> Optional[Marker]: """Options for the point markers of line-like series. Properties like ``fill_color``, ``line_color`` and ``line_width`` define the visual appearance of the markers. Other series types, like column series, don't have markers, but have visual options on the series level instead. :rtype: :class:`Marker` or :obj:`None <python:None>` """ return self._marker @marker.setter @class_sensitive(Marker) def marker(self, value): self._marker = value @property def minimum_cluster_size(self) -> Optional[int]: """The minimum number of points to be combined into a cluster. Defaults to ``2``. .. note:: This value has to be greater or equal to ``2``. :rtype: :class:`int <python:int>` or :obj:`None <python:None>` """ return self._minimum_cluster_size @minimum_cluster_size.setter def minimum_cluster_size(self, value): self._minimum_cluster_size = validators.integer(value, allow_empty = True, minimum = 2) @property def states(self) -> Optional[States]: """Configuration for state-specific configuration to apply to all clusters. :rtype: :class:`States` or :obj:`None <python:None>` """ return self._states @states.setter @class_sensitive(States) def states(self, value): self._states = value @property def zones(self) -> Optional[List[ClusterZone]]: """An array defining zones within marker clusters. :rtype: :obj:`None <python:None>` or :class:`list <python:list>` of :class:`ClusterZone` instances """ return self._zones @zones.setter @class_sensitive(ClusterZone, force_iterable = True) def zones(self, value): self._zones = value @classmethod def _get_kwargs_from_dict(cls, as_dict): kwargs = { 'allow_overlap': as_dict.get('allowOverlap', None), 'animation': as_dict.get('animation', None), 'data_labels': as_dict.get('dataLabels', None), 'drill_to_cluster': as_dict.get('drillToCluster', None), 'enabled': as_dict.get('enabled', None), 'events': as_dict.get('events', None), 'layout_algorithm': as_dict.get('layoutAlgorithm', None), 'marker': as_dict.get('marker', None), 'minimum_cluster_size': as_dict.get('minimumClusterSize', None), 'states': as_dict.get('states', None), 'zones': as_dict.get('zones', None) } return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'allowOverlap': self.allow_overlap, 'animation': self.animation, 'dataLabels': self.data_labels, 'drillToCluster': self.drill_to_cluster, 'enabled': self.enabled, 'events': self.events, 'layoutAlgorithm': self.layout_algorithm, 'marker': self.marker, 'minimumClusterSize': self.minimum_cluster_size, 'states': self.states, 'zones': self.zones } return untrimmed