Skip to content

Commit 80a037a

Browse files
jonmmeasephilippjfr
authored andcommitted
Update plotly backend to support plotly.py version 3 (#3194)
* 'k' is not a valid plotly color string, use 'black' instead 's' is not a valid plotly symbol string, use 'circle' instead 'plasma' is not a valid plotly colorscale, use 'viridis' instead * Rename global plotly library object in JavaScript The global plotly library object was renamed from 'Plotly' to '_Plotly' in plotly.py 3.4.0 to avoid a naming conflict when a Jupyter Notebook heading contained the text 'Plotly' See: plotly/plotly.py#816 plotly/plotly.py#1250 * Replace use of deprecated graph object classes Graph objects are now structured hierarchically, so go.Marker was deprecated in favor of go.layout.Marker. The use of these classes is fully optional, and they can be replaced by plain dict instances in cases where local validation, tab completions, and docstrings aren't needed. * The figure.data property is now a tuple rather than a list, so it cannot be mutated in place. Instead, the add_traces method is used to append additional traces to the figure. * Object arrays (like annotations) are now stored as tuples rather than lists so they cannot be extended in place. The += operator can be used instead to replace the object array with an extended version of itself. * Graph objects are no longer dict subclasses, they are wrappers around dict instances with some dict-like methods. The to_plotly_json method is used to convert a graph object into a Python dict. * Update plotly in holoviews environment to version 3.4 from the plotly channel * Validate required plotly version * Replace deprecated append_trace method call with add_trace * Subplot references ending in 1 (e.g. x1) now have the 1 removed (e.g. x) * Axis ranges are now returned as tuples not lists * Remove unused import causing flake8 failure * When exporting to standalone html the plotly library is loaded as Plotly but when loaded into the notebook it is loaded as _Plotly. (before this commit figures rendered properly in the jupyter notebook but an error was raised when output to an HTML file that _Plotly is not defined) * Run conda env update with travis_wait command to (hopefully) avoid CI timeout. See https://docs.travis-ci.com/user/common-build-problems/#build-times-out-because-no-output-was-received Prior intermittent CI error: ``` ... $ conda env update -n holoviews -q -f environment.yml Solving environment: ...working... done Preparing transaction: ...working... done Verifying transaction: ...working... done Executing transaction: ...working... dbus post-link :: /etc/machine-id not found .. dbus post-link :: .. using /proc/sys/kernel/random/boot_id No output has been received in the last 10m0s, this potentially indicates a stalled build or something wrong with the build itself. ``` * remove stray print statement
1 parent 5862f23 commit 80a037a

File tree

11 files changed

+43
-37
lines changed

11 files changed

+43
-37
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ install:
2828
# Useful for debugging any issues with conda
2929
- conda info -a
3030
- conda create -q -n holoviews python=$TRAVIS_PYTHON_VERSION
31-
- conda env update -n holoviews -q -f environment.yml
31+
- travis_wait conda env update -n holoviews -q -f environment.yml
3232
- source activate holoviews
3333
- python setup.py develop
3434
- conda env export

environment.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ dependencies:
1818
- conda-forge::netcdf4=1.3.1
1919
- conda-forge::ffmpeg
2020
- conda-forge::flexx=0.4.1
21-
- conda-forge::plotly=2.7
21+
- plotly::plotly=3.4
2222
- bokeh::bokeh=1.0.0
2323
- bokeh::selenium
2424
# Testing requirements

examples/reference/elements/plotly/Scatter.ipynb

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"metadata": {},
3838
"outputs": [],
3939
"source": [
40-
"%%opts Scatter (color='k' symbol='s' size=10)\n",
40+
"%%opts Scatter (color='black' symbol='circle' size=10)\n",
4141
"np.random.seed(42)\n",
4242
"coords = [(i, np.random.random()) for i in range(20)]\n",
4343
"hv.Scatter(coords)"
@@ -56,7 +56,7 @@
5656
"metadata": {},
5757
"outputs": [],
5858
"source": [
59-
"%%opts Scatter (color='k' symbol='x' size=10)\n",
59+
"%%opts Scatter (color='black' symbol='x' size=10)\n",
6060
"hv.Scatter(coords)[0:12] + hv.Scatter(coords)[12:20]"
6161
]
6262
},

examples/reference/elements/plotly/Surface.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"metadata": {},
4141
"outputs": [],
4242
"source": [
43-
"%%opts Surface [width=500 height=500] (cmap='plasma')\n",
43+
"%%opts Surface [width=500 height=500] (cmap='viridis')\n",
4444
"hv.Surface(np.sin(np.linspace(0,100*np.pi*2,10000)).reshape(100,100))"
4545
]
4646
},

holoviews/plotting/plotly/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
from .raster import * # noqa (API import)
1111
from .plot import * # noqa (API import)
1212
from .tabular import * # noqa (API import)
13+
from ...core.util import LooseVersion, VersionError
14+
import plotly
15+
16+
if LooseVersion(plotly.__version__) < '3.4.0':
17+
raise VersionError(
18+
"The plotly extension requires a plotly version >=3.4.0, "
19+
"please upgrade from plotly %s to a more recent version."
20+
% plotly.__version__, plotly.__version__, '3.4.0')
1321

