From f8a0767e096ef75b94ee5c10329eaa33b53a9467 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 23 Sep 2017 19:57:05 +0100 Subject: [PATCH 1/6] Implement axis inversions for matplotlib annotations --- holoviews/plotting/mpl/annotation.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/mpl/annotation.py b/holoviews/plotting/mpl/annotation.py index b169ca59f6..92e41256b2 100644 --- a/holoviews/plotting/mpl/annotation.py +++ b/holoviews/plotting/mpl/annotation.py @@ -45,7 +45,10 @@ class VLinePlot(AnnotationPlot): style_opts = ['alpha', 'color', 'linewidth', 'linestyle', 'visible'] def draw_annotation(self, axis, position, opts): - return [axis.axvline(position, **opts)] + if self.invert_axes: + return [axis.axhline(position, **opts)] + else: + return [axis.axvline(position, **opts)] @@ -56,7 +59,10 @@ class HLinePlot(AnnotationPlot): def draw_annotation(self, axis, position, opts): "Draw a horizontal line on the axis" - return [axis.axhline(position, **opts)] + if self.invert_axes: + return [axis.axvline(position, **opts)] + else: + return [axis.axhline(position, **opts)] class TextPlot(AnnotationPlot): @@ -67,6 +73,7 @@ class TextPlot(AnnotationPlot): def draw_annotation(self, axis, data, opts): (x,y, text, fontsize, horizontalalignment, verticalalignment, rotation) = data + if self.invert_axes: x, y = y, x opts['fontsize'] = fontsize return [axis.text(x,y, text, horizontalalignment = horizontalalignment, @@ -85,6 +92,7 @@ class ArrowPlot(AnnotationPlot): def draw_annotation(self, axis, data, opts): x, y, text, direction, points, arrowstyle = data + if self.invert_axes: x, y = y, x direction = direction.lower() arrowprops = dict({'arrowstyle':arrowstyle}, **{k: opts[k] for k in self._arrow_style_opts if k in opts}) From 245ae07003bcf24252fd31dd0648899010bb6941 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 23 Sep 2017 20:16:24 +0100 Subject: [PATCH 2/6] Ensure VectorField y-values are inverted --- holoviews/plotting/mpl/chart.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index d24676b818..46c547434f 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -621,7 +621,8 @@ def get_data(self, element, ranges, style): xs = element.dimension_values(xidx) if len(element.data) else [] ys = element.dimension_values(yidx) if len(element.data) else [] radians = element.dimension_values(2) if len(element.data) else [] - angles = list(np.rad2deg(radians)) + if self.invert_axes: radians = radians+np.pi/2. + angles = list(np.rad2deg(radians)) if self.rescale_lengths: input_scale = input_scale / self._min_dist From 6050bdea13c3b6669b3e30534a7edde2e4ed5ccb Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 23 Sep 2017 20:16:45 +0100 Subject: [PATCH 3/6] Ensure raster types invert_axes works --- holoviews/plotting/mpl/raster.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/mpl/raster.py b/holoviews/plotting/mpl/raster.py index fa4997a3f4..81ec7360d3 100644 --- a/holoviews/plotting/mpl/raster.py +++ b/holoviews/plotting/mpl/raster.py @@ -65,6 +65,10 @@ def get_data(self, element, ranges, style): l, b, r, t = element.bounds.lbrt() data = get_raster_array(element) + if self.invert_axes: + data = data[::-1] + data = data.transpose([1, 0, 2]) if isinstance(element, RGB) else data.T + l, b, r, t = b, l, t, r vdim = element.vdims[0] self._norm_kwargs(element, ranges, style, vdim) style['extent'] = [l, r, b, t] @@ -151,6 +155,7 @@ def get_data(self, element, ranges, style): data = np.flipud(element.gridded.dimension_values(2, flat=False)) data = np.ma.array(data, mask=np.logical_not(np.isfinite(data))) + if self.invert_axes: data = data.T shape = data.shape style['aspect'] = shape[0]/shape[1] style['extent'] = (0, shape[1], 0, shape[0]) @@ -187,9 +192,12 @@ class ImagePlot(RasterPlot): def get_data(self, element, ranges, style): data = np.flipud(element.dimension_values(2, flat=False)) data = np.ma.array(data, mask=np.logical_not(np.isfinite(data))) + l, b, r, t = element.bounds.lbrt() + if self.invert_axes: + data = data[::-1].T + l, b, r, t = b, l, t, r vdim = element.vdims[0] self._norm_kwargs(element, ranges, style, vdim) - l, b, r, t = element.bounds.lbrt() style['extent'] = [l, r, b, t] return (data,), style, {} @@ -211,7 +219,11 @@ class QuadMeshPlot(ColorbarPlot): def get_data(self, element, ranges, style): data = np.ma.array(element.data[2], mask=np.logical_not(np.isfinite(element.data[2]))) - cmesh_data = list(element.data[:2]) + [data] + coords = list(element.data[:2]) + if self.invert_axes: + coords = coords[::-1] + data = data.T + cmesh_data = coords + [data] style['locs'] = np.concatenate(element.data[:2]) vdim = element.vdims[0] self._norm_kwargs(element, ranges, style, vdim) From e77ef1ce93c9a23ca4ad87dccfd5e97a87c978bf Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 24 Sep 2017 01:27:37 +0100 Subject: [PATCH 4/6] Made invert_axes consistent in bokeh --- holoviews/plotting/bokeh/annotation.py | 7 +++-- holoviews/plotting/bokeh/chart.py | 17 ++++++---- holoviews/plotting/bokeh/raster.py | 43 +++++++++++++++++++------- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/holoviews/plotting/bokeh/annotation.py b/holoviews/plotting/bokeh/annotation.py index f84bc5ad6c..1ca7b36220 100644 --- a/holoviews/plotting/bokeh/annotation.py +++ b/holoviews/plotting/bokeh/annotation.py @@ -42,7 +42,6 @@ def get_data(self, element, ranges=None, empty=False): data['text'] = [element.text] return (data, mapping) - def get_batched_data(self, element, ranges=None, empty=False): data = defaultdict(list) for key, el in element.data.items(): @@ -51,7 +50,6 @@ def get_batched_data(self, element, ranges=None, empty=False): data[k].extend(eld) return data, elmapping - def get_extents(self, element, ranges=None): return None, None, None, None @@ -100,7 +98,10 @@ class SplinePlot(ElementPlot): _plot_methods = dict(single='bezier') def get_data(self, element, ranges=None, empty=False): - data_attrs = ['x0', 'y0', 'cx0', 'cy0', 'cx1', 'cy1', 'x1', 'y1',] + if self.invert_axes: + data_attrs = ['y0', 'x0', 'cy0', 'cx0', 'cy1', 'cx1', 'y1', 'x1'] + else: + data_attrs = ['x0', 'y0', 'cx0', 'cy0', 'cx1', 'cy1', 'x1', 'y1'] verts = np.array(element.data[0]) inds = np.where(np.array(element.data[1])==1)[0] data = {da: [] for da in data_attrs} diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index d85129d47c..5a87056e20 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -183,8 +183,12 @@ def get_data(self, element, ranges=None, empty=False): input_scale = style.pop('scale', 1.0) # Get x, y, angle, magnitude and color data - xidx, yidx = (1, 0) if self.invert_axes else (0, 1) rads = element.dimension_values(2) + if self.invert_axes: + xidx, yidx = (1, 0) + rads = rads+1.5*np.pi + else: + xidx, yidx = (0, 1) lens = self._get_lengths(element, ranges)/input_scale cdim = element.get_dimension(self.color_index) cdata, cmapping = self._get_color_data(element, ranges, style, @@ -516,6 +520,9 @@ def get_extents(self, element, ranges): # from position and length plot options for el in self.current_frame.values(): opts = self.lookup_options(el, 'plot').options + frame = self.current_frame or self.hmap.last + for el in frame.values(): + opts = self.lookup_options(element, 'plot').options pos = opts.get('position', self.position) length = opts.get('spike_length', self.spike_length) bs.append(pos) @@ -950,7 +957,7 @@ def _get_factors(self, element): Get factors for categorical axes. """ if not element.kdims: - return [element.label], [] + xfactors, yfactors = [element.label], [] else: if bokeh_version < '0.12.7': factors = [', '.join([d.pprint_value(v).replace(':', ';') @@ -960,10 +967,8 @@ def _get_factors(self, element): factors = [tuple(d.pprint_value(v) for d, v in zip(element.kdims, key)) for key in element.groupby(element.kdims).data.keys()] factors = [f[0] if len(f) == 1 else f for f in factors] - if self.invert_axes: - return [], factors - else: - return factors, [] + xfactors, yfactors = factors, [] + return (yfactors, xfactors) if self.invert_axes else (xfactors, yfactors) def get_data(self, element, ranges=None, empty=False): if element.kdims: diff --git a/holoviews/plotting/bokeh/raster.py b/holoviews/plotting/bokeh/raster.py index ab6f631846..1ca387139e 100644 --- a/holoviews/plotting/bokeh/raster.py +++ b/holoviews/plotting/bokeh/raster.py @@ -43,6 +43,12 @@ def get_data(self, element, ranges=None, empty=False): img = img[::-1] dh, dw = t-b, r-l + if self.invert_axes: + dh, dw, l, b = dw, dh, b, l + if self.invert_yaxis: l = t + if self.invert_yaxis: b = r + img = np.rot90(img) + mapping = dict(image='image', x='x', y='y', dw='dw', dh='dh') if empty: data = dict(image=[], x=[], y=[], dw=[], dh=[]) @@ -93,6 +99,12 @@ def get_data(self, element, ranges=None, empty=False): img = img[::-1] dh, dw = t-b, r-l + if self.invert_axes: + dh, dw, l, b = dw, dh, b, l + if self.invert_yaxis: l = t + if self.invert_yaxis: b = r + img = np.rot90(img) + mapping = dict(image='image', x='x', y='y', dw='dw', dh='dh') if empty: data = dict(image=[], x=[], y=[], dw=[], dh=[]) @@ -138,16 +150,18 @@ def get_data(self, element, ranges=None, empty=False): aggregate = element.gridded style = self.style[self.cyclic_index] cmapper = self._get_colormapper(element.vdims[0], element, ranges, style) - if empty: - data = {x: [], y: [], z: []} - else: - xdim, ydim = aggregate.dimensions()[:2] - xvals, yvals, zvals = (aggregate.dimension_values(i) for i in range(3)) - if xvals.dtype.kind not in 'SU': - xvals = [xdim.pprint_value(xv) for xv in xvals] - if yvals.dtype.kind not in 'SU': - yvals = [ydim.pprint_value(yv) for yv in yvals] - data = {x: xvals, y: yvals, 'zvalues': zvals} + + xdim, ydim = aggregate.dimensions()[:2] + xvals, yvals, zvals = (aggregate.dimension_values(i) for i in range(3)) + if xvals.dtype.kind not in 'SU': + xvals = [xdim.pprint_value(xv) for xv in xvals] + if yvals.dtype.kind not in 'SU': + yvals = [ydim.pprint_value(yv) for yv in yvals] + + if self.invert_axes: + x, y = y, x + xvals, yvals = yvals, xvals + data = {x: xvals, y: yvals, 'zvalues': zvals} if any(isinstance(t, HoverTool) for t in self.state.tools): for vdim in element.vdims: @@ -168,18 +182,23 @@ class QuadMeshPlot(ColorbarPlot): def get_data(self, element, ranges=None, empty=False): x, y, z = element.dimensions(label=True) + if self.invert_axes: x, y = y, x style = self.style[self.cyclic_index] cmapper = self._get_colormapper(element.vdims[0], element, ranges, style) if empty: - data = {x: [], y: [], z: [], 'height': [], 'width': []} + xs, ys, zvals, ws, hs = []*5 else: if len(set(v.shape for v in element.data)) == 1: raise SkipRendering("Bokeh QuadMeshPlot only supports rectangular meshes") - zvals = element.data[2].T.flatten() xvals = element.dimension_values(0, False) yvals = element.dimension_values(1, False) widths = np.diff(element.data[0]) heights = np.diff(element.data[1]) + if self.invert_axes: + zvals = element.data[2].flatten() + xvals, yvals, widths, heights = yvals, xvals, heights, widths + else: + zvals = element.data[2].T.flatten() xs, ys = cartesian_product([xvals, yvals], copy=True) ws, hs = cartesian_product([widths, heights], copy=True) data = {x: xs, y: ys, z: zvals, 'widths': ws, 'heights': hs} From 1040fe85debb6e3c0eb291303207eea02f0d39eb Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 24 Sep 2017 01:27:58 +0100 Subject: [PATCH 5/6] Fixed VectorField invert_axes --- 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 46c547434f..aad85b89de 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -621,7 +621,7 @@ def get_data(self, element, ranges, style): xs = element.dimension_values(xidx) if len(element.data) else [] ys = element.dimension_values(yidx) if len(element.data) else [] radians = element.dimension_values(2) if len(element.data) else [] - if self.invert_axes: radians = radians+np.pi/2. + if self.invert_axes: radians = radians+1.5*np.pi angles = list(np.rad2deg(radians)) if self.rescale_lengths: input_scale = input_scale / self._min_dist From 9ef9c641cda35a5da3e8d4db11873fd1c0eb9a71 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sun, 24 Sep 2017 13:49:21 +0100 Subject: [PATCH 6/6] Reverted change to bokeh SpikesPlot --- holoviews/plotting/bokeh/chart.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py index 5a87056e20..d089df0d3c 100644 --- a/holoviews/plotting/bokeh/chart.py +++ b/holoviews/plotting/bokeh/chart.py @@ -520,9 +520,6 @@ def get_extents(self, element, ranges): # from position and length plot options for el in self.current_frame.values(): opts = self.lookup_options(el, 'plot').options - frame = self.current_frame or self.hmap.last - for el in frame.values(): - opts = self.lookup_options(element, 'plot').options pos = opts.get('position', self.position) length = opts.get('spike_length', self.spike_length) bs.append(pos)