From da6b8d28bda131cb17c97ed02d83711bf1db08a5 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Fri, 31 Mar 2023 17:04:58 -0400 Subject: [PATCH 01/23] Fixed Jupyter Labs rendering. --- highcharts_core/utility_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highcharts_core/utility_functions.py b/highcharts_core/utility_functions.py index 0d172953..5be2a8c7 100644 --- a/highcharts_core/utility_functions.py +++ b/highcharts_core/utility_functions.py @@ -426,6 +426,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 From 071ccce5adfdaffd7210fa5fd8499f6ffd26ad45 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Fri, 31 Mar 2023 17:05:16 -0400 Subject: [PATCH 02/23] Fixed timestamp serialization of timezone naive datetime objects. --- highcharts_core/js_literal_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/highcharts_core/js_literal_functions.py b/highcharts_core/js_literal_functions.py index 56a3aa36..2fa2a4ff 100644 --- a/highcharts_core/js_literal_functions.py +++ b/highcharts_core/js_literal_functions.py @@ -46,6 +46,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})' From be8a714c821cb38f984e1dd214607006adc4434d Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Fri, 31 Mar 2023 17:05:25 -0400 Subject: [PATCH 03/23] Bumped version number. --- highcharts_core/__version__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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' From 79a8cf77f168f82217c50d8bfec35a269b221b27 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Fri, 31 Mar 2023 18:19:21 -0400 Subject: [PATCH 04/23] Fixed JS literal serialization bug when string contains JS placeholder. --- highcharts_core/js_literal_functions.py | 42 ++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/highcharts_core/js_literal_functions.py b/highcharts_core/js_literal_functions.py index 2fa2a4ff..c4b504d7 100644 --- a/highcharts_core/js_literal_functions.py +++ b/highcharts_core/js_literal_functions.py @@ -59,6 +59,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. @@ -167,8 +202,13 @@ def get_js_literal(item) -> str: 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}""" + else: + as_str += f"""'{item}'""" elif item in string.whitespace: as_str += f"""`{item}`""" elif item.startswith == 'HCP: REPLACE-WITH-': From e5b6c15205027cf534a01b9b290cb4c964c34950 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Fri, 31 Mar 2023 18:59:02 -0400 Subject: [PATCH 05/23] Fixed typo in plot_bands serialization. --- highcharts_core/options/axes/numeric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highcharts_core/options/axes/numeric.py b/highcharts_core/options/axes/numeric.py index 0d379d90..b22e2f42 100644 --- a/highcharts_core/options/axes/numeric.py +++ b/highcharts_core/options/axes/numeric.py @@ -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, From 929b46d5627bedad5df63d1949f613afb83c9c4f Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Fri, 31 Mar 2023 18:59:27 -0400 Subject: [PATCH 06/23] Added null support to color validation. --- highcharts_core/utility_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/highcharts_core/utility_functions.py b/highcharts_core/utility_functions.py index 5be2a8c7..10f0ae27 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 From ddba8478cee0652ebe9795d7d9c611a311d81196 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Fri, 31 Mar 2023 18:59:45 -0400 Subject: [PATCH 07/23] Fixed style deserialization. --- .../global_options/language/navigation.py | 2 +- .../options/annotations/label_options.py | 2 +- highcharts_core/options/axes/labels.py | 8 ++++++-- highcharts_core/options/axes/title.py | 2 +- highcharts_core/options/caption.py | 4 +++- highcharts_core/options/chart/__init__.py | 2 +- highcharts_core/options/legend/bubble_legend.py | 2 +- highcharts_core/options/legend/navigation.py | 2 +- highcharts_core/options/legend/title.py | 2 +- highcharts_core/options/loading.py | 2 +- highcharts_core/options/no_data.py | 2 +- highcharts_core/options/plot_options/series.py | 13 ++++++++++++- highcharts_core/options/series/labels.py | 2 +- highcharts_core/options/subtitle.py | 2 +- highcharts_core/options/title.py | 2 +- highcharts_core/options/tooltips.py | 2 +- highcharts_core/utility_classes/data_labels.py | 2 +- 17 files changed, 35 insertions(+), 18 deletions(-) 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/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/title.py b/highcharts_core/options/axes/title.py index 49b22707..8c393bf6 100644 --- a/highcharts_core/options/axes/title.py +++ b/highcharts_core/options/axes/title.py @@ -208,7 +208,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/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..3682b254 100644 --- a/highcharts_core/options/chart/__init__.py +++ b/highcharts_core/options/chart/__init__.py @@ -1192,7 +1192,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..f5d309cf 100644 --- a/highcharts_core/options/plot_options/series.py +++ b/highcharts_core/options/plot_options/series.py @@ -1,3 +1,4 @@ +import datetime from typing import Optional, List from decimal import Decimal @@ -763,7 +764,17 @@ 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: + self._point_start = validators.numeric(value, allow_empty = True) + except (TypeError, ValueError) as error: + 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) + self._point_start = value.timestamp() + else: + raise error + @property def stacking(self) -> Optional[str]: 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/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]: From e457dc16808a0a097df283cf04a5f18a572950dc Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:12:49 -0400 Subject: [PATCH 08/23] Added empty value handling. --- highcharts_core/options/annotations/__init__.py | 4 ++-- highcharts_core/options/axes/numeric.py | 2 +- highcharts_core/options/axes/title.py | 9 ++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) 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/axes/numeric.py b/highcharts_core/options/axes/numeric.py index b22e2f42..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]: diff --git a/highcharts_core/options/axes/title.py b/highcharts_core/options/axes/title.py index 8c393bf6..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 @@ -211,7 +211,7 @@ def style(self, value): 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]: From 33e4a671f88ada800853663b36fec92fca4b7194 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:14:04 -0400 Subject: [PATCH 09/23] Added bool value handling. --- highcharts_core/options/axes/x_axis.py | 10 +++++++--- highcharts_core/options/chart/__init__.py | 10 +++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) 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/chart/__init__.py b/highcharts_core/options/chart/__init__.py index 3682b254..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 From 73a04fc8889e53f747455e32ca04088bf06df09d Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:14:39 -0400 Subject: [PATCH 10/23] Added naive timezone handling for SeriesBase.point_start. --- highcharts_core/options/plot_options/series.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/highcharts_core/options/plot_options/series.py b/highcharts_core/options/plot_options/series.py index f5d309cf..5336cbe1 100644 --- a/highcharts_core/options/plot_options/series.py +++ b/highcharts_core/options/plot_options/series.py @@ -2,7 +2,7 @@ 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 @@ -765,15 +765,19 @@ def point_start(self) -> Optional[int | float | Decimal]: @point_start.setter def point_start(self, value): try: - self._point_start = validators.numeric(value, allow_empty = True) + 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) - self._point_start = value.timestamp() + value = value.timestamp() else: raise error + + self._point_start = value @property From 89df485706dc6bd80c4037390881f75884918c84 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:15:15 -0400 Subject: [PATCH 11/23] Fixed .from_array() 1D data handling bug. --- highcharts_core/options/series/data/cartesian.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/highcharts_core/options/series/data/cartesian.py b/highcharts_core/options/series/data/cartesian.py index d307cb87..6a704468 100644 --- a/highcharts_core/options/series/data/cartesian.py +++ b/highcharts_core/options/series/data/cartesian.py @@ -164,15 +164,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 ' From 7483cb61d785c2b967e1cc642d0e342a160832c6 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:15:46 -0400 Subject: [PATCH 12/23] Fixed JSON deserialization error in RangeData.from_array() --- highcharts_core/options/series/data/range.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/highcharts_core/options/series/data/range.py b/highcharts_core/options/series/data/range.py index 24925c45..248636b8 100644 --- a/highcharts_core/options/series/data/range.py +++ b/highcharts_core/options/series/data/range.py @@ -162,12 +162,18 @@ 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 + + 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) From ebb66ff2ee657bce2beb6399a7f10b932ee4a998 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:16:14 -0400 Subject: [PATCH 13/23] Fixed NaN handling bug in .load_from_pandas(). --- highcharts_core/options/series/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 From d09c474567ad71c393425d9ad6043cf8317d316d Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:16:30 -0400 Subject: [PATCH 14/23] Fixed typo. --- highcharts_core/options/series/series_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 60a9d21227cc25c0b587d69dad2aba8f7ce399be Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:16:50 -0400 Subject: [PATCH 15/23] Modified attribute setting sequence. --- highcharts_core/options/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From d6cafb0d368b5039ca95c7c8be3074f0f436600b Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 10:17:05 -0400 Subject: [PATCH 16/23] Added Highcharts Error #13 handling to Jupyter JS. --- highcharts_core/utility_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/highcharts_core/utility_functions.py b/highcharts_core/utility_functions.py index 10f0ae27..24072940 100644 --- a/highcharts_core/utility_functions.py +++ b/highcharts_core/utility_functions.py @@ -370,7 +370,7 @@ def get_retryHighcharts(): fn() return resolve(); } catch (err) { - if (err instanceof ReferenceError) { + if ((err instanceof ReferenceError) || ((err instanceof Error) && (err.message.includes('#13')))) { if (retriesLeft === 0) { var target_div = document.getElementById(container); if (target_div) { From a0218e0a36b660e61713aa61f9c43db70a7c6280 Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 11:11:25 -0400 Subject: [PATCH 17/23] Fixed JSON deserialization in *.from_array() --- highcharts_core/options/series/data/bar.py | 10 ++++++++ highcharts_core/options/series/data/base.py | 5 ++++ .../options/series/data/boxplot.py | 5 ++++ highcharts_core/options/series/data/bullet.py | 5 ++++ .../options/series/data/cartesian.py | 15 +++++++++++ .../options/series/data/connections.py | 20 +++++++++++++++ highcharts_core/options/series/data/pie.py | 5 ++++ highcharts_core/options/series/data/range.py | 5 ++++ .../options/series/data/single_point.py | 25 +++++++++++++++++++ .../options/series/data/sunburst.py | 5 ++++ .../options/series/data/treemap.py | 5 ++++ highcharts_core/options/series/data/vector.py | 5 ++++ highcharts_core/options/series/data/venn.py | 5 ++++ .../options/series/data/wordcloud.py | 5 ++++ 14 files changed, 120 insertions(+) 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 6a704468..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] @@ -275,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] @@ -427,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 248636b8..ac9ed9ec 100644 --- a/highcharts_core/options/series/data/range.py +++ b/highcharts_core/options/series/data/range.py @@ -167,6 +167,11 @@ def from_array(cls, value): 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] 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] From d23b92c0a60fb8be905c10c045477a9d2df1e92e Mon Sep 17 00:00:00 2001 From: Chris Modzelewski Date: Sat, 1 Apr 2023 13:16:53 -0400 Subject: [PATCH 18/23] Fixed module inclusions. --- highcharts_core/constants.py | 114 ++++++++++++++++++++++++++++------- 1 file changed, 93 insertions(+), 21 deletions(-) diff --git a/highcharts_core/constants.py b/highcharts_core/constants.py index a2fc28a4..4c96deb5 100644 --- a/highcharts_core/constants.py +++ b/highcharts_core/constants.py @@ -31,37 +31,109 @@ 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/arc-diagram.js', + 'https://code.highcharts.com/modules/bellcurve.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' + '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/heatmap.js', + 'https://code.highcharts.com/modules/funnel3d.js', + 'https://code.highcharts.com/modules/histogram.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/sankey.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 = """