1422
Store.renderers['plotly'] = PlotlyRenderer.instance()
1523

holoviews/plotting/plotly/chart3d.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from matplotlib.cm import get_cmap
44
from plotly import colors
55
from plotly.tools import FigureFactory as FF
6-
from plotly.graph_objs import Scene, XAxis, YAxis, ZAxis
76

87
try:
98
from plotly.figure_factory._trisurf import trisurf as trisurface
@@ -50,8 +49,8 @@ def init_layout(self, key, element, ranges):
5049
else:
5150
opts['aspectmode'] = 'manual'
5251
opts['aspectratio'] = self.aspect
53-
scene = Scene(xaxis=XAxis(xaxis), yaxis=YAxis(yaxis),
54-
zaxis=ZAxis(zaxis), **opts)
52+
scene = go.layout.Scene(xaxis=xaxis, yaxis=yaxis,
53+
zaxis=zaxis, **opts)
5554

5655
return dict(width=self.width, height=self.height,
5756
title=self._format_title(key, separator=' '),

holoviews/plotting/plotly/element.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def init_layout(self, key, element, ranges, xdim=None, ydim=None):
191191
options['yaxis'] = yaxis
192192

193193
l, b, r, t = self.margins
194-
margin = go.Margin(l=l, r=r, b=b, t=t, pad=4)
194+
margin = go.layout.Margin(l=l, r=r, b=b, t=t, pad=4)
195195
return go.Layout(width=self.width, height=self.height,
196196
title=self._format_title(key, separator=' '),
197197
plot_bgcolor=self.bgcolor, margin=margin,
@@ -268,7 +268,7 @@ def generate_plot(self, key, ranges):
268268
if figure is None:
269269
figure = fig
270270
else:
271-
figure['data'].extend(fig['data'])
271+
figure.add_traces(fig.data)
272272

273273
layout = self.init_layout(key, element, ranges)
274274
figure['layout'].update(layout)

holoviews/plotting/plotly/plotlywidgets.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ var PlotlyMethods = {
3232
plot.data[i][key] = data.data[i][key];
3333
}
3434
}
35-
Plotly.relayout(plot, data.layout);
36-
Plotly.redraw(plot);
35+
var plotly = window._Plotly || window.Plotly;
36+
plotly.relayout(plot, data.layout);
37+
plotly.redraw(plot);
3738
}
3839
}
3940

