Skip to content

Commit 99a90d6

Browse files
authored
Merge branch 'develop' into v.1.4-rc-branch
2 parents 1964c09 + 7ff526b commit 99a90d6

File tree

10 files changed

+225
-17
lines changed

10 files changed

+225
-17
lines changed

CHANGES.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ Release 1.4.0
2222
* **BUGFIX:** Fixed instability issues in Jupyter Notebooks, both when operating as a Notebook (outside of
2323
Jupyter Lab) and when saved to a static HTML file (#66).
2424

25+
--------------------
26+
27+
Release 1.3.7
28+
=========================================
29+
30+
* **BUGFIX:** Fixed bug in ``HighchartsMeta.copy()`` (#98).
31+
* **BUGFIX:** Fixed bug in data point serialization to primitive array.
32+
33+
---------------------
34+
35+
Release 1.3.6
36+
=========================================
37+
38+
* **BUGFIX:** Adding missing ``menu...Style`` properties to `Navigation` class (#95).
39+
40+
---------------------
41+
42+
Release 1.3.5
43+
=========================================
44+
45+
* **BUGFIX:** Fixed validation of style properties in the ``Legend`` class (#93).
46+
2547
---------------------
2648

2749

highcharts_core/js_literal_functions.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ def serialize_to_js_literal(item,
8181
continue
8282
else:
8383
raise error
84-
8584
if requires_js_objects:
8685
return [serialize_to_js_literal(x,
8786
encoding = encoding,

highcharts_core/metaclasses.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,8 +718,14 @@ def _copy_dict_key(cls,
718718
719719
:returns: The value that should be placed in ``other`` for ``key``.
720720
"""
721+
if not isinstance(original, (dict, UserDict)):
722+
return original
723+
721724
original_value = original[key]
722-
other_value = other.get(key, None)
725+
if other is None:
726+
other_value = None
727+
else:
728+
other_value = other.get(key, None)
723729

724730
if isinstance(original_value, (dict, UserDict)):
725731
new_value = {}

highcharts_core/options/legend/__init__.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -319,18 +319,24 @@ def floating(self, value):
319319
self._floating = bool(value)
320320

321321
@property
322-
def item_checkbox_style(self) -> Optional[str]:
322+
def item_checkbox_style(self) -> Optional[str | dict]:
323323
"""Default styling for the checkbox next to a legend item when
324324
:meth:`Legend.show_checkbox` is ``True``. Defaults to:
325325
``'{"width": "13px", "height": "13px", "position":"absolute"}'``.
326326
327-
:rtype: :class:`str <python:str>` or :obj:`None <python:None>`
327+
:rtype: :class:`str <python:str>` or :class:`dict <python:dict>` or
328+
:obj:`None <python:None>`
328329
"""
329330
return self._item_checkbox_style
330331

331332
@item_checkbox_style.setter
332333
def item_checkbox_style(self, value):
333-
self._item_checkbox_style = validators.string(value, allow_empty = True)
334+
try:
335+
self._item_checkbox_style = validators.dict(value, allow_empty = True)
336+
except (ValueError, TypeError):
337+
self._item_checkbox_style = validators.string(value,
338+
allow_empty = True,
339+
coerce_value = True)
334340

335341
@property
336342
def item_distance(self) -> Optional[int | float | Decimal]:
@@ -348,7 +354,7 @@ def item_distance(self, value):
348354
minimum = 0)
349355

350356
@property
351-
def item_hidden_style(self) -> Optional[str]:
357+
def item_hidden_style(self) -> Optional[str | dict]:
352358
"""Default styling for the legend item when the corresponding series or data
353359
point is hidden. Defaults to:
354360
``'{"color": "#cccccc"}'``.
@@ -367,10 +373,15 @@ def item_hidden_style(self) -> Optional[str]:
367373

368374
@item_hidden_style.setter
369375
def item_hidden_style(self, value):
370-
self._item_hidden_style = validators.string(value, allow_empty = True)
376+
try:
377+
self._item_hidden_style = validators.dict(value, allow_empty = True)
378+
except (ValueError, TypeError):
379+
self._item_hidden_style = validators.string(value,
380+
allow_empty = True,
381+
coerce_value = True)
371382

372383
@property
373-
def item_hover_style(self) -> Optional[str]:
384+
def item_hover_style(self) -> Optional[str | dict]:
374385
"""Default styling for the legend item when the corresponding series or data
375386
point is in a hover state. Defaults to:
376387
``'{"color": "#000000"}'``.
@@ -383,13 +394,19 @@ def item_hover_style(self) -> Optional[str]:
383394
384395
Properties are inherited from :meth:`Legend.style` unless overridden here.
385396
386-
:rtype: :class:`str <python:str>` or :obj:`None <python:None>`
397+
:rtype: :class:`str <python:str>` or :class:`dict <python:dict>` or
398+
:obj:`None <python:None>`
387399
"""
388400
return self._item_hover_style
389401

390402
@item_hover_style.setter
391403
def item_hover_style(self, value):
392-
self._item_hover_style = validators.string(value, allow_empty = True)
404+
try:
405+
self._item_hover_style = validators.dict(value, allow_empty = True)
406+
except (ValueError, TypeError):
407+
self._item_hover_style = validators.string(value,
408+
allow_empty = True,
409+
coerce_value = True)
393410

394411
@property
395412
def item_margin_bottom(self) -> Optional[int | float | Decimal]:
@@ -422,9 +439,9 @@ def item_margin_top(self, value):
422439
minimum = 0)
423440

424441
@property
425-
def item_style(self) -> Optional[str]:
442+
def item_style(self) -> Optional[str | dict]:
426443
"""Default styling for each legend item. Defaults to:
427-
``'{"color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis"}'``.
444+
``{"color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis"}``.
428445
429446
.. warning::
430447
@@ -442,7 +459,12 @@ def item_style(self) -> Optional[str]:
442459

443460
@item_style.setter
444461
def item_style(self, value):
445-
self._item_style = validators.string(value, allow_empty = True)
462+
try:
463+
self._item_style = validators.dict(value, allow_empty = True)
464+
except (ValueError, TypeError):
465+
self._item_style = validators.string(value,
466+
allow_empty = True,
467+
coerce_value = True)
446468

447469
@property
448470
def item_width(self) -> Optional[int | float | Decimal]:

highcharts_core/options/navigation/__init__.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@ def __init__(self, **kwargs):
2323
self._button_options = None
2424
self._events = None
2525
self._icons_url = None
26+
self._menu_item_hover_style = None
27+
self._menu_item_style = None
28+
self._menu_style = None
2629

2730
self.annotation_options = kwargs.get('annotation_options', None)
2831
self.bindings = kwargs.get('bindings', None)
2932
self.bindings_class_name = kwargs.get('bindings_class_name', None)
3033
self.button_options = kwargs.get('button_options', None)
3134
self.events = kwargs.get('events', None)
3235
self.icons_url = kwargs.get('icons_url', None)
36+
self.menu_item_hover_style = kwargs.get('menu_item_hover_style', None)
37+
self.menu_item_style = kwargs.get('menu_item_style', None)
38+
self.menu_style = kwargs.get('menu_style', None)
3339

3440
super().__init__(**kwargs)
3541

@@ -118,6 +124,74 @@ def icons_url(self) -> Optional[str]:
118124
def icons_url(self, value):
119125
self._icons_url = validators.string(value, allow_empty = True)
120126

127+
@property
128+
def menu_item_hover_style(self) -> Optional[str | dict]:
129+
"""CSS styles for the individual items within the popup menu when the user's mouse hovers over them.
130+
131+
.. note::
132+
133+
Font size defaults to 11px on desktop and 14px on touch devices.
134+
135+
Defaults to:
136+
``{"background": "#f2f2f2" }``.
137+
138+
:rtype: :class:`str <python:str>` or :class:`dict <python:dict>` or :obj:`None <python:None>`
139+
"""
140+
return self._menu_item_hover_style
141+
142+
@menu_item_hover_style.setter
143+
def menu_item_hover_style(self, value):
144+
try:
145+
self._menu_item_hover_style = validators.dict(value, allow_empty = True)
146+
except (ValueError, TypeError):
147+
self._menu_item_hover_style = validators.string(value,
148+
allow_empty = True,
149+
coerce_value = True)
150+
151+
@property
152+
def menu_item_style(self) -> Optional[str | dict]:
153+
"""CSS styles for the individual items within the popup menu.
154+
155+
.. note::
156+
157+
Font size defaults to 11px on desktop and 14px on touch devices.
158+
159+
Defaults to:
160+
``{"padding": "0.5em", "color": "#333333", "background": "none", "borderRadius": "3px", "fontSize": "0.8em", "transition": "background 250ms, color 250ms"}``.
161+
162+
:rtype: :class:`str <python:str>` or :class:`dict <python:dict>` or :obj:`None <python:None>`
163+
"""
164+
return self._menu_item_style
165+
166+
@menu_item_style.setter
167+
def menu_item_style(self, value):
168+
try:
169+
self._menu_item_style = validators.dict(value, allow_empty = True)
170+
except (ValueError, TypeError):
171+
self._menu_item_style = validators.string(value,
172+
allow_empty = True,
173+
coerce_value = True)
174+
175+
@property
176+
def menu_style(self) -> Optional[str | dict]:
177+
"""CSS styles for the popup menu appearing when the popup button is clicked.
178+
179+
Defaults to:
180+
``{"background": "#ffffff", "borderRadius": "3px", "padding": "0.5em"}``.
181+
182+
:rtype: :class:`str <python:str>` or :class:`dict <python:dict>` or :obj:`None <python:None>`
183+
"""
184+
return self._menu_style
185+
186+
@menu_style.setter
187+
def menu_style(self, value):
188+
try:
189+
self._menu_style = validators.dict(value, allow_empty = True)
190+
except (ValueError, TypeError):
191+
self._menu_style = validators.string(value,
192+
allow_empty = True,
193+
coerce_value = True)
194+
121195
@classmethod
122196
def _get_kwargs_from_dict(cls, as_dict):
123197
kwargs = {
@@ -127,6 +201,9 @@ def _get_kwargs_from_dict(cls, as_dict):
127201
'button_options': as_dict.get('buttonOptions', None),
128202
'events': as_dict.get('events', None),
129203
'icons_url': as_dict.get('iconsURL', None),
204+
'menu_item_hover_style': as_dict.get('menuItemHoverStyle', None),
205+
'menu_item_style': as_dict.get('menuItemStyle', None),
206+
'menu_style': as_dict.get('menuStyle', None),
130207
}
131208

132209
return kwargs
@@ -138,7 +215,10 @@ def _to_untrimmed_dict(self, in_cls = None) -> dict:
138215
'bindingsClassName': self.bindings_class_name,
139216
'buttonOptions': self.button_options,
140217
'events': self.events,
141-
'iconsURL': self.icons_url
218+
'iconsURL': self.icons_url,
219+
'menuItemHoverStyle': self.menu_item_hover_style,
220+
'menuItemStyle': self.menu_item_style,
221+
'menuStyle': self.menu_style,
142222
}
143223

144224
return untrimmed
@@ -194,6 +274,9 @@ def _get_kwargs_from_dict(cls, as_dict):
194274
'button_options': as_dict.get('buttonOptions', None),
195275
'events': as_dict.get('events', None),
196276
'icons_url': as_dict.get('iconsURL', None),
277+
'menu_item_hover_style': as_dict.get('menuItemHoverStyle', None),
278+
'menu_item_style': as_dict.get('menuItemStyle', None),
279+
'menu_style': as_dict.get('menuStyle', None),
197280

198281
'breadcrumbs': as_dict.get('breadcrumbs', None),
199282
}

tests/input_files/navigation/navigation/01.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,23 @@
3636
selectButton: function (event) {return true;},
3737
showPopup: function(event) {return true;}
3838
},
39-
iconsURL: 'https://www.somewhere.com/'
39+
iconsURL: 'https://www.somewhere.com/',
40+
menuItemHoverStyle: {
41+
'color': '#5f5e5e',
42+
'fontFamily': 'Roboto',
43+
'fontSize': '12px',
44+
'fontWeight': '400'
45+
},
46+
menuItemStyle: {
47+
'color': '#5f5e5e',
48+
'fontFamily': 'Roboto',
49+
'fontSize': '12px',
50+
'fontWeight': '400'
51+
},
52+
menuStyle: {
53+
'color': '#5f5e5e',
54+
'fontFamily': 'Roboto',
55+
'fontSize': '12px',
56+
'fontWeight': '400'
57+
}
4058
}

tests/options/legend/test_legend.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@
5959
'x': 0,
6060
'y': 0
6161
}, None),
62+
({
63+
'item_style': {
64+
'color': '#5f5e5e',
65+
'fontFamily': 'Roboto',
66+
'fontSize': '12px'
67+
}
68+
}, None),
6269
]
6370

6471

tests/options/navigation/test_navigation.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@
9393
'selectButton': """function (event) {return true;}""",
9494
'showPopup': """function(event) {return true;}"""
9595
},
96-
'icons_url': 'https://www.somewhere.com/'
96+
'icons_url': 'https://www.somewhere.com/',
97+
'menu_item_style': {"fontWeight": "bold", "fontSize": "12px"},
98+
'menu_item_hover_style': {"fontWeight": "bold", "fontSize": "12px"},
99+
'menu_style': {"border-width": "1px"}
97100
}, None),
98101
]
99102

tests/options/series/data/test_base.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Tests for ``highcharts.no_data``."""
22

33
import pytest
4+
from typing import List
45

56
from json.decoder import JSONDecodeError
67

@@ -17,13 +18,38 @@ class NonAbstractDataBase(DataBase):
1718
def from_array(cls, value):
1819
pass
1920

21+
def _get_props_from_array(self) -> List[str]:
22+
"""Returns a list of the property names that can be set using the
23+
:meth:`.from_array() <highcharts_core.options.series.data.base.DataBase.from_array>`
24+
method.
25+
26+
:rtype: :class:`list <python:list>` of :class:`str <python:str>`
27+
"""
28+
return ['fromArrayProp1', 'fromArrayProp2']
29+
30+
31+
2032
cls = NonAbstractDataBase
2133

2234

2335
class RequiringJSObject(NonAbstractDataBase):
2436
def _to_untrimmed_dict(self):
2537
return {'someKey': 123}
2638

39+
class NotRequiringJSObject(NonAbstractDataBase):
40+
def _to_untrimmed_dict(self):
41+
return {
42+
'fromArrayProp1': 456,
43+
'fromArrayProp2': 789
44+
}
45+
46+
47+
class RequiringJSObject2(NonAbstractDataBase):
48+
def _to_untrimmed_dict(self):
49+
return {'someKey': 123,
50+
'fromArrayProp1': 456,
51+
'fromArrayProp2': 789}
52+
2753

2854
STANDARD_PARAMS = [
2955
({}, None),
@@ -102,8 +128,10 @@ def test_from_js_literal(input_files, filename, as_file, error):
102128

103129
@pytest.mark.parametrize('cls, expected', [
104130
(NonAbstractDataBase, False),
131+
(NotRequiringJSObject, False),
105132
(RequiringJSObject, True),
106-
133+
(RequiringJSObject2, True)
134+
107135
])
108136
def test_requires_js_object(cls, expected):
109137
obj = cls()

0 commit comments

Comments
 (0)