perspective.widget
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 30class PerspectiveWidget(DOMWidget, PerspectiveViewer): 31 """`PerspectiveWidget` allows for Perspective to be used as a Jupyter 32 widget. 33 34 Using `perspective.Table`, you can create a widget that extends the full 35 functionality of `perspective-viewer`. Changes on the viewer can be 36 programatically set on the `PerspectiveWidget` instance. 37 38 # Examples 39 40 >>> from perspective.widget import PerspectiveWidget 41 >>> data = { 42 ... "a": [1, 2, 3], 43 ... "b": [ 44 ... "2019/07/11 7:30PM", 45 ... "2019/07/11 8:30PM", 46 ... "2019/07/11 9:30PM" 47 ... ] 48 ... } 49 >>> widget = PerspectiveWidget( 50 ... data, 51 ... group_by=["a"], 52 ... sort=[["b", "desc"]], 53 ... filter=[["a", ">", 1]] 54 ... ) 55 >>> widget.sort 56 [["b", "desc"]] 57 >>> widget.sort.append(["a", "asc"]) 58 >>> widget.sort 59 [["b", "desc"], ["a", "asc"]] 60 >>> widget.table.update({"a": [4, 5]}) # Browser UI updates 61 """ 62 63 # Required by ipywidgets for proper registration of the backend 64 _model_name = Unicode("PerspectiveModel").tag(sync=True) 65 _model_module = Unicode("@finos/perspective-jupyterlab").tag(sync=True) 66 _model_module_version = Unicode("~{}".format(__version__)).tag(sync=True) 67 _view_name = Unicode("PerspectiveView").tag(sync=True) 68 _view_module = Unicode("@finos/perspective-jupyterlab").tag(sync=True) 69 _view_module_version = Unicode("~{}".format(__version__)).tag(sync=True) 70 71 def __init__( 72 self, 73 data, 74 index=None, 75 limit=None, 76 binding_mode="server", 77 **kwargs, 78 ): 79 """Initialize an instance of `PerspectiveWidget` 80 with the given table/data and viewer configuration. 81 82 If an `AsyncTable` is passed in, then certain widget methods like 83 `update()` and `delete()` return coroutines which must be awaited. 84 85 # Arguments 86 87 - `data` (`Table`|`AsyncTable`|`dict`|`list`|`pandas.DataFrame`|`bytes`|`str`): a 88 `perspective.Table` instance, a `perspective.AsyncTable` instance, or 89 a dataset to be loaded in the widget. 90 91 # Keyword Arguments 92 93 - `index` (`str`): A column name to be used as the primary key. 94 Ignored if `server` is True. 95 - `binding_mode` (`str`): "client-server" or "server" 96 - `limit` (`int`): A upper limit on the number of rows in the Table. 97 Cannot be set at the same time as `index`, ignored if `server` 98 is True. 99 - `kwargs` (`dict`): configuration options for the `PerspectiveViewer`, 100 and `Table` constructor if `data` is a dataset. 101 102 # Examples 103 104 >>> widget = PerspectiveWidget( 105 ... {"a": [1, 2, 3]}, 106 ... aggregates={"a": "avg"}, 107 ... group_by=["a"], 108 ... sort=[["b", "desc"]], 109 ... filter=[["a", ">", 1]], 110 ... expressions=["\"a\" + 100"]) 111 """ 112 113 self.binding_mode = binding_mode 114 115 # Pass table load options to the front-end, unless in server mode 116 self._options = {} 117 118 if index is not None and limit is not None: 119 raise TypeError("Index and Limit cannot be set at the same time!") 120 121 # Parse the dataset we pass in - if it's Pandas, preserve pivots 122 # if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series): 123 # data, config = deconstruct_pandas(data) 124 125 # if config.get("group_by", None) and "group_by" not in kwargs: 126 # kwargs.update({"group_by": config["group_by"]}) 127 128 # if config.get("split_by", None) and "split_by" not in kwargs: 129 # kwargs.update({"split_by": config["split_by"]}) 130 131 # if config.get("columns", None) and "columns" not in kwargs: 132 # kwargs.update({"columns": config["columns"]}) 133 134 # Initialize the viewer 135 super(PerspectiveWidget, self).__init__(**kwargs) 136 137 # Handle messages from the the front end 138 self.on_msg(self.handle_message) 139 self._sessions = {} 140 141 # If an empty dataset is provided, don't call `load()` and wait 142 # for the user to call `load()`. 143 if data is None: 144 if index is not None or limit is not None: 145 raise TypeError( 146 "Cannot initialize PerspectiveWidget `index` or `limit` without a Table, data, or schema!" 147 ) 148 else: 149 if index is not None: 150 self._options.update({"index": index}) 151 152 if limit is not None: 153 self._options.update({"limit": limit}) 154 155 loading = self.load(data, **self._options) 156 if inspect.isawaitable(loading): 157 import asyncio 158 159 asyncio.create_task(loading) 160 161 def load(self, data, **options): 162 """Load the widget with data.""" 163 # Viewer will ignore **options if `data` is a Table or View. 164 return super(PerspectiveWidget, self).load(data, **options) 165 166 def update(self, data): 167 """Update the widget with new data.""" 168 return super(PerspectiveWidget, self).update(data) 169 170 def clear(self): 171 """Clears the widget's underlying `Table`.""" 172 return super(PerspectiveWidget, self).clear() 173 174 def replace(self, data): 175 """Replaces the widget's `Table` with new data conforming to the same 176 schema. Does not clear user-set state. If in client mode, serializes 177 the data and sends it to the browser. 178 """ 179 return super(PerspectiveWidget, self).replace(data) 180 181 def delete(self, delete_table=True): 182 """Delete the Widget's data and clears its internal state. 183 184 # Arguments 185 186 - `delete_table` (`bool`): whether the underlying `Table` will be 187 deleted. Defaults to True. 188 """ 189 ret = super(PerspectiveWidget, self).delete(delete_table) 190 191 # Close the underlying comm and remove widget from the front-end 192 self.close() 193 return ret 194 195 @observe("value") 196 def handle_message(self, widget, content, buffers): 197 """Given a message from `PerspectiveJupyterClient.send`, process the 198 message and return the result to `self.post`. 199 200 # Arguments 201 202 - `widget`: a reference to the `Widget` instance that received the 203 message. 204 - `content` (dict): - the message from the front-end. Automatically 205 de-serialized by ipywidgets. 206 - `buffers`: optional arraybuffers from the front-end, if any. 207 """ 208 if content["type"] == "connect": 209 client_id = content["client_id"] 210 logging.debug("view {} connected", client_id) 211 212 def send_response(msg): 213 self.send({"type": "binary_msg", "client_id": client_id}, [msg]) 214 215 self._sessions[client_id] = self.new_proxy_session(send_response) 216 elif content["type"] == "binary_msg": 217 [binary_msg] = buffers 218 client_id = content["client_id"] 219 session = self._sessions[client_id] 220 if session is not None: 221 import asyncio 222 223 asyncio.create_task(session.handle_request_async(binary_msg)) 224 else: 225 logging.error("No session for client_id {}".format(client_id)) 226 elif content["type"] == "hangup": 227 # XXX(tom): client won't reliably send this so shouldn't rely on it 228 # to clean up; does jupyter notify us when the client on the 229 # websocket, i.e. the view, disconnects? 230 client_id = content["client_id"] 231 logging.debug("view {} hangup", client_id) 232 session = self._sessions.pop(client_id, None) 233 if session: 234 session.close() 235 236 def _repr_mimebundle_(self, **kwargs): 237 super_bundle = super(DOMWidget, self)._repr_mimebundle_(**kwargs) 238 if not _jupyter_html_export_enabled(): 239 return super_bundle 240 241 # Serialize viewer attrs + view data to be rendered in the template 242 viewer_attrs = self.save() 243 data = self.table.view().to_arrow() 244 b64_data = base64.encodebytes(data) 245 template_path = os.path.join( 246 os.path.dirname(__file__), "../templates/exported_widget.html.template" 247 ) 248 with open(template_path, "r") as template_data: 249 template = Template(template_data.read()) 250 251 def psp_cdn(module, path=None): 252 if path is None: 253 path = f"cdn/{module}.js" 254 255 # perspective developer affordance: works with your local `pnpm run start blocks` 256 # return f"http://localhost:8080/node_modules/@finos/{module}/dist/{path}" 257 return f"https://cdn.jsdelivr.net/npm/@finos/{module}@{__version__}/dist/{path}" 258 259 return super(DOMWidget, self)._repr_mimebundle_(**kwargs) | { 260 "text/html": template.substitute( 261 psp_cdn_perspective=psp_cdn("perspective"), 262 psp_cdn_perspective_viewer=psp_cdn("perspective-viewer"), 263 psp_cdn_perspective_viewer_datagrid=psp_cdn( 264 "perspective-viewer-datagrid" 265 ), 266 psp_cdn_perspective_viewer_d3fc=psp_cdn("perspective-viewer-d3fc"), 267 psp_cdn_perspective_viewer_themes=psp_cdn( 268 "perspective-viewer-themes", "css/themes.css" 269 ), 270 viewer_id=self.model_id, 271 viewer_attrs=viewer_attrs, 272 b64_data=b64_data.decode("utf-8"), 273 ) 274 } 275 276 277def _jupyter_html_export_enabled(): 278 return os.environ.get("PSP_JUPYTER_HTML_EXPORT", None) == "1" 279 280 281def set_jupyter_html_export(val): 282 """Enables HTML export for Jupyter widgets, when set to True. 283 HTML export can also be enabled by setting the environment variable 284 `PSP_JUPYTER_HTML_EXPORT` to the string `1`. 285 """ 286 os.environ["PSP_JUPYTER_HTML_EXPORT"] = "1" if val else "0"
31class PerspectiveWidget(DOMWidget, PerspectiveViewer): 32 """`PerspectiveWidget` allows for Perspective to be used as a Jupyter 33 widget. 34 35 Using `perspective.Table`, you can create a widget that extends the full 36 functionality of `perspective-viewer`. Changes on the viewer can be 37 programatically set on the `PerspectiveWidget` instance. 38 39 # Examples 40 41 >>> from perspective.widget import PerspectiveWidget 42 >>> data = { 43 ... "a": [1, 2, 3], 44 ... "b": [ 45 ... "2019/07/11 7:30PM", 46 ... "2019/07/11 8:30PM", 47 ... "2019/07/11 9:30PM" 48 ... ] 49 ... } 50 >>> widget = PerspectiveWidget( 51 ... data, 52 ... group_by=["a"], 53 ... sort=[["b", "desc"]], 54 ... filter=[["a", ">", 1]] 55 ... ) 56 >>> widget.sort 57 [["b", "desc"]] 58 >>> widget.sort.append(["a", "asc"]) 59 >>> widget.sort 60 [["b", "desc"], ["a", "asc"]] 61 >>> widget.table.update({"a": [4, 5]}) # Browser UI updates 62 """ 63 64 # Required by ipywidgets for proper registration of the backend 65 _model_name = Unicode("PerspectiveModel").tag(sync=True) 66 _model_module = Unicode("@finos/perspective-jupyterlab").tag(sync=True) 67 _model_module_version = Unicode("~{}".format(__version__)).tag(sync=True) 68 _view_name = Unicode("PerspectiveView").tag(sync=True) 69 _view_module = Unicode("@finos/perspective-jupyterlab").tag(sync=True) 70 _view_module_version = Unicode("~{}".format(__version__)).tag(sync=True) 71 72 def __init__( 73 self, 74 data, 75 index=None, 76 limit=None, 77 binding_mode="server", 78 **kwargs, 79 ): 80 """Initialize an instance of `PerspectiveWidget` 81 with the given table/data and viewer configuration. 82 83 If an `AsyncTable` is passed in, then certain widget methods like 84 `update()` and `delete()` return coroutines which must be awaited. 85 86 # Arguments 87 88 - `data` (`Table`|`AsyncTable`|`dict`|`list`|`pandas.DataFrame`|`bytes`|`str`): a 89 `perspective.Table` instance, a `perspective.AsyncTable` instance, or 90 a dataset to be loaded in the widget. 91 92 # Keyword Arguments 93 94 - `index` (`str`): A column name to be used as the primary key. 95 Ignored if `server` is True. 96 - `binding_mode` (`str`): "client-server" or "server" 97 - `limit` (`int`): A upper limit on the number of rows in the Table. 98 Cannot be set at the same time as `index`, ignored if `server` 99 is True. 100 - `kwargs` (`dict`): configuration options for the `PerspectiveViewer`, 101 and `Table` constructor if `data` is a dataset. 102 103 # Examples 104 105 >>> widget = PerspectiveWidget( 106 ... {"a": [1, 2, 3]}, 107 ... aggregates={"a": "avg"}, 108 ... group_by=["a"], 109 ... sort=[["b", "desc"]], 110 ... filter=[["a", ">", 1]], 111 ... expressions=["\"a\" + 100"]) 112 """ 113 114 self.binding_mode = binding_mode 115 116 # Pass table load options to the front-end, unless in server mode 117 self._options = {} 118 119 if index is not None and limit is not None: 120 raise TypeError("Index and Limit cannot be set at the same time!") 121 122 # Parse the dataset we pass in - if it's Pandas, preserve pivots 123 # if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series): 124 # data, config = deconstruct_pandas(data) 125 126 # if config.get("group_by", None) and "group_by" not in kwargs: 127 # kwargs.update({"group_by": config["group_by"]}) 128 129 # if config.get("split_by", None) and "split_by" not in kwargs: 130 # kwargs.update({"split_by": config["split_by"]}) 131 132 # if config.get("columns", None) and "columns" not in kwargs: 133 # kwargs.update({"columns": config["columns"]}) 134 135 # Initialize the viewer 136 super(PerspectiveWidget, self).__init__(**kwargs) 137 138 # Handle messages from the the front end 139 self.on_msg(self.handle_message) 140 self._sessions = {} 141 142 # If an empty dataset is provided, don't call `load()` and wait 143 # for the user to call `load()`. 144 if data is None: 145 if index is not None or limit is not None: 146 raise TypeError( 147 "Cannot initialize PerspectiveWidget `index` or `limit` without a Table, data, or schema!" 148 ) 149 else: 150 if index is not None: 151 self._options.update({"index": index}) 152 153 if limit is not None: 154 self._options.update({"limit": limit}) 155 156 loading = self.load(data, **self._options) 157 if inspect.isawaitable(loading): 158 import asyncio 159 160 asyncio.create_task(loading) 161 162 def load(self, data, **options): 163 """Load the widget with data.""" 164 # Viewer will ignore **options if `data` is a Table or View. 165 return super(PerspectiveWidget, self).load(data, **options) 166 167 def update(self, data): 168 """Update the widget with new data.""" 169 return super(PerspectiveWidget, self).update(data) 170 171 def clear(self): 172 """Clears the widget's underlying `Table`.""" 173 return super(PerspectiveWidget, self).clear() 174 175 def replace(self, data): 176 """Replaces the widget's `Table` with new data conforming to the same 177 schema. Does not clear user-set state. If in client mode, serializes 178 the data and sends it to the browser. 179 """ 180 return super(PerspectiveWidget, self).replace(data) 181 182 def delete(self, delete_table=True): 183 """Delete the Widget's data and clears its internal state. 184 185 # Arguments 186 187 - `delete_table` (`bool`): whether the underlying `Table` will be 188 deleted. Defaults to True. 189 """ 190 ret = super(PerspectiveWidget, self).delete(delete_table) 191 192 # Close the underlying comm and remove widget from the front-end 193 self.close() 194 return ret 195 196 @observe("value") 197 def handle_message(self, widget, content, buffers): 198 """Given a message from `PerspectiveJupyterClient.send`, process the 199 message and return the result to `self.post`. 200 201 # Arguments 202 203 - `widget`: a reference to the `Widget` instance that received the 204 message. 205 - `content` (dict): - the message from the front-end. Automatically 206 de-serialized by ipywidgets. 207 - `buffers`: optional arraybuffers from the front-end, if any. 208 """ 209 if content["type"] == "connect": 210 client_id = content["client_id"] 211 logging.debug("view {} connected", client_id) 212 213 def send_response(msg): 214 self.send({"type": "binary_msg", "client_id": client_id}, [msg]) 215 216 self._sessions[client_id] = self.new_proxy_session(send_response) 217 elif content["type"] == "binary_msg": 218 [binary_msg] = buffers 219 client_id = content["client_id"] 220 session = self._sessions[client_id] 221 if session is not None: 222 import asyncio 223 224 asyncio.create_task(session.handle_request_async(binary_msg)) 225 else: 226 logging.error("No session for client_id {}".format(client_id)) 227 elif content["type"] == "hangup": 228 # XXX(tom): client won't reliably send this so shouldn't rely on it 229 # to clean up; does jupyter notify us when the client on the 230 # websocket, i.e. the view, disconnects? 231 client_id = content["client_id"] 232 logging.debug("view {} hangup", client_id) 233 session = self._sessions.pop(client_id, None) 234 if session: 235 session.close() 236 237 def _repr_mimebundle_(self, **kwargs): 238 super_bundle = super(DOMWidget, self)._repr_mimebundle_(**kwargs) 239 if not _jupyter_html_export_enabled(): 240 return super_bundle 241 242 # Serialize viewer attrs + view data to be rendered in the template 243 viewer_attrs = self.save() 244 data = self.table.view().to_arrow() 245 b64_data = base64.encodebytes(data) 246 template_path = os.path.join( 247 os.path.dirname(__file__), "../templates/exported_widget.html.template" 248 ) 249 with open(template_path, "r") as template_data: 250 template = Template(template_data.read()) 251 252 def psp_cdn(module, path=None): 253 if path is None: 254 path = f"cdn/{module}.js" 255 256 # perspective developer affordance: works with your local `pnpm run start blocks` 257 # return f"http://localhost:8080/node_modules/@finos/{module}/dist/{path}" 258 return f"https://cdn.jsdelivr.net/npm/@finos/{module}@{__version__}/dist/{path}" 259 260 return super(DOMWidget, self)._repr_mimebundle_(**kwargs) | { 261 "text/html": template.substitute( 262 psp_cdn_perspective=psp_cdn("perspective"), 263 psp_cdn_perspective_viewer=psp_cdn("perspective-viewer"), 264 psp_cdn_perspective_viewer_datagrid=psp_cdn( 265 "perspective-viewer-datagrid" 266 ), 267 psp_cdn_perspective_viewer_d3fc=psp_cdn("perspective-viewer-d3fc"), 268 psp_cdn_perspective_viewer_themes=psp_cdn( 269 "perspective-viewer-themes", "css/themes.css" 270 ), 271 viewer_id=self.model_id, 272 viewer_attrs=viewer_attrs, 273 b64_data=b64_data.decode("utf-8"), 274 ) 275 }
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
72 def __init__( 73 self, 74 data, 75 index=None, 76 limit=None, 77 binding_mode="server", 78 **kwargs, 79 ): 80 """Initialize an instance of `PerspectiveWidget` 81 with the given table/data and viewer configuration. 82 83 If an `AsyncTable` is passed in, then certain widget methods like 84 `update()` and `delete()` return coroutines which must be awaited. 85 86 # Arguments 87 88 - `data` (`Table`|`AsyncTable`|`dict`|`list`|`pandas.DataFrame`|`bytes`|`str`): a 89 `perspective.Table` instance, a `perspective.AsyncTable` instance, or 90 a dataset to be loaded in the widget. 91 92 # Keyword Arguments 93 94 - `index` (`str`): A column name to be used as the primary key. 95 Ignored if `server` is True. 96 - `binding_mode` (`str`): "client-server" or "server" 97 - `limit` (`int`): A upper limit on the number of rows in the Table. 98 Cannot be set at the same time as `index`, ignored if `server` 99 is True. 100 - `kwargs` (`dict`): configuration options for the `PerspectiveViewer`, 101 and `Table` constructor if `data` is a dataset. 102 103 # Examples 104 105 >>> widget = PerspectiveWidget( 106 ... {"a": [1, 2, 3]}, 107 ... aggregates={"a": "avg"}, 108 ... group_by=["a"], 109 ... sort=[["b", "desc"]], 110 ... filter=[["a", ">", 1]], 111 ... expressions=["\"a\" + 100"]) 112 """ 113 114 self.binding_mode = binding_mode 115 116 # Pass table load options to the front-end, unless in server mode 117 self._options = {} 118 119 if index is not None and limit is not None: 120 raise TypeError("Index and Limit cannot be set at the same time!") 121 122 # Parse the dataset we pass in - if it's Pandas, preserve pivots 123 # if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series): 124 # data, config = deconstruct_pandas(data) 125 126 # if config.get("group_by", None) and "group_by" not in kwargs: 127 # kwargs.update({"group_by": config["group_by"]}) 128 129 # if config.get("split_by", None) and "split_by" not in kwargs: 130 # kwargs.update({"split_by": config["split_by"]}) 131 132 # if config.get("columns", None) and "columns" not in kwargs: 133 # kwargs.update({"columns": config["columns"]}) 134 135 # Initialize the viewer 136 super(PerspectiveWidget, self).__init__(**kwargs) 137 138 # Handle messages from the the front end 139 self.on_msg(self.handle_message) 140 self._sessions = {} 141 142 # If an empty dataset is provided, don't call `load()` and wait 143 # for the user to call `load()`. 144 if data is None: 145 if index is not None or limit is not None: 146 raise TypeError( 147 "Cannot initialize PerspectiveWidget `index` or `limit` without a Table, data, or schema!" 148 ) 149 else: 150 if index is not None: 151 self._options.update({"index": index}) 152 153 if limit is not None: 154 self._options.update({"limit": limit}) 155 156 loading = self.load(data, **self._options) 157 if inspect.isawaitable(loading): 158 import asyncio 159 160 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
): aperspective.Table
instance, aperspective.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 ifserver
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 asindex
, ignored ifserver
is True.kwargs
(dict
): configuration options for thePerspectiveViewer
, andTable
constructor ifdata
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"])
162 def load(self, data, **options): 163 """Load the widget with data.""" 164 # Viewer will ignore **options if `data` is a Table or View. 165 return super(PerspectiveWidget, self).load(data, **options)
Load the widget with data.
167 def update(self, data): 168 """Update the widget with new data.""" 169 return super(PerspectiveWidget, self).update(data)
Update the widget with new data.
171 def clear(self): 172 """Clears the widget's underlying `Table`.""" 173 return super(PerspectiveWidget, self).clear()
Clears the widget's underlying Table
.
175 def replace(self, data): 176 """Replaces the widget's `Table` with new data conforming to the same 177 schema. Does not clear user-set state. If in client mode, serializes 178 the data and sends it to the browser. 179 """ 180 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.
182 def delete(self, delete_table=True): 183 """Delete the Widget's data and clears its internal state. 184 185 # Arguments 186 187 - `delete_table` (`bool`): whether the underlying `Table` will be 188 deleted. Defaults to True. 189 """ 190 ret = super(PerspectiveWidget, self).delete(delete_table) 191 192 # Close the underlying comm and remove widget from the front-end 193 self.close() 194 return ret
Delete the Widget's data and clears its internal state.
Arguments
delete_table
(bool
): whether the underlyingTable
will be deleted. Defaults to True.
Given a message from PerspectiveJupyterClient.send
, process the
message and return the result to self.post
.
Arguments
widget
: a reference to theWidget
instance that received the message.content
(dict): - the message from the front-end. Automatically de-serialized by ipywidgets.buffers
: optional arraybuffers from the front-end, if any.