Skip to content

Commit

Permalink
Merge pull request #157 from highcharts-for-python/enhance/146-add-su…
Browse files Browse the repository at this point in the history
…pport-for-highcharts-core-js-v11-3

Enhance/146 add support for highcharts core js v11 3
  • Loading branch information
hcpchris authored Apr 6, 2024
2 parents 923a195 + fd37cec commit 4d636f7
Show file tree
Hide file tree
Showing 11 changed files with 408 additions and 40 deletions.
10 changes: 10 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
Release 1.6.0
=========================================

* **ENHANCEMENT:** Align the API to **Highcharts (JS) v.11.3** (#146). In particular, this includes:

* Added ``ChartOptions.axis_layout_runs`` property.
* Added ``ColorAxis.height`` property.
* Added ``ColorAxis.width`` property.
* Added ``Data.column_types`` property.
* Added ``Exporting.fetch_options`` property.
* Implemented support for verbose axis date-time unit labelling configuration (see: ``DateTimeLabelFormats``).


* **BUGFIX:** Added support for ``nodeFormat`` and ``nodeFormatter`` to tooltip properties for
diagram series (Organization, Dependency Wheel, and Sankey). (#148)
* **ENHANCEMENT:** Added ability to remove or override the JavaScript event listener when
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ visualization library, with full integration into the robust Python ecosystem, i
dataframe.
* ...and even more use-case specific integrations across the broader toolkit.

The library supports Highcharts (JS) v.10.2 and higher, including Highcharts (JS) v.11.2.0.
The library supports Highcharts (JS) v.10.2 and higher, including Highcharts (JS) v.11.3.0.

**COMPLETE DOCUMENTATION:** https://core-docs.highchartspython.com/en/latest/index.html

Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Highcharts Core for Python

.. sidebar:: Version Compatibility

**Latest Highcharts (JS) version supported:** v.11.2.0
**Latest Highcharts (JS) version supported:** v.11.3.0

**Highcharts Core for Python** is designed to be compatible with:

Expand Down
2 changes: 1 addition & 1 deletion highcharts_core/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ def module_url(self) -> str:
.. warning::
Module paths wlil be appended to this value without checking that
Module paths will be appended to this value without checking that
they resolve to an actual file, e.g. the module
``module/accessibility.js`` will get appended as
``'https://code.highcharts.com/module/accessibility.js'``. Be sure
Expand Down
8 changes: 8 additions & 0 deletions highcharts_core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,4 +953,12 @@ def __repr__(self):

EMPTY_STRING_CONTEXTS = [
'Annotation.draggable',
]


DATA_COLUMN_TYPES = [
'string',
'number',
'float',
'date',
]
60 changes: 58 additions & 2 deletions highcharts_core/options/axes/color_axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,23 +55,27 @@ class ColorAxis(GenericAxis):
def __init__(self, **kwargs):
self._data_class_color = None
self._data_classes = None
self._height = None
self._layout = None
self._line_color = None
self._marker = None
self._max_color = None
self._min_color = None
self._show_in_legend = None
self._stops = None
self._width = None

self.data_class_color = kwargs.get('data_class_color', None)
self.data_classes = kwargs.get('data_classes', None)
self.height = kwargs.get('height', None)
self.layout = kwargs.get('layout', None)
self.line_color = kwargs.get('line_color', None)
self.marker = kwargs.get('marker', None)
self.max_color = kwargs.get('max_color', None)
self.min_color = kwargs.get('min_color', None)
self.show_in_legend = kwargs.get('show_in_legend', None)
self.stops = kwargs.get('stops', None)
self.width = kwargs.get('width', None)

super().__init__(**kwargs)

Expand Down Expand Up @@ -118,6 +122,30 @@ def data_classes(self) -> Optional[List[DataClass]]:
def data_classes(self, value):
self._data_classes = value

@property
def height(self) -> Optional[str | int | float | Decimal]:
"""The height of the color axis, expressed either in pixels or as a
percentage of the total plot height. Defaults to
:obj:`None <python:None>`.
:rtype: numeric or :class:`str <python:str>` or :obj:`None <python:None>`
"""
return self._height

@height.setter
def height(self, value):
if value is None:
self._height = None
else:
try:
value = validators.string(value)
if "%" not in value:
raise ValueError
except (TypeError, ValueError):
value = validators.numeric(value, minimum=0)

self._height = value

@property
def layout(self) -> Optional[str]:
"""The layout of the color axis. Defaults to :obj:`None <python:None>`.
Expand Down Expand Up @@ -273,6 +301,30 @@ def stops(self, value):

self._stops = processed_items

@property
def width(self) -> Optional[str | int | float | Decimal]:
"""The width of the color axis, expressed either in
pixels or as a percentage of the total plot width. Defaults to
:obj:`None <python:None>`.
:rtype: numeric or :class:`str <python:str>` or :obj:`None <python:None>`
"""
return self._width

@width.setter
def width(self, value):
if value is None:
self._width = None
else:
try:
value = validators.string(value)
if "%" not in value:
raise ValueError
except (TypeError, ValueError):
value = validators.numeric(value, minimum=0)

self._width = value

@classmethod
def _get_kwargs_from_dict(cls, as_dict):
kwargs = {
Expand Down Expand Up @@ -330,13 +382,15 @@ def _get_kwargs_from_dict(cls, as_dict):

'data_class_color': as_dict.get('dataClassColor', None),
'data_classes': as_dict.get('dataClasses', None),
'height': as_dict.get('height', None),
'layout': as_dict.get('layout', None),
'line_color': as_dict.get('lineColor', None),
'marker': as_dict.get('marker', None),
'max_color': as_dict.get('maxColor', None),
'min_color': as_dict.get('minColor', None),
'show_in_legend': as_dict.get('showInLegend', None),
'stops': as_dict.get('stops', None)
'stops': as_dict.get('stops', None),
'width': as_dict.get('width', None),
}

return kwargs
Expand Down Expand Up @@ -397,13 +451,15 @@ def _to_untrimmed_dict(self, in_cls = None) -> dict:

'dataClassColor': self.data_class_color,
'dataClasses': self.data_classes,
'height': self.height,
'layout': self.layout,
'lineColor': self.line_color,
'marker': self.marker,
'maxColor': self.max_color,
'minColor': self.min_color,
'showInLegend': self.show_in_legend,
'stops': self.stops
'stops': self.stops,
'width': self.width,
}

return untrimmed
36 changes: 36 additions & 0 deletions highcharts_core/options/chart/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def __init__(self, **kwargs):
self._align_ticks = None
self._allow_mutating_data = None
self._animation = None
self._axis_layout_runs = None
self._background_color = None
self._border_color = None
self._border_radius = None
Expand Down Expand Up @@ -155,6 +156,7 @@ def __init__(self, **kwargs):
self.align_ticks = kwargs.get('align_ticks', None)
self.allow_mutating_data = kwargs.get('allow_mutating_data', None)
self.animation = kwargs.get('animation', None)
self.axis_layout_runs = kwargs.get('axis_layout_runs', None)
self.background_color = kwargs.get('background_color', None)
self.border_color = kwargs.get('border_color', None)
self.border_radius = kwargs.get('border_radius', None)
Expand Down Expand Up @@ -337,6 +339,38 @@ def animation(self, value):
self._animation = validate_types(value,
types = AnimationOptions)

@property
def axis_layout_runs(self) -> Optional[int]:
"""The number of axis layout runs to execute when rendering the chart. Defaults to ``2``.
.. note::
When a chart with an x and a y-axis is rendered, we first pre-render the labels of both
in order to measure them. Then, if either of the axis labels take up so much space that
it significantly affects the length of the other axis, we repeat the process.
By default we stop at two axis layout runs, but it may be that the second run also alters
the space required by either axis, for example if it causes the labels to rotate. In this
situation, a subsequent redraw of the chart may cause the tick and label placement to
change for apparently no reason.
Use the ``.axis_layout_runs`` property to set the maximum allowed number of repetitions.
.. warning::
Keep in mind that the default value of ``2`` is set because every run costs performance time.
Changing this value option to higher than the default might decrease performance significantly,
especially with bigger sets of data.
:returns: The maximum allowed number of axis layout runs.
:rtype: :class:`int <python:int>`
"""
return self._axis_layout_runs

@axis_layout_runs.setter
def axis_layout_runs(self, value):
self._axis_layout_runs = validators.integer(value, allow_empty = True)

@property
def background_color(self) -> Optional[str | Gradient | Pattern]:
"""The background color or gradient for the outer chart area. Defaults to
Expand Down Expand Up @@ -1349,6 +1383,7 @@ def _get_kwargs_from_dict(cls, as_dict):
'align_ticks': as_dict.get('alignTicks', None),
'allow_mutating_data': as_dict.get('allowMutatingData', None),
'animation': as_dict.get('animation', None),
'axis_layout_runs': as_dict.get('axisLayoutRuns', None),
'background_color': as_dict.get('backgroundColor', None),
'border_color': as_dict.get('borderColor', None),
'border_radius': as_dict.get('borderRadius', None),
Expand Down Expand Up @@ -1403,6 +1438,7 @@ def _to_untrimmed_dict(self, in_cls = None) -> dict:
'alignTicks': self.align_ticks,
'allowMutatingData': self.allow_mutating_data,
'animation': self.animation,
'axisLayoutRuns': self.axis_layout_runs,
'backgroundColor': self.background_color,
'borderColor': self.border_color,
'borderRadius': self.border_radius,
Expand Down
44 changes: 44 additions & 0 deletions highcharts_core/options/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def __init__(self, **kwargs):
self._before_parse = None
self._columns = None
self._columns_url = None
self._column_types = None
self._complete = None
self._csv = None
self._csv_url = None
Expand Down Expand Up @@ -59,6 +60,7 @@ def __init__(self, **kwargs):
self.before_parse = kwargs.get('before_parse', None)
self.columns = kwargs.get('columns', None)
self.columns_url = kwargs.get('columns_url', None)
self.column_types = kwargs.get('column_types', None)
self.complete = kwargs.get('complete', None)
self.csv = kwargs.get('csv', None)
self.csv_url = kwargs.get('csv_url', None)
Expand Down Expand Up @@ -185,6 +187,46 @@ def columns_url(self, value):
except ValueError:
raise error

@property
def column_types(self) -> Optional[List[str]]:
"""The data types for each column in the related CSV data.
This property expects an iterable of :class:`str <python:str>` values,
where each item in the iterable corresponds to a column in the underlying
CSV data. The type specified for each column may be one of the following values:
* ``'string'``
* ``'number'``
* ``'float'``
* ``'date'``
Defaults to :obj:`None <python:None>`.
:returns: The data types for each column in the related CSV data.
:rtype: :class:`list <python:list>` of :class:`str <python:str>` or
:obj:`None <python:None>`
"""
return self._column_types

@column_types.setter
def column_types(self, value):
if not value:
self._column_types = None
else:
values = []
value = validators.iterable(value,
forbid_literals = (str, bytes))
for item in value:
item = str(item).lower()
if item not in constants.DATA_COLUMN_TYPES:
raise errors.HighchartsValueError(
f'column_types expects one of {constants.DATA_COLUMN_TYPES}. '
f'Received: {item}'
)
values.append(item)

self._column_types = values

@property
def complete(self) -> Optional[CallbackFunction]:
"""The JavaScript callback function that is evaluated when the data has finished
Expand Down Expand Up @@ -716,6 +758,7 @@ def _get_kwargs_from_dict(cls, as_dict):
'before_parse': as_dict.get('beforeParse', None),
'columns': as_dict.get('columns', None),
'columns_url': as_dict.get('columnsURL', None),
'column_types': as_dict.get('columnTypes', None),
'complete': as_dict.get('complete', None),
'csv': as_dict.get('csv', None),
'csv_url': as_dict.get('csvURL', None),
Expand Down Expand Up @@ -749,6 +792,7 @@ def _to_untrimmed_dict(self, in_cls = None) -> dict:
'beforeParse': self.before_parse,
'columns': self.columns,
'columnsURL': self.columns_url,
'columnTypes': self.column_types,
'complete': self.complete,
'csv': self.csv,
'csvURL': self.csv_url,
Expand Down
22 changes: 22 additions & 0 deletions highcharts_core/options/exporting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def __init__(self, **kwargs):
self._enabled = None
self._error = None
self._fallback_to_export_server = None
self._fetch_options = None
self._filename = None
self._form_attributes = None
self._lib_url = None
Expand All @@ -104,6 +105,7 @@ def __init__(self, **kwargs):
self.enabled = kwargs.get('enabled', None)
self.error = kwargs.get('error', None)
self.fallback_to_export_server = kwargs.get('fallback_to_export_server', None)
self.fetch_options = kwargs.get('fetch_options', None)
self.filename = kwargs.get('filename', None)
self.form_attributes = kwargs.get('form_attributes', None)
self.lib_url = kwargs.get('lib_url', None)
Expand Down Expand Up @@ -339,6 +341,24 @@ def fallback_to_export_server(self, value):
else:
self._fallback_to_export_server = bool(value)

@property
def fetch_options(self) -> Optional[dict]:
"""Options for the fetch request used when sending the SVG to the export server.
Defaults to :obj:`None <python:None>`.
.. seealso::
* `MDN: Fetch <https://developer.mozilla.org/en-US/docs/Web/API/fetch>`__ for more information
:returns: The options for the fetch request, expressed as a Python :class:`dict <python:dict>`
:rtype: :class:`dict <python:dict>` or :obj:`None <python:None>`
"""
return self._fetch_options

@fetch_options.setter
def fetch_options(self, value):
self._fetch_options = validators.dict(value, allow_empty = True)

@property
def filename(self) -> Optional[str]:
"""The filename (without file type extension) to use for the exported chart.
Expand Down Expand Up @@ -717,6 +737,7 @@ def _get_kwargs_from_dict(cls, as_dict):
'enabled': as_dict.get('enabled', None),
'error': as_dict.get('error', None),
'fallback_to_export_server': as_dict.get('fallbackToExportServer', None),
'fetch_options': as_dict.get('fetchOptions', None),
'filename': as_dict.get('filename', None),
'form_attributes': as_dict.get('formAttributes', None),
'lib_url': as_dict.get('libURL', None),
Expand Down Expand Up @@ -748,6 +769,7 @@ def _to_untrimmed_dict(self, in_cls = None) -> dict:
'enabled': self.enabled,
'error': self.error,
'fallbackToExportServer': self.fallback_to_export_server,
'fetchOptions': self.fetch_options,
'filename': self.filename,
'formAttributes': self.form_attributes,
'libURL': self.lib_url,
Expand Down
Loading

0 comments on commit 4d636f7

Please sign in to comment.