Skip to content

Commit 25659b0

Browse files
committed
Added Chart.from_array() support.
1 parent 6886b2d commit 25659b0

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

highcharts_maps/chart.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,111 @@ def add_series(self, *series):
452452

453453
self.options.series = updated_series
454454

455+
@classmethod
456+
def from_array(cls,
457+
value,
458+
series_type = 'line',
459+
series_kwargs = None,
460+
options_kwargs = None,
461+
chart_kwargs = None,
462+
is_maps_chart = False):
463+
"""Create a :class:`Chart <highcharts_core.chart.Chart>` instance with
464+
one series populated from the array contained in ``value``.
465+
466+
.. seealso::
467+
468+
The specific structure of the expected array is highly dependent on the type of data
469+
point that the series needs, which itself is dependent on the series type itself.
470+
471+
Please review the detailed :ref:`series documentation <series_documentation>` for
472+
series type-specific details of relevant array structures.
473+
474+
:param value: The array to use to populate the series data.
475+
:type value: iterable
476+
477+
:param series_type: Indicates the series type that should be created from the array
478+
data. Defaults to ``'line'``.
479+
:type series_type: :class:`str <python:str>`
480+
481+
:param series_kwargs: An optional :class:`dict <python:dict>` containing keyword
482+
arguments that should be used when instantiating the series instance. Defaults
483+
to :obj:`None <python:None>`.
484+
485+
.. warning::
486+
487+
If ``series_kwargs`` contains a ``data`` key, its value will be *overwritten*.
488+
The ``data`` value will be created from ``df`` instead.
489+
490+
:type series_kwargs: :class:`dict <python:dict>`
491+
492+
:param options_kwargs: An optional :class:`dict <python:dict>` containing keyword
493+
arguments that should be used when instantiating the :class:`HighchartsOptions`
494+
instance. Defaults to :obj:`None <python:None>`.
495+
496+
.. warning::
497+
498+
If ``options_kwargs`` contains a ``series`` key, the ``series`` value will be
499+
*overwritten*. The ``series`` value will be created from the data in ``df``.
500+
501+
:type options_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
502+
503+
:param chart_kwargs: An optional :class:`dict <python:dict>` containing keyword
504+
arguments that should be used when instantiating the :class:`Chart` instance.
505+
Defaults to :obj:`None <python:None>`.
506+
507+
.. warning::
508+
509+
If ``chart_kwargs`` contains an ``options`` key, ``options`` will be
510+
*overwritten*. The ``options`` value will be created from the
511+
``options_kwargs`` and the data in ``df`` instead.
512+
513+
:type chart_kwargs: :class:`dict <python:dict>` or :obj:`None <python:None>`
514+
515+
:param is_maps_chart: if ``True``, enforces the use of
516+
:class:`HighchartsStockOptions <highcharts_stock.options.HighchartsStockOptions>`.
517+
If ``False``, applies
518+
:class:`HighchartsOptions <highcharts_stock.options.HighchartsOptions>`.
519+
Defaults to ``False``.
520+
521+
.. note::
522+
523+
The value given to this argument will override any values specified in
524+
``chart_kwargs``.
525+
526+
:type is_maps_chart: :class:`bool <python:bool>`
527+
528+
:returns: A :class:`Chart <highcharts_core.chart.Chart>` instance with its
529+
data populated from the data in ``value``.
530+
:rtype: :class:`Chart <highcharts_core.chart.Chart>`
531+
532+
"""
533+
series_type = validators.string(series_type, allow_empty = False)
534+
series_type = series_type.lower()
535+
if series_type not in SERIES_CLASSES:
536+
raise errors.HighchartsValueError(f'series_type expects a valid Highcharts '
537+
f'series type. Received: {series_type}')
538+
539+
series_kwargs = validators.dict(series_kwargs, allow_empty = True) or {}
540+
options_kwargs = validators.dict(options_kwargs, allow_empty = True) or {}
541+
chart_kwargs = validators.dict(chart_kwargs, allow_empty = True) or {}
542+
543+
series_cls = SERIES_CLASSES.get(series_type, None)
544+
545+
series = series_cls.from_array(value, series_kwargs = series_kwargs)
546+
547+
options_kwargs['series'] = [series]
548+
chart_kwargs['is_maps_chart'] = is_maps_chart
549+
550+
if is_maps_chart:
551+
options = HighchartsMapsOptions(**options_kwargs)
552+
else:
553+
options = HighchartsOptions(**options_kwargs)
554+
555+
instance = cls(**chart_kwargs)
556+
instance.options = options
557+
558+
return instance
559+
455560
@classmethod
456561
def from_series(cls, *series, kwargs = None):
457562
"""Creates a new :class:`Chart <highcharts_core.chart.Chart>` instance populated

tests/test_chart.py

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

33
import pytest
4+
try:
5+
import numpy as np
6+
HAS_NUMPY = True
7+
except ImportError:
8+
HAS_NUMPY = False
49

510
from json.decoder import JSONDecodeError
611

@@ -406,3 +411,65 @@ def test_from_csv(input_files, filename, property_map, kwargs, expected_series,
406411
result = cls.from_csv(input_file,
407412
property_column_map = property_map,
408413
**kwargs)
414+
415+
416+
@pytest.mark.parametrize('value, expected_shape, has_ndarray, has_data_points, error', [
417+
(np.asarray([
418+
[0.0, 15.0],
419+
[10.0, -50.0],
420+
[20.0, -56.5],
421+
[30.0, -46.5],
422+
[40.0, -22.1],
423+
[50.0, -2.5],
424+
[60.0, -27.7],
425+
[70.0, -55.7],
426+
[80.0, -76.5]
427+
]) if HAS_NUMPY else [
428+
[0.0, 15.0],
429+
[10.0, -50.0],
430+
[20.0, -56.5],
431+
[30.0, -46.5],
432+
[40.0, -22.1],
433+
[50.0, -2.5],
434+
[60.0, -27.7],
435+
[70.0, -55.7],
436+
[80.0, -76.5]
437+
], (9, 2), True, False, None),
438+
([
439+
{
440+
'id': 'some-value'
441+
},
442+
{
443+
'id': 'some other value'
444+
},
445+
], (2, 2), False, True, None),
446+
447+
('Not an Array', None, True, False, ValueError),
448+
])
449+
def test_from_array(value, expected_shape, has_ndarray, has_data_points, error):
450+
if has_ndarray is False and has_data_points is False:
451+
raise AssertionError('Test is invalid. has_ndarray or has_data_points must be '
452+
'True. Both were supplied as False.')
453+
if not error:
454+
result = cls.from_array(value)
455+
assert result is not None
456+
assert result.options.series is not None
457+
assert len(result.options.series) == 1
458+
assert result.options.series[0].data is not None
459+
460+
if has_ndarray:
461+
data = result.options.series[0].data
462+
if HAS_NUMPY:
463+
assert data.ndarray is not None
464+
assert data.ndarray.shape == expected_shape
465+
else:
466+
assert data.array is not None or data.data_points is not None
467+
if data.array:
468+
assert len(data.array) == expected_shape[0]
469+
else:
470+
assert len(data.data_points) == expected_shape[0]
471+
if has_data_points:
472+
assert len(result.options.series[0].data) == expected_shape[0]
473+
else:
474+
with pytest.raises(error):
475+
result = cls.from_array(value)

0 commit comments

Comments
 (0)