Skip to content

Commit

Permalink
Update plotly backend to support plotly.py version 3 (#3194)
Browse files Browse the repository at this point in the history
* '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
  • Loading branch information
jonmmease authored and philippjfr committed Nov 25, 2018
1 parent 5862f23 commit 80a037a
Show file tree
Hide file tree
Showing 11 changed files with 43 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions examples/reference/elements/plotly/Scatter.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand All @@ -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]"
]
},
Expand Down
2 changes: 1 addition & 1 deletion examples/reference/elements/plotly/Surface.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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))"
]
},
Expand Down
8 changes: 8 additions & 0 deletions holoviews/plotting/plotly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
5 changes: 2 additions & 3 deletions holoviews/plotting/plotly/chart3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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=' '),
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/plotly/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions holoviews/plotting/plotly/plotlywidgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
15 changes: 8 additions & 7 deletions holoviews/plotting/plotly/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -98,7 +98,8 @@ def _figure_data(self, plot, fmt=None, divuuid=None, comm=True, as_script=False,
'</script>')

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,
Expand Down
13 changes: 5 additions & 8 deletions holoviews/plotting/plotly/util.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import plotly.graph_objs as go


def add_figure(fig, subfig, r, c, idx):
"""
Combines a figure with an existing figure created with
plotly.tools.make_subplots, by adding the data and merging
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']:
Expand Down
20 changes: 10 additions & 10 deletions holoviews/tests/plotting/plotly/testplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,30 +52,30 @@ 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]))
state = self._get_plot_state(scatter)
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')

Expand All @@ -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')
Expand Down

0 comments on commit 80a037a

Please sign in to comment.