diff --git a/CHANGES.rst b/CHANGES.rst
index 97cf97e1..ecfd34ff 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,20 @@
+Release 1.0.0-rc5
+=========================================
+
+* Bug fixes to Jupyter Labs rendering.
+* Bug fix for timestamp serialization of timezone-naive ``datetime`` objects.
+* Bug fix: typo in Plot Bands serialization.
+* Added null support to color validation.
+* Bug fix in ``style`` deserialization.
+* Bug fix in ``CartesianData.from_array()``.
+* Fixed ``NaN`` handling in ``.load_from_pandas()``.
+* Fixed JSON deserialization in ``.from_array()``.
+* Added support for stylesheet links in Jupyter Labs context.
+* Several bug fixes in JS literal serialization.
+* Major improvements to JavaScript module inclusion.
+
+---------------
+
Release 1.0.0-rc4
=========================================
diff --git a/highcharts_core/__version__.py b/highcharts_core/__version__.py
index 769e8e39..f15ce4a9 100644
--- a/highcharts_core/__version__.py
+++ b/highcharts_core/__version__.py
@@ -1 +1 @@
-__version__ = '1.0.0-rc4'
+__version__ = '1.0.0-rc5'
diff --git a/highcharts_core/constants.py b/highcharts_core/constants.py
index a2fc28a4..86f95ace 100644
--- a/highcharts_core/constants.py
+++ b/highcharts_core/constants.py
@@ -31,37 +31,108 @@ def __eq__(self, other):
'https://code.highcharts.com/highcharts-3d.js',
'https://code.highcharts.com/modules/accessibility.js',
'https://code.highcharts.com/modules/annotations.js',
+ 'https://code.highcharts.com/modules/annotations-advanced.js',
+ 'https://code.highcharts.com/modules/sankey.js',
+ 'https://code.highcharts.com/modules/arc-diagram.js',
'https://code.highcharts.com/modules/boost.js',
'https://code.highcharts.com/modules/broken-axis.js',
+ 'https://code.highcharts.com/modules/bullet.js',
+ 'https://code.highcharts.com/modules/cylinder.js',
'https://code.highcharts.com/modules/data.js',
- 'https://code.highcharts.com/modules/exporting.js',
+ 'https://code.highcharts.com/modules/datagrouping.js',
+ 'https://code.highcharts.com/modules/debugger.js',
+ 'https://code.highcharts.com/modules/dependency-wheel.js',
+ 'https://code.highcharts.com/modules/drag-panes.js',
+ 'https://code.highcharts.com/modules/draggable-points.js',
'https://code.highcharts.com/modules/drilldown.js',
+ 'https://code.highcharts.com/modules/dumbbell.js',
+ 'https://code.highcharts.com/modules/export-data.js',
+ 'https://code.highcharts.com/modules/exporting.js',
'https://code.highcharts.com/modules/funnel.js',
+ 'https://code.highcharts.com/modules/funnel3d.js',
'https://code.highcharts.com/modules/heatmap.js',
+ 'https://code.highcharts.com/modules/item-series.js',
+ 'https://code.highcharts.com/modules/lollipop.js',
+ 'https://code.highcharts.com/modules/networkgraph.js',
'https://code.highcharts.com/modules/no-data-to-display.js',
'https://code.highcharts.com/modules/offline-exporting.js',
- 'https://code.highcharts.com/modules/solid-gauge.js',
+ 'https://code.highcharts.com/modules/oldie.js',
+ 'https://code.highcharts.com/modules/organization.js',
+ 'https://code.highcharts.com/modules/parallel-coordinates.js',
+ 'https://code.highcharts.com/modules/pareto.js',
+ 'https://code.highcharts.com/modules/pyramid3d.js',
'https://code.highcharts.com/modules/series-label.js',
- 'https://code.highcharts.com/modules/treemap.js'
+ 'https://code.highcharts.com/modules/series-on-point.js',
+ 'https://code.highcharts.com/modules/solid-gauge.js',
+ 'https://code.highcharts.com/modules/streamgraph.js',
+ 'https://code.highcharts.com/modules/sunburst.js',
+ 'https://code.highcharts.com/modules/tilemap.js',
+ 'https://code.highcharts.com/modules/timeline.js',
+ 'https://code.highcharts.com/modules/treegraph.js',
+ 'https://code.highcharts.com/modules/treemap.js',
+ 'https://code.highcharts.com/modules/variable-pie.js',
+ 'https://code.highcharts.com/modules/variwide.js',
+ 'https://code.highcharts.com/modules/vector.js',
+ 'https://code.highcharts.com/modules/venn.js',
+ 'https://code.highcharts.com/modules/windbarb.js',
+ 'https://code.highcharts.com/modules/wordcloud.js',
+ 'https://code.highcharts.com/modules/xrange.js',
]
-INCLUDE_STR = """
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+INCLUDE_STR = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
"""
AXIS_TYPES = [
diff --git a/highcharts_core/global_options/language/navigation.py b/highcharts_core/global_options/language/navigation.py
index 6b37641b..a0f8b850 100644
--- a/highcharts_core/global_options/language/navigation.py
+++ b/highcharts_core/global_options/language/navigation.py
@@ -1245,7 +1245,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def time_cycles(self) -> Optional[str]:
diff --git a/highcharts_core/js_literal_functions.py b/highcharts_core/js_literal_functions.py
index 56a3aa36..f43ac272 100644
--- a/highcharts_core/js_literal_functions.py
+++ b/highcharts_core/js_literal_functions.py
@@ -33,7 +33,8 @@ def serialize_to_js_literal(item, encoding = 'utf-8') -> Optional[str]:
elif isinstance(item, bool):
return item
elif checkers.is_string(item):
- return item
+ return_value = item.replace("'", "\\'")
+ return return_value
elif checkers.is_numeric(item) and not isinstance(item, Decimal):
return item
elif isinstance(item, Decimal):
@@ -46,6 +47,8 @@ def serialize_to_js_literal(item, encoding = 'utf-8') -> Optional[str]:
as_dict[key] = serialize_to_js_literal(item[key], encoding = encoding)
return str(as_dict)
elif checkers.is_datetime(item):
+ if not item.tzinfo:
+ item = item.replace(tzinfo = datetime.timezone.utc)
return item.timestamp()
elif checkers.is_date(item):
return f'Date.UTC({item.year}, {item.month - 1}, {item.day})'
@@ -57,6 +60,41 @@ def serialize_to_js_literal(item, encoding = 'utf-8') -> Optional[str]:
return None
+def is_js_object(as_str):
+ """Determine whether ``as_str`` is a JavaScript object.
+
+ :param as_str: The string to evaluate.
+ :type as_str: :class:`str `
+
+ :returns: ``True`` if ``as_str`` is a JavaScript function. ``False`` if not.
+ :rtype: :class:`bool `
+ """
+ expression_item = f'const testName = {as_str}'
+ try:
+ parsed = esprima.parseScript(expression_item)
+ except ParseError:
+ try:
+ parsed = esprima.parseModule(expression_item)
+ except ParseError:
+ return False
+
+ body = parsed.body
+ if not body:
+ return False
+
+ first_item = body[0]
+ if first_item.type != 'VariableDeclaration':
+ return False
+
+ init = first_item.declarations[0].init
+ if not init:
+ return False
+ if init.type in ('ObjectExpression'):
+ return True
+
+ return False
+
+
def attempt_variable_declaration(as_str):
"""Attempt to coerce ``as_str`` to a JavaScript variable declaration form.
@@ -160,13 +198,21 @@ def get_js_literal(item) -> str:
subitem_counter = 0
for subitem in subitems:
subitem_counter += 1
+ if subitem == 'None':
+ subitem = 'null'
as_str += f"""{subitem}"""
if subitem_counter < len(subitems):
as_str += ',\n'
as_str += ']'
elif checkers.is_string(item):
- if item.startswith('{') or item.startswith('[') or item.startswith('Date'):
+ if item.startswith('[') or item.startswith('Date'):
as_str += f"""{item}"""
+ elif item.startswith('{') and item.endswith('}'):
+ if is_js_object(item):
+ as_str += f"""{item}"""
+ elif "'" in item:
+ item = item.replace("'", "\\'")
+ as_str += f'"{item}"'
elif item in string.whitespace:
as_str += f"""`{item}`"""
elif item.startswith == 'HCP: REPLACE-WITH-':
diff --git a/highcharts_core/options/__init__.py b/highcharts_core/options/__init__.py
index 46c453c7..208a1307 100644
--- a/highcharts_core/options/__init__.py
+++ b/highcharts_core/options/__init__.py
@@ -84,7 +84,6 @@ def __init__(self, **kwargs):
self.navigation = kwargs.get('navigation', None)
self.plot_options = kwargs.get('plot_options', None)
self.responsive = kwargs.get('responsive', None)
- self.series = kwargs.get('series', None)
self.subtitle = kwargs.get('subtitle', None)
self.time = kwargs.get('time', None)
self.title = kwargs.get('title', None)
@@ -92,6 +91,7 @@ def __init__(self, **kwargs):
self.x_axis = kwargs.get('x_axis', None)
self.y_axis = kwargs.get('y_axis', None)
+ self.series = kwargs.get('series', None)
@property
def accessibility(self) -> Optional[Accessibility]:
"""Options for configuring accessibility for the chart.
diff --git a/highcharts_core/options/annotations/__init__.py b/highcharts_core/options/annotations/__init__.py
index 5e17d7e2..4e43997d 100644
--- a/highcharts_core/options/annotations/__init__.py
+++ b/highcharts_core/options/annotations/__init__.py
@@ -128,9 +128,9 @@ def draggable(self, value):
if value is None:
self._draggable = None
else:
- value = validators.string(value, allow_empty = True)
+ value = validators.string(value, allow_empty = True) or ''
value = value.lower()
- if value not in ['x', 'xy', 'y']:
+ if value not in ['x', 'xy', 'y', '']:
raise errors.HighchartsValueError(f'draggable must be "x", "xy", "y", '
f'or "". Was: {value}')
self._draggable = value
diff --git a/highcharts_core/options/annotations/label_options.py b/highcharts_core/options/annotations/label_options.py
index ebc44ae1..3cb4b74a 100644
--- a/highcharts_core/options/annotations/label_options.py
+++ b/highcharts_core/options/annotations/label_options.py
@@ -449,7 +449,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def text(self) -> Optional[str]:
diff --git a/highcharts_core/options/axes/labels.py b/highcharts_core/options/axes/labels.py
index a7fde0d8..63d0a361 100644
--- a/highcharts_core/options/axes/labels.py
+++ b/highcharts_core/options/axes/labels.py
@@ -441,7 +441,9 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value,
+ allow_empty = True,
+ coerce_value = True)
@property
def use_html(self) -> Optional[bool]:
@@ -632,7 +634,9 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value,
+ allow_empty = True,
+ coerce_value = True)
@property
def text(self) -> Optional[str]:
diff --git a/highcharts_core/options/axes/numeric.py b/highcharts_core/options/axes/numeric.py
index 0d379d90..9ef2e159 100644
--- a/highcharts_core/options/axes/numeric.py
+++ b/highcharts_core/options/axes/numeric.py
@@ -171,7 +171,7 @@ def categories(self, value):
if not value:
self._categories = None
else:
- self._categories = [validators.string(x) for x in validators.iterable(value)]
+ self._categories = [validators.string(x, allow_empty = True) or '' for x in validators.iterable(value)]
@property
def date_time_label_formats(self) -> Optional[DateTimeLabelFormats]:
@@ -541,7 +541,7 @@ def _to_untrimmed_dict(self, in_cls = None) -> dict:
'opposite': self.opposite,
'pane': self.pane,
'panningEnabled': self.panning_enabled,
- 'plotPands': self.plot_bands,
+ 'plotBands': self.plot_bands,
'plotLines': self.plot_lines,
'reversed': self.reversed,
'reversedStacks': self.reversed_stacks,
diff --git a/highcharts_core/options/axes/title.py b/highcharts_core/options/axes/title.py
index 49b22707..c2ec260c 100644
--- a/highcharts_core/options/axes/title.py
+++ b/highcharts_core/options/axes/title.py
@@ -3,7 +3,7 @@
from validator_collection import validators
-from highcharts_core import errors
+from highcharts_core import errors, constants
from highcharts_core.metaclasses import HighchartsMeta
@@ -208,10 +208,10 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
- def text(self) -> Optional[str]:
+ def text(self) -> Optional[str | constants.EnforcedNullType]:
"""The actual text of the title.
.. note::
@@ -225,7 +225,10 @@ def text(self) -> Optional[str]:
@text.setter
def text(self, value):
- self._text = validators.string(value, allow_empty = True)
+ if isinstance(value, constants.EnforcedNullType):
+ self._text = constants.EnforcedNull
+ else:
+ self._text = validators.string(value, allow_empty = True)
@property
def text_align(self) -> Optional[str]:
diff --git a/highcharts_core/options/axes/x_axis.py b/highcharts_core/options/axes/x_axis.py
index 9aafe663..f77a05a3 100644
--- a/highcharts_core/options/axes/x_axis.py
+++ b/highcharts_core/options/axes/x_axis.py
@@ -4,7 +4,7 @@
from validator_collection import validators
from highcharts_core import errors
-from highcharts_core.decorators import class_sensitive
+from highcharts_core.decorators import class_sensitive, validate_types
from highcharts_core.utility_classes.gradients import Gradient
from highcharts_core.utility_classes.patterns import Pattern
@@ -49,9 +49,13 @@ def crosshair(self) -> Optional[CrosshairOptions]:
return self._crosshair
@crosshair.setter
- @class_sensitive(CrosshairOptions)
def crosshair(self, value):
- self._crosshair = value
+ if isinstance(value, bool):
+ value = {
+ 'enabled': value
+ }
+
+ self._crosshair = validate_types(value, CrosshairOptions)
@property
def height(self) -> Optional[str | int | float | Decimal]:
diff --git a/highcharts_core/options/caption.py b/highcharts_core/options/caption.py
index 9fc10edb..177cce41 100644
--- a/highcharts_core/options/caption.py
+++ b/highcharts_core/options/caption.py
@@ -107,7 +107,9 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value,
+ allow_empty = True,
+ coerce_value = True)
@property
def text(self) -> Optional[str]:
diff --git a/highcharts_core/options/chart/__init__.py b/highcharts_core/options/chart/__init__.py
index 7fc7239d..f1a04321 100644
--- a/highcharts_core/options/chart/__init__.py
+++ b/highcharts_core/options/chart/__init__.py
@@ -759,8 +759,16 @@ def panning(self) -> Optional[PanningOptions]:
return self._panning
@panning.setter
- @class_sensitive(PanningOptions)
def panning(self, value):
+ if value is None:
+ self._panning = None
+ else:
+ if isinstance(value, bool):
+ value = {
+ 'enabled': value
+ }
+ value = validate_types(value, types = PanningOptions)
+
self._panning = value
@property
@@ -1192,7 +1200,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def styled_mode(self) -> Optional[bool]:
diff --git a/highcharts_core/options/legend/bubble_legend.py b/highcharts_core/options/legend/bubble_legend.py
index 6836d5fe..04cb02fc 100644
--- a/highcharts_core/options/legend/bubble_legend.py
+++ b/highcharts_core/options/legend/bubble_legend.py
@@ -139,7 +139,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def x(self) -> Optional[int]:
diff --git a/highcharts_core/options/legend/navigation.py b/highcharts_core/options/legend/navigation.py
index 87827d02..7c2747a9 100644
--- a/highcharts_core/options/legend/navigation.py
+++ b/highcharts_core/options/legend/navigation.py
@@ -132,7 +132,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@classmethod
def _get_kwargs_from_dict(cls, as_dict):
diff --git a/highcharts_core/options/legend/title.py b/highcharts_core/options/legend/title.py
index 1de9c883..3b338cb1 100644
--- a/highcharts_core/options/legend/title.py
+++ b/highcharts_core/options/legend/title.py
@@ -27,7 +27,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def text(self) -> Optional[str]:
diff --git a/highcharts_core/options/loading.py b/highcharts_core/options/loading.py
index bb8fea6b..cc4af4fe 100644
--- a/highcharts_core/options/loading.py
+++ b/highcharts_core/options/loading.py
@@ -87,7 +87,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@classmethod
def _get_kwargs_from_dict(cls, as_dict):
diff --git a/highcharts_core/options/no_data.py b/highcharts_core/options/no_data.py
index 9b698f95..e38c4035 100644
--- a/highcharts_core/options/no_data.py
+++ b/highcharts_core/options/no_data.py
@@ -70,7 +70,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def use_html(self) -> Optional[bool]:
diff --git a/highcharts_core/options/plot_options/series.py b/highcharts_core/options/plot_options/series.py
index 15cfb837..5336cbe1 100644
--- a/highcharts_core/options/plot_options/series.py
+++ b/highcharts_core/options/plot_options/series.py
@@ -1,7 +1,8 @@
+import datetime
from typing import Optional, List
from decimal import Decimal
-from validator_collection import validators
+from validator_collection import validators, checkers
from highcharts_core import errors
from highcharts_core.decorators import class_sensitive, validate_types
@@ -763,7 +764,21 @@ def point_start(self) -> Optional[int | float | Decimal]:
@point_start.setter
def point_start(self, value):
- self._point_start = validators.numeric(value, allow_empty = True)
+ try:
+ value = validators.numeric(value, allow_empty = True)
+ except (TypeError, ValueError) as error:
+ value = validators.datetime(value)
+
+ if hasattr(value, 'timestamp') and value.tzinfo is not None:
+ self._point_start = value.timestamp()
+ elif hasattr(value, 'timestamp'):
+ value = value.replace(tzinfo = datetime.timezone.utc)
+ value = value.timestamp()
+ else:
+ raise error
+
+ self._point_start = value
+
@property
def stacking(self) -> Optional[str]:
diff --git a/highcharts_core/options/responsive.py b/highcharts_core/options/responsive.py
index 57e78dc8..f698ed2f 100644
--- a/highcharts_core/options/responsive.py
+++ b/highcharts_core/options/responsive.py
@@ -154,8 +154,8 @@ def __init__(self, **kwargs):
@property
def chart_options(self) -> Optional[HighchartsMeta]:
- """A full set of chart :class:`Options` to apply as overrides to the general chart
- :class:`Options`. The chart options are applied when the given rule is active, as
+ """A full set of chart :class:`HighchartsOptions` to apply as overrides to the general chart
+ :class:`HighchartsOptions`. The chart options are applied when the given rule is active, as
per :meth:`ResponsiveRules.condition`.
.. note::
@@ -168,15 +168,15 @@ def chart_options(self) -> Optional[HighchartsMeta]:
with two series without an ``id``, will cause the existing chart's two series to
be updated with respective options.
- :rtype: :class:`Options` or :obj:`None `
+ :rtype: :class:`HighchartsOptions` or :obj:`None `
"""
return self._chart_options
@chart_options.setter
def chart_options(self, value):
- from highcharts_core.options import Options
+ from highcharts_core.options import HighchartsOptions
- self._chart_options = validate_types(value, types = Options)
+ self._chart_options = validate_types(value, types = HighchartsOptions)
@property
def condition(self) -> Optional[Condition]:
diff --git a/highcharts_core/options/series/base.py b/highcharts_core/options/series/base.py
index e8783cf2..05e9eb2b 100644
--- a/highcharts_core/options/series/base.py
+++ b/highcharts_core/options/series/base.py
@@ -14,7 +14,7 @@
from validator_collection import validators, checkers
-from highcharts_core import errors, utility_functions
+from highcharts_core import errors, utility_functions, constants
from highcharts_core.decorators import class_sensitive
from highcharts_core.options.plot_options.series import SeriesOptions
from highcharts_core.options.series.data.base import DataBase
@@ -686,7 +686,7 @@ def load_from_pandas(self,
not available in the runtime environment
"""
try:
- from pandas import DataFrame
+ from pandas import DataFrame, isna
except ImportError:
raise errors.HighchartsDependencyError('pandas is not available in the '
'runtime environment. Please install '
@@ -716,6 +716,8 @@ def load_from_pandas(self,
for key in property_map:
map_value = property_map[key]
record_as_dict[key] = record.get(map_value, None)
+ if isna(record_as_dict[key]):
+ record_as_dict[key] = constants.EnforcedNull
records_as_dicts.append(record_as_dict)
self.data = records_as_dicts
diff --git a/highcharts_core/options/series/data/bar.py b/highcharts_core/options/series/data/bar.py
index ec473972..979199b8 100644
--- a/highcharts_core/options/series/data/bar.py
+++ b/highcharts_core/options/series/data/bar.py
@@ -324,6 +324,11 @@ def value(self, value_):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -518,6 +523,11 @@ def x2(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/base.py b/highcharts_core/options/series/data/base.py
index 87e34d0d..1c240d7e 100644
--- a/highcharts_core/options/series/data/base.py
+++ b/highcharts_core/options/series/data/base.py
@@ -385,6 +385,11 @@ def from_array(cls, value):
"""
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/boxplot.py b/highcharts_core/options/series/data/boxplot.py
index 570c6cfc..a6772eaf 100644
--- a/highcharts_core/options/series/data/boxplot.py
+++ b/highcharts_core/options/series/data/boxplot.py
@@ -237,6 +237,11 @@ def whisker_dash_style(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/bullet.py b/highcharts_core/options/series/data/bullet.py
index 5ed889ab..f4a6889a 100644
--- a/highcharts_core/options/series/data/bullet.py
+++ b/highcharts_core/options/series/data/bullet.py
@@ -52,6 +52,11 @@ def target_options(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/cartesian.py b/highcharts_core/options/series/data/cartesian.py
index d307cb87..21a189e0 100644
--- a/highcharts_core/options/series/data/cartesian.py
+++ b/highcharts_core/options/series/data/cartesian.py
@@ -150,6 +150,11 @@ def y(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -164,15 +169,14 @@ def from_array(cls, value):
elif item is None or checkers.is_numeric(item):
as_obj = cls(y = item)
elif checkers.is_iterable(item):
- if len(item) != 2:
+ if len(item) == 2:
+ as_obj = cls(x = item[0], y = item[1])
+ elif len(item) == 1:
+ as_obj = cls(y = item[0])
+ else:
raise errors.HighchartsValueError(f'data expects either a 1D or 2D '
f'collection. Collection received '
f'had {len(item)} dimensions.')
- as_dict = {
- 'x': item[0],
- 'y': item[1]
- }
- as_obj = cls.from_dict(as_dict)
else:
raise errors.HighchartsValueError(f'each data point supplied must either '
f'be a Cartesian Data Point or be '
@@ -276,6 +280,11 @@ def z(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -428,6 +437,11 @@ def value(self, value_):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/connections.py b/highcharts_core/options/series/data/connections.py
index 700b096e..0492e5f4 100644
--- a/highcharts_core/options/series/data/connections.py
+++ b/highcharts_core/options/series/data/connections.py
@@ -50,6 +50,11 @@ def to(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -181,6 +186,11 @@ def from_array(cls, value):
"""
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -283,6 +293,11 @@ def from_array(cls, value):
"""
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -404,6 +419,11 @@ def from_array(cls, value):
"""
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/pie.py b/highcharts_core/options/series/data/pie.py
index 8bdd0b38..8966312c 100644
--- a/highcharts_core/options/series/data/pie.py
+++ b/highcharts_core/options/series/data/pie.py
@@ -116,6 +116,11 @@ def z(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/range.py b/highcharts_core/options/series/data/range.py
index 24925c45..ac9ed9ec 100644
--- a/highcharts_core/options/series/data/range.py
+++ b/highcharts_core/options/series/data/range.py
@@ -162,12 +162,23 @@ def x(self, value):
def from_array(cls, value):
if not value:
return []
- elif not checkers.is_iterable(value):
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
+
+ if not checkers.is_iterable(value):
value = [value]
collection = []
for item in value:
- if checkers.is_type(item, 'AreaRangeData'):
+ if checkers.is_type(item, 'RangeData'):
as_obj = item
elif checkers.is_dict(item):
as_obj = cls.from_dict(item)
diff --git a/highcharts_core/options/series/data/single_point.py b/highcharts_core/options/series/data/single_point.py
index eb60c36f..f8effcf3 100644
--- a/highcharts_core/options/series/data/single_point.py
+++ b/highcharts_core/options/series/data/single_point.py
@@ -160,6 +160,11 @@ def y(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -274,6 +279,11 @@ def value(self, value_):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -387,6 +397,11 @@ def x(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -497,6 +512,11 @@ def label(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
@@ -627,6 +647,11 @@ def connector_width(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/sunburst.py b/highcharts_core/options/series/data/sunburst.py
index ad954868..012052db 100644
--- a/highcharts_core/options/series/data/sunburst.py
+++ b/highcharts_core/options/series/data/sunburst.py
@@ -57,6 +57,11 @@ def sliced(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/treemap.py b/highcharts_core/options/series/data/treemap.py
index 5229a665..4d4dd9c9 100644
--- a/highcharts_core/options/series/data/treemap.py
+++ b/highcharts_core/options/series/data/treemap.py
@@ -127,6 +127,11 @@ def value(self, value_):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/vector.py b/highcharts_core/options/series/data/vector.py
index 6958836e..df0ed6c4 100644
--- a/highcharts_core/options/series/data/vector.py
+++ b/highcharts_core/options/series/data/vector.py
@@ -54,6 +54,11 @@ def length(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/venn.py b/highcharts_core/options/series/data/venn.py
index 2ad7f6cd..46977883 100644
--- a/highcharts_core/options/series/data/venn.py
+++ b/highcharts_core/options/series/data/venn.py
@@ -116,6 +116,11 @@ def value(self, value_):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/data/wordcloud.py b/highcharts_core/options/series/data/wordcloud.py
index e7702f44..4d38810a 100644
--- a/highcharts_core/options/series/data/wordcloud.py
+++ b/highcharts_core/options/series/data/wordcloud.py
@@ -79,6 +79,11 @@ def weight(self, value):
def from_array(cls, value):
if not value:
return []
+ elif checkers.is_string(value):
+ try:
+ value = validators.json(value)
+ except (ValueError, TypeError):
+ pass
elif not checkers.is_iterable(value):
value = [value]
diff --git a/highcharts_core/options/series/labels.py b/highcharts_core/options/series/labels.py
index e95b64a4..9db40b8a 100644
--- a/highcharts_core/options/series/labels.py
+++ b/highcharts_core/options/series/labels.py
@@ -295,7 +295,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@classmethod
def _get_kwargs_from_dict(cls, as_dict):
diff --git a/highcharts_core/options/series/series_generator.py b/highcharts_core/options/series/series_generator.py
index bc74cccd..06caef53 100644
--- a/highcharts_core/options/series/series_generator.py
+++ b/highcharts_core/options/series/series_generator.py
@@ -62,7 +62,7 @@
SERIES_CLASSES = {
'arcdiagram': ArcDiagramSeries,
- 'areaseries': AreaSeries,
+ 'area': AreaSeries,
'arearange': AreaRangeSeries,
'areaspline': AreaSplineSeries,
'areasplinerange': AreaSplineRangeSeries,
diff --git a/highcharts_core/options/subtitle.py b/highcharts_core/options/subtitle.py
index 0840cc62..75961602 100644
--- a/highcharts_core/options/subtitle.py
+++ b/highcharts_core/options/subtitle.py
@@ -92,7 +92,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def text(self) -> Optional[str]:
diff --git a/highcharts_core/options/title.py b/highcharts_core/options/title.py
index 17366478..44d7cd42 100644
--- a/highcharts_core/options/title.py
+++ b/highcharts_core/options/title.py
@@ -105,7 +105,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def text(self) -> Optional[str]:
diff --git a/highcharts_core/options/tooltips.py b/highcharts_core/options/tooltips.py
index 0e66feca..2d9c86ba 100644
--- a/highcharts_core/options/tooltips.py
+++ b/highcharts_core/options/tooltips.py
@@ -759,7 +759,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def use_html(self) -> Optional[bool]:
diff --git a/highcharts_core/utility_classes/data_labels.py b/highcharts_core/utility_classes/data_labels.py
index db94ad2f..84199c5e 100644
--- a/highcharts_core/utility_classes/data_labels.py
+++ b/highcharts_core/utility_classes/data_labels.py
@@ -714,7 +714,7 @@ def style(self) -> Optional[str]:
@style.setter
def style(self, value):
- self._style = validators.string(value, allow_empty = True)
+ self._style = validators.string(value, allow_empty = True, coerce_value = True)
@property
def text_path(self) -> Optional[TextPath]:
diff --git a/highcharts_core/utility_functions.py b/highcharts_core/utility_functions.py
index 0d172953..06767b89 100644
--- a/highcharts_core/utility_functions.py
+++ b/highcharts_core/utility_functions.py
@@ -136,6 +136,8 @@ def validate_color(value):
if not value:
return None
+ elif value.__class__.__name__ == 'EnforcedNullType':
+ return value
elif isinstance(value, (Gradient, Pattern)):
return value
elif isinstance(value, (dict, str)) and ('linearGradient' in value or
@@ -339,6 +341,8 @@ def jupyter_add_script(url, is_last = False):
:rtype: :class:`str `
"""
url = validators.url(url)
+ if url.endswith('.css'):
+ return jupyter_add_link(url, is_last = is_last)
js_str = ''
js_str += """new Promise(function(resolve, reject) {\n"""
@@ -355,6 +359,38 @@ def jupyter_add_script(url, is_last = False):
return js_str
+def jupyter_add_link(url, is_last = False):
+ """Generates the JavaScript code Promise which adds a tag to the Jupyter
+ Lab environment.
+
+ :param url: The URL to use for the link's source.
+ :type url: :class:`str `
+
+ :param is_last: Whether the URL is the last of the promises.
+ :type is_last: :class:`bool `
+
+ :returns: The JavaScript code for adding the link.
+ :rtype: :class:`str `
+ """
+ url = validators.url(url)
+
+ js_str = ''
+ js_str += """new Promise(function(resolve, reject) {\n"""
+ js_str += f""" var existing_tags = document.querySelectorAll("link[href='{url}']");"""
+ js_str += """ if (existing_tags.length == 0) {
+ var link = document.createElement("link");
+ link.onload = resolve;
+ link.onerror = reject;"""
+ js_str += f""" link.href = '{url}';"""
+ js_str += f""" link.rel = 'stylesheet';"""
+ js_str += f""" link.type = 'text/css';"""
+ js_str += """ document.head.appendChild(link);
+ };
+})"""
+
+ return js_str
+
+
def get_retryHighcharts():
"""Retrieve the ``retryHighcharts()`` JavaScript function.
@@ -368,7 +404,7 @@ def get_retryHighcharts():
fn()
return resolve();
} catch (err) {
- if (err instanceof ReferenceError) {
+ if ((err instanceof ReferenceError) || (err instanceof TypeError) || ((err instanceof Error) && (err.message.includes('#13')))) {
if (retriesLeft === 0) {
var target_div = document.getElementById(container);
if (target_div) {
@@ -426,6 +462,6 @@ def prep_js_for_jupyter(js_str,
function_str += """{\n"""
function_str += js_str
function_str += """\n};\n"""
- function_str += f"""retryHighcharts(insertChart_{random_slug}, {container}, {retries}, {retries}, {interval});"""
+ function_str += f"""retryHighcharts(insertChart_{random_slug}, '{container}', {retries}, {retries}, {interval});"""
return function_str