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.

1386 lines
46 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. Pyplot
  17. ======
  18. .. currentmodule:: bqplot.pyplot
  19. .. autosummary::
  20. :toctree: _generate/
  21. figure
  22. show
  23. axes
  24. plot
  25. scatter
  26. hist
  27. bar
  28. ohlc
  29. geo
  30. clear
  31. close
  32. current_figure
  33. scales
  34. xlim
  35. ylim
  36. axes
  37. xlabel
  38. ylabel
  39. """
  40. import sys
  41. from collections import OrderedDict
  42. from IPython.display import display
  43. from ipywidgets import VBox
  44. from ipywidgets import Image as ipyImage
  45. from numpy import arange, issubdtype, array, column_stack, shape
  46. from .figure import Figure
  47. from .scales import Scale, LinearScale, Mercator
  48. from .axes import Axis
  49. from .marks import (Lines, Scatter, ScatterGL, Hist, Bars, OHLC, Pie, Map, Image,
  50. Label, HeatMap, GridHeatMap, topo_load, Boxplot, Bins)
  51. from .toolbar import Toolbar
  52. from .interacts import (BrushIntervalSelector, FastIntervalSelector,
  53. BrushSelector, IndexSelector, MultiSelector,
  54. LassoSelector)
  55. from traitlets.utils.sentinel import Sentinel
  56. import functools
  57. SCATTER_SIZE_LIMIT = 10*1000 # above this limit, ScatterGL will be used by default
  58. Keep = Sentinel('Keep', 'bqplot.pyplot', '''
  59. Used in bqplot.pyplot to specify that the same scale should be used for
  60. a certain dimension.
  61. ''')
  62. # `_context` object contains the global information for pyplot.
  63. # `figure`: refers to the current figure to which marks will be added.
  64. # `scales`: The current set of scales which will be used for drawing a mark. if
  65. # the scale for an attribute is not present, it is created based on the range
  66. # type.
  67. # `scale_registry`: This is a dictionary where the keys are the context
  68. # names and the values are the set of scales which were used on the last plot
  69. # in that context. This is useful when switching context.
  70. # `last_mark`: refers to the last mark that has been plotted.
  71. # `current_key`: The key for the current context figure. If there is no key,
  72. # then the value is `None`.
  73. _context = {
  74. 'figure': None,
  75. 'figure_registry': {},
  76. 'scales': {},
  77. 'scale_registry': {},
  78. 'last_mark': None,
  79. 'current_key': None
  80. }
  81. LINE_STYLE_CODES = OrderedDict([(':', 'dotted'), ('-.', 'dash_dotted'),
  82. ('--', 'dashed'), ('-', 'solid')])
  83. COLOR_CODES = {'b': 'blue', 'g': 'green', 'r': 'red', 'c': 'cyan',
  84. 'm': 'magenta', 'y': 'yellow', 'k': 'black'}
  85. MARKER_CODES = {'o': 'circle', 'v': 'triangle-down', '^': 'triangle-up',
  86. 's': 'square', 'd': 'diamond', '+': 'cross'}
  87. PY2 = sys.version_info[0] == 2
  88. if PY2:
  89. string_types = basestring,
  90. else:
  91. string_types = str,
  92. def hashable(data, v):
  93. """Determine whether `v` can be hashed."""
  94. try:
  95. data[v]
  96. except (TypeError, KeyError, IndexError):
  97. return False
  98. return True
  99. def show(key=None, display_toolbar=True):
  100. """Shows the current context figure in the output area.
  101. Parameters
  102. ----------
  103. key : hashable, optional
  104. Any variable that can be used as a key for a dictionary.
  105. display_toolbar: bool (default: True)
  106. If True, a toolbar for different mouse interaction is displayed with
  107. the figure.
  108. Raises
  109. ------
  110. KeyError
  111. When no context figure is associated with the provided key.
  112. Examples
  113. --------
  114. >>> import numpy as np
  115. >>> import pyplot as plt
  116. >>> n = 100
  117. >>> x = np.arange(n)
  118. >>> y = np.cumsum(np.random.randn(n))
  119. >>> plt.plot(x,y)
  120. >>> plt.show()
  121. """
  122. if key is None:
  123. figure = current_figure()
  124. else:
  125. figure = _context['figure_registry'][key]
  126. if display_toolbar:
  127. if not hasattr(figure, 'pyplot'):
  128. figure.pyplot = Toolbar(figure=figure)
  129. figure.pyplot_vbox = VBox([figure, figure.pyplot])
  130. display(figure.pyplot_vbox)
  131. else:
  132. display(figure)
  133. def figure(key=None, fig=None, **kwargs):
  134. """Creates figures and switches between figures.
  135. If a ``bqplot.Figure`` object is provided via the fig optional argument,
  136. this figure becomes the current context figure.
  137. Otherwise:
  138. - If no key is provided, a new empty context figure is created.
  139. - If a key is provided for which a context already exists, the
  140. corresponding context becomes current.
  141. - If a key is provided and no corresponding context exists, a new context
  142. is reated for that key and becomes current.
  143. Besides, optional arguments allow to set or modify Attributes
  144. of the selected context figure.
  145. Parameters
  146. ----------
  147. key: hashable, optional
  148. Any variable that can be used as a key for a dictionary
  149. fig: Figure, optional
  150. A bqplot Figure
  151. """
  152. scales_arg = kwargs.pop('scales', {})
  153. _context['current_key'] = key
  154. if fig is not None: # fig provided
  155. _context['figure'] = fig
  156. if key is not None:
  157. _context['figure_registry'][key] = fig
  158. for arg in kwargs:
  159. setattr(_context['figure'], arg, kwargs[arg])
  160. else: # no fig provided
  161. if key is None: # no key provided
  162. _context['figure'] = Figure(**kwargs)
  163. else: # a key is provided
  164. if key not in _context['figure_registry']:
  165. if 'title' not in kwargs:
  166. kwargs['title'] = 'Figure' + ' ' + str(key)
  167. _context['figure_registry'][key] = Figure(**kwargs)
  168. _context['figure'] = _context['figure_registry'][key]
  169. for arg in kwargs:
  170. setattr(_context['figure'], arg, kwargs[arg])
  171. scales(key, scales=scales_arg)
  172. # Set the axis reference dictionary. This dictionary contains the mapping
  173. # from the possible dimensions in the figure to the list of scales with
  174. # respect to which axes have been drawn for this figure.
  175. # Used to automatically generate axis.
  176. if(getattr(_context['figure'], 'axis_registry', None) is None):
  177. setattr(_context['figure'], 'axis_registry', {})
  178. return _context['figure']
  179. def close(key):
  180. """Closes and unregister the context figure corresponding to the key.
  181. Parameters
  182. ----------
  183. key: hashable
  184. Any variable that can be used as a key for a dictionary
  185. """
  186. figure_registry = _context['figure_registry']
  187. if key not in figure_registry:
  188. return
  189. if _context['figure'] == figure_registry[key]:
  190. figure()
  191. fig = figure_registry[key]
  192. if hasattr(fig, 'pyplot'):
  193. fig.pyplot.close()
  194. fig.pyplot_vbox.close()
  195. fig.close()
  196. del figure_registry[key]
  197. del _context['scale_registry'][key]
  198. def _process_data(*kwarg_names):
  199. """Helper function to handle data keyword argument
  200. """
  201. def _data_decorator(func):
  202. @functools.wraps(func)
  203. def _mark_with_data(*args, **kwargs):
  204. data = kwargs.pop('data', None)
  205. if data is None:
  206. return func(*args, **kwargs)
  207. else:
  208. data_args = [data[i] if hashable(data, i) else i for i in args]
  209. data_kwargs = {
  210. kw: data[kwargs[kw]] if hashable(data, kwargs[kw]) else kwargs[kw] for kw in set(kwarg_names).intersection(list(kwargs.keys()))
  211. }
  212. try:
  213. # if any of the plots want to use the index_data, they can
  214. # use it by referring to this attribute.
  215. data_kwargs['index_data'] = data.index
  216. except AttributeError as e:
  217. pass
  218. kwargs_update = kwargs.copy()
  219. kwargs_update.update(data_kwargs)
  220. return func(*data_args, **kwargs_update)
  221. return _mark_with_data
  222. return _data_decorator
  223. def scales(key=None, scales={}):
  224. """Creates and switches between context scales.
  225. If no key is provided, a new blank context is created.
  226. If a key is provided for which a context already exists, the existing
  227. context is set as the current context.
  228. If a key is provided and no corresponding context exists, a new context is
  229. created for that key and set as the current context.
  230. Parameters
  231. ----------
  232. key: hashable, optional
  233. Any variable that can be used as a key for a dictionary
  234. scales: dictionary
  235. Dictionary of scales to be used in the new context
  236. Example
  237. -------
  238. >>> scales(scales={
  239. >>> 'x': Keep,
  240. >>> 'color': ColorScale(min=0, max=1)
  241. >>> })
  242. This creates a new scales context, where the 'x' scale is kept from the
  243. previous context, the 'color' scale is an instance of ColorScale
  244. provided by the user. Other scales, potentially needed such as the 'y'
  245. scale in the case of a line chart will be created on the fly when
  246. needed.
  247. Notes
  248. -----
  249. Every call to the function figure triggers a call to scales.
  250. The `scales` parameter is ignored if the `key` argument is not Keep and
  251. context scales already exist for that key.
  252. """
  253. old_ctxt = _context['scales']
  254. if key is None: # No key provided
  255. _context['scales'] = {_get_attribute_dimension(k): scales[k] if scales[k] is not Keep
  256. else old_ctxt[_get_attribute_dimension(k)] for k in scales}
  257. else: # A key is provided
  258. if key not in _context['scale_registry']:
  259. _context['scale_registry'][key] = {
  260. _get_attribute_dimension(k): scales[k]
  261. if scales[k] is not Keep
  262. else old_ctxt[_get_attribute_dimension(k)]
  263. for k in scales
  264. }
  265. _context['scales'] = _context['scale_registry'][key]
  266. def xlim(min, max):
  267. """Set the domain bounds of the current 'x' scale.
  268. """
  269. return set_lim(min, max, 'x')
  270. def ylim(min, max):
  271. """Set the domain bounds of the current 'y' scale.
  272. """
  273. return set_lim(min, max, 'y')
  274. def set_lim(min, max, name):
  275. """Set the domain bounds of the scale associated with the provided key.
  276. Parameters
  277. ----------
  278. name: hashable
  279. Any variable that can be used as a key for a dictionary
  280. Raises
  281. ------
  282. KeyError
  283. When no context figure is associated with the provided key.
  284. """
  285. scale = _context['scales'][_get_attribute_dimension(name)]
  286. scale.min = min
  287. scale.max = max
  288. return scale
  289. def axes(mark=None, options={}, **kwargs):
  290. """Draws axes corresponding to the scales of a given mark.
  291. It also returns a dictionary of drawn axes. If the mark is not provided,
  292. the last drawn mark is used.
  293. Parameters
  294. ----------
  295. mark: Mark or None (default: None)
  296. The mark to inspect to create axes. If None, the last mark drawn is
  297. used instead.
  298. options: dict (default: {})
  299. Options for the axes to be created. If a scale labeled 'x' is required
  300. for that mark, options['x'] contains optional keyword arguments for the
  301. constructor of the corresponding axis type.
  302. """
  303. if mark is None:
  304. mark = _context['last_mark']
  305. if mark is None:
  306. return {}
  307. fig = kwargs.get('figure', current_figure())
  308. scales = mark.scales
  309. fig_axes = [axis for axis in fig.axes]
  310. axes = {}
  311. for name in scales:
  312. if name not in mark.class_trait_names(scaled=True):
  313. # The scale is not needed.
  314. continue
  315. scale_metadata = mark.scales_metadata.get(name, {})
  316. dimension = scale_metadata.get('dimension', scales[name])
  317. axis_args = dict(scale_metadata,
  318. **(options.get(name, {})))
  319. axis = _fetch_axis(fig, dimension, scales[name])
  320. if axis is not None:
  321. # For this figure, an axis exists for the scale in the given
  322. # dimension. Apply the properties and return back the object.
  323. _apply_properties(axis, options.get(name, {}))
  324. axes[name] = axis
  325. continue
  326. # An axis must be created. We fetch the type from the registry
  327. # the key being provided in the scaled attribute decoration
  328. key = mark.class_traits()[name].get_metadata('atype')
  329. if(key is not None):
  330. axis_type = Axis.axis_types[key]
  331. axis = axis_type(scale=scales[name], **axis_args)
  332. axes[name] = axis
  333. fig_axes.append(axis)
  334. # Update the axis registry of the figure once the axis is added
  335. _update_fig_axis_registry(fig, dimension, scales[name], axis)
  336. fig.axes = fig_axes
  337. return axes
  338. def _set_label(label, mark, dim, **kwargs):
  339. """Helper function to set labels for an axis
  340. """
  341. if mark is None:
  342. mark = _context['last_mark']
  343. if mark is None:
  344. return {}
  345. fig = kwargs.get('figure', current_figure())
  346. scales = mark.scales
  347. scale_metadata = mark.scales_metadata.get(dim, {})
  348. scale = scales.get(dim, None)
  349. if scale is None:
  350. return
  351. dimension = scale_metadata.get('dimension', scales[dim])
  352. axis = _fetch_axis(fig, dimension, scales[dim])
  353. if axis is not None:
  354. _apply_properties(axis, {'label': label})
  355. def xlabel(label=None, mark=None, **kwargs):
  356. """Sets the value of label for an axis whose associated scale has the
  357. dimension `x`.
  358. Parameters
  359. ----------
  360. label: Unicode or None (default: None)
  361. The label for x axis
  362. """
  363. _set_label(label, mark, 'x', **kwargs)
  364. def ylabel(label=None, mark=None, **kwargs):
  365. """Sets the value of label for an axis whose associated scale has the
  366. dimension `y`.
  367. Parameters
  368. ----------
  369. label: Unicode or None (default: None)
  370. The label for y axis
  371. """
  372. _set_label(label, mark, 'y', **kwargs)
  373. def grids(fig=None, value='solid'):
  374. """Sets the value of the grid_lines for the axis to the passed value.
  375. The default value is `solid`.
  376. Parameters
  377. ----------
  378. fig: Figure or None(default: None)
  379. The figure for which the axes should be edited. If the value is None,
  380. the current figure is used.
  381. value: {'none', 'solid', 'dashed'}
  382. The display of the grid_lines
  383. """
  384. if fig is None:
  385. fig = current_figure()
  386. for a in fig.axes:
  387. a.grid_lines = value
  388. def title(label, style=None):
  389. """Sets the title for the current figure.
  390. Parameters
  391. ----------
  392. label : str
  393. The new title for the current figure.
  394. style: dict
  395. The CSS style to be applied to the figure title
  396. """
  397. fig = current_figure()
  398. fig.title = label
  399. if style is not None:
  400. fig.title_style = style
  401. def legend():
  402. """Places legend in the current figure."""
  403. for m in current_figure().marks:
  404. m.display_legend = True
  405. def hline(level, **kwargs):
  406. """Draws a horizontal line at the given level.
  407. Parameters
  408. ----------
  409. level: float
  410. The level at which to draw the horizontal line.
  411. preserve_domain: boolean (default: False)
  412. If true, the line does not affect the domain of the 'y' scale.
  413. """
  414. kwargs.setdefault('colors', ['dodgerblue'])
  415. kwargs.setdefault('stroke_width', 1)
  416. scales = kwargs.pop('scales', {})
  417. fig = kwargs.get('figure', current_figure())
  418. scales['x'] = fig.scale_x
  419. level = array(level)
  420. if len(level.shape) == 0:
  421. x = [0, 1]
  422. y = [level, level]
  423. else:
  424. x = [0, 1]
  425. y = column_stack([level, level])
  426. return plot(x, y, scales=scales, preserve_domain={
  427. 'x': True,
  428. 'y': kwargs.get('preserve_domain', False)
  429. }, axes=False, update_context=False, **kwargs)
  430. def vline(level, **kwargs):
  431. """Draws a vertical line at the given level.
  432. Parameters
  433. ----------
  434. level: float
  435. The level at which to draw the vertical line.
  436. preserve_domain: boolean (default: False)
  437. If true, the line does not affect the domain of the 'x' scale.
  438. """
  439. kwargs.setdefault('colors', ['dodgerblue'])
  440. kwargs.setdefault('stroke_width', 1)
  441. scales = kwargs.pop('scales', {})
  442. fig = kwargs.get('figure', current_figure())
  443. scales['y'] = fig.scale_y
  444. level = array(level)
  445. if len(level.shape) == 0:
  446. x = [level, level]
  447. y = [0, 1]
  448. else:
  449. x = column_stack([level, level])
  450. # TODO: repeating [0, 1] should not be required once we allow for
  451. # 2-D x and 1-D y
  452. y = [[0, 1]] * len(level)
  453. return plot(x, y, scales=scales, preserve_domain={
  454. 'x': kwargs.get('preserve_domain', False),
  455. 'y': True
  456. }, axes=False, update_context=False, **kwargs)
  457. def _process_cmap(cmap):
  458. '''
  459. Returns a kwarg dict suitable for a ColorScale
  460. '''
  461. option = {}
  462. if isinstance(cmap, str):
  463. option['scheme'] = cmap
  464. elif isinstance(cmap, list):
  465. option['colors'] = cmap
  466. else:
  467. raise ValueError('''`cmap` must be a string (name of a color scheme)
  468. or a list of colors, but a value of {} was given
  469. '''.format(cmap))
  470. return option
  471. def set_cmap(cmap):
  472. '''
  473. Set the color map of the current 'color' scale.
  474. '''
  475. scale = _context['scales']['color']
  476. for k, v in _process_cmap(cmap).items():
  477. setattr(scale, k, v)
  478. return scale
  479. def _draw_mark(mark_type, options={}, axes_options={}, **kwargs):
  480. """Draw the mark of specified mark type.
  481. Parameters
  482. ----------
  483. mark_type: type
  484. The type of mark to be drawn
  485. options: dict (default: {})
  486. Options for the scales to be created. If a scale labeled 'x' is
  487. required for that mark, options['x'] contains optional keyword
  488. arguments for the constructor of the corresponding scale type.
  489. axes_options: dict (default: {})
  490. Options for the axes to be created. If an axis labeled 'x' is required
  491. for that mark, axes_options['x'] contains optional keyword arguments
  492. for the constructor of the corresponding axis type.
  493. figure: Figure or None
  494. The figure to which the mark is to be added.
  495. If the value is None, the current figure is used.
  496. cmap: list or string
  497. List of css colors, or name of bqplot color scheme
  498. """
  499. fig = kwargs.pop('figure', current_figure())
  500. scales = kwargs.pop('scales', {})
  501. update_context = kwargs.pop('update_context', True)
  502. # Set the color map of the color scale
  503. cmap = kwargs.pop('cmap', None)
  504. if cmap is not None:
  505. # Add the colors or scheme to the color scale options
  506. options['color'] = dict(options.get('color', {}),
  507. **_process_cmap(cmap))
  508. # Going through the list of data attributes
  509. for name in mark_type.class_trait_names(scaled=True):
  510. dimension = _get_attribute_dimension(name, mark_type)
  511. # TODO: the following should also happen if name in kwargs and
  512. # scales[name] is incompatible.
  513. if name not in kwargs:
  514. # The scaled attribute is not being passed to the mark. So no need
  515. # create a scale for this.
  516. continue
  517. elif name in scales:
  518. if update_context:
  519. _context['scales'][dimension] = scales[name]
  520. # Scale has to be fetched from the context or created as it has not
  521. # been passed.
  522. elif dimension not in _context['scales']:
  523. # Creating a scale for the dimension if a matching scale is not
  524. # present in _context['scales']
  525. traitlet = mark_type.class_traits()[name]
  526. rtype = traitlet.get_metadata('rtype')
  527. dtype = traitlet.validate(None, kwargs[name]).dtype
  528. # Fetching the first matching scale for the rtype and dtype of the
  529. # scaled attributes of the mark.
  530. compat_scale_types = [
  531. Scale.scale_types[key]
  532. for key in Scale.scale_types
  533. if Scale.scale_types[key].rtype == rtype and
  534. issubdtype(dtype, Scale.scale_types[key].dtype)
  535. ]
  536. sorted_scales = sorted(compat_scale_types,
  537. key=lambda x: x.precedence)
  538. scales[name] = sorted_scales[-1](**options.get(name, {}))
  539. # Adding the scale to the context scales
  540. if update_context:
  541. _context['scales'][dimension] = scales[name]
  542. else:
  543. scales[name] = _context['scales'][dimension]
  544. mark = mark_type(scales=scales, **kwargs)
  545. _context['last_mark'] = mark
  546. fig.marks = [m for m in fig.marks] + [mark]
  547. if kwargs.get('axes', True):
  548. axes(mark, options=axes_options)
  549. return mark
  550. def _infer_x_for_line(y):
  551. """
  552. Infers the x for a line if no x is provided.
  553. """
  554. array_shape = shape(y)
  555. if len(array_shape) == 0:
  556. return []
  557. if len(array_shape) == 1:
  558. return arange(array_shape[0])
  559. if len(array_shape) > 1:
  560. return arange(array_shape[1])
  561. @_process_data('color')
  562. def plot(*args, **kwargs):
  563. """Draw lines in the current context figure.
  564. Signature: `plot(x, y, **kwargs)` or `plot(y, **kwargs)`, depending of the
  565. length of the list of positional arguments. In the case where the `x` array
  566. is not provided.
  567. Parameters
  568. ----------
  569. x: numpy.ndarray or list, 1d or 2d (optional)
  570. The x-coordinates of the plotted line. When not provided, the function
  571. defaults to `numpy.arange(len(y))`
  572. x can be 1-dimensional or 2-dimensional.
  573. y: numpy.ndarray or list, 1d or 2d
  574. The y-coordinates of the plotted line. If argument `x` is 2-dimensional
  575. it must also be 2-dimensional.
  576. marker_str: string
  577. string representing line_style, marker and color.
  578. For e.g. 'g--o', 'sr' etc
  579. options: dict (default: {})
  580. Options for the scales to be created. If a scale labeled 'x' is
  581. required for that mark, options['x'] contains optional keyword
  582. arguments for the constructor of the corresponding scale type.
  583. axes_options: dict (default: {})
  584. Options for the axes to be created. If an axis labeled 'x' is required
  585. for that mark, axes_options['x'] contains optional keyword arguments
  586. for the constructor of the corresponding axis type.
  587. figure: Figure or None
  588. The figure to which the line is to be added.
  589. If the value is None, the current figure is used.
  590. """
  591. marker_str = None
  592. if len(args) == 1:
  593. kwargs['y'] = args[0]
  594. if kwargs.get('index_data', None) is not None:
  595. kwargs['x'] = kwargs['index_data']
  596. else:
  597. kwargs['x'] = _infer_x_for_line(args[0])
  598. elif len(args) == 2:
  599. if type(args[1]) == str:
  600. kwargs['y'] = args[0]
  601. kwargs['x'] = _infer_x_for_line(args[0])
  602. marker_str = args[1].strip()
  603. else:
  604. kwargs['x'] = args[0]
  605. kwargs['y'] = args[1]
  606. elif len(args) == 3:
  607. kwargs['x'] = args[0]
  608. kwargs['y'] = args[1]
  609. if type(args[2]) == str:
  610. marker_str = args[2].strip()
  611. if marker_str:
  612. line_style, color, marker = _get_line_styles(marker_str)
  613. # only marker specified => draw scatter
  614. if marker and not line_style:
  615. kwargs['marker'] = marker
  616. if color:
  617. kwargs['colors'] = [color]
  618. return _draw_mark(Scatter, **kwargs)
  619. else: # draw lines in all other cases
  620. kwargs['line_style'] = line_style or 'solid'
  621. if marker:
  622. kwargs['marker'] = marker
  623. if color:
  624. kwargs['colors'] = [color]
  625. return _draw_mark(Lines, **kwargs)
  626. else:
  627. return _draw_mark(Lines, **kwargs)
  628. def imshow(image, format, **kwargs):
  629. """Draw an image in the current context figure.
  630. Parameters
  631. ----------
  632. image: image data
  633. Image data, depending on the passed format, can be one of:
  634. - an instance of an ipywidgets Image
  635. - a file name
  636. - a raw byte string
  637. format: {'widget', 'filename', ...}
  638. Type of the input argument.
  639. If not 'widget' or 'filename', must be a format supported by
  640. the ipywidgets Image.
  641. options: dict (default: {})
  642. Options for the scales to be created. If a scale labeled 'x' is
  643. required for that mark, options['x'] contains optional keyword
  644. arguments for the constructor of the corresponding scale type.
  645. axes_options: dict (default: {})
  646. Options for the axes to be created. If an axis labeled 'x' is required
  647. for that mark, axes_options['x'] contains optional keyword arguments
  648. for the constructor of the corresponding axis type.
  649. """
  650. if format == 'widget':
  651. ipyimage = image
  652. elif format == 'filename':
  653. with open(image, 'rb') as f:
  654. data = f.read()
  655. ipyimage = ipyImage(value=data)
  656. else:
  657. ipyimage = ipyImage(value=image, format=format)
  658. kwargs['image'] = ipyimage
  659. kwargs.setdefault('x', [0., 1.])
  660. kwargs.setdefault('y', [0., 1.])
  661. return _draw_mark(Image, **kwargs)
  662. def ohlc(*args, **kwargs):
  663. """Draw OHLC bars or candle bars in the current context figure.
  664. Signature: `ohlc(x, y, **kwargs)` or `ohlc(y, **kwargs)`, depending of the
  665. length of the list of positional arguments. In the case where the `x` array
  666. is not provided
  667. Parameters
  668. ----------
  669. x: numpy.ndarray or list, 1d (optional)
  670. The x-coordinates of the plotted line. When not provided, the function
  671. defaults to `numpy.arange(len(y))`.
  672. y: numpy.ndarray or list, 2d
  673. The ohlc (open/high/low/close) information. A two dimensional array. y
  674. must have the shape (n, 4).
  675. options: dict (default: {})
  676. Options for the scales to be created. If a scale labeled 'x' is
  677. required for that mark, options['x'] contains optional keyword
  678. arguments for the constructor of the corresponding scale type.
  679. axes_options: dict (default: {})
  680. Options for the axes to be created. If an axis labeled 'x' is required
  681. for that mark, axes_options['x'] contains optional keyword arguments
  682. for the constructor of the corresponding axis type.
  683. """
  684. if len(args) == 2:
  685. kwargs['x'] = args[0]
  686. kwargs['y'] = args[1]
  687. elif len(args) == 1:
  688. kwargs['y'] = args[0]
  689. length = len(args[0])
  690. kwargs['x'] = arange(length)
  691. return _draw_mark(OHLC, **kwargs)
  692. @_process_data('color', 'opacity', 'size', 'skew', 'rotation')
  693. def scatter(x, y, use_gl=None, **kwargs):
  694. """Draw a scatter in the current context figure.
  695. Parameters
  696. ----------
  697. x: numpy.ndarray, 1d
  698. The x-coordinates of the data points.
  699. y: numpy.ndarray, 1d
  700. The y-coordinates of the data points.
  701. use_gl: If true, will use the ScatterGL mark (pixelized but faster), if false a normal
  702. Scatter mark is used. If None, a choised is made automatically depending on the length
  703. of x.
  704. options: dict (default: {})
  705. Options for the scales to be created. If a scale labeled 'x' is
  706. required for that mark, options['x'] contains optional keyword
  707. arguments for the constructor of the corresponding scale type.
  708. axes_options: dict (default: {})
  709. Options for the axes to be created. If an axis labeled 'x' is required
  710. for that mark, axes_options['x'] contains optional keyword arguments
  711. for the constructor of the corresponding axis type.
  712. """
  713. kwargs['x'] = x
  714. kwargs['y'] = y
  715. if use_gl is None:
  716. mark_class = ScatterGL if len(x) >= SCATTER_SIZE_LIMIT else Scatter
  717. else:
  718. mark_class = ScatterGL if use_gl else Scatter
  719. return _draw_mark(mark_class, **kwargs)
  720. @_process_data()
  721. def hist(sample, options={}, **kwargs):
  722. """Draw a histogram in the current context figure.
  723. Parameters
  724. ----------
  725. sample: numpy.ndarray, 1d
  726. The sample for which the histogram must be generated.
  727. options: dict (default: {})
  728. Options for the scales to be created. If a scale labeled 'counts'
  729. is required for that mark, options['counts'] contains optional keyword
  730. arguments for the constructor of the corresponding scale type.
  731. axes_options: dict (default: {})
  732. Options for the axes to be created. If an axis labeled 'counts' is
  733. required for that mark, axes_options['counts'] contains optional
  734. keyword arguments for the constructor of the corresponding axis type.
  735. """
  736. kwargs['sample'] = sample
  737. scales = kwargs.pop('scales', {})
  738. if 'count' not in scales:
  739. dimension = _get_attribute_dimension('count', Hist)
  740. if dimension in _context['scales']:
  741. scales['count'] = _context['scales'][dimension]
  742. else:
  743. scales['count'] = LinearScale(**options.get('count', {}))
  744. _context['scales'][dimension] = scales['count']
  745. kwargs['scales'] = scales
  746. return _draw_mark(Hist, options=options, **kwargs)
  747. @_process_data()
  748. def bin(sample, options={}, **kwargs):
  749. """Draw a histogram in the current context figure.
  750. Parameters
  751. ----------
  752. sample: numpy.ndarray, 1d
  753. The sample for which the histogram must be generated.
  754. options: dict (default: {})
  755. Options for the scales to be created. If a scale labeled 'x'
  756. is required for that mark, options['x'] contains optional keyword
  757. arguments for the constructor of the corresponding scale type.
  758. axes_options: dict (default: {})
  759. Options for the axes to be created. If an axis labeled 'x' is
  760. required for that mark, axes_options['x'] contains optional
  761. keyword arguments for the constructor of the corresponding axis type.
  762. """
  763. kwargs['sample'] = sample
  764. scales = kwargs.pop('scales', {})
  765. for xy in ['x', 'y']:
  766. if xy not in scales:
  767. dimension = _get_attribute_dimension(xy, Bars)
  768. if dimension in _context['scales']:
  769. scales[xy] = _context['scales'][dimension]
  770. else:
  771. scales[xy] = LinearScale(**options.get(xy, {}))
  772. _context['scales'][dimension] = scales[xy]
  773. kwargs['scales'] = scales
  774. return _draw_mark(Bins, options=options, **kwargs)
  775. @_process_data('color')
  776. def bar(x, y, **kwargs):
  777. """Draws a bar chart in the current context figure.
  778. Parameters
  779. ----------
  780. x: numpy.ndarray, 1d
  781. The x-coordinates of the data points.
  782. y: numpy.ndarray, 1d
  783. The y-coordinates of the data pints.
  784. options: dict (default: {})
  785. Options for the scales to be created. If a scale labeled 'x' is
  786. required for that mark, options['x'] contains optional keyword
  787. arguments for the constructor of the corresponding scale type.
  788. axes_options: dict (default: {})
  789. Options for the axes to be created. If an axis labeled 'x' is required
  790. for that mark, axes_options['x'] contains optional keyword arguments
  791. for the constructor of the corresponding axis type.
  792. """
  793. kwargs['x'] = x
  794. kwargs['y'] = y
  795. return _draw_mark(Bars, **kwargs)
  796. @_process_data()
  797. def boxplot(x, y, **kwargs):
  798. """Draws a boxplot in the current context figure.
  799. Parameters
  800. ----------
  801. x: numpy.ndarray, 1d
  802. The x-coordinates of the data points.
  803. y: numpy.ndarray, 2d
  804. The data from which the boxes are to be created. Each row of the data
  805. corresponds to one box drawn in the plot.
  806. options: dict (default: {})
  807. Options for the scales to be created. If a scale labeled 'x' is
  808. required for that mark, options['x'] contains optional keyword
  809. arguments for the constructor of the corresponding scale type.
  810. axes_options: dict (default: {})
  811. Options for the axes to be created. If an axis labeled 'x' is required
  812. for that mark, axes_options['x'] contains optional keyword arguments
  813. for the constructor of the corresponding axis type.
  814. """
  815. kwargs['x'] = x
  816. kwargs['y'] = y
  817. return _draw_mark(Boxplot, **kwargs)
  818. @_process_data('color')
  819. def barh(*args, **kwargs):
  820. """Draws a horizontal bar chart in the current context figure.
  821. Parameters
  822. ----------
  823. x: numpy.ndarray, 1d
  824. The domain of the data points.
  825. y: numpy.ndarray, 1d
  826. The range of the data pints.
  827. options: dict (default: {})
  828. Options for the scales to be created. If a scale labeled 'x' is
  829. required for that mark, options['x'] contains optional keyword
  830. arguments for the constructor of the corresponding scale type.
  831. axes_options: dict (default: {})
  832. Options for the axes to be created. If an axis labeled 'x' is required
  833. for that mark, axes_options['x'] contains optional keyword arguments
  834. for the constructor of the corresponding axis type.
  835. """
  836. kwargs['orientation'] = "horizontal"
  837. return bar(*args, **kwargs)
  838. @_process_data('color')
  839. def pie(sizes, **kwargs):
  840. """Draws a Pie in the current context figure.
  841. Parameters
  842. ----------
  843. sizes: numpy.ndarray, 1d
  844. The proportions to be represented by the Pie.
  845. options: dict (default: {})
  846. Options for the scales to be created. If a scale labeled 'x' is
  847. required for that mark, options['x'] contains optional keyword
  848. arguments for the constructor of the corresponding scale type.
  849. axes_options: dict (default: {})
  850. Options for the axes to be created. If an axis labeled 'x' is required
  851. for that mark, axes_options['x'] contains optional keyword arguments
  852. for the constructor of the corresponding axis type.
  853. """
  854. kwargs['sizes'] = sizes
  855. return _draw_mark(Pie, **kwargs)
  856. def label(text, **kwargs):
  857. """Draws a Label in the current context figure.
  858. Parameters
  859. ----------
  860. text: string
  861. The label to be displayed.
  862. options: dict (default: {})
  863. Options for the scales to be created. If a scale labeled 'x' is
  864. required for that mark, options['x'] contains optional keyword
  865. arguments for the constructor of the corresponding scale type.
  866. axes_options: dict (default: {})
  867. Options for the axes to be created. If an axis labeled 'x' is required
  868. for that mark, axes_options['x'] contains optional keyword arguments
  869. for the constructor of the corresponding axis type.
  870. """
  871. kwargs['text'] = text
  872. return _draw_mark(Label, **kwargs)
  873. def geo(map_data, **kwargs):
  874. """Draw a map in the current context figure.
  875. Parameters
  876. ----------
  877. map_data: string or bqplot.map (default: WorldMap)
  878. Name of the map or json file required for the map data.
  879. options: dict (default: {})
  880. Options for the scales to be created. If a scale labeled 'x' is
  881. required for that mark, options['x'] contains optional keyword
  882. arguments for the constructor of the corresponding scale type.
  883. axes_options: dict (default: {})
  884. Options for the axes to be created. If an axis labeled 'x' is required
  885. for that mark, axes_options['x'] contains optional keyword arguments
  886. for the constructor of the corresponding axis type.
  887. """
  888. scales = kwargs.pop('scales', _context['scales'])
  889. options = kwargs.get('options', {})
  890. if 'projection' not in scales:
  891. scales['projection'] = Mercator(**options.get('projection', {}))
  892. kwargs['scales'] = scales
  893. if isinstance(map_data, string_types):
  894. kwargs['map_data'] = topo_load('map_data/' + map_data + '.json')
  895. else:
  896. kwargs['map_data'] = map_data
  897. return _draw_mark(Map, **kwargs)
  898. def heatmap(color, **kwargs):
  899. """Draw a heatmap in the current context figure.
  900. Parameters
  901. ----------
  902. color: numpy.ndarray, 2d
  903. Matrix of color of the data points
  904. options: dict (default: {})
  905. Options for the scales to be created. If a scale labeled 'x' is
  906. required for that mark, options['x'] contains optional keyword
  907. arguments for the constructor of the corresponding scale type.
  908. axes_options: dict (default: {})
  909. Options for the axes to be created. If an axis labeled 'x' is required
  910. for that mark, axes_options['x'] contains optional keyword arguments
  911. for the constructor of the corresponding axis type.
  912. """
  913. kwargs['color'] = color
  914. return _draw_mark(HeatMap, **kwargs)
  915. def gridheatmap(color, **kwargs):
  916. """Draw a GridHeatMap in the current context figure.
  917. Parameters
  918. ----------
  919. color: numpy.ndarray, 2d
  920. Matrix of color of the data points
  921. options: dict (default: {})
  922. Options for the scales to be created. If a scale labeled 'x' is
  923. required for that mark, options['x'] contains optional keyword
  924. arguments for the constructor of the corresponding scale type.
  925. axes_options: dict (default: {})
  926. Options for the axes to be created. If an axis labeled 'x' is required
  927. for that mark, axes_options['x'] contains optional keyword arguments
  928. for the constructor of the corresponding axis type.
  929. """
  930. kwargs['color'] = color
  931. return _draw_mark(GridHeatMap, **kwargs)
  932. def _add_interaction(int_type, **kwargs):
  933. """Add the interaction for the specified type.
  934. If a figure is passed using the key-word argument `figure` it is used. Else
  935. the context figure is used.
  936. If a list of marks are passed using the key-word argument `marks` it
  937. is used. Else the latest mark that is passed is used as the only mark
  938. associated with the selector.
  939. Parameters
  940. ----------
  941. int_type: type
  942. The type of interaction to be added.
  943. """
  944. fig = kwargs.pop('figure', current_figure())
  945. marks = kwargs.pop('marks', [_context['last_mark']])
  946. for name, traitlet in int_type.class_traits().items():
  947. dimension = traitlet.get_metadata('dimension')
  948. if dimension is not None:
  949. # only scales have this attribute in interactions
  950. kwargs[name] = _get_context_scale(dimension)
  951. kwargs['marks'] = marks
  952. interaction = int_type(**kwargs)
  953. if fig.interaction is not None:
  954. fig.interaction.close()
  955. fig.interaction = interaction
  956. return interaction
  957. def _get_context_scale(dimension):
  958. """Return the scale instance in the current context for a given dimension.
  959. Parameters
  960. ----------
  961. dimension: string
  962. The dimension along which the current context scale is to be fetched.
  963. """
  964. return _context['scales'][dimension]
  965. def _create_selector(int_type, func, trait, **kwargs):
  966. """Create a selector of the specified type.
  967. Also attaches the function `func` as an `on_trait_change` listener
  968. for the trait `trait` of the selector.
  969. This is an internal function which should not be called by the user.
  970. Parameters
  971. ----------
  972. int_type: type
  973. The type of selector to be added.
  974. func: function
  975. The call back function. It should take atleast two arguments. The name
  976. of the trait and the value of the trait are passed as arguments.
  977. trait: string
  978. The name of the Selector trait whose change triggers the
  979. call back function `func`.
  980. """
  981. interaction = _add_interaction(int_type, **kwargs)
  982. if func is not None:
  983. interaction.on_trait_change(func, trait)
  984. return interaction
  985. def brush_int_selector(func=None, trait='selected', **kwargs):
  986. """Create a `BrushIntervalSelector` interaction for the `figure`.
  987. Also attaches the function `func` as an event listener for the
  988. specified trait.
  989. Parameters
  990. ----------
  991. func: function
  992. The call back function. It should take atleast two arguments. The name
  993. of the trait and the value of the trait are passed as arguments.
  994. trait: string
  995. The name of the BrushIntervalSelector trait whose change triggers the
  996. call back function `func`.
  997. """
  998. return _create_selector(BrushIntervalSelector, func, trait, **kwargs)
  999. def int_selector(func=None, trait='selected', **kwargs):
  1000. """Creates a `FastIntervalSelector` interaction for the `figure`.
  1001. Also attaches the function `func` as an event listener for the
  1002. trait `trait`.
  1003. Parameters
  1004. ----------
  1005. func: function
  1006. The call back function. It should take atleast two arguments. The name
  1007. of the trait and the value of the trait are passed as arguments.
  1008. trait: string
  1009. The name of the IntervalSelector trait whose change triggers the
  1010. call back function `func`.
  1011. """
  1012. return _create_selector(FastIntervalSelector, func, trait, **kwargs)
  1013. def index_selector(func=None, trait='selected', **kwargs):
  1014. """Creates an `IndexSelector` interaction for the `figure`.
  1015. Also attaches the function `func` as an event listener for the
  1016. trait `trait`.
  1017. Parameters
  1018. ----------
  1019. func: function
  1020. The call back function. It should take atleast two arguments. The name
  1021. of the trait and the value of the trait are passed as arguments.
  1022. trait: string
  1023. The name of the IndexSelector trait whose change triggers the
  1024. call back function `func`.
  1025. """
  1026. return _create_selector(IndexSelector, func, trait, **kwargs)
  1027. def brush_selector(func=None, trait='selected', **kwargs):
  1028. """Creates a `BrushSelector` interaction for the `figure`.
  1029. Also attaches the function `func` as an event listener for the
  1030. trait `trait`.
  1031. Parameters
  1032. ----------
  1033. func: function
  1034. The call back function. It should take atleast two arguments. The name
  1035. of the trait and the value of the trait are passed as arguments.
  1036. trait: string
  1037. The name of the BrushSelector trait whose change triggers the
  1038. call back function `func`.
  1039. """
  1040. return _create_selector(BrushSelector, func, trait, **kwargs)
  1041. def multi_selector(func=None, trait='selected', **kwargs):
  1042. """Creates a `MultiSelector` interaction for the `figure`.
  1043. Also attaches the function `func` as an event listener for the
  1044. trait `trait`.
  1045. Parameters
  1046. ----------
  1047. func: function
  1048. The call back function. It should take atleast two arguments. The name
  1049. of the trait and the value of the trait are passed as arguments.
  1050. trait: string
  1051. The name of the MultiSelector trait whose change triggers the
  1052. call back function `func`.
  1053. """
  1054. return _create_selector(MultiSelector, func, trait, **kwargs)
  1055. def lasso_selector(func=None, trait='selected', **kwargs):
  1056. """Creates a `LassoSelector` interaction for the `figure`.
  1057. Also attaches the function `func` as an event listener for the
  1058. specified trait.
  1059. Parameters
  1060. ----------
  1061. func: function
  1062. The call back function. It should take atleast two arguments. The name
  1063. of the trait and the value of the trait are passed as arguments.
  1064. trait: string
  1065. The name of the LassoSelector trait whose change triggers the
  1066. call back function `func`.
  1067. """
  1068. return _create_selector(LassoSelector, func, trait, **kwargs)
  1069. def clear():
  1070. """Clears the current context figure of all marks axes and grid lines."""
  1071. fig = _context['figure']
  1072. if fig is not None:
  1073. fig.marks = []
  1074. fig.axes = []
  1075. setattr(fig, 'axis_registry', {})
  1076. _context['scales'] = {}
  1077. key = _context['current_key']
  1078. if key is not None:
  1079. _context['scale_registry'][key] = {}
  1080. def current_figure():
  1081. """Returns the current context figure."""
  1082. if _context['figure'] is None:
  1083. figure()
  1084. return _context['figure']
  1085. # FOR DEBUG ONLY
  1086. def get_context():
  1087. """Used for debug only. Return a copy of the current global
  1088. context dictionary."""
  1089. return {k: v for k, v in _context.items()}
  1090. def set_context(context):
  1091. """Sets the current global context dictionary. All the attributes to be set
  1092. should be set. Otherwise, it will result in unpredictable behavior."""
  1093. global _context
  1094. _context = {k: v for k, v in context.items()}
  1095. def _fetch_axis(fig, dimension, scale):
  1096. # Internal utility function.
  1097. # Given a figure instance `fig`, the dimension of the scaled attribute and
  1098. # the instance of a scale, returns the axis if an axis is present for that
  1099. # combination. Else returns `None`
  1100. axis_registry = getattr(fig, 'axis_registry', {})
  1101. dimension_data = axis_registry.get(dimension, [])
  1102. dimension_scales = [dim['scale'] for dim in dimension_data]
  1103. dimension_axes = [dim['axis'] for dim in dimension_data]
  1104. try:
  1105. return dimension_axes[dimension_scales.index(scale)]
  1106. except (ValueError, IndexError):
  1107. return None
  1108. def _update_fig_axis_registry(fig, dimension, scale, axis):
  1109. axis_registry = fig.axis_registry
  1110. dimension_scales = axis_registry.get(dimension, [])
  1111. dimension_scales.append({'scale': scale, 'axis': axis})
  1112. axis_registry[dimension] = dimension_scales
  1113. setattr(fig, 'axis_registry', axis_registry)
  1114. def _get_attribute_dimension(trait_name, mark_type=None):
  1115. """Returns the dimension for the name of the trait for the specified mark.
  1116. If `mark_type` is `None`, then the `trait_name` is returned
  1117. as is.
  1118. Returns `None` if the `trait_name` is not valid for `mark_type`.
  1119. """
  1120. if(mark_type is None):
  1121. return trait_name
  1122. scale_metadata = mark_type.class_traits()['scales_metadata']\
  1123. .default_args[0]
  1124. return scale_metadata.get(trait_name, {}).get('dimension', None)
  1125. def _apply_properties(widget, properties={}):
  1126. """Applies the specified properties to the widget.
  1127. `properties` is a dictionary with key value pairs corresponding
  1128. to the properties to be applied to the widget.
  1129. """
  1130. with widget.hold_sync():
  1131. for key, value in properties.items():
  1132. setattr(widget, key, value)
  1133. def _get_line_styles(marker_str):
  1134. """Return line style, color and marker type from specified marker string.
  1135. For example, if ``marker_str`` is 'g-o' then the method returns
  1136. ``('solid', 'green', 'circle')``.
  1137. """
  1138. def _extract_marker_value(marker_str, code_dict):
  1139. """Extracts the marker value from a given marker string.
  1140. Looks up the `code_dict` and returns the corresponding marker for a
  1141. specific code.
  1142. For example if `marker_str` is 'g-o' then the method extracts
  1143. - 'green' if the code_dict is color_codes,
  1144. - 'circle' if the code_dict is marker_codes etc.
  1145. """
  1146. val = None
  1147. for code in code_dict:
  1148. if code in marker_str:
  1149. val = code_dict[code]
  1150. break
  1151. return val
  1152. return [_extract_marker_value(marker_str, code_dict) for
  1153. code_dict in [LINE_STYLE_CODES, COLOR_CODES, MARKER_CODES]]