Skip to content

Commit aa77764

Browse files
author
Tim Shawver
committed
New "grouped" style for Dataframes with a multi-index. Don't show column name for unnamed index columns.
1 parent caaa0cc commit aa77764

4 files changed

Lines changed: 136 additions & 8 deletions

File tree

js/src/qgrid.css

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,11 +322,56 @@
322322

323323
.q-grid .slick-cell.idx-col {
324324
font-weight: bold;
325+
border-right: 1px solid rgb(225, 232, 237);
326+
margin-right: 3px;
327+
}
328+
329+
.q-grid .idx-col.group-top {
330+
border-bottom-color: transparent;
331+
background-color: #FFF;
332+
}
333+
334+
.q-grid .idx-col.group-middle {
335+
border-top-color: transparent;
336+
border-bottom-color: transparent;
337+
color: transparent;
338+
background-color: #FFF;
339+
}
340+
341+
.q-grid .idx-col.group-bottom {
342+
border-top-color: transparent;
343+
color: transparent;
344+
background-color: #FFF;
345+
}
346+
347+
.q-grid .idx-col.group-single {
348+
background-color: transparent;
325349
}
326350

327351
.q-grid .slick-cell.l0.r0 {
328352
border-left: none;
329353
padding-left: 6px;
354+
z-index: 90;
355+
}
356+
357+
.q-grid .slick-cell.l1.r1 {
358+
z-index: 89;
359+
}
360+
361+
.q-grid .slick-cell.l2.r2 {
362+
z-index: 88;
363+
}
364+
365+
.q-grid .slick-cell.l3.r3 {
366+
z-index: 87;
367+
}
368+
369+
.q-grid .slick-cell.l4.r4 {
370+
z-index: 86;
371+
}
372+
373+
.q-grid .slick-cell.l5.r5 {
374+
z-index: 85;
330375
}
331376

332377
.q-grid .slick-cell.selected {
@@ -641,5 +686,6 @@ input.bool-filter-radio {
641686
}
642687

643688
.slick-row .slick-cell:not(:first-child) {
644-
padding-left: 4px;
689+
padding-left: 5px;
690+
margin-left: -4px;
645691
}

js/src/qgrid.widget.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ class QgridView extends widgets.DOMWidgetView {
204204
this.data_view = this.create_data_view(df_json.data);
205205
this.grid_options = this.model.get('grid_options');
206206
this.index_col_name = this.model.get("_index_col_name");
207+
this.row_styles = this.model.get("_row_styles");
207208

208209
this.columns = [];
209210
this.index_columns = [];
@@ -346,6 +347,8 @@ class QgridView extends widgets.DOMWidgetView {
346347
if (cur_column.is_index) {
347348
slick_column.editor = editors.IndexEditor;
348349
slick_column.cssClass += ' idx-col';
350+
slick_column.name = cur_column.index_display_text;
351+
slick_column.level = cur_column.level;
349352
this.index_columns.push(slick_column);
350353
continue;
351354
}
@@ -386,6 +389,7 @@ class QgridView extends widgets.DOMWidgetView {
386389
}, 1);
387390

388391
this.slick_grid.setSelectionModel(new Slick.RowSelectionModel());
392+
this.slick_grid.setCellCssStyles("grouping", this.row_styles);
389393
this.slick_grid.render();
390394

391395
var render_header_cell = (e, args) => {
@@ -469,7 +473,7 @@ class QgridView extends widgets.DOMWidgetView {
469473
};
470474
this.send(msg);
471475
this.viewport_timeout = null;
472-
}, 100);
476+
}, 10);
473477
});
474478

