From 8b18239168d9319a3724a91686e04dbb8e891e68 Mon Sep 17 00:00:00 2001 From: jchuahtacc Date: Tue, 25 Oct 2016 12:31:56 -0500 Subject: [PATCH 1/5] Use jupypter-pip to install notebook extension js automatically during setup.py --- requirements.txt | 1 + setup.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1cd2f6aa..53acffb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ notebook>=4.0.0 pandas>=0.16.2 ipywidgets>=4.0.0 +jupyter-pip>=0.3.0 diff --git a/setup.py b/setup.py index 23b4fbfe..762a1185 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,12 @@ join, dirname, abspath ) +#jchuahtacc +try: + from jupyterpip import cmdclass +except: + import pip, importlib + pip.main(['install', 'jupyter-pip']); cmdclass = importlib.import_module('jupyterpip').cmdclass def read_requirements(basename): reqs_file = join(dirname(abspath(__file__)), basename) @@ -50,5 +56,6 @@ def read_requirements(basename): 'Topic :: Scientific/Engineering :: Information Analysis', ], install_requires=reqs, - url="https://github.com/quantopian/qgrid" + url="https://github.com/quantopian/qgrid", + cmdclass=cmdclass('qgrid/qgridjs'), ) From 4f42ad5ddc263717bee3d89d72485dae58ac0826 Mon Sep 17 00:00:00 2001 From: jchuahtacc Date: Tue, 25 Oct 2016 13:35:31 -0500 Subject: [PATCH 2/5] filtered trait which allows user to retrieve indices of what is currently viewable after filtered --- qgrid/grid.py | 7 ++++++- qgrid/qgridjs/qgrid.widget.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/qgrid/grid.py b/qgrid/grid.py index 7e505c30..2ce6073b 100644 --- a/qgrid/grid.py +++ b/qgrid/grid.py @@ -12,7 +12,7 @@ from IPython.html import widgets from IPython.display import display, Javascript try: - from traitlets import Unicode, Instance, Bool, Integer, Dict, List + from traitlets import Unicode, Instance, Bool, Integer, Dict, List, observe except ImportError: from IPython.utils.traitlets import ( Unicode, Instance, Bool, Integer, Dict, List @@ -233,6 +233,7 @@ class QGridWidget(widgets.DOMWidget): _multi_index = Bool(False) _selected_rows = List() + filtered = List(sync=True) df = Instance(pd.DataFrame) precision = Integer(6) grid_options = Dict(sync=True) @@ -385,3 +386,7 @@ def export(self, value=None): display_html(raw_html, raw=True) display_javascript(raw_js, raw=True) + + @observe('filtered') + def filtered_changed(self, change): + self.filtered = change['new'] diff --git a/qgrid/qgridjs/qgrid.widget.js b/qgrid/qgridjs/qgrid.widget.js index 5ad64356..09324f4d 100644 --- a/qgrid/qgridjs/qgrid.widget.js +++ b/qgrid/qgridjs/qgrid.widget.js @@ -122,6 +122,18 @@ define([path], function(widget) { grid = new this.dgrid.QGrid(this.tableDiv, df, column_types); grid.initialize_slick_grid(options); + grid.data_view.onRowCountChanged.subscribe(function(e, args) { + var items = grid.data_view.getItems(); + var indexes = [ ]; + for (var i in items) { + if (items[i].include) { + indexes.push(items[i].Index); + } + } + that.model.set('filtered', indexes); + that.model.save_changes(); + }); + // set up editing var sgrid = grid.slick_grid; var columns = sgrid.getColumns(); From 92d0fd9777e5f9229cdce5191dc68556041f7908 Mon Sep 17 00:00:00 2001 From: jchuahtacc Date: Mon, 31 Oct 2016 13:33:32 -0500 Subject: [PATCH 3/5] Use indexes of all elements as initial filtered traitlet value --- qgrid/grid.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qgrid/grid.py b/qgrid/grid.py index 2ce6073b..7c4de2ac 100644 --- a/qgrid/grid.py +++ b/qgrid/grid.py @@ -272,6 +272,8 @@ def _update_table(self): if not df.index.name: df.index.name = 'Index' + self.filtered = df.index.tolist() + if type(df.index) == pd.core.index.MultiIndex: df.reset_index(inplace=True) self._multi_index = True From a0c2b130b922d6095bee141e77eceba24d0669bd Mon Sep 17 00:00:00 2001 From: jchuahtacc Date: Mon, 31 Oct 2016 13:46:05 -0500 Subject: [PATCH 4/5] Parse filtered indices as ints --- grid.py | 397 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 grid.py diff --git a/grid.py b/grid.py new file mode 100644 index 00000000..b815eb1e --- /dev/null +++ b/grid.py @@ -0,0 +1,397 @@ +import pandas as pd +import numpy as np +import uuid +import os +import json +from numbers import Integral + +from IPython.display import display_html, display_javascript +try: + from ipywidgets import widgets +except ImportError: + from IPython.html import widgets +from IPython.display import display, Javascript +try: + from traitlets import Unicode, Instance, Bool, Integer, Dict, List, observe +except ImportError: + from IPython.utils.traitlets import ( + Unicode, Instance, Bool, Integer, Dict, List + ) + + +def template_contents(filename): + template_filepath = os.path.join( + os.path.dirname(__file__), + 'templates', + filename, + ) + with open(template_filepath) as f: + return f.read() + + +SLICK_GRID_CSS = template_contents('slickgrid.css.template') +SLICK_GRID_JS = template_contents('slickgrid.js.template') +REMOTE_URL = ("https://cdn.rawgit.com/quantopian/qgrid/" + "73eaa7adf1762f66eaf4d30ed9cbf385a7e9d9fa/qgrid/qgridjs/") +LOCAL_URL = "/nbextensions/qgridjs" + + +class _DefaultSettings(object): + + def __init__(self): + self._grid_options = { + 'fullWidthRows': True, + 'syncColumnCellResize': True, + 'forceFitColumns': True, + 'defaultColumnWidth': 150, + 'rowHeight': 28, + 'enableColumnReorder': False, + 'enableTextSelectionOnCells': True, + 'editable': True, + 'autoEdit': False + } + self._show_toolbar = False + self._remote_js = False + self._precision = None # Defer to pandas.get_option + + def set_grid_option(self, optname, optvalue): + self._grid_options[optname] = optvalue + + def set_defaults(self, show_toolbar=None, remote_js=None, precision=None, grid_options=None): + if show_toolbar is not None: + self._show_toolbar = show_toolbar + if remote_js is not None: + self._remote_js = remote_js + if precision is not None: + self._precision = precision + if grid_options is not None: + self._grid_options = grid_options + + @property + def show_toolbar(self): + return self._show_toolbar + + @property + def grid_options(self): + return self._grid_options + + @property + def remote_js(self): + return self._remote_js + + @property + def precision(self): + return self._precision or pd.get_option('display.precision') - 1 + +defaults = _DefaultSettings() + + +def set_defaults(show_toolbar=None, remote_js=None, precision=None, grid_options=None): + """ + Set the default qgrid options. The options that you can set here are the + same ones that you can pass into ``show_grid``. See the documentation + for ``show_grid`` for more information. + + Notes + ----- + This function will be useful to you if you find yourself + setting the same options every time you make a call to ``show_grid``. + Calling this ``set_defaults`` function once sets the options for the + lifetime of the kernel, so you won't have to include the same options + every time you call ``show_grid``. + + See Also + -------- + show_grid : + The function whose default behavior is changed by ``set_defaults``. + """ + defaults.set_defaults(show_toolbar, remote_js, precision, grid_options) + + +def set_grid_option(optname, optvalue): + """ + Set the default value for one of the options that gets passed into the + SlickGrid constructor. + + Parameters + ---------- + optname : str + The name of the option to set. + optvalue : object + The new value to set. + + Notes + ----- + The options you can set here are the same ones + that you can set via the ``grid_options`` parameter of the ``set_defaults`` + or ``show_grid`` functions. See the `SlickGrid documentation + `_ for the full + list of available options. + """ + defaults.grid_options[optname] = optvalue + + +def show_grid(data_frame, show_toolbar=None, remote_js=None, precision=None, grid_options=None): + """ + Main entry point for rendering DataFrames as SlickGrids. + + Parameters + ---------- + grid_options : dict + Options to use when creating javascript SlickGrid instances. See the Notes section below for + more information on the available options, as well as the default options that qgrid uses. + remote_js : bool + Whether to load slickgrid.js from a local filesystem or from a + remote CDN. Loading from the local filesystem means that SlickGrid + will function even when not connected to the internet, but grid + cells created with local filesystem loading will not render + correctly on external sharing services like NBViewer. + precision : integer + The number of digits of precision to display for floating-point + values. If unset, we use the value of + `pandas.get_option('display.precision')`. + show_toolbar : bool + Whether to show a toolbar with options for adding/removing rows and + exporting the widget to a static view. Adding/removing rows is an + experimental feature which only works with DataFrames that have an + integer index. The export feature is used to generate a copy of the + grid that will be mostly functional when rendered in nbviewer.jupyter.org + or when exported to html via the notebook's File menu. + + Notes + ----- + By default, the following options get passed into SlickGrid when + ``show_grid`` is called. See the `SlickGrid documentation + `_ for information + about these options:: + + { + 'fullWidthRows': True, + 'syncColumnCellResize': True, + 'forceFitColumns': True, + 'rowHeight': 28, + 'enableColumnReorder': False, + 'enableTextSelectionOnCells': True, + 'editable': True, + 'autoEdit': False + } + + See Also + -------- + set_defaults : Permanently set global defaults for `show_grid`. + set_grid_option : Permanently set individual SlickGrid options. + """ + + if show_toolbar is None: + show_toolbar = defaults.show_toolbar + if remote_js is None: + remote_js = defaults.remote_js + if precision is None: + precision = defaults.precision + if not isinstance(precision, Integral): + raise TypeError("precision must be int, not %s" % type(precision)) + if grid_options is None: + grid_options = defaults.grid_options + else: + options = defaults.grid_options.copy() + options.update(grid_options) + grid_options = options + if not isinstance(grid_options, dict): + raise TypeError( + "grid_options must be dict, not %s" % type(grid_options) + ) + + # create a visualization for the dataframe + grid = QGridWidget(df=data_frame, precision=precision, + grid_options=grid_options, + remote_js=remote_js) + + if show_toolbar: + add_row = widgets.Button(description="Add Row") + add_row.on_click(grid.add_row) + + rem_row = widgets.Button(description="Remove Row") + rem_row.on_click(grid.remove_row) + + export = widgets.Button(description="Export") + export.on_click(grid.export) + + display(widgets.HBox((add_row, rem_row, export)), grid) + else: + display(grid) + + +class QGridWidget(widgets.DOMWidget): + _view_module = Unicode("nbextensions/qgridjs/qgrid.widget", sync=True) + _view_name = Unicode('QGridView', sync=True) + _df_json = Unicode('', sync=True) + _column_types_json = Unicode('', sync=True) + _index_name = Unicode('') + _initialized = Bool(False) + _dirty = Bool(False) + _cdn_base_url = Unicode(LOCAL_URL, sync=True) + _multi_index = Bool(False) + _selected_rows = List() + + filtered = List(sync=True) + df = Instance(pd.DataFrame) + precision = Integer(6) + grid_options = Dict(sync=True) + remote_js = Bool(False) + + def __init__(self, *args, **kwargs): + """Initialize all variables before building the table.""" + self._initialized = False + super(QGridWidget, self).__init__(*args, **kwargs) + # register a callback for custom messages + self.on_msg(self._handle_qgrid_msg) + self._initialized = True + self._selected_rows = [] + if self.df is not None: + self._update_table() + + def _grid_options_default(self): + return defaults.grid_options + + def _remote_js_default(self): + return defaults.remote_js + + def _precision_default(self): + return defaults.precision + + def _df_changed(self): + """Build the Data Table for the DataFrame.""" + if not self._initialized: + return + self._update_table() + self.send({'type': 'draw_table'}) + + def _update_table(self): + df = self.df.copy() + + if not df.index.name: + df.index.name = 'Index' + + self.filtered = df.index.tolist() + + if type(df.index) == pd.core.index.MultiIndex: + df.reset_index(inplace=True) + self._multi_index = True + else: + df.insert(0, df.index.name, df.index) + self._multi_index = False + + self._index_name = df.index.name or 'Index' + + tc = dict(np.typecodes) + for key in np.typecodes.keys(): + if "All" in key: + del tc[key] + + column_types = [] + for col_name, dtype in df.dtypes.iteritems(): + if str(dtype) == 'category': + categories = list(df[col_name].cat.categories) + column_type = {'field': col_name, + 'categories': ','.join(categories)} + # XXXX: work around bug in to_json for categorical types + # https://github.com/pydata/pandas/issues/10778 + df[col_name] = df[col_name].astype(str) + column_types.append(column_type) + continue + column_type = {'field': col_name} + for type_name, type_codes in tc.items(): + if dtype.kind in type_codes: + column_type['type'] = type_name + break + column_types.append(column_type) + self._column_types_json = json.dumps(column_types) + + self._df_json = df.to_json( + orient='records', + date_format='iso', + double_precision=self.precision, + ) + self._cdn_base_url = REMOTE_URL if self.remote_js else LOCAL_URL + self._dirty = False + + def add_row(self, value=None): + """Append a row at the end of the dataframe.""" + df = self.df + if not df.index.is_integer(): + msg = "Cannot add a row to a table with a non-integer index" + display(Javascript('alert("%s")' % msg)) + return + last = df.iloc[-1] + last.name += 1 + df.loc[last.name] = last.values + precision = pd.get_option('display.precision') - 1 + row_data = last.to_json(date_format='iso', + double_precision=precision) + msg = json.loads(row_data) + msg[self._index_name] = str(last.name) + msg['slick_grid_id'] = str(last.name) + msg['type'] = 'add_row' + self._dirty = True + self.send(msg) + + def remove_row(self, value=None): + """Remove the current row from the table""" + if self._multi_index: + msg = "Cannot remove a row from a table with a multi index" + display(Javascript('alert("%s")' % msg)) + return + self.send({'type': 'remove_row'}) + + def _handle_qgrid_msg(self, widget, content, buffers=None): + """Handle incoming messages from the QGridView""" + if 'type' not in content: + return + if content['type'] == 'remove_row': + self.df.drop(content['row'], inplace=True) + self._dirty = True + + elif content['type'] == 'cell_change': + try: + self.df.set_value(self.df.index[content['row']], + content['column'], content['value']) + self._dirty = True + except ValueError: + pass + + elif content['type'] == 'selection_change': + self._selected_rows = content['rows'] + + def get_selected_rows(self): + """Get the currently selected rows""" + return self._selected_rows + + def export(self, value=None): + if self._dirty: + self._update_table() + base_url = REMOTE_URL + div_id = str(uuid.uuid4()) + grid_options = self.grid_options + grid_options['editable'] = False + + raw_html = SLICK_GRID_CSS.format( + div_id=div_id, + cdn_base_url=base_url, + ) + raw_js = SLICK_GRID_JS.format( + cdn_base_url=base_url, + div_id=div_id, + data_frame_json=self._df_json, + column_types_json=self._column_types_json, + options_json=json.dumps(grid_options), + ) + + display_html(raw_html, raw=True) + display_javascript(raw_js, raw=True) + + @observe('filtered') + def filtered_changed(self, change): + self.filtered = [ ] + for index in change['new']: + self.filtered.append(int(index)) + From c791f0f3f1616f0067c2aace8ca9fb388edfe501 Mon Sep 17 00:00:00 2001 From: jchuahtacc Date: Mon, 31 Oct 2016 13:46:54 -0500 Subject: [PATCH 5/5] Merged filiterlist-traitlet --- grid.py | 397 -------------------------------------------------- qgrid/grid.py | 5 +- 2 files changed, 4 insertions(+), 398 deletions(-) delete mode 100644 grid.py diff --git a/grid.py b/grid.py deleted file mode 100644 index b815eb1e..00000000 --- a/grid.py +++ /dev/null @@ -1,397 +0,0 @@ -import pandas as pd -import numpy as np -import uuid -import os -import json -from numbers import Integral - -from IPython.display import display_html, display_javascript -try: - from ipywidgets import widgets -except ImportError: - from IPython.html import widgets -from IPython.display import display, Javascript -try: - from traitlets import Unicode, Instance, Bool, Integer, Dict, List, observe -except ImportError: - from IPython.utils.traitlets import ( - Unicode, Instance, Bool, Integer, Dict, List - ) - - -def template_contents(filename): - template_filepath = os.path.join( - os.path.dirname(__file__), - 'templates', - filename, - ) - with open(template_filepath) as f: - return f.read() - - -SLICK_GRID_CSS = template_contents('slickgrid.css.template') -SLICK_GRID_JS = template_contents('slickgrid.js.template') -REMOTE_URL = ("https://cdn.rawgit.com/quantopian/qgrid/" - "73eaa7adf1762f66eaf4d30ed9cbf385a7e9d9fa/qgrid/qgridjs/") -LOCAL_URL = "/nbextensions/qgridjs" - - -class _DefaultSettings(object): - - def __init__(self): - self._grid_options = { - 'fullWidthRows': True, - 'syncColumnCellResize': True, - 'forceFitColumns': True, - 'defaultColumnWidth': 150, - 'rowHeight': 28, - 'enableColumnReorder': False, - 'enableTextSelectionOnCells': True, - 'editable': True, - 'autoEdit': False - } - self._show_toolbar = False - self._remote_js = False - self._precision = None # Defer to pandas.get_option - - def set_grid_option(self, optname, optvalue): - self._grid_options[optname] = optvalue - - def set_defaults(self, show_toolbar=None, remote_js=None, precision=None, grid_options=None): - if show_toolbar is not None: - self._show_toolbar = show_toolbar - if remote_js is not None: - self._remote_js = remote_js - if precision is not None: - self._precision = precision - if grid_options is not None: - self._grid_options = grid_options - - @property - def show_toolbar(self): - return self._show_toolbar - - @property - def grid_options(self): - return self._grid_options - - @property - def remote_js(self): - return self._remote_js - - @property - def precision(self): - return self._precision or pd.get_option('display.precision') - 1 - -defaults = _DefaultSettings() - - -def set_defaults(show_toolbar=None, remote_js=None, precision=None, grid_options=None): - """ - Set the default qgrid options. The options that you can set here are the - same ones that you can pass into ``show_grid``. See the documentation - for ``show_grid`` for more information. - - Notes - ----- - This function will be useful to you if you find yourself - setting the same options every time you make a call to ``show_grid``. - Calling this ``set_defaults`` function once sets the options for the - lifetime of the kernel, so you won't have to include the same options - every time you call ``show_grid``. - - See Also - -------- - show_grid : - The function whose default behavior is changed by ``set_defaults``. - """ - defaults.set_defaults(show_toolbar, remote_js, precision, grid_options) - - -def set_grid_option(optname, optvalue): - """ - Set the default value for one of the options that gets passed into the - SlickGrid constructor. - - Parameters - ---------- - optname : str - The name of the option to set. - optvalue : object - The new value to set. - - Notes - ----- - The options you can set here are the same ones - that you can set via the ``grid_options`` parameter of the ``set_defaults`` - or ``show_grid`` functions. See the `SlickGrid documentation - `_ for the full - list of available options. - """ - defaults.grid_options[optname] = optvalue - - -def show_grid(data_frame, show_toolbar=None, remote_js=None, precision=None, grid_options=None): - """ - Main entry point for rendering DataFrames as SlickGrids. - - Parameters - ---------- - grid_options : dict - Options to use when creating javascript SlickGrid instances. See the Notes section below for - more information on the available options, as well as the default options that qgrid uses. - remote_js : bool - Whether to load slickgrid.js from a local filesystem or from a - remote CDN. Loading from the local filesystem means that SlickGrid - will function even when not connected to the internet, but grid - cells created with local filesystem loading will not render - correctly on external sharing services like NBViewer. - precision : integer - The number of digits of precision to display for floating-point - values. If unset, we use the value of - `pandas.get_option('display.precision')`. - show_toolbar : bool - Whether to show a toolbar with options for adding/removing rows and - exporting the widget to a static view. Adding/removing rows is an - experimental feature which only works with DataFrames that have an - integer index. The export feature is used to generate a copy of the - grid that will be mostly functional when rendered in nbviewer.jupyter.org - or when exported to html via the notebook's File menu. - - Notes - ----- - By default, the following options get passed into SlickGrid when - ``show_grid`` is called. See the `SlickGrid documentation - `_ for information - about these options:: - - { - 'fullWidthRows': True, - 'syncColumnCellResize': True, - 'forceFitColumns': True, - 'rowHeight': 28, - 'enableColumnReorder': False, - 'enableTextSelectionOnCells': True, - 'editable': True, - 'autoEdit': False - } - - See Also - -------- - set_defaults : Permanently set global defaults for `show_grid`. - set_grid_option : Permanently set individual SlickGrid options. - """ - - if show_toolbar is None: - show_toolbar = defaults.show_toolbar - if remote_js is None: - remote_js = defaults.remote_js - if precision is None: - precision = defaults.precision - if not isinstance(precision, Integral): - raise TypeError("precision must be int, not %s" % type(precision)) - if grid_options is None: - grid_options = defaults.grid_options - else: - options = defaults.grid_options.copy() - options.update(grid_options) - grid_options = options - if not isinstance(grid_options, dict): - raise TypeError( - "grid_options must be dict, not %s" % type(grid_options) - ) - - # create a visualization for the dataframe - grid = QGridWidget(df=data_frame, precision=precision, - grid_options=grid_options, - remote_js=remote_js) - - if show_toolbar: - add_row = widgets.Button(description="Add Row") - add_row.on_click(grid.add_row) - - rem_row = widgets.Button(description="Remove Row") - rem_row.on_click(grid.remove_row) - - export = widgets.Button(description="Export") - export.on_click(grid.export) - - display(widgets.HBox((add_row, rem_row, export)), grid) - else: - display(grid) - - -class QGridWidget(widgets.DOMWidget): - _view_module = Unicode("nbextensions/qgridjs/qgrid.widget", sync=True) - _view_name = Unicode('QGridView', sync=True) - _df_json = Unicode('', sync=True) - _column_types_json = Unicode('', sync=True) - _index_name = Unicode('') - _initialized = Bool(False) - _dirty = Bool(False) - _cdn_base_url = Unicode(LOCAL_URL, sync=True) - _multi_index = Bool(False) - _selected_rows = List() - - filtered = List(sync=True) - df = Instance(pd.DataFrame) - precision = Integer(6) - grid_options = Dict(sync=True) - remote_js = Bool(False) - - def __init__(self, *args, **kwargs): - """Initialize all variables before building the table.""" - self._initialized = False - super(QGridWidget, self).__init__(*args, **kwargs) - # register a callback for custom messages - self.on_msg(self._handle_qgrid_msg) - self._initialized = True - self._selected_rows = [] - if self.df is not None: - self._update_table() - - def _grid_options_default(self): - return defaults.grid_options - - def _remote_js_default(self): - return defaults.remote_js - - def _precision_default(self): - return defaults.precision - - def _df_changed(self): - """Build the Data Table for the DataFrame.""" - if not self._initialized: - return - self._update_table() - self.send({'type': 'draw_table'}) - - def _update_table(self): - df = self.df.copy() - - if not df.index.name: - df.index.name = 'Index' - - self.filtered = df.index.tolist() - - if type(df.index) == pd.core.index.MultiIndex: - df.reset_index(inplace=True) - self._multi_index = True - else: - df.insert(0, df.index.name, df.index) - self._multi_index = False - - self._index_name = df.index.name or 'Index' - - tc = dict(np.typecodes) - for key in np.typecodes.keys(): - if "All" in key: - del tc[key] - - column_types = [] - for col_name, dtype in df.dtypes.iteritems(): - if str(dtype) == 'category': - categories = list(df[col_name].cat.categories) - column_type = {'field': col_name, - 'categories': ','.join(categories)} - # XXXX: work around bug in to_json for categorical types - # https://github.com/pydata/pandas/issues/10778 - df[col_name] = df[col_name].astype(str) - column_types.append(column_type) - continue - column_type = {'field': col_name} - for type_name, type_codes in tc.items(): - if dtype.kind in type_codes: - column_type['type'] = type_name - break - column_types.append(column_type) - self._column_types_json = json.dumps(column_types) - - self._df_json = df.to_json( - orient='records', - date_format='iso', - double_precision=self.precision, - ) - self._cdn_base_url = REMOTE_URL if self.remote_js else LOCAL_URL - self._dirty = False - - def add_row(self, value=None): - """Append a row at the end of the dataframe.""" - df = self.df - if not df.index.is_integer(): - msg = "Cannot add a row to a table with a non-integer index" - display(Javascript('alert("%s")' % msg)) - return - last = df.iloc[-1] - last.name += 1 - df.loc[last.name] = last.values - precision = pd.get_option('display.precision') - 1 - row_data = last.to_json(date_format='iso', - double_precision=precision) - msg = json.loads(row_data) - msg[self._index_name] = str(last.name) - msg['slick_grid_id'] = str(last.name) - msg['type'] = 'add_row' - self._dirty = True - self.send(msg) - - def remove_row(self, value=None): - """Remove the current row from the table""" - if self._multi_index: - msg = "Cannot remove a row from a table with a multi index" - display(Javascript('alert("%s")' % msg)) - return - self.send({'type': 'remove_row'}) - - def _handle_qgrid_msg(self, widget, content, buffers=None): - """Handle incoming messages from the QGridView""" - if 'type' not in content: - return - if content['type'] == 'remove_row': - self.df.drop(content['row'], inplace=True) - self._dirty = True - - elif content['type'] == 'cell_change': - try: - self.df.set_value(self.df.index[content['row']], - content['column'], content['value']) - self._dirty = True - except ValueError: - pass - - elif content['type'] == 'selection_change': - self._selected_rows = content['rows'] - - def get_selected_rows(self): - """Get the currently selected rows""" - return self._selected_rows - - def export(self, value=None): - if self._dirty: - self._update_table() - base_url = REMOTE_URL - div_id = str(uuid.uuid4()) - grid_options = self.grid_options - grid_options['editable'] = False - - raw_html = SLICK_GRID_CSS.format( - div_id=div_id, - cdn_base_url=base_url, - ) - raw_js = SLICK_GRID_JS.format( - cdn_base_url=base_url, - div_id=div_id, - data_frame_json=self._df_json, - column_types_json=self._column_types_json, - options_json=json.dumps(grid_options), - ) - - display_html(raw_html, raw=True) - display_javascript(raw_js, raw=True) - - @observe('filtered') - def filtered_changed(self, change): - self.filtered = [ ] - for index in change['new']: - self.filtered.append(int(index)) - diff --git a/qgrid/grid.py b/qgrid/grid.py index 7c4de2ac..b815eb1e 100644 --- a/qgrid/grid.py +++ b/qgrid/grid.py @@ -391,4 +391,7 @@ def export(self, value=None): @observe('filtered') def filtered_changed(self, change): - self.filtered = change['new'] + self.filtered = [ ] + for index in change['new']: + self.filtered.append(int(index)) +