From 9fdad77891d755017ede926cd139e04133ff4286 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 29 Jan 2016 00:04:09 +0000 Subject: [PATCH 1/6] Added Area Element and plotting classes for mpl and bokeh --- holoviews/element/chart.py | 13 ++++++ holoviews/plotting/bokeh/__init__.py | 8 +++- holoviews/plotting/bokeh/chart.py | 33 ++++++++++++-- holoviews/plotting/mpl/__init__.py | 3 ++ holoviews/plotting/mpl/chart.py | 66 ++++++++++++++++++---------- 5 files changed, 95 insertions(+), 28 deletions(-) diff --git a/holoviews/element/chart.py b/holoviews/element/chart.py index 57b049fca8..eab37db4fe 100644 --- a/holoviews/element/chart.py +++ b/holoviews/element/chart.py @@ -379,3 +379,16 @@ class Spikes(Chart): _1d = True + +class Area(Curve): + """ + An Area Element represents the area under a Curve + and is specified in the same format as a regular + Curve, with the key dimension corresponding to a + column of x-values and the value dimension + corresponding to a column of y-values. Optionally + a second value dimension may be supplied to shade + the region between the curves. + """ + + group = param.String(default='Area') diff --git a/holoviews/plotting/bokeh/__init__.py b/holoviews/plotting/bokeh/__init__.py index 725ab4f203..8f1962362d 100644 --- a/holoviews/plotting/bokeh/__init__.py +++ b/holoviews/plotting/bokeh/__init__.py @@ -4,7 +4,8 @@ RGB, Histogram, Spread, HeatMap, Contours, Bars, Box, Bounds, Ellipse, Polygons, BoxWhisker, ErrorBars, Text, HLine, VLine, Spline, Spikes, - Table, ItemTable, Surface, Scatter3D, Trisurface) + Table, ItemTable, Surface, Scatter3D, Trisurface, + Area) from ...core.options import Options, Cycle from ...interface import DFrame from ..plot import PlotSelector @@ -14,7 +15,8 @@ from .callbacks import Callbacks # noqa (API import) from .element import OverlayPlot, BokehMPLWrapper, BokehMPLRawWrapper from .chart import (PointPlot, CurvePlot, SpreadPlot, ErrorPlot, HistogramPlot, - SideHistogramPlot, BoxPlot, BarPlot, SpikesPlot, SideSpikesPlot) + SideHistogramPlot, BoxPlot, BarPlot, SpikesPlot, + SideSpikesPlot, AreaPlot) from .path import PathPlot, PolygonPlot from .plot import GridPlot, LayoutPlot, AdjointLayoutPlot from .raster import RasterPlot, RGBPlot, HeatmapPlot @@ -40,6 +42,7 @@ Spikes: SpikesPlot, BoxWhisker: BoxPlot, Bars: BarPlot, + Area: AreaPlot, # Rasters Image: RasterPlot, @@ -118,6 +121,7 @@ options.Histogram = Options('style', fill_color="#036564", line_color="#033649") options.Points = Options('style', color=Cycle()) options.Spikes = Options('style', color='black') +options.Area = Options('style', color=Cycle(), line_color='black') # Paths options.Contours = Options('style', color=Cycle()) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 8d6f9d6adb..418bf0f655 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -4,6 +4,7 @@ import param from ...element import Raster, Points, Polygons, Spikes +from ...core.util import max_range from ..util import compute_sizes, get_sideplot_ranges, match_spec from .element import ElementPlot, line_properties, fill_properties from .path import PathPlot, PolygonPlot @@ -111,13 +112,38 @@ def get_data(self, element, ranges=None, empty=False): dict(x=x, y=y)) +class AreaPlot(PolygonPlot): + + def get_extents(self, element, ranges): + vdims = element.vdims + vdim = vdims[0].name + if len(vdims) > 1: + ranges[vdim] = max_range([ranges[vd.name] for vd in vdims]) + else: + vdim = vdims[0].name + ranges[vdim] = (np.nanmin([0, ranges[vdim][0]]), ranges[vdim][1]) + return super(AreaPlot, self).get_extents(element, ranges) + + def get_data(self, element, ranges=None, empty=False): + mapping = dict(self._mapping) + if empty: return {'xs': [], 'ys': []} + xs = element.dimension_values(0) + x2 = np.hstack((xs[::-1], xs)) + + if len(element.vdims) > 1: + bottom = element.dimension_values(2) + else: + bottom = np.zeros(len(element)) + ys = np.hstack((bottom[::-1], element.dimension_values(1))) + + data = dict(xs=[x2], ys=[ys]) + return data, mapping + + class SpreadPlot(PolygonPlot): style_opts = ['color'] + line_properties + fill_properties - def __init__(self, *args, **kwargs): - super(SpreadPlot, self).__init__(*args, **kwargs) - def get_data(self, element, ranges=None, empty=None): if empty: return dict(xs=[], ys=[]), self._mapping @@ -477,4 +503,3 @@ def _init_chart(self, element, ranges): plot = Bar(element.dframe(), values=vdim, continuous_range=crange, **kwargs) return plot - diff --git a/holoviews/plotting/mpl/__init__.py b/holoviews/plotting/mpl/__init__.py index 19d93e85e6..e258e86a8d 100644 --- a/holoviews/plotting/mpl/__init__.py +++ b/holoviews/plotting/mpl/__init__.py @@ -110,6 +110,7 @@ def grid_selector(grid): Spread: SpreadPlot, Spikes: SpikesPlot, BoxWhisker: BoxPlot, + Area: AreaPlot, # General plots GridSpace: GridPlot, @@ -180,8 +181,10 @@ def grid_selector(grid): options.Scatter3D = Options('plot', fig_size=150) options.Surface = Options('plot', fig_size=150) options.Spikes = Options('style', color='black') +options.Area = Options('style', color=Cycle(), edgecolor='black') options.BoxWhisker = Options('style', boxprops=dict(color='k'), whiskerprops=dict(color='k')) + # Rasters options.Image = Options('style', cmap='hot', interpolation='nearest') options.Raster = Options('style', cmap='hot', interpolation='nearest') diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 8ec189d1e0..2da7a931b1 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -9,7 +9,8 @@ import param from ...core import OrderedDict -from ...core.util import match_spec, unique_iterator, safe_unicode, basestring +from ...core.util import (match_spec, unique_iterator, safe_unicode, + basestring, max_range) from ...element import Points, Raster, Polygons from ..util import compute_sizes, get_sideplot_ranges from .element import ElementPlot, ColorbarPlot, LegendPlot @@ -228,18 +229,12 @@ def update_handles(self, axis, element, key, ranges=None): [xvals[i], tdata[i]]]) -class SpreadPlot(ChartPlot): - """ - SpreadPlot plots the Spread Element type. - """ - style_opts = ['alpha', 'color', 'linestyle', 'linewidth', - 'edgecolor', 'facecolor', 'hatch'] - - def __init__(self, *args, **kwargs): - super(SpreadPlot, self).__init__(*args, **kwargs) - self._extent = None +class AreaPlot(ChartPlot): + style_opts = ['color', 'facecolor', 'alpha', 'edgecolor', 'linewidth', + 'hatch', 'linestyle', 'joinstyle', + 'fill', 'capstyle', 'interpolate'] def initialize_plot(self, ranges=None): element = self.hmap.last @@ -248,27 +243,54 @@ def initialize_plot(self, ranges=None): ranges = self.compute_ranges(self.hmap, key, ranges) ranges = match_spec(element, ranges) + self.update_handles(axis, element, key, ranges) - return self._finalize_axis(self.keys[-1], ranges=ranges) + ylabel = str(element.vdims[0]) + return self._finalize_axis(self.keys[-1], ranges=ranges, ylabel=ylabel) + + def get_data(self, element): + xs = element.dimension_values(0) + ys = [element.dimension_values(vdim) for vdim in element.vdims] + return tuple([xs]+ys) + def get_extents(self, element, ranges): + vdims = element.vdims + vdim = vdims[0].name + ranges[vdim] = max_range([ranges[vd.name] for vd in vdims]) + return super(AreaPlot, self).get_extents(element, ranges) def update_handles(self, axis, element, key, ranges=None): - if 'paths' in self.handles: - self.handles['paths'].remove() + if 'artist' in self.handles: + self.handles['artist'].remove() - xvals = element.dimension_values(0) + # Create line segments and apply style + style = self.style[self.cyclic_index] + data = self.get_data(element) + fill_fn = axis.fill_betweenx if self.invert_axes else axis.fill_between + stack = fill_fn(*data, zorder=self.zorder, **style) + self.handles['artist'] = stack + + + +class SpreadPlot(AreaPlot): + """ + SpreadPlot plots the Spread Element type. + """ + + + def __init__(self, element, **params): + self.table = element.table() + super(SpreadPlot, self).__init__(element, **params) + self._extents = None + + def get_data(self, element): + xs = element.dimension_values(0) mean = element.dimension_values(1) neg_error = element.dimension_values(2) pos_idx = 3 if len(element.dimensions()) > 3 else 2 pos_error = element.dimension_values(pos_idx) - - paths = axis.fill_between(xvals, mean-neg_error, - mean+pos_error, zorder=self.zorder, - label=element.label if self.show_legend else None, - **self.style[self.cyclic_index]) - self.handles['paths'] = paths - + return xs, mean-neg_error, mean+pos_error class HistogramPlot(ChartPlot): From 1f68433fdbfc8a736e4d4a0d58314fc4725bf81e Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 29 Jan 2016 00:05:17 +0000 Subject: [PATCH 2/6] Added examples of Area Element to Elements Tutorial --- doc/Tutorials/Elements.ipynb | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/doc/Tutorials/Elements.ipynb b/doc/Tutorials/Elements.ipynb index c727f23fb3..ff9d4b3af1 100644 --- a/doc/Tutorials/Elements.ipynb +++ b/doc/Tutorials/Elements.ipynb @@ -28,6 +28,7 @@ "
Curve
A continuous relation between a dependent and an independent variable.
\n", "
ErrorBars
A collection of x-/y-coordinates with associated error magnitudes.
\n", "
Spread
Continuous version of ErrorBars.
\n", + "
Area
\n", "
Bars
Data collected and binned into categories.
\n", "
Histogram
Data collected and binned in a continuous space using specified bin edges.
\n", "
BoxWhisker
Distributions of data varying by 0-N key dimensions.
\n", @@ -280,6 +281,58 @@ " vdims=['y', 'yerrneg', 'yerrpos'])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ``Area`` " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "** *Area under the curve* **\n", + "\n", + "By default the Area Element draws just the area under the curve, i.e. the region between the curve and the origin." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "xs = np.linspace(0, np.pi*4, 40)\n", + "hv.Area((xs, np.sin(xs)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "** * Area between curves * **\n", + "\n", + "When supplied a second value dimension the area is defined as the area between two curves." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "X = np.linspace(0,3,200)\n", + "Y = X**2 + 3\n", + "Y2 = np.exp(X) + 2\n", + "Y3 = np.cos(X)\n", + "hv.Area((X, Y, Y2), vdims=['y', 'y2']) * hv.Area((X, Y, Y3), vdims=['y', 'y3'])" + ] + }, { "cell_type": "markdown", "metadata": {}, From 760df94e9156dd84f1008b916e867f4930588c12 Mon Sep 17 00:00:00 2001 From: philippjfr Date: Fri, 29 Jan 2016 02:18:00 +0000 Subject: [PATCH 3/6] Updated reference_data submodule reference --- doc/reference_data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/reference_data b/doc/reference_data index feca6b6bcd..ab3290b23b 160000 --- a/doc/reference_data +++ b/doc/reference_data @@ -1 +1 @@ -Subproject commit feca6b6bcdb21a5face716f92921e10084bad3c1 +Subproject commit ab3290b23b00438314b42023e63bf788cd6c8cb9 From 864c6dffa1873d9fe866cb96127a853eb7941424 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 29 Jan 2016 02:36:09 +0000 Subject: [PATCH 4/6] Added Area Element comparison --- holoviews/element/comparison.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/holoviews/element/comparison.py b/holoviews/element/comparison.py index 740ed0d934..6fa7aa7a87 100644 --- a/holoviews/element/comparison.py +++ b/holoviews/element/comparison.py @@ -149,6 +149,7 @@ def register(cls): cls.equality_type_funcs[Curve] = cls.compare_curve cls.equality_type_funcs[ErrorBars] = cls.compare_errorbars cls.equality_type_funcs[Spread] = cls.compare_spread + cls.equality_type_funcs[Area] = cls.compare_area cls.equality_type_funcs[Scatter] = cls.compare_scatter cls.equality_type_funcs[Scatter3D] = cls.compare_scatter3d cls.equality_type_funcs[Trisurface] = cls.compare_trisurface @@ -471,6 +472,10 @@ def compare_errorbars(cls, el1, el2, msg='ErrorBars'): def compare_spread(cls, el1, el2, msg='Spread'): cls.compare_columns(el1, el2, msg) + @classmethod + def compare_area(cls, el1, el2, msg='Area'): + cls.compare_columns(el1, el2, msg) + @classmethod def compare_scatter(cls, el1, el2, msg='Scatter'): cls.compare_columns(el1, el2, msg) From 8d2173791b2778a71111454c0a7967e6066ec696 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 29 Jan 2016 02:36:31 +0000 Subject: [PATCH 5/6] Declared Area Element group parameter constant --- holoviews/element/chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/element/chart.py b/holoviews/element/chart.py index eab37db4fe..62062d0ffd 100644 --- a/holoviews/element/chart.py +++ b/holoviews/element/chart.py @@ -391,4 +391,4 @@ class Area(Curve): the region between the curves. """ - group = param.String(default='Area') + group = param.String(default='Area', constant=True) From f6e96fe2faaabca8234e1afb8baf998063621d23 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 29 Jan 2016 03:05:42 +0000 Subject: [PATCH 6/6] Renamed Area matplotlib color with facecolor to avoid ordering issues --- holoviews/plotting/mpl/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/plotting/mpl/__init__.py b/holoviews/plotting/mpl/__init__.py index e258e86a8d..2ee94bbb5e 100644 --- a/holoviews/plotting/mpl/__init__.py +++ b/holoviews/plotting/mpl/__init__.py @@ -181,7 +181,7 @@ def grid_selector(grid): options.Scatter3D = Options('plot', fig_size=150) options.Surface = Options('plot', fig_size=150) options.Spikes = Options('style', color='black') -options.Area = Options('style', color=Cycle(), edgecolor='black') +options.Area = Options('style', facecolor=Cycle(), edgecolor='black') options.BoxWhisker = Options('style', boxprops=dict(color='k'), whiskerprops=dict(color='k'))