Source code for highcharts_core.options.series.base

from typing import Optional, List
from decimal import Decimal
from collections import UserDict

try:
    import orjson as json
except ImportError:
    try:
        import rapidjson as json
    except ImportError:
        try:
            import simplejson as json
        except ImportError:
            import json
try:
    import numpy as np
    HAS_NUMPY = True
except ImportError:
    HAS_NUMPY = False

from validator_collection import validators, checkers

from highcharts_core import errors, utility_functions, constants
from highcharts_core.options.plot_options.series import SeriesOptions
from highcharts_core.options.series.data.base import DataBase
from highcharts_core.options.series.data.collections import DataPointCollection


[docs]class SeriesBase(SeriesOptions): """Generic base class for specific series configurations.""" def __init__(self, **kwargs): self._data = None self._id = None self._index = None self._legend_index = None self._name = None self._stack = None self._x_axis = None self._y_axis = None self._z_index = None self.data = kwargs.get('data', None) self.id = kwargs.get('id', None) self.index = kwargs.get('index', None) self.legend_index = kwargs.get('legend_index', None) self.name = kwargs.get('name', None) self.stack = kwargs.get('stack', None) self.x_axis = kwargs.get('x_axis', None) self.y_axis = kwargs.get('y_axis', None) self.z_index = kwargs.get('z_index', None) super().__init__(**kwargs) def __str__(self): """Return a human-readable :class:`str <python:str>` representation of the series. .. warning:: To ensure that the result is human-readable, the string representation will be generated *without* its :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property. .. tip:: If you would like a *complete* and *unambiguous* :class:`str <python:str>` representation, then you can: * use the :meth:`__repr__() <highcharts_core.options.series.base.SeriesBase.__repr__>` method, * call ``repr(my_series)``, or * serialize the series to JSON using ``my_series.to_json()``. :returns: A :class:`str <python:str>` representation of the chart. :rtype: :class:`str <python:str>` """ as_dict = self.to_dict() kwargs = {utility_functions.to_snake_case(key): as_dict[key] for key in as_dict if key != 'data'} kwargs_as_str = ', '.join([f'{key} = {repr(kwargs[key])}' for key in kwargs]) return f'{self.__class__.__name__}({kwargs_as_str})' def __getattr__(self, name): """Facilitates the retrieval of properties from the series and its underlying data. The logic is: 1. If the attribute exists on the series object, then return it. 2. If ``.data`` is empty, then return :obj:`None <python:None>`. 3. If ``.data`` contains a :class:`DataPointCollection <highcharts_core.options.series.data.collections.DataPointCollection>`, then return the attribute from the collection. 4. Since ``.data`` contains a list of data points, return an iterable containing the attribute from each data point. If NumPy is available, return this iterable as a NumPy :class:`ndarray <numpy:numpy.ndarray>`. :param name: The name of the attribute to retrieve. :type name: :class:`str <python:str>` :returns: The value of the attribute. :raises AttributeError: If ``name`` is not a valid attribute of the data point class or the instance. """ try: return super().__getattribute__(name) except AttributeError as error: if name in ['__iter__', '__next__', 'requires_js_object']: raise error pass if not self.data: raise AttributeError(name) if isinstance(self.data, DataPointCollection): return getattr(self.data, name) results = [getattr(x, name) for x in self.data] if HAS_NUMPY: results = np.asarray(results) return results def __setattr__(self, name, value): """Updates the series attribute, or descendent attributes on the ``.data`` properties. """ try: super().__setattr__(name, value) return except AttributeError: pass collection_cls = self._data_collection_class() data_point_cls = self._data_point_class() if not utility_functions.is_ndarray(self.data) and not self.data: if HAS_NUMPY: collection = collection_cls() setattr(collection, name, value) self.data = collection elif checkers.is_iterable(value, forbid_literals = (str, bytes, dict, UserDict)): collection = collection_cls() setattr(collection, name, value) self.data = collection else: data_point = data_point_cls(name = value) self._data = [data_point] elif not self.data: collection = collection_cls() setattr(collection, name, value) self.data = collection elif checkers.is_type(self.data, 'DataPointCollection'): setattr(self.data, name, value) else: if not checkers.is_iterable(value, forbid_literals = (str, bytes, dict, UserDict)): value = [value for x in self.data] if len(self.data) > len(value): value = value + [None for x in range(len(self.data) - len(value))] elif len(self.data) < len(value): self.data = self.data + [data_point_cls() for x in range(len(value) - len(self.data))] for index in range(len(self.data)): setattr(self.data[index], name, value[index]) @classmethod def _data_collection_class(cls): """Returns the class object used for the data collection. :rtype: :class:`DataPointCollection <highcharts_core.options.series.data.collections.DataPointCollection>` descendent """ return DataPointCollection @classmethod def _data_point_class(cls): """Returns the class object used for individual data points. :rtype: :class:`DataBase <highcharts_core.options.series.data.base.DataBase>` descendent """ return DataBase @property def _dot_path(self) -> Optional[str]: """The dot-notation path to the options key for the current class. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return f'series.{self.type}' @property def data(self) -> Optional[List[DataBase] | DataPointCollection]: """The collection of data points for the series. Defaults to :obj:`None <python:None>`. :rtype: :class:`DataBase` or :class:`DataPointCollection <highcharts_core.options.series.data.collections.DataPointCollection>` or :obj:`None <python:None>` """ return self._data @data.setter def data(self, value): if not utility_functions.is_ndarray(value) and not value: self._data = None else: self._data = self._data_point_class().from_array(value) @property def id(self) -> Optional[str]: """An id for the series. Defaults to :obj:`None <python:None>`. .. hint:: This can be used (in JavaScript) after render time to get a pointer to the series object through ``chart.get()``. :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 index(self) -> Optional[int]: """The index for the series in the chart, affecting the internal index in the (JavaScript) ``chart.series`` array, the visible Z-index, and the order of the series in the legend. Defaults to :obj:`None <python:None>`. :rtype: :class:`int <python:int>` or :obj:`None <python:None>` """ return self._index @index.setter def index(self, value): self._index = validators.integer(value, allow_empty = True, minimum = 0) @property def legend_index(self) -> Optional[int]: """The sequential index for the series in the legend. Defaults to :obj:`None <python:None>`. :rtype: :class:`int <python:int>` or :obj:`None <python:None>` """ return self._legend_index @legend_index.setter def legend_index(self, value): self._legend_index = validators.integer(value, allow_empty = True, minimum = 0) @property def name(self) -> Optional[str]: """The name of the series as shown in the legend, tooltip, etc. Defaults to :obj:`None <python:None>`. :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 stack(self) -> Optional[str]: """Indicates the "stack" into which the series should be grouped, if the chart groups series into stacks. Defaults to :obj:`None <python:None>`. .. note:: The value can be a string or a numeric value, provided that series in the same stack all have the same value when converted to a string. For ease of ues, Highcharts for Python will attempt to force the conversion of the relevant value to a string. :rtype: :class:`str <python:str>` or :obj:`None <python:None>` """ return self._stack @stack.setter def stack(self, value): if not value: self._stack = None else: self._stack = validators.string(value, coerce_value = True) @property def x_axis(self) -> Optional[str | int]: """When using multiple X-axes, this setting determines on which axis the series should be drawn. Its value should be either a numerical index position in the :meth:`Options.x_axis` array (starting at 0), or a :class:`str <python:str>` indicating the :meth:`id <XAxis.id>` of the axis to which the series should be connected. Defaults to :obj:`None <python:None>`, which behaves as if the value were set to ``0``. :rtype: :class:`str <python:str>`, :class:`int <python:int>`, or :obj:`None <python:None>` """ return self._x_axis @x_axis.setter def x_axis(self, value): if value is None: self._x_axis = None else: try: value = validators.integer(value, minimum = 0) except (ValueError, TypeError): value = validators.string(value) self._x_axis = value @property def y_axis(self) -> Optional[str | int]: """When using multiple Y-axes, this setting determines on which axis the series should be drawn. Its value should be either a numerical index position in the :meth:`Options.y_axis` array (starting at 0), or a :class:`str <python:str>` indicating the :meth:`id <YAxis.id>` of the axis to which the series should be connected. Defaults to :obj:`None <python:None>`, which behaves as if the value were set to ``0``. :rtype: :class:`str <python:str>`, :class:`int <python:int>`, or :obj:`None <python:None>` """ return self._y_axis @y_axis.setter def y_axis(self, value): if value is None: self._y_axis = None else: try: value = validators.integer(value, minimum = 0) except (ValueError, TypeError): value = validators.string(value) self._y_axis = value @property def z_index(self) -> Optional[int | float | Decimal]: """The visual z-index of the series. Defaults to :obj:`None <python:None>`. :rtype: numeric or :obj:`None <python:None>` """ return self._z_index @z_index.setter def z_index(self, value): if value is None: self._z_index = None else: self._z_index = validators.numeric(value) @classmethod def _get_kwargs_from_dict(cls, as_dict): kwargs = { 'accessibility': as_dict.get('accessibility', None), 'allow_point_select': as_dict.get('allowPointSelect', None), 'animation': as_dict.get('animation', None), 'class_name': as_dict.get('className', None), 'clip': as_dict.get('clip', None), 'color': as_dict.get('color', None), 'cursor': as_dict.get('cursor', None), 'custom': as_dict.get('custom', None), 'dash_style': as_dict.get('dashStyle', None), 'data_labels': as_dict.get('dataLabels', None), 'description': as_dict.get('description', None), 'enable_mouse_tracking': as_dict.get('enableMouseTracking', None), 'events': as_dict.get('events', None), 'include_in_data_export': as_dict.get('includeInDataExport', None), 'keys': as_dict.get('keys', None), 'label': as_dict.get('label', None), 'legend_symbol': as_dict.get('legendSymbol', None), 'linked_to': as_dict.get('linkedTo', None), 'marker': as_dict.get('marker', None), 'on_point': as_dict.get('onPoint', None), 'opacity': as_dict.get('opacity', None), 'point': as_dict.get('point', None), 'point_description_formatter': as_dict.get('pointDescriptionFormatter', None), 'selected': as_dict.get('selected', None), 'show_checkbox': as_dict.get('showCheckbox', None), 'show_in_legend': as_dict.get('showInLegend', None), 'skip_keyboard_navigation': as_dict.get('skipKeyboardNavigation', None), 'sonification': as_dict.get('sonification', None), 'states': as_dict.get('states', None), 'sticky_tracking': as_dict.get('stickyTracking', None), 'threshold': as_dict.get('threshold', None), 'tooltip': as_dict.get('tooltip', None), 'turbo_threshold': as_dict.get('turboThreshold', None), 'visible': as_dict.get('visible', None), 'animation_limit': as_dict.get('animationLimit', None), 'boost_blending': as_dict.get('boostBlending', None), 'boost_threshold': as_dict.get('boostThreshold', None), 'color_axis': as_dict.get('colorAxis', None), 'color_index': as_dict.get('colorIndex', None), 'color_key': as_dict.get('colorKey', None), 'connect_ends': as_dict.get('connectEnds', None), 'connect_nulls': as_dict.get('connectNulls', None), 'crisp': as_dict.get('crisp', None), 'crop_threshold': as_dict.get('cropThreshold', None), 'data_sorting': as_dict.get('dataSorting', None), 'drag_drop': as_dict.get('dragDrop', None), 'find_nearest_point_by': as_dict.get('findNearestPointBy', None), 'get_extremes_from_all': as_dict.get('getExtremesFromAll', None), 'inactive_other_points': as_dict.get('inactiveOtherPoints', None), 'linecap': as_dict.get('linecap', None), 'line_width': as_dict.get('lineWidth', None), 'negative_color': as_dict.get('negativeColor', None), 'point_description_format': as_dict.get('pointDescriptionFormat', None), 'point_interval': as_dict.get('pointInterval', None), 'point_interval_unit': as_dict.get('pointIntervalUnit', None), 'point_placement': as_dict.get('pointPlacement', None), 'point_start': as_dict.get('pointStart', None), 'relative_x_value': as_dict.get('relativeXValue', None), 'shadow': as_dict.get('shadow', None), 'soft_threshold': as_dict.get('softThreshold', None), 'stacking': as_dict.get('stacking', None), 'step': as_dict.get('step', None), 'zone_axis': as_dict.get('zoneAxis', None), 'zones': as_dict.get('zones', None), 'data': as_dict.get('data', None), 'id': as_dict.get('id', None), 'index': as_dict.get('index', None), 'legend_index': as_dict.get('legendIndex', None), 'name': as_dict.get('name', None), 'stack': as_dict.get('stack', None), 'x_axis': as_dict.get('xAxis', None), 'y_axis': as_dict.get('yAxis', None), 'z_index': as_dict.get('zIndex', None), } return kwargs def _to_untrimmed_dict(self, in_cls = None) -> dict: untrimmed = { 'data': self.data, 'id': self.id, 'index': self.index, 'legendIndex': self.legend_index, 'name': self.name, 'stack': self.stack, 'xAxis': self.x_axis, 'yAxis': self.y_axis, 'zIndex': self.z_index, } 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] def load_from_array(self, value): """Update the :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property with data loaded from an iterable in ``value``. :param value: The value that should contain the data which will be converted into data point instances. .. note:: If ``value`` is not an iterable, it will be converted into an iterable to be further de-serialized correctly. :type value: iterable """ data_point_cls = self._data_point_class() self.data = data_point_cls.from_array(value)
[docs] @classmethod def from_array(cls, value, series_kwargs = None): """Create one instance of the series with ``data`` populated from ``value``. :param value: The value that should contain the data which will be converted into data point instances. .. note:: If ``value`` is not an iterable, it will be converted into an iterable to be further de-serialized correctly. :type value: iterable :param series_kwargs: Optional keyword arguments to apply when instanting the series. Defaults to :obj:`None <python:None>`. :type series_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>` :returns: An instance of the series type with ``data`` populated from the value. :rtype: :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>` descendent """ series_kwargs = validators.dict(series_kwargs, allow_empty = True) or {} data_point_cls = cls._data_point_class() data_points = data_point_cls.from_array(value) series_kwargs['data'] = data_points series = cls(**series_kwargs) return series
[docs] def load_from_csv(self, as_string_or_file, property_column_map = None, has_header_row = True, delimiter = ',', null_text = 'None', wrapper_character = "'", line_terminator = '\r\n', wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\", series_in_rows = False, series_index = True, **kwargs): """Replace the existing :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property with a new value populated from data in a CSV string or file. .. note:: For an example :class:`LineSeries <highcharts_core.options.series.area.LineSeries>`, the minimum code required would be: .. code-block:: python my_series = LineSeries() # EXAMPLE 1. Minimal code - will attempt to update the line series # taking x-values from the first column, and y-values from # the second column. If there are too many columns in the CSV, # will throw an error. my_series = my_series.from_csv('some-csv-file.csv') # EXAMPLE 2. More precise code - will attempt to update the line series # mapping columns in the CSV file to properties on the series # instance. my_series = my_series.from_csv('some-csv-file.csv', property_column_map = { 'x': 0, 'y': 3, 'id': 'id' }) # EXAMPLE 3. More precise code - will update the line series # using a specific series generated from the CSV file. my_series = my_series.from_csv('some-csv-file.csv', series_index = 2) As the example above shows, data is loaded into the ``my_series`` instance from the CSV file with a filename ``some-csv-file.csv``. As shown in EXAMPLE 1, unless otherwise specified, the :meth:`.x <CartesianData.x>` values for each data point will be taken from the first (index 0) column in the CSV file, while the :meth:`.y <CartesianData.y>` values will be taken from the second column. If the CSV has more than 2 columns, then this will throw an :exc:`HighchartsCSVDeserializationError` because the function is not certain which columns to use to update the series. If this happens, you can either: #. As shown in EXAMPLE 2, precisely specify which columns to use by providing a ``property_column_map`` argument. In EXAMPLE 2, the :meth:`.x <CartesianData.x>` values for each data point will be taken from the first (index 0) column in the CSV file. The :meth:`.y <CartesianData.y>` values will be taken from the fourth (index 3) column in the CSV file. And the :meth:`.id <CartesianData.id>` values will be taken from a column whose header row is labeled ``'id'`` (regardless of its index). #. Supply a ``series_index`` argument, which indicates which of the series generated from the CSV file should be used to update the instance. :param as_string_or_file: The CSV data to use to pouplate data. Accepts either the raw CSV data as a :class:`str <python:str>` or a path to a file in the runtime environment that contains the CSV data. .. tip:: Unwrapped empty column values are automatically interpreted as null (:obj:`None <python:None>`). :type as_string_or_file: :class:`str <python:str>` or Path-like :param property_column_map: An optional :class:`dict <python:dict>` used to indicate which data point property should be set to which CSV column. The keys in the :class:`dict <python:dict>` should correspond to properties in the data point class, while the value can either be a numerical index (starting with 0) or a :class:`str <python:str>` indicating the label for the CSV column. Defaults to :obj:`None <python:None>`. .. warning:: If the ``property_column_map`` uses :class:`str <python:str>` values, the CSV file *must* have a header row (this is expected, by default). If there is no header row and a :class:`str <python:str>` value is found, a :exc:`HighchartsCSVDeserializationError` will be raised. :type property_column_map: :class:`dict <python:dict>` or :obj:`None <python:None>` :param has_header_row: If ``True``, indicates that the first row of ``as_string_or_file`` contains column labels, rather than actual data. Defaults to ``True``. :type has_header_row: :class:`bool <python:bool>` :param delimiter: The delimiter used between columns. Defaults to ``,``. :type delimiter: :class:`str <python:str>` :param wrapper_character: The string used to wrap string values when wrapping is applied. Defaults to ``'``. :type wrapper_character: :class:`str <python:str>` :param null_text: The string used to indicate an empty value if empty values are wrapped. Defaults to `None`. :type null_text: :class:`str <python:str>` :param line_terminator: The string used to indicate the end of a line/record in the CSV data. Defaults to ``'\\r\\n'``. :type line_terminator: :class:`str <python:str>` :param line_terminator: The string used to indicate the end of a line/record in the CSV data. Defaults to ``'\\r\\n'``. .. note:: The Python :mod:`csv <python:csv>` currently ignores the ``line_terminator`` parameter and always applies ``'\\r\\n'``, by design. The Python docs say this may change in the future, so for future backwards compatibility we are including it here. :type line_terminator: :class:`str <python:str>` :param wrap_all_strings: If ``True``, indicates that the CSV file has all string data values wrapped in quotation marks. Defaults to ``False``. .. warning:: If set to ``True``, the :mod:`csv <python:csv>` module will try to coerce any value that is *not* wrapped in quotation marks to a :class:`float <python:float>`. This can cause unexpected behavior, and typically we recommend leaving this as ``False`` and then re-casting values after they have been parsed. :type wrap_all_strings: :class:`bool <python:bool>` :param double_wrapper_character_when_nested: If ``True``, quote character is doubled when appearing within a string value. If ``False``, the ``escape_character`` is used to prefix quotation marks. Defaults to ``False``. :type double_wrapper_character_when_nested: :class:`bool <python:bool>` :param escape_character: A one-character string that indicates the character used to escape quotation marks if they appear within a string value that is already wrapped in quotation marks. Defaults to ``\\`` (which is Python for ``'\'``, which is Python's native escape character). :type escape_character: :class:`str <python:str>` :param series_in_rows: if ``True``, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults to :obj:`False <python:False>`. :type series_in_rows: :class:`bool <python:bool>` :param series_index: if :obj:`None <python:None>`, will raise a :exc:`HighchartsCSVDeserializationError <highcharts_core.errors.HighchartsCSVDeserializationError>` if the CSV data contains more than one series and no ``property_column_map`` is provided. Otherwise, will update the instance with the series found in the CSV at the ``series_index`` value. Defaults to :obj:`None <python:None>`. :type series_index: :class:`int <python:int>` or :obj:`None <python:None>` :param **kwargs: Remaining keyword arguments will be attempted on the resulting :term:`series` instance and the data points it contains. :raises HighchartsCSVDeserializationError: if ``property_column_map`` references CSV columns by their label, but the CSV data does not contain a header row """ cls = self.__class__ new_instance = cls.from_csv( as_string_or_file, property_column_map = property_column_map, has_header_row = has_header_row, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = wrap_all_strings, double_wrapper_character_when_nested = double_wrapper_character_when_nested, escape_character = escape_character, series_in_rows = series_in_rows, series_index = series_index, **kwargs ) if series_index is None and isinstance(new_instance, list): raise errors.HighchartsCSVDeserializationError( f'Expected data for a single series, but got {len(new_instance)} when ' f'loading from CSV. Please either modify the structure of your CSV ' f'or provide more targeted instructions using the property_column_map ' f'argument.' ) elif isinstance(new_instance, list): new_instance = new_instance[series_index] self.data = new_instance.data
@classmethod def _from_csv_multi_map(cls, as_string_or_file, property_column_map = None, has_header_row = True, series_kwargs = None, delimiter = ',', null_text = 'None', wrapper_character = "'", line_terminator = '\r\n', wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\", series_in_rows = False, **kwargs): """Replace the existing :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property with a new value populated from data in a CSV string or file. .. note:: For an example :class:`LineSeries <highcharts_core.options.series.area.LineSeries>`, the minimum code required would be: .. code-block:: python my_series = LineSeries() my_series = my_series.from_csv('some-csv-file.csv', property_column_map = { 'x': 0, 'y': 3, 'id': 'id' }) As the example above shows, data is loaded into the ``my_series`` instance from the CSV file with a filename ``some-csv-file.csv``. The :meth:`x <CartesianData.x>` values for each data point will be taken from the first (index 0) column in the CSV file. The :meth:`y <CartesianData.y>` values will be taken from the fourth (index 3) column in the CSV file. And the :meth:`id <CartesianData.id>` values will be taken from a column whose header row is labeled ``'id'`` (regardless of its index). :param as_string_or_file: The CSV data to use to pouplate data. Accepts either the raw CSV data as a :class:`str <python:str>` or a path to a file in the runtime environment that contains the CSV data. .. tip:: Unwrapped empty column values are automatically interpreted as null (:obj:`None <python:None>`). :type as_string_or_file: :class:`str <python:str>` or Path-like :param property_column_map: A :class:`dict <python:dict>` used to indicate which data point property should be set to which CSV column. The keys in the :class:`dict <python:dict>` should correspond to properties in the data point class, while the value can either be a numerical index (starting with 0) or a :class:`str <python:str>` indicating the label for the CSV column. Defaults to :obj:`None <python:None>`. .. warning:: If the ``property_column_map`` uses :class:`str <python:str>` values, the CSV file *must* have a header row (this is expected, by default). If there is no header row and a :class:`str <python:str>` value is found, a :exc:`HighchartsCSVDeserializationError` will be raised. :type property_column_map: :class:`dict <python:dict>` or :obj:`None <python:None>` :param has_header_row: If ``True``, indicates that the first row of ``as_string_or_file`` contains column labels, rather than actual data. Defaults to ``True``. :type has_header_row: :class:`bool <python:bool>` :param series_kwargs: An optional :class:`dict <python:dict>` containing keyword arguments that should be used when instantiating the series instance. Defaults to :obj:`None <python:None>`. .. warning:: If ``series_kwargs`` contains a ``data`` key, its value will be *overwritten*. The ``data`` value will be created from the CSV file instead. :type series_kwargs: :class:`dict <python:dict>` :param delimiter: The delimiter used between columns. Defaults to ``,``. :type delimiter: :class:`str <python:str>` :param wrapper_character: The string used to wrap string values when wrapping is applied. Defaults to ``'``. :type wrapper_character: :class:`str <python:str>` :param null_text: The string used to indicate an empty value if empty values are wrapped. Defaults to `None`. :type null_text: :class:`str <python:str>` :param line_terminator: The string used to indicate the end of a line/record in the CSV data. Defaults to ``'\\r\\n'``. :type line_terminator: :class:`str <python:str>` :param line_terminator: The string used to indicate the end of a line/record in the CSV data. Defaults to ``'\\r\\n'``. .. note:: The Python :mod:`csv <python:csv>` currently ignores the ``line_terminator`` parameter and always applies ``'\\r\\n'``, by design. The Python docs say this may change in the future, so for future backwards compatibility we are including it here. :type line_terminator: :class:`str <python:str>` :param wrap_all_strings: If ``True``, indicates that the CSV file has all string data values wrapped in quotation marks. Defaults to ``False``. .. warning:: If set to ``True``, the :mod:`csv <python:csv>` module will try to coerce any value that is *not* wrapped in quotation marks to a :class:`float <python:float>`. This can cause unexpected behavior, and typically we recommend leaving this as ``False`` and then re-casting values after they have been parsed. :type wrap_all_strings: :class:`bool <python:bool>` :param double_wrapper_character_when_nested: If ``True``, quote character is doubled when appearing within a string value. If ``False``, the ``escape_character`` is used to prefix quotation marks. Defaults to ``False``. :type double_wrapper_character_when_nested: :class:`bool <python:bool>` :param escape_character: A one-character string that indicates the character used to escape quotation marks if they appear within a string value that is already wrapped in quotation marks. Defaults to ``\\`` (which is Python for ``'\'``, which is Python's native escape character). :type escape_character: :class:`str <python:str>` :param series_in_rows: if ``True``, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults to :obj:`False <python:False>`. :type series_in_rows: :class:`bool <python:bool>` :param **kwargs: Remaining keyword arguments will be attempted on the resulting :term:`series` instance and the data points it contains. :raises HighchartsCSVDeserializationError: if ``property_column_map`` references CSV columns by their label, but the CSV data does not contain a header row """ try: as_string_or_file = as_string_or_file.strip() except AttributeError: pass property_column_map = validators.dict(property_column_map, allow_empty = True) or {} cleaned_column_map = {} for key in property_column_map: map_value = property_column_map.get(key, None) if map_value is None: continue if not isinstance(map_value, int) and not has_header_row: raise errors.HighchartsCSVDeserializationError(f'The supplied CSV ' f'data does not have a' f'header row, but the ' f'property_column_map ' f'did not supply an ' f'index. Received: ' f'column name ' f'"{map_value}" ' f'instead.') cleaned_column_map[key] = map_value if not checkers.is_on_filesystem(as_string_or_file): as_str = as_string_or_file columns, csv_records = utility_functions.parse_csv( as_str, has_header_row = has_header_row, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\" ) else: with open(as_string_or_file, 'r', newline = '') as file_: columns, csv_records = utility_functions.parse_csv( file_, has_header_row = has_header_row, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\" ) fixed_values = {} iterable_values = {} number_of_series = 1 mismatched_series = {} names = [] for key in cleaned_column_map: map_value = cleaned_column_map[key] is_iterable = not isinstance(map_value, (str, bytes, dict, UserDict)) and \ hasattr(map_value, '__iter__') if is_iterable: for item in map_value: if item not in columns: raise errors.HighchartsCSVDeserializationError( f'property_column_map is looking for a column labeled ' f'"{item}", but no corresponding column was found.' ) implied_series = len(map_value) if number_of_series == 1 and implied_series > number_of_series: number_of_series = implied_series elif implied_series != number_of_series: mismatched_series[key] = implied_series iterable_values[key] = map_value if key == 'y': name_list = [x if isinstance(x, str) else columns[x] for x in map_value] names.extend(name_list) else: if isinstance(map_value, str) and map_value not in columns: raise errors.HighchartsCSVDeserializationError( f'property_column_map is looking for a column labeled ' f'"{map_value}", but no corresponding column was found.' ) elif map_value not in columns and checkers.is_integer( map_value, coerce_value = True ) and int(map_value) > len(columns): raise errors.HighchartsCSVDeserializationError( f'property_column_map is looking for a column at index ' f'{map_value}, but no corresponding column was found.' ) fixed_values[key] = map_value if key == 'y': if isinstance(map_value, str): names.append(map_value) else: names.append(columns[map_value]) if mismatched_series: raise errors.HighchartsCSVDeserializationError( f'Unable to create series from CSV. The property map implied ' f'multiple series were needed, but properties had mismatched ' f'number of values:\n{mismatched_series}' ) collections = [] for index in range(number_of_series): collection_cls = cls._data_collection_class() collection_instance = collection_cls() for key in iterable_values: iterable_value = iterable_values[key][index] prop_array = [x.get(iterable_value, None) for x in csv_records] for i, value in enumerate(prop_array): if value and isinstance(value, str) and ',' in value: test_value = value.replace(',', '') if checkers.is_numeric(test_value): value = test_value prop_array[i] = value setattr(collection_instance, key, prop_array) for key in fixed_values: fixed_value = fixed_values[key] prop_array = [x.get(fixed_value, None) for x in csv_records] for i, value in enumerate(prop_array): if value and isinstance(value, str) and ',' in value: test_value = value.replace(',', '') if checkers.is_numeric(test_value): value = test_value prop_array[i] = value setattr(collection_instance, key, prop_array) getattr(collection_instance, key, None) collections.append(collection_instance) series_list = [] for index in range(number_of_series): series_kwargs['data'] = collections[index] series_instance = cls(**series_kwargs) try: series_instance.name = names[index] except IndexError: pass for key in kwargs: if key not in series_kwargs and key not in cleaned_column_map: setattr(series_instance, key, kwargs[key]) series_list.append(series_instance) return series_list
[docs] @classmethod def from_csv(cls, as_string_or_file, property_column_map = None, has_header_row = True, series_kwargs = None, delimiter = ',', null_text = 'None', wrapper_character = "'", line_terminator = '\r\n', wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\", series_in_rows = False, series_index = None, **kwargs): """Create one or more new :term:`series` instances with :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` populated from data in a CSV string or file. .. note:: For an example :class:`LineSeries <highcharts_core.options.series.area.LineSeries>`, the minimum code required would be: .. code-block:: python # Create one or more LineSeries instances from the CSV file "some-csv-file.csv". # EXAMPLE 1. The minimum code to produce one series for each # column in the CSV file (excluding the first column): my_series = LineSeries.from_csv('some-csv-file.csv') # EXAMPLE 2. Produces ONE series with more precise configuration: my_series = LineSeries.from_csv('some-csv-file.csv', property_column_map = { 'x': 0, 'y': 3, 'id': 'id' }) # EXAMPLE 3. Produces THREE series instances with # more precise configuration: my_series = LineSeries.from_csv('some-csv-file.csv', property_column_map = { 'x': 0, 'y': [3, 5, 8], 'id': 'id' }) As the example above shows, data is loaded into the ``my_series`` instance from the CSV file with a filename ``some-csv-file.csv``. The :meth:`x <CartesianData.x>` values for each data point will be taken from the first (index 0) column in the CSV file. The :meth:`y <CartesianData.y>` values will be taken from the fourth (index 3) column in the CSV file. And the :meth:`id <CartesianData.id>` values will be taken from a column whose header row is labeled ``'id'`` (regardless of its index). :param as_string_or_file: The CSV data to use to pouplate data. Accepts either the raw CSV data as a :class:`str <python:str>` or a path to a file in the runtime environment that contains the CSV data. .. tip:: Unwrapped empty column values are automatically interpreted as null (:obj:`None <python:None>`). :type as_string_or_file: :class:`str <python:str>` or Path-like :param property_column_map: A :class:`dict <python:dict>` used to indicate which data point property should be set to which CSV column. The keys in the :class:`dict <python:dict>` should correspond to properties in the data point class, while the value can either be a numerical index (starting with 0) or a :class:`str <python:str>` indicating the label for the CSV column. Defaults to :obj:`None <python:None>`. .. note:: If any of the values in ``property_column_map`` contain an iterable, then one series will be produced for each item in the iterable. For example, the following: .. code-block:: python { 'x': 0, 'y': [3, 5, 8] } will return *three* series, each of which will have its :meth:`.x <CartesianData.x>` value populated from the first column (index 0), and whose :meth:`.y <CartesianData.y>` values will be populated from the fourth, sixth, and ninth columns (indices 3, 5, and 8), respectively. .. warning:: If the ``property_column_map`` uses :class:`str <python:str>` values, the CSV file *must* have a header row (this is expected, by default). If there is no header row and a :class:`str <python:str>` value is found, a :exc:`HighchartsCSVDeserializationError` will be raised. :type property_column_map: :class:`dict <python:dict>` or :obj:`None <python:None>` :param has_header_row: If ``True``, indicates that the first row of ``as_string_or_file`` contains column labels, rather than actual data. Defaults to ``True``. :type has_header_row: :class:`bool <python:bool>` :param series_kwargs: An optional :class:`dict <python:dict>` containing keyword arguments that should be used when instantiating the series instance. Defaults to :obj:`None <python:None>`. .. warning:: If ``series_kwargs`` contains a ``data`` key, its value will be *overwritten*. The ``data`` value will be created from the CSV file instead. :type series_kwargs: :class:`dict <python:dict>` :param delimiter: The delimiter used between columns. Defaults to ``,``. :type delimiter: :class:`str <python:str>` :param wrapper_character: The string used to wrap string values when wrapping is applied. Defaults to ``'``. :type wrapper_character: :class:`str <python:str>` :param null_text: The string used to indicate an empty value if empty values are wrapped. Defaults to `None`. :type null_text: :class:`str <python:str>` :param line_terminator: The string used to indicate the end of a line/record in the CSV data. Defaults to ``'\\r\\n'``. :type line_terminator: :class:`str <python:str>` :param line_terminator: The string used to indicate the end of a line/record in the CSV data. Defaults to ``'\\r\\n'``. .. note:: The Python :mod:`csv <python:csv>` currently ignores the ``line_terminator`` parameter and always applies ``'\\r\\n'``, by design. The Python docs say this may change in the future, so for future backwards compatibility we are including it here. :type line_terminator: :class:`str <python:str>` :param wrap_all_strings: If ``True``, indicates that the CSV file has all string data values wrapped in quotation marks. Defaults to ``False``. .. warning:: If set to ``True``, the :mod:`csv <python:csv>` module will try to coerce any value that is *not* wrapped in quotation marks to a :class:`float <python:float>`. This can cause unexpected behavior, and typically we recommend leaving this as ``False`` and then re-casting values after they have been parsed. :type wrap_all_strings: :class:`bool <python:bool>` :param double_wrapper_character_when_nested: If ``True``, quote character is doubled when appearing within a string value. If ``False``, the ``escape_character`` is used to prefix quotation marks. Defaults to ``False``. :type double_wrapper_character_when_nested: :class:`bool <python:bool>` :param escape_character: A one-character string that indicates the character used to escape quotation marks if they appear within a string value that is already wrapped in quotation marks. Defaults to ``\\\\`` (which is Python for ``'\\'``, which is Python's native escape character). :type escape_character: :class:`str <python:str>` :param series_in_rows: if ``True``, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults to ``False``. :obj:`False <python:False>`. :type series_in_rows: :class:`bool <python:bool>` :param series_index: If supplied, return the series that Highcharts for Python generated from the CSV at the ``series_index`` position. Defaults to :obj:`None <python:None>`, which returns all series generated from the CSV. :type series_index: :class:`int <python:int>`, slice, or :obj:`None <python:None>` :param **kwargs: Remaining keyword arguments will be attempted on the resulting :term:`series` instance and the data points it contains. :returns: A :term:`series` instance (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) OR :class:`list <python:list>` of series instances with its :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property populated from the data in ``df``. :rtype: :class:`list <python:list>` of series instances (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) or :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`-descendent :raises HighchartsCSVDeserializationError: if ``property_column_map`` references CSV columns by their label, but the CSV data does not contain a header row """ series_kwargs = validators.dict(series_kwargs, allow_empty = True) or {} if series_in_rows: return cls.from_csv_in_rows( as_string_or_file, has_header_row = has_header_row, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = wrap_all_strings, double_wrapper_character_when_nested = double_wrapper_character_when_nested, escape_character = escape_character, series_index = series_index, **kwargs ) # SCENARIO 1: Has Property Map if property_column_map: series_list = cls._from_csv_multi_map( as_string_or_file, property_column_map = property_column_map, has_header_row = has_header_row, series_kwargs = series_kwargs, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = wrap_all_strings, double_wrapper_character_when_nested = double_wrapper_character_when_nested, escape_character = escape_character, **kwargs ) if len(series_list) == 1: return series_list[0] return series_list # SCENARIO 2: Properties in KWARGS collection_cls = cls._data_collection_class() data_point_cls = cls._data_point_class() props_from_array = data_point_cls._get_props_from_array() if not props_from_array: props_from_array = ['x', 'y'] property_map = {} for prop in props_from_array: if prop in kwargs: property_map[prop] = kwargs[prop] if property_map: series_list = cls._from_csv_multi_map( as_string_or_file, property_column_map = property_map, has_header_row = has_header_row, series_kwargs = series_kwargs, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = wrap_all_strings, double_wrapper_character_when_nested = double_wrapper_character_when_nested, escape_character = escape_character, **kwargs ) for index in range(len(series_list)): for key in kwargs: if key not in props_from_array and key not in series_kwargs: setattr(series_list[index], key, kwargs[key]) if len(series_list) == 1: return series_list[0] if series_index is not None: return series_list[index] return series_list # SCENARIO 3: No Explicit Properties if not checkers.is_on_filesystem(as_string_or_file): as_str = as_string_or_file columns, csv_records = utility_functions.parse_csv( as_str, has_header_row = has_header_row, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\" ) else: with open(as_string_or_file, 'r', newline = '') as file_: columns, csv_records = utility_functions.parse_csv( file_, has_header_row = has_header_row, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\" ) try: series_idx = kwargs.get('index', columns[0]) except IndexError: series_idx = kwargs.get('index', 0) column_count = len(columns) if not columns: column_count = len(csv_records[0]) supported_dimensions = collection_cls._get_supported_dimensions() # SCENARIO 3a: Single Series, Data Frame Columns align exactly to Data Point Properties if column_count in supported_dimensions: property_map = {} props_from_array = data_point_cls._get_props_from_array(length = column_count) if not props_from_array: props_from_array = ['x', 'y'] property_map[props_from_array[0]] = [x.get(series_idx, None) for x in csv_records] for index, prop in enumerate(props_from_array[1:]): if series_idx is not None: prop_array = [x.get(columns[index + 1], index + 1) for x in csv_records] else: prop_array = [x.get(columns[index], index) for x in csv_records] for i, value in enumerate(prop_array): if value and isinstance(value, str) and ',' in value: test_value = value.replace(',', '') if checkers.is_numeric(test_value): value = test_value prop_array[i] = value property_map[prop] = prop_array collection = collection_cls() for key in property_map: setattr(collection, key, property_map[key]) series_kwargs['data'] = collection series_instance = cls(**series_kwargs) for key in kwargs: if key not in series_kwargs and key not in property_map: setattr(series_instance, key, kwargs[key]) return series_instance # SCENARIO 3b: Multiple Series, Data Frame Columns correspond to multiples of Data Point Properties reversed_dimensions = sorted(supported_dimensions, reverse = True) columns_per_series = None if reversed_dimensions: for dimension in reversed_dimensions: if series_idx is not None and dimension > 1 and column_count % (dimension - 1) == 0: if dimension > 2 and props_from_array[-1] == 'name': columns_per_series = dimension - 2 else: columns_per_series = dimension - 1 break if dimension > 1 and column_count % dimension == 0: columns_per_series = dimension break elif dimension == 1: columns_per_series = 1 if not columns_per_series: raise errors.HighchartsCSVDeserializationError( f'Could not determine how to deserialize CSV with {column_count}' f' columns into a {collection_cls.__name__} instance. Please supply ' f'more precise instructions using property_column_map or ' f'by explicitly specificying data property kwargs.' ) series_count = column_count // columns_per_series if columns_per_series == 1 and series_idx: series_count -= 1 series_list = [] for index in range(series_count): start = 1 + (len(series_list) * columns_per_series) property_map = {} if series_idx is not None: expected_length = columns_per_series + 1 else: expected_length = columns_per_series props_from_array = data_point_cls._get_props_from_array(length = expected_length) if not props_from_array: props_from_array = ['x', 'y'] property_map[props_from_array[0]] = [x.get(series_idx, None) for x in csv_records] has_implicit_series_name = 'name' not in kwargs and 'name' not in series_kwargs if has_implicit_series_name: try: series_name = columns[start] except (IndexError, TypeError): series_name = None else: series_name = series_kwargs.get('name', None) or kwargs.get('name', None) props_from_array = props_from_array[1:] for idx, prop in enumerate(props_from_array): index = start + idx prop_array = [x.get(columns[index], idx) for x in csv_records] property_map[prop] = prop_array collection = collection_cls() for key in property_map: try: setattr(collection, key, property_map[key]) except ValueError as error: if key not in ['x', 'name'] and 'name' not in property_map: setattr(collection, 'name', property_map[key]) else: raise error series_kwargs['data'] = collection series_instance = cls(**series_kwargs) for key in kwargs: if key not in series_kwargs and key not in property_map: setattr(series_instance, key, kwargs[key]) if 'name' not in series_kwargs and 'name' not in kwargs: series_instance.name = series_name series_list.append(series_instance) if series_index is not None: return series_list[series_index] return series_list
[docs] @classmethod def from_csv_in_rows(cls, as_string_or_file, has_header_row = True, series_kwargs = None, delimiter = ',', null_text = 'None', wrapper_character = "'", line_terminator = '\r\n', wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\", **kwargs): """Create a new :term:`series` instance with a :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property populated from data in a CSV string or file. .. note:: For an example :class:`LineSeries <highcharts_core.options.series.area.LineSeries>`, the minimum code required would be: .. code-block:: python my_series = LineSeries.from_csv_in_rows('some-csv-file.csv') :param as_string_or_file: The CSV data to use to pouplate data. Accepts either the raw CSV data as a :class:`str <python:str>` or a path to a file in the runtime environment that contains the CSV data. .. tip:: Unwrapped empty column values are automatically interpreted as null (:obj:`None <python:None>`). :type as_string_or_file: :class:`str <python:str>` or Path-like :param has_header_row: If ``True``, indicates that the first row of ``as_string_or_file`` contains column labels, rather than actual data. Defaults to ``True``. :type has_header_row: :class:`bool <python:bool>` :param series_kwargs: An optional :class:`dict <python:dict>` containing keyword arguments that should be used when instantiating the series instance. Defaults to :obj:`None <python:None>`. .. warning:: If ``series_kwargs`` contains a ``data`` key, its value will be *overwritten*. The ``data`` value will be created from the CSV file instead. :type series_kwargs: :class:`dict <python:dict>` :param delimiter: The delimiter used between columns. Defaults to ``,``. :type delimiter: :class:`str <python:str>` :param wrapper_character: The string used to wrap string values when wrapping is applied. Defaults to ``'``. :type wrapper_character: :class:`str <python:str>` :param null_text: The string used to indicate an empty value if empty values are wrapped. Defaults to `None`. :type null_text: :class:`str <python:str>` :param line_terminator: The string used to indicate the end of a line/record in the CSV data. Defaults to ``'\\r\\n'``. :type line_terminator: :class:`str <python:str>` :param line_terminator: The string used to indicate the end of a line/record in the CSV data. Defaults to ``'\\r\\n'``. .. note:: The Python :mod:`csv <python:csv>` currently ignores the ``line_terminator`` parameter and always applies ``'\\r\\n'``, by design. The Python docs say this may change in the future, so for future backwards compatibility we are including it here. :type line_terminator: :class:`str <python:str>` :param wrap_all_strings: If ``True``, indicates that the CSV file has all string data values wrapped in quotation marks. Defaults to ``False``. .. warning:: If set to ``True``, the :mod:`csv <python:csv>` module will try to coerce any value that is *not* wrapped in quotation marks to a :class:`float <python:float>`. This can cause unexpected behavior, and typically we recommend leaving this as ``False`` and then re-casting values after they have been parsed. :type wrap_all_strings: :class:`bool <python:bool>` :param double_wrapper_character_when_nested: If ``True``, quote character is doubled when appearing within a string value. If ``False``, the ``escape_character`` is used to prefix quotation marks. Defaults to ``False``. :type double_wrapper_character_when_nested: :class:`bool <python:bool>` :param escape_character: A one-character string that indicates the character used to escape quotation marks if they appear within a string value that is already wrapped in quotation marks. Defaults to ``\\\\`` (which is Python for ``'\\'``, which is Python's native escape character). :type escape_character: :class:`str <python:str>` :param **kwargs: Remaining keyword arguments will be attempted on the resulting :term:`series` instance and the data points it contains. :returns: A :term:`series` instance (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) OR :class:`list <python:list>` of series instances with its :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property populated from the data in ``df``. :rtype: :class:`list <python:list>` of series instances (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) or :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`-descendent :raises HighchartsCSVDeserializationError: if ``property_column_map`` references CSV columns by their label, but the CSV data does not contain a header row """ series_kwargs = validators.dict(series_kwargs, allow_empty = True) or {} if not checkers.is_on_filesystem(as_string_or_file): as_str = as_string_or_file columns, csv_records = utility_functions.parse_csv( as_str, has_header_row = has_header_row, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\" ) else: with open(as_string_or_file, 'r', newline = '') as file_: columns, csv_records = utility_functions.parse_csv( file_, has_header_row = has_header_row, delimiter = delimiter, null_text = null_text, wrapper_character = wrapper_character, line_terminator = line_terminator, wrap_all_strings = False, double_wrapper_character_when_nested = False, escape_character = "\\" ) collection_cls = cls._data_collection_class() supported_dimensions = collection_cls._get_supported_dimensions() if 2 not in supported_dimensions: raise errors.HighchartsPandasDeserializationError( f'Unable to create a collection of {cls.__name__} instances ' f'from CSV using a 2-dimensional array because {cls.__name__} does ' f'not support 2-dimensional arrays as inputs. Please use a ' f'different series type, or transpose the CSV to a columnar structure ' f'and supply a column_property_map for greater precision.' ) data_properties = collection_cls._get_props_from_array() if columns: x_values = columns[1:] else: x_values = [x for x in range(len(csv_records[0].keys()) - 1)] name_key = list(csv_records[0].keys())[0] name_values = [row[name_key] for row in csv_records] series_count = len(csv_records) series_list = [] for row in range(series_count): series_name = name_values[row] y_values = [x for x in list(csv_records[row].values())[1:]] for i, value in enumerate(y_values): if value and isinstance(value, str) and ',' in value: test_value = value.replace(',', '') if checkers.is_numeric(test_value): value = test_value y_values[i] = value as_array = zip(x_values, y_values) collection = collection_cls.from_array(as_array) series_instance_kwargs = series_kwargs.copy() series_instance_kwargs['data'] = collection series_instance_kwargs['name'] = series_name series_instance = cls(**series_instance_kwargs) for key in kwargs: if key not in series_instance_kwargs and key not in data_properties: setattr(series_instance, key, kwargs[key]) series_list.append(series_instance) return series_list
[docs] def load_from_pandas(self, df, property_map = None, series_in_rows = False, series_index = None): """Replace the contents of the :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property with data points populated from a `pandas <https://pandas.pydata.org/>`_ :class:`DataFrame <pandas:pandas.DataFrame>`. :param df: The :class:`DataFrame <pandas:pandas.DataFrame>` from which data should be loaded. :type df: :class:`DataFrame <pandas:pandas.DataFrame>` :param property_map: A :class:`dict <python:dict>` used to indicate which data point property should be set to which column in ``df``. The keys in the :class:`dict <python:dict>` should correspond to properties in the data point class, while the value should indicate the label for the :class:`DataFrame <pandas:pandas.DataFrame>` column. Defaults to :obj:`None <python:None>`. :type property_map: :class:`dict <python:dict>` or :obj:`None <python:None>` :param series_in_rows: if ``True``, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults to :obj:`False <python:False>`. :type series_in_rows: :class:`bool <python:bool>` :param series_index: If supplied, return the series that Highcharts for Python generated from ``df`` at the ``series_index`` value. Defaults to :obj:`None <python:None>`, which returns all series generated from ``df``. .. warning:: If :obj:`None <python:None>` and Highcharts for Python generates multiple series, then a :exc:`HighchartsPandasDeserializationError` will be raised. :type series_index: :class:`int <python:int>`, or :obj:`None <python:None>` :raises HighchartsPandasDeserializationError: if ``property_map`` references a column that does not exist in the data frame :raises HighchartsPandasDeserializationError: if ``series_index`` is :obj:`None <python:None>`, and it is ambiguous which series generated from the dataframe should be used :raises HighchartsDependencyError: if `pandas <https://pandas.pydata.org/>`_ is not available in the runtime environment """ cls = self.__class__ new_instance = cls.from_pandas(df, property_map = property_map, series_in_rows = series_in_rows) if series_index is None and isinstance(new_instance, list): raise errors.HighchartsPandasDeserializationError( f'Expected data for a single series, but got {len(new_instance)} when ' f'loading from df. Please either modify the structure of df ' f'or provide more targeted instructions using the property_map ' f'argument.' ) elif isinstance(new_instance, list): new_instance = new_instance[series_index] self.data = new_instance.data
@classmethod def _from_pandas_multi_map(cls, df, property_map, series_kwargs = None, **kwargs): """Create one or more :term:`series` instances whose :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` properties are populated from a `pandas <https://pandas.pydata.org/>`_ :class:`DataFrame <pandas:pandas.DataFrame>`, when ``property_map`` suggests there are multiple series. :param df: The :class:`DataFrame <pandas:pandas.DataFrame>` from which data should be loaded. :type df: :class:`DataFrame <pandas:pandas.DataFrame>` :param property_map: A :class:`dict <python:dict>` used to indicate which data point property should be set to which column in ``df``. The keys in the :class:`dict <python:dict>` should correspond to properties in the data point class, while the value should indicate the label for the :class:`DataFrame <pandas:pandas.DataFrame>` column. Defaults to :obj:`None <python:None>` :type property_map: :class:`dict <python:dict>` or :obj:`None <python:None>` :param series_kwargs: An optional :class:`dict <python:dict>` containing keyword arguments that should be used when instantiating the series instance. Defaults to :obj:`None <python:None>`. .. warning:: If ``series_kwargs`` contains a ``data`` key, its value will be *overwritten*. The ``data`` value will be created from ``df`` instead. :type series_kwargs: :class:`dict <python:dict>` :param **kwargs: Remaining keyword arguments will be attempted on the resulting :term:`series` instance and the data points it contains. :returns: A :term:`series` instance (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) OR :class:`list <python:list>` of series instances with its :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property populated from the data in ``df``. :rtype: :class:`list <python:list>` of series instances (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) or :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`-descendent :raises HighchartsPandasDeserializationError: if ``property_map`` references a column that does not exist in the data frame :raises HighchartsDependencyError: if `pandas <https://pandas.pydata.org/>`_ is not available in the runtime environment """ series_kwargs = validators.dict(series_kwargs, allow_empty = True) or {} fixed_values = {} iterable_values = {} number_of_series = 1 mismatched_series = {} names = [] for key in property_map: map_value = property_map[key] is_iterable = not isinstance(map_value, (str, bytes, dict, UserDict)) and \ hasattr(map_value, '__iter__') if is_iterable: for item in map_value: if item not in df.columns.values: raise errors.HighchartsPandasDeserializationError( f'Unable to find a column labeled "{item}" in df.' ) implied_series = len(map_value) if number_of_series == 1 and implied_series > number_of_series: number_of_series = implied_series elif implied_series != number_of_series: mismatched_series[key] = implied_series iterable_values[key] = map_value if key == 'y': names.extend(map_value) else: if map_value not in df.columns.values: if map_value != df.index.name: raise errors.HighchartsPandasDeserializationError( f'Unable to find a column labeled "{map_value}" in df.' ) fixed_values[key] = map_value if key == 'y': names.append(map_value) if mismatched_series: raise errors.HighchartsPandasDeserializationError( f'Unable to create series from df. The property map implied ' f'multiple series were needed, but properties had mismatched ' f'number of values:\n{mismatched_series}' ) collections = [] for index in range(number_of_series): collection_cls = cls._data_collection_class() collection_instance = collection_cls() for key in iterable_values: iterable_value = iterable_values[key][index] prop_array = df[iterable_value].values setattr(collection_instance, key, prop_array) for key in fixed_values: fixed_value = fixed_values[key] try: prop_array = df[fixed_value].values except KeyError: prop_array = df.index.values setattr(collection_instance, key, prop_array) collections.append(collection_instance) series_list = [] for index in range(number_of_series): series_kwargs['data'] = collections[index] series_instance = cls(**series_kwargs) try: series_instance.name = names[index] except IndexError: pass for key in kwargs: if key not in series_kwargs and property_map: setattr(series_instance, key, kwargs[key]) series_list.append(series_instance) return series_list
[docs] @classmethod def from_pandas_in_rows(cls, df, series_kwargs = None, series_index = None, **kwargs): """Create a collection of :term:`series` instances, one for each row in ``df``. :param df: The :class:`DataFrame <pyspark:pyspark.sql.DataFrame>` from which data should be loaded. :type df: :class:`DataFrame <pyspark:pyspark.sql.DataFrame>` :param series_kwargs: An optional :class:`dict <python:dict>` containing keyword arguments that should be used when instantiating the series instance. Defaults to :obj:`None <python:None>`. .. warning:: If ``series_kwargs`` contains a ``data`` key, its value will be *overwritten*. The ``data`` value will be created from ``df`` instead. :type series_kwargs: :class:`dict <python:dict>` :param series_index: If supplied, return the series that Highcharts for Python generated from ``df`` at the ``series_index`` value. Defaults to :obj:`None <python:None>`, which returns all series generated from ``df``. :type series_index: :class:`int <python:int>`, slice, or :obj:`None <python:None>` :param **kwargs: Remaining keyword arguments will be attempted on the resulting :term:`series` instance and the data points it contains. :returns: Collection of :term:`series` instances corresponding, with one series per row in ``df``, and where: * the series x-values are populated from the column labels in ``df`` * the series name is set to the row label from ``df`` * the series y-values are populated from the values within that row in ``df`` :rtype: :class:`list <python:list>` of :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`-descendent instances """ try: from pandas import DataFrame except ImportError: raise errors.HighchartsDependencyError('pandas is not available in the ' 'runtime environment. Please install ' 'using "pip install pandas"') if not checkers.is_type(df, ('DataFrame')): raise errors.HighchartsValueError(f'df is expected to be a Pandas DataFrame.' f'Was: {df.__class__.__name__}') series_kwargs = validators.dict(series_kwargs, allow_empty = True) or {} collection_cls = cls._data_collection_class() supported_dimensions = collection_cls._get_supported_dimensions() if 2 not in supported_dimensions: raise errors.HighchartsPandasDeserializationError( f'Unable to create a collection of {cls.__name__} instances ' f'from df using a 2-dimensional array because {cls.__name__} does ' f'not support 2-dimensional arrays as inputs. Please use a ' f'different series type, or transpose df to a columnar structure ' f'and supply a property_map for greater precision.' ) data_properties = collection_cls._get_props_from_array() x_values = df.columns.values name_values = df.index.values series_count = len(df) series_list = [] for row in range(series_count): series_name = name_values[row] y_values = df.iloc[[row]].values y_values = y_values.reshape(x_values.shape) as_array = np.column_stack((x_values, y_values)) collection = collection_cls.from_array(as_array) series_instance_kwargs = series_kwargs.copy() series_instance_kwargs['data'] = collection series_instance_kwargs['name'] = series_name series_instance = cls(**series_instance_kwargs) for key in kwargs: if key not in series_instance_kwargs and key not in data_properties: setattr(series_instance, key, kwargs[key]) series_list.append(series_instance) if series_index is not None: return series_list[series_index] return series_list
[docs] @classmethod def from_pandas(cls, df, property_map = None, series_kwargs = None, series_in_rows = False, series_index = None, **kwargs): """Create one or more :term:`series` instances whose :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` properties are populated from a `pandas <https://pandas.pydata.org/>`_ :class:`DataFrame <pandas:pandas.DataFrame>`. .. code-block:: python # Given a Pandas DataFrame instance named "df" from highcharts_core.chart import Chart from highcharts_core.options.series.area import LineSeries # Creating a Series from the DataFrame ## EXAMPLE 1. Minimum code required. Creates one or more series. my_series = LineSeries.from_pandas(df) ## EXAMPLE 2. More precise configuration. Creates ONE series. my_series = LineSeries.from_pandas(df, series_index = 2) ## EXAMPLE 3. More precise configuration. Creates ONE series. my_series = LineSeries.from_pandas(df, property_map = { 'x': 'date', 'y': 'value', 'id': 'id' }) ## EXAMPLE 4. More precise configuration. Creates THREE series. my_series = LineSeries.from_pandas(df, property_map = { 'x': 'date', 'y': ['value1', 'value2', 'value3'], 'id': 'id' }) :param df: The :class:`DataFrame <pandas:pandas.DataFrame>` from which data should be loaded. :type df: :class:`DataFrame <pandas:pandas.DataFrame>` :param property_map: A :class:`dict <python:dict>` used to indicate which data point property should be set to which column in ``df``. The keys in the :class:`dict <python:dict>` should correspond to properties in the data point class, while the value should indicate the label for the :class:`DataFrame <pandas:pandas.DataFrame>` column. Defaults to :obj:`None <python:None>`. .. note:: If any of the values in ``property_map`` contain an iterable, then one series will be produced for each item in the iterable. For example, the following: .. code-block:: python { 'x': 'timestamp', 'y': ['value1', 'value2', 'value3'] } will return *three* series, each of which will have its :meth:`.x <CartesianData.x>` value populated from the column labeled ``'timestamp'``, and whose :meth:`.y <CartesianData.y>` values will be populated from the columns labeled ``'value1'``, ``'value2'``, and ``'value3'``, respectively. :type property_map: :class:`dict <python:dict>` or :obj:`None <python:None>` :param series_kwargs: An optional :class:`dict <python:dict>` containing keyword arguments that should be used when instantiating the series instance. Defaults to :obj:`None <python:None>`. .. warning:: If ``series_kwargs`` contains a ``data`` key, its value will be *overwritten*. The ``data`` value will be created from ``df`` instead. :type series_kwargs: :class:`dict <python:dict>` :param series_in_rows: if ``True``, will attempt a streamlined cartesian series with x-values taken from column names, y-values taken from row values, and the series name taken from the row index. Defaults to ``False``. :obj:`False <python:False>`. :type series_in_rows: :class:`bool <python:bool>` :param series_index: If supplied, return the series that Highcharts for Python generated from ``df`` at the ``series_index`` value. Defaults to :obj:`None <python:None>`, which returns all series generated from ``df``. :type series_index: :class:`int <python:int>`, slice, or :obj:`None <python:None>` :param **kwargs: Remaining keyword arguments will be attempted on the resulting :term:`series` instance and the data points it contains. :returns: A :term:`series` instance (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) OR :class:`list <python:list>` of series instances with its :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property populated from the data in ``df``. :rtype: :class:`list <python:list>` of series instances (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) or :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`-descendent :raises HighchartsPandasDeserializationError: if ``property_map`` references a column that does not exist in the data frame :raises HighchartsDependencyError: if `pandas <https://pandas.pydata.org/>`_ is not available in the runtime environment """ series_kwargs = validators.dict(series_kwargs, allow_empty = True) or {} # SCENARIO 0: Series in Rows if series_in_rows: return cls.from_pandas_in_rows(df, series_kwargs, series_index = series_index, **kwargs) # SCENARIO 1: Has Property Map if property_map: series_list = cls._from_pandas_multi_map(df, property_map, series_kwargs, **kwargs) if len(series_list) == 1: return series_list[0] if series_index is not None: return series_list[series_index] return series_list # SCENARIO 2: Properties in KWARGS collection_cls = cls._data_collection_class() data_point_cls = cls._data_point_class() props_from_array = data_point_cls._get_props_from_array() if not props_from_array: props_from_array = ['x', 'y'] property_map = {} for prop in props_from_array: if prop in kwargs: property_map[prop] = kwargs[prop] if property_map: series_list = cls._from_pandas_multi_map(df, property_map, series_kwargs) for index in range(len(series_list)): for key in kwargs: if key not in props_from_array and key not in series_kwargs: setattr(series_list[index], key, kwargs[key]) if len(series_list) == 1: return series_list[0] if series_index is not None: return series_list[series_index] return series_list # SCENARIO 3: No Explicit Properties series_idx = kwargs.get('index', df.index) column_count = len(df.columns) supported_dimensions = collection_cls._get_supported_dimensions() # SCENARIO 3a: Single Series, Data Frame Columns align exactly to Data Point Properties if column_count in supported_dimensions: property_map = {} props_from_array = data_point_cls._get_props_from_array(length = column_count) if not props_from_array: props_from_array = ['x', 'y'] property_map[props_from_array[0]] = series_idx for index, prop in enumerate(props_from_array[1:]): prop_value = df.iloc[:, index + 1].values property_map[prop] = prop_value collection = collection_cls() for key in property_map: setattr(collection, key, property_map[key]) series_kwargs['data'] = collection series_instance = cls(**series_kwargs) for key in kwargs: if key not in series_kwargs and key not in property_map: setattr(series_instance, key, kwargs[key]) return series_instance # SCENARIO 3b: Multiple Series, Data Frame Columns correspond to multiples of Data Point Properties reversed_dimensions = sorted(supported_dimensions, reverse = True) columns_per_series = None if reversed_dimensions: for dimension in reversed_dimensions: if series_idx is not None and dimension > 1 and column_count % (dimension - 1) == 0: if dimension > 2 and props_from_array[-1] == 'name': columns_per_series = dimension - 2 else: columns_per_series = dimension - 1 break elif dimension > 1 and column_count % dimension == 0: columns_per_series = dimension break elif dimension == 1: columns_per_series = 1 if not columns_per_series: raise errors.HighchartsPandasDeserializationError( f'Could not determine how to deserialize data frame with {column_count}' f' columns into a {collection_cls.__name__} instance. Please supply ' f'more precise instructions using property_map or ' f'by explicitly specificying data property kwargs.' ) series_count = column_count // columns_per_series series_list = [] for index in range(series_count): start = len(series_list) * columns_per_series property_map = {} if series_idx is not None: expected_length = columns_per_series + 1 else: expected_length = columns_per_series props_from_array = data_point_cls._get_props_from_array(length = expected_length) if not props_from_array: props_from_array = ['x', 'y'] property_map[props_from_array[0]] = series_idx has_implicit_series_name = 'name' not in kwargs and 'name' not in series_kwargs if has_implicit_series_name: series_name = df.columns[start] else: series_name = series_kwargs.get('name', None) or kwargs.get('name', None) for index, prop in enumerate(props_from_array[1:]): index = start + index prop_value = df.iloc[:, index].values property_map[prop] = prop_value collection = collection_cls() for key in property_map: setattr(collection, key, property_map[key]) series_kwargs['data'] = collection series_kwargs['name'] = series_name series_instance = cls(**series_kwargs) for key in kwargs: if key not in series_kwargs and key not in property_map: setattr(series_instance, key, kwargs[key]) series_list.append(series_instance) if series_index is not None: return series_list[index] return series_list
[docs] def load_from_pyspark(self, df, property_map): """Replaces the contents of the :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property with values from a `PySpark <https://spark.apache.org/docs/latest/api/python/>`_ :class:`DataFrame <pyspark:pyspark.sql.DataFrame>`. :param df: The :class:`DataFrame <pyspark:pyspark.sql.DataFrame>` from which data should be loaded. :type df: :class:`DataFrame <pyspark:pyspark.sql.DataFrame>` :param property_map: A :class:`dict <python:dict>` used to indicate which data point property should be set to which column in ``df``. The keys in the :class:`dict <python:dict>` should correspond to properties in the data point class, while the value should indicate the label for the :class:`DataFrame <pyspark:pyspark.sql.DataFrame>` column. :type property_map: :class:`dict <python:dict>` :raises HighchartsPySparkDeserializationError: if ``property_map`` references a column that does not exist in the data frame :raises HighchartsDependencyError: if `PySpark <https://spark.apache.org/docs/latest/api/python/>`_ is not available in the runtime environment """ try: from pyspark.sql import DataFrame except ImportError: raise errors.HighchartsDependencyError('pyspark is not available in the ' 'runtime environment. Please install ' 'using "pip install pyspark"') if not checkers.is_type(df, ('DataFrame')): raise errors.HighchartsValueError(f'df is expected to be a PySpark DataFrame.' f'Was: {df.__class__.__name__}') property_map = validators.dict(property_map) column_instances = [] for key in property_map: map_value = property_map[key] if map_value not in df.columns: raise errors.HighchartsPySparkDeserializationError( f'Unable to find a column labeled "{map_value}" in df.' ) column_instance = getattr(df, map_value) column_instances.append(column_instance) narrower_df = df.select(*column_instances) rdd_as_jsons = narrower_df.toJSON() df_as_dicts = [json.loads(x) for x in rdd_as_jsons.toLocalIterator()] records_as_dicts = [] for record in df_as_dicts: record_as_dict = {} for key in property_map: map_value = property_map[key] record_as_dict[key] = record.get(map_value, None) records_as_dicts.append(record_as_dict) self.data = records_as_dicts
[docs] @classmethod def from_pyspark(cls, df, property_map, series_kwargs = None): """Create a :term:`series` instance whose :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property is populated from a `PySpark <https://spark.apache.org/docs/latest/api/python/>`_ :class:`DataFrame <pyspark:pyspark.sql.DataFrame>`. :param df: The :class:`DataFrame <pyspark:pyspark.sql.DataFrame>` from which data should be loaded. :type df: :class:`DataFrame <pyspark:pyspark.sql.DataFrame>` :param property_map: A :class:`dict <python:dict>` used to indicate which data point property should be set to which column in ``df``. The keys in the :class:`dict <python:dict>` should correspond to properties in the data point class, while the value should indicate the label for the :class:`DataFrame <pyspark:pyspark.sql.DataFrame>` column. :type property_map: :class:`dict <python:dict>` :param series_kwargs: An optional :class:`dict <python:dict>` containing keyword arguments that should be used when instantiating the series instance. Defaults to :obj:`None <python:None>`. .. warning:: If ``series_kwargs`` contains a ``data`` key, its value will be *overwritten*. The ``data`` value will be created from ``df`` instead. :type series_kwargs: :class:`dict <python:dict>` :returns: A :term:`series` instance (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) with its :meth:`.data <highcharts_core.options.series.base.SeriesBase.data>` property populated from the data in ``df``. :rtype: :class:`list <python:list>` of series instances (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) :raises HighchartsPySparkDeserializationError: if ``property_map`` references a column that does not exist in the data frame :raises HighchartsDependencyError: if `PySpark <https://spark.apache.org/docs/latest/api/python/>`_ is not available in the runtime environment """ series_kwargs = validators.dict(series_kwargs, allow_empty = True) or {} instance = cls(**series_kwargs) instance.load_from_pyspark(df, property_map) return instance
[docs] def to_chart(self, chart_kwargs = None, options_kwargs = None): """Create a :class:`Chart <highcharts_core.chart.Chart>` instance containing the series instance. :param chart_kwargs: Optional keyword arguments to use when constructing the :class:`Chart <highcharts_core.chart.Chart>` instance. Defaults to :obj:`None <python:None>`. :type chart_kwargs: :class:`dict <python:dict>` :param options_kwargs: Optional keyword arguments to use when constructing the chart's :class:`HighchartsOptions <highcharts_core.options.HighchartsOptions>` object. Defaults to :obj:`None <python:None>`. .. warning:: If your ``chart_kwargs`` contains an ``options`` key, its value will be overwritten if you supply ``options_kwargs``. :type options_kwargs: :class:`dict <python:dict>` :returns: A :class:`Chart <highcharts_core.chart.Chart>` instance containing the series instance. :rtype: :class:`Chart <highcharts_core.chart.Chart>` """ from highcharts_core.chart import Chart chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {} as_chart = Chart(**chart_kwargs) if options_kwargs: as_chart.options = options_kwargs as_chart.add_series(self) return as_chart
[docs] def display(self, global_options = None, container = None, retries = 5, interval = 1000, chart_kwargs = None, options_kwargs = None): """Display the series in `Jupyter Labs <https://jupyter.org/>`_ or `Jupyter Notebooks <https://jupyter.org/>`_. :param global_options: The :term:`shared options` to use when rendering the chart. Defaults to :obj:`None <python:None>` :type global_options: :class:`SharedOptions <highcharts_stock.global_options.shared_options.SharedOptions>` or :obj:`None <python:None>` :param container: The ID to apply to the HTML container when rendered in Jupyter Labs. Defaults to :obj:`None <python:None>`, which applies the :meth:`.container <highcharts_core.chart.Chart.container>` property if set, and ``'highcharts_target_div'`` if not set. .. note:: Highcharts for Python will append a 6-character random string to the value of ``container`` to ensure uniqueness of the chart's container when rendering in a Jupyter Notebook/Labs context. The :class:`Chart <highcharts_core.chart.Chart>` instance will retain the mapping between container and the random string so long as the instance exists, thus allowing you to easily update the rendered chart by calling the :meth:`.display() <highcharts_core.chart.Chart.display>` method again. If you wish to create a new chart from the instance that does not update the existing chart, then you can do so by specifying a new ``container`` value. :type container: :class:`str <python:str>` or :obj:`None <python:None>` :param retries: The number of times to retry rendering the chart. Used to avoid race conditions with the Highcharts script. Defaults to 5. :type retries: :class:`int <python:int>` :param interval: The number of milliseconds to wait between retrying rendering the chart. Defaults to 1000 (1 seocnd). :type interval: :class:`int <python:int>` :param chart_kwargs: Optional keyword arguments to use when constructing the :class:`Chart <highcharts_core.chart.Chart>` instance. Defaults to :obj:`None <python:None>`. :type chart_kwargs: :class:`dict <python:dict>` :param options_kwargs: Optional keyword arguments to use when constructing the chart's :class:`HighchartsOptions <highcharts_core.options.HighchartsOptions>` object. Defaults to :obj:`None <python:None>`. .. warning:: If your ``chart_kwargs`` contains an ``options`` key, its value will be overwritten if you supply ``options_kwargs``. :type options_kwargs: :class:`dict <python:dict>` :raises HighchartsDependencyError: if `ipython <https://ipython.readthedocs.io/en/stable/>`_ is not available in the runtime environment """ as_chart = self.to_chart(chart_kwargs = chart_kwargs, options_kwargs = options_kwargs) as_chart.display(global_options = global_options, container = container, retries = retries, interval = interval)
[docs] def convert_to(self, series_type): """Creates a new series of ``series_type`` from the current series. :param series_type: The series type that should be returned. :type series_type: :class:`str <python:str>` or :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`-descended .. warning:: This operation is *not* guaranteed to work converting between all series types. This is because some series types have different properties, different logic / functionality for their properties, and may have entirely different data requirements. In general, this method is expected to be *lossy* in nature, meaning that when the series can be converted "close enough" the series will be converted. However, if the target ``series_type`` does not support certain properties set on the original instance, then those settings will *not* be propagated to the new series. In certain cases, this method may raise an :exc:`HighchartsSeriesConversionError <highcharts_core.errors.HighchartsSeriesConversionError>` if the method is unable to convert (even losing some data) the original into ``series_type``. :returns: A new series of ``series_type``, maintaining relevant properties and data from the original instance. :rtype: ``series_type`` :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>` descendant :raises HighchartsSeriesConversionError: if unable to convert (even after losing some data) the original instance into an instance of ``series_type``. :raises HighchartsValueError: if ``series_type`` is not a recognized series type """ from highcharts_core.options.series.series_generator import SERIES_CLASSES if isinstance(series_type, str): series_type = series_type.lower() if series_type not in SERIES_CLASSES: raise errors.HighchartsValueError(f'series_type expects a valid Highcharts ' f'series type. Received: {series_type}') series_type_name = series_type series_type = SERIES_CLASSES.get(series_type) elif not issubclass(series_type, SeriesBase): raise errors.HighchartsValueError(f'series_type expects a valid Highcharts ' f'series type. Received: {series_type}') else: series_type_name = series_type.__name__ as_js_literal = self.to_js_literal() try: target = series_type.from_js_literal(as_js_literal) except (ValueError, TypeError): raise errors.HighchartsSeriesConversionError(f'Unable to convert ' f'{self.__class__.__name__} instance ' f'to {series_type_name}') return target