From 6fba6acc2b370084ada9905ae21e13c7b072225e Mon Sep 17 00:00:00 2001 From: mzwiessele Date: Wed, 7 Oct 2015 11:37:05 +0100 Subject: [PATCH] [plotly] starting --- GPy/plotting/plotly_dep/__init__.py | 0 GPy/plotting/plotly_dep/defaults.py | 75 +++++ GPy/plotting/plotly_dep/plot_definitions.py | 318 ++++++++++++++++++++ 3 files changed, 393 insertions(+) create mode 100644 GPy/plotting/plotly_dep/__init__.py create mode 100644 GPy/plotting/plotly_dep/defaults.py create mode 100644 GPy/plotting/plotly_dep/plot_definitions.py diff --git a/GPy/plotting/plotly_dep/__init__.py b/GPy/plotting/plotly_dep/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/GPy/plotting/plotly_dep/defaults.py b/GPy/plotting/plotly_dep/defaults.py new file mode 100644 index 00000000..f67b6e14 --- /dev/null +++ b/GPy/plotting/plotly_dep/defaults.py @@ -0,0 +1,75 @@ +#=============================================================================== +# Copyright (c) 2015, Max Zwiessele +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of GPy nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#=============================================================================== + +from matplotlib import cm +from .. import Tango + +''' +This file is for defaults for the gpy plot, specific to the plotting library. + +Create a kwargs dictionary with the right name for the plotting function +you are implementing. If you do not provide defaults, the default behaviour of +the plotting library will be used. + +In the code, always ise plotting.gpy_plots.defaults to get the defaults, as +it gives back an empty default, when defaults are not defined. +''' + +# Data plots: +data_1d = dict(lw=1.5, marker='x', edgecolor='k') +data_2d = dict(s=35, edgecolors='none', linewidth=0., cmap=cm.get_cmap('hot'), alpha=.5) +inducing_1d = dict(lw=0, s=500, facecolors=Tango.colorsHex['darkRed']) +inducing_2d = dict(s=14, edgecolors='k', linewidth=.4, facecolors='white', alpha=.5) +inducing_3d = dict(lw=.3, s=500, facecolors='white', edgecolors='k') +xerrorbar = dict(color='k', fmt='none', elinewidth=.5, alpha=.5) +yerrorbar = dict(color=Tango.colorsHex['darkRed'], fmt='none', elinewidth=.5, alpha=.5) + +# GP plots: +meanplot_1d = dict(color=Tango.colorsHex['mediumBlue'], linewidth=2) +meanplot_2d = dict(cmap='hot', linewidth=.5) +meanplot_3d = dict(linewidth=0, antialiased=True, cstride=1, rstride=1, cmap='hot', alpha=.3) +samples_1d = dict(color=Tango.colorsHex['mediumBlue'], linewidth=.3) +samples_3d = dict(cmap='hot', alpha=.1, antialiased=True, cstride=1, rstride=1, linewidth=0) +confidence_interval = dict(edgecolor=Tango.colorsHex['darkBlue'], linewidth=.5, color=Tango.colorsHex['lightBlue'],alpha=.2) +density = dict(alpha=.5, color=Tango.colorsHex['lightBlue']) + +# GPLVM plots: +data_y_1d = dict(linewidth=0, cmap='RdBu', s=40) +data_y_1d_plot = dict(color='k', linewidth=1.5) + +# Kernel plots: +ard = dict(edgecolor='k', linewidth=1.2) + +# Input plots: +latent = dict(aspect='auto', cmap='Greys', interpolation='bicubic') +gradient = dict(aspect='auto', cmap='RdBu', interpolation='nearest', alpha=.7) +magnification = dict(aspect='auto', cmap='Greys', interpolation='bicubic') +latent_scatter = dict(s=40, linewidth=.2, edgecolor='k', alpha=.9) +annotation = dict(fontdict=dict(family='sans-serif', weight='light', fontsize=9), zorder=.3, alpha=.7) \ No newline at end of file diff --git a/GPy/plotting/plotly_dep/plot_definitions.py b/GPy/plotting/plotly_dep/plot_definitions.py new file mode 100644 index 00000000..0de1c7c4 --- /dev/null +++ b/GPy/plotting/plotly_dep/plot_definitions.py @@ -0,0 +1,318 @@ +#=============================================================================== +# Copyright (c) 2015, Max Zwiessele +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of GPy.plotting.matplot_dep.plot_definitions nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#=============================================================================== +import numpy as np +from ..abstract_plotting_library import AbstractPlottingLibrary +from .. import Tango +from . import defaults +import itertools +from plotly import tools +from plotly import plotly as py +from plotly.graph_objs import Scatter, Line, Data + +class PlotlyPlots(AbstractPlottingLibrary): + def __init__(self): + super(PlotlyPlots, self).__init__() + self._defaults = defaults.__dict__ + self.current_states = dict() + + def figure(self, rows=1, cols=1, **kwargs): + if 'filename' not in kwargs: + print('PlotlyWarning: filename was not given, this may clutter your plotly workspace') + filename = None + else: filename = kwargs.pop('filename') + figure = tools.make_subplots(rows, cols, **kwargs) + self.current_states[hex(id(figure))] = dict(filename=filename) + index = 1 + for i in rows: + for j in cols: + self.current_states[hex(id(figure))][(i,j)] = ('xaxis{}'.format(index), 'yaxis{}'.format(index)) + index += 1 + return figure + + def new_canvas(self, figure=None, row=1, col=1, projection='2d', xlabel=None, ylabel=None, zlabel=None, title=None, xlim=None, ylim=None, zlim=None, **kwargs): + if figure is None: + figure = self.figure(**kwargs) + return (figure, self.current_states[hex(id(figure))][(row,col)]), kwargs + + def show_canvas(self, canvas, traces, legend=False, **kwargs): + fig = canvas[0] + axis = fig['layout'][canvas[1]] + axis.update(title=title, label) + figure.add_traces(traces, row, col, **kwargs) + + # If shared_axes is False (default) use list_of_domains + # This is used for insets and irregular layouts + if not shared_xaxes and not shared_yaxes: + x_dom = list_of_domains[::2] + y_dom = list_of_domains[1::2] + subtitle_pos_x = [] + subtitle_pos_y = [] + for x_domains in x_dom: + subtitle_pos_x.append(sum(x_domains) / 2) + for y_domains in y_dom: + subtitle_pos_y.append(y_domains[1]) + # If shared_axes is True the domin of each subplot is not returned so the + # title position must be calculated for each subplot + else: + subtitle_pos_x = [None] * cols + subtitle_pos_y = [None] * rows + delt_x = (x_e - x_s) + for index in range(cols): + subtitle_pos_x[index] = ((delt_x / 2) + + ((delt_x + horizontal_spacing) * index)) + subtitle_pos_x *= rows + for index in range(rows): + subtitle_pos_y[index] = (1 - ((y_e + vertical_spacing) * index)) + subtitle_pos_y *= cols + subtitle_pos_y = sorted(subtitle_pos_y, reverse=True) + + plot_titles = [] + for index in range(len(subplot_titles)): + if not subplot_titles[index]: + pass + else: + plot_titles.append({'y': subtitle_pos_y[index], + 'xref': 'paper', + 'x': subtitle_pos_x[index], + 'yref': 'paper', + 'text': subplot_titles[index], + 'showarrow': False, + 'font': graph_objs.Font(size=16), + 'xanchor': 'center', + 'yanchor': 'bottom' + }) + + layout['annotations'] = plot_titles + try: + url = py.iplot(figure, self.current_filename) + except: + url = py.plot(figure, self.current_filename) + return url + + def scatter(self, ax, X, Y, Z=None, color=Tango.colorsHex['mediumBlue'], label=None, marker='o', **kwargs): + + + def plot(self, ax, X, Y, Z=None, color=None, label=None, **kwargs): + if Z is not None: + return ax.plot(X, Y, color=color, zs=Z, label=label, **kwargs) + return ax.plot(X, Y, color=color, label=label, **kwargs) + + def plot_axis_lines(self, ax, X, color=Tango.colorsHex['mediumBlue'], label=None, **kwargs): + from matplotlib import transforms + from matplotlib.path import Path + if 'marker' not in kwargs: + kwargs['marker'] = Path([[-.2,0.], [-.2,.5], [0.,1.], [.2,.5], [.2,0.], [-.2,0.]], + [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]) + if 'transform' not in kwargs: + if X.shape[1] == 1: + kwargs['transform'] = transforms.blended_transform_factory(ax.transData, ax.transAxes) + if X.shape[1] == 2: + return ax.scatter(X[:,0], X[:,1], ax.get_zlim()[0], c=color, label=label, **kwargs) + return ax.scatter(X, np.zeros_like(X), c=color, label=label, **kwargs) + + def barplot(self, ax, x, height, width=0.8, bottom=0, color=Tango.colorsHex['mediumBlue'], label=None, **kwargs): + if 'align' not in kwargs: + kwargs['align'] = 'center' + return ax.bar(left=x, height=height, width=width, + bottom=bottom, label=label, color=color, + **kwargs) + + def xerrorbar(self, ax, X, Y, error, Z=None, color=Tango.colorsHex['mediumBlue'], label=None, **kwargs): + if not('linestyle' in kwargs or 'ls' in kwargs): + kwargs['ls'] = 'none' + if Z is not None: + return ax.errorbar(X, Y, Z, xerr=error, ecolor=color, label=label, **kwargs) + return ax.errorbar(X, Y, xerr=error, ecolor=color, label=label, **kwargs) + + def yerrorbar(self, ax, X, Y, error, Z=None, color=Tango.colorsHex['mediumBlue'], label=None, **kwargs): + if not('linestyle' in kwargs or 'ls' in kwargs): + kwargs['ls'] = 'none' + if Z is not None: + return ax.errorbar(X, Y, Z, yerr=error, ecolor=color, label=label, **kwargs) + return ax.errorbar(X, Y, yerr=error, ecolor=color, label=label, **kwargs) + + def imshow(self, ax, X, extent=None, label=None, vmin=None, vmax=None, **imshow_kwargs): + if 'origin' not in imshow_kwargs: + imshow_kwargs['origin'] = 'lower' + #xmin, xmax, ymin, ymax = extent + #xoffset, yoffset = (xmax - xmin) / (2. * X.shape[0]), (ymax - ymin) / (2. * X.shape[1]) + #xmin, xmax, ymin, ymax = extent = xmin-xoffset, xmax+xoffset, ymin-yoffset, ymax+yoffset + return ax.imshow(X, label=label, extent=extent, vmin=vmin, vmax=vmax, **imshow_kwargs) + + def imshow_interact(self, ax, plot_function, extent=None, label=None, resolution=None, vmin=None, vmax=None, **imshow_kwargs): + if 'origin' not in imshow_kwargs: + imshow_kwargs['origin'] = 'lower' + return ImshowController(ax, plot_function, extent, resolution=resolution, vmin=vmin, vmax=vmax, **imshow_kwargs) + + def annotation_heatmap(self, ax, X, annotation, extent=None, label=None, imshow_kwargs=None, **annotation_kwargs): + imshow_kwargs = imshow_kwargs or {} + if 'origin' not in imshow_kwargs: + imshow_kwargs['origin'] = 'lower' + if ('ha' not in annotation_kwargs) and ('horizontalalignment' not in annotation_kwargs): + annotation_kwargs['ha'] = 'center' + if ('va' not in annotation_kwargs) and ('verticalalignment' not in annotation_kwargs): + annotation_kwargs['va'] = 'center' + imshow = self.imshow(ax, X, extent, label, **imshow_kwargs) + if extent is None: + extent = (0, X.shape[0], 0, X.shape[1]) + xmin, xmax, ymin, ymax = extent + xoffset, yoffset = (xmax - xmin) / (2. * X.shape[0]), (ymax - ymin) / (2. * X.shape[1]) + xmin, xmax, ymin, ymax = extent = xmin+xoffset, xmax-xoffset, ymin+yoffset, ymax-yoffset + xlin = np.linspace(xmin, xmax, X.shape[0], endpoint=False) + ylin = np.linspace(ymin, ymax, X.shape[1], endpoint=False) + annotations = [] + for [i, x], [j, y] in itertools.product(enumerate(xlin), enumerate(ylin)): + annotations.append(ax.text(x, y, "{}".format(annotation[j, i]), **annotation_kwargs)) + return imshow, annotations + + def annotation_heatmap_interact(self, ax, plot_function, extent, label=None, resolution=15, imshow_kwargs=None, **annotation_kwargs): + if 'origin' not in imshow_kwargs: + imshow_kwargs['origin'] = 'lower' + return ImAnnotateController(ax, plot_function, extent, resolution=resolution, imshow_kwargs=imshow_kwargs or {}, **annotation_kwargs) + + def contour(self, ax, X, Y, C, levels=20, label=None, **kwargs): + return ax.contour(X, Y, C, levels=np.linspace(C.min(), C.max(), levels), label=label, **kwargs) + + def surface(self, ax, X, Y, Z, color=None, label=None, **kwargs): + return ax.plot_surface(X, Y, Z, label=label, **kwargs) + + def fill_between(self, ax, X, lower, upper, color=Tango.colorsHex['mediumBlue'], label=None, **kwargs): + return ax.fill_between(X, lower, upper, facecolor=color, label=label, **kwargs) + + def fill_gradient(self, canvas, X, percentiles, color=Tango.colorsHex['mediumBlue'], label=None, **kwargs): + ax = canvas + plots = [] + + if 'edgecolors' not in kwargs: + kwargs['edgecolors'] = 'none' + + if 'facecolors' in kwargs: + color = kwargs.pop('facecolors') + + if 'array' in kwargs: + array = kwargs.pop('array') + else: + array = 1.-np.abs(np.linspace(-.97, .97, len(percentiles)-1)) + + if 'alpha' in kwargs: + alpha = kwargs.pop('alpha') + else: + alpha = .8 + + if 'cmap' in kwargs: + cmap = kwargs.pop('cmap') + else: + cmap = LinearSegmentedColormap.from_list('WhToColor', (color, color), N=array.size) + cmap._init() + cmap._lut[:-3, -1] = alpha*array + + kwargs['facecolors'] = [cmap(i) for i in np.linspace(0,1,cmap.N)] + + # pop where from kwargs + where = kwargs.pop('where') if 'where' in kwargs else None + # pop interpolate, which we actually do not do here! + if 'interpolate' in kwargs: kwargs.pop('interpolate') + + def pairwise(iterable): + "s -> (s0,s1), (s1,s2), (s2, s3), ..." + from itertools import tee + #try: + # from itertools import izip as zip + #except ImportError: + # pass + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + polycol = [] + for y1, y2 in pairwise(percentiles): + import matplotlib.mlab as mlab + # Handle united data, such as dates + ax._process_unit_info(xdata=X, ydata=y1) + ax._process_unit_info(ydata=y2) + # Convert the arrays so we can work with them + from numpy import ma + x = ma.masked_invalid(ax.convert_xunits(X)) + y1 = ma.masked_invalid(ax.convert_yunits(y1)) + y2 = ma.masked_invalid(ax.convert_yunits(y2)) + + if y1.ndim == 0: + y1 = np.ones_like(x) * y1 + if y2.ndim == 0: + y2 = np.ones_like(x) * y2 + + if where is None: + where = np.ones(len(x), np.bool) + else: + where = np.asarray(where, np.bool) + + if not (x.shape == y1.shape == y2.shape == where.shape): + raise ValueError("Argument dimensions are incompatible") + + from functools import reduce + mask = reduce(ma.mask_or, [ma.getmask(a) for a in (x, y1, y2)]) + if mask is not ma.nomask: + where &= ~mask + + polys = [] + for ind0, ind1 in mlab.contiguous_regions(where): + xslice = x[ind0:ind1] + y1slice = y1[ind0:ind1] + y2slice = y2[ind0:ind1] + + if not len(xslice): + continue + + N = len(xslice) + p = np.zeros((2 * N + 2, 2), np.float) + + # the purpose of the next two lines is for when y2 is a + # scalar like 0 and we want the fill to go all the way + # down to 0 even if none of the y1 sample points do + start = xslice[0], y2slice[0] + end = xslice[-1], y2slice[-1] + + p[0] = start + p[N + 1] = end + + p[1:N + 1, 0] = xslice + p[1:N + 1, 1] = y1slice + p[N + 2:, 0] = xslice[::-1] + p[N + 2:, 1] = y2slice[::-1] + + polys.append(p) + polycol.extend(polys) + from matplotlib.collections import PolyCollection + if 'zorder' not in kwargs: + kwargs['zorder'] = 0 + plots.append(PolyCollection(polycol, **kwargs)) + ax.add_collection(plots[-1], autolim=True) + ax.autoscale_view() + return plots