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

# Copyright 2015 Bloomberg Finance L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
======
Pyplot
======
.. currentmodule:: bqplot.pyplot
.. autosummary::
:toctree: _generate/
figure
show
axes
plot
scatter
hist
bar
ohlc
geo
clear
close
current_figure
scales
xlim
ylim
axes
xlabel
ylabel
"""
import sys
from collections import OrderedDict
from IPython.display import display
from ipywidgets import VBox
from ipywidgets import Image as ipyImage
from numpy import arange, issubdtype, array, column_stack, shape
from .figure import Figure
from .scales import Scale, LinearScale, Mercator
from .axes import Axis
from .marks import (Lines, Scatter, ScatterGL, Hist, Bars, OHLC, Pie, Map, Image,
Label, HeatMap, GridHeatMap, topo_load, Boxplot, Bins)
from .toolbar import Toolbar
from .interacts import (BrushIntervalSelector, FastIntervalSelector,
BrushSelector, IndexSelector, MultiSelector,
LassoSelector)
from traitlets.utils.sentinel import Sentinel
import functools
SCATTER_SIZE_LIMIT = 10*1000 # above this limit, ScatterGL will be used by default
Keep = Sentinel('Keep', 'bqplot.pyplot', '''
Used in bqplot.pyplot to specify that the same scale should be used for
a certain dimension.
''')
# `_context` object contains the global information for pyplot.
# `figure`: refers to the current figure to which marks will be added.
# `scales`: The current set of scales which will be used for drawing a mark. if
# the scale for an attribute is not present, it is created based on the range
# type.
# `scale_registry`: This is a dictionary where the keys are the context
# names and the values are the set of scales which were used on the last plot
# in that context. This is useful when switching context.
# `last_mark`: refers to the last mark that has been plotted.
# `current_key`: The key for the current context figure. If there is no key,
# then the value is `None`.
_context = {
'figure': None,
'figure_registry': {},
'scales': {},
'scale_registry': {},
'last_mark': None,
'current_key': None
}
LINE_STYLE_CODES = OrderedDict([(':', 'dotted'), ('-.', 'dash_dotted'),
('--', 'dashed'), ('-', 'solid')])
COLOR_CODES = {'b': 'blue', 'g': 'green', 'r': 'red', 'c': 'cyan',
'm': 'magenta', 'y': 'yellow', 'k': 'black'}
MARKER_CODES = {'o': 'circle', 'v': 'triangle-down', '^': 'triangle-up',
's': 'square', 'd': 'diamond', '+': 'cross'}
PY2 = sys.version_info[0] == 2
if PY2:
string_types = basestring,
else:
string_types = str,
def hashable(data, v):
"""Determine whether `v` can be hashed."""
try:
data[v]
except (TypeError, KeyError, IndexError):
return False
return True
def show(key=None, display_toolbar=True):
"""Shows the current context figure in the output area.
Parameters
----------
key : hashable, optional
Any variable that can be used as a key for a dictionary.
display_toolbar: bool (default: True)
If True, a toolbar for different mouse interaction is displayed with
the figure.
Raises
------
KeyError
When no context figure is associated with the provided key.
Examples
--------
>>> import numpy as np
>>> import pyplot as plt
>>> n = 100
>>> x = np.arange(n)
>>> y = np.cumsum(np.random.randn(n))
>>> plt.plot(x,y)
>>> plt.show()
"""
if key is None:
figure = current_figure()
else:
figure = _context['figure_registry'][key]
if display_toolbar:
if not hasattr(figure, 'pyplot'):
figure.pyplot = Toolbar(figure=figure)
figure.pyplot_vbox = VBox([figure, figure.pyplot])
display(figure.pyplot_vbox)
else:
display(figure)
def figure(key=None, fig=None, **kwargs):
"""Creates figures and switches between figures.
If a ``bqplot.Figure`` object is provided via the fig optional argument,
this figure becomes the current context figure.
Otherwise:
- If no key is provided, a new empty context figure is created.
- If a key is provided for which a context already exists, the
corresponding context becomes current.
- If a key is provided and no corresponding context exists, a new context
is reated for that key and becomes current.
Besides, optional arguments allow to set or modify Attributes
of the selected context figure.
Parameters
----------
key: hashable, optional
Any variable that can be used as a key for a dictionary
fig: Figure, optional
A bqplot Figure
"""
scales_arg = kwargs.pop('scales', {})
_context['current_key'] = key
if fig is not None: # fig provided
_context['figure'] = fig
if key is not None:
_context['figure_registry'][key] = fig
for arg in kwargs:
setattr(_context['figure'], arg, kwargs[arg])
else: # no fig provided
if key is None: # no key provided
_context['figure'] = Figure(**kwargs)
else: # a key is provided
if key not in _context['figure_registry']:
if 'title' not in kwargs:
kwargs['title'] = 'Figure' + ' ' + str(key)
_context['figure_registry'][key] = Figure(**kwargs)
_context['figure'] = _context['figure_registry'][key]
for arg in kwargs:
setattr(_context['figure'], arg, kwargs[arg])
scales(key, scales=scales_arg)
# Set the axis reference dictionary. This dictionary contains the mapping
# from the possible dimensions in the figure to the list of scales with
# respect to which axes have been drawn for this figure.
# Used to automatically generate axis.
if(getattr(_context['figure'], 'axis_registry', None) is None):
setattr(_context['figure'], 'axis_registry', {})
return _context['figure']
def close(key):
"""Closes and unregister the context figure corresponding to the key.
Parameters
----------
key: hashable
Any variable that can be used as a key for a dictionary
"""
figure_registry = _context['figure_registry']
if key not in figure_registry:
return
if _context['figure'] == figure_registry[key]:
figure()
fig = figure_registry[key]
if hasattr(fig, 'pyplot'):
fig.pyplot.close()
fig.pyplot_vbox.close()
fig.close()
del figure_registry[key]
del _context['scale_registry'][key]
def _process_data(*kwarg_names):
"""Helper function to handle data keyword argument
"""
def _data_decorator(func):
@functools.wraps(func)
def _mark_with_data(*args, **kwargs):
data = kwargs.pop('data', None)
if data is None:
return func(*args, **kwargs)
else:
data_args = [data[i] if hashable(data, i) else i for i in args]
data_kwargs = {
kw: data[kwargs[kw]] if hashable(data, kwargs[kw]) else kwargs[kw] for kw in set(kwarg_names).intersection(list(kwargs.keys()))
}
try:
# if any of the plots want to use the index_data, they can
# use it by referring to this attribute.
data_kwargs['index_data'] = data.index
except AttributeError as e:
pass
kwargs_update = kwargs.copy()
kwargs_update.update(data_kwargs)
return func(*data_args, **kwargs_update)
return _mark_with_data
return _data_decorator
def scales(key=None, scales={}):
"""Creates and switches between context scales.
If no key is provided, a new blank context is created.
If a key is provided for which a context already exists, the existing
context is set as the current context.
If a key is provided and no corresponding context exists, a new context is
created for that key and set as the current context.
Parameters
----------
key: hashable, optional
Any variable that can be used as a key for a dictionary
scales: dictionary
Dictionary of scales to be used in the new context
Example
-------
>>> scales(scales={
>>> 'x': Keep,
>>> 'color': ColorScale(min=0, max=1)
>>> })
This creates a new scales context, where the 'x' scale is kept from the
previous context, the 'color' scale is an instance of ColorScale
provided by the user. Other scales, potentially needed such as the 'y'
scale in the case of a line chart will be created on the fly when
needed.
Notes
-----
Every call to the function figure triggers a call to scales.
The `scales` parameter is ignored if the `key` argument is not Keep and
context scales already exist for that key.
"""
old_ctxt = _context['scales']
if key is None: # No key provided
_context['scales'] = {_get_attribute_dimension(k): scales[k] if scales[k] is not Keep
else old_ctxt[_get_attribute_dimension(k)] for k in scales}
else: # A key is provided
if key not in _context['scale_registry']:
_context['scale_registry'][key] = {
_get_attribute_dimension(k): scales[k]
if scales[k] is not Keep
else old_ctxt[_get_attribute_dimension(k)]
for k in scales
}
_context['scales'] = _context['scale_registry'][key]
def xlim(min, max):
"""Set the domain bounds of the current 'x' scale.
"""
return set_lim(min, max, 'x')
def ylim(min, max):
"""Set the domain bounds of the current 'y' scale.
"""
return set_lim(min, max, 'y')
def set_lim(min, max, name):
"""Set the domain bounds of the scale associated with the provided key.
Parameters
----------
name: hashable
Any variable that can be used as a key for a dictionary
Raises
------
KeyError
When no context figure is associated with the provided key.
"""
scale = _context['scales'][_get_attribute_dimension(name)]
scale.min = min
scale.max = max
return scale
def axes(mark=None, options={}, **kwargs):
"""Draws axes corresponding to the scales of a given mark.
It also returns a dictionary of drawn axes. If the mark is not provided,
the last drawn mark is used.
Parameters
----------
mark: Mark or None (default: None)
The mark to inspect to create axes. If None, the last mark drawn is
used instead.
options: dict (default: {})
Options for the axes to be created. If a scale labeled 'x' is required
for that mark, options['x'] contains optional keyword arguments for the
constructor of the corresponding axis type.
"""
if mark is None:
mark = _context['last_mark']
if mark is None:
return {}
fig = kwargs.get('figure', current_figure())
scales = mark.scales
fig_axes = [axis for axis in fig.axes]
axes = {}
for name in scales:
if name not in mark.class_trait_names(scaled=True):
# The scale is not needed.
continue
scale_metadata = mark.scales_metadata.get(name, {})
dimension = scale_metadata.get('dimension', scales[name])
axis_args = dict(scale_metadata,
**(options.get(name, {})))
axis = _fetch_axis(fig, dimension, scales[name])
if axis is not None:
# For this figure, an axis exists for the scale in the given
# dimension. Apply the properties and return back the object.
_apply_properties(axis, options.get(name, {}))
axes[name] = axis
continue
# An axis must be created. We fetch the type from the registry
# the key being provided in the scaled attribute decoration
key = mark.class_traits()[name].get_metadata('atype')
if(key is not None):
axis_type = Axis.axis_types[key]
axis = axis_type(scale=scales[name], **axis_args)
axes[name] = axis
fig_axes.append(axis)
# Update the axis registry of the figure once the axis is added
_update_fig_axis_registry(fig, dimension, scales[name], axis)
fig.axes = fig_axes
return axes
def _set_label(label, mark, dim, **kwargs):
"""Helper function to set labels for an axis
"""
if mark is None:
mark = _context['last_mark']
if mark is None:
return {}
fig = kwargs.get('figure', current_figure())
scales = mark.scales
scale_metadata = mark.scales_metadata.get(dim, {})
scale = scales.get(dim, None)
if scale is None:
return
dimension = scale_metadata.get('dimension', scales[dim])
axis = _fetch_axis(fig, dimension, scales[dim])
if axis is not None:
_apply_properties(axis, {'label': label})
def xlabel(label=None, mark=None, **kwargs):
"""Sets the value of label for an axis whose associated scale has the
dimension `x`.
Parameters
----------
label: Unicode or None (default: None)
The label for x axis
"""
_set_label(label, mark, 'x', **kwargs)
def ylabel(label=None, mark=None, **kwargs):
"""Sets the value of label for an axis whose associated scale has the
dimension `y`.
Parameters
----------
label: Unicode or None (default: None)
The label for y axis
"""
_set_label(label, mark, 'y', **kwargs)
def grids(fig=None, value='solid'):
"""Sets the value of the grid_lines for the axis to the passed value.
The default value is `solid`.
Parameters
----------
fig: Figure or None(default: None)
The figure for which the axes should be edited. If the value is None,
the current figure is used.
value: {'none', 'solid', 'dashed'}
The display of the grid_lines
"""
if fig is None:
fig = current_figure()
for a in fig.axes:
a.grid_lines = value
def title(label, style=None):
"""Sets the title for the current figure.
Parameters
----------
label : str
The new title for the current figure.
style: dict
The CSS style to be applied to the figure title
"""
fig = current_figure()
fig.title = label
if style is not None:
fig.title_style = style
def legend():
"""Places legend in the current figure."""
for m in current_figure().marks:
m.display_legend = True
def hline(level, **kwargs):
"""Draws a horizontal line at the given level.
Parameters
----------
level: float
The level at which to draw the horizontal line.
preserve_domain: boolean (default: False)
If true, the line does not affect the domain of the 'y' scale.
"""
kwargs.setdefault('colors', ['dodgerblue'])
kwargs.setdefault('stroke_width', 1)
scales = kwargs.pop('scales', {})
fig = kwargs.get('figure', current_figure())
scales['x'] = fig.scale_x
level = array(level)
if len(level.shape) == 0:
x = [0, 1]
y = [level, level]
else:
x = [0, 1]
y = column_stack([level, level])
return plot(x, y, scales=scales, preserve_domain={
'x': True,
'y': kwargs.get('preserve_domain', False)
}, axes=False, update_context=False, **kwargs)
def vline(level, **kwargs):
"""Draws a vertical line at the given level.
Parameters
----------
level: float
The level at which to draw the vertical line.
preserve_domain: boolean (default: False)
If true, the line does not affect the domain of the 'x' scale.
"""
kwargs.setdefault('colors', ['dodgerblue'])
kwargs.setdefault('stroke_width', 1)
scales = kwargs.pop('scales', {})
fig = kwargs.get('figure', current_figure())
scales['y'] = fig.scale_y
level = array(level)
if len(level.shape) == 0:
x = [level, level]
y = [0, 1]
else:
x = column_stack([level, level])
# TODO: repeating [0, 1] should not be required once we allow for
# 2-D x and 1-D y
y = [[0, 1]] * len(level)
return plot(x, y, scales=scales, preserve_domain={
'x': kwargs.get('preserve_domain', False),
'y': True
}, axes=False, update_context=False, **kwargs)
def _process_cmap(cmap):
'''
Returns a kwarg dict suitable for a ColorScale
'''
option = {}
if isinstance(cmap, str):
option['scheme'] = cmap
elif isinstance(cmap, list):
option['colors'] = cmap
else:
raise ValueError('''`cmap` must be a string (name of a color scheme)
or a list of colors, but a value of {} was given
'''.format(cmap))
return option
def set_cmap(cmap):
'''
Set the color map of the current 'color' scale.
'''
scale = _context['scales']['color']
for k, v in _process_cmap(cmap).items():
setattr(scale, k, v)
return scale
def _draw_mark(mark_type, options={}, axes_options={}, **kwargs):
"""Draw the mark of specified mark type.
Parameters
----------
mark_type: type
The type of mark to be drawn
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
figure: Figure or None
The figure to which the mark is to be added.
If the value is None, the current figure is used.
cmap: list or string
List of css colors, or name of bqplot color scheme
"""
fig = kwargs.pop('figure', current_figure())
scales = kwargs.pop('scales', {})
update_context = kwargs.pop('update_context', True)
# Set the color map of the color scale
cmap = kwargs.pop('cmap', None)
if cmap is not None:
# Add the colors or scheme to the color scale options
options['color'] = dict(options.get('color', {}),
**_process_cmap(cmap))
# Going through the list of data attributes
for name in mark_type.class_trait_names(scaled=True):
dimension = _get_attribute_dimension(name, mark_type)
# TODO: the following should also happen if name in kwargs and
# scales[name] is incompatible.
if name not in kwargs:
# The scaled attribute is not being passed to the mark. So no need
# create a scale for this.
continue
elif name in scales:
if update_context:
_context['scales'][dimension] = scales[name]
# Scale has to be fetched from the context or created as it has not
# been passed.
elif dimension not in _context['scales']:
# Creating a scale for the dimension if a matching scale is not
# present in _context['scales']
traitlet = mark_type.class_traits()[name]
rtype = traitlet.get_metadata('rtype')
dtype = traitlet.validate(None, kwargs[name]).dtype
# Fetching the first matching scale for the rtype and dtype of the
# scaled attributes of the mark.
compat_scale_types = [
Scale.scale_types[key]
for key in Scale.scale_types
if Scale.scale_types[key].rtype == rtype and
issubdtype(dtype, Scale.scale_types[key].dtype)
]
sorted_scales = sorted(compat_scale_types,
key=lambda x: x.precedence)
scales[name] = sorted_scales[-1](**options.get(name, {}))
# Adding the scale to the context scales
if update_context:
_context['scales'][dimension] = scales[name]
else:
scales[name] = _context['scales'][dimension]
mark = mark_type(scales=scales, **kwargs)
_context['last_mark'] = mark
fig.marks = [m for m in fig.marks] + [mark]
if kwargs.get('axes', True):
axes(mark, options=axes_options)
return mark
def _infer_x_for_line(y):
"""
Infers the x for a line if no x is provided.
"""
array_shape = shape(y)
if len(array_shape) == 0:
return []
if len(array_shape) == 1:
return arange(array_shape[0])
if len(array_shape) > 1:
return arange(array_shape[1])
@_process_data('color')
def plot(*args, **kwargs):
"""Draw lines in the current context figure.
Signature: `plot(x, y, **kwargs)` or `plot(y, **kwargs)`, depending of the
length of the list of positional arguments. In the case where the `x` array
is not provided.
Parameters
----------
x: numpy.ndarray or list, 1d or 2d (optional)
The x-coordinates of the plotted line. When not provided, the function
defaults to `numpy.arange(len(y))`
x can be 1-dimensional or 2-dimensional.
y: numpy.ndarray or list, 1d or 2d
The y-coordinates of the plotted line. If argument `x` is 2-dimensional
it must also be 2-dimensional.
marker_str: string
string representing line_style, marker and color.
For e.g. 'g--o', 'sr' etc
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
figure: Figure or None
The figure to which the line is to be added.
If the value is None, the current figure is used.
"""
marker_str = None
if len(args) == 1:
kwargs['y'] = args[0]
if kwargs.get('index_data', None) is not None:
kwargs['x'] = kwargs['index_data']
else:
kwargs['x'] = _infer_x_for_line(args[0])
elif len(args) == 2:
if type(args[1]) == str:
kwargs['y'] = args[0]
kwargs['x'] = _infer_x_for_line(args[0])
marker_str = args[1].strip()
else:
kwargs['x'] = args[0]
kwargs['y'] = args[1]
elif len(args) == 3:
kwargs['x'] = args[0]
kwargs['y'] = args[1]
if type(args[2]) == str:
marker_str = args[2].strip()
if marker_str:
line_style, color, marker = _get_line_styles(marker_str)
# only marker specified => draw scatter
if marker and not line_style:
kwargs['marker'] = marker
if color:
kwargs['colors'] = [color]
return _draw_mark(Scatter, **kwargs)
else: # draw lines in all other cases
kwargs['line_style'] = line_style or 'solid'
if marker:
kwargs['marker'] = marker
if color:
kwargs['colors'] = [color]
return _draw_mark(Lines, **kwargs)
else:
return _draw_mark(Lines, **kwargs)
def imshow(image, format, **kwargs):
"""Draw an image in the current context figure.
Parameters
----------
image: image data
Image data, depending on the passed format, can be one of:
- an instance of an ipywidgets Image
- a file name
- a raw byte string
format: {'widget', 'filename', ...}
Type of the input argument.
If not 'widget' or 'filename', must be a format supported by
the ipywidgets Image.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
if format == 'widget':
ipyimage = image
elif format == 'filename':
with open(image, 'rb') as f:
data = f.read()
ipyimage = ipyImage(value=data)
else:
ipyimage = ipyImage(value=image, format=format)
kwargs['image'] = ipyimage
kwargs.setdefault('x', [0., 1.])
kwargs.setdefault('y', [0., 1.])
return _draw_mark(Image, **kwargs)
def ohlc(*args, **kwargs):
"""Draw OHLC bars or candle bars in the current context figure.
Signature: `ohlc(x, y, **kwargs)` or `ohlc(y, **kwargs)`, depending of the
length of the list of positional arguments. In the case where the `x` array
is not provided
Parameters
----------
x: numpy.ndarray or list, 1d (optional)
The x-coordinates of the plotted line. When not provided, the function
defaults to `numpy.arange(len(y))`.
y: numpy.ndarray or list, 2d
The ohlc (open/high/low/close) information. A two dimensional array. y
must have the shape (n, 4).
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
if len(args) == 2:
kwargs['x'] = args[0]
kwargs['y'] = args[1]
elif len(args) == 1:
kwargs['y'] = args[0]
length = len(args[0])
kwargs['x'] = arange(length)
return _draw_mark(OHLC, **kwargs)
@_process_data('color', 'opacity', 'size', 'skew', 'rotation')
def scatter(x, y, use_gl=None, **kwargs):
"""Draw a scatter in the current context figure.
Parameters
----------
x: numpy.ndarray, 1d
The x-coordinates of the data points.
y: numpy.ndarray, 1d
The y-coordinates of the data points.
use_gl: If true, will use the ScatterGL mark (pixelized but faster), if false a normal
Scatter mark is used. If None, a choised is made automatically depending on the length
of x.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
kwargs['x'] = x
kwargs['y'] = y
if use_gl is None:
mark_class = ScatterGL if len(x) >= SCATTER_SIZE_LIMIT else Scatter
else:
mark_class = ScatterGL if use_gl else Scatter
return _draw_mark(mark_class, **kwargs)
@_process_data()
def hist(sample, options={}, **kwargs):
"""Draw a histogram in the current context figure.
Parameters
----------
sample: numpy.ndarray, 1d
The sample for which the histogram must be generated.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'counts'
is required for that mark, options['counts'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'counts' is
required for that mark, axes_options['counts'] contains optional
keyword arguments for the constructor of the corresponding axis type.
"""
kwargs['sample'] = sample
scales = kwargs.pop('scales', {})
if 'count' not in scales:
dimension = _get_attribute_dimension('count', Hist)
if dimension in _context['scales']:
scales['count'] = _context['scales'][dimension]
else:
scales['count'] = LinearScale(**options.get('count', {}))
_context['scales'][dimension] = scales['count']
kwargs['scales'] = scales
return _draw_mark(Hist, options=options, **kwargs)
@_process_data()
def bin(sample, options={}, **kwargs):
"""Draw a histogram in the current context figure.
Parameters
----------
sample: numpy.ndarray, 1d
The sample for which the histogram must be generated.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x'
is required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is
required for that mark, axes_options['x'] contains optional
keyword arguments for the constructor of the corresponding axis type.
"""
kwargs['sample'] = sample
scales = kwargs.pop('scales', {})
for xy in ['x', 'y']:
if xy not in scales:
dimension = _get_attribute_dimension(xy, Bars)
if dimension in _context['scales']:
scales[xy] = _context['scales'][dimension]
else:
scales[xy] = LinearScale(**options.get(xy, {}))
_context['scales'][dimension] = scales[xy]
kwargs['scales'] = scales
return _draw_mark(Bins, options=options, **kwargs)
@_process_data('color')
def bar(x, y, **kwargs):
"""Draws a bar chart in the current context figure.
Parameters
----------
x: numpy.ndarray, 1d
The x-coordinates of the data points.
y: numpy.ndarray, 1d
The y-coordinates of the data pints.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
kwargs['x'] = x
kwargs['y'] = y
return _draw_mark(Bars, **kwargs)
@_process_data()
def boxplot(x, y, **kwargs):
"""Draws a boxplot in the current context figure.
Parameters
----------
x: numpy.ndarray, 1d
The x-coordinates of the data points.
y: numpy.ndarray, 2d
The data from which the boxes are to be created. Each row of the data
corresponds to one box drawn in the plot.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
kwargs['x'] = x
kwargs['y'] = y
return _draw_mark(Boxplot, **kwargs)
@_process_data('color')
def barh(*args, **kwargs):
"""Draws a horizontal bar chart in the current context figure.
Parameters
----------
x: numpy.ndarray, 1d
The domain of the data points.
y: numpy.ndarray, 1d
The range of the data pints.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
kwargs['orientation'] = "horizontal"
return bar(*args, **kwargs)
@_process_data('color')
def pie(sizes, **kwargs):
"""Draws a Pie in the current context figure.
Parameters
----------
sizes: numpy.ndarray, 1d
The proportions to be represented by the Pie.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
kwargs['sizes'] = sizes
return _draw_mark(Pie, **kwargs)
def label(text, **kwargs):
"""Draws a Label in the current context figure.
Parameters
----------
text: string
The label to be displayed.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
kwargs['text'] = text
return _draw_mark(Label, **kwargs)
def geo(map_data, **kwargs):
"""Draw a map in the current context figure.
Parameters
----------
map_data: string or bqplot.map (default: WorldMap)
Name of the map or json file required for the map data.
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
scales = kwargs.pop('scales', _context['scales'])
options = kwargs.get('options', {})
if 'projection' not in scales:
scales['projection'] = Mercator(**options.get('projection', {}))
kwargs['scales'] = scales
if isinstance(map_data, string_types):
kwargs['map_data'] = topo_load('map_data/' + map_data + '.json')
else:
kwargs['map_data'] = map_data
return _draw_mark(Map, **kwargs)
def heatmap(color, **kwargs):
"""Draw a heatmap in the current context figure.
Parameters
----------
color: numpy.ndarray, 2d
Matrix of color of the data points
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
kwargs['color'] = color
return _draw_mark(HeatMap, **kwargs)
def gridheatmap(color, **kwargs):
"""Draw a GridHeatMap in the current context figure.
Parameters
----------
color: numpy.ndarray, 2d
Matrix of color of the data points
options: dict (default: {})
Options for the scales to be created. If a scale labeled 'x' is
required for that mark, options['x'] contains optional keyword
arguments for the constructor of the corresponding scale type.
axes_options: dict (default: {})
Options for the axes to be created. If an axis labeled 'x' is required
for that mark, axes_options['x'] contains optional keyword arguments
for the constructor of the corresponding axis type.
"""
kwargs['color'] = color
return _draw_mark(GridHeatMap, **kwargs)
def _add_interaction(int_type, **kwargs):
"""Add the interaction for the specified type.
If a figure is passed using the key-word argument `figure` it is used. Else
the context figure is used.
If a list of marks are passed using the key-word argument `marks` it
is used. Else the latest mark that is passed is used as the only mark
associated with the selector.
Parameters
----------
int_type: type
The type of interaction to be added.
"""
fig = kwargs.pop('figure', current_figure())
marks = kwargs.pop('marks', [_context['last_mark']])
for name, traitlet in int_type.class_traits().items():
dimension = traitlet.get_metadata('dimension')
if dimension is not None:
# only scales have this attribute in interactions
kwargs[name] = _get_context_scale(dimension)
kwargs['marks'] = marks
interaction = int_type(**kwargs)
if fig.interaction is not None:
fig.interaction.close()
fig.interaction = interaction
return interaction
def _get_context_scale(dimension):
"""Return the scale instance in the current context for a given dimension.
Parameters
----------
dimension: string
The dimension along which the current context scale is to be fetched.
"""
return _context['scales'][dimension]
def _create_selector(int_type, func, trait, **kwargs):
"""Create a selector of the specified type.
Also attaches the function `func` as an `on_trait_change` listener
for the trait `trait` of the selector.
This is an internal function which should not be called by the user.
Parameters
----------
int_type: type
The type of selector to be added.
func: function
The call back function. It should take atleast two arguments. The name
of the trait and the value of the trait are passed as arguments.
trait: string
The name of the Selector trait whose change triggers the
call back function `func`.
"""
interaction = _add_interaction(int_type, **kwargs)
if func is not None:
interaction.on_trait_change(func, trait)
return interaction
def brush_int_selector(func=None, trait='selected', **kwargs):
"""Create a `BrushIntervalSelector` interaction for the `figure`.
Also attaches the function `func` as an event listener for the
specified trait.
Parameters
----------
func: function
The call back function. It should take atleast two arguments. The name
of the trait and the value of the trait are passed as arguments.
trait: string
The name of the BrushIntervalSelector trait whose change triggers the
call back function `func`.
"""
return _create_selector(BrushIntervalSelector, func, trait, **kwargs)
def int_selector(func=None, trait='selected', **kwargs):
"""Creates a `FastIntervalSelector` interaction for the `figure`.
Also attaches the function `func` as an event listener for the
trait `trait`.
Parameters
----------
func: function
The call back function. It should take atleast two arguments. The name
of the trait and the value of the trait are passed as arguments.
trait: string
The name of the IntervalSelector trait whose change triggers the
call back function `func`.
"""
return _create_selector(FastIntervalSelector, func, trait, **kwargs)
def index_selector(func=None, trait='selected', **kwargs):
"""Creates an `IndexSelector` interaction for the `figure`.
Also attaches the function `func` as an event listener for the
trait `trait`.
Parameters
----------
func: function
The call back function. It should take atleast two arguments. The name
of the trait and the value of the trait are passed as arguments.
trait: string
The name of the IndexSelector trait whose change triggers the
call back function `func`.
"""
return _create_selector(IndexSelector, func, trait, **kwargs)
def brush_selector(func=None, trait='selected', **kwargs):
"""Creates a `BrushSelector` interaction for the `figure`.
Also attaches the function `func` as an event listener for the
trait `trait`.
Parameters
----------
func: function
The call back function. It should take atleast two arguments. The name
of the trait and the value of the trait are passed as arguments.
trait: string
The name of the BrushSelector trait whose change triggers the
call back function `func`.
"""
return _create_selector(BrushSelector, func, trait, **kwargs)
def multi_selector(func=None, trait='selected', **kwargs):
"""Creates a `MultiSelector` interaction for the `figure`.
Also attaches the function `func` as an event listener for the
trait `trait`.
Parameters
----------
func: function
The call back function. It should take atleast two arguments. The name
of the trait and the value of the trait are passed as arguments.
trait: string
The name of the MultiSelector trait whose change triggers the
call back function `func`.
"""
return _create_selector(MultiSelector, func, trait, **kwargs)
def lasso_selector(func=None, trait='selected', **kwargs):
"""Creates a `LassoSelector` interaction for the `figure`.
Also attaches the function `func` as an event listener for the
specified trait.
Parameters
----------
func: function
The call back function. It should take atleast two arguments. The name
of the trait and the value of the trait are passed as arguments.
trait: string
The name of the LassoSelector trait whose change triggers the
call back function `func`.
"""
return _create_selector(LassoSelector, func, trait, **kwargs)
def clear():
"""Clears the current context figure of all marks axes and grid lines."""
fig = _context['figure']
if fig is not None:
fig.marks = []
fig.axes = []
setattr(fig, 'axis_registry', {})
_context['scales'] = {}
key = _context['current_key']
if key is not None:
_context['scale_registry'][key] = {}
def current_figure():
"""Returns the current context figure."""
if _context['figure'] is None:
figure()
return _context['figure']
# FOR DEBUG ONLY
def get_context():
"""Used for debug only. Return a copy of the current global
context dictionary."""
return {k: v for k, v in _context.items()}
def set_context(context):
"""Sets the current global context dictionary. All the attributes to be set
should be set. Otherwise, it will result in unpredictable behavior."""
global _context
_context = {k: v for k, v in context.items()}
def _fetch_axis(fig, dimension, scale):
# Internal utility function.
# Given a figure instance `fig`, the dimension of the scaled attribute and
# the instance of a scale, returns the axis if an axis is present for that
# combination. Else returns `None`
axis_registry = getattr(fig, 'axis_registry', {})
dimension_data = axis_registry.get(dimension, [])
dimension_scales = [dim['scale'] for dim in dimension_data]
dimension_axes = [dim['axis'] for dim in dimension_data]
try:
return dimension_axes[dimension_scales.index(scale)]
except (ValueError, IndexError):
return None
def _update_fig_axis_registry(fig, dimension, scale, axis):
axis_registry = fig.axis_registry
dimension_scales = axis_registry.get(dimension, [])
dimension_scales.append({'scale': scale, 'axis': axis})
axis_registry[dimension] = dimension_scales
setattr(fig, 'axis_registry', axis_registry)
def _get_attribute_dimension(trait_name, mark_type=None):
"""Returns the dimension for the name of the trait for the specified mark.
If `mark_type` is `None`, then the `trait_name` is returned
as is.
Returns `None` if the `trait_name` is not valid for `mark_type`.
"""
if(mark_type is None):
return trait_name
scale_metadata = mark_type.class_traits()['scales_metadata']\
.default_args[0]
return scale_metadata.get(trait_name, {}).get('dimension', None)
def _apply_properties(widget, properties={}):
"""Applies the specified properties to the widget.
`properties` is a dictionary with key value pairs corresponding
to the properties to be applied to the widget.
"""
with widget.hold_sync():
for key, value in properties.items():
setattr(widget, key, value)
def _get_line_styles(marker_str):
"""Return line style, color and marker type from specified marker string.
For example, if ``marker_str`` is 'g-o' then the method returns
``('solid', 'green', 'circle')``.
"""
def _extract_marker_value(marker_str, code_dict):
"""Extracts the marker value from a given marker string.
Looks up the `code_dict` and returns the corresponding marker for a
specific code.
For example if `marker_str` is 'g-o' then the method extracts
- 'green' if the code_dict is color_codes,
- 'circle' if the code_dict is marker_codes etc.
"""
val = None
for code in code_dict:
if code in marker_str:
val = code_dict[code]
break
return val
return [_extract_marker_value(marker_str, code_dict) for
code_dict in [LINE_STYLE_CODES, COLOR_CODES, MARKER_CODES]]