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})