from typing import Optional
from validator_collection import validators, checkers
from highcharts_stock.chart import Chart as ChartBase
from highcharts_gantt import constants, errors, utility_functions
from highcharts_gantt.options import (HighchartsOptions,
HighchartsStockOptions,
HighchartsGanttOptions)
from highcharts_gantt.decorators import validate_types
from highcharts_gantt.js_literal_functions import serialize_to_js_literal
from highcharts_gantt.headless_export import ExportServer
from highcharts_gantt.options.series.series_generator import (create_series_obj,
SERIES_CLASSES,
GANTT_SERIES_LIST)
from highcharts_gantt.global_options.shared_options import (SharedGanttOptions,
SharedStockOptions,
SharedOptions)
from highcharts_gantt.options.chart import ChartOptions
[docs]class Chart(ChartBase):
"""Python representation of a Highcharts ``Chart`` object."""
def __init__(self, **kwargs):
self._is_gantt_chart = None
self.is_gantt_chart = kwargs.get('is_gantt_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_gantt_chart:
for item in constants.GANTT_INCLUDE_LIBS:
js_str += utility_functions.jupyter_add_script(item)
js_str += """.then(() => {"""
for item in constants.GANTT_INCLUDE_LIBS:
js_str += """});"""
elif self.is_stock_chart:
for item in constants.STOCK_INCLUDE_LIBS:
js_str += utility_functions.jupyter_add_script(item)
js_str += """.then(() => {"""
for item in constants.STOCK_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,
random_slug = 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 random_slug: The random sequence of characters to append to the container name to ensure uniqueness.
Defaults to :obj:`None <python:None>`
:type random_slug: :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
new_container = container or self.container or 'highcharts_target_div'
if not random_slug:
self.container = new_container
else:
self.container = f'{new_container}_{random_slug}'
if global_options is not None:
global_options = validate_types(global_options,
types = (SharedGanttOptions, SharedStockOptions, 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,
random_slug = random_slug,
retries = retries,
interval = interval)
self.container = original_container
return js_str
def _repr_html_(self):
"""Produce the HTML representation of the chart.
.. note::
Currently includes *all* `Highcharts JS <https://www.highcharts.com/>`__ or
`Highcharts Gantt <https://www.highcharts.com/products/gantt/>`__ 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>`
"""
if self.options.chart:
height = self.options.chart.height or 400
else:
height = 400
container_str = f"""<div id=\"{self.container}\" style=\"width:100%; height:{height};\"></div>\n"""
as_str = self.to_js_literal()
script_str = '<script>\n' + as_str + '\n</script>'
html_str = container_str + script_str
return html_str
@property
def is_gantt_chart(self) -> bool:
"""If ``True``, indicates that the chart should be rendered as a
`Highcharts Gantt <https://www.highcharts.com/products/gantt/>`__ 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_gantt_chart
@is_gantt_chart.setter
def is_gantt_chart(self, value):
self._is_gantt_chart = bool(value)
@property
def options(self) -> Optional[HighchartsOptions | HighchartsGanttOptions | HighchartsStockOptions]:
"""The Python representation of the
`Highcharts Gantt <https://www.highcharts.com/products/gantt/>`__
``options`` `configuration object <https://api.highcharts.com/gantt/>`_
Defaults to :obj:`None <python:None>`.
:rtype: :class:`HighchartsOptions` or :class:`HighchartsGanttOptions` or
:obj:`None <python:None>`
"""
return self._options
@options.setter
def options(self, value):
if not value:
self._options = None
elif self.is_gantt_chart:
self._options = validate_types(value, HighchartsGanttOptions)
self.is_gantt_chart = True
else:
if checkers.is_type(value, 'HighchartsGanttOptions'):
self._options = value
self.is_gantt_chart = True
elif checkers.is_type(value, 'HighchartsOptions'):
self._options = value
elif ('navigator' in value
or 'range_selector' in value
or 'rangeSelector' in value
or 'connectors' in value):
self._options = validate_types(value, HighchartsGanttOptions)
self.is_gantt_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_stock_chart': as_dict.get('is_stock_chart',
None) or as_dict.get('isStockChart', False),
'is_gantt_chart': as_dict.get('is_gantt_chart',
None) or as_dict.get('isGanttChart', 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
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 = """{}"""
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_gantt_chart:
signature = """new Highcharts.ganttChart("""
elif self.is_stock_chart:
signature = """new Highcharts.stockChart("""
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
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
[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>`
"""
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,
format_ = format_,
**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,
format_ = format_,
**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_gantt_chart:
self.options = HighchartsGanttOptions()
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_gantt.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_gantt.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_gantt.chart.Chart>` instance
:rtype: :class:`Chart <highcharts_gantt.chart.Chart>`
"""
kwargs = validators.dict(kwargs, allow_empty = True) or {}
instance = cls(**kwargs)
instance.add_series(series)
@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:`HighchartsGanttOptions`
"""
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 GANTT_SERIES_LIST:
options = HighchartsOptions(**options_kwargs)
else:
options = HighchartsGanttOptions(**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_gantt_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_gantt_chart: If ``True``, indicates that the chart should be
instantiated as a **Highcharts Stock for Python** chart. Defaults to ``False``.
:type is_gantt_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_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_gantt.chart.Chart>` instance from a
:class:`HighchartsOptions <highcharts_gantt.options.HighchartsOptions>` or
:class:`HighchartsGanttOptions <highcharts_gantt.options.HighchartsGanttOptions>`
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, 'HighchartsGanttOptions'):
options = options
chart_kwargs['is_gantt_chart'] = True
elif checkers.is_type(options, 'HighchartsOptions'):
options = options
elif ('navigator' in options
or 'range_selector' in options
or 'rangeSelector' in options
or 'connectors' in options):
options = validate_types(options, HighchartsGanttOptions)
chart_kwargs['is_gantt_chart'] = True
elif chart_kwargs.get('is_gantt_chart', False):
options = validate_types(options, HighchartsGanttOptions)
chart_kwargs['is_gantt_chart'] = True
else:
options = validate_types(options, HighchartsOptions)
instance = cls(**chart_kwargs)
instance.options = options
return instance
[docs] @classmethod
def from_asana(cls,
project_gid,
section_gid = None,
completed_since = None,
use_html_description = True,
personal_access_token = None,
asana_client = None,
api_request_params = None,
connection_kwargs = None,
connection_callback = None,
series_kwargs = None,
options_kwargs = None,
chart_kwargs = None):
"""Create a Gantt :class:`Chart <highcharts_gantt.chart.Chart>`
instance from an `Asana <https://www.asana.com/>`__ project.
.. note::
**Highcharts Gantt for Python** can create an Asana API client for you,
authenticating using the :term:`Personal Access Token`` method supported by
the Asana API. However, if you wish to use the more-involved OAuth2 handshake
process you will need to create your own Asana API client using the
`asana-python <https://pypi.org/project/asana/>`__ library.
The reason for this is because the OAuth2 handshake has various permutations
involving redirects, token refreshes, etc. which are outside the scope of the
**Highcharts Gantt for Python** library, and if you are integrating
**Highcharts Gantt for Python** into a larger application you are likely
already facilitating the OAuth2 dance in a fashion appropriate for your use
case.
:param project_gid: The globally unique ID of the Project whose tasks should be
used to assemble the Gantt chart.
:type project_gid: :class:`str <python:str>`
:param section_gid: The optional unique ID of the section whose tasks should be
used to assemble the Gantt chart. Defaults to :obj:`None <python:None>`, which
returns all tasks in the project.
:type section_gid: :class:`str <python:str>` or :obj:`None <python:None>`
:param completed_since: An optional filter which only returns tasks that have
been completed after this date. Defaults to :obj:`None <python:None>`, which
returns all tasks.
:type completed_since: :class:`datetime <python:datetime.datetime>` or
:obj:`None <python:None>`
:param use_html_description: If ``True``, will use the Asana task's HTML notes
in the data point's
:meth:`.description <highcharts_gantt.options.series.data.gantt.GanttData.description>`
field. If ``False``, will use the non-HTML notes. Defaults to ``True``.
:type use_html_description: :class:`bool <python:bool>`
:param personal_access_token: A Personal Access Token created by Asana.
Defaults to :obj:`None <python:None>`, which tries to determine its value
by looking in the ``ASANA_PERSONAL_ACCESS_TOKEN`` environment variable.
:type personal_access_token: :class:`str <python:str>` or
:obj:`None <python:None>`
:param api_request_params: Collection of additional request parameters to
submit to the Asana API. Defaults to :obj:`None <python:None>`.
:type api_request_params: :class:`dict <python:dict>` or
:obj:`None <python:None>`
:param connection_kwargs: Set of keyword arugments to supply to the
:class:`DataConnection <highcharts_gantt.options.series.data.connect.DataConnection>`
constructor, besides the :meth:`.to <highcharts_gantt.options.series.data.connect.DataConnection.to>` property which is derived from the task. Defaults
to :obj:`None <python:None>`
:type connection_kwargs: :class:`dict <python:dict>` or
:obj:`None <python:None>`
:param connection_callback: A custom Python function or method which accepts two
keyword arguments: ``connection_target`` (which expects the dependency
:class:`dict <python:dict>` object from the Asana task), and ``asana_task``
(which expects the Asana task :class:`dict <pythoN:dict>` object). The
function should return a
:class:`DataConnection <highcharts_gantt.options.series.data.connect.DataConnection>` instance. Defaults to :obj:`None <python:None>`
.. tip::
The ``connection_callback`` argument is useful if you want to customize the
connection styling based on properties included in the Asana task.
:type connection_callback: Callable or :obj:`None <python:None>`
:param series_kwargs: Collection of additional keyword arguments to use when
instantiating the
:class:`GanttSeries <highcharts_gantt.options.series.GanttSeries>` (besides
the ``data`` argument, which will be determined from the Asana tasks).
Defaults to :obj:`None <python:None>`.
: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 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 Gantt :class:`Chart <highcharts_gantt.chart.Chart>` instance
:rtype: :class:`Chart <highcharts_gantt.chart.Chart>`
"""
series_cls = SERIES_CLASSES.get('gantt', None)
options_kwargs = validators.dict(options_kwargs, allow_empty = True) or {}
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
series = series_cls.from_asana(project_gid = project_gid,
section_gid = section_gid,
completed_since = completed_since,
use_html_description = use_html_description,
personal_access_token = personal_access_token,
asana_client = asana_client,
api_request_params = api_request_params,
connection_kwargs = connection_kwargs,
connection_callback = connection_callback,
series_kwargs = series_kwargs)
options = HighchartsGanttOptions(**options_kwargs)
options.series = [series]
instance = cls(**chart_kwargs)
instance.options = options
instance.is_gantt_chart = True
return instance
[docs] @classmethod
def from_monday(cls,
board_id,
api_token = None,
template = None,
property_column_map = None,
connection_kwargs = None,
connection_callback = None,
series_kwargs = None,
options_kwargs = None,
chart_kwargs = None):
"""Create a :class:`Chart <highcharts_gantt.chart.Chart>` instance from a
`Monday.com <https://www.monday.com>`__ work board.
:param board_id: The ID of the Monday.com board whose items should be retrieved
to populate the Gantt series.
:type board_id: :class:`int <python:int>`
:param api_token: The Monday.com API token to use when authenticating your
request against the Monday.com API. Defaults to :obj:`None <python:None>`,
which will then try to determine the token from the ``MONDAY_API_TOKEN``
environment variable.
.. warning::
If no token is either passed to the method *or* found in the
``MONDAY_API_TOKEN`` environment variable, calling this method will raise
an error.
:type api_token: :class:`str <python:str>` or :obj:`None <python:None>`
:param template: The name of a standard Mpnday.com board template supported by
**Highcharts for Python**. If supplied, will override the
``property_column_map`` argument. Defaults to :obj:`None <python:None>`.
.. note::
If ``property_column_map`` is set, the ``template`` argument will be
*ignored* and overridden by ``property_column_map``.
:type template: :class:`str <python:str>` or :obj:`None <python:None>`
:param property_column_map: A :class:`dict <python:dict>` used to map Monday.com
columns to their corresponding
:class:`GanttSeries <highcharts_gantt.options.series.gantt.GanttSeries>`
properties. Keys are expected to be
:class:`GanttSeries <highcharts_gantt.options.series.gantt.GanttSeries>`
properties, while values are expected to be Monday.com column field names.
Defaults to :obj:`None <python:None>`.
.. note::
If ``property_column_map`` is supplied, its settings *override* the
``template`` setting.
:type property_column_map: :class:`dict <python:dict>` or
:obj:`None <python:None>`
:param connection_kwargs: Set of keyword arugments to supply to the
:class:`DataConnection <highcharts_gantt.options.series.data.connect.DataConnection>`
constructor, besides the :meth:`.to <highcharts_gantt.options.series.data.connect.DataConnection.to>`
property which is derived from the task. Defaults to :obj:`None <python:None>`
:type connection_kwargs: :class:`dict <python:dict>` or
:obj:`None <python:None>`
:param connection_callback: A custom Python function or method which accepts two
keyword arguments: ``connection_target`` (which expects the dependency
:class:`dict <python:dict>` object from the Asana task), and ``asana_task``
(which expects the Asana task :class:`dict <pythoN:dict>` object). The
function should return a
:class:`DataConnection <highcharts_gantt.options.series.data.connect.DataConnection>` instance. Defaults to
:obj:`None <python:None>`
.. tip::
The ``connection_callback`` argument is useful if you want to customize the
connection styling based on properties included in the Asana task.
:type connection_callback: Callable or :obj:`None <python:None>`
:param series_kwargs: Collection of additional keyword arguments to use when
instantiating the
:class:`GanttSeries <highcharts_gantt.options.series.GanttSeries>` (besides
the ``data`` argument, which will be determined from the Asana tasks).
Defaults to :obj:`None <python:None>`.
:type series_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:param series_kwargs: Collection of additional keyword arguments to use when
instantiating the
:class:`GanttSeries <highcharts_gantt.options.series.GanttSeries>` (besides
the ``data`` argument, which will be determined from the Asana tasks).
Defaults to :obj:`None <python:None>`.
: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 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 Gantt :class:`Chart <highcharts_gantt.chart.Chart>` instance
:rtype: :class:`GanttSeries <highcharts_gantt.options.series.gantt.GanttSeries>`
:raises HighchartsDependencyError: if the
`monday <https://pypi.org/project/monday/>`__ Python library is not available
in the runtime environment
:raises MondayAuthenticationError: if there is no Monday.com API token supplied
:raises HighchartsValueError: if both ``template`` and ``property_column_map``
are empty
"""
series_cls = SERIES_CLASSES.get('gantt', None)
options_kwargs = validators.dict(options_kwargs, allow_empty = True) or {}
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
series = series_cls.from_monday(board_id = board_id,
api_token = api_token,
template = template,
property_column_map = property_column_map,
connection_kwargs = connection_kwargs,
connection_callback = connection_callback,
series_kwargs = series_kwargs)
options = HighchartsGanttOptions(**options_kwargs)
options.series = [series]
instance = cls(**chart_kwargs)
instance.options = options
instance.is_gantt_chart = True
return instance
[docs] @classmethod
def from_jira(cls,
project_key,
server = None,
jql = None,
username = None,
password_or_token = None,
oauth_dict = None,
client_kwargs = None,
jira_client = None,
connection_kwargs = None,
connection_callback = None,
series_kwargs = None,
options_kwargs = None,
chart_kwargs = None):
"""Create a :class:`Chart <highcharts_gantt.chart.Chart>` instance from an
`Atlassian <https://www.atlassian.com>`__ JIRA project.
.. note::
**Highcharts Gantt for Python** can create a JIRA API client for you,
authenticating using either the :term:`Basic Authentication` or
:term:`Access Token` methods supported by the JIRA API. However, if you wish
to use the more-involved OAuth2 handshake, you can do so yourself and either
* supply an ``oauth_dict`` argument containing the OAuth2 configuration
details, or
* supply a fully-authenticated ``jira_client``
The reason for this is because the OAuth2 handshake has various permutations
involving redirects, token refreshes, etc. which are outside the scope of the
**Highcharts Gantt for Python** library, and if you are integrating
**Highcharts Gantt for Python** into a larger application you are likely
already facilitating the OAuth2 dance in a fashion appropriate for your use
case.
:param project_key: The globally unique key of the Project whose tasks should be
used to assemble the Gantt chart. For example, ``JRA``.
:type project_key: :class:`str <python:str>`
:param server: The URL of the JIRA instance from which data should be retrieved.
Defaults to :obj:`None <python:None>`, which looks for a value in the ``HIGHCHARTS_JIRA_SERVER`` environment
variable. If no value is found there, will then fallback to JIRA Cloud: ``'https://jira.atlasian.com'``.
.. note::
This argument will override the comparable setting in ``client_kwargs`` if
``client_kwargs`` is supplied.
:type server: :class:`str <python:str>` or :obj:`None <python:None>`
:param jql: An optional :term:`JIRA Query Language` query string to further
narrow the issues returned from JIRA. Defaults to :obj:`None <python:None>`.
:type jql: :class:`str <python:str>` or :obj:`None <python:None>`
:param username: The username to use when authenticating using either ``basic``
or ``token`` authentication. Defaults to :obj:`None <python:None>`, which
looks for a value in the ``HIGHCHARTS_JIRA_USERNAME`` environment variable.
.. note::
If ``oauth2_dict`` is supplied, the ``username`` argument will be ignored
since OAuth2 authentication will be used.
:type username: :class:`str <python:str>` or :obj:`None <python:None>`
:param password_or_token: The password or access token to use when
authenticating using either ``basic`` or ``token`` authentication. Defaults
to :obj:`None <python:None>`, which looks for a vlaue in the
``HIGHCHARTS_JIRA_TOKEN`` environment variable.
.. note::
If ``oauth_dict`` is supplied, the ``password_or_token`` will be ignored
since OAuth2 authentication will be used.
:type password_or_token: :class:`str <python:str>` or :obj:`None <python:None>`
:param oauth_dict: A :class:`dict <python:dict>` of key/value pairs providing
configuration of the Oauth2 authentication details. Expected keys are:
* ``'access_token'``
* ``'access_token_secret'``
* ``'consumer_key'``
* ``'key_cert'``
Defaults to :obj:`None <python:None>`.
.. note::
To use OAuth2 authentication, an ``oauth_dict`` *must* be supplied. If you
wish to force either basic or token authentication, make sure this argument
remains :obj:`None <python:None>`.
:type oauth_dict: :class:`dict <python:dict>` or :obj:`None <python:None>`
:param client_kwargs: An optional :class:`dict <python:dict>` providing keyword
arguments to use when instantiating the JIRA client.
:type client_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:param jira_client: A fully-configured and fully-authenticated JIRA API client.
Defaults to :obj:`None <python:None>`.
:type jira_client: :class:`jira.client.JIRA <jira:jira.client.JIRA>` instance
that has been fully authenticated
:param connection_kwargs: Set of keyword arugments to supply to the
:class:`DataConnection <highcharts_gantt.options.series.data.connect.DataConnection>`
constructor, besides the
:meth:`.to <highcharts_gantt.options.series.data.connect.DataConnection.to>`
property which is derived from the task. Defaults to :obj:`None <python:None>`
:type connection_kwargs: :class:`dict <python:dict>` or
:obj:`None <python:None>`
:param connection_callback: A custom Python function or method which accepts two
keyword arguments: ``connection_target`` (which expects the dependency
:class:`Issue <jira:jira.resources.Issue>` object from the initial
:class:`Issue <jira:jira.resources.Issue>`), and ``issue``
(which expects the initial :class:`Issue <jira:jira.resources.Issue>`
object). The function should return a
:class:`DataConnection <highcharts_gantt.options.series.data.connect.DataConnection>`
instance. Defaults to :obj:`None <python:None>`.
.. tip::
The ``connection_callback`` argument is useful if you want to customize the
connection styling based on properties included in the target issue.
:type connection_callback: Callable or :obj:`None <python:None>`
:param series_kwargs: Collection of additional keyword arguments to use when
instantiating the
:class:`GanttSeries <highcharts_gantt.options.series.GanttSeries>` (besides
the ``data`` argument, which will be determined from the JIRA issues).
Defaults to :obj:`None <python:None>`.
:type series_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
:returns: A Gantt :class:`Chart <highcharts_gantt.chart.Chart>` instance
:rtype: :class:`GanttSeries <highcharts_gantt.options.series.gantt.GanttSeries>`
:raises HighchartsDependencyError: if the
`jira <https://pypi.org/project/jira/>`__ Python library is not available
in the runtime environment
:raises JIRAAuthenticationError: if authentication against the JIRA server fails
:raises HighchartsValueError: if both ``template`` and ``property_column_map``
are empty
"""
series_cls = SERIES_CLASSES.get('gantt', None)
options_kwargs = validators.dict(options_kwargs, allow_empty = True) or {}
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
series = series_cls.from_jira(project_key = project_key,
server = server,
jql = jql,
username = username,
password_or_token = password_or_token,
oauth_dict = oauth_dict,
client_kwargs = client_kwargs,
jira_client = jira_client,
connection_kwargs = connection_kwargs,
connection_callback = connection_callback,
series_kwargs = series_kwargs)
options = HighchartsGanttOptions(**options_kwargs)
options.series = [series]
instance = cls(**chart_kwargs)
instance.options = options
instance.is_gantt_chart = True
return instance