def __init__(self, *args, **kwargs): """Create a Vega Grouped Bar Chart""" if 'grouped' not in kwargs: kwargs['grouped'] = True super(GroupedBar, self).__init__(*args, **kwargs) del self.data['stats'] del self.scales['y'].type self.scales['y'].domain.field = 'data.val' self.scales['y'].domain.data = 'table' self.scales['x'].padding = 0.2 del self.marks[0].from_.transform[1] self.marks[0].from_.transform[0].keys[0] = 'data.idx' enter_props = PropertySet(x=ValueRef(scale='x', field="key"), width=ValueRef(scale='x', band=True)) self.marks[0].properties = MarkProperties(enter=enter_props) self.marks[0].scales = KeyedList() self.marks[0].scales['pos'] = Scale(name='pos', type='ordinal', range='width', domain=DataRef(field='data.group')) self.marks[0].marks[0].properties.enter.width.scale = 'pos' self.marks[0].marks[0].properties.enter.y.field = 'data.val' self.marks[0].marks[0].properties.enter.x.field = 'data.group' self.marks[0].marks[0].properties.enter.x.scale = 'pos' del self.marks[0].marks[0].properties.enter.y2.field self.marks[0].marks[0].properties.enter.y2.value = 0
def __init__(self, *args, **kwargs): """Initialize a Visualization In addition to setting any attributes, this sets the data, marks, scales, and axes properties to empty KeyedLists if they aren't defined by the arguments. """ super(Visualization, self).__init__(*args, **kwargs) for attrib in ('data', 'scales'): if not getattr(self, attrib): setattr(self, attrib, KeyedList(attr_name='name')) # The axes get keyed by "type" instead of name. if not self.axes: self.axes = KeyedList(attr_name='type') # Marks and Legends don't get keyed. if not self.marks: self.marks = [] if not self.legends: self.legends = []
class Visualization(GrammarClass): """Visualization container class. This class defines the full visualization. Calling its ``to_json`` method should return a complete Vega definition. The sub-elements of the visualization are stored in the ``data``, ``axes``, ``marks``, and ``scales`` attributes. See the docs for each attribute for details. """ def __init__(self, *args, **kwargs): """Initialize a Visualization In addition to setting any attributes, this sets the data, marks, scales, and axes properties to empty KeyedLists if they aren't defined by the arguments. """ super(Visualization, self).__init__(*args, **kwargs) for attrib in ('data', 'scales'): if not getattr(self, attrib): setattr(self, attrib, KeyedList(attr_name='name')) # The axes get keyed by "type" instead of name. if not self.axes: self.axes = KeyedList(attr_name='type') # Marks and Legends don't get keyed. if not self.marks: self.marks = [] if not self.legends: self.legends = [] @grammar(str) def name(value): """string : Name of the visualization (optional) """ @grammar(int) def width(value): """int : Width of the visualization in pixels Default is 500 if undefined. """ if value < 0: raise ValueError('width cannot be negative') @grammar(int) def height(value): """int : Height of the visualization in pixels Default is 500 if undefined. """ if value < 0: raise ValueError('height cannot be negative') @grammar(list) def viewport(value): """2-element list of ints : Dimensions of the viewport The viewport is a bounding box containing the visualization. If the dimensions of the visualization are larger than the viewport, then the visualization will be scrollable. If undefined, then the full visualization is shown. """ if len(value) != 2: raise ValueError('viewport must have 2 dimensions') for v in value: _assert_is_type('viewport dimension', v, int) if v < 0: raise ValueError('viewport dimensions cannot be negative') @grammar((int, dict)) def padding(value): """int or dict : Padding around visualization The padding defines the distance between the edge of the visualization canvas to the visualization box. It does not count as part of the visualization width/height. Values cannot be negative. If a dict, padding must have all keys ``''top'``, ``'left'``, ``'right'``, and ``'bottom'`` with int values. """ if isinstance(value, dict): required_keys = ['top', 'left', 'right', 'bottom'] for key in required_keys: if key not in value: error = ('Padding must have keys "{0}".' .format('", "'.join(required_keys))) raise ValueError(error) _assert_is_type('padding: {0}'.format(key), value[key], int) if value[key] < 0: raise ValueError('Padding cannot be negative.') else: if value < 0: raise ValueError('Padding cannot be negative.') @grammar((list, KeyedList)) def data(value): """list or KeyedList of ``Data`` : Data definitions This defines the data being visualized. See the :class:`Data` class for details. """ for i, entry in enumerate(value): _assert_is_type('data[{0}]'.format(i), entry, Data) @grammar((list, KeyedList)) def scales(value): """list or KeyedList of ``Scale`` : Scale definitions Scales map the data from the domain of the data to some visualization space (such as an x-axis). See the :class:`Scale` class for details. """ for i, entry in enumerate(value): _assert_is_type('scales[{0}]'.format(i), entry, Scale) @grammar((list, KeyedList)) def axes(value): """list or KeyedList of ``Axis`` : Axis definitions Axes define the locations of the data being mapped by the scales. See the :class:`Axis` class for details. """ for i, entry in enumerate(value): _assert_is_type('axes[{0}]'.format(i), entry, Axis) @grammar((list, KeyedList)) def marks(value): """list or KeyedList of ``Mark`` : Mark definitions Marks are the visual objects (such as lines, bars, etc.) that represent the data in the visualization space. See the :class:`Mark` class for details. """ for i, entry in enumerate(value): _assert_is_type('marks[{0}]'.format(i), entry, Mark) @grammar((list, KeyedList)) def legends(value): """list or KeyedList of ``Legends`` : Legend definitions Legends visualize scales, and take one or more scales as their input. They can be customized via a LegendProperty object. """ for i, entry in enumerate(value): _assert_is_type('legends[{0}]'.format(i), entry, Legend) def axis_titles(self, x=None, y=None): """Apply axis titles to the figure. This is a convenience method for manually modifying the "Axes" mark. Parameters ---------- x: string, default 'null' X-axis title y: string, default 'null' Y-axis title Example ------- >>>vis.axis_titles(y="Data 1", x="Data 2") """ keys = self.axes.get_keys() if keys: for key in keys: if key == 'x': self.axes[key].title = x elif key == 'y': self.axes[key].title = y else: self.axes.extend([Axis(type='x', title=x), Axis(type='y', title=y)]) def legend(self, title=None, scale='color'): """Convience method for adding a legend to the figure. Important: This defaults to the color scale that is generated with Line, Area, Stacked Line, etc charts. For bar charts, the scale ref is usually 'y'. Parameters ---------- title: string, default None Legend Title scale: string, default 'color' Scale reference for legend """ self.legends.append(Legend(title=title, fill=scale, offset=0)) def colors(self, brew=None): """Convenience method for adding color brewer scales to charts with a color scale, such as stacked or grouped bars. See the colors here: http://colorbrewer2.org/ Or here: http://bl.ocks.org/mbostock/5577023 This assumes that a 'color' scale exists on your chart. Parameters ---------- brew: string, default None Color brewer scheme (BuGn, YlOrRd, etc) """ self.scales['color'].range = brews[brew] def validate(self, require_all=True, scale='colors'): """Validate the visualization contents. Parameters ---------- require_all : boolean, default True If True (default), then all fields ``data``, ``scales``, ``axes``, and ``marks`` must be defined. The user is allowed to disable this if the intent is to define the elements client-side. If the contents of the visualization are not valid Vega, then a :class:`ValidationError` is raised. """ super(self.__class__, self).validate() required_attribs = ('data', 'scales', 'axes', 'marks') for elem in required_attribs: attr = getattr(self, elem) if attr: # Validate each element of the sets of data, etc for entry in attr: entry.validate() names = [a.name for a in attr] if len(names) != len(set(names)): raise ValidationError(elem + ' has duplicate names') elif require_all: raise ValidationError( elem + ' must be defined for valid visualization') def display(self): """Display visualization inline in IPython notebook""" from IPython.core.display import display, HTML, Javascript # Copied from vincent.ipynb: # HACK: use a randomly chosen unique div id id = random.randint(0, 2 ** 16) a = HTML('<div id="vis%d"></div>' % id) b = Javascript('vg.parse.spec(%s, function(chart) ' '{ chart({el:"#vis%d"}).update(); });' % (self.to_json(pretty_print=False), id)) display(a, b)
class Visualization(GrammarClass): """Visualization container class. This class defines the full visualization. Calling its ``to_json`` method should return a complete Vega definition. The sub-elements of the visualization are stored in the ``data``, ``axes``, ``marks``, and ``scales`` attributes. See the docs for each attribute for details. """ def __init__(self, *args, **kwargs): """Initialize a Visualization In addition to setting any attributes, this sets the data, marks, scales, and axes properties to empty KeyedLists if they aren't defined by the arguments. """ super(Visualization, self).__init__(*args, **kwargs) for attrib in ('data', 'scales'): if not getattr(self, attrib): setattr(self, attrib, KeyedList(attr_name='name')) # The axes get keyed by "type" instead of name. if not self.axes: self.axes = KeyedList(attr_name='type') # Marks and Legends don't get keyed. if not self.marks: self.marks = [] if not self.legends: self.legends = [] @grammar(str) def name(value): """string : Name of the visualization (optional) """ @grammar(int) def width(value): """int : Width of the visualization in pixels Default is 500 if undefined. """ if value < 0: raise ValueError('width cannot be negative') @grammar(int) def height(value): """int : Height of the visualization in pixels Default is 500 if undefined. """ if value < 0: raise ValueError('height cannot be negative') @grammar(list) def viewport(value): """2-element list of ints : Dimensions of the viewport The viewport is a bounding box containing the visualization. If the dimensions of the visualization are larger than the viewport, then the visualization will be scrollable. If undefined, then the full visualization is shown. """ if len(value) != 2: raise ValueError('viewport must have 2 dimensions') for v in value: _assert_is_type('viewport dimension', v, int) if v < 0: raise ValueError('viewport dimensions cannot be negative') @grammar((int, dict, str)) def padding(value): """int or dict : Padding around visualization The padding defines the distance between the edge of the visualization canvas to the visualization box. It does not count as part of the visualization width/height. Values cannot be negative. If a dict, padding must have all keys ``''top'``, ``'left'``, ``'right'``, and ``'bottom'`` with int values. """ if isinstance(value, dict): required_keys = ['top', 'left', 'right', 'bottom'] for key in required_keys: if key not in value: error = ('Padding must have keys "{0}".'.format( '", "'.join(required_keys))) raise ValueError(error) _assert_is_type('padding: {0}'.format(key), value[key], int) if value[key] < 0: raise ValueError('Padding cannot be negative.') else: if value < 0: raise ValueError('Padding cannot be negative.') @grammar((list, KeyedList)) def data(value): """list or KeyedList of ``Data`` : Data definitions This defines the data being visualized. See the :class:`Data` class for details. """ for i, entry in enumerate(value): _assert_is_type('data[{0}]'.format(i), entry, Data) @grammar((list, KeyedList)) def scales(value): """list or KeyedList of ``Scale`` : Scale definitions Scales map the data from the domain of the data to some visualization space (such as an x-axis). See the :class:`Scale` class for details. """ for i, entry in enumerate(value): _assert_is_type('scales[{0}]'.format(i), entry, Scale) @grammar((list, KeyedList)) def axes(value): """list or KeyedList of ``Axis`` : Axis definitions Axes define the locations of the data being mapped by the scales. See the :class:`Axis` class for details. """ for i, entry in enumerate(value): _assert_is_type('axes[{0}]'.format(i), entry, Axis) @grammar((list, KeyedList)) def marks(value): """list or KeyedList of ``Mark`` : Mark definitions Marks are the visual objects (such as lines, bars, etc.) that represent the data in the visualization space. See the :class:`Mark` class for details. """ for i, entry in enumerate(value): _assert_is_type('marks[{0}]'.format(i), entry, Mark) @grammar((list, KeyedList)) def legends(value): """list or KeyedList of ``Legends`` : Legend definitions Legends visualize scales, and take one or more scales as their input. They can be customized via a LegendProperty object. """ for i, entry in enumerate(value): _assert_is_type('legends[{0}]'.format(i), entry, Legend) def axis_titles(self, x=None, y=None): """Apply axis titles to the figure. This is a convenience method for manually modifying the "Axes" mark. Parameters ---------- x: string, default 'null' X-axis title y: string, default 'null' Y-axis title Example ------- >>>vis.axis_titles(y="Data 1", x="Data 2") """ keys = self.axes.get_keys() if keys: for key in keys: if key == 'x': self.axes[key].title = x elif key == 'y': self.axes[key].title = y else: self.axes.extend( [Axis(type='x', title=x), Axis(type='y', title=y)]) def legend(self, title=None, scale='color'): """Convience method for adding a legend to the figure. Important: This defaults to the color scale that is generated with Line, Area, Stacked Line, etc charts. For bar charts, the scale ref is usually 'y'. Parameters ---------- title: string, default None Legend Title scale: string, default 'color' Scale reference for legend """ self.legends.append(Legend(title=title, fill=scale, offset=0)) def colors(self, brew=None): """Convenience method for adding color brewer scales to charts with a color scale, such as stacked or grouped bars. See the colors here: http://colorbrewer2.org/ Or here: http://bl.ocks.org/mbostock/5577023 This assumes that a 'color' scale exists on your chart. Parameters ---------- brew: string, default None Color brewer scheme (BuGn, YlOrRd, etc) """ self.scales['color'].range = brews[brew] def validate(self, require_all=True, scale='colors'): """Validate the visualization contents. Parameters ---------- require_all : boolean, default True If True (default), then all fields ``data``, ``scales``, ``axes``, and ``marks`` must be defined. The user is allowed to disable this if the intent is to define the elements client-side. If the contents of the visualization are not valid Vega, then a :class:`ValidationError` is raised. """ super(self.__class__, self).validate() required_attribs = ('data', 'scales', 'axes', 'marks') for elem in required_attribs: attr = getattr(self, elem) if attr: # Validate each element of the sets of data, etc for entry in attr: entry.validate() names = [a.name for a in attr] if len(names) != len(set(names)): raise ValidationError(elem + ' has duplicate names') elif require_all: raise ValidationError( elem + ' must be defined for valid visualization') def display(self): """Display visualization inline in IPython notebook""" from IPython.core.display import display, HTML, Javascript # Copied from vincent.ipynb: # HACK: use a randomly chosen unique div id id = random.randint(0, 2**16) a = HTML('<div id="vis%d"></div>' % id) b = Javascript('vg.parse.spec(%s, function(chart) ' '{ chart({el:"#vis%d"}).update(); });' % (self.to_json(pretty_print=False), id)) display(a, b)