import datetime
from typing import Optional, List
from collections import UserDict
try:
import numpy as np
HAS_NUMPY = True
except ImportError:
HAS_NUMPY = False
from validator_collection import checkers, validators, errors as validator_errors
from highcharts_core import constants, errors, utility_functions
from highcharts_core.decorators import validate_types
from highcharts_core.metaclasses import HighchartsMeta
from highcharts_core.js_literal_functions import serialize_to_js_literal, assemble_js_literal
from highcharts_core.options.series.data.base import DataBase
[docs]class DataPointCollection(HighchartsMeta):
"""Collection of data points.
This class stores numerical values that Highcharts can interpret
from a primitive array in a :class:`numpy.ndarray <numpy:numpy.ndarray>`
(in the
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointCollection.ndarray>`
property) and non-numerical data point properties as Highcharts for Python
:class:`DataBase <highcharts_core.options.series.data.base.DataBase>`-descended
objects (in the
:meth:`.data_points <highcharts_core.options.series.data.collections.DataPointCollection.data_points>`
property).
.. note::
When serializing to JS literals, if possible, the collection is serialized to a primitive
array to boost performance within Python *and* JavaScript. However, this may not always be
possible if data points have non-array-compliant properties configured (e.g. adjusting their
style, names, identifiers, etc.). If serializing to a primitive array is not possible, the
results are serialized as JS literal objects.
"""
def __init__(self, **kwargs):
self._array = None
self._ndarray = None
self._data_points = None
self.array = kwargs.get('array', None)
self.ndarray = kwargs.get('ndarray', None)
self.data_points = kwargs.get('data_points', None)
def __getattr__(self, name):
"""Facilitates the retrieval of a 1D array of values from the collection.
The basic logic is as follows:
1. This method is automatically called when an attribute is not found in the
instance.
2. It checks to see whether the attribute is a valid property of the data point
class.
3. If it is, and NumPy is installed, it assembles the array and returns the
dimension indicated by the attribute name. If NumPy is not installed, it
returns a simple list with values as per the attribute name.
4. If ``name`` is not a valid property of the data point class, then it
calls the ``super().__getattribute__()`` method to handle the attribute.
:param name: The name of the attribute to retrieve.
:type name: :class:`str <python:str>`
:returns: The value of the attribute.
:raises AttributeError: If ``name`` is not a valid attribute of the data point
class or the instance.
"""
data_point_properties = self._get_props_from_array()
data_point_class = self._get_data_point_class()
if name in ['_array',
'array',
'_ndarray',
'ndarray',
'_data_points',
'data_points']:
return super().__getattr__(name)
if name in data_point_properties and (
self.ndarray is not None or self.array is not None
):
if HAS_NUMPY and self.ndarray is not None and name in self.ndarray:
return self.ndarray[name]
position = data_point_properties.index(name)
try:
return [x[position] for x in self.array]
except (TypeError, IndexError):
raise AttributeError(name)
data_points = self._assemble_data_points()
as_list = [getattr(x, name, None) for x in data_points]
if HAS_NUMPY:
return np.asarray(as_list)
return as_list
def __setattr__(self, name, value):
"""Updates the collected data values if ``name`` is a valid property of the
data point.
The basic logic is as follows:
1. Check if ``name`` is a valid property of the data point class.
2. If it is not, then call the ``super().__setattr__()`` method to handle
the attribute. End the method.
3. If it is, then check whether the call requires merging into existing
data (as opposed to wholesale overwrite).
4. If merging is required, check whether ``value`` is of the same length
as other existing data. If it is shorter, then pad it with empty values.
If it is longer, then raise an error.
5. If NumPy is supported, then convert ``value`` to a NumPy array. Otherwise,
leave it as is.
6. If NumPy is supported and an array is present, replace the corresponding
slice with the new value.Otherwise, reconstitute the resulting array with
new values.
7. If no array is supported, then set the corresponding property on the data
points.
"""
if name.startswith('_'):
super().__setattr__(name, value)
return
elif name in ['array', 'ndarray', 'data_points']:
super().__setattr__(name, value)
return
data_point_properties = self._get_props_from_array()
try:
has_ndarray = self.ndarray is not None
has_array = self.array is not None
has_data_points = self.data_points is not None
except AttributeError:
has_ndarray = False
has_array = False
has_data_points = False
if name in data_point_properties and has_ndarray and name != 'name':
index = data_point_properties.index(name)
is_arraylike = utility_functions.is_arraylike(value)
array_dict = self.ndarray.copy()
# if value is not an array
if not is_arraylike:
value = np.full((self.ndarray_length, 1), value)
extend_ndarray = len(value) > self.ndarray_length
extend_value = len(value) < self.ndarray_length
# if value has more members (values) than the existing ndarray
if extend_ndarray:
for key in self.ndarray:
if key == name:
continue
array_dict[key] = utility_functions.lengthen_array(array_dict[key],
members = len(value))
array_dict[name] = value
# if value has fewer members (values) than the existing ndarray
elif extend_value:
value = utility_functions.lengthen_array(value,
members = self.ndarray_length)
array_dict[name] = value
self._ndarray = array_dict
elif name in data_point_properties and has_array and name != 'name':
index = data_point_properties.index(name)
is_arraylike = utility_functions.is_arraylike(value)
# if value is not an array
if not is_arraylike:
value = [value for x in range(len(self.array))]
if len(value) > len(self.array):
self.array.extend([[] for x in range(len(value) - len(self.array))])
elif len(value) < len(self.array):
value.extend([None for x in range(len(self.array) - len(value))])
array = []
for row_index, inner_array in enumerate(self.array):
revised_array = [x for x in inner_array]
revised_array = utility_functions.extend_columns(revised_array,
index + 1)
row_value = value[row_index]
if utility_functions.is_iterable(row_value):
revised_array[index] = row_value[index]
else:
revised_array[index] = row_value
array.append(revised_array)
self.array = array
elif name in data_point_properties and has_data_points:
is_arraylike = utility_functions.is_arraylike(value)
if not is_arraylike:
value = np.full((len(self.data_points), 1), value)
if len(self.data_points) < len(value):
missing = len(value) - len(self.data_points)
for i in range(missing):
data_point_cls = self._get_data_point_class()
empty_data_point = data_point_cls()
self.data_points.append(empty_data_point)
if len(value) < len(self.data_points):
value = utility_functions.lengthen_array(value,
members = len(self.data_points))
for i in range(len(self.data_points)):
if hasattr(value[i], 'item'):
checked_value = value[i].item()
else:
checked_value = value[i]
try:
setattr(self.data_points[i], name, checked_value)
except validator_errors.CannotCoerceError as error:
if isinstance(checked_value, str) and ',' in checked_value:
checked_value = checked_value.replace(',', '')
setattr(self.data_points[i], name, checked_value)
elif checkers.is_numeric(checked_value):
checked_value = str(checked_value)
setattr(self.data_points[i], name, checked_value)
else:
raise error
elif name in data_point_properties and name == 'name':
index = data_point_properties.index(name)
is_iterable = not isinstance(value,
(str, bytes, dict, UserDict)) and hasattr(value,
'__iter__')
if is_iterable:
as_list = []
for i in range(len(value)):
if HAS_NUMPY:
if name != 'name' and data_point_properties[-1] == 'name':
inner_list = [np.nan for x in data_point_properties[:-1]]
else:
inner_list = [np.nan for x in data_point_properties]
else:
if name != 'name' and data_point_properties[-1] == 'name':
inner_list = [None for x in data_point_properties[:-1]]
else:
inner_list = [None for x in data_point_properties]
if index < len(inner_list):
inner_list[index] = value[i]
as_list.append(inner_list)
else:
if name != 'name' and data_point_properties[-1] == 'name':
as_list = [None for x in data_point_properties[:-1]]
else:
as_list = [None for x in data_point_properties]
as_list[index] = value
if HAS_NUMPY:
self.ndarray = as_list
else:
self.array = as_list
elif utility_functions.is_arraylike(value):
if not has_data_points:
data_point_cls = self._get_data_point_class()
data_points = [data_point_cls() for x in value]
for index in range(len(data_points)):
try:
setattr(data_points[index], name, value[index])
except validator_errors.CannotCoerceError:
if 'datetime64' in value[index].__class__.__name__:
try:
coerced_value = value[index].astype(datetime.datetime)
IS_DATETIME = True
except (ValueError, TypeError):
IS_DATETIME = False
else:
IS_DATETIME = False
if IS_DATETIME:
setattr(data_points[index], name, coerced_value)
elif isinstance(value[index], str) and ',' in value[index]:
coerced_value = value[index].replace(',', '')
setattr(data_points[index], name, coerced_value)
elif checkers.is_numeric(value[index]) or (
HAS_NUMPY and isinstance(value[index], np.number)
):
coerced_value = str(value[index])
setattr(data_points[index], name, coerced_value)
else:
raise errors.HighchartsValueError(
f'Unable to set {name} to {value[index]}. '
f'If using a helper method, this is likely '
f'due to mismatched columns. Please review '
f'your input data.')
super().__setattr__('data_points', [x for x in data_points])
elif len(value) <= len(self.data_points):
for index in range(len(value)):
setattr(self.data_points[index], name, value[index])
else:
cut_off = len(self.data_points)
data_point_cls = self._get_data_point_class()
for index in range(cut_off):
setattr(self.data_points[index], name, value[index])
for index in range(len(value[cut_off:])):
data_point = data_point_cls()
setattr(data_point, name, value[index])
self.data_points.append(data_point)
elif name == 'name':
if not has_data_points:
data_point_cls = self._get_data_point_class()
if has_ndarray:
length = self.ndarray_length
elif has_array:
length = len(self.array)
else:
length = 1
data_points = [data_point_cls() for x in range(length)]
for index in range(len(data_points)):
setattr(data_points[index], name, value)
super().__setattr__('data_points', [x for x in data_points])
else:
for index in range(len(value)):
setattr(self.data_points[index], name, value[index])
else:
super().__setattr__(name, value)
def __len__(self):
"""Returns the number of data points in the collection.
:rtype: :class:`int <python:int>`
"""
if self.ndarray is not None:
result = self.ndarray_length
elif self.array:
result = len(self.array)
elif self.data_points:
result = len(self.data_points)
else:
result = 0
return result
def __iter__(self):
self._current_index = 0
return iter(self.to_array(force_object = True))
def __next__(self):
if self._current_index < len(self):
x = self.to_array(force_object = True)[self._current_index]
self._current_index += 1
return x
raise StopIteration
def __bool__(self):
return len(self) > 0
@property
def array(self) -> Optional[List]:
"""Primitive collection of values for data points in the collection. Used if
`NumPy <https://www.numpy.org>`__ is not available. Defaults to
:obj:`None <python:None>`.
.. note::
If `NumPy <https://www.numpy.org>`__ is availalbe, will instead behave as
an alias for
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointCollection.ndarray>`
:rtype: :class:`list <python:list>` or :obj:`None <python:None>`
"""
return self._array
@array.setter
def array(self, value):
if not value:
self._array = None
elif utility_functions.is_iterable(value):
self._array = [x for x in value]
else:
raise errors.HighchartsValueError(f'.array requires an iterable value. '
f'Received: {value}')
@property
def data_points(self) -> Optional[List[DataBase]]:
"""The collection of data points for the series. Defaults to
:obj:`None <python:None>`.
:rtype: :class:`list <python:list>` of :class:`DataBase` or
:obj:`None <python:None>`
"""
return self._data_points
@data_points.setter
def data_points(self, value):
if not value:
self._data_points = None
else:
validated = validate_types(value,
types = self._get_data_point_class(),
force_iterable = True)
if not checkers.is_iterable(validated, forbid_literals = (str,
bytes,
dict,
UserDict)):
validated = [validated]
super().__setattr__('_data_points', validated)
@property
def ndarray(self):
"""A :class:`dict <python:dict>` whose keys correspond to data point properties,
and whose values are :class:`numpy.ndarray <numpy:numpy.ndarray>` instances that
contain the data point collection's values.
:rtype: :class:`dict <python:dict>` or :obj:`None <python:None>`
"""
return self._ndarray
@ndarray.setter
def ndarray(self, value):
def raise_unsupported_dimension_error(length):
supported_dimensions = self._get_supported_dimensions()
supported_as_str = ', '.join([str(x) for x in supported_dimensions[:-1]])
supported_as_str += f', or {str(supported_dimensions[-1])}'
raise errors.HighchartsValueError(f'{self.__name__} supports arrays with '
f'{supported_as_str} dimensions. Received'
f' a value with: {length}')
is_iterable = not isinstance(value,
(str, bytes, dict, UserDict)) and hasattr(value,
'__iter__')
if value is None:
self._ndarray = None
as_array = False
elif HAS_NUMPY and not isinstance(value, np.ndarray) and is_iterable:
length = len(value[0])
for item in value:
if len(item) not in self._get_supported_dimensions():
raise_unsupported_dimension_error(len(item))
props_from_array = self._get_props_from_array(length = length)
as_dict = {}
for index, prop in enumerate(props_from_array):
prop_value = [x[index] for x in value]
as_dict[prop] = utility_functions.to_ndarray(prop_value)
as_array = utility_functions.to_ndarray(value)
else:
as_array = value
if HAS_NUMPY and isinstance(as_array, np.ndarray):
dimensions = as_array.ndim
supported_dimensions = self._get_supported_dimensions()
if dimensions not in supported_dimensions:
dimensions = as_array.ndim + 1
if dimensions not in supported_dimensions:
raise_unsupported_dimension_error(dimensions)
props_from_array = self._get_props_from_array(length = dimensions)
if props_from_array and props_from_array[-1] != 'name':
props_from_array.append('name')
as_dict = {}
for index, prop in enumerate(props_from_array):
try:
as_dict[prop] = as_array[:, index]
except IndexError as error:
if index == len(props_from_array) - 1 and prop == 'name':
pass
else:
raise error
self._ndarray = as_dict
elif value is not None:
raise errors.HighchartsValueError(f'.ndarray expects a numpy.ndarray '
f'or an iterable that can easily be '
f'coerced to one. Received: '
f'{value.__class__.__name__}')
@classmethod
def _get_data_point_class(cls):
"""The Python class to use as the underlying data point within the Collection.
:rtype: class object
"""
return DataBase
@classmethod
def _get_supported_dimensions(cls) -> List[int]:
"""The number of dimensions supported by the collection.
:rtype: :class:`list <python:list>` of :class:`int <python:int>`
"""
dimensions = cls._get_data_point_class()._get_supported_dimensions()
last_dimension = dimensions[-1]
data_point_properties = cls._get_props_from_array()
if 'name' not in data_point_properties or len(data_point_properties) > last_dimension:
dimensions.append(last_dimension + 1)
return dimensions
@classmethod
def _get_props_from_array(cls, length = None) -> List[str]:
"""Returns a list of the property names that can be set using the
:meth:`.from_array() <highcharts_core.options.series.data.base.DataBase.from_array>`
method.
:param length: The length of the array, which may determine the properties to
parse. Defaults to :obj:`None <python:None>`, which returns the full list of
properties.
:type length: :class:`int <python:int>` or :obj:`None <python:None>`
:rtype: :class:`list <python:list>` of :class:`str <python:str>`
"""
data_point_cls = cls._get_data_point_class()
return data_point_cls._get_props_from_array(length)
[docs] @classmethod
def from_array(cls, value):
"""Creates a
:class:`DataPointCollection <highcharts_core.options.series.data.collections.DataPointCollection>`
instance from an array of values.
:param value: The value that should contain the data which will be converted into
data point instances.
:type value: iterable or :class:`numpy.ndarray <numpy:numpy.ndarray>`
:returns: A single-object collection of data points.
:rtype: :class:`DataPointCollection <highcharts_core.options.series.data.collections.DataPointCollection>`
or :obj:`None <python:None>`
:raises HighchartsDependencyError: if `NumPy <https://numpy.org>`__ is not installed
"""
if HAS_NUMPY and isinstance(value, np.ndarray) and value.dtype != np.dtype('O'):
return cls.from_ndarray(value)
elif HAS_NUMPY and isinstance(value, np.ndarray):
as_list = value.tolist()
else:
as_list = value
data_points = cls._get_data_point_class().from_array(as_list)
return cls(data_points = data_points)
[docs] @classmethod
def from_ndarray(cls, value):
"""Creates a
:class:`DataPointCollection <highcharts_core.options.series.data.collections.DataPointCollection>`
instance from an array of values.
:param value: The value that should contain the data which will be converted into
data point instances.
:type value: :class:`numpy.ndarray <numpy:numpy.ndarray>`
:returns: A single-object collection of data points.
:rtype: :class:`DataPointCollection <highcharts_core.options.series.data.collections.DataPointCollection>`
or :obj:`None <python:None>`
:raises HighchartsDependencyError: if `NumPy <https://numpy.org>`__ is not installed
"""
if not HAS_NUMPY:
raise errors.HighchartsDependencyError('DataPointCollection requires NumPy '
'be installed. The runtime '
'environment does not currently have '
'NumPy installed. Please use the data '
'point pattern instead, or install NumPy'
' using "pip install numpy" or similar.')
if not isinstance(value, np.ndarray):
raise errors.HighchartsValueError(f'Expected a NumPy ndarray instance, but '
f'received: {value.__class__.__name__}')
if value.dtype == np.dtype('O'):
return cls.from_array(value.tolist())
return cls(ndarray = value)
@property
def requires_js_object(self) -> bool:
"""Indicates whether or not the data point *must* be serialized to a JS literal
object or whether it can be serialized to a primitive array.
:returns: ``True`` if the data point *must* be serialized to a JS literal object.
``False`` if it can be serialized to an array.
:rtype: :class:`bool <python:bool>`
"""
if not self.data_points:
return False
from_array_props = [utility_functions.to_camelCase(x)
for x in self._get_props_from_array()]
data_points_as_dict = [x.to_dict() for x in self.data_points]
for data_point in data_points_as_dict:
for prop in from_array_props:
if prop in data_point:
del data_point[prop]
data_points = sum([1 for x in data_points_as_dict if x])
if data_points:
return True
return False
@property
def ndarray_length(self) -> int:
"""The length of the array stored in
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointCollection.ndarray>`.
:rtype: :class:`int <python:int>`
"""
if not self.ndarray:
return 0
return len(self.ndarray[list(self.ndarray.keys())[0]])
def _assemble_data_points(self):
"""Assemble a collection of
:class:`DataBase <highcharts_core.options.series.data.base.DataBase>`-descended
objects from the provided data. The algorithm should be as follows:
1. Take any data points provided in the
:meth:`.data_points <highcharts_core.options.series.data.collections.DataPointCollection.data_points>` property.
2. If the
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointcollection.data_points>`
is empty, return the data points as-is.
3. Strip the data points of properties from the
:meth:`._get_props_from_array() <highcharts_core.options.series.data.collections.DataPointCollection._get_props_from_array>`
method.
4. Populate the data points with property values from
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointcollection.data_points>`.
5. Return the re-hydrated data points.
:rtype: :class:`list <python:list>` of
:class:`DataBase <highcharts_core.options.series.data.base.DataBase>`
instances.
"""
if self.data_points is not None:
data_points = [x for x in self.data_points]
else:
data_points = []
if self.ndarray is None and not self.array:
return data_points
for index, data_point in enumerate(data_points):
for prop in self._get_props_from_array():
if getattr(data_point, prop) is not None:
setattr(data_points[index], prop, None)
if HAS_NUMPY and self.ndarray is not None:
if len(data_points) < self.ndarray_length:
missing = self.ndarray_length - len(data_points)
for i in range(missing):
data_points.append(self._get_data_point_class()())
for index in range(self.ndarray_length):
inner_list = [self.ndarray[key][index] for key in self.ndarray]
data_points[index].populate_from_array(inner_list)
else:
if len(data_points) < len(self.array):
missing = len(self.array) - len(data_points)
for i in range(missing):
data_points.append(self._get_data_point_class()())
for index in range(len(self.array)):
array = self.array[index]
data_points[index].populate_from_array(array)
return data_points
def _assemble_ndarray(self):
"""Assemble a :class:`numpy.ndarray <numpy:numpy.ndarray>` from the contents
of
:meth:`.data_points <highcharts_core.options.series.data.collections.DataPointCollection.data_points>`.
.. warning::
This method will *ignore* properties that Highcharts (JS) cannot support in a
primitive nested array structure.
:returns: A :class:`numpy.ndarray <numpy:numpy.ndarray>` of the data points.
:rtype: :class:`numpy.ndarray <numpy:numpy.ndarray>`
"""
if not self.data_points:
return np.ndarray.empty()
props = self._get_props_from_array()
if props and props[-1] == 'name':
props = props[:-1]
as_list = [[getattr(data_point, x, constants.EnforcedNull)
for x in props]
for data_point in self.data_points]
return utility_functions.to_ndarray(as_list)
[docs] def to_array(self, force_object = False, force_ndarray = False) -> List:
"""Generate the array representation of the data points (the inversion
of
:meth:`.from_array() <highcharts_core.options.series.data.base.DataBase.from_array>`).
.. warning::
If any data points *cannot* be serialized to a JavaScript array,
this method will instead return the untrimmed :class:`dict <python:dict>`
representation of the data points as a fallback.
:param force_object: if ``True``, forces the return of the instance's
untrimmed :class:`dict <python:dict>` representation. Defaults to ``False``.
.. warning::
Values in
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointCollection.ndarray>`
are *ignored* within this operation in favor of data points stored in
:meth:`.data_points <highcharts_core.options.series.data.collections.DataPointCollection.data_points>`.
However, if there are no data points in
:meth:`.data_points <highcharts_core.options.series.data.collections.DataPointCollection.data_points>`
then data point objects will be assembled based on
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointCollection.ndarray>`.
:type force_object: :class:`bool <python:bool>`
:param force_ndarray: if ``True``, forces the return of the instance's
data points as a :class:`numpy.ndarray <numpy:numpy.ndarray>`. Defaults to
``False``.
.. warning::
Properties of any
:meth:`.data_points <highcharts_core.options.series.data.collections.DataPointCollection.data_points>`
are *ignored* within this operation if
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointCollection.ndarray>`
is populated.
However, if
:meth:`.ndarray <highcharts_core.options.series.data.collections.DataPointCollection.ndarray>`
is not populated, then a :class:`numpy.ndarray <numpy:numpy.ndarray>` will
be assembled from values in
:meth:`.data_points <highcharts_core.options.series.data.collections.DataPointCollection.data_points>`
(ignoring properties that Highcharts (JS) cannot interpret as a primitive array).
:type force_ndarray: :class:`bool <python:bool>`
:raises HighchartsValueError: if both `force_object` and `force_ndarray` are
``True``
:returns: The array representation of the data point collection.
:rtype: :class:`list <python:list>`
"""
if force_object and force_ndarray:
raise errors.HighchartsValueError('force_object and force_ndarray cannot '
'both be True')
if self.ndarray is None and not self.array and not self.data_points:
return []
if force_object and self.data_points and not self.array:
return [x for x in self.data_points]
elif force_object and self.ndarray is not None:
return [x for x in self._assemble_data_points()]
elif force_object and self.array is not None:
return [x for x in self._assemble_data_points()]
if force_ndarray and not HAS_NUMPY:
raise errors.HighchartsDependencyError('Cannot force ndarray if NumPy is '
'not available in the runtime '
'environment. Please install using '
'"pip install numpy" or similar.')
elif force_ndarray and self.ndarray is not None:
return utility_functions.from_ndarray(self.ndarray)
elif force_ndarray and self.data_points:
as_ndarray = self._assemble_ndarray()
return utility_functions.from_ndarray(as_ndarray)
if self.ndarray is not None and not self.requires_js_object:
as_list = []
columns = []
for key in self.ndarray:
value = self.ndarray[key]
if utility_functions.is_ndarray(value):
columns.append(utility_functions.from_ndarray(value))
else:
columns.append(value)
as_list = [list(x) for x in zip(*columns)]
return as_list
elif self.array is not None and not self.requires_js_object:
return [x for x in self.array]
if not self.array and self.data_points:
return [x for x in self.data_points]
return [x for x in self._assemble_data_points()]
@classmethod
def _get_kwargs_from_dict(cls, as_dict):
"""Convenience method which returns the keyword arguments used to initialize the
class from a Highcharts Javascript-compatible :class:`dict <python:dict>` object.
:param as_dict: The HighCharts JS compatible :class:`dict <python:dict>`
representation of the object.
:type as_dict: :class:`dict <python:dict>`
:returns: The keyword arguments that would be used to initialize an instance.
:rtype: :class:`dict <python:dict>`
"""
kwargs = {
'array': as_dict.get('array', None),
'ndarray': as_dict.get('ndarray', None),
'data_points': as_dict.get('dataPoints', None),
}
return kwargs
def _to_untrimmed_dict(self, in_cls = None) -> dict:
untrimmed = {
'array': self.array,
'ndarray': self.ndarray,
'dataPoints': self.data_points,
}
return untrimmed
[docs] def to_js_literal(self,
filename = None,
encoding = 'utf-8',
careful_validation = False) -> Optional[str]:
"""Return the object represented as a :class:`str <python:str>` containing the
JavaScript object literal.
:param filename: The name of a file to which the JavaScript object literal should
be persisted. Defaults to :obj:`None <python:None>`
:type filename: Path-like
:param encoding: The character encoding to apply to the resulting object. Defaults
to ``'utf-8'``.
:type encoding: :class:`str <python:str>`
:param careful_validation: if ``True``, will carefully validate JavaScript values
along the way using the
`esprima-python <https://github.com/Kronuz/esprima-python>`__ library. Defaults
to ``False``.
.. warning::
Setting this value to ``True`` will significantly degrade serialization
performance, though it may prove useful for debugging purposes.
:type careful_validation: :class:`bool <python:bool>`
:rtype: :class:`str <python:str>` or :obj:`None <python:None>`
"""
if filename:
filename = validators.path(filename)
untrimmed = self.to_array()
is_ndarray = all([isinstance(x, list) for x in untrimmed])
if not is_ndarray:
as_str = '['
as_str += ','.join([x.to_js_literal(encoding = encoding,
careful_validation = careful_validation)
for x in untrimmed])
as_str += ']'
else:
serialized = serialize_to_js_literal(untrimmed,
encoding = encoding,
careful_validation = careful_validation)
as_str = serialized
if filename:
with open(filename, 'w', encoding = encoding) as file_:
file_.write(as_str)
return as_str