From f2b276b772b23bed99f2573d2dfc2beba8a359fd Mon Sep 17 00:00:00 2001
From: philippjfr
Date: Thu, 30 Apr 2015 16:30:25 +0100
Subject: [PATCH] Added ErrorBars Element
---
holoviews/element/chart.py | 33 ++++++++++++++++
holoviews/plotting/__init__.py | 1 +
holoviews/plotting/chart.py | 71 +++++++++++++++++++++++++++++++++-
3 files changed, 103 insertions(+), 2 deletions(-)
diff --git a/holoviews/element/chart.py b/holoviews/element/chart.py
index 84feadefd0..26fbe6a9ea 100644
--- a/holoviews/element/chart.py
+++ b/holoviews/element/chart.py
@@ -239,6 +239,39 @@ def progressive(self):
+class ErrorBars(Chart):
+ """
+ ErrorBars is a Chart Element type representing any number of
+ errorbars situated in a 2D space. The errors must be supplied
+ as an Nx3 or Nx4 array representing the x/y-positions and
+ either the symmetric error or assymetric errors respectively.
+ Internally the data is always held as an Nx4 array with the
+ lower and upper bounds.
+ """
+
+ group = param.String(default='ErrorBars', doc="""
+ A string describing the quantitity measured by the ErrorBars
+ object.""")
+
+ key_dimensions = param.List(default=[Dimension('x'), Dimension('y')],
+ bounds=(2, 2), constant=True, doc="""
+ The Dimensions corresponding to the x- and y-positions of
+ the error bars.""")
+
+ value_dimensions = param.List(default=[Dimension('lerror'), Dimension('uerror')],
+ bounds=(2,2), constant=True)
+
+ def __init__(self, data, **params):
+ data = list(data)
+ data = self._null_value if (data is None) or (len(data) == 0) else data
+ if len(data) and not isinstance(data, np.ndarray):
+ data = np.array(data)
+ if data.shape[1] == 3:
+ data = np.hstack([data, np.atleast_2d(data[:, 2]).T])
+ super(ErrorBars, self).__init__(data, **params)
+
+
+
class Bars(NdElement):
"""
Bars is an Element type, representing a number of stacked and
diff --git a/holoviews/plotting/__init__.py b/holoviews/plotting/__init__.py
index b6cbcc1576..9c068d7f55 100644
--- a/holoviews/plotting/__init__.py
+++ b/holoviews/plotting/__init__.py
@@ -299,6 +299,7 @@ def set_style(key):
# Charts
Store.options.Curve = Options('style', color=Cycle(), linewidth=2)
Store.options.Scatter = Options('style', color=Cycle(), marker='o')
+Store.options.ErrorBars = Options('style', ecolor='k')
Store.options.Bars = Options('style', ec='k', color=Cycle())
Store.options.Histogram = Options('style', ec='k', fc=Cycle())
Store.options.Points = Options('style', color=Cycle(), marker='o')
diff --git a/holoviews/plotting/chart.py b/holoviews/plotting/chart.py
index effa945811..b341d0b3c7 100644
--- a/holoviews/plotting/chart.py
+++ b/holoviews/plotting/chart.py
@@ -10,7 +10,7 @@
from ..core.options import Store
from ..core import OrderedDict, NdMapping, ViewableElement, CompositeOverlay, HoloMap
from ..core.util import match_spec
-from ..element import Scatter, Curve, Histogram, Bars, Points, Raster, VectorField
+from ..element import Scatter, Curve, Histogram, Bars, Points, Raster, VectorField, ErrorBars
from .element import ElementPlot
from .plot import Plot
@@ -144,6 +144,72 @@ def update_handles(self, axis, view, key, ranges=None):
+
+class ErrorPlot(ChartPlot):
+ """
+ ErrorPlot plots the ErrorBar Element type and supporting
+ both horizontal and vertical error bars via the vertical
+ plot option.
+ """
+
+ vertical = param.Boolean(default=False, doc="""
+ Whether to draw horizontal or vertical error bars.""")
+
+ style_opts = ['ecolor', 'elinewidth', 'capsize', 'capthick',
+ 'barsabove', 'lolims', 'uplims', 'xlolims',
+ 'errorevery', 'xuplims', 'alpha', 'linestyle',
+ 'linewidth', 'markeredgecolor', 'markeredgewidth',
+ 'markerfacecolor', 'markersize', 'solid_capstyle',
+ 'solid_joinstyle', 'dashes', 'color']
+
+
+ def __call__(self, ranges=None):
+ element = self.map.last
+ axis = self.handles['axis']
+ key = self.keys[-1]
+
+ ranges = self.compute_ranges(self.map, key, ranges)
+ ranges = match_spec(element, ranges)
+
+ error_kwargs = dict(self.style[self.cyclic_index], fmt='none',
+ zorder=self.zorder)
+ kwarg = 'xerr' if self.vertical else 'yerr'
+ error_kwargs[kwarg] = element.data[:, 2:4].T
+ _, (bottoms, tops), verts = axis.errorbar(element.data[:, 0],
+ element.data[:, 1],
+ **error_kwargs)
+ self.handles['bottoms'] = bottoms
+ self.handles['tops'] = tops
+ self.handles['verts'] = verts[0]
+
+ return self._finalize_axis(self.keys[-1], ranges=ranges)
+
+
+ def update_handles(self, axis, view, key, ranges=None):
+ data = view.data
+ bottoms = self.handles['bottoms']
+ tops = self.handles['tops']
+ verts = self.handles['verts']
+ paths = verts.get_paths()
+ if self.vertical:
+ bdata = data[:, 0] - data[:, 2]
+ tdata = data[:, 0] + data[:, 3]
+ tops.set_xdata(bdata)
+ bottoms.set_xdata(tdata)
+ for i, path in enumerate(paths):
+ path.vertices = np.array([[bdata[i], data[i, 1]],
+ [tdata[i], data[i, 1]]])
+ else:
+ bdata = data[:, 1] - data[:, 2]
+ tdata = data[:, 1] + data[:, 3]
+ bottoms.set_ydata(bdata)
+ tops.set_ydata(tdata)
+ for i, path in enumerate(paths):
+ path.vertices = np.array([[data[i, 0], bdata[i]],
+ [data[i, 0], tdata[i]]])
+
+
+
class HistogramPlot(ChartPlot):
"""
HistogramPlot can plot DataHistograms and ViewMaps of
@@ -885,6 +951,7 @@ def update_handles(self, axis, element, key, ranges=None):
Bars: BarPlot,
Histogram: HistogramPlot,
Points: PointPlot,
- VectorField: VectorFieldPlot})
+ VectorField: VectorFieldPlot,
+ ErrorBars: ErrorPlot})
Plot.sideplots.update({Histogram: SideHistogramPlot})