holoviews/plotting/plotly/renderer.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
plot.data[i][key] = obj[key];
2020
}});
2121
}});
22-
Plotly.relayout(plot, data.layout);
23-
Plotly.redraw(plot);
22+
var plotly = window._Plotly || window.Plotly;
23+
plotly.relayout(plot, data.layout);
24+
plotly.redraw(plot);
2425
"""
2526

2627
PLOTLY_WARNING = """
@@ -70,8 +71,7 @@ def diff(self, plot, serialize=True):
7071
Returns a json diff required to update an existing plot with
7172
the latest plot data.
7273
"""
73-
diff = {'data': plot.state.get('data', []),
74-
'layout': plot.state.get('layout', {})}
74+
diff = plot.state.to_plotly_json()
7575
if serialize:
7676
return json.dumps(diff, cls=utils.PlotlyJSONEncoder)
7777
else:
@@ -83,8 +83,8 @@ def _figure_data(self, plot, fmt=None, divuuid=None, comm=True, as_script=False,
8383
if divuuid is None:
8484
divuuid = plot.id
8585

86-
jdata = json.dumps(figure.get('data', []), cls=utils.PlotlyJSONEncoder)
87-
jlayout = json.dumps(figure.get('layout', {}), cls=utils.PlotlyJSONEncoder)
86+
jdata = json.dumps(figure.data, cls=utils.PlotlyJSONEncoder)
87+
jlayout = json.dumps(figure.layout, cls=utils.PlotlyJSONEncoder)
8888

8989
config = {}
9090
config['showLink'] = False
@@ -98,7 +98,8 @@ def _figure_data(self, plot, fmt=None, divuuid=None, comm=True, as_script=False,
9898
'</script>')
9999

100100
script = '\n'.join([
101-
'Plotly.plot("{id}", {data}, {layout}, {config}).then(function() {{',
101+
'var plotly = window._Plotly || window.Plotly;'
102+
'plotly.plot("{id}", {data}, {layout}, {config}).then(function() {{',
102103
' var elem = document.getElementById("{id}.loading"); elem.parentNode.removeChild(elem);',
103104
'}})']).format(id=divuuid,
104105
data=jdata,

holoviews/plotting/plotly/util.py

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
1-
import plotly.graph_objs as go
2-
3-
41
def add_figure(fig, subfig, r, c, idx):
52
"""
63
Combines a figure with an existing figure created with
74
plotly.tools.make_subplots, by adding the data and merging
85
axis layout options.
96
"""
107
ref = fig._grid_ref[r][c][0][1:]
11-
layout = replace_refs(subfig['layout'], ref)
8+
layout = replace_refs(subfig['layout'].to_plotly_json(), ref)
129

1310
fig['layout']['xaxis%s'%ref].update(layout.get('xaxis', {}))
1411
fig['layout']['yaxis%s'%ref].update(layout.get('yaxis', {}))
15-
fig['layout']['annotations'].extend(layout.get('annotations', []))
12+
fig['layout']['annotations'] += layout.get('annotations', ())
1613
for d in subfig['data']:
17-
fig.append_trace(d, r+1, c+1)
14+
fig.add_trace(d, row=r+1, col=c+1)
1815

1916

2017
def replace_refs(obj, ind):
2118
"""
2219
Replaces xref and yref to allow combining multiple plots
2320
"""
24-
if isinstance(obj, go.graph_objs.PlotlyList):
21+
if isinstance(obj, tuple):
2522
return [replace_refs(o, ind) for o in obj]
26-
elif isinstance(obj, go.graph_objs.PlotlyDict):
23+
elif isinstance(obj, dict):
2724
new_obj = {}
2825
for k, v in obj.items():
2926
if k in ['xref', 'yref']:

holoviews/tests/plotting/plotly/testplot.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,30 @@ def test_curve_state(self):
5252
curve = Curve([1, 2, 3])
5353
state = self._get_plot_state(curve)
5454
self.assertEqual(state['data'][0]['y'], np.array([1, 2, 3]))
55-
self.assertEqual(state['layout']['yaxis']['range'], [1, 3])
55+
self.assertEqual(state['layout']['yaxis']['range'], (1, 3))
5656

5757
def test_scatter3d_state(self):
5858
scatter = Scatter3D(([0,1], [2,3], [4,5]))
5959
state = self._get_plot_state(scatter)
6060
self.assertEqual(state['data'][0]['x'], np.array([0, 1]))
6161
self.assertEqual(state['data'][0]['y'], np.array([2, 3]))
6262
self.assertEqual(state['data'][0]['z'], np.array([4, 5]))
63-
self.assertEqual(state['layout']['scene']['xaxis']['range'], [0, 1])
64-
self.assertEqual(state['layout']['scene']['yaxis']['range'], [2, 3])
65-
self.assertEqual(state['layout']['scene']['zaxis']['range'], [4, 5])
63+
self.assertEqual(state['layout']['scene']['xaxis']['range'], (0, 1))
64+
self.assertEqual(state['layout']['scene']['yaxis']['range'], (2, 3))
65+
self.assertEqual(state['layout']['scene']['zaxis']['range'], (4, 5))
6666

6767
def test_overlay_state(self):
6868
layout = Curve([1, 2, 3]) * Curve([2, 4, 6])
6969
state = self._get_plot_state(layout)
7070
self.assertEqual(state['data'][0]['y'], np.array([1, 2, 3]))
7171
self.assertEqual(state['data'][1]['y'], np.array([2, 4, 6]))
72-
self.assertEqual(state['layout']['yaxis']['range'], [1, 6])
72+
self.assertEqual(state['layout']['yaxis']['range'], (1, 6))
7373

7474
def test_layout_state(self):
7575
layout = Curve([1, 2, 3]) + Curve([2, 4, 6])
7676
state = self._get_plot_state(layout)
7777
self.assertEqual(state['data'][0]['y'], np.array([1, 2, 3]))
78-
self.assertEqual(state['data'][0]['yaxis'], 'y1')
78+
self.assertEqual(state['data'][0]['yaxis'], 'y')
7979
self.assertEqual(state['data'][1]['y'], np.array([2, 4, 6]))
8080
self.assertEqual(state['data'][1]['yaxis'], 'y2')
8181

@@ -84,13 +84,13 @@ def test_grid_state(self):
8484
for j in [0, 1]})
8585
state = self._get_plot_state(grid)
8686
self.assertEqual(state['data'][0]['y'], np.array([0, 0]))
87-
self.assertEqual(state['data'][0]['xaxis'], 'x1')
88-
self.assertEqual(state['data'][0]['yaxis'], 'y1')
87+
self.assertEqual(state['data'][0]['xaxis'], 'x')
88+
self.assertEqual(state['data'][0]['yaxis'], 'y')
8989
self.assertEqual(state['data'][1]['y'], np.array([1, 0]))
9090
self.assertEqual(state['data'][1]['xaxis'], 'x2')
91-
self.assertEqual(state['data'][1]['yaxis'], 'y1')
91+
self.assertEqual(state['data'][1]['yaxis'], 'y')
9292
self.assertEqual(state['data'][2]['y'], np.array([0, 1]))
93-
self.assertEqual(state['data'][2]['xaxis'], 'x1')
93+
self.assertEqual(state['data'][2]['xaxis'], 'x')
9494
self.assertEqual(state['data'][2]['yaxis'], 'y2')
9595
self.assertEqual(state['data'][3]['y'], np.array([1, 1]))
9696
self.assertEqual(state['data'][3]['xaxis'], 'x2')

0 commit comments

Comments
 (0)