Source code for highcharts_core.utility_classes.nodes

import os
from typing import Optional, List
from decimal import Decimal

from validator_collection import validators

from highcharts_core import errors
from highcharts_core.decorators import class_sensitive
from highcharts_core.metaclasses import HighchartsMeta
from highcharts_core.utility_classes.gradients import Gradient
from highcharts_core.utility_classes.patterns import Pattern
from highcharts_core.utility_classes.data_labels import DataLabel, NodeDataLabel


[docs]class NodeOptions(HighchartsMeta): """Configuration of options for nodes in an Arc Diagram that are associated with a specific :class:`ArcDiagramSeries` by the :meth:`ArcDiagramSeries.id`.""" def __init__(self, **kwargs): self._color = None self._color_index = None self._data_labels = None self._height = None self._id = None self._name = None self._offset_horizontal = None self._offset_vertical = None self.color = kwargs.get('color', None) self.color_index = kwargs.get('color_index', None) self.data_labels = kwargs.get('data_labels', None) self.height = kwargs.get('height', None) self.id = kwargs.get('id', None) self.name = kwargs.get('name', None) self.offset_horizontal = kwargs.get('offset_horizontal', None) self.offset_vertical = kwargs.get('offset_vertical', None) @property def color(self) -> Optional[str | Gradient | Pattern]: """The color of the auto-generated node. Defaults to :obj:`None <python:None>`. :rtype: :obj:`None <python:None>`, :class:`Gradient`, :class:`Pattern`, or :class:`str <python:str>` """ return self._color @color.setter def color(self, value): from highcharts_core import utility_functions self._color = utility_functions.validate_color(value) @property def color_index(self) -> Optional[int]: """When operating in :term:`styled mode`, a specific color index to use for the auto-generated node. Defaults to :obj:`None <python:None>`. :rtype: :class:`int <python:int>` or :obj:`None <python:None>` """ return self._color_index @color_index.setter def color_index(self, value): self._color_index = validators.integer(value, allow_empty = True, minimum = 0) @property def data_labels(self) -> Optional[DataLabel]: """Options for the node's data label. :rtype: :class:`DataLabel`, :class:`list <python:list>` of :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 height(self) -> Optional[int | float | Decimal]: """The height of the node. :rtype: numeric or :obj:`None <python:None>` """ return self._height @height.setter def height(self, value): self._height = validators.numeric(value, allow_empty=True) @property def id(self) -> Optional[str]: """The id of the auto-generated node, refering to the ``from`` or ``to`` setting of the link. Defaults to :obj:`None <python:None>`. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return self._id @id.setter def id(self, value): self._id = validators.string(value, allow_empty = True) @property def name(self) -> Optional[str]: """The name to display for the node in data labels and tooltips. Defaults to :obj:`None <python:None>`. .. hint:: Use this when the name is different from the :meth:`NodeOptions.id`. Where the ``id`` must be unique for each node, this is not necessary for the name. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return self._name @name.setter def name(self, value): self._name = validators.string(value, allow_empty = True) @property def offset_horizontal(self) -> Optional[str | int | float | Decimal]: """The horizontal offset of a node, expressed in either pixels or as a percentage of the node size. Defaults to :obj:`None <python:None>`. .. hint:: Positive values shift the node right, negative shift it left. :rtype: numeric or :class:`str <python:str>` or :obj:`None <python:None>` """ return self._offset_horizontal @offset_horizontal.setter def offset_horizontal(self, value): if value is None: self._offset_horizontal = None else: if isinstance(value, str): value = value.lower() if '%' not in value: raise errors.HighchartsValueError(f'offset_horizontal must be either ' f'a numeric value, or a percentage ' f'string. "%" was not found in: ' f'{value}') else: value = validators.numeric(value) self._offset_horizontal = value @property def offset_vertical(self) -> Optional[str | int | float | Decimal]: """The vertical offset of a node, expressed in either pixels or as a percentage of the node size. Defaults to :obj:`None <python:None>`. .. hint:: Positive values shift the node down, negative shift it up. :rtype: numeric or :class:`str <python:str>` or :obj:`None <python:None>` """ return self._offset_vertical @offset_vertical.setter def offset_vertical(self, value): if value is None: self._offset_vertical = None else: if isinstance(value, str): value = value.lower() if '%' not in value: raise errors.HighchartsValueError(f'offset_vertical must be either ' f'a numeric value, or a percentage ' f'string. "%" was not found in: ' f'{value}') else: value = validators.numeric(value) self._offset_vertical = value @classmethod def _get_kwargs_from_dict(cls, as_dict): kwargs = { 'color': as_dict.get('color', None), 'color_index': as_dict.get('colorIndex', None), 'data_labels': as_dict.get('dataLabels', None), 'height': as_dict.get('height', None), 'id': as_dict.get('id', None), 'name': as_dict.get('name', None), 'offset_horizontal': as_dict.get('offsetHorizontal', None), 'offset_vertical': as_dict.get('offsetVertical', None), } return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'color': self.color, 'colorIndex': self.color_index, 'dataLabels': self.data_labels, 'height': self.height, 'id': self.id, 'name': self.name, 'offsetHorizontal': self.offset_horizontal, 'offsetVertical': self.offset_vertical, } return untrimmed
[docs]class DependencyWheelNodeOptions(NodeOptions): """Variant of :class:`NodeOptions` for use in :class:`DependencyWheelSeries`.""" def __init__(self, **kwargs): self._column = None self._level = None self.column = kwargs.get('column', None) self.level = kwargs.get('level', None) super().__init__(**kwargs) @property def column(self) -> Optional[int]: """An optional column index of where to place the node. Defaults to :obj:`None <python:None>`, which places it next to the preceding node. .. warning:: This option name is counter-intuitive in inverted charts, like for example an organization chart rendered top-down. In this case the "columns" are rendered horizontally, more like "rows". :rtype: :class:`int <python:int>` or :obj:`None <python:None>` """ return self._column @column.setter def column(self, value): self._column = validators.integer(value, allow_empty = True) @property def data_labels(self) -> Optional[NodeDataLabel]: """Options for the node's data label. :rtype: :class:`NodeDataLabel` or :obj:`None <python:None>` """ return self._data_labels @data_labels.setter @class_sensitive(NodeDataLabel) def data_labels(self, value): self._data_labels = value @property def level(self) -> Optional[int]: """An optional level index of where to place the node. Defaults to :obj:`None <python:None>`, which places it next to the preceding node. .. notes: Alias of :meth:`DependencyWheelNodeOptions.column`, but in inverted sankeys and org charts, the levels are laid out as rows. :rtype: :class:`int <python:int>` or :obj:`None <python:None>` """ return self._level @level.setter def level(self, value): self._level = validators.integer(value, allow_empty = True) @classmethod def _get_kwargs_from_dict(cls, as_dict): kwargs = { 'color': as_dict.get('color', None), 'color_index': as_dict.get('colorIndex', None), 'data_labels': as_dict.get('dataLabels', None), 'height': as_dict.get('height', None), 'id': as_dict.get('id', None), 'name': as_dict.get('name', None), 'offset_horizontal': as_dict.get('offsetHorizontal', None), 'offset_vertical': as_dict.get('offsetVertical', None), 'column': as_dict.get('column', None), 'level': as_dict.get('level', None), } return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'column': self.column, 'level': self.level, } parent_as_dict = super()._to_untrimmed_dict(in_cls = in_cls) or {} for key in parent_as_dict: untrimmed[key] = parent_as_dict[key] return untrimmed
[docs]class OrganizationNodeOptions(DependencyWheelNodeOptions): """Variant of :class:`NodeOptions` for use in :class:`OrganizationSeries`.""" def __init__(self, **kwargs): self._image = None self._layout = None self._title = None self.image = kwargs.get('image', None) self.layout = kwargs.get('layout', None) self.title = kwargs.get('title', None) super().__init__(**kwargs) @property def image(self) -> Optional[str]: """The URL of an image for the node card, which will be inserted by the default :meth:`DataLabel.node_formatter`. Defaults to :obj:`None <python:None>`. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return self._image @image.setter def image(self, value): if not value: self._image = None else: try: value = validators.url( value, allow_special_ips=os.getenv("HCP_ALLOW_SPECIAL_IPS", False) ) except ValueError: value = validators.path(value) self._image = value @property def layout(self) -> Optional[str]: """The layout for the node's children. Defaults to :obj:`None <python:None>`, which behaves as ``'normal'``. Accepts: * ``'normal'`` - renders children spaced evenly below the node * ``'hanging'`` - renders children condensed in a vertical hanging fashion below the node, allowing for tighter packing of nodes in the resulting diagram .. note:: Unless explicitly overridden, nodes whose parent use a ``'hanging'`` layout will *also* apply a ``'hanging'`` layout. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return self._layout @layout.setter def layout(self, value): if not value: self._layout = None else: value = validators.string(value) value = value.lower() if value not in ['normal', 'hanging']: raise errors.HighchartsValueError(f'layout expects either "normal" or ' f'"hanging". Received: {value}') self._layout = value @property def title(self) -> Optional[str]: """The job title for the node card. Defaults to :obj:`None <python:None>`. .. note:: Will be inserted by the default :meth:`DataLabel.node_formatter`. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return self._title @title.setter def title(self, value): self._title = validators.string(value, allow_empty = True) @classmethod def _get_kwargs_from_dict(cls, as_dict): kwargs = { 'color': as_dict.get('color', None), 'color_index': as_dict.get('colorIndex', None), 'data_labels': as_dict.get('dataLabels', None), 'height': as_dict.get('height', None), 'id': as_dict.get('id', None), 'name': as_dict.get('name', None), 'offset_horizontal': as_dict.get('offsetHorizontal', None), 'offset_vertical': as_dict.get('offsetVertical', None), 'column': as_dict.get('column', None), 'level': as_dict.get('level', None), 'image': as_dict.get('image', None), 'layout': as_dict.get('layout', None), 'title': as_dict.get('title', None), } return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'image': self.image, 'layout': self.layout, 'title': self.title, } parent_as_dict = super()._to_untrimmed_dict(in_cls = in_cls) or {} for key in parent_as_dict: untrimmed[key] = parent_as_dict[key] return untrimmed