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"
class PerspectiveWidget(ipywidgets.widgets.domwidget.DOMWidget, perspective.widget.viewer.viewer.PerspectiveViewer):
 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
PerspectiveWidget(data, index=None, limit=None, binding_mode='server', **kwargs)
 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): 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):
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.

def update(self, 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.

def clear(self):
171    def clear(self):
172        """Clears the widget's underlying `Table`."""
173        return super(PerspectiveWidget, self).clear()

Clears the widget's underlying Table.

def replace(self, data):
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.

def delete(self, delete_table=True):
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 underlying Table will be deleted. Defaults to True.
def handle_message(unknown):

Given a message from PerspectiveJupyterClient.send, process the message and return the result to self.post.

Arguments

  • widget: a reference to the Widget 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.