475479
// set up callbacks
@@ -631,6 +635,7 @@ class QgridView extends widgets.DOMWidgetView {
631635
}
632636
this.update_timeout = setTimeout(() => {
633637
var df_json = JSON.parse(this.model.get('_df_json'));
638+
this.row_styles = this.model.get("_row_styles");
634639
var data_view = this.create_data_view(df_json.data);
635640

636641
if (msg.triggered_by == 'sort_changed' && this.sort_indicator){
@@ -649,6 +654,7 @@ class QgridView extends widgets.DOMWidgetView {
649654
}
650655

651656
this.set_data_view(data_view);
657+
this.slick_grid.setCellCssStyles("grouping", this.row_styles);
652658
this.slick_grid.render();
653659

654660
if ((msg.triggered_by == 'add_row' ||
@@ -674,7 +680,7 @@ class QgridView extends widgets.DOMWidgetView {
674680
'rows': selected_rows,
675681
'type': 'selection_changed'
676682
});
677-
}, 100);
683+
}, 10);
678684
} else if (msg.col_info) {
679685
var filter = this.filters[msg.col_info.name];
680686
filter.handle_msg(msg);

qgrid/grid.py

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@ class QgridWidget(widgets.DOMWidget):
470470
_df = Instance(pd.DataFrame)
471471
_df_json = Unicode('', sync=True)
472472
_primary_key = List()
473+
_primary_key_display = Dict({})
474+
_row_styles = Dict({}, sync=True)
475+
_disable_grouping = Bool(False)
473476
_columns = Dict({}, sync=True)
474477
_filter_tables = Dict({})
475478
_sorted_column_cache = Dict({})
@@ -762,17 +765,21 @@ def should_be_stringified(col_series):
762765
for idx, cur_level in enumerate(df.index.levels):
763766
if cur_level.name:
764767
col_name = cur_level.name
768+
self._primary_key_display[col_name] = col_name
765769
else:
766770
col_name = 'level_%s' % idx
771+
self._primary_key_display[col_name] = ""
767772
self._primary_key.append(col_name)
768773
if should_be_stringified(cur_level):
769774
self._string_columns.append(col_name)
770775
else:
771776
self._multi_index = False
772777
if df.index.name:
773778
col_name = df.index.name
779+
self._primary_key_display[col_name] = col_name
774780
else:
775781
col_name = 'index'
782+
self._primary_key_display[col_name] = ""
776783
self._primary_key = [col_name]
777784

778785
if should_be_stringified(df.index):
@@ -786,10 +793,50 @@ def should_be_stringified(col_series):
786793
series_to_set = df[sort_column_name]
787794
else:
788795
series_to_set = self._get_col_series_from_df(
789-
col_name, df
796+
col_name, df, level_vals=True
790797
).map(str)
791798
self._set_col_series_on_df(col_name, df, series_to_set)
792799

800+
if type(df.index) == pd.core.index.MultiIndex and \
801+
not self._disable_grouping:
802+
previous_value = None
803+
row_styles = {}
804+
row_styles_idx = 0
805+
for index, row in df.iterrows():
806+
row_style = {}
807+
row_loc = self._df.index.get_loc(index)
808+
last_row = row_loc == (len(self._df) - 1)
809+
prev_idx = row_loc - 1
810+
for idx, index_val in enumerate(index):
811+
col_name = self._primary_key[idx]
812+
if previous_value == None:
813+
row_style[col_name] = 'group-top'
814+
continue
815+
elif index_val == previous_value[idx]:
816+
if prev_idx < 0:
817+
row_style[col_name] = 'group-top'
818+
continue
819+
if row_styles[prev_idx][col_name] == 'group-top':
820+
row_style[col_name] = 'group-middle'
821+
elif row_styles[prev_idx][col_name] == 'group-bottom':
822+
row_style[col_name] = 'group-top'
823+
else:
824+
row_style[col_name] = 'group-middle'
825+
else:
826+
row_style[col_name] = 'single' if last_row else 'group-top'
827+
if prev_idx >= 0:
828+
if row_styles[prev_idx][col_name] == 'group-middle':
829+
row_styles[prev_idx][col_name] = 'group-bottom'
830+
elif row_styles[prev_idx][col_name] == 'group-top':
831+
row_styles[prev_idx][col_name] = 'group-single'
832+
previous_value = index
833+
row_styles[row_loc] = row_style
834+
row_styles_idx += 1
835+
836+
self._row_styles = row_styles
837+
else:
838+
self._row_styles = {}
839+
793840
df_json = pd_json.to_json(None, df,
794841
orient='table',
795842
date_format='iso',
@@ -825,6 +872,10 @@ def should_be_stringified(col_series):
825872

826873
if col_name in self._primary_key:
827874
cur_column['is_index'] = True
875+
cur_column['index_display_text'] = \
876+
self._primary_key_display[col_name]
877+
if len(self._primary_key) > 0:
878+
cur_column['level'] = self._primary_key.index(col_name)
828879

829880
cur_column['position'] = i
830881
columns[col_name] = cur_column
@@ -836,7 +887,9 @@ def should_be_stringified(col_series):
836887
# json that has interval columns replaced with text columns
837888
if len(self._interval_columns) > 0:
838889
for col_name in self._interval_columns:
839-
col_series = self._get_col_series_from_df(col_name, df)
890+
col_series = self._get_col_series_from_df(col_name,
891+
df,
892+
level_vals=True)
840893
col_series_as_strings = col_series.map(lambda x: str(x))
841894
self._set_col_series_on_df(col_name, df,
842895
col_series_as_strings)
@@ -850,7 +903,7 @@ def should_be_stringified(col_series):
850903
series_to_set = df[sort_column_name]
851904
else:
852905
series_to_set = self._get_col_series_from_df(
853-
col_name, df
906+
col_name, df, level_vals=True
854907
).to_timestamp()
855908
self._set_col_series_on_df(col_name, df, series_to_set)
856909

@@ -882,6 +935,7 @@ def _update_sort(self):
882935
try:
883936
if self._sort_field is None:
884937
return
938+
self._disable_grouping = False
885939
if self._sort_field in self._primary_key:
886940
if len(self._primary_key) == 1:
887941
self._df.sort_index(
@@ -890,19 +944,23 @@ def _update_sort(self):
890944
)
891945
else:
892946
level_id = self._sort_field
947+
level_index = self._primary_key.index(level_id)
893948
if self._sort_field.startswith('level_'):
894949
level_id = int(self._sort_field[6:])
895950
self._df.sort_index(
896951
level=level_id,
897952
ascending=self._sort_ascending,
898953
inplace=True
899954
)
955+
if level_index > 0:
956+
self._disable_grouping = True
900957
else:
901958
self._df.sort_values(
902959
self._sort_field,
903960
ascending=self._sort_ascending,
904961
inplace=True
905962
)
963+
self._disable_grouping = True
906964
except TypeError:
907965
self.log.info('TypeError occurred, assuming mixed data type '
908966
'column')
@@ -1114,15 +1172,18 @@ def get_value_from_filter_table(k):
11141172
})
11151173

