From 445dbb213b391db6026c3d26a4d63d1e43ca2fba Mon Sep 17 00:00:00 2001 From: Maximilian Albert Date: Fri, 4 Dec 2015 12:04:16 +0000 Subject: [PATCH 01/86] Fix copy & paste errors in docstrings. --- holoviews/plotting/bokeh/element.py | 2 +- holoviews/plotting/mpl/element.py | 4 ++-- holoviews/plotting/mpl/plot.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 194788634e..b90b0c3e74 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -120,7 +120,7 @@ class ElementPlot(BokehPlot, GenericElementPlot): Whether the y-axis of the plot will be a log axis.""") yrotation = param.Integer(default=None, bounds=(0, 360), doc=""" - Rotation angle of the xticks.""") + Rotation angle of the yticks.""") yticks = param.Parameter(default=None, doc=""" Ticks along y-axis specified as an integer, explicit list of diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index 191ba798ad..c5e87a26a9 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -92,10 +92,10 @@ class ElementPlot(GenericElementPlot, MPLPlot): default matplotlib ticking behavior is applied.""") yrotation = param.Integer(default=0, bounds=(0, 360), doc=""" - Rotation angle of the xticks.""") + Rotation angle of the yticks.""") zrotation = param.Integer(default=0, bounds=(0, 360), doc=""" - Rotation angle of the xticks.""") + Rotation angle of the zticks.""") zticks = param.Parameter(default=None, doc=""" Ticks along z-axis specified as an integer, explicit list of diff --git a/holoviews/plotting/mpl/plot.py b/holoviews/plotting/mpl/plot.py index 512c9895d0..974c88b2c9 100644 --- a/holoviews/plotting/mpl/plot.py +++ b/holoviews/plotting/mpl/plot.py @@ -253,7 +253,7 @@ class GridPlot(CompositePlot): Rotation angle of the xticks.""") yrotation = param.Integer(default=0, bounds=(0, 360), doc=""" - Rotation angle of the xticks.""") + Rotation angle of the yticks.""") def __init__(self, layout, axis=None, create_axes=True, ranges=None, keys=None, dimensions=None, layout_num=1, **params): From 1f2053acb3dd1df75d47911f8c2d383248a768df Mon Sep 17 00:00:00 2001 From: Maximilian Albert Date: Mon, 7 Dec 2015 11:32:32 +0000 Subject: [PATCH 02/86] Fix TypeError caused by int vs. string comparison in Python 3. --- holoviews/plotting/bokeh/element.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index b90b0c3e74..c1623c2331 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -6,6 +6,7 @@ from bokeh.models import Range, HoverTool from bokeh.models.tickers import Ticker, BasicTicker, FixedTicker from bokeh.models.widgets import Panel, Tabs +from distutils.version import LooseVersion try: from bokeh import mpl @@ -245,8 +246,7 @@ def _init_plot(self, key, plots, ranges=None): properties['x_axis_label'] = xlabel if 'x' in self.show_labels else ' ' properties['y_axis_label'] = ylabel if 'y' in self.show_labels else ' ' - major, minor = [int(v) for v in bokeh.__version__.split('.')[0:2]] - if major > 0 or minor > '10': + if LooseVersion(bokeh.__version__) > LooseVersion('0.10'): properties['webgl'] = True return bokeh.plotting.Figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, From fe75802d2e6fa622330376d85cadc39798e35cf7 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 7 Dec 2015 14:35:17 +0000 Subject: [PATCH 03/86] Fix for Seaborn plotting interface --- holoviews/plotting/mpl/seaborn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/plotting/mpl/seaborn.py b/holoviews/plotting/mpl/seaborn.py index e4a940df61..b9a7f161a4 100644 --- a/holoviews/plotting/mpl/seaborn.py +++ b/holoviews/plotting/mpl/seaborn.py @@ -326,7 +326,7 @@ def _update_plot(self, axis, view): for opt, args in map_opts: plot_fn = getattr(sns, args[0]) if hasattr(sns, args[0]) else getattr(plt, args[0]) getattr(g, opt)(plot_fn, *args[1:]) - if self._close_fig: + if self._close_figures: plt.close(self.handles['fig']) self.handles['fig'] = plt.gcf() else: From 14cd5e2f9ca2682c9522128e320cb860a1386d74 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 7 Dec 2015 14:36:53 +0000 Subject: [PATCH 04/86] Fix for Pandas_Seaborn regression example --- doc/Tutorials/Pandas_Seaborn.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Tutorials/Pandas_Seaborn.ipynb b/doc/Tutorials/Pandas_Seaborn.ipynb index 513f0831e8..fa01cedd63 100644 --- a/doc/Tutorials/Pandas_Seaborn.ipynb +++ b/doc/Tutorials/Pandas_Seaborn.ipynb @@ -408,8 +408,8 @@ "outputs": [], "source": [ "%%opts Regression [apply_databounds=True]\n", - "tips.regression('total_bill', 'tip', mdims=['smoker','sex'],\n", - " extents=(0, 0, 50, 10), reduce_fn=np.mean).overlay('smoker').layout('sex')" + "tips.regression(['total_bill'], ['tip'], mdims=['smoker','sex'],\n", + " extents=(0, 0, 50, 10)).overlay('smoker').layout('sex')" ] }, { From 04e0d2bb91f17eb8836672d65e3befb02ec70c56 Mon Sep 17 00:00:00 2001 From: philippjfr Date: Mon, 7 Dec 2015 19:53:15 +0000 Subject: [PATCH 05/86] 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 3eaf4f8771..0391727272 160000 --- a/doc/reference_data +++ b/doc/reference_data @@ -1 +1 @@ -Subproject commit 3eaf4f8771d5042689caf4de5c4be7ee56d0a329 +Subproject commit 0391727272a64381496f8a62b591672c3fb8f2c1 From 9a551b165dae46804589cf3a95f41b462788735c Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 8 Dec 2015 19:06:49 +0000 Subject: [PATCH 06/86] Fixed Columns interface sample methods --- holoviews/core/data.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/holoviews/core/data.py b/holoviews/core/data.py index 090440bf9e..cb91086a81 100644 --- a/holoviews/core/data.py +++ b/holoviews/core/data.py @@ -812,11 +812,13 @@ def values(cls, columns, dim): @classmethod def sample(cls, columns, samples=[]): data = columns.data - mask = np.zeros(cls.length(columns), dtype=bool) + mask = False for sample in samples: + sample_mask = True if np.isscalar(sample): sample = [sample] for i, v in enumerate(sample): - mask = np.logical_or(mask, data.iloc[:, i]==v) + sample_mask = np.logical_and(sample_mask, data.iloc[:, i]==v) + mask |= sample_mask return data[mask] @@ -995,9 +997,12 @@ def sample(cls, columns, samples=[]): data = columns.data mask = False for sample in samples: + sample_mask = True if np.isscalar(sample): sample = [sample] for i, v in enumerate(sample): - mask |= data[:, i]==v + sample_mask &= data[:, i]==v + mask |= sample_mask + return data[mask] @@ -1195,13 +1200,17 @@ def select(cls, columns, selection_mask=None, **selection): @classmethod def sample(cls, columns, samples=[]): - mask = np.zeros(len(columns), dtype=np.bool) + mask = False for sample in samples: + sample_mask = True if np.isscalar(sample): sample = [sample] for i, v in enumerate(sample): name = columns.get_dimension(i).name - mask |= (np.array(columns.data[name])==v) - return {k:np.array(col)[mask] for k, col in columns.data.items()} + sample_mask &= (np.array(columns.data[name])==v) + mask |= sample_mask + return {k: np.array(col)[mask] + for k, col in columns.data.items()} + @classmethod def aggregate(cls, columns, kdims, function, **kwargs): From d3cb76701b549fc9610dcbb326caa71336c67fcb Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 8 Dec 2015 19:09:35 +0000 Subject: [PATCH 07/86] Columns interface only tries supported datatypes --- holoviews/core/data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/core/data.py b/holoviews/core/data.py index cb91086a81..224f22f3cc 100644 --- a/holoviews/core/data.py +++ b/holoviews/core/data.py @@ -417,7 +417,8 @@ def initialize(cls, eltype, data, kdims, vdims, datatype=None): # Set interface priority order if datatype is None: datatype = eltype.datatype - prioritized = [cls.interfaces[p] for p in datatype] + prioritized = [cls.interfaces[p] for p in datatype + if p in cls.interfaces] head = [intfc for intfc in prioritized if type(data) in intfc.types] if head: From ebca1fc0dea50cd6e917ea4f59b2efa9a5405ab6 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 8 Dec 2015 19:10:42 +0000 Subject: [PATCH 08/86] Fixed datatype in NdMapping.table method --- holoviews/core/ndmapping.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py index 2d3863d5fc..0cb9e8f639 100644 --- a/holoviews/core/ndmapping.py +++ b/holoviews/core/ndmapping.py @@ -440,8 +440,9 @@ def info(self): def table(self, datatype=None, **kwargs): "Creates a table from the stored keys and data." + if datatype is None: + datatype = ['dictionary', 'dataframe'] - datatype = ['ndelement', 'dataframe'] tables = [] for key, value in self.data.items(): value = value.table(datatype=datatype, **kwargs) From 6bcc45f38e41bb5717a26753f50a7cafdc0c8e93 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 8 Dec 2015 22:49:37 +0000 Subject: [PATCH 09/86] Correctly tearing down datatypes in Column interface tests --- tests/testcolumns.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/testcolumns.py b/tests/testcolumns.py index 52d952e242..fc48157c95 100644 --- a/tests/testcolumns.py +++ b/tests/testcolumns.py @@ -341,6 +341,7 @@ class ArrayColumnsTest(ComparisonTestCase, HomogeneousColumnTypes): Test of the ArrayColumns interface. """ def setUp(self): + self.restore_datatype = Columns.datatype Columns.datatype = ['array'] self.data_instance_type = np.ndarray self.init_data() @@ -354,6 +355,7 @@ class DFColumnsTest(ComparisonTestCase, HeterogeneousColumnTypes): def setUp(self): if pd is None: raise SkipTest("Pandas not available") + self.restore_datatype = Columns.datatype Columns.datatype = ['dataframe'] self.data_instance_type = pd.DataFrame self.init_data() @@ -366,6 +368,7 @@ class DictColumnsTest(ComparisonTestCase, HeterogeneousColumnTypes): """ def setUp(self): + self.restore_datatype = Columns.datatype Columns.datatype = ['dictionary'] self.data_instance_type = (dict, cyODict, OrderedDict) self.init_data() @@ -378,6 +381,7 @@ class NdColumnsTest(ComparisonTestCase, HeterogeneousColumnTypes): """ def setUp(self): + self.restore_datatype = Columns.datatype Columns.datatype = ['ndelement'] self.data_instance_type = NdElement self.init_data() From d980a613bf1e72330f3633b2471cc61738d76cb1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 8 Dec 2015 23:27:22 +0000 Subject: [PATCH 10/86] Fixed Columns interface unit test inheritance --- tests/testcolumns.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/testcolumns.py b/tests/testcolumns.py index fc48157c95..cb144a37af 100644 --- a/tests/testcolumns.py +++ b/tests/testcolumns.py @@ -336,7 +336,7 @@ def test_columns_array_ht(self): np.column_stack([self.xs, self.ys])) -class ArrayColumnsTest(ComparisonTestCase, HomogeneousColumnTypes): +class ArrayColumnsTest(HomogeneousColumnTypes, ComparisonTestCase): """ Test of the ArrayColumns interface. """ @@ -347,7 +347,7 @@ def setUp(self): self.init_data() -class DFColumnsTest(ComparisonTestCase, HeterogeneousColumnTypes): +class DFColumnsTest(HeterogeneousColumnTypes, ComparisonTestCase): """ Test of the pandas DFColumns interface. """ @@ -362,7 +362,7 @@ def setUp(self): -class DictColumnsTest(ComparisonTestCase, HeterogeneousColumnTypes): +class DictColumnsTest(HeterogeneousColumnTypes, ComparisonTestCase): """ Test of the generic dictionary interface. """ @@ -375,7 +375,7 @@ def setUp(self): -class NdColumnsTest(ComparisonTestCase, HeterogeneousColumnTypes): +class NdColumnsTest(HeterogeneousColumnTypes, ComparisonTestCase): """ Test of the NdColumns interface (mostly for backwards compatibility) """ From b13f4eb84546adbd41efff0bc5f5d65125d87d37 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 9 Dec 2015 03:32:34 +0000 Subject: [PATCH 11/86] Added support for 1D Columns types --- holoviews/core/data.py | 24 ++++++++++++++++++++---- holoviews/plotting/bokeh/element.py | 4 +++- holoviews/plotting/mpl/seaborn.py | 2 +- holoviews/plotting/plot.py | 8 ++++++-- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/holoviews/core/data.py b/holoviews/core/data.py index 224f22f3cc..f9fcdc3d48 100644 --- a/holoviews/core/data.py +++ b/holoviews/core/data.py @@ -52,6 +52,10 @@ class Columns(Element): format listed will be used until a suitable format is found (or the data fails to be understood).""") + # In the 1D case the interfaces should not automatically add x-values + # to supplied data + _1d = False + def __init__(self, data, **kwargs): if isinstance(data, Element): pvals = util.get_param_values(data) @@ -572,7 +576,10 @@ def reshape(cls, eltype, data, kdims, vdims): data = tuple(data.get(d) for d in dimensions) elif isinstance(data, np.ndarray): if data.ndim == 1: - data = (np.arange(len(data)), data) + if eltype._1d: + data = np.atleast_2d(data).T + else: + data = (np.arange(len(data)), data) else: data = tuple(data[:, i] for i in range(data.shape[1])) elif isinstance(data, list) and np.isscalar(data[0]): @@ -694,7 +701,10 @@ def reshape(cls, eltype, data, kdims, vdims): data = OrderedDict(((c, col) for c, col in zip(columns, column_data))) elif isinstance(data, np.ndarray): if data.ndim == 1: - data = (range(len(data)), data) + if eltype._1d: + data = np.atleast_2d(data).T + else: + data = (range(len(data)), data) else: data = tuple(data[:, i] for i in range(data.shape[1])) @@ -877,7 +887,10 @@ def reshape(cls, eltype, data, kdims, vdims): if data is None or data.ndim > 2 or data.dtype.kind in ['S', 'U', 'O']: raise ValueError("ArrayColumns interface could not handle input type.") elif data.ndim == 1: - data = np.column_stack([np.arange(len(data)), data]) + if eltype._1d: + data = np.atleast_2d(data).T + else: + data = np.column_stack([np.arange(len(data)), data]) if kdims is None: kdims = eltype.kdims @@ -1063,7 +1076,10 @@ def reshape(cls, eltype, data, kdims, vdims): data = {d: data[d] for d in dimensions} elif isinstance(data, np.ndarray): if data.ndim == 1: - data = np.column_stack([np.arange(len(data)), data]) + if eltype._1d: + data = np.atleast_2d(data).T + else: + data = np.column_stack([np.arange(len(data)), data]) data = {k: data[:,i] for i,k in enumerate(dimensions)} elif isinstance(data, list) and np.isscalar(data[0]): data = {dimensions[0]: np.arange(len(data)), dimensions[1]: data} diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index c1623c2331..80243c0c53 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -158,6 +158,7 @@ def _init_tools(self, element): def _axes_props(self, plots, subplots, element, ranges): + dims = element.dimensions() xlabel, ylabel, zlabel = self._axis_labels(element, subplots) if self.invert_axes: xlabel, ylabel = ylabel, xlabel @@ -180,7 +181,8 @@ def _axes_props(self, plots, subplots, element, ranges): x_axis_type = 'datetime' else: x_axis_type = 'log' if self.logx else 'auto' - if element.get_dimension_type(1) is np.datetime64: + + if len(dims) > 1 and element.get_dimension_type(1) is np.datetime64: y_axis_type = 'datetime' else: y_axis_type = 'log' if self.logy else 'auto' diff --git a/holoviews/plotting/mpl/seaborn.py b/holoviews/plotting/mpl/seaborn.py index b9a7f161a4..06b12b6d16 100644 --- a/holoviews/plotting/mpl/seaborn.py +++ b/holoviews/plotting/mpl/seaborn.py @@ -177,7 +177,7 @@ def initialize_plot(self, ranges=None): def _update_plot(self, axis, view): label = view.label if self.overlaid == 1 else '' - sns.distplot(view.dimension_values(1), ax=axis, label=label, **self.style) + sns.distplot(view.dimension_values(0), ax=axis, label=label, **self.style) diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py index c202c9e6a4..e6d81561a9 100644 --- a/holoviews/plotting/plot.py +++ b/holoviews/plotting/plot.py @@ -492,17 +492,21 @@ def get_extents(self, view, ranges): Gets the extents for the axes from the current View. The globally computed ranges can optionally override the extents. """ + ndims = len(view.dimensions()) num = 6 if self.projection == '3d' else 4 if self.apply_ranges: if ranges: dims = view.dimensions() x0, x1 = ranges[dims[0].name] - y0, y1 = ranges[dims[1].name] + if ndims > 1: + y0, y1 = ranges[dims[1].name] + else: + y0, y1 = (np.NaN, np.NaN) if self.projection == '3d': z0, z1 = ranges[dims[2].name] else: x0, x1 = view.range(0) - y0, y1 = view.range(1) + y0, y1 = view.range(1) if ndims > 1 else (np.NaN, np.NaN) if self.projection == '3d': z0, z1 = view.range(2) if self.projection == '3d': From b3040bb7461065bf71550b5fd258c71077739712 Mon Sep 17 00:00:00 2001 From: PhilippJFR Date: Wed, 9 Dec 2015 17:25:00 +0000 Subject: [PATCH 12/86] Fixed bug in Unpickler.collect method --- holoviews/core/io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/core/io.py b/holoviews/core/io.py index 0ab3085435..45eb74d288 100644 --- a/holoviews/core/io.py +++ b/holoviews/core/io.py @@ -420,7 +420,7 @@ def collect(self_or_cls, files, drop=[], metadata=True): aslist = not isinstance(files, (NdMapping, Element)) if isinstance(files, Element): files = Collator(files) - file_kdims = [] + file_kdims = files.kdims else: file_kdims = files.kdims drop_extra = files.drop if isinstance(files, Collator) else [] From af26280e774620c1019a927cf73810fd87f237d1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 9 Dec 2015 18:45:00 +0000 Subject: [PATCH 13/86] Fixed handling of nested partially overlapping Overlays in bokeh backend --- holoviews/plotting/bokeh/element.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 80243c0c53..9d82b13d4f 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -677,7 +677,8 @@ def update_frame(self, key, ranges=None, element=None): ranges = self.compute_ranges(self.hmap, key, ranges) for k, subplot in self.subplots.items(): - subplot.update_frame(key, ranges, element=element.get(k, None)) + el = None if element is None else element.get(k, None) + subplot.update_frame(key, ranges, element=el) if not self.overlaid and not self.tabs: self._update_ranges(element, ranges) self._update_plot(key, self.handles['plot'], element) From 64041e3cedaf498cce35f83d870c0b31f11d3bcb Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 9 Dec 2015 18:54:44 +0000 Subject: [PATCH 14/86] Fixes to Overlay nesting issues in bokeh plotting --- holoviews/plotting/bokeh/element.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 9d82b13d4f..8e3272ba72 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -627,7 +627,9 @@ def _init_tools(self, element): """ tools = [] for i, subplot in enumerate(self.subplots.values()): - tools.extend(subplot._init_tools(element.get(i))) + el = element.get(i) + if el is not None: + tools.extend(subplot._init_tools(el)) return list(set(tools)) @@ -677,7 +679,7 @@ def update_frame(self, key, ranges=None, element=None): ranges = self.compute_ranges(self.hmap, key, ranges) for k, subplot in self.subplots.items(): - el = None if element is None else element.get(k, None) + el = element.get(k, None) if self.dynamic and element is not None else None subplot.update_frame(key, ranges, element=el) if not self.overlaid and not self.tabs: self._update_ranges(element, ranges) From 7c4c595c75339859a8c605779c58543e39228c62 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Thu, 10 Dec 2015 00:09:14 +0000 Subject: [PATCH 15/86] Columns.__setstate__ now calling super to handle pickling --- holoviews/core/data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/holoviews/core/data.py b/holoviews/core/data.py index f9fcdc3d48..7de10b667a 100644 --- a/holoviews/core/data.py +++ b/holoviews/core/data.py @@ -86,6 +86,7 @@ def __setstate__(self, state): elif util.is_dataframe(self.data): self.interface = DFColumns + super(Columns, self).__setstate__(state) def closest(self, coords): """ From 54dfec81fe264bc66f0209d06d9a7c4321ae3ea6 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Thu, 10 Dec 2015 00:16:48 +0000 Subject: [PATCH 16/86] HeatMap.__setstate__ now calling super to handle pickling --- holoviews/element/raster.py | 1 + 1 file changed, 1 insertion(+) diff --git a/holoviews/element/raster.py b/holoviews/element/raster.py index d03dbde833..a2a0e8b3a5 100644 --- a/holoviews/element/raster.py +++ b/holoviews/element/raster.py @@ -433,6 +433,7 @@ def __setstate__(self, state): (d1, d2) = self.raster.shape[:2] self.extents = (0, 0, d2, d1) + super(HeatMap, self).__setstate__(state) def dense_keys(self): d1keys = self.dimension_values(0, True) From ceef4301d8c7ac889825fb2d39c602611ab7328d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 10 Dec 2015 17:45:13 +0000 Subject: [PATCH 17/86] Renamed matplotlib orientation parameter to invert_axes --- holoviews/plotting/bokeh/element.py | 4 +++- holoviews/plotting/mpl/chart.py | 22 ++++++---------------- holoviews/plotting/mpl/element.py | 17 ++++++++++------- holoviews/plotting/mpl/plot.py | 6 +++--- holoviews/plotting/mpl/raster.py | 2 +- 5 files changed, 23 insertions(+), 28 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 8e3272ba72..f5d9166657 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -58,6 +58,9 @@ class ElementPlot(BokehPlot, GenericElementPlot): {'ticks': '20pt', 'title': '15pt', 'ylabel': '5px', 'xlabel': '5px'}""") + invert_axes = param.Boolean(default=False, doc=""" + Whether to invert the x- and y-axis""") + invert_xaxis = param.Boolean(default=False, doc=""" Whether to invert the plot x-axis.""") @@ -139,7 +142,6 @@ class ElementPlot(BokehPlot, GenericElementPlot): def __init__(self, element, plot=None, invert_axes=False, show_labels=['x', 'y'], **params): - self.invert_axes = invert_axes self.show_labels = show_labels self.current_ranges = None super(ElementPlot, self).__init__(element, **params) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 2cd6547826..c9612727a5 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -291,7 +291,7 @@ def __init__(self, histograms, **params): super(HistogramPlot, self).__init__(histograms, **params) - if self.orientation == 'vertical': + if self.invert_axes: self.axis_settings = ['ylabel', 'xlabel', 'yticks'] else: self.axis_settings = ['xlabel', 'ylabel', 'xticks'] @@ -309,7 +309,7 @@ def initialize_plot(self, ranges=None): # Get plot ranges and values edges, hvals, widths, lims = self._process_hist(hist) - if self.orientation == 'vertical': + if self.invert_axes: self.offset_linefn = self.handles['axis'].axvline self.plotfn = self.handles['axis'].barh else: @@ -363,7 +363,7 @@ def _compute_ticks(self, element, edges, widths, lims): def get_extents(self, element, ranges): x0, y0, x1, y1 = super(HistogramPlot, self).get_extents(element, ranges) y0 = np.nanmin([0, y0]) - return (y0, x0, y1, x1) if self.orientation == 'vertical' else (x0, y0, x1, y1) + return (x0, y0, x1, y1) def _process_axsettings(self, hist, lims, ticks): @@ -390,7 +390,7 @@ def _update_artists(self, key, hist, edges, hvals, widths, lims, ranges): """ plot_vals = zip(self.handles['artist'], edges, hvals, widths) for bar, edge, height, width in plot_vals: - if self.orientation == 'vertical': + if self.invert_axes: bar.set_y(edge) bar.set_width(height) bar.set_height(width) @@ -446,16 +446,6 @@ def _process_hist(self, hist): return edges, hvals, widths, lims - def _process_axsettings(self, hist, lims, ticks): - axsettings = super(SideHistogramPlot, self)._process_axsettings(hist, lims, ticks) - label = 'ylabel' if self.orientation == 'vertical' else 'xlabel' - if not self.show_xlabel: - axsettings[label] = '' - else: - axsettings[label] = str(hist.kdims[0]) - return axsettings - - def _update_artists(self, n, element, edges, hvals, widths, lims, ranges): super(SideHistogramPlot, self)._update_artists(n, element, edges, hvals, widths, lims, ranges) self._update_plot(n, element, self.handles['artist'], lims, ranges) @@ -494,7 +484,7 @@ def _update_plot(self, key, element, bars, lims, ranges): def get_extents(self, element, ranges): x0, _, x1, _ = element.extents _, y1 = element.range(1) - return (0, x0, y1, x1) if self.orientation == 'vertical' else (x0, 0, x1, y1) + return (x0, 0, x1, y1) def _colorize_bars(self, cmap, bars, element, main_range, dim): @@ -521,7 +511,7 @@ def _update_separator(self, offset): offset_line.set_visible(False) else: offset_line.set_visible(True) - if self.orientation == 'vertical': + if self.invert_axes: offset_line.set_xdata(offset) else: offset_line.set_ydata(offset) diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index c5e87a26a9..9dfc455418 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -31,6 +31,11 @@ class ElementPlot(GenericElementPlot, MPLPlot): bgcolor = param.ClassSelector(class_=(str, tuple), default=None, doc=""" If set bgcolor overrides the background color of the axis.""") + invert_axes = param.ObjectSelector(default=False, doc=""" + Inverts the axes of the plot. Note that this parameter may not + always be respected by all plots but should be respected by + adjoined plots when appropriate.""") + invert_xaxis = param.Boolean(default=False, doc=""" Whether to invert the plot x-axis.""") @@ -46,13 +51,6 @@ class ElementPlot(GenericElementPlot, MPLPlot): logz = param.Boolean(default=False, doc=""" Whether to apply log scaling to the y-axis of the Chart.""") - orientation = param.ObjectSelector(default='horizontal', - objects=['horizontal', 'vertical'], doc=""" - The orientation of the plot. Note that this parameter may not - always be respected by all plots but should be respected by - adjoined plots when appropriate valid options are 'horizontal' - and 'vertical'.""") - show_legend = param.Boolean(default=False, doc=""" Whether to show legend for the plot.""") @@ -139,6 +137,9 @@ def _finalize_axis(self, key, title=None, ranges=None, xticks=None, yticks=None, if isinstance(sp.hmap, HoloMap)) if element is not None and not suppress: xlabel, ylabel, zlabel = self._axis_labels(element, subplots, xlabel, ylabel, zlabel) + if self.invert_axes: + xlabel, ylabel = ylabel, xlabel + self._finalize_limits(axis, element, subplots, ranges) # Tick formatting @@ -227,6 +228,8 @@ def _finalize_limits(self, axis, view, subplots, ranges): axis.set_zlim((zmin, zmax)) else: l, b, r, t = [coord if np.isreal(coord) else np.NaN for coord in extents] + if self.invert_axes: + l, b, r, t = b, l, t, r l, r = (c if np.isfinite(c) else None for c in (l, r)) if self.invert_xaxis or any(p.invert_xaxis for p in subplots): r, l = l, r diff --git a/holoviews/plotting/mpl/plot.py b/holoviews/plotting/mpl/plot.py index 974c88b2c9..9e4ea423d2 100644 --- a/holoviews/plotting/mpl/plot.py +++ b/holoviews/plotting/mpl/plot.py @@ -988,11 +988,11 @@ def _create_subplots(self, layout, positions, layout_dimensions, ranges, axes={} if not isinstance(view, GridSpace): override_opts = dict(aspect='square') elif pos == 'right': - right_opts = dict(orientation='vertical', - xaxis=None, yaxis='left') + right_opts = dict(invert_axes=True, + xaxis=None) override_opts = dict(subplot_opts, **right_opts) elif pos == 'top': - top_opts = dict(xaxis='bottom', yaxis=None) + top_opts = dict(yaxis=None) override_opts = dict(subplot_opts, **top_opts) # Override the plotopts as required diff --git a/holoviews/plotting/mpl/raster.py b/holoviews/plotting/mpl/raster.py index 4ba9c6da5d..abfeddf683 100644 --- a/holoviews/plotting/mpl/raster.py +++ b/holoviews/plotting/mpl/raster.py @@ -240,6 +240,7 @@ class RasterGridPlot(GridPlot, OverlayPlot): apply_ranges = param.Parameter(precedence=-1) apply_ticks = param.Parameter(precedence=-1) bgcolor = param.Parameter(precedence=-1) + invert_axes = param.Parameter(precedence=-1) invert_xaxis = param.Parameter(precedence=-1) invert_yaxis = param.Parameter(precedence=-1) legend_cols = param.Parameter(precedence=-1) @@ -247,7 +248,6 @@ class RasterGridPlot(GridPlot, OverlayPlot): logx = param.Parameter(precedence=-1) logy = param.Parameter(precedence=-1) logz = param.Parameter(precedence=-1) - orientation = param.Parameter(precedence=-1) show_grid = param.Parameter(precedence=-1) style_grouping = param.Parameter(precedence=-1) xticks = param.Parameter(precedence=-1) From 381ba64c97065b4523d7abc2f857314d02f86665 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 10 Dec 2015 17:45:32 +0000 Subject: [PATCH 18/86] Avoid hardcoding PathPlot aspect option --- holoviews/plotting/mpl/path.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/mpl/path.py b/holoviews/plotting/mpl/path.py index a59c686ad5..cf0cd25ae4 100644 --- a/holoviews/plotting/mpl/path.py +++ b/holoviews/plotting/mpl/path.py @@ -9,10 +9,13 @@ class PathPlot(ElementPlot): + aspect = param.Parameter(default='equal', doc=""" + PathPlots axes usually define single space so aspect of Paths + follows aspect in data coordinates by default.""") + style_opts = ['alpha', 'color', 'linestyle', 'linewidth', 'visible'] def __init__(self, *args, **params): - self.aspect = 'equal' super(PathPlot, self).__init__(*args, **params) def initialize_plot(self, ranges=None): From ac6bf2957a96c8a5496515338aa0d6a387891360 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 10 Dec 2015 17:46:42 +0000 Subject: [PATCH 19/86] Added Spikes Element and plotting classes --- holoviews/element/chart.py | 20 ++++++ holoviews/plotting/bokeh/__init__.py | 7 +- holoviews/plotting/bokeh/chart.py | 50 +++++++++++++ holoviews/plotting/mpl/__init__.py | 4 +- holoviews/plotting/mpl/chart.py | 104 +++++++++++++++++++++++++++ 5 files changed, 181 insertions(+), 4 deletions(-) diff --git a/holoviews/element/chart.py b/holoviews/element/chart.py index f74902949b..5e5a01ab73 100644 --- a/holoviews/element/chart.py +++ b/holoviews/element/chart.py @@ -344,3 +344,23 @@ def __init__(self, data, **params): if isinstance(data, list) and all(isinstance(d, np.ndarray) for d in data): data = np.column_stack([d.flat if d.ndim > 1 else d for d in data]) super(VectorField, self).__init__(data, **params) + + + +class Spikes(Chart): + """ + Spikes is a 1D or 2D Element, which represents vertical or + horizontal ticks along some dimension. If an additional dimension + is supplied it will be used to specify the height of the + lines. The Element may therefore be used to represent 1D + distributions, spectrograms or spike trains in electrophysiology. + """ + + group = param.String(default='Spikes', constant=True) + + kdims = param.List(default=[Dimension('x')]) + + vdims = param.List(default=[]) + + _1d = True + diff --git a/holoviews/plotting/bokeh/__init__.py b/holoviews/plotting/bokeh/__init__.py index e0aa6eb758..c1978d4e16 100644 --- a/holoviews/plotting/bokeh/__init__.py +++ b/holoviews/plotting/bokeh/__init__.py @@ -3,7 +3,7 @@ from ...element import (Curve, Points, Scatter, Image, Raster, Path, RGB, Histogram, Spread, HeatMap, Contours, Path, Box, Bounds, Ellipse, Polygons, - ErrorBars, Text, HLine, VLine, Spline, + ErrorBars, Text, HLine, VLine, Spline, Spikes, Table, ItemTable, Surface, Scatter3D, Trisurface) from ...core.options import Options, Cycle, OptionTree from ...interface import DFrame @@ -14,7 +14,7 @@ from .callbacks import Callbacks from .element import OverlayPlot, BokehMPLWrapper, BokehMPLRawWrapper from .chart import (PointPlot, CurvePlot, SpreadPlot, ErrorPlot, HistogramPlot, - AdjointHistogramPlot) + AdjointHistogramPlot, SpikesPlot) from .path import PathPlot, PolygonPlot from .plot import GridPlot, LayoutPlot, AdjointLayoutPlot from .raster import RasterPlot, RGBPlot, HeatmapPlot @@ -37,6 +37,7 @@ Scatter: PointPlot, ErrorBars: ErrorPlot, Spread: SpreadPlot, + Spikes: SpikesPlot, # Rasters Image: RasterPlot, @@ -81,7 +82,7 @@ AdjointLayoutPlot.registry[Histogram] = AdjointHistogramPlot - +AdjointLayoutPlot.registry[Spikes] = Spikes try: from ..mpl.seaborn import TimeSeriesPlot, BivariatePlot, DistributionPlot diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 2d22abf261..4d55bb3b55 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -203,3 +203,53 @@ def get_data(self, element, ranges=None): err_xs.append((x, x)) err_ys.append((y - neg, y + pos)) return (dict(xs=err_xs, ys=err_ys), self._mapping) + + +class SpikesPlot(PathPlot): + + color_index = param.Integer(default=1, doc=""" + Index of the dimension from which the color will the drawn""") + + spike_height = param.Number(default=0.1) + + yposition = param.Number(default=0.) + + style_opts = (['cmap', 'palette'] + line_properties) + + def get_extents(self, element, ranges): + l, b, r, t = super(SpikesPlot, self).get_extents(element, ranges) + if len(element.dimensions()) == 1: + b, t = self.yposition, self.yposition+self.spike_height + return l, b, r, t + + + def get_data(self, element, ranges=None): + style = self.style[self.cyclic_index] + dims = element.dimensions(label=True) + + ypos = self.yposition + if len(dims) > 1: + xs, ys = zip(*(((x, x), (ypos, ypos+y)) + for x, y in element.array())) + mapping = dict(xs=dims[0], ys=dims[1]) + keys = (dims[0], dims[1]) + else: + height = self.spike_height + xs, ys = zip(*(((x[0], x[0]), (ypos, ypos+height)) + for x in element.array())) + mapping = dict(xs=dims[0], ys='heights') + keys = (dims[0], 'heights') + if self.invert_axes: keys = keys[::-1] + data = dict(zip(keys, (xs, ys))) + + cmap = style.get('palette', style.get('cmap', None)) + if self.color_index < len(dims) and cmap: + cdim = dims[self.color_index] + map_key = 'color_' + cdim + mapping['color'] = map_key + cmap = get_cmap(cmap) + colors = element.dimension_values(cdim) + crange = ranges.get(cdim, None) + data[map_key] = map_colors(colors, crange, cmap) + + return data, mapping diff --git a/holoviews/plotting/mpl/__init__.py b/holoviews/plotting/mpl/__init__.py index 2e6c8ccf6d..1a1768f261 100644 --- a/holoviews/plotting/mpl/__init__.py +++ b/holoviews/plotting/mpl/__init__.py @@ -108,6 +108,7 @@ def grid_selector(grid): VectorField: VectorFieldPlot, ErrorBars: ErrorPlot, Spread: SpreadPlot, + Spikes: SpikesPlot, # General plots GridSpace: GridPlot, @@ -157,7 +158,8 @@ def grid_selector(grid): MPLPlot.sideplots.update({Histogram: SideHistogramPlot, - GridSpace: GridPlot}) + GridSpace: GridPlot, + Spikes: MarginalRugPlot}) options = Store.options(backend='matplotlib') diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index c9612727a5..4db6d40601 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -4,6 +4,7 @@ import numpy as np from matplotlib import cm from matplotlib import pyplot as plt +from matplotlib.collections import LineCollection import param @@ -12,6 +13,7 @@ from ...element import Points, Raster, Polygons from ..util import compute_sizes, get_sideplot_ranges from .element import ElementPlot, ColorbarPlot, LegendPlot +from .path import PathPlot class ChartPlot(ElementPlot): @@ -970,3 +972,105 @@ def update_handles(self, axis, element, key, ranges=None): bar[0].set_height(height) bar[0].set_y(prev) prev += height if np.isfinite(height) else 0 + + +class SpikesPlot(PathPlot): + + aspect = param.Parameter(default='square', doc=""" + The aspect ratio mode of the plot. Allows setting an + explicit aspect ratio as width/height as well as + 'square' and 'equal' options.""") + + color_index = param.Integer(default=1, doc=""" + Index of the dimension from which the color will the drawn""") + + spike_height = param.Number(default=0.5) + + yposition = param.Number(default=0) + + style_opts = PathPlot.style_opts + ['cmap'] + + def initialize_plot(self, ranges=None): + lines = self.hmap.last + key = self.keys[-1] + + ranges = self.compute_ranges(self.hmap, key, ranges) + ranges = match_spec(lines, ranges) + style = self.style[self.cyclic_index] + label = lines.label if self.show_legend else '' + + data, array, clim = self.get_data(lines, ranges) + if array is not None: + style['array'] = array + style['clim'] = clim + + line_segments = LineCollection(data, label=label, + zorder=self.zorder, **style) + self.handles['artist'] = line_segments + self.handles['axis'].add_collection(line_segments) + + return self._finalize_axis(key, ranges=ranges) + + + def get_data(self, element, ranges): + dimensions = element.dimensions(label=True) + ndims = len(dimensions) + + ypos = self.yposition + if ndims > 1: + data = [[(x, ypos), (x, ypos+y)] for x, y in element.array()] + else: + height = self.spike_height + data = [[(x[0], ypos), (x[0], ypos+height)] for x in element.array()] + + if self.invert_axes: + data = [(line[0][::-1], line[1][::-1]) for line in data] + + array, clim = None, None + if self.color_index < ndims: + cdim = dimensions[self.color_index] + array = element.dimension_values(cdim) + clime = ranges[cdim] + return data, array, clim + + + def update_handles(self, axis, element, key, ranges=None): + artist = self.handles['artist'] + data, array, clim = self.get_data(element) + artist.set_paths(data) + visible = self.style[self.cyclic_index].get('visible', True) + artist.set_visible(visible) + if array is not None: + paths.set_clim(ranges[val_dim]) + paths.set_array(cs) + + +class MarginalRugPlot(SpikesPlot): + + aspect = param.Parameter(default='auto', doc=""" + Aspect ratios on SideHistogramPlot should be determined by the + AdjointLayoutPlot.""") + + show_title = param.Boolean(default=False, doc=""" + Titles should be disabled on all SidePlots to avoid clutter.""") + + show_frame = param.Boolean(default=False) + + show_xlabel = param.Boolean(default=False, doc=""" + Whether to show the x-label of the plot. Disabled by default + because plots are often too cramped to fit the title correctly.""") + + xaxis = param.ObjectSelector(default='bare', + objects=['top', 'bottom', 'bare', 'top-bare', + 'bottom-bare', None], doc=""" + Whether and where to display the xaxis, bare options allow suppressing + all axis labels including ticks and xlabel. Valid options are 'top', + 'bottom', 'bare', 'top-bare' and 'bottom-bare'.""") + + yaxis = param.ObjectSelector(default='bare', + objects=['left', 'right', 'bare', 'left-bare', + 'right-bare', None], doc=""" + Whether and where to display the yaxis, bare options allow suppressing + all axis labels including ticks and ylabel. Valid options are 'left', + 'right', 'bare' 'left-bare' and 'right-bare'.""") + From 2d4105d0612a9d8db5b372e3ec88566e5384637d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 10 Dec 2015 17:47:18 +0000 Subject: [PATCH 20/86] Added Spikes Element examples to Tutorial --- doc/Tutorials/Elements.ipynb | 90 ++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/doc/Tutorials/Elements.ipynb b/doc/Tutorials/Elements.ipynb index a6d17f0bd6..3a3965a95c 100644 --- a/doc/Tutorials/Elements.ipynb +++ b/doc/Tutorials/Elements.ipynb @@ -33,6 +33,7 @@ "
[``Scatter``](#Scatter)
Discontinuous collection of points indexed over a single dimension.
\n", "
[``Points``](#Points)
Discontinuous collection of points indexed over two dimensions.
\n", "
[``VectorField``](#VectorField)
Cyclic variable (and optional auxiliary data) distributed over two-dimensional space.
\n", + "
[``Spikes``](#Spikes)
A collection of horizontal or vertical lines at various locations with fixed height (1D) or variable height (2D).
\n", "
[``SideHistogram``](#SideHistogram)
Histogram binning data contained by some other ``Element``.
\n", " \n", "\n", @@ -461,6 +462,95 @@ "points + points[0.3:0.7, 0.3:0.7].hist()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ``Spikes`` " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Spikes represent any number of horizontal or vertical line segments with fixed or variable heights. There are a number of uses for this type, first of all they may be used as a rugplot to give an overview of a one-dimensional distribution. They may also be useful in more domain specific cases, such as visualizing spike trains for neurophysiology or spectrograms in physics and chemistry applications.\n", + "\n", + "In the simplest case a Spikes object therefore represents a 1D distribution:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%opts Spikes [spike_height=0.1] (alpha=0.4)\n", + "xs = np.random.rand(50)\n", + "ys = np.random.rand(50)\n", + "hv.Points((xs, ys)) * hv.Spikes(xs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When supplying two dimensions to the Spikes object the second dimension will be mapped onto the line height. Optionally you may also supply a cmap and color_index to map color onto one of the dimensions. This way we can for example plot a mass spectrogram:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%opts Spikes (cmap='Blues')\n", + "hv.Spikes(np.random.rand(20, 2), kdims=['Mass'], vdims=['Intensity'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Another possibility is to draw a number of spike trains as you would encounter in neuroscience:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%opts Spikes [spike_height=0.1] NdOverlay [show_legend=False]\n", + "hv.NdOverlay({i: hv.Spikes(np.random.rand(10, 1))(plot=dict(yposition=0.1*i))\n", + " for i in range(10)})(plot=dict(yticks=[((i+1)*0.1-0.05, i)for i in range(10)]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally we may use ``Spikes`` to adjoin to a regular plot to visualize marginal distributions:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%opts Spikes (alpha=0.4) [bgcolor='w'] AdjointLayout [border_size=0]\n", + "points = hv.Points(np.random.randn(100, 2))\n", + "points << hv.Spikes(points['y']) << hv.Spikes(points['x'])" + ] + }, { "cell_type": "markdown", "metadata": {}, From 45b542f7e865511026e539053ed4f9f169d1bb94 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 10 Dec 2015 18:01:12 +0000 Subject: [PATCH 21/86] Minor fixes in bokeh plotting --- holoviews/plotting/bokeh/__init__.py | 2 +- holoviews/plotting/bokeh/element.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/holoviews/plotting/bokeh/__init__.py b/holoviews/plotting/bokeh/__init__.py index c1978d4e16..5a8cb47d1d 100644 --- a/holoviews/plotting/bokeh/__init__.py +++ b/holoviews/plotting/bokeh/__init__.py @@ -82,7 +82,7 @@ AdjointLayoutPlot.registry[Histogram] = AdjointHistogramPlot -AdjointLayoutPlot.registry[Spikes] = Spikes +AdjointLayoutPlot.registry[Spikes] = SpikesPlot try: from ..mpl.seaborn import TimeSeriesPlot, BivariatePlot, DistributionPlot diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index f5d9166657..48dd3b90b5 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -140,8 +140,7 @@ class ElementPlot(BokehPlot, GenericElementPlot): # instance attribute. _update_handles = ['source', 'glyph'] - def __init__(self, element, plot=None, invert_axes=False, - show_labels=['x', 'y'], **params): + def __init__(self, element, plot=None, show_labels=['x', 'y'], **params): self.show_labels = show_labels self.current_ranges = None super(ElementPlot, self).__init__(element, **params) From 8f38f3c350e6e293697f9372d9cf4003953a273f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 02:18:26 +0000 Subject: [PATCH 22/86] Renamed SpikesPlot plot options --- holoviews/plotting/bokeh/chart.py | 19 +++++++++++-------- holoviews/plotting/mpl/chart.py | 14 ++++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 4d55bb3b55..5c5c634a62 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -210,16 +210,18 @@ class SpikesPlot(PathPlot): color_index = param.Integer(default=1, doc=""" Index of the dimension from which the color will the drawn""") - spike_height = param.Number(default=0.1) + spike_length = param.Number(default=0.5, doc=""" + The length of each spike if Spikes object is one dimensional.""") - yposition = param.Number(default=0.) + position = param.Number(default=0., doc=""" + The position of the lower end of each spike.""") - style_opts = (['cmap', 'palette'] + line_properties) + style_opts = (['color', 'cmap', 'palette'] + line_properties) def get_extents(self, element, ranges): l, b, r, t = super(SpikesPlot, self).get_extents(element, ranges) if len(element.dimensions()) == 1: - b, t = self.yposition, self.yposition+self.spike_height + b, t = self.position, self.position+self.spike_length return l, b, r, t @@ -227,18 +229,19 @@ def get_data(self, element, ranges=None): style = self.style[self.cyclic_index] dims = element.dimensions(label=True) - ypos = self.yposition + pos = self.position if len(dims) > 1: - xs, ys = zip(*(((x, x), (ypos, ypos+y)) + xs, ys = zip(*(((x, x), (pos, pos+y)) for x, y in element.array())) mapping = dict(xs=dims[0], ys=dims[1]) keys = (dims[0], dims[1]) else: - height = self.spike_height - xs, ys = zip(*(((x[0], x[0]), (ypos, ypos+height)) + height = self.spike_length + xs, ys = zip(*(((x[0], x[0]), (pos, pos+height)) for x in element.array())) mapping = dict(xs=dims[0], ys='heights') keys = (dims[0], 'heights') + if self.invert_axes: keys = keys[::-1] data = dict(zip(keys, (xs, ys))) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 4db6d40601..b6082029ee 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -984,9 +984,11 @@ class SpikesPlot(PathPlot): color_index = param.Integer(default=1, doc=""" Index of the dimension from which the color will the drawn""") - spike_height = param.Number(default=0.5) + spike_length = param.Number(default=0.5, doc=""" + The length of each spike if Spikes object is one dimensional.""") - yposition = param.Number(default=0) + position = param.Number(default=0., doc=""" + The position of the lower end of each spike.""") style_opts = PathPlot.style_opts + ['cmap'] @@ -1016,12 +1018,12 @@ def get_data(self, element, ranges): dimensions = element.dimensions(label=True) ndims = len(dimensions) - ypos = self.yposition + ypos = self.position if ndims > 1: - data = [[(x, ypos), (x, ypos+y)] for x, y in element.array()] + data = [[(x, pos), (x, pos+y)] for x, y in element.array()] else: - height = self.spike_height - data = [[(x[0], ypos), (x[0], ypos+height)] for x in element.array()] + height = self.spike_length + data = [[(x[0], pos), (x[0], pos+height)] for x in element.array()] if self.invert_axes: data = [(line[0][::-1], line[1][::-1]) for line in data] From 45f48196419d99649a89fa8af492c5aef84acc1f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 02:19:07 +0000 Subject: [PATCH 23/86] Improved default options for adjoined plots in bokeh --- holoviews/plotting/bokeh/chart.py | 12 +++++++++--- holoviews/plotting/bokeh/plot.py | 14 ++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 5c5c634a62..980741b686 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -4,7 +4,7 @@ from ...core import Dimension from ...core.util import max_range -from ...element import Chart, Raster, Points, Polygons +from ...element import Chart, Raster, Points, Polygons, Spikes from ..util import compute_sizes, get_sideplot_ranges from .element import ElementPlot, line_properties, fill_properties from .path import PathPlot, PolygonPlot @@ -147,13 +147,19 @@ class AdjointHistogramPlot(HistogramPlot): style_opts = HistogramPlot.style_opts + ['cmap'] - width = param.Integer(default=125) + height = param.Integer(default=125, doc="The height of the plot") + + width = param.Integer(default=125, doc="The width of the plot") + + show_title = param.Boolean(default=False, doc=""" + Whether to display the plot title.""") def get_data(self, element, ranges=None): if self.invert_axes: mapping = dict(top='left', bottom='right', left=0, right='top') else: mapping = dict(top='top', bottom=0, left='left', right='right') + data = dict(top=element.values, left=element.edges[:-1], right=element.edges[1:]) @@ -161,7 +167,7 @@ def get_data(self, element, ranges=None): main = self.adjoined.main range_item, main_range, dim = get_sideplot_ranges(self, element, main, ranges) vals = element.dimension_values(dim) - if isinstance(range_item, (Raster, Points, Polygons)): + if isinstance(range_item, (Raster, Points, Polygons, Spikes)): style = self.lookup_options(range_item, 'style')[self.cyclic_index] else: style = {} diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index dd04e3893b..fae3328145 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -353,13 +353,15 @@ def _create_subplots(self, layout, positions, layout_dimensions, ranges, num=0): if pos != 'main': plot_type = AdjointLayoutPlot.registry.get(vtype, plot_type) if pos == 'right': - side_opts = dict(height=main_plot.height, yaxis='right', - invert_axes=True, width=120, show_labels=['y'], - xticks=2, show_title=False) + yaxis = 'right-bare' if 'bare' in plot_type.yaxis else 'right' + side_opts = dict(height=main_plot.height, yaxis=yaxis, + width=plot_type.width, invert_axes=True, + show_labels=['y'], xticks=1, xaxis=main_plot.xaxis) else: - side_opts = dict(width=main_plot.width, xaxis='top', - height=120, show_labels=['x'], yticks=2, - show_title=False) + xaxis = 'top-bare' if 'bare' in plot_type.xaxis else 'top' + side_opts = dict(width=main_plot.width, xaxis=xaxis, + height=plot_type.height, show_labels=['x'], + yticks=1, yaxis=main_plot.yaxis) # Override the plotopts as required # Customize plotopts depending on position. From 588ae8a740a7dbf333fbbfee221e924ded0157c2 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 02:19:59 +0000 Subject: [PATCH 24/86] Added a MarginalSpikesPlot class for adjoined Spikes in bokeh --- holoviews/plotting/bokeh/__init__.py | 4 ++-- holoviews/plotting/bokeh/chart.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/bokeh/__init__.py b/holoviews/plotting/bokeh/__init__.py index 5a8cb47d1d..600f43b220 100644 --- a/holoviews/plotting/bokeh/__init__.py +++ b/holoviews/plotting/bokeh/__init__.py @@ -14,7 +14,7 @@ from .callbacks import Callbacks from .element import OverlayPlot, BokehMPLWrapper, BokehMPLRawWrapper from .chart import (PointPlot, CurvePlot, SpreadPlot, ErrorPlot, HistogramPlot, - AdjointHistogramPlot, SpikesPlot) + AdjointHistogramPlot, SpikesPlot, MarginalSpikesPlot) from .path import PathPlot, PolygonPlot from .plot import GridPlot, LayoutPlot, AdjointLayoutPlot from .raster import RasterPlot, RGBPlot, HeatmapPlot @@ -82,7 +82,7 @@ AdjointLayoutPlot.registry[Histogram] = AdjointHistogramPlot -AdjointLayoutPlot.registry[Spikes] = SpikesPlot +AdjointLayoutPlot.registry[Spikes] = MarginalSpikesPlot try: from ..mpl.seaborn import TimeSeriesPlot, BivariatePlot, DistributionPlot diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 980741b686..b2350b7f6c 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -262,3 +262,28 @@ def get_data(self, element, ranges=None): data[map_key] = map_colors(colors, crange, cmap) return data, mapping + + + +class MarginalSpikesPlot(SpikesPlot): + """ + SpikesPlot with useful defaults for plotting adjoined rug plot. + """ + + xaxis = param.ObjectSelector(default='top-bare', + objects=['top', 'bottom', 'bare', 'top-bare', + 'bottom-bare', None], doc=""" + Whether and where to display the xaxis, bare options allow suppressing + all axis labels including ticks and xlabel. Valid options are 'top', + 'bottom', 'bare', 'top-bare' and 'bottom-bare'.""") + + yaxis = param.ObjectSelector(default='right-bare', + objects=['left', 'right', 'bare', 'left-bare', + 'right-bare', None], doc=""" + Whether and where to display the yaxis, bare options allow suppressing + all axis labels including ticks and ylabel. Valid options are 'left', + 'right', 'bare' 'left-bare' and 'right-bare'.""") + + height = param.Integer(default=80, doc="Height of plot") + + width = param.Integer(default=80, doc="Width of plot") From 96daeb455b6005807ff92179f9b16955ad144da4 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 02:23:12 +0000 Subject: [PATCH 25/86] Fixed bug in mplt SpikesPlot --- holoviews/plotting/mpl/chart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index b6082029ee..3af3bdd4c2 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -1018,7 +1018,7 @@ def get_data(self, element, ranges): dimensions = element.dimensions(label=True) ndims = len(dimensions) - ypos = self.position + pos = self.position if ndims > 1: data = [[(x, pos), (x, pos+y)] for x, y in element.array()] else: From 4345ea13696311578797641dfb99f91c8abc835e Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 02:41:10 +0000 Subject: [PATCH 26/86] Changed Spikes styling details --- holoviews/plotting/bokeh/__init__.py | 1 + holoviews/plotting/mpl/__init__.py | 1 + holoviews/plotting/mpl/chart.py | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/__init__.py b/holoviews/plotting/bokeh/__init__.py index 600f43b220..136e2e6ddb 100644 --- a/holoviews/plotting/bokeh/__init__.py +++ b/holoviews/plotting/bokeh/__init__.py @@ -115,6 +115,7 @@ options.Spread = Options('style', fill_color=Cycle(), fill_alpha=0.6, line_color='black') options.Histogram = Options('style', fill_color="#036564", line_color="#033649") options.Points = Options('style', color=Cycle()) +options.Spikes = Options('style', color='black') # Paths options.Contours = Options('style', color=Cycle()) diff --git a/holoviews/plotting/mpl/__init__.py b/holoviews/plotting/mpl/__init__.py index 1a1768f261..1fd5d3c228 100644 --- a/holoviews/plotting/mpl/__init__.py +++ b/holoviews/plotting/mpl/__init__.py @@ -177,6 +177,7 @@ def grid_selector(grid): options.Scatter3D = Options('style', facecolors=Cycle(), marker='o') options.Scatter3D = Options('plot', fig_size=150) options.Surface = Options('plot', fig_size=150) +options.Spikes = Options('style', color='black') # 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 3af3bdd4c2..bb3fc23d90 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -984,7 +984,7 @@ class SpikesPlot(PathPlot): color_index = param.Integer(default=1, doc=""" Index of the dimension from which the color will the drawn""") - spike_length = param.Number(default=0.5, doc=""" + spike_length = param.Number(default=0.1, doc=""" The length of each spike if Spikes object is one dimensional.""") position = param.Number(default=0., doc=""" @@ -1053,6 +1053,9 @@ class MarginalRugPlot(SpikesPlot): Aspect ratios on SideHistogramPlot should be determined by the AdjointLayoutPlot.""") + bgcolor = param.Parameter(default=(1, 1, 1, 0), doc=""" + Make plot background invisible.""") + show_title = param.Boolean(default=False, doc=""" Titles should be disabled on all SidePlots to avoid clutter.""") From 1c047bb5934a654f78062b77cd88da30722a95e5 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 02:52:48 +0000 Subject: [PATCH 27/86] Updated Spikes examples in notebooks --- doc/Tutorials/Elements.ipynb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/Tutorials/Elements.ipynb b/doc/Tutorials/Elements.ipynb index 3a3965a95c..66f14e9995 100644 --- a/doc/Tutorials/Elements.ipynb +++ b/doc/Tutorials/Elements.ipynb @@ -486,7 +486,7 @@ }, "outputs": [], "source": [ - "%%opts Spikes [spike_height=0.1] (alpha=0.4)\n", + "%%opts Spikes (alpha=0.4)\n", "xs = np.random.rand(50)\n", "ys = np.random.rand(50)\n", "hv.Points((xs, ys)) * hv.Spikes(xs)" @@ -507,7 +507,7 @@ }, "outputs": [], "source": [ - "%%opts Spikes (cmap='Blues')\n", + "%%opts Spikes (cmap='Reds')\n", "hv.Spikes(np.random.rand(20, 2), kdims=['Mass'], vdims=['Intensity'])" ] }, @@ -515,7 +515,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Another possibility is to draw a number of spike trains as you would encounter in neuroscience:" + "Another possibility is to draw a number of spike trains as you would encounter in neuroscience. Here we generate 10 separate random spike trains and distribute them evenly across the space by setting their ``position``. By also declaring some yticks each spike traing can be labeled individually:" ] }, { @@ -526,16 +526,16 @@ }, "outputs": [], "source": [ - "%%opts Spikes [spike_height=0.1] NdOverlay [show_legend=False]\n", - "hv.NdOverlay({i: hv.Spikes(np.random.rand(10, 1))(plot=dict(yposition=0.1*i))\n", - " for i in range(10)})(plot=dict(yticks=[((i+1)*0.1-0.05, i)for i in range(10)]))" + "%%opts Spikes NdOverlay [show_legend=False]\n", + "hv.NdOverlay({i: hv.Spikes(np.random.randint(0, 100, 10), kdims=['Time'])(plot=dict(position=0.1*i))\n", + " for i in range(10)})(plot=dict(yticks=[((i+1)*0.1-0.05, i) for i in range(10)]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Finally we may use ``Spikes`` to adjoin to a regular plot to visualize marginal distributions:" + "Finally we may use ``Spikes`` to visualize marginal distributions as adjoined plots:" ] }, { @@ -546,8 +546,8 @@ }, "outputs": [], "source": [ - "%%opts Spikes (alpha=0.4) [bgcolor='w'] AdjointLayout [border_size=0]\n", - "points = hv.Points(np.random.randn(100, 2))\n", + "%%opts Spikes (alpha=0.05) [spike_length=0.5] AdjointLayout [border_size=0]\n", + "points = hv.Points(np.random.randn(500, 2))\n", "points << hv.Spikes(points['y']) << hv.Spikes(points['x'])" ] }, From b42241a7182ae5acb491c71afe57dc0c8aaa0158 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 04:17:44 +0000 Subject: [PATCH 28/86] Added faster pandas based NdMapping.groupby helper function --- holoviews/core/ndmapping.py | 18 +++------------ holoviews/core/util.py | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py index 0cb9e8f639..b91f329b7b 100644 --- a/holoviews/core/ndmapping.py +++ b/holoviews/core/ndmapping.py @@ -11,7 +11,7 @@ import param -from . import traversal +from . import traversal, util from .dimension import OrderedDict, Dimension, Dimensioned, ViewableElement from .util import unique_iterator, sanitize_identifier, dimension_sort, group_select, iterative_select, basestring, wrap_tuple, process_ellipses @@ -275,22 +275,10 @@ def groupby(self, dimensions, container_type=None, group_type=None, **kwargs): if self.ndims == 1: self.warning('Cannot split Map with only one dimension.') return self - - dimensions = [self.get_dimension(d).name for d in dimensions] container_type = container_type if container_type else type(self) group_type = group_type if group_type else type(self) - dims, inds = zip(*((self.get_dimension(dim), self.get_dimension_index(dim)) - for dim in dimensions)) - inames, idims = zip(*((dim.name, dim) for dim in self.kdims - if not dim.name in dimensions)) - selects = unique_iterator(itemgetter(*inds)(key) if len(inds) > 1 else (key[inds[0]],) - for key in self.data.keys()) - with item_check(False): - selects = group_select(list(selects)) - groups = [(k, group_type((v.reindex(inames) if isinstance(v, NdMapping) - else [((), (v,))]), **kwargs)) - for k, v in iterative_select(self, dimensions, selects)] - return container_type(groups, kdims=dims) + dimensions = [self.get_dimension(d) for d in dimensions] + return util.ndmapping_groupby(self, dimensions, container_type, group_type, **kwargs) def add_dimension(self, dimension, dim_pos, dim_val, vdim=False, **kwargs): diff --git a/holoviews/core/util.py b/holoviews/core/util.py index c45a239ee9..7a25b1bd3c 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -710,3 +710,47 @@ def get_param_values(data): def wrap_tuple(unwrapped): """ Wraps any non-tuple types in a tuple """ return (unwrapped if isinstance(unwrapped, tuple) else (unwrapped,)) + +def unpack_group(group, getter): + for k, v in group.iterrows(): + obj = v.values[0] + key = getter(k) + if hasattr(obj, 'kdims'): + yield (key, obj) + else: + yield [((), (v.ix[0],))] + + +def ndmapping_groupby_pandas(ndmapping, dimensions, container_type, group_type, **kwargs): + from .ndmapping import NdMapping + idims = [dim for dim in ndmapping.kdims if dim not in dimensions] + all_dims = [d.name for d in ndmapping.kdims] + inds = [ndmapping.get_dimension_index(dim) for dim in idims] + getter = operator.itemgetter(*inds) + multi_index = pd.MultiIndex.from_tuples(ndmapping.keys(), + names=all_dims) + df = pd.DataFrame(ndmapping.values(), index=multi_index) + grouped = ((k, group_type(unpack_group(group, getter), kdims=idims, **kwargs)) + for k, group in df.groupby(level=[d.name for d in dimensions])) + return container_type(grouped, kdims=dimensions) + + +def ndmapping_groupby_python(ndmapping, dimensions, container_type, group_type, **kwargs): + inds = [ndmapping.get_dimension_index(dim) for dim in dimensions] + idims = [dim for dim in ndmapping.kdims if dim not in dimensions] + dim_names = [dim.name for dim in dimensions] + getter = operator.itemgetter(*inds) + selects = unique_iterator(getter(key) if len(inds) > 1 else (key[inds[0]],) + for key in ndmapping.data.keys()) + selects = group_select(list(selects)) + groups = [(k, group_type((v.reindex(idims) if hasattr(v, 'kdims') + else [((), (v,))]), **kwargs)) + for k, v in iterative_select(ndmapping, dim_names, selects)] + return container_type(groups, kdims=dimensions) + + +try: + import pandas + ndmapping_groupby = ndmapping_groupby_pandas +except: + ndmapping_groupby = ndmapping_groupby_python From 9e3e44e28bcbb0f3026eaec225ebcc44e566644e Mon Sep 17 00:00:00 2001 From: philippjfr Date: Fri, 11 Dec 2015 14:30:41 +0000 Subject: [PATCH 29/86] 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 0391727272..860116e0fb 160000 --- a/doc/reference_data +++ b/doc/reference_data @@ -1 +1 @@ -Subproject commit 0391727272a64381496f8a62b591672c3fb8f2c1 +Subproject commit 860116e0fb8744d85a90d88d0a6ec5d35da814e1 From 6c7c9f5cea30b6eed6d1bf33ee1322366fe4b328 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 14:40:12 +0000 Subject: [PATCH 30/86] Added Spikes 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 7bf3aaee0e..56c5046389 100644 --- a/holoviews/element/comparison.py +++ b/holoviews/element/comparison.py @@ -154,6 +154,7 @@ def register(cls): cls.equality_type_funcs[Trisurface] = cls.compare_trisurface cls.equality_type_funcs[Histogram] = cls.compare_histogram cls.equality_type_funcs[Bars] = cls.compare_bars + cls.equality_type_funcs[Spikes] = cls.compare_spikes # Tables cls.equality_type_funcs[ItemTable] = cls.compare_itemtables @@ -500,6 +501,10 @@ def compare_vectorfield(cls, el1, el2, msg='VectorField'): def compare_bars(cls, el1, el2, msg='Bars'): cls.compare_columns(el1, el2, msg) + @classmethod + def compare_spikes(cls, el1, el2, msg='Spikes'): + cls.compare_columns(el1, el2, msg) + #=========# # Rasters # #=========# From 527033ec583e907f5c275a30acf2bf49f3ab228c Mon Sep 17 00:00:00 2001 From: philippjfr Date: Fri, 11 Dec 2015 15:25:23 +0000 Subject: [PATCH 31/86] 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 860116e0fb..014c069ea2 160000 --- a/doc/reference_data +++ b/doc/reference_data @@ -1 +1 @@ -Subproject commit 860116e0fb8744d85a90d88d0a6ec5d35da814e1 +Subproject commit 014c069ea26ed90f289b0362577cdee2310b3618 From 95dbccd3a4e2e8e6442060f8bbb2ee5c633d1846 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 15:23:57 +0000 Subject: [PATCH 32/86] Updated Spikes docstring --- holoviews/element/chart.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/holoviews/element/chart.py b/holoviews/element/chart.py index 5e5a01ab73..82f66a10a5 100644 --- a/holoviews/element/chart.py +++ b/holoviews/element/chart.py @@ -349,11 +349,12 @@ def __init__(self, data, **params): class Spikes(Chart): """ - Spikes is a 1D or 2D Element, which represents vertical or - horizontal ticks along some dimension. If an additional dimension - is supplied it will be used to specify the height of the - lines. The Element may therefore be used to represent 1D - distributions, spectrograms or spike trains in electrophysiology. + Spikes is a 1D or 2D Element, which represents a series of + vertical or horizontal lines distributed along some dimension. If + an additional dimension is supplied it will be used to specify the + height of the lines. The Element may therefore be used to + represent 1D distributions, spectrograms or spike trains in + electrophysiology. """ group = param.String(default='Spikes', constant=True) From 7716fba118d75eade861d2120c017bcb9ccf4b0d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 16:03:32 +0000 Subject: [PATCH 33/86] Consistent naming for adjoined plots --- holoviews/plotting/bokeh/__init__.py | 6 +++--- holoviews/plotting/bokeh/chart.py | 4 ++-- holoviews/plotting/mpl/__init__.py | 2 +- holoviews/plotting/mpl/chart.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/holoviews/plotting/bokeh/__init__.py b/holoviews/plotting/bokeh/__init__.py index 136e2e6ddb..485294b374 100644 --- a/holoviews/plotting/bokeh/__init__.py +++ b/holoviews/plotting/bokeh/__init__.py @@ -14,7 +14,7 @@ from .callbacks import Callbacks from .element import OverlayPlot, BokehMPLWrapper, BokehMPLRawWrapper from .chart import (PointPlot, CurvePlot, SpreadPlot, ErrorPlot, HistogramPlot, - AdjointHistogramPlot, SpikesPlot, MarginalSpikesPlot) + SideHistogramPlot, SpikesPlot, SideSpikesPlot) from .path import PathPlot, PolygonPlot from .plot import GridPlot, LayoutPlot, AdjointLayoutPlot from .raster import RasterPlot, RGBPlot, HeatmapPlot @@ -81,8 +81,8 @@ 'bokeh') -AdjointLayoutPlot.registry[Histogram] = AdjointHistogramPlot -AdjointLayoutPlot.registry[Spikes] = MarginalSpikesPlot +AdjointLayoutPlot.registry[Histogram] = SideHistogramPlot +AdjointLayoutPlot.registry[Spikes] = SideSpikesPlot try: from ..mpl.seaborn import TimeSeriesPlot, BivariatePlot, DistributionPlot diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index b2350b7f6c..79f8ad16bf 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -143,7 +143,7 @@ def get_data(self, element, ranges=None): return (data, mapping) -class AdjointHistogramPlot(HistogramPlot): +class SideHistogramPlot(HistogramPlot): style_opts = HistogramPlot.style_opts + ['cmap'] @@ -265,7 +265,7 @@ def get_data(self, element, ranges=None): -class MarginalSpikesPlot(SpikesPlot): +class SideSpikesPlot(SpikesPlot): """ SpikesPlot with useful defaults for plotting adjoined rug plot. """ diff --git a/holoviews/plotting/mpl/__init__.py b/holoviews/plotting/mpl/__init__.py index 1fd5d3c228..e331ee7d56 100644 --- a/holoviews/plotting/mpl/__init__.py +++ b/holoviews/plotting/mpl/__init__.py @@ -159,7 +159,7 @@ def grid_selector(grid): MPLPlot.sideplots.update({Histogram: SideHistogramPlot, GridSpace: GridPlot, - Spikes: MarginalRugPlot}) + Spikes: SideSpikesPlot}) options = Store.options(backend='matplotlib') diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index bb3fc23d90..4858ef159a 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -1047,7 +1047,7 @@ def update_handles(self, axis, element, key, ranges=None): paths.set_array(cs) -class MarginalRugPlot(SpikesPlot): +class SideSpikesPlot(SpikesPlot): aspect = param.Parameter(default='auto', doc=""" Aspect ratios on SideHistogramPlot should be determined by the From 885bc2fd54ca02d6987da1799bf0cbf8faa9d9c2 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 17:13:23 +0000 Subject: [PATCH 34/86] Fixed shared_axes for Bokeh adjoined plots --- holoviews/plotting/bokeh/plot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index fae3328145..8e436037e1 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -530,7 +530,8 @@ def initialize_plot(self, ranges=None, plots=[]): subplot = self.subplots.get(pos, None) # If no view object or empty position, disable the axis if subplot: - adjoined_plots.append(subplot.initialize_plot(ranges=ranges, plots=plots)) + passed_plots = plots + adjoined_plots + adjoined_plots.append(subplot.initialize_plot(ranges=ranges, plots=passed_plots)) self.drawn = True if not adjoined_plots: adjoined_plots = [None] return adjoined_plots From 8f164edbe790f5252aebcd4092ee98e418b1a76b Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 11 Dec 2015 18:15:51 +0000 Subject: [PATCH 35/86] Fixed bug in matplotlib pdf exporting --- holoviews/plotting/mpl/renderer.py | 2 +- holoviews/plotting/renderer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/mpl/renderer.py b/holoviews/plotting/mpl/renderer.py index 70b549ccf3..f75252881f 100644 --- a/holoviews/plotting/mpl/renderer.py +++ b/holoviews/plotting/mpl/renderer.py @@ -273,7 +273,7 @@ def validate(cls, options): """ Validates a dictionary of options set on the backend. """ - if options['fig']=='pdf' and not cls.options['fig'] == 'pdf': + if options['fig']=='pdf': outputwarning.warning("PDF output is experimental, may not be supported" "by your browser and may change in future.") diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py index e473741039..9d34dd7d64 100644 --- a/holoviews/plotting/renderer.py +++ b/holoviews/plotting/renderer.py @@ -210,6 +210,7 @@ def html(self, obj, fmt=None, css=None): """ plot, fmt = self._validate(obj, fmt) figdata, _ = self(plot, fmt) + if css is None: css = self.css if fmt in ['html', 'json']: return figdata @@ -220,7 +221,6 @@ def html(self, obj, fmt=None, css=None): w,h = self.get_size(plot) css['height'] = '%dpx' % (h*self.dpi*1.15) - if css is None: css = self.css if isinstance(css, dict): css = '; '.join("%s: %s" % (k, v) for k, v in css.items()) else: From 166dcad91f84752455f4e08dc4b26b8187d29192 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 12 Dec 2015 02:03:37 +0000 Subject: [PATCH 36/86] Moved OrderedDict conditional import into core.util --- holoviews/core/dimension.py | 7 +------ holoviews/core/util.py | 5 +++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index 39b5e95744..3ab15b0452 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -6,17 +6,12 @@ import re from operator import itemgetter -try: - from cyordereddict import OrderedDict -except: - from collections import OrderedDict - import numpy as np import param from ..core.util import (basestring, sanitize_identifier, group_sanitizer, label_sanitizer, max_range, - find_range, dimension_sanitizer) + find_range, dimension_sanitizer, OrderedDict) from .options import Store, StoreOptions from .pprint import PrettyPrinter diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 7a25b1bd3c..e5952f188f 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -8,6 +8,11 @@ import numpy as np import param +try: + from cyordereddict import OrderedDict +except: + from collections import OrderedDict + try: import pandas as pd except ImportError: From 70236059c3f9166f7feac70659702a85b11e8266 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 12 Dec 2015 02:04:16 +0000 Subject: [PATCH 37/86] Fixes and cleanup of NdElement --- holoviews/core/element.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/holoviews/core/element.py b/holoviews/core/element.py index d94885a2f0..7e42eed403 100644 --- a/holoviews/core/element.py +++ b/holoviews/core/element.py @@ -348,8 +348,11 @@ def reindex(self, kdims=None, vdims=None, force=False): def _add_item(self, key, value, sort=True, update=True): - value = (value,) if np.isscalar(value) else tuple(value) - if len(value) != len(self.vdims): + if np.isscalar(value): + value = (value,) + elif not isinstance(value, NdElement): + value = tuple(value) + if len(value) != len(self.vdims) and not isinstance(value, NdElement): raise ValueError("%s values must match value dimensions" % type(self).__name__) super(NdElement, self)._add_item(key, value, sort, update) @@ -458,14 +461,6 @@ def sample(self, samples=[]): return self.clone(sample_data) - def _item_check(self, dim_vals, data): - if isinstance(data, tuple): - for el in data: - self._item_check(dim_vals, el) - return - super(NdElement, self)._item_check(dim_vals, data) - - def aggregate(self, dimensions, function, **kwargs): """ Allows aggregating. @@ -474,7 +469,7 @@ def aggregate(self, dimensions, function, **kwargs): grouped = self.groupby(dimensions) if len(dimensions) else HoloMap({(): self}, kdims=[]) for k, group in grouped.data.items(): reduced = [] - for vdim in group.vdims: + for vdim in self.vdims: data = group[vdim.name] if isinstance(function, np.ufunc): reduced.append(function.reduce(data, **kwargs)) From 214ed1524f1aae1f14d92e06e53d492d9f57fedb Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 12 Dec 2015 02:04:50 +0000 Subject: [PATCH 38/86] Fixed sorting in NdMapping pandas groupby implementation --- holoviews/core/ndmapping.py | 4 +++- holoviews/core/util.py | 39 ++++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py index b91f329b7b..018f423504 100644 --- a/holoviews/core/ndmapping.py +++ b/holoviews/core/ndmapping.py @@ -278,7 +278,9 @@ def groupby(self, dimensions, container_type=None, group_type=None, **kwargs): container_type = container_type if container_type else type(self) group_type = group_type if group_type else type(self) dimensions = [self.get_dimension(d) for d in dimensions] - return util.ndmapping_groupby(self, dimensions, container_type, group_type, **kwargs) + sort = not self._sorted + return util.ndmapping_groupby(self, dimensions, container_type, + group_type, sort=sort, **kwargs) def add_dimension(self, dimension, dim_pos, dim_val, vdim=False, **kwargs): diff --git a/holoviews/core/util.py b/holoviews/core/util.py index e5952f188f..1127ddfb2c 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -716,6 +716,14 @@ def wrap_tuple(unwrapped): """ Wraps any non-tuple types in a tuple """ return (unwrapped if isinstance(unwrapped, tuple) else (unwrapped,)) + +def get_unique_keys(ndmapping, dimensions): + inds = [ndmapping.get_dimension_index(dim) for dim in dimensions] + getter = operator.itemgetter(*inds) + return unique_iterator(getter(key) if len(inds) > 1 else (key[inds[0]],) + for key in ndmapping.data.keys()) + + def unpack_group(group, getter): for k, v in group.iterrows(): obj = v.values[0] @@ -723,30 +731,35 @@ def unpack_group(group, getter): if hasattr(obj, 'kdims'): yield (key, obj) else: - yield [((), (v.ix[0],))] + obj = tuple(v) + yield (wrap_tuple(key), obj) -def ndmapping_groupby_pandas(ndmapping, dimensions, container_type, group_type, **kwargs): - from .ndmapping import NdMapping - idims = [dim for dim in ndmapping.kdims if dim not in dimensions] +def ndmapping_groupby_pandas(ndmapping, dimensions, container_type, + group_type, sort=False, **kwargs): + idims = [dim for dim in ndmapping.kdims if dim not in dimensions + and dim != 'Index'] all_dims = [d.name for d in ndmapping.kdims] inds = [ndmapping.get_dimension_index(dim) for dim in idims] - getter = operator.itemgetter(*inds) - multi_index = pd.MultiIndex.from_tuples(ndmapping.keys(), - names=all_dims) + getter = operator.itemgetter(*inds) if inds else lambda x: tuple() + + multi_index = pd.MultiIndex.from_tuples(ndmapping.keys(), names=all_dims) df = pd.DataFrame(ndmapping.values(), index=multi_index) - grouped = ((k, group_type(unpack_group(group, getter), kdims=idims, **kwargs)) + + kwargs = dict(dict(get_param_values(ndmapping), kdims=idims), **kwargs) + groups = ((wrap_tuple(k), group_type(OrderedDict(unpack_group(group, getter)), **kwargs)) for k, group in df.groupby(level=[d.name for d in dimensions])) - return container_type(grouped, kdims=dimensions) + + if sort: + selects = list(get_unique_keys(ndmapping, dimensions)) + groups = sorted(groups, key=lambda x: selects.index(x[0])) + return container_type(groups, kdims=dimensions) def ndmapping_groupby_python(ndmapping, dimensions, container_type, group_type, **kwargs): - inds = [ndmapping.get_dimension_index(dim) for dim in dimensions] idims = [dim for dim in ndmapping.kdims if dim not in dimensions] dim_names = [dim.name for dim in dimensions] - getter = operator.itemgetter(*inds) - selects = unique_iterator(getter(key) if len(inds) > 1 else (key[inds[0]],) - for key in ndmapping.data.keys()) + selects = get_unique_keys(ndmapping, dimensions) selects = group_select(list(selects)) groups = [(k, group_type((v.reindex(idims) if hasattr(v, 'kdims') else [((), (v,))]), **kwargs)) From 4698695f6b8320dfbe0b8f637d19444d709a9fce Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 12 Dec 2015 02:37:34 +0000 Subject: [PATCH 39/86] Fixes for pure Python NdElement groupby --- holoviews/core/element.py | 10 +++++++--- holoviews/core/util.py | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/holoviews/core/element.py b/holoviews/core/element.py index 7e42eed403..f70ce8dd85 100644 --- a/holoviews/core/element.py +++ b/holoviews/core/element.py @@ -284,13 +284,17 @@ def __init__(self, data=None, **params): if isinstance(data, list) and all(np.isscalar(el) for el in data): data = (((k,), (v,)) for k, v in enumerate(data)) - if not isinstance(data, NdElement) and isinstance(data, Element): - mapping = data.mapping() + if isinstance(data, Element): + params = dict(get_param_values(data), **params) + if isinstance(data, NdElement): + mapping = data.mapping() + data = mapping.data + else: + data = data.data if 'kdims' not in params: params['kdims'] = mapping.kdims if 'vdims' not in params: params['vdims'] = mapping.vdims - data = mapping.data kdims = params.get('kdims', self.kdims) if (data is not None and not isinstance(data, NdMapping) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 1127ddfb2c..9ae7b1ff18 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -756,7 +756,8 @@ def ndmapping_groupby_pandas(ndmapping, dimensions, container_type, return container_type(groups, kdims=dimensions) -def ndmapping_groupby_python(ndmapping, dimensions, container_type, group_type, **kwargs): +def ndmapping_groupby_python(ndmapping, dimensions, container_type, + group_type, sort=False, **kwargs): idims = [dim for dim in ndmapping.kdims if dim not in dimensions] dim_names = [dim.name for dim in dimensions] selects = get_unique_keys(ndmapping, dimensions) From c22a10baae6adff29ef094cec20777ed04812ea7 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 12 Dec 2015 03:07:15 +0000 Subject: [PATCH 40/86] Disabled item_checks in NdMapping.groupby --- holoviews/core/ndmapping.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py index 018f423504..612e09ada0 100644 --- a/holoviews/core/ndmapping.py +++ b/holoviews/core/ndmapping.py @@ -279,8 +279,9 @@ def groupby(self, dimensions, container_type=None, group_type=None, **kwargs): group_type = group_type if group_type else type(self) dimensions = [self.get_dimension(d) for d in dimensions] sort = not self._sorted - return util.ndmapping_groupby(self, dimensions, container_type, - group_type, sort=sort, **kwargs) + with item_check(False): + return util.ndmapping_groupby(self, dimensions, container_type, + group_type, sort=sort, **kwargs) def add_dimension(self, dimension, dim_pos, dim_val, vdim=False, **kwargs): From a76f7443a8e9a07eb7467047c4f3bdc0b4ad805c Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 12 Dec 2015 13:44:03 +0000 Subject: [PATCH 41/86] Small fix to ndmapping_groupby_pandas function Avoids hardcoding the 'Index' dimension used for NdElement types --- holoviews/core/util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 9ae7b1ff18..5c84ada3be 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -737,8 +737,11 @@ def unpack_group(group, getter): def ndmapping_groupby_pandas(ndmapping, dimensions, container_type, group_type, sort=False, **kwargs): - idims = [dim for dim in ndmapping.kdims if dim not in dimensions - and dim != 'Index'] + if 'kdims' in kwargs: + idims = [ndmapping.get_dimension(d) for d in kwargs['kdims']] + else: + idims = [dim for dim in ndmapping.kdims if dim not in dimensions] + all_dims = [d.name for d in ndmapping.kdims] inds = [ndmapping.get_dimension_index(dim) for dim in idims] getter = operator.itemgetter(*inds) if inds else lambda x: tuple() From e6b9320d72ce9127baa3639cfcd4dcde889e8a3b Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sat, 12 Dec 2015 14:52:20 +0000 Subject: [PATCH 42/86] Refactored ndmapping_groupby into a parameterized function --- holoviews/core/util.py | 78 ++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 5c84ada3be..af59932822 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -735,44 +735,56 @@ def unpack_group(group, getter): yield (wrap_tuple(key), obj) -def ndmapping_groupby_pandas(ndmapping, dimensions, container_type, - group_type, sort=False, **kwargs): - if 'kdims' in kwargs: - idims = [ndmapping.get_dimension(d) for d in kwargs['kdims']] - else: - idims = [dim for dim in ndmapping.kdims if dim not in dimensions] - all_dims = [d.name for d in ndmapping.kdims] - inds = [ndmapping.get_dimension_index(dim) for dim in idims] - getter = operator.itemgetter(*inds) if inds else lambda x: tuple() - multi_index = pd.MultiIndex.from_tuples(ndmapping.keys(), names=all_dims) - df = pd.DataFrame(ndmapping.values(), index=multi_index) +class ndmapping_groupby(param.ParameterizedFunction): + """ + Apply a groupby operation to an NdMapping, using pandas to improve + performance (if available). + """ - kwargs = dict(dict(get_param_values(ndmapping), kdims=idims), **kwargs) - groups = ((wrap_tuple(k), group_type(OrderedDict(unpack_group(group, getter)), **kwargs)) - for k, group in df.groupby(level=[d.name for d in dimensions])) + def __call__(self, ndmapping, dimensions, container_type, + group_type, sort=False, **kwargs): + try: + import pandas + groupby = self.groupby_pandas + except: + groupby = self.groupby_python + return groupby(ndmapping, dimensions, container_type, + group_type, sort=sort, **kwargs) - if sort: - selects = list(get_unique_keys(ndmapping, dimensions)) - groups = sorted(groups, key=lambda x: selects.index(x[0])) - return container_type(groups, kdims=dimensions) + @param.parameterized.bothmethod + def groupby_pandas(self_or_cls, ndmapping, dimensions, container_type, + group_type, sort=False, **kwargs): + if 'kdims' in kwargs: + idims = [ndmapping.get_dimension(d) for d in kwargs['kdims']] + else: + idims = [dim for dim in ndmapping.kdims if dim not in dimensions] + all_dims = [d.name for d in ndmapping.kdims] + inds = [ndmapping.get_dimension_index(dim) for dim in idims] + getter = operator.itemgetter(*inds) if inds else lambda x: tuple() -def ndmapping_groupby_python(ndmapping, dimensions, container_type, - group_type, sort=False, **kwargs): - idims = [dim for dim in ndmapping.kdims if dim not in dimensions] - dim_names = [dim.name for dim in dimensions] - selects = get_unique_keys(ndmapping, dimensions) - selects = group_select(list(selects)) - groups = [(k, group_type((v.reindex(idims) if hasattr(v, 'kdims') - else [((), (v,))]), **kwargs)) - for k, v in iterative_select(ndmapping, dim_names, selects)] - return container_type(groups, kdims=dimensions) + multi_index = pd.MultiIndex.from_tuples(ndmapping.keys(), names=all_dims) + df = pd.DataFrame(ndmapping.values(), index=multi_index) + kwargs = dict(dict(get_param_values(ndmapping), kdims=idims), **kwargs) + groups = ((wrap_tuple(k), group_type(OrderedDict(unpack_group(group, getter)), **kwargs)) + for k, group in df.groupby(level=[d.name for d in dimensions])) -try: - import pandas - ndmapping_groupby = ndmapping_groupby_pandas -except: - ndmapping_groupby = ndmapping_groupby_python + if sort: + selects = list(get_unique_keys(ndmapping, dimensions)) + groups = sorted(groups, key=lambda x: selects.index(x[0])) + return container_type(groups, kdims=dimensions) + + @param.parameterized.bothmethod + def groupby_python(self_or_cls, ndmapping, dimensions, container_type, + group_type, sort=False, **kwargs): + idims = [dim for dim in ndmapping.kdims if dim not in dimensions] + dim_names = [dim.name for dim in dimensions] + selects = get_unique_keys(ndmapping, dimensions) + selects = group_select(list(selects)) + groups = [(k, group_type((v.reindex(idims) if hasattr(v, 'kdims') + else [((), (v,))]), **kwargs)) + for k, v in iterative_select(ndmapping, dim_names, selects)] + return container_type(groups, kdims=dimensions) From 367a9d28ede890679c98c4fdf7a318b7c25f7381 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sat, 12 Dec 2015 16:33:51 +0000 Subject: [PATCH 43/86] Added support for setting the legend font size --- holoviews/plotting/bokeh/element.py | 4 ++++ holoviews/plotting/mpl/element.py | 2 +- holoviews/plotting/plot.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 48dd3b90b5..6bb3121048 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -611,6 +611,10 @@ def _process_legend(self): if not plot.legend: return plot.legend[0].set(**options) + legend_fontsize = self._fontsize('legend', 'size').get('size',False) + if legend_fontsize: + plot.legend[0].label_text_font_size = legend_fontsize + plot.legend.orientation = self.legend_position legends = plot.legend[0].legends new_legends = [] diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index 9dfc455418..a1620dc1fe 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -646,7 +646,7 @@ def _adjust_legend(self, axis): if self.legend_cols: leg_spec['ncol'] = self.legend_cols leg = axis.legend(data.keys(), data.values(), title=title, scatterpoints=1, - **leg_spec) + **dict(leg_spec, **self._fontsize('legend'))) frame = leg.get_frame() frame.set_facecolor('1.0') frame.set_edgecolor('0.0') diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py index e6d81561a9..5d27bdc334 100644 --- a/holoviews/plotting/plot.py +++ b/holoviews/plotting/plot.py @@ -135,7 +135,7 @@ class DimensionedPlot(Plot): Finer control is available by supplying a dictionary where any unmentioned keys reverts to the default sizes, e.g: - {'ticks':20, 'title':15, 'ylabel':5, 'xlabel':5} + {'ticks':20, 'title':15, 'ylabel':5, 'xlabel':5, 'legend':8} You can set the fontsize of both 'ylabel' and 'xlabel' together using the 'labels' key.""") From 46533c5c18065b6ab99edab05becb33db0eb8faf Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sat, 12 Dec 2015 16:49:22 +0000 Subject: [PATCH 44/86] Added support for setting the legend title font size --- holoviews/plotting/mpl/element.py | 3 +++ holoviews/plotting/plot.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index a1620dc1fe..5838d759d9 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -647,6 +647,9 @@ def _adjust_legend(self, axis): leg = axis.legend(data.keys(), data.values(), title=title, scatterpoints=1, **dict(leg_spec, **self._fontsize('legend'))) + title_fontsize = self._fontsize('legend_title') + if title_fontsize: + leg.get_title().set_fontsize(title_fontsize['fontsize']) frame = leg.get_frame() frame.set_facecolor('1.0') frame.set_edgecolor('0.0') diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py index 5d27bdc334..7201b42605 100644 --- a/holoviews/plotting/plot.py +++ b/holoviews/plotting/plot.py @@ -135,7 +135,9 @@ class DimensionedPlot(Plot): Finer control is available by supplying a dictionary where any unmentioned keys reverts to the default sizes, e.g: - {'ticks':20, 'title':15, 'ylabel':5, 'xlabel':5, 'legend':8} + {'ticks':20, 'title':15, + 'ylabel':5, 'xlabel':5, + 'legend':8, 'legend_title':13} You can set the fontsize of both 'ylabel' and 'xlabel' together using the 'labels' key.""") From 28030ccc484e519f79e90ea9c2574fed48178127 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sat, 12 Dec 2015 19:44:58 +0000 Subject: [PATCH 45/86] Improved the Options tutorial with example of fontsize plot option --- doc/Tutorials/Options.ipynb | 44 +++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/doc/Tutorials/Options.ipynb b/doc/Tutorials/Options.ipynb index e3bd347a64..83aedd7b4b 100644 --- a/doc/Tutorials/Options.ipynb +++ b/doc/Tutorials/Options.ipynb @@ -228,7 +228,8 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "scrolled": false }, "outputs": [], "source": [ @@ -523,6 +524,45 @@ "\n", "Here parentheses indicate style options, square brackets indicate plot options, and curly brackets indicate norm options (with ``+axiswise`` and ``+framewise`` indicating True for those values, and ``-axiswise`` and ``-framewise`` indicating False). Additional *target-specification*s and associated options of each type for that *target-specification* can be supplied at the end of this line. This ultra-concise syntax is used throughout the other tutorials, because it helps minimize the code needed to specify the plotting options, and helps make it very clear that these options are handled separately from the actual data.\n", "\n", + "Here we demonstrate the concise syntax by customizing the style and plot options of the ``Curve`` in the layout:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%opts Curve (color='r') [fontsize={'xlabel':15, 'ticks':8}] \n", + "layout" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The color of the curve has been changed to red and the fontsizes of the x-axis label and all the tick labels has been modified. The ``fontsize`` is an important plot option and you can find more information about the available options in the ``fontsize`` documentation above.\n", + "\n", + "The ``%%opts`` magic is designed to allow incremental customization which explains why the curve in the cell above has retained the increased thickness specified earlier. To reset all the customizations that have been applied to an object, you can create a fresh, uncustomized copy as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "The ``%opts`` \"line\" magic (with one ``%``) works just the same as the ``%%opts`` \"cell\" magic, but it changes the global default options for all future cells, allowing you to choose a new default colormap, line width, etc.\n", "\n", "Apart from its brevity, a big benefit of using the IPython magic syntax ``%%opts`` or ``%opts`` is that it is fully tab-completable. Each of the options that is currently available will be listed if you press ```` when you are ready to write it, which makes it much easier to find the right parameter. Of course, you will still need to consult the full ``holoviews.help`` documentation (described above) to see the type, allowable values, and documentation for each option, but the tab completion should at least get you started and is great for helping you remember the list of options and see which options are available.\n", @@ -600,7 +640,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.10" + "version": "2.7.11" } }, "nbformat": 4, From 351b13d85d8728069fcee2b46b6b90d05f68e7f9 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sun, 13 Dec 2015 01:46:48 +0000 Subject: [PATCH 46/86] Added display_formats to Store and notebook_extension --- holoviews/core/options.py | 4 ++++ holoviews/ipython/__init__.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/holoviews/core/options.py b/holoviews/core/options.py index ab2a3aaac0..f5185f6d11 100644 --- a/holoviews/core/options.py +++ b/holoviews/core/options.py @@ -769,6 +769,10 @@ class Store(object): # types grouped by the backend. Set using the register method. registry = {} + # A list of formats to be published for display on the frontend (e.g + # IPython Notebook or a GUI application) + display_formats = ['html'] + # Once register_plotting_classes is called, this OptionTree is # populated for the given backend. _options = {} diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py index 3e37c80eec..371d0b3b7c 100644 --- a/holoviews/ipython/__init__.py +++ b/holoviews/ipython/__init__.py @@ -105,6 +105,16 @@ class notebook_extension(param.ParameterizedFunction): width = param.Number(default=None, bounds=(0, 100), doc=""" Width of the notebook as a percentage of the browser screen window width.""") + display_formats = param.List(default=['html'], doc=""" + A list of formats that are rendered to the notebook where + multiple formats may be selected at once (although only one + format will be displayed). + + Although the 'html' format is supported across backends, other + formats supported by the current backend (e.g 'png' and 'svg' + using the matplotlib backend) may be used. This may be useful to + export figures to other formats such as PDF with nbconvert. """) + ip = param.Parameter(default=None, doc="IPython kernel instance") _loaded = False @@ -113,6 +123,13 @@ def __call__(self, **params): resources = self._get_resources(params) ip = params.pop('ip', None) p = param.ParamOverrides(self, params) + Store.display_formats = p.display_formats + + if 'html' not in p.display_formats and len(p.display_formats) > 1: + msg = ('Output magic unable to control displayed format ' + 'as IPython notebook uses fixed precedence ' + 'between %r' % p.display_formats) + display(HTML('Warning: %s' % msg)) if notebook_extension._loaded == False: ip = get_ipython() if ip is None else ip From 9e2ec3b1006f4d1b9f4b146933cc2201e8fab701 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sun, 13 Dec 2015 01:48:41 +0000 Subject: [PATCH 47/86] Added optional PNG and SVG display hooks --- holoviews/ipython/display_hooks.py | 54 +++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/holoviews/ipython/display_hooks.py b/holoviews/ipython/display_hooks.py index 0d84e8d639..bf4ae1e56b 100644 --- a/holoviews/ipython/display_hooks.py +++ b/holoviews/ipython/display_hooks.py @@ -188,13 +188,59 @@ def display(obj, raw=False, **kwargs): def pprint_display(obj): - # If pretty printing is off, return None (will fallback to repr) + if 'html' not in Store.display_formats: + return None + + # If pretty printing is off, return None (fallback to next display format) ip = get_ipython() # # pyflakes:ignore (in IPython namespace) if not ip.display_formatter.formatters['text/plain'].pprint: return None return display(obj, raw=True) +@display_hook +def element_png_display(element, max_frames, max_branches): + """ + Used to render elements to PNG if requested in the display formats. + """ + if 'png' not in Store.display_formats: + return None + info = process_object(element) + if info: return info + + backend = Store.current_backend + if type(element) not in Store.registry[backend]: + return None + renderer = Store.renderers[backend] + # Current renderer does not support PNG + if 'png' not in renderer.params('fig').objects: + return None + + data, info = renderer(element, fmt='png') + return data + + +@display_hook +def element_svg_display(element, max_frames, max_branches): + """ + Used to render elements to SVG if requested in the display formats. + """ + if 'svg' not in Store.display_formats: + return None + info = process_object(element) + if info: return info + + backend = Store.current_backend + if type(element) not in Store.registry[backend]: + return None + renderer = Store.renderers[backend] + # Current renderer does not support SVG + if 'svg' not in renderer.params('fig').objects: + return None + data, info = renderer(element, fmt='svg') + return data + + # display_video output by default, but may be set to first_frame, # middle_frame or last_frame (e.g. for testing purposes) render_anim = None @@ -205,3 +251,9 @@ def set_display_hooks(ip): html_formatter.for_type(UniformNdMapping, pprint_display) html_formatter.for_type(AdjointLayout, pprint_display) html_formatter.for_type(Layout, pprint_display) + + png_formatter = ip.display_formatter.formatters['image/png'] + png_formatter.for_type(ViewableElement, element_png_display) + + svg_formatter = ip.display_formatter.formatters['image/svg+xml'] + svg_formatter.for_type(ViewableElement, element_svg_display) From f39cf843e2209fc6b9715f55eae2e818ebd25e8f Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sun, 13 Dec 2015 01:49:29 +0000 Subject: [PATCH 48/86] Output magic now validates fig against Store.display_formats --- holoviews/ipython/magics.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/holoviews/ipython/magics.py b/holoviews/ipython/magics.py index 7b734202be..5da3d94149 100644 --- a/holoviews/ipython/magics.py +++ b/holoviews/ipython/magics.py @@ -60,6 +60,7 @@ def update_options(cls, options, items): Allows updating options depending on class attributes and unvalidated options. """ + pass @classmethod def get_options(cls, line, options, linemagic): @@ -98,10 +99,10 @@ def get_options(cls, line, options, linemagic): raise ValueError("Value %r for key %r not between %s and %s" % info) options[keyword] = value - return cls._validate(options, linemagic) + return cls._validate(options, items, linemagic) @classmethod - def _validate(cls, options, linemagic): + def _validate(cls, options, items, linemagic): "Allows subclasses to check options are valid." raise NotImplementedError("OptionsMagic is an abstract base class.") @@ -303,8 +304,16 @@ def _generate_docstring(cls): @classmethod - def _validate(cls, options, linemagic): + def _validate(cls, options, items, linemagic): "Validation of edge cases and incompatible options" + + if 'html' in Store.display_formats: + pass + elif 'fig' in items and items['fig'] not in Store.display_formats: + msg = ("Output magic requesting figure format %r " % items['fig'] + + "not in display formats %r" % Store.display_formats) + display(HTML("Warning: %s" % msg)) + backend = Store.current_backend return Store.renderers[backend].validate(options) From 18ed28414c8cf1e8d2ab09dc379bc47d4ea7fb85 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sun, 13 Dec 2015 17:17:18 +0000 Subject: [PATCH 49/86] Minor correction to Options Tutorial --- doc/Tutorials/Options.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Tutorials/Options.ipynb b/doc/Tutorials/Options.ipynb index 83aedd7b4b..ab0e452bfb 100644 --- a/doc/Tutorials/Options.ipynb +++ b/doc/Tutorials/Options.ipynb @@ -543,7 +543,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The color of the curve has been changed to red and the fontsizes of the x-axis label and all the tick labels has been modified. The ``fontsize`` is an important plot option and you can find more information about the available options in the ``fontsize`` documentation above.\n", + "The color of the curve has been changed to red and the fontsizes of the x-axis label and all the tick labels have been modified. The ``fontsize`` is an important plot option and you can find more information about the available options in the ``fontsize`` documentation above.\n", "\n", "The ``%%opts`` magic is designed to allow incremental customization which explains why the curve in the cell above has retained the increased thickness specified earlier. To reset all the customizations that have been applied to an object, you can create a fresh, uncustomized copy as follows:" ] From a82e857920fdd73ad0f60100b6d9d370d80cf343 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sun, 13 Dec 2015 18:48:54 +0000 Subject: [PATCH 50/86] 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 014c069ea2..ce95f69271 160000 --- a/doc/reference_data +++ b/doc/reference_data @@ -1 +1 @@ -Subproject commit 014c069ea26ed90f289b0362577cdee2310b3618 +Subproject commit ce95f69271774ead4054f04874e70c40cad3bcb8 From 69d1a76352d36b2c80dd715d2566442ea862288b Mon Sep 17 00:00:00 2001 From: jlstevens Date: Sun, 13 Dec 2015 20:35:18 +0000 Subject: [PATCH 51/86] Allowed specifying resources via *args in notebook_extension --- holoviews/ipython/__init__.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py index 371d0b3b7c..328bec402e 100644 --- a/holoviews/ipython/__init__.py +++ b/holoviews/ipython/__init__.py @@ -119,8 +119,8 @@ class notebook_extension(param.ParameterizedFunction): _loaded = False - def __call__(self, **params): - resources = self._get_resources(params) + def __call__(self, *args, **params): + resources = self._get_resources(args, params) ip = params.pop('ip', None) p = param.ParamOverrides(self, params) Store.display_formats = p.display_formats @@ -162,7 +162,7 @@ def __call__(self, **params): Store.renderers[r].load_nb() - def _get_resources(self, params): + def _get_resources(self, args, params): """ Finds the list of resources from the keyword parameters and pops them out of the params dictionary. @@ -170,13 +170,23 @@ def _get_resources(self, params): resources = [] disabled = [] for resource in ['holoviews'] + list(Store.renderers.keys()): + if resource in args: + resources.append(resource) + if resource in params: setting = params.pop(resource) if setting is True and resource != 'matplotlib': - resources.append(resource) + if resource not in resources: + resources.append(resource) if setting is False: disabled.append(resource) + unmatched_args = set(args) - set(resources) + if unmatched_args: + display(HTML('Warning: Unrecognized resources %s' + % ', '.join(unmatched_args))) + + resources = [r for r in resources if r not in disabled] if ('holoviews' not in disabled) and ('holoviews' not in resources): resources = ['holoviews'] + resources return resources From 64acd57701b2b3fe6791d49f926caf4f9a181679 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 14 Dec 2015 00:30:31 +0000 Subject: [PATCH 52/86] Fixed bug in bokeh AdjointLayoutPlot --- holoviews/plotting/bokeh/plot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index 8e436037e1..b625662bf4 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -524,13 +524,14 @@ def initialize_plot(self, ranges=None, plots=[]): invoke subplots with correct options and styles and hide any empty axes as necessary. """ + if plots is None: plots = [] adjoined_plots = [] for pos in ['main', 'right', 'top']: # Pos will be one of 'main', 'top' or 'right' or None subplot = self.subplots.get(pos, None) # If no view object or empty position, disable the axis if subplot: - passed_plots = plots + adjoined_plots + passed_plots = plots + adjoined_plots adjoined_plots.append(subplot.initialize_plot(ranges=ranges, plots=passed_plots)) self.drawn = True if not adjoined_plots: adjoined_plots = [None] From d4cdafd50e4765ed21ca8ce9f56ab35d1fe26c21 Mon Sep 17 00:00:00 2001 From: Chris B Date: Mon, 14 Dec 2015 18:53:15 +0000 Subject: [PATCH 53/86] Fixed minor typo. --- holoviews/core/spaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py index 47e4df901d..5c5614aee1 100644 --- a/holoviews/core/spaces.py +++ b/holoviews/core/spaces.py @@ -258,7 +258,7 @@ def sample(self, samples=[], bounds=None, **sample_values): X,Y = np.meshgrid(xsamples, ysamples) linsamples = zip(X.flat, Y.flat) else: - raise NotImplementedError("Regular sampling not implented" + raise NotImplementedError("Regular sampling not implented " "for high-dimensional Views.") samples = set(self.last.closest(linsamples)) From 1c330be4972c813a631ea5bb30b14a8a4e34dd11 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 15 Dec 2015 02:59:28 +0000 Subject: [PATCH 54/86] NdMapping.label/group no longer recursively iterate --- holoviews/core/ndmapping.py | 29 ++++++++++++++++++----------- holoviews/core/util.py | 10 ++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py index 612e09ada0..f977582559 100644 --- a/holoviews/core/ndmapping.py +++ b/holoviews/core/ndmapping.py @@ -13,7 +13,8 @@ from . import traversal, util from .dimension import OrderedDict, Dimension, Dimensioned, ViewableElement -from .util import unique_iterator, sanitize_identifier, dimension_sort, group_select, iterative_select, basestring, wrap_tuple, process_ellipses +from .util import (unique_iterator, sanitize_identifier, dimension_sort, + basestring, wrap_tuple, process_ellipses, itervalues) class item_check(object): @@ -754,12 +755,14 @@ def group(self): if self._group: return self._group else: - vals = self.values() - groups = {v.group for v in vals - if not v._auxiliary_component} - if len(groups) == 1: - tp = type(vals[0]).__name__ - group = list(groups)[0] + if len(self): + group = None + els = itervalues(self.data) + while group is None: + el = next(els) + if not el._auxiliary_component: + group = el.group + tp = type(el).__name__ if tp != group: return group return type(self).__name__ @@ -778,10 +781,14 @@ def label(self): if self._label: return self._label else: - labels = {v.label for v in self.values() - if not v._auxiliary_component} - if len(labels) == 1: - return list(labels)[0] + if len(self): + label = None + els = itervalues(self.data) + while label is None: + el = next(els) + if not el._auxiliary_component: + label = el.label + return '' if label is None else label else: return '' diff --git a/holoviews/core/util.py b/holoviews/core/util.py index af59932822..3f7b3ed8f2 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -717,6 +717,16 @@ def wrap_tuple(unwrapped): return (unwrapped if isinstance(unwrapped, tuple) else (unwrapped,)) +def itervalues(obj): + "Get value iterator from dictionary for Python 2 and 3" + return iter(obj.values()) if sys.version_info.major == 3 else obj.itervalues() + + +def iterkeys(obj): + "Get key iterator from dictionary for Python 2 and 3" + return iter(obj.keys()) if sys.version_info.major == 3 else obj.iterkeys() + + def get_unique_keys(ndmapping, dimensions): inds = [ndmapping.get_dimension_index(dim) for dim in dimensions] getter = operator.itemgetter(*inds) From 6f898868330814bcdcc4706b672e5bcd97d58f56 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 15 Dec 2015 16:28:09 +0000 Subject: [PATCH 55/86] Fixed bokeh webgl version requirement --- holoviews/plotting/bokeh/element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 6bb3121048..5e2f2a475c 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -249,7 +249,7 @@ def _init_plot(self, key, plots, ranges=None): properties['x_axis_label'] = xlabel if 'x' in self.show_labels else ' ' properties['y_axis_label'] = ylabel if 'y' in self.show_labels else ' ' - if LooseVersion(bokeh.__version__) > LooseVersion('0.10'): + if LooseVersion(bokeh.__version__) >= LooseVersion('0.10'): properties['webgl'] = True return bokeh.plotting.Figure(x_axis_type=x_axis_type, y_axis_type=y_axis_type, From 0d029a0b60e94abbf8f422cd21b3a3a1b8cfdf50 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 15 Dec 2015 16:29:46 +0000 Subject: [PATCH 56/86] Minor fix for matplotlib PointPlot color_index option --- holoviews/plotting/mpl/chart.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 4858ef159a..62b94e9157 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -562,17 +562,19 @@ def initialize_plot(self, ranges=None): ndims = points.shape[1] xs = points.dimension_values(0) if len(points.data) else [] ys = points.dimension_values(1) if len(points.data) else [] - cs = points.dimension_values(self.color_index) if self.color_index < ndims else None + cs = None + if self.color_index is not None and self.color_index < ndims: + cs = points.dimension_values(self.color_index) style = self.style[self.cyclic_index] if self.size_index < ndims and self.scaling_factor > 1: style['s'] = self._compute_size(points, style) color = style.pop('color', None) - if cs is not None: - style['c'] = cs - else: + if cs is None: style['c'] = color + else: + style['c'] = cs edgecolor = style.pop('edgecolors', 'none') legend = points.label if self.show_legend else '' scatterplot = axis.scatter(xs, ys, zorder=self.zorder, label=legend, From 9712dc3a21d5b9f321ecd77798964c28f27d50c6 Mon Sep 17 00:00:00 2001 From: Chris B Date: Tue, 15 Dec 2015 16:43:07 +0000 Subject: [PATCH 57/86] Fixed minor typo. --- holoviews/core/spaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py index 5c5614aee1..be80d24114 100644 --- a/holoviews/core/spaces.py +++ b/holoviews/core/spaces.py @@ -258,7 +258,7 @@ def sample(self, samples=[], bounds=None, **sample_values): X,Y = np.meshgrid(xsamples, ysamples) linsamples = zip(X.flat, Y.flat) else: - raise NotImplementedError("Regular sampling not implented " + raise NotImplementedError("Regular sampling not implemented " "for high-dimensional Views.") samples = set(self.last.closest(linsamples)) From edb1360fa1439f75d5052f45e275cfa5066ce3b1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 15 Dec 2015 19:14:54 +0000 Subject: [PATCH 58/86] Bokeh callbacks now have option to be applied during plotting --- holoviews/plotting/bokeh/callbacks.py | 36 +++++++++++++++++++++------ holoviews/plotting/bokeh/element.py | 3 +++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index e3c40a4abc..2480ea471e 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -34,6 +34,9 @@ class Callback(param.ParameterizedFunction): modified bokeh plot objects. """ + apply_on_update = param.Boolean(default=True, doc=""" + Whether the callback is applied when the plot is updated""") + callback_obj = param.ClassSelector(class_=(PlotObject,), doc=""" Bokeh PlotObject the callback is applied to.""") @@ -286,7 +289,7 @@ def initialize_callback(self, cb_obj, plot, pycallback): object to be installed on the appropriate plot object. """ if pycallback.reinitialize: - pycallback = pycallback.instance() + pycallback = pycallback.instance(plots=[]) pycallback.callback_obj = cb_obj pycallback.plots.append(plot) @@ -297,23 +300,20 @@ def initialize_callback(self, cb_obj, plot, pycallback): # Generate callback JS code to get all the requested data self_callback = Callback.IPython_callback.format(callback_id=cb_id) - data, code = {}, '' + code = '' for k, v in pycallback.plot_attributes.items(): format_kwargs = dict(key=repr(k), attrs=repr(v)) if v is None: code += "data[{key}] = plot.get({key});\n".format(**format_kwargs) - data[k] = plot.state.vm_props().get(k) else: code += "data[{key}] = {attrs}.map(function(attr) {{" \ " return plot.get({key}).get(attr)" \ "}})\n".format(**format_kwargs) - data[k] = [plot.state.vm_props().get(k).vm_props().get(attr) - for attr in v] if pycallback.cb_attributes: code += "data['cb_obj'] = {attrs}.map(function(attr) {{"\ " return cb_obj.get(attr)}});\n".format(attrs=repr(pycallback.cb_attributes)) - data['cb_obj'] = [pycallback.callback_obj.vm_props().get(attr) - for attr in pycallback.cb_attributes] + + data = self._get_data(pycallback, plot) code = Callback.JS_callback + code + pycallback.code + self_callback # Generate CustomJS object @@ -326,6 +326,19 @@ def initialize_callback(self, cb_obj, plot, pycallback): return customjs, pycallback + def _get_data(self, pycallback, plot): + data = {} + for k, v in pycallback.plot_attributes.items(): + if v is None: + data[k] = plot.state.vm_props().get(k) + else: + data[k] = [plot.state.vm_props().get(k).vm_props().get(attr) + for attr in v] + if pycallback.cb_attributes: + data['cb_obj'] = [pycallback.callback_obj.vm_props().get(attr) + for attr in pycallback.cb_attributes] + return data + def _chain_callbacks(self, plot, cb_obj, callbacks): """ Initializes new callbacks and chains them to @@ -368,3 +381,12 @@ def __call__(self, plot): if self.selection: for tool in plot.state.select(type=(ColumnDataSource)): self._chain_callbacks(plot, tool, self.selection) + + def update(self, plot): + """ + Allows updating the callbacks before data is sent to frontend. + """ + for cb in self.callbacks.values(): + if cb.apply_on_update and plot in cb.plots: + data = self._get_data(cb, plot) + cb(data) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 5e2f2a475c..4ca5b433d4 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -431,6 +431,7 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): self._update_plot(key, plot, element) if self.callbacks: self.callbacks(self) + self.callbacks.update(self) self._process_legend() self.drawn = True @@ -474,6 +475,8 @@ def update_frame(self, key, ranges=None, plot=None, element=None): if not self.overlaid: self._update_ranges(element, ranges) self._update_plot(key, plot, element) + if self.callbacks: + self.callbacks.update(self) @property From 8f846cc2f4d5bdc07c2e3368c3ac7a2e1a36d0e3 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 15 Dec 2015 19:53:01 +0000 Subject: [PATCH 59/86] Fixed and optimized Bokeh DownsampleColumns callback --- holoviews/plotting/bokeh/callbacks.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index 2480ea471e..792ff7095a 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -14,6 +14,7 @@ import param +from ...core.data import ArrayColumns from .plot import BokehPlot @@ -212,13 +213,24 @@ class DownsampleColumns(Callback): up to max_samples. """ - max_samples = param.Integer(default=800) + max_samples = param.Integer(default=800, doc=""" + Maximum number of samples to display at the same time.""") + + random_seed = param.Integer(default=42, doc=""" + Seed used to initialize randomization.""") + + reinitialize = param.Boolean(default=True, doc=""" + DownsampleColumns should be reinitialized per plot object""") plot_attributes = param.Dict(default={'x_range': ['start', 'end'], 'y_range': ['start', 'end']}) def initialize(self, data): - return self(data) + plot = self.plots[0] + maxn = np.max([len(el) for el in plot.hmap]) + np.random.seed(self.random_seed) + self.random_index = np.random.choice(maxn, maxn, False) + def __call__(self, data): xstart, xend = data['x_range'] @@ -226,6 +238,8 @@ def __call__(self, data): plot = self.plots[0] element = plot.current_frame + if element.interface is not ArrayColumns: + element = plot.current_frame.clone(datatype=['array']) ranges = plot.current_ranges # Slice element to current ranges @@ -237,14 +251,13 @@ def __call__(self, data): if len(sliced) > self.max_samples: # Randomize element samples and slice to region # Randomization consistent to avoid "flicker". - np.random.seed(42) - inds = np.random.choice(len(element), len(element), False) + length = len(element) + inds = self.random_index[self.random_index Date: Tue, 15 Dec 2015 20:25:20 +0000 Subject: [PATCH 60/86] Callbacks don't get called on each plotting update by default --- holoviews/plotting/bokeh/callbacks.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index 792ff7095a..742173b847 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -35,7 +35,7 @@ class Callback(param.ParameterizedFunction): modified bokeh plot objects. """ - apply_on_update = param.Boolean(default=True, doc=""" + apply_on_update = param.Boolean(default=False, doc=""" Whether the callback is applied when the plot is updated""") callback_obj = param.ClassSelector(class_=(PlotObject,), doc=""" @@ -165,6 +165,10 @@ class DownsampleImage(Callback): constraints. """ + apply_on_update = param.Boolean(default=True, doc=""" + Callback should always be applied after each update to + downsample the data before it is displayed.""") + max_width = param.Integer(default=250, doc=""" Maximum plot width in pixels after slicing and downsampling.""") @@ -213,6 +217,10 @@ class DownsampleColumns(Callback): up to max_samples. """ + apply_on_update = param.Boolean(default=True, doc=""" + Callback should always be applied after each update to + downsample the data before it is displayed.""") + max_samples = param.Integer(default=800, doc=""" Maximum number of samples to display at the same time.""") From 68e275e7884a9201557b089bb5af625e3f7df9a4 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 15 Dec 2015 21:04:58 +0000 Subject: [PATCH 61/86] Fix to how AttrTree handles name mangled attributes --- holoviews/core/tree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/core/tree.py b/holoviews/core/tree.py index e78958f706..d940d9a630 100644 --- a/holoviews/core/tree.py +++ b/holoviews/core/tree.py @@ -198,7 +198,8 @@ def __getattr__(self, identifier): return super(AttrTree, self).__getattr__(identifier) except AttributeError: pass - if identifier.startswith('__'): + # Attributes starting with __ get name mangled + if identifier.startswith('_' + type(self).__name__): raise AttributeError('Attribute %s not found.' % identifier) elif self.fixed==True: raise AttributeError(self._fixed_error % identifier) identifier = util.sanitize_identifier(identifier, escape=False) From c98c84eaacf2647f8443269f7b8d1b3210adaae6 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 15 Dec 2015 21:25:15 +0000 Subject: [PATCH 62/86] AttrTree checking for double underscore in special methods --- holoviews/core/tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/core/tree.py b/holoviews/core/tree.py index d940d9a630..97c63d0f43 100644 --- a/holoviews/core/tree.py +++ b/holoviews/core/tree.py @@ -199,7 +199,7 @@ def __getattr__(self, identifier): except AttributeError: pass # Attributes starting with __ get name mangled - if identifier.startswith('_' + type(self).__name__): + if identifier.startswith('_' + type(self).__name__) or identifier.startswith('__'): raise AttributeError('Attribute %s not found.' % identifier) elif self.fixed==True: raise AttributeError(self._fixed_error % identifier) identifier = util.sanitize_identifier(identifier, escape=False) From 0679b76945e70cd5da005429351182ba1c30eb7d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 03:59:49 +0000 Subject: [PATCH 63/86] Factored out function to get NdMapping label and group --- holoviews/core/ndmapping.py | 17 ++++------------- holoviews/core/util.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py index f977582559..2ff44cb85c 100644 --- a/holoviews/core/ndmapping.py +++ b/holoviews/core/ndmapping.py @@ -14,7 +14,8 @@ from . import traversal, util from .dimension import OrderedDict, Dimension, Dimensioned, ViewableElement from .util import (unique_iterator, sanitize_identifier, dimension_sort, - basestring, wrap_tuple, process_ellipses, itervalues) + basestring, wrap_tuple, process_ellipses, itervalues, + get_ndmapping_label) class item_check(object): @@ -756,12 +757,7 @@ def group(self): return self._group else: if len(self): - group = None - els = itervalues(self.data) - while group is None: - el = next(els) - if not el._auxiliary_component: - group = el.group + group = get_ndmapping_label(self, 'group') tp = type(el).__name__ if tp != group: return group @@ -782,12 +778,7 @@ def label(self): return self._label else: if len(self): - label = None - els = itervalues(self.data) - while label is None: - el = next(els) - if not el._auxiliary_component: - label = el.label + label = get_ndmapping_label(self, 'label') return '' if label is None else label else: return '' diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 3f7b3ed8f2..7043005235 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -712,6 +712,20 @@ def get_param_values(data): return params +def get_ndmapping_label(ndmapping, attr): + """ + Function to get the first non-auxiliary object + label attribute from an NdMapping. + """ + label = None + els = itervalues(self.data) + while label is None: + el = next(els) + if not el._auxiliary_component: + label = getattr(el, attr) + return label + + def wrap_tuple(unwrapped): """ Wraps any non-tuple types in a tuple """ return (unwrapped if isinstance(unwrapped, tuple) else (unwrapped,)) From 931b06b955cc4327bcbc479cc8cf96275a88f607 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 04:00:34 +0000 Subject: [PATCH 64/86] Implemented more efficient get_dimension_type on Columns interfaces --- holoviews/core/data.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/holoviews/core/data.py b/holoviews/core/data.py index 7de10b667a..fcce252f09 100644 --- a/holoviews/core/data.py +++ b/holoviews/core/data.py @@ -348,6 +348,18 @@ def dimension_values(self, dim, unique=False): return dim_vals + def get_dimension_type(self, dim): + """ + Returns the specified Dimension type if specified or + if the dimension_values types are consistent otherwise + None is returned. + """ + dim_obj = self.get_dimension(dim) + if dim_obj and dim_obj.type is not None: + return dim_obj.type + return self.interface.dtype(self, dim) + + def dframe(self, dimensions=None): """ Returns the data in the form of a DataFrame. @@ -356,6 +368,7 @@ def dframe(self, dimensions=None): dimensions = [self.get_dimension(d).name for d in dimensions] return self.interface.dframe(self, dimensions) + def columns(self, dimensions=None): if dimensions is None: dimensions = self.dimensions() dimensions = [self.get_dimension(d) for d in dimensions] @@ -602,6 +615,9 @@ def reshape(cls, eltype, data, kdims, vdims): raise ValueError("NdColumns interface couldn't convert data.""") return data, kdims, vdims + @classmethod + def dimension_type(cls, columns, dim): + return Dimensioned.get_dimension_type(columns, dim) @classmethod def shape(cls, columns): @@ -670,6 +686,12 @@ class DFColumns(DataColumns): datatype = 'dataframe' + @classmethod + def dimension_type(cls, columns, dim): + name = columns.get_dimension(dim).name + idx = list(columns.data.columns).index(name) + return columns.data.dtypes[idx].type + @classmethod def reshape(cls, eltype, data, kdims, vdims): element_params = eltype.params() @@ -856,6 +878,10 @@ class ArrayColumns(DataColumns): datatype = 'array' + @classmethod + def dimension_type(cls, columns, dim): + return columns.data.dtype.type + @classmethod def reshape(cls, eltype, data, kdims, vdims): if kdims is None: @@ -1060,6 +1086,11 @@ class DictColumns(DataColumns): datatype = 'dictionary' + @classmethod + def dimension_type(cls, columns, dim): + name = columns.get_dimension(dim).name + return columns.data[name].dtype.type + @classmethod def reshape(cls, eltype, data, kdims, vdims): odict_types = (OrderedDict, cyODict) From f2e693105886a8e19b98f58a5e0aed8de05dbfd5 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 04:25:02 +0000 Subject: [PATCH 65/86] Fixed bugs in get_ndmapping_label function --- holoviews/core/ndmapping.py | 10 ++++------ holoviews/core/util.py | 6 +++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py index 2ff44cb85c..08202d439d 100644 --- a/holoviews/core/ndmapping.py +++ b/holoviews/core/ndmapping.py @@ -755,13 +755,10 @@ def clone(self, data=None, shared_data=True, *args, **overrides): def group(self): if self._group: return self._group - else: - if len(self): - group = get_ndmapping_label(self, 'group') - tp = type(el).__name__ - if tp != group: - return group + group = get_ndmapping_label(self, 'group') if len(self) else None + if group is None: return type(self).__name__ + return group @group.setter @@ -783,6 +780,7 @@ def label(self): else: return '' + @label.setter def label(self, label): if label is not None and not sanitize_identifier.allowable(label): diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 7043005235..057a5710cd 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -718,11 +718,15 @@ def get_ndmapping_label(ndmapping, attr): label attribute from an NdMapping. """ label = None - els = itervalues(self.data) + els = itervalues(ndmapping.data) while label is None: el = next(els) if not el._auxiliary_component: label = getattr(el, attr) + if attr == 'group': + tp = type(el).__name__ + if tp == label: + return None return label From b8a7efd8d1625ae5479b99612bd77e1638ac5bfc Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 15 Dec 2015 22:47:03 +0000 Subject: [PATCH 66/86] Allowed updating styles per frame in Bokeh --- holoviews/plotting/bokeh/element.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 4ca5b433d4..42e3cebb0f 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -469,6 +469,8 @@ def update_frame(self, key, ranges=None, plot=None, element=None): source = self.handles['source'] data, mapping = self.get_data(element, ranges) self._update_datasource(source, data) + + self.style = self.lookup_options(element, 'style') if 'glyph' in self.handles: properties = self._glyph_properties(plot, element, source, ranges) self._update_glyph(self.handles['glyph'], properties, mapping) From 25d66737f88a8127f3369a252d929082852ae067 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 12:07:54 +0000 Subject: [PATCH 67/86] BokehPlot get_data now supports initializing empty datasource --- holoviews/plotting/bokeh/annotation.py | 47 +++++++------ holoviews/plotting/bokeh/chart.py | 91 ++++++++++++++++---------- holoviews/plotting/bokeh/path.py | 14 ++-- holoviews/plotting/bokeh/plot.py | 2 +- holoviews/plotting/bokeh/raster.py | 43 +++++++----- holoviews/plotting/bokeh/tabular.py | 4 +- 6 files changed, 122 insertions(+), 79 deletions(-) diff --git a/holoviews/plotting/bokeh/annotation.py b/holoviews/plotting/bokeh/annotation.py index 59eb8dddeb..03e89b50ea 100644 --- a/holoviews/plotting/bokeh/annotation.py +++ b/holoviews/plotting/bokeh/annotation.py @@ -9,8 +9,10 @@ class TextPlot(ElementPlot): style_opts = text_properties _plot_method = 'text' - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): mapping = dict(x='x', y='y', text='text') + if empty: + return dict(x=[], y=[], text=[]), mapping return (dict(x=[element.x], y=[element.y], text=[element.text]), mapping) @@ -23,19 +25,21 @@ class LineAnnotationPlot(ElementPlot): style_opts = line_properties _plot_method = 'segment' - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): plot = self.handles['plot'] - if isinstance(element, HLine): - x0 = plot.x_range.start - y0 = element.data - x1 = plot.x_range.end - y1 = element.data + if empty: + x0, y0, x1, y1 = [], [], [], [] + elif isinstance(element, HLine): + x0 = [plot.x_range.start] + y0 = [element.data] + x1 = [plot.x_range.end] + y1 = [element.data] elif isinstance(element, VLine): - x0 = element.data - y0 = plot.y_range.start - x1 = element.data - y1 = plot.y_range.end - return (dict(x0=[x0], y0=[y0], x1=[x1], y1=[y1]), + x0 = [element.data] + y0 = [plot.y_range.start] + x1 = [element.data] + y1 = [plot.y_range.end] + return (dict(x0=x0, y0=y0, x1=x1, y1=y1), dict(x0='x0', y0='y0', x1='x1', y1='y1')) @@ -53,10 +57,15 @@ class SplinePlot(ElementPlot): style_opts = line_properties _plot_method = 'bezier' - def get_data(self, element, ranges=None): - verts = np.array(element.data[0]) - xs, ys = verts[:, 0], verts[:, 1] - return (dict(x0=[xs[0]], y0=[ys[0]], x1=[xs[-1]], y1=[ys[-1]], - cx0=[xs[1]], cy0=[ys[1]], cx1=[xs[2]], cy1=[ys[2]]), - dict(x0='x0', y0='y0', x1='x1', y1='y1', - cx0='cx0', cx1='cx1', cy0='cy0', cy1='cy1')) + def get_data(self, element, ranges=None, empty=False): + data_attrs = ['x0', 'y0', 'x1', 'y1', + 'cx0', 'cx1', 'cy0', 'cy1'] + if empty: + data = {attr: [] for attr in data_attrs} + else: + verts = np.array(element.data[0]) + xs, ys = verts[:, 0], verts[:, 1] + data = dict(x0=[xs[0]], y0=[ys[0]], x1=[xs[-1]], y1=[ys[-1]], + cx0=[xs[1]], cy0=[ys[1]], cx1=[xs[2]], cy1=[ys[2]]) + + return (data, dict(zip(data_attrs, data_attrs))) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 79f8ad16bf..8e497faf19 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -41,7 +41,7 @@ class PointPlot(ElementPlot): _plot_method = 'scatter' - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): style = self.style[self.cyclic_index] dims = element.dimensions(label=True) @@ -52,22 +52,29 @@ def get_data(self, element, ranges=None): if self.color_index < len(dims) and cmap: map_key = 'color_' + dims[self.color_index] mapping['color'] = map_key - cmap = get_cmap(cmap) - colors = element.dimension_values(self.color_index) - crange = ranges.get(dims[self.color_index], None) - data[map_key] = map_colors(colors, crange, cmap) + if empty: + data[map_key] = [] + else: + cmap = get_cmap(cmap) + colors = element.dimension_values(self.color_index) + crange = ranges.get(dims[self.color_index], None) + data[map_key] = map_colors(colors, crange, cmap) if self.size_index < len(dims): map_key = 'size_' + dims[self.size_index] mapping['size'] = map_key - ms = style.get('size', 1) - sizes = element.dimension_values(self.size_index) - data[map_key] = compute_sizes(sizes, self.size_fn, - self.scaling_factor, ms) - data[dims[0]] = element.dimension_values(0) - data[dims[1]] = element.dimension_values(1) + if empty: + data[map_key] = [] + else: + ms = style.get('size', 1) + sizes = element.dimension_values(self.size_index) + data[map_key] = compute_sizes(sizes, self.size_fn, + self.scaling_factor, ms) + + data[dims[0]] = [] if empty else element.dimension_values(0) + data[dims[1]] = [] if empty else element.dimension_values(1) if 'hover' in self.tools: for d in dims[2:]: - data[d] = element.dimension_values(d) + data[d] = [] if empty else element.dimension_values(d) return data, mapping @@ -97,11 +104,11 @@ class CurvePlot(ElementPlot): style_opts = ['color'] + line_properties _plot_method = 'line' - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): x = element.get_dimension(0).name y = element.get_dimension(1).name - return ({x: element.dimension_values(0), - y: element.dimension_values(1)}, + return ({x: [] if empty else element.dimension_values(0), + y: [] if empty else element.dimension_values(1)}, dict(x=x, y=y)) @@ -112,7 +119,9 @@ class SpreadPlot(PolygonPlot): def __init__(self, *args, **kwargs): super(SpreadPlot, self).__init__(*args, **kwargs) - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=None): + if empty: + return dict(xs=[], ys=[]), self._mapping xvals = element.dimension_values(0) mean = element.dimension_values(1) @@ -132,13 +141,16 @@ class HistogramPlot(ElementPlot): style_opts = ['color'] + line_properties + fill_properties _plot_method = 'quad' - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=None): mapping = dict(top='top', bottom=0, left='left', right='right') - data = dict(top=element.values, left=element.edges[:-1], - right=element.edges[1:]) + if empty: + data = dict(top=[], left=[], right=[]) + else: + data = dict(top=element.values, left=element.edges[:-1], + right=element.edges[1:]) if 'hover' in self.default_tools + self.tools: - data.update({d: element.dimension_values(d) + data.update({d: [] if empty else element.dimension_values(d) for d in element.dimensions(label=True)}) return (data, mapping) @@ -154,14 +166,17 @@ class SideHistogramPlot(HistogramPlot): show_title = param.Boolean(default=False, doc=""" Whether to display the plot title.""") - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=None): if self.invert_axes: mapping = dict(top='left', bottom='right', left=0, right='top') else: mapping = dict(top='top', bottom=0, left='left', right='right') - data = dict(top=element.values, left=element.edges[:-1], - right=element.edges[1:]) + if empty: + data = dict(top=[], left=[], right=[]) + else: + data = dict(top=element.values, left=element.edges[:-1], + right=element.edges[1:]) dim = element.get_dimension(0).name main = self.adjoined.main @@ -174,12 +189,11 @@ def get_data(self, element, ranges=None): if 'cmap' in style or 'palette' in style: cmap = get_cmap(style.get('cmap', style.get('palette', None))) - colors = map_colors(vals, main_range, cmap) - data['color'] = colors + data['color'] = [] if empty else map_colors(vals, main_range, cmap) mapping['fill_color'] = 'color' if 'hover' in self.default_tools + self.tools: - data.update({d: element.dimension_values(d) + data.update({d: [] if empty else element.dimension_values(d) for d in element.dimensions(label=True)}) return (data, mapping) @@ -191,7 +205,10 @@ class ErrorPlot(PathPlot): style_opts = ['color'] + line_properties - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): + if empty: + return dict(xs=[], ys=[]), self._mapping + data = element.array(dimensions=element.dimensions()[0:4]) err_xs = [] err_ys = [] @@ -231,12 +248,14 @@ def get_extents(self, element, ranges): return l, b, r, t - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): style = self.style[self.cyclic_index] dims = element.dimensions(label=True) pos = self.position - if len(dims) > 1: + if empty: + xs, ys, keys = [], [], [] + elif len(dims) > 1: xs, ys = zip(*(((x, x), (pos, pos+y)) for x, y in element.array())) mapping = dict(xs=dims[0], ys=dims[1]) @@ -248,7 +267,7 @@ def get_data(self, element, ranges=None): mapping = dict(xs=dims[0], ys='heights') keys = (dims[0], 'heights') - if self.invert_axes: keys = keys[::-1] + if not empty and self.invert_axes: keys = keys[::-1] data = dict(zip(keys, (xs, ys))) cmap = style.get('palette', style.get('cmap', None)) @@ -256,10 +275,14 @@ def get_data(self, element, ranges=None): cdim = dims[self.color_index] map_key = 'color_' + cdim mapping['color'] = map_key - cmap = get_cmap(cmap) - colors = element.dimension_values(cdim) - crange = ranges.get(cdim, None) - data[map_key] = map_colors(colors, crange, cmap) + if empty: + colors = [] + else: + cmap = get_cmap(cmap) + cvals = element.dimension_values(cdim) + crange = ranges.get(cdim, None) + colors = map_colors(cvals, crange, cmap) + data[map_key] = colors return data, mapping diff --git a/holoviews/plotting/bokeh/path.py b/holoviews/plotting/bokeh/path.py index 4f63cf6abd..cdf192a6c0 100644 --- a/holoviews/plotting/bokeh/path.py +++ b/holoviews/plotting/bokeh/path.py @@ -10,9 +10,9 @@ class PathPlot(ElementPlot): _plot_method = 'multi_line' _mapping = dict(xs='xs', ys='ys') - def get_data(self, element, ranges=None): - xs = [path[:, 0] for path in element.data] - ys = [path[:, 1] for path in element.data] + def get_data(self, element, ranges=None, empty=False): + xs = [] if empty else [path[:, 0] for path in element.data] + ys = [] if empty else [path[:, 1] for path in element.data] return dict(xs=xs, ys=ys), self._mapping @@ -21,9 +21,9 @@ class PolygonPlot(PathPlot): style_opts = ['color', 'cmap', 'palette'] + line_properties + fill_properties _plot_method = 'patches' - def get_data(self, element, ranges=None): - xs = [path[:, 0] for path in element.data] - ys = [path[:, 1] for path in element.data] + def get_data(self, element, ranges=None, empty=False): + xs = [] if empty else [path[:, 0] for path in element.data] + ys = [] if empty else [path[:, 1] for path in element.data] data = dict(xs=xs, ys=ys) style = self.style[self.cyclic_index] @@ -33,6 +33,6 @@ def get_data(self, element, ranges=None): cmap = get_cmap(cmap) colors = map_colors(np.array([element.level]), ranges[element.vdims[0].name], cmap) mapping['color'] = 'color' - data['color'] = list(colors)*len(element.data) + data['color'] = [] if empty else list(colors)*len(element.data) return data, mapping diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index b625662bf4..f8101837f8 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -37,7 +37,7 @@ class BokehPlot(DimensionedPlot): renderer = BokehRenderer - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): """ Returns the data from an element in the appropriate format for initializing or updating a ColumnDataSource and a dictionary diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py index 6ff3fd884c..a5dc7408ec 100644 --- a/holoviews/plotting/bokeh/raster.py +++ b/holoviews/plotting/bokeh/raster.py @@ -20,7 +20,7 @@ def __init__(self, *args, **kwargs): self.invert_yaxis = not self.invert_yaxis - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): img = element.data if isinstance(element, Image): l, b, r, t = element.bounds.lbrt() @@ -29,9 +29,14 @@ def get_data(self, element, ranges=None): dh = t-b if type(element) is Raster: b = t + mapping = dict(image='image', x='x', y='y', dw='dw', dh='dh') - return (dict(image=[np.flipud(img)], x=[l], - y=[b], dw=[r-l], dh=[dh]), mapping) + if empty: + data = dict(image=[], x=[], y=[], dw=[], dh=[]) + else: + data = dict(image=[np.flipud(img)], x=[l], + y=[b], dw=[r-l], dh=[dh]) + return (data, mapping) def _glyph_properties(self, plot, element, source, ranges): @@ -65,11 +70,13 @@ class RGBPlot(RasterPlot): style_opts = [] _plot_method = 'image_rgba' - def get_data(self, element, ranges=None): - data, mapping = super(RGBPlot, self).get_data(element, ranges) + def get_data(self, element, ranges=None, empty=False): + data, mapping = super(RGBPlot, self).get_data(element, ranges, empty) img = data['image'][0] - if img.ndim == 3: + if empty: + data['image'] = [] + elif img.ndim == 3: if img.shape[2] == 3: # alpha channel not included alpha = np.ones(img.shape[:2]) if img.dtype.name == 'uint8': @@ -102,14 +109,18 @@ def _axes_props(self, plots, subplots, element, ranges): return ('auto', 'auto'), labels, plot_ranges - def get_data(self, element, ranges=None): - style = self.style[self.cyclic_index] - cmap = style.get('palette', style.get('cmap', None)) - cmap = get_cmap(cmap) + def get_data(self, element, ranges=None, empty=False): x, y, z = element.dimensions(label=True) - zvals = np.rot90(element.raster, 3).flatten() - colors = map_colors(zvals, ranges[z], cmap) - xvals, yvals = [[str(v) for v in element.dimension_values(i)] - for i in range(2)] - return ({x: xvals, y: yvals, z: zvals, 'color': colors}, - {'x': x, 'y': y, 'fill_color': 'color', 'height': 1, 'width': 1}) + if empty: + data = {x: [], y: [], z: [], 'color': []} + else: + style = self.style[self.cyclic_index] + cmap = style.get('palette', style.get('cmap', None)) + cmap = get_cmap(cmap) + zvals = np.rot90(element.raster, 3).flatten() + colors = map_colors(zvals, ranges[z], cmap) + xvals, yvals = [[str(v) for v in element.dimension_values(i)] + for i in range(2)] + data = {x: xvals, y: yvals, z: zvals, 'color': colors} + + return (data, {'x': x, 'y': y, 'fill_color': 'color', 'height': 1, 'width': 1}) diff --git a/holoviews/plotting/bokeh/tabular.py b/holoviews/plotting/bokeh/tabular.py index 394e58c00c..835408ba59 100644 --- a/holoviews/plotting/bokeh/tabular.py +++ b/holoviews/plotting/bokeh/tabular.py @@ -16,9 +16,9 @@ class TablePlot(BokehPlot, GenericElementPlot): style_opts = ['row_headers', 'selectable', 'editable', 'sortable', 'fit_columns', 'width', 'height'] - def get_data(self, element, ranges=None): + def get_data(self, element, ranges=None, empty=False): dims = element.dimensions() - return ({d.name: element.dimension_values(d) for d in dims}, + return ({d.name: [] if empty else element.dimension_values(d) for d in dims}, {d.name: d.name for d in dims}) From 8aeeba7a71ca1c092fe45f4fba966fb7d0dbeaf7 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 12:09:30 +0000 Subject: [PATCH 68/86] Added DownsamplingCallback baseclass and hooked it up in plots --- holoviews/plotting/bokeh/callbacks.py | 34 +++++++++++++++++---------- holoviews/plotting/bokeh/element.py | 7 ++++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index 742173b847..48f6a38a9c 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -156,7 +156,22 @@ def serialize(self, objects): -class DownsampleImage(Callback): +class DownsampleCallback(Callback): + """ + DownsampleCallbacks can downsample the data before it is + plotted and can therefore provide major speed optimizations. + """ + + apply_on_update = param.Boolean(default=True, doc=""" + Callback should always be applied after each update to + downsample the data before it is displayed.""") + + reinitialize = param.Boolean(default=True, doc=""" + DownsampleColumns should be reinitialized per plot object""") + + + +class DownsampleImage(DownsampleCallback): """ Downsamples any Image plot to the specified max_width and max_height by slicing the @@ -165,10 +180,6 @@ class DownsampleImage(Callback): constraints. """ - apply_on_update = param.Boolean(default=True, doc=""" - Callback should always be applied after each update to - downsample the data before it is displayed.""") - max_width = param.Integer(default=250, doc=""" Maximum plot width in pixels after slicing and downsampling.""") @@ -210,16 +221,13 @@ def __call__(self, data): -class DownsampleColumns(Callback): +class DownsampleColumns(DownsampleCallback): """ Downsamples any column based Element by randomizing the rows and updating the ColumnDataSource with up to max_samples. """ - apply_on_update = param.Boolean(default=True, doc=""" - Callback should always be applied after each update to - downsample the data before it is displayed.""") max_samples = param.Integer(default=800, doc=""" Maximum number of samples to display at the same time.""") @@ -227,9 +235,6 @@ class DownsampleColumns(Callback): random_seed = param.Integer(default=42, doc=""" Seed used to initialize randomization.""") - reinitialize = param.Boolean(default=True, doc=""" - DownsampleColumns should be reinitialized per plot object""") - plot_attributes = param.Dict(default={'x_range': ['start', 'end'], 'y_range': ['start', 'end']}) @@ -381,6 +386,11 @@ def _chain_callbacks(self, plot, cb_obj, callbacks): else: cb_obj.callback = callback + @property + def downsample(self): + return any(isinstance(v, DownsampleCallback) + for _ , v in self.get_param_values()) + def __call__(self, plot): """ diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 42e3cebb0f..54cdaac03b 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -414,7 +414,9 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): self._init_axes(plot) self.handles['plot'] = plot - data, mapping = self.get_data(element, ranges) + # Get data and initialize data source + empty = self.callbacks and self.callbacks.downsample + data, mapping = self.get_data(element, ranges, empty) if source is None: source = self._init_datasource(data) self.handles['source'] = source @@ -467,7 +469,8 @@ def update_frame(self, key, ranges=None, plot=None, element=None): plot = self.handles['plot'] source = self.handles['source'] - data, mapping = self.get_data(element, ranges) + empty = self.callbacks and self.callbacks.downsample + data, mapping = self.get_data(element, ranges, empty) self._update_datasource(source, data) self.style = self.lookup_options(element, 'style') From df31767c6e7640e5a2540fc63b5f5d2c874fc92f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 12:10:16 +0000 Subject: [PATCH 69/86] DownsampleCallback allows recomputing ranges in selected region --- holoviews/plotting/bokeh/callbacks.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py index 48f6a38a9c..ad612d5158 100644 --- a/holoviews/plotting/bokeh/callbacks.py +++ b/holoviews/plotting/bokeh/callbacks.py @@ -228,6 +228,8 @@ class DownsampleColumns(DownsampleCallback): up to max_samples. """ + compute_ranges = param.Boolean(default=False, doc=""" + Whether the ranges are recomputed for the sliced region""") max_samples = param.Integer(default=800, doc=""" Maximum number of samples to display at the same time.""") @@ -253,13 +255,17 @@ def __call__(self, data): element = plot.current_frame if element.interface is not ArrayColumns: element = plot.current_frame.clone(datatype=['array']) - ranges = plot.current_ranges # Slice element to current ranges xdim, ydim = element.dimensions(label=True)[0:2] sliced = element.select(**{xdim: (xstart, xend), ydim: (ystart, yend)}) + if self.compute_ranges: + ranges = {d: element.range(d) for d in element.dimensions()} + else: + ranges = plot.current_ranges + # Avoid randomizing if possible (expensive) if len(sliced) > self.max_samples: # Randomize element samples and slice to region From c7c9f8146e8ead325a5a47f3993f3a64fda811d1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 12:10:53 +0000 Subject: [PATCH 70/86] Fix for bokeh OverlayPlot.init_tools --- holoviews/plotting/bokeh/element.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 54cdaac03b..c59467ae04 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -639,10 +639,12 @@ def _init_tools(self, element): Processes the list of tools to be supplied to the plot. """ tools = [] - for i, subplot in enumerate(self.subplots.values()): - el = element.get(i) - if el is not None: - tools.extend(subplot._init_tools(el)) + for key, subplot in self.subplots.items(): + try: + el = element[key] + except: + el = None + tools.extend(subplot._init_tools(el)) return list(set(tools)) From b36d39e0b4e5bc51fb5fdd01413dcbed9a078aff Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 12:13:03 +0000 Subject: [PATCH 71/86] Fixed bug in Columns.get_dimension_type --- holoviews/core/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/core/data.py b/holoviews/core/data.py index fcce252f09..0ede6b90b5 100644 --- a/holoviews/core/data.py +++ b/holoviews/core/data.py @@ -21,7 +21,7 @@ import param -from .dimension import Dimension +from .dimension import Dimension, Dimensioned from .element import Element, NdElement from .dimension import OrderedDict as cyODict from .ndmapping import NdMapping, item_check, sorted_context @@ -357,7 +357,7 @@ def get_dimension_type(self, dim): dim_obj = self.get_dimension(dim) if dim_obj and dim_obj.type is not None: return dim_obj.type - return self.interface.dtype(self, dim) + return self.interface.dimension_type(self, dim) def dframe(self, dimensions=None): From db0cdcabace0bd92a74057d0773a91cd53e297fe Mon Sep 17 00:00:00 2001 From: philippjfr Date: Wed, 16 Dec 2015 14:44:30 +0000 Subject: [PATCH 72/86] 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 ce95f69271..a2c858cb26 160000 --- a/doc/reference_data +++ b/doc/reference_data @@ -1 +1 @@ -Subproject commit ce95f69271774ead4054f04874e70c40cad3bcb8 +Subproject commit a2c858cb26c1191cbaf7dea7ad13e18c1c3c87e2 From be820f7811018d0f045e0c57de419208220d5bbd Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 15:25:21 +0000 Subject: [PATCH 73/86] BokehPlot._init_glyph now returns the glyph object --- holoviews/plotting/bokeh/chart.py | 8 ++++---- holoviews/plotting/bokeh/element.py | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 8e497faf19..13f8ec7d5e 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -91,12 +91,12 @@ def _init_glyph(self, plot, mapping, properties): color = mapping.pop('color', color) properties.pop('legend', None) unselected = Circle(**dict(properties, fill_color=unselect_color, **mapping)) - selected = Circle(**dict(properties, fill_color=color, **mapping)) - plot.add_glyph(source, selected, selection_glyph=selected, + glyph = Circle(**dict(properties, fill_color=color, **mapping)) + plot.add_glyph(source, selected, selection_glyph=glyph, nonselection_glyph=unselected) else: - getattr(plot, self._plot_method)(**dict(properties, **mapping)) - + glyph = getattr(plot, self._plot_method)(**dict(properties, **mapping)) + return glyph class CurvePlot(ElementPlot): diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index c59467ae04..4c82bb2141 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -3,7 +3,7 @@ import numpy as np import bokeh import bokeh.plotting -from bokeh.models import Range, HoverTool +from bokeh.models import Range, HoverTool, Renderer from bokeh.models.tickers import Ticker, BasicTicker, FixedTicker from bokeh.models.widgets import Panel, Tabs from distutils.version import LooseVersion @@ -372,7 +372,7 @@ def _init_glyph(self, plot, mapping, properties): Returns a Bokeh glyph object. """ properties = mpl_to_bokeh(properties) - getattr(plot, self._plot_method)(**dict(properties, **mapping)) + return getattr(plot, self._plot_method)(**dict(properties, **mapping)) def _glyph_properties(self, plot, element, source, ranges): @@ -422,10 +422,11 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): self.handles['source'] = source properties = self._glyph_properties(plot, element, source, ranges) - self._init_glyph(plot, mapping, properties) - glyph = plot.renderers[-1].glyph - self.handles['glyph_renderer'] = plot.renderers[-1] + glyph = self._init_glyph(plot, mapping, properties) self.handles['glyph'] = glyph + renderer = plot.renderers[-1] + if isinstance(renderer, Renderer): + self.handles['glyph_renderer'] = plot.renderers[-1] # Update plot, source and glyph self._update_glyph(glyph, properties, mapping) From 0659263293d797d3364a4798e242ebe6421a04a1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 15:25:53 +0000 Subject: [PATCH 74/86] Switched to BoxAnnotation type for bokeh HLine and VLine --- holoviews/plotting/bokeh/__init__.py | 5 ++++ holoviews/plotting/bokeh/annotation.py | 34 +++++++++++++++----------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/holoviews/plotting/bokeh/__init__.py b/holoviews/plotting/bokeh/__init__.py index 485294b374..a3feba9bcc 100644 --- a/holoviews/plotting/bokeh/__init__.py +++ b/holoviews/plotting/bokeh/__init__.py @@ -130,3 +130,8 @@ options.Raster = Options('style', cmap='hot') options.QuadMesh = Options('style', cmap='hot') options.HeatMap = Options('style', cmap='RdYlBu_r', line_alpha=0) + +# Annotations +options.HLine = Options('style', line_color='black', line_width=3, line_alpha=1) +options.VLine = Options('style', line_color='black', line_width=3, line_alpha=1) + diff --git a/holoviews/plotting/bokeh/annotation.py b/holoviews/plotting/bokeh/annotation.py index 03e89b50ea..4bdf02c98e 100644 --- a/holoviews/plotting/bokeh/annotation.py +++ b/holoviews/plotting/bokeh/annotation.py @@ -1,4 +1,5 @@ import numpy as np +from bokeh.models import BoxAnnotation from ...element import HLine, VLine from .element import ElementPlot, text_properties, line_properties @@ -23,24 +24,29 @@ def get_extents(self, element, ranges=None): class LineAnnotationPlot(ElementPlot): style_opts = line_properties - _plot_method = 'segment' def get_data(self, element, ranges=None, empty=False): plot = self.handles['plot'] - if empty: - x0, y0, x1, y1 = [], [], [], [] - elif isinstance(element, HLine): - x0 = [plot.x_range.start] - y0 = [element.data] - x1 = [plot.x_range.end] - y1 = [element.data] + data, mapping = {}, {} + if isinstance(element, HLine): + mapping['bottom'] = element.data + mapping['top'] = element.data elif isinstance(element, VLine): - x0 = [element.data] - y0 = [plot.y_range.start] - x1 = [element.data] - y1 = [plot.y_range.end] - return (dict(x0=x0, y0=y0, x1=x1, y1=y1), - dict(x0='x0', y0='y0', x1='x1', y1='y1')) + mapping['left'] = element.data + mapping['right'] = element.data + return (data, mapping) + + + def _init_glyph(self, plot, mapping, properties): + """ + Returns a Bokeh glyph object. + """ + properties.pop('source') + properties.pop('legend') + box = BoxAnnotation(plot=plot, level='overlay', + **dict(mapping, **properties)) + plot.renderers.append(box) + return box def get_extents(self, element, ranges=None): From f8b7eef216c530e11dfad21cf91d046057478c7f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 15:27:44 +0000 Subject: [PATCH 75/86] Dimensioned.dimensions doesn't return constant dimensions by default --- holoviews/core/dimension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index 3ab15b0452..c092b01884 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -508,7 +508,7 @@ class to be associated with dimensions. The contents associated __abstract = True _sorted = False - _dim_groups = ['kdims', 'vdims', 'cdims', 'ddims'] + _dim_groups = ['kdims', 'vdims', 'ddims'] _dim_aliases = dict(key_dimensions='kdims', value_dimensions='vdims', constant_dimensions='cdims', deep_dimensions='ddims') @@ -528,7 +528,7 @@ def constant_dimensions(self): return self.cdims def deep_dimensions(self): return self.ddims def __init__(self, data, **params): - for group in self._dim_groups+list(self._dim_aliases.keys()): + for group in self._dim_groups+['cdims']+list(self._dim_aliases.keys()): if group in ['deep_dimensions', 'ddims']: continue if group in params: if group in self._dim_aliases: From 5a3df236e172892084bbc2d9708cfc514c45d5ba Mon Sep 17 00:00:00 2001 From: philippjfr Date: Wed, 16 Dec 2015 18:28:22 +0000 Subject: [PATCH 76/86] Readded cdims to _dim_groups --- holoviews/core/dimension.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index c092b01884..40aa7ac25a 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -508,7 +508,7 @@ class to be associated with dimensions. The contents associated __abstract = True _sorted = False - _dim_groups = ['kdims', 'vdims', 'ddims'] + _dim_groups = ['kdims', 'vdims', 'cdims', 'ddims'] _dim_aliases = dict(key_dimensions='kdims', value_dimensions='vdims', constant_dimensions='cdims', deep_dimensions='ddims') @@ -528,7 +528,7 @@ def constant_dimensions(self): return self.cdims def deep_dimensions(self): return self.ddims def __init__(self, data, **params): - for group in self._dim_groups+['cdims']+list(self._dim_aliases.keys()): + for group in self._dim_groups+list(self._dim_aliases.keys()): if group in ['deep_dimensions', 'ddims']: continue if group in params: if group in self._dim_aliases: @@ -587,7 +587,8 @@ def dimensions(self, selection='all', label=False): 'c': (lambda x: x.cdims, {})} aliases = {'key': 'k', 'value': 'v', 'constant': 'c'} if selection == 'all': - dims = [dim for group in self._dim_groups + groups = [d for d in self._dim_groups if d != 'cdims'] + dims = [dim for group in groups for dim in getattr(self, group)] elif isinstance(selection, list): dims = [dim for group in selection From 57799f3538e943d5ff43a042916abdfc41a5a0ca Mon Sep 17 00:00:00 2001 From: philippjfr Date: Wed, 16 Dec 2015 19:11:05 +0000 Subject: [PATCH 77/86] 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 a2c858cb26..8b42fd26d3 160000 --- a/doc/reference_data +++ b/doc/reference_data @@ -1 +1 @@ -Subproject commit a2c858cb26c1191cbaf7dea7ad13e18c1c3c87e2 +Subproject commit 8b42fd26d314540cfa94eea06b0725da6d7d6ac1 From 878603f5f73c42098d258890e6b4f0a146656db2 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 20:10:00 +0000 Subject: [PATCH 78/86] Improvements for bokeh PointPlot --- holoviews/plotting/bokeh/chart.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 13f8ec7d5e..d4ddf87f24 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -59,7 +59,7 @@ def get_data(self, element, ranges=None, empty=False): colors = element.dimension_values(self.color_index) crange = ranges.get(dims[self.color_index], None) data[map_key] = map_colors(colors, crange, cmap) - if self.size_index < len(dims): + if self.size_index < len(dims) and self.scaling_factor != 1: map_key = 'size_' + dims[self.size_index] mapping['size'] = map_key if empty: @@ -72,8 +72,8 @@ def get_data(self, element, ranges=None, empty=False): data[dims[0]] = [] if empty else element.dimension_values(0) data[dims[1]] = [] if empty else element.dimension_values(1) - if 'hover' in self.tools: - for d in dims[2:]: + if 'hover' in self.tools+self.default_tools: + for d in dims: data[d] = [] if empty else element.dimension_values(d) return data, mapping From 1b8c4439e09d6f942ca7160a268d9f832fd19bc0 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 16 Dec 2015 22:12:26 +0000 Subject: [PATCH 79/86] Fixed handling of glyphs and renderers in bokeh backend --- holoviews/plotting/bokeh/annotation.py | 2 +- holoviews/plotting/bokeh/chart.py | 10 +++++----- holoviews/plotting/bokeh/element.py | 10 +++++----- holoviews/plotting/bokeh/plot.py | 6 ++++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/holoviews/plotting/bokeh/annotation.py b/holoviews/plotting/bokeh/annotation.py index 4bdf02c98e..f27e86ebbf 100644 --- a/holoviews/plotting/bokeh/annotation.py +++ b/holoviews/plotting/bokeh/annotation.py @@ -46,7 +46,7 @@ def _init_glyph(self, plot, mapping, properties): box = BoxAnnotation(plot=plot, level='overlay', **dict(mapping, **properties)) plot.renderers.append(box) - return box + return None, box def get_extents(self, element, ranges=None): diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index d4ddf87f24..33b3aed531 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -91,12 +91,12 @@ def _init_glyph(self, plot, mapping, properties): color = mapping.pop('color', color) properties.pop('legend', None) unselected = Circle(**dict(properties, fill_color=unselect_color, **mapping)) - glyph = Circle(**dict(properties, fill_color=color, **mapping)) - plot.add_glyph(source, selected, selection_glyph=glyph, - nonselection_glyph=unselected) + selected = Circle(**dict(properties, fill_color=color, **mapping)) + renderer = plot.add_glyph(source, selected, selection_glyph=glyph, + nonselection_glyph=unselected) else: - glyph = getattr(plot, self._plot_method)(**dict(properties, **mapping)) - return glyph + renderer = getattr(plot, self._plot_method)(**dict(properties, **mapping)) + return renderer, renderer.glyph class CurvePlot(ElementPlot): diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 4c82bb2141..33caf6b76e 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -372,7 +372,8 @@ def _init_glyph(self, plot, mapping, properties): Returns a Bokeh glyph object. """ properties = mpl_to_bokeh(properties) - return getattr(plot, self._plot_method)(**dict(properties, **mapping)) + renderer = getattr(plot, self._plot_method)(**dict(properties, **mapping)) + return renderer, renderer.glyph def _glyph_properties(self, plot, element, source, ranges): @@ -422,11 +423,10 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): self.handles['source'] = source properties = self._glyph_properties(plot, element, source, ranges) - glyph = self._init_glyph(plot, mapping, properties) - self.handles['glyph'] = glyph - renderer = plot.renderers[-1] + renderer, glyph = self._init_glyph(plot, mapping, properties) + self.handles['glyph'] = glyph if isinstance(renderer, Renderer): - self.handles['glyph_renderer'] = plot.renderers[-1] + self.handles['glyph_renderer'] = renderer # Update plot, source and glyph self._update_glyph(glyph, properties, mapping) diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index f8101837f8..76333f0aa4 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -108,8 +108,10 @@ def sync_sources(self): source_data.update(plot.handles['source'].data) new_source = ColumnDataSource(source_data) for _, plot in group: - renderer = plot.handles['glyph_renderer'] - if 'data_source' in renderer.properties(): + renderer = plot.handles.get('glyph_renderer') + if renderer is None: + continue + elif 'data_source' in renderer.properties(): renderer.update(data_source=new_source) else: renderer.update(source=new_source) From 5d111b38aaa811162b59e02d25f4e0c7c72c8c6b Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 17 Dec 2015 20:55:08 +0000 Subject: [PATCH 80/86] Fixed bug displaying Overlays inside sampled DynamicMaps --- holoviews/plotting/bokeh/element.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 33caf6b76e..86dc70dcc6 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -689,13 +689,14 @@ def update_frame(self, key, ranges=None, element=None): """ if element is None: element = self._get_frame(key) + ranges = self.compute_ranges(element, key, ranges) else: self.current_frame = element self.current_key = key ranges = self.compute_ranges(self.hmap, key, ranges) for k, subplot in self.subplots.items(): - el = element.get(k, None) if self.dynamic and element is not None else None + el = element.get(k, None) if isinstance(element, CompositeOverlay) else None subplot.update_frame(key, ranges, element=el) if not self.overlaid and not self.tabs: self._update_ranges(element, ranges) From 369e1bfc272f8c1ba8d1f072860cf74bde9323e2 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 17 Dec 2015 21:02:47 +0000 Subject: [PATCH 81/86] Fixed missing import in plotting/bokeh/element.py --- holoviews/plotting/bokeh/element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 86dc70dcc6..359461877c 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -14,7 +14,7 @@ mpl = None import param -from ...core import Store, HoloMap, Overlay +from ...core import Store, HoloMap, Overlay, CompositeOverlay from ...core import util from ...element import RGB from ..plot import GenericElementPlot, GenericOverlayPlot From d40587ab2c166b116ad2913ae50dcac98d73c042 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Fri, 18 Dec 2015 01:28:48 +0000 Subject: [PATCH 82/86] Only abbreviating tracebacks for errors in the backends --- holoviews/ipython/display_hooks.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/holoviews/ipython/display_hooks.py b/holoviews/ipython/display_hooks.py index bf4ae1e56b..cadd07147b 100644 --- a/holoviews/ipython/display_hooks.py +++ b/holoviews/ipython/display_hooks.py @@ -2,7 +2,7 @@ Definition and registration of display hooks for the IPython Notebook. """ from functools import wraps -import sys, traceback +import sys, traceback, inspect import IPython import param @@ -105,7 +105,12 @@ def wrapped(element): return html except Exception as e: StoreOptions.state(element, state=optstate) - if ABBREVIATE_TRACEBACKS: + frame = inspect.trace()[-1] + mod = inspect.getmodule(frame[0]) + module = (mod.__name__ if mod else frame[1]).split('.')[0] + backends = Store.renderers.keys() + abbreviate = module in backends + if ABBREVIATE_TRACEBACKS and abbreviate: info = dict(name=type(e).__name__, message=str(e).replace('\n','
')) return "{name}
{message}".format(**info) From 2ee8edd228ff66b5d34f6459c6b37ddb73dbc819 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Fri, 18 Dec 2015 01:35:24 +0000 Subject: [PATCH 83/86] Introduced the BackendError exception --- holoviews/core/options.py | 11 +++++++++++ holoviews/ipython/display_hooks.py | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/holoviews/core/options.py b/holoviews/core/options.py index f5185f6d11..341b4f2ace 100644 --- a/holoviews/core/options.py +++ b/holoviews/core/options.py @@ -42,6 +42,17 @@ from .util import sanitize_identifier, group_sanitizer,label_sanitizer from .pprint import InfoPrinter + +class BackendError(Exception): + """ + Custom exception used to generate abbreviated tracebacks when there + is an error in the backend. Use to suppress long tracebacks that can + easily be caused by the users (e.g a typo in the style options) + where the user would be better served by a short error message + rather than a long traceback. + """ + pass + class OptionError(Exception): """ Custom exception raised when there is an attempt to apply invalid diff --git a/holoviews/ipython/display_hooks.py b/holoviews/ipython/display_hooks.py index cadd07147b..8aa713f84d 100644 --- a/holoviews/ipython/display_hooks.py +++ b/holoviews/ipython/display_hooks.py @@ -7,7 +7,7 @@ import IPython import param -from ..core.options import Store, StoreOptions +from ..core.options import Store, StoreOptions, BackendError from ..core import (LabelledData, Element, ViewableElement, UniformNdMapping, HoloMap, AdjointLayout, NdLayout, GridSpace, Layout, CompositeOverlay, DynamicMap) @@ -109,7 +109,7 @@ def wrapped(element): mod = inspect.getmodule(frame[0]) module = (mod.__name__ if mod else frame[1]).split('.')[0] backends = Store.renderers.keys() - abbreviate = module in backends + abbreviate = isinstance(e, BackendError) or module in backends if ABBREVIATE_TRACEBACKS and abbreviate: info = dict(name=type(e).__name__, message=str(e).replace('\n','
')) From 48845db25b20f3c5580721e05fb1d7e98312800a Mon Sep 17 00:00:00 2001 From: jlstevens Date: Fri, 18 Dec 2015 01:46:18 +0000 Subject: [PATCH 84/86] Added the show_traceback function to the ipython module --- holoviews/ipython/__init__.py | 10 +++++++++- holoviews/ipython/display_hooks.py | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py index 328bec402e..08ff93da3a 100644 --- a/holoviews/ipython/__init__.py +++ b/holoviews/ipython/__init__.py @@ -14,7 +14,7 @@ from ..plotting.widgets import NdWidget from .archive import notebook_archive from .magics import load_magics -from .display_hooks import display # pyflakes:ignore (API import) +from .display_hooks import display # pyflakes:ignore (API import) from .display_hooks import set_display_hooks, OutputMagic from .parser import Parser from .widgets import RunProgress @@ -25,6 +25,14 @@ holoviews.archive = notebook_archive +def show_traceback(): + """ + Display the full traceback after an abbreviated traceback has occured. + """ + from .display_hooks import FULL_TRACEBACK + print FULL_TRACEBACK + + class IPTestCase(ComparisonTestCase): """ This class extends ComparisonTestCase to handle IPython specific diff --git a/holoviews/ipython/display_hooks.py b/holoviews/ipython/display_hooks.py index 8aa713f84d..13cde3c479 100644 --- a/holoviews/ipython/display_hooks.py +++ b/holoviews/ipython/display_hooks.py @@ -2,9 +2,10 @@ Definition and registration of display hooks for the IPython Notebook. """ from functools import wraps -import sys, traceback, inspect +import sys, traceback, inspect, io import IPython +from IPython.core.ultratb import AutoFormattedTB import param from ..core.options import Store, StoreOptions, BackendError @@ -16,7 +17,8 @@ from .archive import notebook_archive # To assist with debugging of display hooks -ABBREVIATE_TRACEBACKS=True +FULL_TRACEBACK = None +ABBREVIATE_TRACEBACKS = True #==================# # Helper functions # @@ -92,6 +94,7 @@ def last_frame(obj): def display_hook(fn): @wraps(fn) def wrapped(element): + global FULL_TRACEBACK optstate = StoreOptions.state(element) try: html = fn(element, @@ -111,9 +114,15 @@ def wrapped(element): backends = Store.renderers.keys() abbreviate = isinstance(e, BackendError) or module in backends if ABBREVIATE_TRACEBACKS and abbreviate: + AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux') + buff = io.StringIO() + AutoTB(out=buff) + buff.seek(0) + FULL_TRACEBACK = buff.read() info = dict(name=type(e).__name__, message=str(e).replace('\n','
')) - return "{name}
{message}".format(**info) + msg =' [Call ipython.show_traceback() for details]' + return "{name}{msg}
{message}".format(msg=msg, **info) else: traceback.print_exc() return wrapped From 9cd75aa38d78896eafd4aaf8f8c3c79b79214067 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Fri, 18 Dec 2015 02:15:07 +0000 Subject: [PATCH 85/86] Fixed print statement for Python 3 compatibility --- holoviews/ipython/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/ipython/__init__.py b/holoviews/ipython/__init__.py index 08ff93da3a..d9c58225bc 100644 --- a/holoviews/ipython/__init__.py +++ b/holoviews/ipython/__init__.py @@ -30,7 +30,7 @@ def show_traceback(): Display the full traceback after an abbreviated traceback has occured. """ from .display_hooks import FULL_TRACEBACK - print FULL_TRACEBACK + print(FULL_TRACEBACK) class IPTestCase(ComparisonTestCase): From 22e8b3890d8c2f62afe558a7165b56a297d48534 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 19 Dec 2015 01:56:04 +0000 Subject: [PATCH 86/86] Fixes for updating Spikes Element --- holoviews/plotting/bokeh/chart.py | 1 + holoviews/plotting/mpl/chart.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 33b3aed531..eed5325b09 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -255,6 +255,7 @@ def get_data(self, element, ranges=None, empty=False): pos = self.position if empty: xs, ys, keys = [], [], [] + mapping = dict(xs=dims[0], ys=dims[1] if len(dims) > 1 else 'heights') elif len(dims) > 1: xs, ys = zip(*(((x, x), (pos, pos+y)) for x, y in element.array())) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 62b94e9157..49a3e32462 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -1040,13 +1040,13 @@ def get_data(self, element, ranges): def update_handles(self, axis, element, key, ranges=None): artist = self.handles['artist'] - data, array, clim = self.get_data(element) + data, array, clim = self.get_data(element, ranges) artist.set_paths(data) visible = self.style[self.cyclic_index].get('visible', True) artist.set_visible(visible) if array is not None: - paths.set_clim(ranges[val_dim]) - paths.set_array(cs) + artist.set_clim(clim) + artist.set_array(array) class SideSpikesPlot(SpikesPlot):