from typing import Optional
from validator_collection import validators, checkers
from highcharts_core.chart import Chart as ChartBase
from highcharts_maps import constants, errors, utility_functions
from highcharts_maps.options import HighchartsOptions, HighchartsMapsOptions
from highcharts_maps.decorators import validate_types
from highcharts_maps.js_literal_functions import serialize_to_js_literal
from highcharts_maps.headless_export import ExportServer
from highcharts_maps.options.series.series_generator import (create_series_obj,
SERIES_CLASSES,
MAPS_SERIES_LIST)
from highcharts_maps.global_options.shared_options import SharedMapsOptions, SharedOptions
from highcharts_maps.options.chart import ChartOptions
from highcharts_maps.options.map_views import MapViewOptions
from highcharts_maps.options.series.data.map_data import MapData
from highcharts_maps.utility_classes.projections import ProjectionOptions, CustomProjection
[docs]class Chart(ChartBase):
"""Python representation of a Highcharts ``Chart`` object."""
def __init__(self, **kwargs):
self._is_maps_chart = None
self.is_maps_chart = kwargs.get('is_maps_chart', False)
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 = ''
if self.is_maps_chart:
for item in constants.MAPS_INCLUDE_LIBS:
js_str += utility_functions.jupyter_add_script(item)
js_str += """.then(() => {"""
for item in constants.MAPS_INCLUDE_LIBS:
js_str += """});"""
else:
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 = (SharedMapsOptions, 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
@property
def is_maps_chart(self) -> bool:
"""If ``True``, indicates that the chart should be rendered as a
`Highcharts Maps <https://www.highcharts.com/products/maps/>`__ chart. If
``False``, the chart will be rendered using the standard
`Highcharts JS <https://www.highcharts.com/products/highcharts/>`__ constructor.
Defaults to ``False``.
:rtype: :class:`bool <python:bool>`
"""
return self._is_maps_chart
@is_maps_chart.setter
def is_maps_chart(self, value):
self._is_maps_chart = bool(value)
@property
def options(self) -> Optional[HighchartsOptions | HighchartsMapsOptions]:
"""The Python representation of the
`Highcharts Maps <https://www.highcharts.com/products/maps/>`__
``options`` `configuration object <https://api.highcharts.com/highmaps/>`_
Defaults to :obj:`None <python:None>`.
:rtype: :class:`HighchartsOptions` or :class:`HighchartsMapsOptions` or
:obj:`None <python:None>`
"""
return self._options
@options.setter
def options(self, value):
if not value:
self._options = None
elif self.is_maps_chart:
self._options = validate_types(value, HighchartsMapsOptions)
self.is_maps_chart = True
else:
if checkers.is_type(value, 'HighchartsMapsOptions'):
self._options = value
self.is_maps_chart = True
elif checkers.is_type(value, 'HighchartsOptions'):
self._options = value
elif ('mapNavigation' in value
or 'map_navigation' in value
or 'mapView' in value
or 'map_view' in value):
self._options = validate_types(value, HighchartsMapsOptions)
self.is_maps_chart = True
else:
self._options = validate_types(value, HighchartsOptions)
@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),
'is_maps_chart': as_dict.get('is_maps_chart',
None) or as_dict.get('isMapsChart', False)
}
return kwargs
[docs] 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
fetch_as_str = ''
if self.is_async:
urls = []
topologies = []
if self.options.chart and self.options.chart.map:
url = self.options.chart.map.url
urls.append(url)
self.options.chart.map.fetch_counter = 1
map_data_as_str = self.options.chart.map.to_js_literal(encoding = encoding)
topologies.append(map_data_as_str)
for index, series in enumerate(self.options.series):
if series.is_async:
url = series.map_data.url
if url in urls:
continue
urls.append(url)
if len(urls) > 1:
self.options.series[index].map_data.fetch_counter += 1
map_data_as_str = series.map_data.to_js_literal(encoding = encoding)
topologies.append(map_data_as_str)
fetch_as_str = '\n'.join(topologies)
custom_projection_as_str = ''
if self.uses_custom_projection:
custom_projection_as_str = self.options.map_view.projection.custom.to_js_literal(encoding = encoding)
custom_projection_as_str += f"""\nHighcharts.Projection.add('{self.options.map_view.projection.custom.name}', {self.options.map_view.projection.custom.class_name})\n"""
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)
if self.options.chart and self.options.chart.map and self.options.chart.is_async:
chart_map_str = self.options.chart.map.to_js_literal(encoding = encoding)
chart_map_str = f"""'{chart_map_str}'"""
fetch_counter = self.options.chart.map.fetch_counter
options_as_str = options_as_str.replace(chart_map_str, f'topology{fetch_counter}')
options_as_str = f"""{options_as_str}"""
else:
options_as_str = """{}"""
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("""
if self.is_maps_chart:
signature = """new Highcharts.mapChart("""
if container_as_str:
signature += container_as_str
if signature_elements > 1:
signature += ',\n'
if options_as_str:
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
if self.is_async:
prefix = """document.addEventListener('DOMContentLoaded', function() {\n"""
if custom_projection_as_str:
prefix += custom_projection_as_str
prefix += """(async () => { """
suffix = """})()});"""
as_str = fetch_as_str + '\n' + as_str
else:
prefix = """document.addEventListener('DOMContentLoaded', function() {\n"""
if custom_projection_as_str:
prefix += custom_projection_as_str
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
[docs] 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 self.is_maps_chart:
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)
[docs] 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 = []
if self.is_maps_chart:
self.options = HighchartsMapsOptions()
else:
self.options = HighchartsOptions()
updated_series = existing_series + new_series
self.options.series = updated_series
[docs] @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
:class:`IndicatorSeriesBase <highcharts_maps.options.series.base.IndicatorSeriesBase>`
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_maps.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_maps.chart.Chart>` instance
:rtype: :class:`Chart <highcharts_maps.chart.Chart>`
"""
kwargs = validators.dict(kwargs, allow_empty = True) or {}
instance = cls(**kwargs)
instance.add_series(series)
[docs] def display(self, global_options = None):
"""Display the chart in `Jupyter Labs <https://jupyter.org/>`_ or
`Jupyter Notebooks <https://jupyter.org/>`_.
:raises HighchartsDependencyError: if
`ipython <https://ipython.readthedocs.io/en/stable/>`_ is not available in the
runtime environment
"""
try:
from IPython import display
except ImportError:
raise errors.HighchartsDependencyError('Unable to import IPython.display. '
'Make sure that it is available in '
'your runtime environment. To install,'
'use: pip install ipython')
if global_options is not None:
global_options = validate_types(global_options,
types = SharedMapsOptions)
if self.is_maps_chart:
include_str = constants.MAPS_INCLUDE_STR
else:
include_str = constants.INCLUDE_STR
html_str = include_str + '\n'
if global_options:
html_str += global_options._repr_html_() + '\n'
html_str += self._repr_html_()
display.display_html(html_str, raw = True)
@staticmethod
def _get_options_obj(series_type, options_kwargs):
"""Return an :class:`Options` descendent based on the series type.
:param series_type: Indicates the series type that should be created from the CSV
data.
:type series_type: :class:`str <python:str>`
:param options_kwargs: A :class:`dict <python:dict>` containing keyword
arguments that should be used when instantiating the :class:`Options`
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>`
:returns: An :class:`Options` descendent.
:rtype: :class:`HighchartsOptions` or :class:`HighchartsMapsOptions`
"""
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 {}
if series_type not in MAPS_SERIES_LIST:
options = HighchartsOptions(**options_kwargs)
else:
options = HighchartsMapsOptions(**options_kwargs)
return options
[docs] @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 = "\\",
is_maps_chart = False):
"""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>`
:param is_maps_chart: If ``True``, indicates that the chart should be
instantiated as a **Highcharts Stock for Python** chart. Defaults to ``False``.
:type is_maps_chart: :class:`bool <python:bool>`
: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
"""
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
options = cls._get_options_obj(series_type, options_kwargs)
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.series = [series]
instance = cls(**chart_kwargs)
instance.options = options
return instance
[docs] @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
"""
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
options = cls._get_options_obj(series_type, options_kwargs)
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
[docs] @classmethod
def from_geopandas(cls,
gdf,
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 `geopandas <https://geopandas.org/>`__
:class:`GeoDataFrame <geopandas:GeoDataFrame>`.
:param gdf: The :class:`GeoDataFrame <geopandas:GeoDataFrame>` from which data
should be loaded.
:type gdf: :class:`GeoDataFrame <geopandas:GeoDataFrame>`
:param property_map: A :class:`dict <python:dict>` used to indicate which
data point property should be set to which column in ``gdf``. 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:`GeoDataFrame <geopandas:GeoDataFrame>` column.
:type property_map: :class:`dict <python:dict>`
:param series_type: Indicates the series type that should be created from the data
in ``gdf``.
: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 ``gdf`` 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 ``gdf``.
: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 ``gdf`` 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 ``gdf``.
: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
"""
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
options = cls._get_options_obj(series_type, options_kwargs)
series_cls = SERIES_CLASSES.get(series_type, None)
series = series_cls.from_pandas(gdf,
property_map,
series_kwargs)
options = HighchartsMapsOptions(**options_kwargs)
options.chart = ChartOptions()
options.chart.map_data = MapData.from_geodataframe(as_gdf = gdf)
options.series = [series]
instance = cls(**chart_kwargs)
instance.options = options
return instance
[docs] @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
"""
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
options = cls._get_options_obj(series_type, options_kwargs)
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
[docs] @classmethod
def from_options(cls,
options,
chart_kwargs = None):
"""Create a :class:`Chart <highcharts_maps.chart.Chart>` instance from a
:class:`HighchartsOptions <highcharts_maps.options.HighchartsOptions>` or
:class:`HighchartsMapsOptions <highcharts_maps.options.HighchartsMapsOptions>`
object.
:param options: The configuration options to use to instantiate the chart.
:type options:
:class:`HighchartsOptions <highcharts_core.options.HighchartsOptions>` or
related 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 {}
if checkers.is_type(options, 'HighchartsMapsOptions'):
options = options
chart_kwargs['is_maps_chart'] = True
elif checkers.is_type(options, 'HighchartsOptions'):
options = options
elif ('mapNavigation' in options
or 'map_navigation' in options
or 'mapView' in options
or 'map_view' in options):
options = validate_types(options, HighchartsMapsOptions)
chart_kwargs['is_maps_chart'] = True
else:
options = validate_types(options, HighchartsOptions)
instance = cls(**chart_kwargs)
instance.options = options
return instance
@property
def is_async(self) -> bool:
"""Read-only property which indicates whether the data visualization should be
rendered using asynchronous logic.
.. note::
This property will only return ``True`` if one or more series rely on
:class:`AsyncMapData <highcharts_maps.options.series.data.map_data.AsyncMapData>`
:rtype: :class:`bool <python:bool>`
"""
if not self.options or not self.options.series:
return False
if self.options.chart and self.options.chart.is_async:
return True
for series in self.options.series:
if hasattr(series, 'is_async') and series.is_async:
return True
return False
@property
def uses_custom_projection(self) -> bool:
"""Read-only property which indicates whether the map visualization applies a
custom projection.
.. note::
This property will only return ``True`` if the
``options.map_views.projection.custom`` property is set.
:rtype: :class:`bool <python:bool>`
"""
if (not self.options
or not hasattr(self.options, 'map_views')
or not self.options.map_views
or not self.options.map_views.projection):
return False
return self.options.map_views.projection.custom is not None
[docs] def set_map_data(self, map_data):
"""Sets the default :term:`map geometries <map geometry>` for the chart.
:param map_data: The :term:`map geometries <map geometry>` to set. Accepts:
* :class:`MapData <highcharts_maps.options.series.data.map_data.MapData>`
* :class:`AsyncMapData <highcharts_maps.options.series.data.map_data.AsyncMapData>`
* :class:`VariableName <highcharts_maps.utility_classes.javascript_functions.VariableName>`
* :class:`GeoJSONBase <highcharts_maps.utility_classes.geojson.GeoJSONBase>` or
descendant
* :class:`Topology <highcharts_maps.utility_classes.topojson.Topology>`
* a :class:`str <python:str>` URL, which will be coerced to
:class:`AsyncMapData <highcharts_maps.options.series.data.map_data.AsyncMapData>`
:type map_data: :class:`MapData <highcharts_maps.options.series.data.map_data.MapData>` or
:class:`AsyncMapData <highcharts_maps.options.series.data.map_data.AsyncMapData>`
or :obj:`None <python:None>`
"""
if self.options.chart:
self.options.chart.map = map_data
else:
chart_options = ChartOptions(map = map_data)
if self.options:
self.options.chart = chart_options
else:
self.options = HighchartsMapsOptions(chart = chart_options)
[docs] @classmethod
def from_map_data(cls,
map_data,
options_kwargs = None,
chart_kwargs = None):
"""Create a :class:`Chart <highcharts_maps.chart.Chart>` instance from a
:class:`MapData <highcharts_maps.options.series.data.map_data.MapData>` or
:class:`AsyncMapData <highcharts_maps.options.series.data.map_data.AsyncMapData>`
object.
:param map_data: The :term:`map geometries <map geometry>` to set. Accepts:
* :class:`MapData <highcharts_maps.options.series.data.map_data.MapData>`
* :class:`AsyncMapData <highcharts_maps.options.series.data.map_data.AsyncMapData>`
* :class:`VariableName <highcharts_maps.utility_classes.javascript_functions.VariableName>`
* :class:`GeoJSONBase <highcharts_maps.utility_classes.geojson.GeoJSONBase>` or
descendant
* :class:`Topology <highcharts_maps.utility_classes.topojson.Topology>`
* a :class:`str <python:str>` URL, which will be coerced to
:class:`AsyncMapData <highcharts_maps.options.series.data.map_data.AsyncMapData>`
:type map_data: :class:`MapData <highcharts_maps.options.series.data.map_data.MapData>`
or :class:`AsyncMapData <highcharts_maps.options.series.data.map_data.AsyncMapData>`
or :obj:`None <python:None>`
:param options_kwargs: An optional :class:`dict <python:dict>` containing keyword
arguments that should be used when instanting the options for the :class:`Chart`
instance. Defaults to :obj:`None <python:None>`
.. warning::
If ``options_kwargs`` contains a ``chart.map`` setting, that value will
be *overwritten* by the contents of ``map_data``.
: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 instance. Defaults to
:obj:`None <python:None>`.
.. warning::
If ``chart_kwargs`` contains an ``options`` setting, that value will
be *overwritten* by the options implied by ``options_kwargs``
: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>`
"""
options_kwargs = validators.dict(options_kwargs, allow_empty = True) or {}
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
chart_kwargs['is_maps_chart'] = True
options = HighchartsMapsOptions(**options_kwargs)
if not options.chart:
options.chart = ChartOptions()
options.chart.map_data = map_data
instance = cls(**chart_kwargs)
instance.options = options
return instance
[docs] def set_custom_projection(self, projection):
"""Applies a custom map :term:`projection` to the chart.
:param projection: The custom :term:`projection` definition to apply.
:type projection: :class:`CustomProjection <highcharts_maps.utility_classes.projections.CustomProjection>`
.. seealso::
* :ref:`Using Highcharts Maps for Python <using>` > :ref:`Using Custom Projections <custom_projections>`
"""
projection = validate_types(projection, CustomProjection)
if not self.options:
self.options = HighchartsMapsOptions()
if not self.options.map_view:
self.options.map_view = MapViewOptions()
if not self.options.map_view.projection:
self.options.map_view.projection = ProjectionOptions()
self.options.map_view.projection.custom = projection