11161174
# get any column from a dataframe, including index columns
1117-
def _get_col_series_from_df(self, col_name, df):
1175+
def _get_col_series_from_df(self, col_name, df, level_vals=False):
11181176
sort_column_name = self._sort_helper_columns.get(col_name)
11191177
if sort_column_name:
11201178
return df[sort_column_name]
11211179

11221180
if col_name in self._primary_key:
11231181
if len(self._primary_key) > 1:
11241182
key_index = self._primary_key.index(col_name)
1125-
return df.index.levels[key_index]
1183+
if level_vals:
1184+
return df.index.levels[key_index]
1185+
1186+
return df.index.get_level_values(key_index)
11261187
else:
11271188
return df.index
11281189
else:

qgrid/tests/test_grid.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,17 @@ def test_multi_index():
382382
}
383383
})
384384

385+
widget._handle_qgrid_msg_helper({
386+
'type': 'filter_changed',
387+
'field': 'level_1',
388+
'filter_info': {
389+
'field': 'level_1',
390+
'type': 'text',
391+
'selected': [0],
392+
'excluded': []
393+
}
394+
})
395+
385396
widget._handle_qgrid_msg_helper({
386397
'type': 'sort_changed',
387398
'sort_field': 3,
@@ -411,6 +422,10 @@ def test_multi_index():
411422
'name': 'filter_changed',
412423
'column': 3
413424
},
425+
{
426+
'name': 'filter_changed',
427+
'column': 'level_1'
428+
},
414429
{
415430
'name': 'sort_changed',
416431
'old': {

0 commit comments

Comments
 (0)