This notebook introduces the ipyplotly enhancements to the plotly.py visualization library and demonstrates some of its features.
# ipyplotly
from plotly.graph_objs import FigureWidget
from plotly.callbacks import Points, InputDeviceState
# pandas
import pandas as pd
# numpy
import numpy as np
# scikit learn
from sklearn import datasets
# ipywidgets
from ipywidgets import HBox, VBox, Button
# functools
from functools import partial
# Load iris dataset
iris_data = datasets.load_iris()
feature_names = [name.replace(' (cm)', '').replace(' ', '_') for name in iris_data.feature_names]
iris_df = pd.DataFrame(iris_data.data, columns=feature_names)
iris_class = iris_data.target + 1
iris_df.head()
| sepal_length | sepal_width | petal_length | petal_width | |
|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 |
A FigureWidget behaves almost identically to a Figure but it is also an ipywidget that can be displayed directly in the notebook without calling iplot
f1 = FigureWidget()
f1
Entering f1.add_<tab> displays add methods for all of the supported trace types
# f1.add_
Entering f1.add_scatter(<tab>) displays the names of all of the top-level properties for the scatter trace type
Entering f1.add_scatter(<shift+tab>) displays the signature pop-up. Expanding this pop-up reveals the method doc string which contains the descriptions of all of the top level properties
# f1.add_scatter(
scatt1 = f1.add_scatter(x=iris_df.sepal_length, y=iris_df.petal_width)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-7-ca1e8c8a4df1> in <module>() ----> 1 scatt1 = f1.add_scatter(x=iris_df.sepal_length, y=iris_df.petal_width) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/plotly/graph_objs/_figurewidget.pyc in add_scatter(self, cliponaxis, connectgaps, customdata, customdatasrc, dx, dy, error_x, error_y, fill, fillcolor, hoverinfo, hoverinfosrc, hoverlabel, hoveron, hovertext, hovertextsrc, ids, idssrc, legendgroup, line, marker, mode, name, opacity, r, rsrc, selected, selectedpoints, showlegend, stream, t, text, textfont, textposition, textpositionsrc, textsrc, tsrc, uid, unselected, visible, x, x0, xaxis, xcalendar, xsrc, y, y0, yaxis, ycalendar, ysrc, row, col, **kwargs) 5580 **kwargs 5581 ) -> 5582 return self.add_trace(new_trace, row=row, col=col) 5583 5584 def add_scatter3d( /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/plotly/basedatatypes.pyc in add_trace(self, trace, row, col) 1000 return self.add_traces(data=[trace], 1001 rows=[row] if row is not None else None, -> 1002 cols=[col] if col is not None else None 1003 )[0] 1004 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/plotly/basedatatypes.pyc in add_traces(self, data, rows, cols) 1101 1102 # Update messages -> 1103 self._send_addTraces_msg(new_traces_data) 1104 1105 return data /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/plotly/basewidget.pyc in _send_addTraces_msg(self, new_traces_data) 277 # Send message 278 # ------------ --> 279 self._py2js_addTraces = add_traces_msg 280 self._py2js_addTraces = None 281 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/plotly/basedatatypes.pyc in __setattr__(self, prop, value) 272 if prop.startswith('_') or hasattr(self, prop): 273 # Let known properties and private properties through --> 274 super(BaseFigure, self).__setattr__(prop, value) 275 else: 276 # Raise error on unknown public properties /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/traitlets/traitlets.pyc in __set__(self, obj, value) 583 raise TraitError('The "%s" trait is read-only.' % self.name) 584 else: --> 585 self.set(obj, value) 586 587 def _validate(self, obj, value): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/traitlets/traitlets.pyc in set(self, obj, value) 572 # we explicitly compare silent to True just in case the equality 573 # comparison above returns something other than True/False --> 574 obj._notify_trait(self.name, old_value, new_value) 575 576 def __set__(self, obj, value): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/traitlets/traitlets.pyc in _notify_trait(self, name, old_value, new_value) 1137 new=new_value, 1138 owner=self, -> 1139 type='change', 1140 )) 1141 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipywidgets/widgets/widget.pyc in notify_change(self, change) 395 if name in self.keys and self._should_send_property(name, change['new']): 396 # Send new state to front-end --> 397 self.send_state(key=name) 398 LoggingConfigurable.notify_change(self, change) 399 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipywidgets/widgets/widget.pyc in send_state(self, key) 280 state, buffer_keys, buffers = self._split_state_buffers(state) 281 msg = {'method': 'update', 'state': state, 'buffers': buffer_keys} --> 282 self._send(msg, buffers=buffers) 283 284 def get_state(self, key=None, drop_defaults=False): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipywidgets/widgets/widget.pyc in _send(self, msg, buffers) 534 """Sends a message to the model in the front-end.""" 535 if self.comm is not None and self.comm.kernel is not None: --> 536 self.comm.send(data=msg, buffers=buffers) 537 538 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/comm/comm.pyc in send(self, data, metadata, buffers) 119 """Send a message to the frontend-side version of this comm""" 120 self._publish_msg('comm_msg', --> 121 data=data, metadata=metadata, buffers=buffers, 122 ) 123 /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/comm/comm.pyc in _publish_msg(self, msg_type, data, metadata, buffers, **keys) 63 data = {} if data is None else data 64 metadata = {} if metadata is None else metadata ---> 65 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) 66 self.kernel.session.send(self.kernel.iopub_socket, msg_type, 67 content, /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/jsonutil.pyc in json_clean(obj) 165 out = {} 166 for k,v in iteritems(obj): --> 167 out[unicode_type(k)] = json_clean(v) 168 return out 169 if isinstance(obj, datetime): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/jsonutil.pyc in json_clean(obj) 165 out = {} 166 for k,v in iteritems(obj): --> 167 out[unicode_type(k)] = json_clean(v) 168 return out 169 if isinstance(obj, datetime): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/jsonutil.pyc in json_clean(obj) 165 out = {} 166 for k,v in iteritems(obj): --> 167 out[unicode_type(k)] = json_clean(v) 168 return out 169 if isinstance(obj, datetime): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/jsonutil.pyc in json_clean(obj) 165 out = {} 166 for k,v in iteritems(obj): --> 167 out[unicode_type(k)] = json_clean(v) 168 return out 169 if isinstance(obj, datetime): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/jsonutil.pyc in json_clean(obj) 151 152 if isinstance(obj, list): --> 153 return [json_clean(x) for x in obj] 154 155 if isinstance(obj, dict): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/jsonutil.pyc in json_clean(obj) 165 out = {} 166 for k,v in iteritems(obj): --> 167 out[unicode_type(k)] = json_clean(v) 168 return out 169 if isinstance(obj, datetime): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/jsonutil.pyc in json_clean(obj) 165 out = {} 166 for k,v in iteritems(obj): --> 167 out[unicode_type(k)] = json_clean(v) 168 return out 169 if isinstance(obj, datetime): /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/ipykernel/jsonutil.pyc in json_clean(obj) 171 172 # we don't understand it, it's probably an unserializable object --> 173 raise ValueError("Can't clean for JSON: %r" % obj) ValueError: Can't clean for JSON: <memory at 0x10bf55b98>
f1
Failed to display Jupyter Widget of type FigureWidget.
If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.
If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.
scatt1.mode?
# That's not what we wanted, change the mode to 'markers'
scatt1.mode = 'markers'
# Set size to 8
scatt1.marker.size = 8
# Color markers by iris class
scatt1.marker.color = iris_class
# Change colorscale
scatt1.marker.cmin = 0.5
scatt1.marker.cmax = 3.5
scatt1.marker.colorscale = [[0, 'red'], [0.33, 'red'],
[0.33, 'green'], [0.67, 'green'],
[0.67, 'blue'], [1.0, 'blue']]
scatt1.marker.showscale = True
# Fix up colorscale ticks
scatt1.marker.colorbar.ticks = 'outside'
scatt1.marker.colorbar.tickvals = [1, 2, 3]
scatt1.marker.colorbar.ticktext = iris_data.target_names.tolist()
# Set colorscale title
scatt1.marker.colorbar.title = 'Species'
scatt1.marker.colorbar.titlefont.size = 16
scatt1.marker.colorbar.titlefont.family = 'Rockwell'
# Add axis labels
f1.layout.xaxis.title = 'sepal_length'
f1.layout.yaxis.title = 'petal_width'
f1
Failed to display Jupyter Widget of type FigureWidget.
If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.
If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.
# Hover info
scatt1.text = iris_data.target_names[iris_data.target]
scatt1.hoverinfo = 'text+x+y'
f1.layout.hovermode = 'closest'
f1
Failed to display Jupyter Widget of type FigureWidget.
If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.
If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.
# Set marker size based on petal_length
with f1.batch_animate(duration=1000):
scatt1.marker.size = np.sqrt(iris_df.petal_length.values * 50)
# Restore constant marker size
with f1.batch_animate(duration=1000):
scatt1.marker.size = 8
Make points more transparent when dragmode is zoom
def set_opacity(marker, layout, dragmode):
if dragmode == 'zoom':
marker.opacity = 0.5
else:
marker.opacity = 1.0
f1.layout.on_change(partial(set_opacity, scatt1.marker), 'dragmode')
scatt1.marker.colorbar = None
scatt1.marker.colorscale = [[0, 'lightgray'], [0.5, 'lightgray'], [0.5, 'red'], [1, 'red']]
scatt1.marker.cmin = -0.5
scatt1.marker.cmax = 1.5
scatt1.marker.colorbar.ticks = 'outside'
scatt1.marker.colorbar.tickvals = [0, 1]
scatt1.marker.colorbar.ticktext = ['unselected', 'selected']
# Reset colors to zeros (unselected)
scatt1.marker.color = np.zeros(iris_class.size)
selected = np.zeros(iris_class.size)
f1
Failed to display Jupyter Widget of type FigureWidget.
If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.
If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.
# Assigning these variables here is not required. But doing so tricks Jupyter into
# providing property tab completion on the parameters to the brush function below
trace, points, state = scatt1, Points(), InputDeviceState()
def brush(trace, points, state):
inds = np.array(points.point_inds)
if inds.size:
selected[inds] = 1
trace.marker.color = selected
scatt1.on_selected(brush)
Now box or lasso select points on the figure and see them turn red
# Reset brush
selected = np.zeros(iris_class.size)
scatt1.marker.color = selected
f2 = FigureWidget(data=[{'type': 'scatter',
'x': iris_df.petal_length,
'y': iris_df.sepal_width,
'mode': 'markers'}])
f2
Failed to display Jupyter Widget of type FigureWidget.
If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.
If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.
# Set axis titles
f2.layout.xaxis.title = 'petal_length'
f2.layout.yaxis.title = 'sepal_width'
# Grab trace reference
scatt2 = f2.data[0]
# Set marker styles / colorbars to match between figures
scatt2.marker = scatt1.marker
# Configure brush on both plots to update both plots
def brush(trace, points, state):
inds = np.array(points.point_inds)
if inds.size:
selected = scatt1.marker.color.copy()
selected[inds] = 1
scatt1.marker.color = selected
scatt2.marker.color = selected
scatt1.on_selected(brush)
scatt2.on_selected(brush)
f2.layout.on_change(partial(set_opacity, scatt2.marker), 'dragmode')
# Reset brush
def reset_brush(btn):
selected = np.zeros(iris_class.size)
scatt1.marker.color = selected
scatt2.marker.color = selected
# Create reset button
button = Button(description="clear")
button.on_click(reset_brush)
# Hide colorbar for figure 1
scatt1.marker.showscale = False
# Set dragmode to lasso for both plots
f1.layout.dragmode = 'lasso'
f2.layout.dragmode = 'lasso'
# Display two figures and the reset button
f1.layout.width = 500
f2.layout.width = 500
VBox([HBox([f1, f2]), button])
Failed to display Jupyter Widget of type VBox.
If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean that the widgets JavaScript is still loading. If this message persists, it likely means that the widgets JavaScript library is either not installed or not enabled. See the Jupyter Widgets Documentation for setup instructions.
If you're reading this message in another frontend (for example, a static rendering on GitHub or NBViewer), it may mean that your frontend doesn't currently support widgets.
# Save figure 2 to a svg image in the exports directory
f2.save_image('exports/f2.svg')
# Save figure 1 to a pdf in the exports directory (requires cairosvg be installed)
# f1.save_image('exports/f1.pdf')