from typing import Optional
from collections import UserDict
from validator_collection import validators, checkers
from highcharts_core import constants, errors, utility_functions
from highcharts_core.decorators import class_sensitive, validate_types
from highcharts_core.metaclasses import HighchartsMeta
from highcharts_core.options import HighchartsOptions
from highcharts_core.utility_classes.javascript_functions import CallbackFunction
from highcharts_core.js_literal_functions import serialize_to_js_literal
from highcharts_core.headless_export import ExportServer
from highcharts_core.options.series.series_generator import create_series_obj, SERIES_CLASSES
from highcharts_core.global_options.shared_options import SharedOptions
class Chart(HighchartsMeta):
"""Python representation of a Highcharts ``Chart`` object."""
def __init__(self, **kwargs):
self._callback = None
self._container = None
self._options = None
self._variable_name = None
self.callback = kwargs.get('callback', None)
self.container = kwargs.get('container', None)
self.options = kwargs.get('options', None)
self.variable_name = kwargs.get('variable_name', None)
super().__init__(**kwargs)
def _jupyter_include_scripts(self):
"""Return the JavaScript code that is used to load the Highcharts JS libraries.
.. note::
Currently includes *all* `Highcharts JS <https://www.highcharts.com/>`_ modules
in the HTML. This issue will be addressed when roadmap issue :issue:`2` is
released.
:rtype: :class:`str <python:str>`
"""
js_str = ''
for item in constants.INCLUDE_LIBS:
js_str += utility_functions.jupyter_add_script(item)
js_str += """.then(() => {"""
for item in constants.INCLUDE_LIBS:
js_str += """});"""
return js_str
def _jupyter_javascript(self,
global_options = None,
container = None,
retries = 3,
interval = 1000):
"""Return the JavaScript code which Jupyter Labs will need to render the chart.
: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.
: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 3.
: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>`
:rtype: :class:`str <python:str>`
"""
original_container = self.container
self.container = container or self.container or 'highcharts_target_div'
if global_options is not None:
global_options = validate_types(global_options,
types = SharedOptions)
js_str = ''
js_str += utility_functions.get_retryHighcharts()
if global_options:
js_str += '\n' + utility_functions.prep_js_for_jupyter(global_options.to_js_literal()) + '\n'
js_str += utility_functions.prep_js_for_jupyter(self.to_js_literal(),
container = self.container,
retries = retries,
interval = interval)
self.container = original_container
return js_str
def _jupyter_container_html(self, container = None):
"""Returns the Jupyter Labs HTML container for rendering the chart in Jupyter Labs context.
: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.
:type container: :class:`str <python:str>` or :obj:`None <python:None>`
:rtype: :class:`str <python:str>`
"""
if self.options.chart:
height = self.options.chart.height or 400
else:
height = 400
container = container or self.container or 'highcharts_target_div'
container_str = f"""<div id=\"{container}\" style=\"width:100%; height:{height};\"></div>\n"""
return container_str
def _repr_html_(self):
"""Produce the HTML representation of the chart.
.. note::
Currently includes *all* `Highcharts JS <https://www.highcharts.com/>`_ modules
in the HTML. This issue will be addressed when roadmap issue :issue:`2` is
released.
:returns: The HTML representation of the chart.
:rtype: :class:`str <python:str>`
"""
return self.display()
@property
def callback(self) -> Optional[CallbackFunction]:
"""A (JavaScript) function that is run when the chart has loaded and all external
images have been loaded. Defaults to :obj:`None <python:None>`.
.. note::
Setting this proprety is equivalent to setting a value for
:meth:`ChartOptions.events.load <highcharts.utility_classes.events.ChartEvents.load>`
:rtype: :class:`CallbackFunction` or :obj:`None <python:None>`
"""
return self._callback
@callback.setter
@class_sensitive(CallbackFunction)
def callback(self, value):
self._callback = value
@property
def options(self) -> Optional[HighchartsOptions]:
"""The Python representation of the `Highcharts <https://highcharts.com>`_
``options`` `configuration object <https://api.highcharts.com/highcharts/>`_
Defaults to :obj:`None <python:None>`.
:rtype: :class:`HighchartsOptions` or :obj:`None <python:None>`
"""
return self._options
@options.setter
@class_sensitive(HighchartsOptions)
def options(self, value):
self._options = value
@property
def container(self) -> Optional[str]:
"""The ``id`` of the ``<div>`` element in which your Highcharts chart should be
rendered. Defaults to :obj:`None <python:None>`.
:rtype: :class:`str <python:str>` or :obj:`None <python:None>`
"""
if self._container:
return self._container
if self.options and self.options.chart and self.options.chart.render_to:
return self.options.chart.render_to
return None
@container.setter
def container(self, value):
self._container = validators.string(value, allow_empty = True)
@property
def variable_name(self) -> Optional[str]:
"""The name given to the (JavaScript) variable to which the (JavaScript) Chart
instance wil be assigned. Defaults to :obj:`None <python:None>`.
.. note::
When the :class:`Chart` object is converted to JavaScript code, the
(JavaScript) chart instance is assigned to a variable in your JavaScript code.
In the example code below, the Chart instance is assigned to a ``variable_name``
of ``chart1``:
.. code-block:: javascript
var chart1 = Highcharts.Chart('myTargetDiv', {});
.. warning::
If :obj:`None <python:None>`, when converted to a JavaScript literal, the
:class:`Chart` instance will simply not be assigned to a variable.
:rtype: :class:`str <python:str>` or :obj:`None <python:None>`
"""
return self._variable_name
@variable_name.setter
def variable_name(self, value):
self._variable_name = validators.variable_name(value, allow_empty = True)
@classmethod
def _get_kwargs_from_dict(cls, as_dict):
kwargs = {
'callback': as_dict.get('callback', None),
'container': as_dict.get('container', None) or as_dict.get('renderTo', None),
'options': as_dict.get('options', None) or as_dict.get('userOptions', None),
'variable_name': as_dict.get('variable_name',
None) or as_dict.get('variableName', None)
}
return kwargs
def _to_untrimmed_dict(self, in_cls = None) -> dict:
untrimmed = {
'callback': self.callback,
'container': self.container,
'userOptions': self.options
}
return untrimmed
def to_js_literal(self,
filename = None,
encoding = 'utf-8') -> Optional[str]:
"""Return the object represented as a :class:`str <python:str>` containing the
JavaScript object literal.
:param filename: The name of a file to which the JavaScript object literal should
be persisted. Defaults to :obj:`None <python:None>`
:type filename: Path-like
:param encoding: The character encoding to apply to the resulting object. Defaults
to ``'utf-8'``.
:type encoding: :class:`str <python:str>`
.. note::
If :meth:`variable_name <Chart.variable_name>` is set, will render a string as
a new JavaScript instance invocation in the (pseudo-code) form:
.. code-block:: javascript
new VARIABLE_NAME = new Chart(...);
If :meth:`variable_name <Chart.variable_name>` is not set, will simply return
the ``new Chart(...)`` portion in the string.
:rtype: :class:`str <python:str>` or :obj:`None <python:None>`
"""
if filename:
filename = validators.path(filename)
untrimmed = self._to_untrimmed_dict()
as_dict = {}
for key in untrimmed:
item = untrimmed[key]
serialized = serialize_to_js_literal(item, encoding = encoding)
if serialized is not None:
as_dict[key] = serialized
signature_elements = 0
container_as_str = ''
if self.container:
container_as_str = f"""'{self.container}'"""
else:
container_as_str = """null"""
signature_elements += 1
options_as_str = ''
if self.options:
options_as_str = self.options.to_js_literal(encoding = encoding)
options_as_str = f"""{options_as_str}"""
else:
options_as_str = """null"""
signature_elements += 1
callback_as_str = ''
if self.callback:
callback_as_str = self.callback.to_js_literal(encoding = encoding)
callback_as_str = f"""{callback_as_str}"""
signature_elements += 1
signature = """new Highcharts.chart("""
signature += container_as_str
if signature_elements > 1:
signature += ',\n'
signature += options_as_str
if signature_elements > 1:
signature += ',\n'
if callback_as_str:
signature += callback_as_str
signature += ');'
constructor_prefix = ''
if self.variable_name:
constructor_prefix = f'var {self.variable_name} = '
as_str = constructor_prefix + signature
prefix = """document.addEventListener('DOMContentLoaded', function() {\n"""
suffix = """});"""
as_str = prefix + as_str + '\n' + suffix
if filename:
with open(filename, 'w', encoding = encoding) as file_:
file_.write(as_str)
return as_str
def download_chart(self,
format = 'png',
scale = 1,
width = None,
filename = None,
auth_user = None,
auth_password = None,
timeout = 0.5,
server_instance = None,
**kwargs):
"""Export a downloaded form of the chart using a Highcharts :term:`Export Server`.
:param filename: The name of the file where the exported chart should (optionally)
be persisted. Defaults to :obj:`None <python:None>`.
:type filename: Path-like or :obj:`None <python:None>`
:param auth_user: The username to use to authenticate against the
Export Server, using :term:`basic authentication`. Defaults to
:obj:`None <python:None>`.
:type auth_user: :class:`str <python:str>` or :obj:`None <python:None>`
:param auth_password: The password to use to authenticate against the Export
Server (using :term:`basic authentication`). Defaults to
:obj:`None <python:None>`.
:type auth_password: :class:`str <python:str>` or :obj:`None <python:None>`
:param timeout: The number of seconds to wait before issuing a timeout error.
The timeout check is passed if bytes have been received on the socket in less
than the ``timeout`` value. Defaults to ``0.5``.
:type timeout: numeric or :obj:`None <python:None>`
:param server_instance: Provide an already-configured :class:`ExportServer`
instance to use to programmatically produce the exported chart. Defaults to
:obj:`None <python:None>`, which causes Highcharts for Python to instantiate
a new :class:`ExportServer` instance.
:type server_instance: :class:`ExportServer` or :obj:`None <python:None>`
.. note::
All other keyword arguments are as per the :class:`ExportServer` constructor.
:returns: The exported chart image, either as a :class:`bytes <python:bytes>`
binary object or as a base-64 encoded string (depending on the ``use_base64``
keyword argument).
:rtype: :class:`bytes <python:bytes>` or :class:`str <python:str>`
"""
if checkers.is_type(self.options, 'HighchartsStockOptions'):
constructor = 'Stock'
else:
constructor = 'Chart'
if not server_instance:
return ExportServer.get_chart(filename = filename,
auth_user = auth_user,
auth_password = auth_password,
timeout = timeout,
options = self.options,
constructor = constructor,
scale = scale,
width = width,
**kwargs)
if not isinstance(server_instance, ExportServer):
raise errors.HighchartsValueError(f'server_instance is expected to be an '
f'ExportServer instance. Was: '
f'{server_instance.__class__.__name__}')
return server_instance.request_chart(filename = filename,
auth_user = auth_user,
auth_password = auth_password,
timeout = timeout,
options = self.options,
constructor = constructor,
**kwargs)
@classmethod
def _copy_dict_key(cls,
key,
original,
other,
overwrite = True,
**kwargs):
"""Copies the value of ``key`` from ``original`` to ``other``.
:param key: The key that is to be copied.
:type key: :class:`str <python:str>`
:param original: The original :class:`dict <python:dict>` from which it should
be copied.
:type original: :class:`dict <python:dict>`
:param other: The :class:`dict <python:dict>` to which it should be copied.
:type other: :class:`dict <python:dict>`
:returns: The value that should be placed in ``other`` for ``key``.
"""
preserve_data = kwargs.get('preserve_data', True)
original_value = original[key]
other_value = other.get(key, None)
if key == 'data' and preserve_data:
return other_value
if key == 'points' and preserve_data:
return other_value
if key == 'series' and preserve_data:
if not other_value:
return [x for x in original_value]
if len(other_value) != len(original_value):
matched_series = []
new_series = []
for original_item in original_value:
matched = False
for other_item in other_value:
if checkers.are_dicts_equivalent(original_item, other_item):
matched_series.append((original_item, other_item))
matched = True
break
if not matched:
new_series.append(original_item)
updated_series = []
for items in matched_series:
original_item = items[0]
other_item = items[1]
new_item = {}
for subkey in original_item:
new_item_value = cls._copy_dict_key(subkey,
original_item,
new_item,
overwrite = overwrite,
**kwargs)
new_item[subkey] = new_item_value
updated_series.append(new_item)
updated_series.extend(new_series)
return updated_series
elif isinstance(original_value, (dict, UserDict)):
new_value = {}
for subkey in original_value:
new_key_value = cls._copy_dict_key(subkey,
original_value,
other_value,
overwrite = overwrite,
**kwargs)
new_value[subkey] = new_key_value
return new_value
elif checkers.is_iterable(original_value,
forbid_literals = (str,
bytes,
dict,
UserDict)):
if overwrite:
new_value = [x for x in original_value]
return new_value
return other_value
elif other_value and not overwrite:
return other_value
return original_value
[docs] def copy(self,
other = None,
overwrite = True,
**kwargs):
"""Copy the configuration settings from this chart to the ``other`` chart.
:param other: The target chart to which the properties of this chart should
be copied. If :obj:`None <python:None>`, will create a new chart and populate
it with properties copied from ``self``. Defaults to :obj:`None <python:None>`.
:type other: :class:`Chart`
:param overwrite: if ``True``, properties in ``other`` that are already set will
be overwritten by their counterparts in ``self``. Defaults to ``True``.
:type overwrite: :class:`bool <python:bool>`
:param preserve_data: If ``True``, will preserve the data values in any
:term:`series` contained in ``other`` and the configuration of the
:meth:`options.data <Options.data>` property, but will still copy other
properties as applicable. If ``False``, will overwrite data in ``other``
with data from ``self``. Defaults to ``True``.
:type preserve_data: :class:`bool <python:bool>`
:param kwargs: Additional keyword arguments. Some special descendents of
:class:`HighchartsMeta` may have special implementations of this method which
rely on additional keyword arguments.
:returns: A mutated version of ``other`` with new property values
"""
super().copy(other = other,
overwrite = overwrite,
**kwargs)
def add_series(self, *series):
"""Adds ``series`` to the
:meth:`Chart.options.series <highcharts_core.options.HighchartsOptions.series>`
property.
:param series: One or more :term:`series` instances (descended from
:class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) or an
instance (e.g. :class:`dict <python:dict>`, :class:`str <python:str>`, etc.)
coercable to one
:type series: one or more
:class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`
or coercable
"""
new_series = []
for item in series:
item_series = create_series_obj(item)
new_series.append(item_series)
if self.options and self.options.series:
existing_series = [x for x in self.options.series]
elif self.options:
existing_series = []
else:
existing_series = []
self.options = HighchartsOptions()
updated_series = existing_series + new_series
self.options.series = updated_series
[docs] def update_series(self, *series, add_if_unmatched = False):
"""Replace existing series with the new versions supplied in ``series``,
matching them based on their
:meth:`.id <highcharts_core.options.series.base.SeriesBase.id>` property.
:param series: One or more :term:`series` instances (descended from
:class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) or an
instance (e.g. :class:`dict <python:dict>`, :class:`str <python:str>`, etc.)
coercable to one
:type series: one or more
:class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`
or coercable
:param add_if_unmatched: If ``True``, will add a series that does not have a
match. If ``False``, will raise a
:exc:`HighchartsMissingSeriesError <highcharts_core.errors.HighchartsMissingSeriesError>`
if a series does not have a match on the chart. Defaults to ``False``.
:type add_if_unmatched: :class:`bool <python:bool>`
"""
new_series = []
for item in series:
item_series = create_series_obj(item)
new_series.append(item_series)
if self.options and self.options.series:
existing_series = [x for x in self.options.series]
elif self.options:
existing_series = []
else:
existing_series = []
self.options = HighchartsOptions()
existing_ids = [x.id for x in existing_series]
new_ids = [x.id for x in new_series]
overlap_ids = [x for x in new_ids if x in existing_ids]
updated_series = []
for existing in existing_series:
if existing.id not in overlap_ids:
updated_series.append(existing)
for new in new_series:
if new.id not in overlap_ids and not add_if_unmatched:
raise errors.HighchartsMissingSeriesError(f'attempted to update series '
f'id "{new.id}", but that '
f'series is not present in '
f'the chart')
updated_series.append(new)
self.options.series = updated_series
@classmethod
def from_series(cls, *series, kwargs = None):
"""Creates a new :class:`Chart <highcharts_core.chart.Chart>` instance populated
with ``series``.
:param series: One or more :term:`series` instances (descended from
:class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`) or an
instance (e.g. :class:`dict <python:dict>`, :class:`str <python:str>`, etc.)
coercable to one
:type series: one or more
:class:`SeriesBase <highcharts_core.options.series.base.SeriesBase>`
or coercable
:param kwargs: Other properties to use as keyword arguments for the instance to be
created.
.. warning::
If ``kwargs`` sets the
:meth:`options.series <highcharts_core.options.HighchartsOptions.series>`
property, that setting will be *overridden* by the contents of ``series``.
:type kwargs: :class:`dict <python:dict>`
:returns: A new :class:`Chart <highcharts_core.chart.Chart>` instance
:rtype: :class:`Chart <highcharts_core.chart.Chart>`
"""
kwargs = validators.dict(kwargs, allow_empty = True) or {}
instance = cls(**kwargs)
if checkers.is_iterable(series) is True:
for item in series:
instance.add_series(item)
else:
instance.add_series(series)
return instance
def display(self,
global_options = None,
container = None,
retries = 3,
interval = 1000):
"""Display the chart 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.
: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 3.
: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>`
:raises HighchartsDependencyError: if
`ipython <https://ipython.readthedocs.io/en/stable/>`_ is not available in the
runtime environment
"""
try:
from IPython import display as display_mod
from IPython.core.display_functions import display
except ImportError:
raise errors.HighchartsDependencyError('Unable to import IPython modules. '
'Make sure that it is available in '
'your runtime environment. To install,'
'use: pip install ipython')
include_js_str = self._jupyter_include_scripts()
include_display = display_mod.Javascript(data = include_js_str)
container = container or self.container or 'highcharts_target_div'
html_str = self._jupyter_container_html(container)
html_display = display_mod.HTML(data = html_str)
chart_js_str = self._jupyter_javascript(global_options = global_options,
container = container,
retries = retries,
interval = interval)
javascript_display = display_mod.Javascript(data = chart_js_str)
display(include_display)
display(html_display)
display(javascript_display)
@classmethod
def from_csv(cls,
as_string_or_file,
property_column_map,
series_type,
has_header_row = True,
series_kwargs = None,
options_kwargs = None,
chart_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 :class:`Chart <highcharts_core.chart.Chart>` instance with
data populated from 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_chart = Chart.from_csv('some-csv-file.csv',
property_column_map = {
'x': 0,
'y': 3,
'id': 'id'
},
series_type = 'line')
As the example above shows, data is loaded into the ``my_chart`` 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 series_type: Indicates the series type that should be created from the CSV
data.
:type series_type: :class:`str <python:str>`
: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>` or :obj:`None <python:None>`
:param options_kwargs: An optional :class:`dict <python:dict>` containing keyword
arguments that should be used when instantiating the :class:`HighchartsOptions`
instance. Defaults to :obj:`None <python:None>`.
.. warning::
If ``options_kwargs`` contains a ``series`` key, the ``series`` value will be
*overwritten*. The ``series`` value will be created from the CSV file instead.
:type options_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:param chart_kwargs: An optional :class:`dict <python:dict>` containing keyword
arguments that should be used when instantiating the :class:`Chart` instance.
Defaults to :obj:`None <python:None>`.
.. warning::
If ``chart_kwargs`` contains an ``options`` key, ``options`` will be
*overwritten*. The ``options`` value will be created from the
``options_kwargs`` and CSV file instead.
:type chart_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
: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'``.
.. 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 :class:`Chart <highcharts_core.chart.Chart>` instance with its
data populated from the CSV data.
:rtype: :class:`Chart <highcharts_core.chart.Chart>`
:raises HighchartsCSVDeserializationError: if ``property_column_map`` references
CSV columns by their label, but the CSV data does not contain a header row
"""
series_type = validators.string(series_type, allow_empty = False)
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}')
options_kwargs = validators.dict(options_kwargs, allow_empty = True) or {}
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
series_cls = SERIES_CLASSES.get(series_type, None)
series = series_cls.from_csv(as_string_or_file,
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)
options = HighchartsOptions(**options_kwargs)
options.series = [series]
instance = cls(**chart_kwargs)
instance.options = options
return instance
@classmethod
def from_pandas(cls,
df,
property_map,
series_type,
series_kwargs = None,
options_kwargs = None,
chart_kwargs = None):
"""Create a :class:`Chart <highcharts_core.chart.Chart>` instance whose
data 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_type: Indicates the series type that should be created from the data
in ``df``.
:type series_type: :class:`str <python:str>`
: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 options_kwargs: An optional :class:`dict <python:dict>` containing keyword
arguments that should be used when instantiating the :class:`HighchartsOptions`
instance. Defaults to :obj:`None <python:None>`.
.. warning::
If ``options_kwargs`` contains a ``series`` key, the ``series`` value will be
*overwritten*. The ``series`` value will be created from the data in ``df``.
:type options_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:param chart_kwargs: An optional :class:`dict <python:dict>` containing keyword
arguments that should be used when instantiating the :class:`Chart` instance.
Defaults to :obj:`None <python:None>`.
.. warning::
If ``chart_kwargs`` contains an ``options`` key, ``options`` will be
*overwritten*. The ``options`` value will be created from the
``options_kwargs`` and the data in ``df`` instead.
:type chart_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:returns: A :class:`Chart <highcharts_core.chart.Chart>` instance with its
data populated from the data in ``df``.
:rtype: :class:`Chart <highcharts_core.chart.Chart>`
: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_type = validators.string(series_type, allow_empty = False)
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}')
options_kwargs = validators.dict(options_kwargs, allow_empty = True) or {}
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
series_cls = SERIES_CLASSES.get(series_type, None)
series = series_cls.from_pandas(df,
property_map,
series_kwargs)
options = HighchartsOptions(**options_kwargs)
options.series = [series]
instance = cls(**chart_kwargs)
instance.options = options
return instance
@classmethod
def from_pyspark(cls,
df,
property_map,
series_type,
series_kwargs = None,
options_kwargs = None,
chart_kwargs = None):
"""Create a :class:`Chart <highcharts_core.chart.Chart>` instance whose
data 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_type: Indicates the series type that should be created from the data
in ``df``.
:type series_type: :class:`str <python:str>`
: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 options_kwargs: An optional :class:`dict <python:dict>` containing keyword
arguments that should be used when instantiating the :class:`HighchartsOptions`
instance. Defaults to :obj:`None <python:None>`.
.. warning::
If ``options_kwargs`` contains a ``series`` key, the ``series`` value will be
*overwritten*. The ``series`` value will be created from the data in ``df``.
:type options_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:param chart_kwargs: An optional :class:`dict <python:dict>` containing keyword
arguments that should be used when instantiating the :class:`Chart` instance.
Defaults to :obj:`None <python:None>`.
.. warning::
If ``chart_kwargs`` contains an ``options`` key, ``options`` will be
*overwritten*. The ``options`` value will be created from the
``options_kwargs`` and the data in ``df`` instead.
:type chart_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:returns: A :class:`Chart <highcharts_core.chart.Chart>` instance with its
data populated from the data in ``df``.
:rtype: :class:`Chart <highcharts_core.chart.Chart>`
: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_type = validators.string(series_type, allow_empty = False)
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}')
options_kwargs = validators.dict(options_kwargs, allow_empty = True) or {}
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
series_cls = SERIES_CLASSES.get(series_type, None)
series = series_cls.from_pyspark(df,
property_map,
series_kwargs)
options = HighchartsOptions(**options_kwargs)
options.series = [series]
instance = cls(**chart_kwargs)
instance.options = options
return instance
@classmethod
def from_options(cls,
options,
chart_kwargs = None):
"""Create a :class:`Chart <highcharts_core.chart.Chart>` instance from a
:class:`HighchartsOptions <highcharts_core.options.HighchartsOptions>` object.
:param options: The configuration options to use to instantiate the chart.
:type options:
:class:`HighchartsOptions <highcharts_core.options.HighchartsOptions>` or
coercable
:param chart_kwargs: An optional :class:`dict <python:dict>` containing keyword
arguments that should be used when instantiating the instance. Defaults to
:obj:`None <python:None>`.
.. warning::
If ``chart_kwargs`` contains an ``options`` key, ``options`` will be
*overwritten* by the contents of ``options``.
:type chart_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:returns: The :class:`Chart <highcharts_core.chart.Chart>` instance
:rtype: :class:`Chart <highcharts_core.chart.Chart>`
"""
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
options = validate_types(options,
types = (HighchartsOptions))
instance = cls(**chart_kwargs)
instance.options = options
return instance