GeezerVison is showing perspective-dev.github.io — perspective.widget API documentation
PerspectiveWidget is a JupyterLab widget that implements the same API as
<perspective-viewer>, allowing for fast, intuitive
transformations/visualizations of various data formats within JupyterLab.
PerspectiveWidget is compatible with Jupyterlab 3 and Jupyter Notebook 6 via a
prebuilt extension.
To use it, simply install perspective-python and the extensions should be
available.
perspective-python's JupyterLab extension also provides convenient builtin
viewers for csv, json, or arrow files. Simply right-click on a file with
this extension and choose the appropriate Perpective option from the context
menu.
Building on top of the API provided by perspective.Table, the
PerspectiveWidget is a JupyterLab plugin that offers the entire functionality
of Perspective within the Jupyter environment. It supports the same API
semantics of <perspective-viewer>, along with the additional data types
supported by perspective.Table. PerspectiveWidget takes keyword arguments
for the managed View:
from perspective.widget import PerspectiveWidget
w = perspective.PerspectiveWidget(
data,
plugin="X Bar",
aggregates={"datetime": "any"},
sort=[["date", "desc"]]
)
Creating a widget
A widget is created through the PerspectiveWidget constructor, which takes as
its first, required parameter a perspective.Table, a dataset, a schema, or
None, which serves as a special value that tells the Widget to defer loading
any data until later. In maintaining consistency with the Javascript API,
Widgets cannot be created with empty dictionaries or lists—None should be used
if the intention is to await data for loading later on. A widget can be
constructed from a dataset:
from perspective.widget import PerspectiveWidget
PerspectiveWidget(data, group_by=["date"])
.. or a schema:
PerspectiveWidget({"a": int, "b": str})
.. or an instance of a perspective.Table:
table = perspective.table(data)
PerspectiveWidget(table)
View Source
1# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7# ┃ Copyright (c) 2017, the Perspective Authors. ┃
8# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9# ┃ This file is part of the Perspective library, distributed under the terms ┃
10# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13import base64
14import logging
15import os
16import re
17import importlib.metadata
18import inspect
19
20from string import Template
21from ipywidgets import DOMWidget
22from traitlets import Unicode, observe
23from .viewer import PerspectiveViewer
24
25__version__ = re.sub(".dev[0-9]+", "", importlib.metadata.version("perspective-python"))
26
27__all__ = ["PerspectiveWidget"]
28
29__doc__ = """
30`PerspectiveWidget` is a JupyterLab widget that implements the same API as
31`<perspective-viewer>`, allowing for fast, intuitive
32transformations/visualizations of various data formats within JupyterLab.
33
34`PerspectiveWidget` is compatible with Jupyterlab 3 and Jupyter Notebook 6 via a
35[prebuilt extension](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#prebuilt-extensions).
36To use it, simply install `perspective-python` and the extensions should be
37available.
38
39`perspective-python`'s JupyterLab extension also provides convenient builtin
40viewers for `csv`, `json`, or `arrow` files. Simply right-click on a file with
41this extension and choose the appropriate `Perpective` option from the context
42menu.
43
44## `PerspectiveWidget`
45
46Building on top of the API provided by `perspective.Table`, the
47`PerspectiveWidget` is a JupyterLab plugin that offers the entire functionality
48of Perspective within the Jupyter environment. It supports the same API
49semantics of `<perspective-viewer>`, along with the additional data types
50supported by `perspective.Table`. `PerspectiveWidget` takes keyword arguments
51for the managed `View`:
52
53```python
54from perspective.widget import PerspectiveWidget
55w = perspective.PerspectiveWidget(
56 data,
57 plugin="X Bar",
58 aggregates={"datetime": "any"},
59 sort=[["date", "desc"]]
60)
61```
62
63### Creating a widget
64
65A widget is created through the `PerspectiveWidget` constructor, which takes as
66its first, required parameter a `perspective.Table`, a dataset, a schema, or
67`None`, which serves as a special value that tells the Widget to defer loading
68any data until later. In maintaining consistency with the Javascript API,
69Widgets cannot be created with empty dictionaries or lists—`None` should be used
70if the intention is to await data for loading later on. A widget can be
71constructed from a dataset:
72
73```python
74from perspective.widget import PerspectiveWidget
75PerspectiveWidget(data, group_by=["date"])
76```
77
78.. or a schema:
79
80```python
81PerspectiveWidget({"a": int, "b": str})
82```
83
84.. or an instance of a `perspective.Table`:
85
86```python
87table = perspective.table(data)
88PerspectiveWidget(table)
89```
90"""
91
92
93class PerspectiveWidget(DOMWidget, PerspectiveViewer):
94 """`PerspectiveWidget` allows for Perspective to be used as a Jupyter
95 widget.
96
97 Using `perspective.Table`, you can create a widget that extends the full
98 functionality of `perspective-viewer`. Changes on the viewer can be
99 programatically set on the `PerspectiveWidget` instance.
100
101 # Examples
102
103 >>> from perspective.widget import PerspectiveWidget
104 >>> data = {
105 ... "a": [1, 2, 3],
106 ... "b": [
107 ... "2019/07/11 7:30PM",
108 ... "2019/07/11 8:30PM",
109 ... "2019/07/11 9:30PM"
110 ... ]
111 ... }
112 >>> widget = PerspectiveWidget(
113 ... data,
114 ... group_by=["a"],
115 ... sort=[["b", "desc"]],
116 ... filter=[["a", ">", 1]]
117 ... )
118 >>> widget.sort
119 [["b", "desc"]]
120 >>> widget.sort.append(["a", "asc"])
121 >>> widget.sort
122 [["b", "desc"], ["a", "asc"]]
123 >>> widget.table.update({"a": [4, 5]}) # Browser UI updates
124 """
125
126 # Required by ipywidgets for proper registration of the backend
127 _model_name = Unicode("PerspectiveModel").tag(sync=True)
128 _model_module = Unicode("@perspective-dev/jupyterlab").tag(sync=True)
129 _model_module_version = Unicode("~{}".format(__version__)).tag(sync=True)
130 _view_name = Unicode("PerspectiveView").tag(sync=True)
131 _view_module = Unicode("@perspective-dev/jupyterlab").tag(sync=True)
132 _view_module_version = Unicode("~{}".format(__version__)).tag(sync=True)
133
134 def __init__(
135 self,
136 data,
137 index=None,
138 limit=None,
139 binding_mode="server",
140 **kwargs,
141 ):
142 """Initialize an instance of `PerspectiveWidget`
143 with the given table/data and viewer configuration.
144
145 If an `AsyncTable` is passed in, then certain widget methods like
146 `update()` and `delete()` return coroutines which must be awaited.
147
148 # Arguments
149
150 - `data` (`Table`|`AsyncTable`|`dict`|`list`|`pandas.DataFrame`|`bytes`|`str`): a
151 `perspective.Table` instance, a `perspective.AsyncTable` instance, or
152 a dataset to be loaded in the widget.
153
154 # Keyword Arguments
155
156 - `index` (`str`): A column name to be used as the primary key.
157 Ignored if `server` is True.
158 - `binding_mode` (`str`): "client-server" or "server"
159 - `limit` (`int`): A upper limit on the number of rows in the Table.
160 Cannot be set at the same time as `index`, ignored if `server`
161 is True.
162 - `kwargs` (`dict`): configuration options for the `PerspectiveViewer`,
163 and `Table` constructor if `data` is a dataset.
164
165 # Examples
166
167 >>> widget = PerspectiveWidget(
168 ... {"a": [1, 2, 3]},
169 ... aggregates={"a": "avg"},
170 ... group_by=["a"],
171 ... sort=[["b", "desc"]],
172 ... filter=[["a", ">", 1]],
173 ... expressions=["\"a\" + 100"])
174 """
175
176 self.binding_mode = binding_mode
177
178 # Pass table load options to the front-end, unless in server mode
179 self._options = {}
180
181 if index is not None and limit is not None:
182 raise TypeError("Index and Limit cannot be set at the same time!")
183
184 # Parse the dataset we pass in - if it's Pandas, preserve pivots
185 # if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series):
186 # data, config = deconstruct_pandas(data)
187
188 # if config.get("group_by", None) and "group_by" not in kwargs:
189 # kwargs.update({"group_by": config["group_by"]})
190
191 # if config.get("split_by", None) and "split_by" not in kwargs:
192 # kwargs.update({"split_by": config["split_by"]})
193
194 # if config.get("columns", None) and "columns" not in kwargs:
195 # kwargs.update({"columns": config["columns"]})
196
197 # Initialize the viewer
198 super(PerspectiveWidget, self).__init__(**kwargs)
199
200 # Handle messages from the the front end
201 self.on_msg(self.handle_message)
202 self._sessions = {}
203
204 # If an empty dataset is provided, don't call `load()` and wait
205 # for the user to call `load()`.
206 if data is None:
207 if index is not None or limit is not None:
208 raise TypeError(
209 "Cannot initialize PerspectiveWidget `index` or `limit` without a Table, data, or schema!"
210 )
211 else:
212 if index is not None:
213 self._options.update({"index": index})
214
215 if limit is not None:
216 self._options.update({"limit": limit})
217
218 loading = self.load(data, **self._options)
219 if inspect.isawaitable(loading):
220 import asyncio
221
222 asyncio.create_task(loading)
223
224 def load(self, data, **options):
225 """Load the widget with data."""
226 # Viewer will ignore **options if `data` is a Table or View.
227 return super(PerspectiveWidget, self).load(data, **options)
228
229 def update(self, data):
230 """Update the widget with new data."""
231 return super(PerspectiveWidget, self).update(data)
232
233 def clear(self):
234 """Clears the widget's underlying `Table`."""
235 return super(PerspectiveWidget, self).clear()
236
237 def replace(self, data):
238 """Replaces the widget's `Table` with new data conforming to the same
239 schema. Does not clear user-set state. If in client mode, serializes
240 the data and sends it to the browser.
241 """
242 return super(PerspectiveWidget, self).replace(data)
243
244 def delete(self, delete_table=True):
245 """Delete the Widget's data and clears its internal state.
246
247 # Arguments
248
249 - `delete_table` (`bool`): whether the underlying `Table` will be
250 deleted. Defaults to True.
251 """
252 ret = super(PerspectiveWidget, self).delete(delete_table)
253
254 # Close the underlying comm and remove widget from the front-end
255 self.close()
256 return ret
257
258 @observe("value")
259 def handle_message(self, widget, content, buffers):
260 """Given a message from `PerspectiveJupyterClient.send`, process the
261 message and return the result to `self.post`.
262
263 # Arguments
264
265 - `widget`: a reference to the `Widget` instance that received the
266 message.
267 - `content` (dict): - the message from the front-end. Automatically
268 de-serialized by ipywidgets.
269 - `buffers`: optional arraybuffers from the front-end, if any.
270 """
271 if content["type"] == "connect":
272 client_id = content["client_id"]
273 logging.debug("view {} connected", client_id)
274
275 def send_response(msg):
276 self.send({"type": "binary_msg", "client_id": client_id}, [msg])
277
278 self._sessions[client_id] = self.new_proxy_session(send_response)
279 elif content["type"] == "binary_msg":
280 [binary_msg] = buffers
281 client_id = content["client_id"]
282 session = self._sessions[client_id]
283 if session is not None:
284 import asyncio
285
286 asyncio.create_task(session.handle_request_async(binary_msg))
287 else:
288 logging.error("No session for client_id {}".format(client_id))
289 elif content["type"] == "hangup":
290 # XXX(tom): client won't reliably send this so shouldn't rely on it
291 # to clean up; does jupyter notify us when the client on the
292 # websocket, i.e. the view, disconnects?
293 client_id = content["client_id"]
294 logging.debug("view {} hangup", client_id)
295 session = self._sessions.pop(client_id, None)
296 if session:
297 session.close()
298
299 def _repr_mimebundle_(self, **kwargs):
300 super_bundle = super(DOMWidget, self)._repr_mimebundle_(**kwargs)
301 if not _jupyter_html_export_enabled():
302 return super_bundle
303
304 # Serialize viewer attrs + view data to be rendered in the template
305 viewer_attrs = self.save()
306 data = self.table.view().to_arrow()
307 b64_data = base64.encodebytes(data)
308 template_path = os.path.join(
309 os.path.dirname(__file__), "../templates/exported_widget.html.template"
310 )
311 with open(template_path, "r") as template_data:
312 template = Template(template_data.read())
313
314 def psp_cdn(module, path=None):
315 if path is None:
316 path = f"cdn/{module}.js"
317
318 # perspective developer affordance: works with your local `pnpm run start blocks`
319 # return f"http://localhost:8080/node_modules/@perspective-dev/{module}/dist/{path}"
320 return f"https://cdn.jsdelivr.net/npm/@perspective-dev/{module}@{__version__}/dist/{path}"
321
322 return super(DOMWidget, self)._repr_mimebundle_(**kwargs) | {
323 "text/html": template.substitute(
324 psp_cdn_perspective=psp_cdn("perspective"),
325 psp_cdn_perspective_viewer=psp_cdn("perspective-viewer"),
326 psp_cdn_perspective_viewer_datagrid=psp_cdn(
327 "perspective-viewer-datagrid"
328 ),
329 psp_cdn_perspective_viewer_d3fc=psp_cdn("perspective-viewer-d3fc"),
330 psp_cdn_perspective_viewer_themes=psp_cdn(
331 "perspective-viewer-themes", "css/themes.css"
332 ),
333 viewer_id=self.model_id,
334 viewer_attrs=viewer_attrs,
335 b64_data=b64_data.decode("utf-8"),
336 )
337 }
338
339
340def _jupyter_html_export_enabled():
341 return os.environ.get("PSP_JUPYTER_HTML_EXPORT", None) == "1"
342
343
344def set_jupyter_html_export(val):
345 """Enables HTML export for Jupyter widgets, when set to True.
346 HTML export can also be enabled by setting the environment variable
347 `PSP_JUPYTER_HTML_EXPORT` to the string `1`.
348 """
349 os.environ["PSP_JUPYTER_HTML_EXPORT"] = "1" if val else "0"
class
PerspectiveWidget(ipywidgets.widgets.domwidget.DOMWidget, perspective.widget.viewer.viewer.PerspectiveViewer):
View Source
94class PerspectiveWidget(DOMWidget, PerspectiveViewer):
95 """`PerspectiveWidget` allows for Perspective to be used as a Jupyter
96 widget.
97
98 Using `perspective.Table`, you can create a widget that extends the full
99 functionality of `perspective-viewer`. Changes on the viewer can be
100 programatically set on the `PerspectiveWidget` instance.
101
102 # Examples
103
104 >>> from perspective.widget import PerspectiveWidget
105 >>> data = {
106 ... "a": [1, 2, 3],
107 ... "b": [
108 ... "2019/07/11 7:30PM",
109 ... "2019/07/11 8:30PM",
110 ... "2019/07/11 9:30PM"
111 ... ]
112 ... }
113 >>> widget = PerspectiveWidget(
114 ... data,
115 ... group_by=["a"],
116 ... sort=[["b", "desc"]],
117 ... filter=[["a", ">", 1]]
118 ... )
119 >>> widget.sort
120 [["b", "desc"]]
121 >>> widget.sort.append(["a", "asc"])
122 >>> widget.sort
123 [["b", "desc"], ["a", "asc"]]
124 >>> widget.table.update({"a": [4, 5]}) # Browser UI updates
125 """
126
127 # Required by ipywidgets for proper registration of the backend
128 _model_name = Unicode("PerspectiveModel").tag(sync=True)
129 _model_module = Unicode("@perspective-dev/jupyterlab").tag(sync=True)
130 _model_module_version = Unicode("~{}".format(__version__)).tag(sync=True)
131 _view_name = Unicode("PerspectiveView").tag(sync=True)
132 _view_module = Unicode("@perspective-dev/jupyterlab").tag(sync=True)
133 _view_module_version = Unicode("~{}".format(__version__)).tag(sync=True)
134
135 def __init__(
136 self,
137 data,
138 index=None,
139 limit=None,
140 binding_mode="server",
141 **kwargs,
142 ):
143 """Initialize an instance of `PerspectiveWidget`
144 with the given table/data and viewer configuration.
145
146 If an `AsyncTable` is passed in, then certain widget methods like
147 `update()` and `delete()` return coroutines which must be awaited.
148
149 # Arguments
150
151 - `data` (`Table`|`AsyncTable`|`dict`|`list`|`pandas.DataFrame`|`bytes`|`str`): a
152 `perspective.Table` instance, a `perspective.AsyncTable` instance, or
153 a dataset to be loaded in the widget.
154
155 # Keyword Arguments
156
157 - `index` (`str`): A column name to be used as the primary key.
158 Ignored if `server` is True.
159 - `binding_mode` (`str`): "client-server" or "server"
160 - `limit` (`int`): A upper limit on the number of rows in the Table.
161 Cannot be set at the same time as `index`, ignored if `server`
162 is True.
163 - `kwargs` (`dict`): configuration options for the `PerspectiveViewer`,
164 and `Table` constructor if `data` is a dataset.
165
166 # Examples
167
168 >>> widget = PerspectiveWidget(
169 ... {"a": [1, 2, 3]},
170 ... aggregates={"a": "avg"},
171 ... group_by=["a"],
172 ... sort=[["b", "desc"]],
173 ... filter=[["a", ">", 1]],
174 ... expressions=["\"a\" + 100"])
175 """
176
177 self.binding_mode = binding_mode
178
179 # Pass table load options to the front-end, unless in server mode
180 self._options = {}
181
182 if index is not None and limit is not None:
183 raise TypeError("Index and Limit cannot be set at the same time!")
184
185 # Parse the dataset we pass in - if it's Pandas, preserve pivots
186 # if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series):
187 # data, config = deconstruct_pandas(data)
188
189 # if config.get("group_by", None) and "group_by" not in kwargs:
190 # kwargs.update({"group_by": config["group_by"]})
191
192 # if config.get("split_by", None) and "split_by" not in kwargs:
193 # kwargs.update({"split_by": config["split_by"]})
194
195 # if config.get("columns", None) and "columns" not in kwargs:
196 # kwargs.update({"columns": config["columns"]})
197
198 # Initialize the viewer
199 super(PerspectiveWidget, self).__init__(**kwargs)
200
201 # Handle messages from the the front end
202 self.on_msg(self.handle_message)
203 self._sessions = {}
204
205 # If an empty dataset is provided, don't call `load()` and wait
206 # for the user to call `load()`.
207 if data is None:
208 if index is not None or limit is not None:
209 raise TypeError(
210 "Cannot initialize PerspectiveWidget `index` or `limit` without a Table, data, or schema!"
211 )
212 else:
213 if index is not None:
214 self._options.update({"index": index})
215
216 if limit is not None:
217 self._options.update({"limit": limit})
218
219 loading = self.load(data, **self._options)
220 if inspect.isawaitable(loading):
221 import asyncio
222
223 asyncio.create_task(loading)
224
225 def load(self, data, **options):
226 """Load the widget with data."""
227 # Viewer will ignore **options if `data` is a Table or View.
228 return super(PerspectiveWidget, self).load(data, **options)
229
230 def update(self, data):
231 """Update the widget with new data."""
232 return super(PerspectiveWidget, self).update(data)
233
234 def clear(self):
235 """Clears the widget's underlying `Table`."""
236 return super(PerspectiveWidget, self).clear()
237
238 def replace(self, data):
239 """Replaces the widget's `Table` with new data conforming to the same
240 schema. Does not clear user-set state. If in client mode, serializes
241 the data and sends it to the browser.
242 """
243 return super(PerspectiveWidget, self).replace(data)
244
245 def delete(self, delete_table=True):
246 """Delete the Widget's data and clears its internal state.
247
248 # Arguments
249
250 - `delete_table` (`bool`): whether the underlying `Table` will be
251 deleted. Defaults to True.
252 """
253 ret = super(PerspectiveWidget, self).delete(delete_table)
254
255 # Close the underlying comm and remove widget from the front-end
256 self.close()
257 return ret
258
259 @observe("value")
260 def handle_message(self, widget, content, buffers):
261 """Given a message from `PerspectiveJupyterClient.send`, process the
262 message and return the result to `self.post`.
263
264 # Arguments
265
266 - `widget`: a reference to the `Widget` instance that received the
267 message.
268 - `content` (dict): - the message from the front-end. Automatically
269 de-serialized by ipywidgets.
270 - `buffers`: optional arraybuffers from the front-end, if any.
271 """
272 if content["type"] == "connect":
273 client_id = content["client_id"]
274 logging.debug("view {} connected", client_id)
275
276 def send_response(msg):
277 self.send({"type": "binary_msg", "client_id": client_id}, [msg])
278
279 self._sessions[client_id] = self.new_proxy_session(send_response)
280 elif content["type"] == "binary_msg":
281 [binary_msg] = buffers
282 client_id = content["client_id"]
283 session = self._sessions[client_id]
284 if session is not None:
285 import asyncio
286
287 asyncio.create_task(session.handle_request_async(binary_msg))
288 else:
289 logging.error("No session for client_id {}".format(client_id))
290 elif content["type"] == "hangup":
291 # XXX(tom): client won't reliably send this so shouldn't rely on it
292 # to clean up; does jupyter notify us when the client on the
293 # websocket, i.e. the view, disconnects?
294 client_id = content["client_id"]
295 logging.debug("view {} hangup", client_id)
296 session = self._sessions.pop(client_id, None)
297 if session:
298 session.close()
299
300 def _repr_mimebundle_(self, **kwargs):
301 super_bundle = super(DOMWidget, self)._repr_mimebundle_(**kwargs)
302 if not _jupyter_html_export_enabled():
303 return super_bundle
304
305 # Serialize viewer attrs + view data to be rendered in the template
306 viewer_attrs = self.save()
307 data = self.table.view().to_arrow()
308 b64_data = base64.encodebytes(data)
309 template_path = os.path.join(
310 os.path.dirname(__file__), "../templates/exported_widget.html.template"
311 )
312 with open(template_path, "r") as template_data:
313 template = Template(template_data.read())
314
315 def psp_cdn(module, path=None):
316 if path is None:
317 path = f"cdn/{module}.js"
318
319 # perspective developer affordance: works with your local `pnpm run start blocks`
320 # return f"http://localhost:8080/node_modules/@perspective-dev/{module}/dist/{path}"
321 return f"https://cdn.jsdelivr.net/npm/@perspective-dev/{module}@{__version__}/dist/{path}"
322
323 return super(DOMWidget, self)._repr_mimebundle_(**kwargs) | {
324 "text/html": template.substitute(
325 psp_cdn_perspective=psp_cdn("perspective"),
326 psp_cdn_perspective_viewer=psp_cdn("perspective-viewer"),
327 psp_cdn_perspective_viewer_datagrid=psp_cdn(
328 "perspective-viewer-datagrid"
329 ),
330 psp_cdn_perspective_viewer_d3fc=psp_cdn("perspective-viewer-d3fc"),
331 psp_cdn_perspective_viewer_themes=psp_cdn(
332 "perspective-viewer-themes", "css/themes.css"
333 ),
334 viewer_id=self.model_id,
335 viewer_attrs=viewer_attrs,
336 b64_data=b64_data.decode("utf-8"),
337 )
338 }
PerspectiveWidget allows for Perspective to be used as a Jupyter
widget.
Using perspective.Table, you can create a widget that extends the full
functionality of perspective-viewer. Changes on the viewer can be
programatically set on the PerspectiveWidget instance.
Examples
>>> from perspective.widget import PerspectiveWidget
>>> data = {
... "a": [1, 2, 3],
... "b": [
... "2019/07/11 7:30PM",
... "2019/07/11 8:30PM",
... "2019/07/11 9:30PM"
... ]
... }
>>> widget = PerspectiveWidget(
... data,
... group_by=["a"],
... sort=[["b", "desc"]],
... filter=[["a", ">", 1]]
... )
>>> widget.sort
[["b", "desc"]]
>>> widget.sort.append(["a", "asc"])
>>> widget.sort
[["b", "desc"], ["a", "asc"]]
>>> widget.table.update({"a": [4, 5]}) # Browser UI updates
PerspectiveWidget(data, index=None, limit=None, binding_mode='server', **kwargs)
View Source
135 def __init__(
136 self,
137 data,
138 index=None,
139 limit=None,
140 binding_mode="server",
141 **kwargs,
142 ):
143 """Initialize an instance of `PerspectiveWidget`
144 with the given table/data and viewer configuration.
145
146 If an `AsyncTable` is passed in, then certain widget methods like
147 `update()` and `delete()` return coroutines which must be awaited.
148
149 # Arguments
150
151 - `data` (`Table`|`AsyncTable`|`dict`|`list`|`pandas.DataFrame`|`bytes`|`str`): a
152 `perspective.Table` instance, a `perspective.AsyncTable` instance, or
153 a dataset to be loaded in the widget.
154
155 # Keyword Arguments
156
157 - `index` (`str`): A column name to be used as the primary key.
158 Ignored if `server` is True.
159 - `binding_mode` (`str`): "client-server" or "server"
160 - `limit` (`int`): A upper limit on the number of rows in the Table.
161 Cannot be set at the same time as `index`, ignored if `server`
162 is True.
163 - `kwargs` (`dict`): configuration options for the `PerspectiveViewer`,
164 and `Table` constructor if `data` is a dataset.
165
166 # Examples
167
168 >>> widget = PerspectiveWidget(
169 ... {"a": [1, 2, 3]},
170 ... aggregates={"a": "avg"},
171 ... group_by=["a"],
172 ... sort=[["b", "desc"]],
173 ... filter=[["a", ">", 1]],
174 ... expressions=["\"a\" + 100"])
175 """
176
177 self.binding_mode = binding_mode
178
179 # Pass table load options to the front-end, unless in server mode
180 self._options = {}
181
182 if index is not None and limit is not None:
183 raise TypeError("Index and Limit cannot be set at the same time!")
184
185 # Parse the dataset we pass in - if it's Pandas, preserve pivots
186 # if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series):
187 # data, config = deconstruct_pandas(data)
188
189 # if config.get("group_by", None) and "group_by" not in kwargs:
190 # kwargs.update({"group_by": config["group_by"]})
191
192 # if config.get("split_by", None) and "split_by" not in kwargs:
193 # kwargs.update({"split_by": config["split_by"]})
194
195 # if config.get("columns", None) and "columns" not in kwargs:
196 # kwargs.update({"columns": config["columns"]})
197
198 # Initialize the viewer
199 super(PerspectiveWidget, self).__init__(**kwargs)
200
201 # Handle messages from the the front end
202 self.on_msg(self.handle_message)
203 self._sessions = {}
204
205 # If an empty dataset is provided, don't call `load()` and wait
206 # for the user to call `load()`.
207 if data is None:
208 if index is not None or limit is not None:
209 raise TypeError(
210 "Cannot initialize PerspectiveWidget `index` or `limit` without a Table, data, or schema!"
211 )
212 else:
213 if index is not None:
214 self._options.update({"index": index})
215
216 if limit is not None:
217 self._options.update({"limit": limit})
218
219 loading = self.load(data, **self._options)
220 if inspect.isawaitable(loading):
221 import asyncio
222
223 asyncio.create_task(loading)
Initialize an instance of PerspectiveWidget
with the given table/data and viewer configuration.
If an AsyncTable is passed in, then certain widget methods like
update() and delete() return coroutines which must be awaited.
Arguments
data (Table|AsyncTable|dict|list|pandas.DataFrame|bytes|str): a
perspective.Table instance, a perspective.AsyncTable instance, or
a dataset to be loaded in the widget.
Keyword Arguments
index (str): A column name to be used as the primary key.
Ignored if server is True.
binding_mode (str): "client-server" or "server"
limit (int): A upper limit on the number of rows in the Table.
Cannot be set at the same time as index, ignored if server
is True.
kwargs (dict): configuration options for the PerspectiveViewer,
and Table constructor if data is a dataset.
Examples
>>> widget = PerspectiveWidget(
... {"a": [1, 2, 3]},
... aggregates={"a": "avg"},
... group_by=["a"],
... sort=[["b", "desc"]],
... filter=[["a", ">", 1]],
... expressions=[""a" + 100"])
binding_mode
An enum whose value must be in a given sequence.
def
load(self, data, **options):
View Source
225 def load(self, data, **options):
226 """Load the widget with data."""
227 # Viewer will ignore **options if `data` is a Table or View.
228 return super(PerspectiveWidget, self).load(data, **options)
Load the widget with data.
def
update(self, data):
View Source
230 def update(self, data):
231 """Update the widget with new data."""
232 return super(PerspectiveWidget, self).update(data)
Update the widget with new data.
def
clear(self):
View Source
234 def clear(self):
235 """Clears the widget's underlying `Table`."""
236 return super(PerspectiveWidget, self).clear()
Clears the widget's underlying Table.
def
replace(self, data):
View Source
238 def replace(self, data):
239 """Replaces the widget's `Table` with new data conforming to the same
240 schema. Does not clear user-set state. If in client mode, serializes
241 the data and sends it to the browser.
242 """
243 return super(PerspectiveWidget, self).replace(data)
Replaces the widget's Table with new data conforming to the same
schema. Does not clear user-set state. If in client mode, serializes
the data and sends it to the browser.
def
delete(self, delete_table=True):
View Source
245 def delete(self, delete_table=True):
246 """Delete the Widget's data and clears its internal state.
247
248 # Arguments
249
250 - `delete_table` (`bool`): whether the underlying `Table` will be
251 deleted. Defaults to True.
252 """
253 ret = super(PerspectiveWidget, self).delete(delete_table)
254
255 # Close the underlying comm and remove widget from the front-end
256 self.close()
257 return ret
Delete the Widget's data and clears its internal state.
Arguments
delete_table (bool): whether the underlying Table will be
deleted. Defaults to True.
def
handle_message(unknown):
The type of the None singleton.
|