diff --git a/.travis.yml b/.travis.yml index a56894c8f6..50cb3a1939 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ install: # Useful for debugging any issues with conda - conda info -a - conda create -q -n holoviews python=$TRAVIS_PYTHON_VERSION - - conda env update -n holoviews -q -f environment.yml + - travis_wait conda env update -n holoviews -q -f environment.yml - source activate holoviews - python setup.py develop - conda env export diff --git a/environment.yml b/environment.yml index b4a74c5289..b426703f2a 100644 --- a/environment.yml +++ b/environment.yml @@ -18,7 +18,7 @@ dependencies: - conda-forge::netcdf4=1.3.1 - conda-forge::ffmpeg - conda-forge::flexx=0.4.1 - - conda-forge::plotly=2.7 + - plotly::plotly=3.4 - bokeh::bokeh=1.0.0 - bokeh::selenium # Testing requirements diff --git a/examples/reference/elements/plotly/Scatter.ipynb b/examples/reference/elements/plotly/Scatter.ipynb index 83cde2c212..a49694af41 100644 --- a/examples/reference/elements/plotly/Scatter.ipynb +++ b/examples/reference/elements/plotly/Scatter.ipynb @@ -37,7 +37,7 @@ "metadata": {}, "outputs": [], "source": [ - "%%opts Scatter (color='k' symbol='s' size=10)\n", + "%%opts Scatter (color='black' symbol='circle' size=10)\n", "np.random.seed(42)\n", "coords = [(i, np.random.random()) for i in range(20)]\n", "hv.Scatter(coords)" @@ -56,7 +56,7 @@ "metadata": {}, "outputs": [], "source": [ - "%%opts Scatter (color='k' symbol='x' size=10)\n", + "%%opts Scatter (color='black' symbol='x' size=10)\n", "hv.Scatter(coords)[0:12] + hv.Scatter(coords)[12:20]" ] }, diff --git a/examples/reference/elements/plotly/Surface.ipynb b/examples/reference/elements/plotly/Surface.ipynb index 09cd76e23d..09c71fbc4f 100644 --- a/examples/reference/elements/plotly/Surface.ipynb +++ b/examples/reference/elements/plotly/Surface.ipynb @@ -40,7 +40,7 @@ "metadata": {}, "outputs": [], "source": [ - "%%opts Surface [width=500 height=500] (cmap='plasma')\n", + "%%opts Surface [width=500 height=500] (cmap='viridis')\n", "hv.Surface(np.sin(np.linspace(0,100*np.pi*2,10000)).reshape(100,100))" ] }, diff --git a/holoviews/plotting/plotly/__init__.py b/holoviews/plotting/plotly/__init__.py index 0d20a9d7c7..35564629a1 100644 --- a/holoviews/plotting/plotly/__init__.py +++ b/holoviews/plotting/plotly/__init__.py @@ -10,6 +10,14 @@ from .raster import * # noqa (API import) from .plot import * # noqa (API import) from .tabular import * # noqa (API import) +from ...core.util import LooseVersion, VersionError +import plotly + +if LooseVersion(plotly.__version__) < '3.4.0': + raise VersionError( + "The plotly extension requires a plotly version >=3.4.0, " + "please upgrade from plotly %s to a more recent version." + % plotly.__version__, plotly.__version__, '3.4.0') Store.renderers['plotly'] = PlotlyRenderer.instance() diff --git a/holoviews/plotting/plotly/chart3d.py b/holoviews/plotting/plotly/chart3d.py index 71a7df7938..aadecd35d7 100644 --- a/holoviews/plotting/plotly/chart3d.py +++ b/holoviews/plotting/plotly/chart3d.py @@ -3,7 +3,6 @@ from matplotlib.cm import get_cmap from plotly import colors from plotly.tools import FigureFactory as FF -from plotly.graph_objs import Scene, XAxis, YAxis, ZAxis try: from plotly.figure_factory._trisurf import trisurf as trisurface @@ -50,8 +49,8 @@ def init_layout(self, key, element, ranges): else: opts['aspectmode'] = 'manual' opts['aspectratio'] = self.aspect - scene = Scene(xaxis=XAxis(xaxis), yaxis=YAxis(yaxis), - zaxis=ZAxis(zaxis), **opts) + scene = go.layout.Scene(xaxis=xaxis, yaxis=yaxis, + zaxis=zaxis, **opts) return dict(width=self.width, height=self.height, title=self._format_title(key, separator=' '), diff --git a/holoviews/plotting/plotly/element.py b/holoviews/plotting/plotly/element.py index b932d7dda8..86a3e85006 100644 --- a/holoviews/plotting/plotly/element.py +++ b/holoviews/plotting/plotly/element.py @@ -191,7 +191,7 @@ def init_layout(self, key, element, ranges, xdim=None, ydim=None): options['yaxis'] = yaxis l, b, r, t = self.margins - margin = go.Margin(l=l, r=r, b=b, t=t, pad=4) + margin = go.layout.Margin(l=l, r=r, b=b, t=t, pad=4) return go.Layout(width=self.width, height=self.height, title=self._format_title(key, separator=' '), plot_bgcolor=self.bgcolor, margin=margin, @@ -268,7 +268,7 @@ def generate_plot(self, key, ranges): if figure is None: figure = fig else: - figure['data'].extend(fig['data']) + figure.add_traces(fig.data) layout = self.init_layout(key, element, ranges) figure['layout'].update(layout) diff --git a/holoviews/plotting/plotly/plotlywidgets.js b/holoviews/plotting/plotly/plotlywidgets.js index 2496a4db36..8e246f41f8 100644 --- a/holoviews/plotting/plotly/plotlywidgets.js +++ b/holoviews/plotting/plotly/plotlywidgets.js @@ -32,8 +32,9 @@ var PlotlyMethods = { plot.data[i][key] = data.data[i][key]; } } - Plotly.relayout(plot, data.layout); - Plotly.redraw(plot); + var plotly = window._Plotly || window.Plotly; + plotly.relayout(plot, data.layout); + plotly.redraw(plot); } } diff --git a/holoviews/plotting/plotly/renderer.py b/holoviews/plotting/plotly/renderer.py index 3a61d5ea2c..2a26316552 100644 --- a/holoviews/plotting/plotly/renderer.py +++ b/holoviews/plotting/plotly/renderer.py @@ -19,8 +19,9 @@ plot.data[i][key] = obj[key]; }}); }}); -Plotly.relayout(plot, data.layout); -Plotly.redraw(plot); +var plotly = window._Plotly || window.Plotly; +plotly.relayout(plot, data.layout); +plotly.redraw(plot); """ PLOTLY_WARNING = """ @@ -70,8 +71,7 @@ def diff(self, plot, serialize=True): Returns a json diff required to update an existing plot with the latest plot data. """ - diff = {'data': plot.state.get('data', []), - 'layout': plot.state.get('layout', {})} + diff = plot.state.to_plotly_json() if serialize: return json.dumps(diff, cls=utils.PlotlyJSONEncoder) else: @@ -83,8 +83,8 @@ def _figure_data(self, plot, fmt=None, divuuid=None, comm=True, as_script=False, if divuuid is None: divuuid = plot.id - jdata = json.dumps(figure.get('data', []), cls=utils.PlotlyJSONEncoder) - jlayout = json.dumps(figure.get('layout', {}), cls=utils.PlotlyJSONEncoder) + jdata = json.dumps(figure.data, cls=utils.PlotlyJSONEncoder) + jlayout = json.dumps(figure.layout, cls=utils.PlotlyJSONEncoder) config = {} config['showLink'] = False @@ -98,7 +98,8 @@ def _figure_data(self, plot, fmt=None, divuuid=None, comm=True, as_script=False, '') script = '\n'.join([ - 'Plotly.plot("{id}", {data}, {layout}, {config}).then(function() {{', + 'var plotly = window._Plotly || window.Plotly;' + 'plotly.plot("{id}", {data}, {layout}, {config}).then(function() {{', ' var elem = document.getElementById("{id}.loading"); elem.parentNode.removeChild(elem);', '}})']).format(id=divuuid, data=jdata, diff --git a/holoviews/plotting/plotly/util.py b/holoviews/plotting/plotly/util.py index bd72369985..93e767e81e 100644 --- a/holoviews/plotting/plotly/util.py +++ b/holoviews/plotting/plotly/util.py @@ -1,6 +1,3 @@ -import plotly.graph_objs as go - - def add_figure(fig, subfig, r, c, idx): """ Combines a figure with an existing figure created with @@ -8,22 +5,22 @@ def add_figure(fig, subfig, r, c, idx): axis layout options. """ ref = fig._grid_ref[r][c][0][1:] - layout = replace_refs(subfig['layout'], ref) + layout = replace_refs(subfig['layout'].to_plotly_json(), ref) fig['layout']['xaxis%s'%ref].update(layout.get('xaxis', {})) fig['layout']['yaxis%s'%ref].update(layout.get('yaxis', {})) - fig['layout']['annotations'].extend(layout.get('annotations', [])) + fig['layout']['annotations'] += layout.get('annotations', ()) for d in subfig['data']: - fig.append_trace(d, r+1, c+1) + fig.add_trace(d, row=r+1, col=c+1) def replace_refs(obj, ind): """ Replaces xref and yref to allow combining multiple plots """ - if isinstance(obj, go.graph_objs.PlotlyList): + if isinstance(obj, tuple): return [replace_refs(o, ind) for o in obj] - elif isinstance(obj, go.graph_objs.PlotlyDict): + elif isinstance(obj, dict): new_obj = {} for k, v in obj.items(): if k in ['xref', 'yref']: diff --git a/holoviews/tests/plotting/plotly/testplot.py b/holoviews/tests/plotting/plotly/testplot.py index b14ddfde23..cff4c9238f 100644 --- a/holoviews/tests/plotting/plotly/testplot.py +++ b/holoviews/tests/plotting/plotly/testplot.py @@ -52,7 +52,7 @@ def test_curve_state(self): curve = Curve([1, 2, 3]) state = self._get_plot_state(curve) self.assertEqual(state['data'][0]['y'], np.array([1, 2, 3])) - self.assertEqual(state['layout']['yaxis']['range'], [1, 3]) + self.assertEqual(state['layout']['yaxis']['range'], (1, 3)) def test_scatter3d_state(self): scatter = Scatter3D(([0,1], [2,3], [4,5])) @@ -60,22 +60,22 @@ def test_scatter3d_state(self): self.assertEqual(state['data'][0]['x'], np.array([0, 1])) self.assertEqual(state['data'][0]['y'], np.array([2, 3])) self.assertEqual(state['data'][0]['z'], np.array([4, 5])) - self.assertEqual(state['layout']['scene']['xaxis']['range'], [0, 1]) - self.assertEqual(state['layout']['scene']['yaxis']['range'], [2, 3]) - self.assertEqual(state['layout']['scene']['zaxis']['range'], [4, 5]) + self.assertEqual(state['layout']['scene']['xaxis']['range'], (0, 1)) + self.assertEqual(state['layout']['scene']['yaxis']['range'], (2, 3)) + self.assertEqual(state['layout']['scene']['zaxis']['range'], (4, 5)) def test_overlay_state(self): layout = Curve([1, 2, 3]) * Curve([2, 4, 6]) state = self._get_plot_state(layout) self.assertEqual(state['data'][0]['y'], np.array([1, 2, 3])) self.assertEqual(state['data'][1]['y'], np.array([2, 4, 6])) - self.assertEqual(state['layout']['yaxis']['range'], [1, 6]) + self.assertEqual(state['layout']['yaxis']['range'], (1, 6)) def test_layout_state(self): layout = Curve([1, 2, 3]) + Curve([2, 4, 6]) state = self._get_plot_state(layout) self.assertEqual(state['data'][0]['y'], np.array([1, 2, 3])) - self.assertEqual(state['data'][0]['yaxis'], 'y1') + self.assertEqual(state['data'][0]['yaxis'], 'y') self.assertEqual(state['data'][1]['y'], np.array([2, 4, 6])) self.assertEqual(state['data'][1]['yaxis'], 'y2') @@ -84,13 +84,13 @@ def test_grid_state(self): for j in [0, 1]}) state = self._get_plot_state(grid) self.assertEqual(state['data'][0]['y'], np.array([0, 0])) - self.assertEqual(state['data'][0]['xaxis'], 'x1') - self.assertEqual(state['data'][0]['yaxis'], 'y1') + self.assertEqual(state['data'][0]['xaxis'], 'x') + self.assertEqual(state['data'][0]['yaxis'], 'y') self.assertEqual(state['data'][1]['y'], np.array([1, 0])) self.assertEqual(state['data'][1]['xaxis'], 'x2') - self.assertEqual(state['data'][1]['yaxis'], 'y1') + self.assertEqual(state['data'][1]['yaxis'], 'y') self.assertEqual(state['data'][2]['y'], np.array([0, 1])) - self.assertEqual(state['data'][2]['xaxis'], 'x1') + self.assertEqual(state['data'][2]['xaxis'], 'x') self.assertEqual(state['data'][2]['yaxis'], 'y2') self.assertEqual(state['data'][3]['y'], np.array([1, 1])) self.assertEqual(state['data'][3]['xaxis'], 'x2')