Source code for highcharts_core.options.series.base

from typing import Optional, List
from decimal import Decimal

try:
    import orjson as json
except ImportError:
    try:
        import rapidjson as json
    except ImportError:
        try:
            import simplejson as json
        except ImportError:
            import json

from validator_collection import validators, checkers

from highcharts_core import errors, utility_functions
from highcharts_core.decorators import class_sensitive
from highcharts_core.options.plot_options.series import SeriesOptions
from highcharts_core.options.series.data.base import DataBase


[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) @property def data(self) -> Optional[List[DataBase]]: """The collection of data points for the series. Defaults to :obj:`None <python:None>`. :rtype: :class:`DataBase` or :obj:`None <python:None>` """ return self._data @data.setter @class_sensitive(DataBase, force_iterable = True) def data(self, value): self._data = 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), '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), '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), 'linecap': as_dict.get('linecap', None), 'line_width': as_dict.get('lineWidth', None), 'negative_color': as_dict.get('negativeColor', 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_csv(self, as_string_or_file, property_column_map, 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 = "\\"): """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. .. 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>` :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>` :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 if checkers.is_file_on_filesystem(as_string_or_file): with open(as_string_or_file, 'r') as file_: as_str = file_.read() else: as_str = as_string_or_file property_column_map = validators.dict(property_column_map, allow_empty = False) 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 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 = "\\" ) for key in cleaned_column_map: map_value = cleaned_column_map[key] if map_value not in columns: if isinstance(map_value, str): raise errors.HighchartsCSVDeserializationError( f'property_column_map is looking for a column labeled ' f'"{map_value}", but no corresponding column was found.' ) else: raise errors.HighchartsCSVDeserializationError( f'property_column_map is looking for a column indexed ' f'{map_value}, but no corresponding column was found.' ) data_point_dicts = [] for record in csv_records: data_point_dict = {} for key in cleaned_column_map: map_value = cleaned_column_map[key] data_point_dict[key] = record.get(map_value, None) data_point_dicts.append(data_point_dict) self.data = data_point_dicts
[docs] @classmethod def from_csv(cls, as_string_or_file, property_column_map, 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 = "\\"): """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('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. .. 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>` :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>` :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 CSV data in ``as_string_or_file``. :rtype: :class:`list <python:list>` of series instances (descended from :class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) :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 {} instance = cls(**series_kwargs) instance.load_from_csv(as_string_or_file, property_column_map, 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 = "\\") return instance
[docs] def load_from_pandas(self, df, property_map): """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:DataFrame>`. :param df: The :class:`DataFrame <pandas:DataFrame>` from which data should be loaded. :type df: :class:`DataFrame <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:DataFrame>` column. :type property_map: :class:`dict <python:dict>` :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 """ 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', 'Series')): raise errors.HighchartsValueError(f'df is expected to be a pandas DataFrame ' f'or Series. Was: {df.__class__.__name__}') if not property_map: raise errors.HighchartsValueError('property_map cannot be None or empty') property_map = validators.dict(property_map) for key in property_map: map_value = property_map[key] if map_value not in df.columns: raise errors.HighchartsPandasDeserializationError( f'Unable to find a column labeled "{map_value}" in df.' ) narrower_df = df[[property_map[key] for key in property_map]] df_as_dicts = narrower_df.to_dict(orient = 'records') 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_pandas(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 `pandas <https://pandas.pydata.org/>`_ :class:`DataFrame <pandas:DataFrame>`. :param df: The :class:`DataFrame <pandas:DataFrame>` from which data should be loaded. :type df: :class:`DataFrame <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: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 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 {} instance = cls(**series_kwargs) instance.load_from_pandas(df, property_map) return instance
[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