You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

224 lines
7.4 KiB

4 years ago
  1. # Copyright 2015 Bloomberg Finance L.P.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. r"""
  15. ============
  16. Traits Types
  17. ============
  18. .. currentmodule:: bqplot.traits
  19. .. autosummary::
  20. :toctree: _generate/
  21. Date
  22. """
  23. from traitlets import Instance, TraitError, TraitType, Undefined
  24. import traittypes as tt
  25. import numpy as np
  26. import pandas as pd
  27. import warnings
  28. import datetime as dt
  29. import six
  30. import warnings
  31. # Date
  32. def date_to_json(value, obj):
  33. if value is None:
  34. return value
  35. else:
  36. return value.strftime('%Y-%m-%dT%H:%M:%S.%f')
  37. def date_from_json(value, obj):
  38. if value:
  39. return dt.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
  40. else:
  41. return value
  42. date_serialization = dict(to_json=date_to_json, from_json=date_from_json)
  43. class Date(TraitType):
  44. """
  45. A datetime trait type.
  46. Converts the passed date into a string format that can be used to
  47. construct a JavaScript datetime.
  48. """
  49. def validate(self, obj, value):
  50. try:
  51. if isinstance(value, dt.datetime):
  52. return value
  53. if isinstance(value, dt.date):
  54. return dt.datetime(value.year, value.month, value.day)
  55. if np.issubdtype(np.dtype(value), np.datetime64):
  56. # TODO: Fix this. Right now, we have to limit the precision
  57. # of time to microseconds because np.datetime64.astype(datetime)
  58. # returns date values only for precision <= 'us'
  59. value_truncated = np.datetime64(value, 'us')
  60. return value_truncated.astype(dt.datetime)
  61. except Exception:
  62. self.error(obj, value)
  63. self.error(obj, value)
  64. def __init__(self, default_value=dt.datetime.today(), **kwargs):
  65. args = (default_value,)
  66. self.default_value = default_value
  67. super(Date, self).__init__(args=args, **kwargs)
  68. self.tag(**date_serialization)
  69. def array_from_json(value, obj=None):
  70. if value is not None:
  71. # this will accept regular json data, like an array of values, which can be useful it you want
  72. # to link bqplot to other libraries that use that
  73. if isinstance(value, list):
  74. if len(value) > 0 and isinstance(value[0], dict) and 'value' in value[0]:
  75. return np.array([array_from_json(k) for k in value])
  76. else:
  77. return np.array(value)
  78. elif 'value' in value:
  79. try:
  80. ar = np.frombuffer(value['value'], dtype=value['dtype']).reshape(value['shape'])
  81. except AttributeError:
  82. # in some python27/numpy versions it does not like the memoryview
  83. # we go the .tobytes() route, but since i'm not 100% sure memory copying
  84. # is happening or not, we one take this path if the above fails.
  85. ar = np.frombuffer(value['value'].tobytes(), dtype=value['dtype']).reshape(value['shape'])
  86. if value.get('type') == 'date':
  87. assert value['dtype'] == 'float64'
  88. ar = ar.astype('datetime64[ms]')
  89. return ar
  90. def array_to_json(ar, obj=None, force_contiguous=True):
  91. if ar is None:
  92. return None
  93. if ar.dtype.kind in ['O']:
  94. has_strings = False
  95. all_strings = True # empty array we can interpret as an empty list
  96. for el in ar:
  97. if isinstance(el, six.string_types):
  98. has_strings = True
  99. else:
  100. all_strings = False
  101. if all_strings:
  102. ar = ar.astype('U')
  103. else:
  104. if has_strings:
  105. warnings.warn('Your array contains mixed strings and other types')
  106. if ar.dtype.kind in ['S', 'U']: # strings to as plain json
  107. return ar.tolist()
  108. type = None
  109. if ar.dtype.kind == 'O':
  110. # If it's a Timestamp object
  111. istimestamp = np.vectorize(lambda x: isinstance(x, pd.Timestamp))
  112. if np.all(istimestamp(ar)):
  113. ar = ar.astype('datetime64[ms]').astype(np.float64)
  114. type = 'date'
  115. else:
  116. raise ValueError("Unsupported dtype object")
  117. if ar.dtype.kind == 'M':
  118. # since there is no support for int64, we'll use float64 but as ms
  119. # resolution, since that is the resolution the js Date object understands
  120. ar = ar.astype('datetime64[ms]').astype(np.float64)
  121. type = 'date'
  122. if ar.dtype.kind not in ['u', 'i', 'f']: # ints and floats, and datetime
  123. raise ValueError("Unsupported dtype: %s" % (ar.dtype))
  124. if ar.dtype == np.int64: # JS does not support int64
  125. ar = ar.astype(np.int32)
  126. if force_contiguous and not ar.flags["C_CONTIGUOUS"]: # make sure it's contiguous
  127. ar = np.ascontiguousarray(ar)
  128. if not ar.dtype.isnative:
  129. dtype = ar.dtype.newbyteorder()
  130. ar = ar.astype(dtype)
  131. return {'value': memoryview(ar), 'dtype': str(ar.dtype), 'shape': ar.shape, 'type': type}
  132. array_serialization = dict(to_json=array_to_json, from_json=array_from_json)
  133. def array_squeeze(trait, value):
  134. if len(value.shape) > 1:
  135. return np.squeeze(value)
  136. else:
  137. return value
  138. def array_dimension_bounds(mindim=0, maxdim=np.inf):
  139. def validator(trait, value):
  140. dim = len(value.shape)
  141. if dim < mindim or dim > maxdim:
  142. raise TraitError('Dimension mismatch for trait %s of class %s: expected an \
  143. array of dimension comprised in interval [%s, %s] and got an array of shape %s'\
  144. % (trait.name, trait.this_class, mindim, maxdim, value.shape))
  145. return value
  146. return validator
  147. def array_supported_kinds(kinds='biufMSUO'):
  148. def validator(trait, value):
  149. if value.dtype.kind not in kinds:
  150. raise TraitError('Array type not supported for trait %s of class %s: expected a \
  151. array of kind in list %r and got an array of type %s (kind %s)'\
  152. % (trait.name, trait.this_class, list(kinds), value.dtype, value.dtype.kind))
  153. return value
  154. return validator
  155. # DataFrame
  156. def dataframe_from_json(value, obj):
  157. if value is None:
  158. return None
  159. else:
  160. return pd.DataFrame(value)
  161. def dataframe_to_json(df, obj):
  162. if df is None:
  163. return None
  164. else:
  165. return df.to_dict(orient='records')
  166. dataframe_serialization = dict(to_json=dataframe_to_json, from_json=dataframe_from_json)
  167. # dataframe validators
  168. def dataframe_warn_indexname(trait, value):
  169. if value.index.name is not None:
  170. warnings.warn("The '%s' dataframe trait of the %s instance disregards the index name" % (trait.name, trait.this_class))
  171. value = value.reset_index()
  172. return value
  173. # Series
  174. def series_from_json(value, obj):
  175. return pd.Series(value)
  176. def series_to_json(value, obj):
  177. return value.to_dict()
  178. series_serialization = dict(to_json=series_to_json, from_json=series_from_json)
  179. def _array_equal(a, b):
  180. """Really tests if arrays are equal, where nan == nan == True"""
  181. try:
  182. return np.allclose(a, b, 0, 0, equal_nan=True)
  183. except (TypeError, ValueError):
  184. return False