Skip to content

Commit 38b1291

Browse files
committed
feat(display): add max columns dropdown to TableWidget
1 parent 8f3955f commit 38b1291

File tree

3 files changed

+78
-5
lines changed

3 files changed

+78
-5
lines changed

bigframes/display/anywidget.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class TableWidget(_WIDGET_BASE):
6666

6767
page = traitlets.Int(0).tag(sync=True)
6868
page_size = traitlets.Int(0).tag(sync=True)
69+
max_columns = traitlets.Int(allow_none=True, default_value=None).tag(sync=True)
6970
row_count = traitlets.Int(allow_none=True, default_value=None).tag(sync=True)
7071
table_html = traitlets.Unicode("").tag(sync=True)
7172
sort_context = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
@@ -103,10 +104,13 @@ def __init__(self, dataframe: bigframes.dataframe.DataFrame):
103104

104105
# respect display options for initial page size
105106
initial_page_size = bigframes.options.display.max_rows
107+
initial_max_columns = bigframes.options.display.max_columns
106108

107109
# set traitlets properties that trigger observers
108110
# TODO(b/462525985): Investigate and improve TableWidget UX for DataFrames with a large number of columns.
109111
self.page_size = initial_page_size
112+
self.max_columns = initial_max_columns
113+
110114
# TODO(b/469861913): Nested columns from structs (e.g., 'struct_col.name') are not currently sortable.
111115
# TODO(b/463754889): Support non-string column labels for sorting.
112116
if all(isinstance(col, str) for col in dataframe.columns):
@@ -218,6 +222,14 @@ def _validate_page_size(self, proposal: dict[str, Any]) -> int:
218222
max_page_size = 1000
219223
return min(value, max_page_size)
220224

225+
@traitlets.validate("max_columns")
226+
def _validate_max_columns(self, proposal: dict[str, Any]) -> int:
227+
"""Validate max columns to ensure it's positive or 0 (for all)."""
228+
value = proposal["value"]
229+
if value is None:
230+
return 0 # Normalize None to 0 for traitlet
231+
return max(0, value)
232+
221233
def _get_next_batch(self) -> bool:
222234
"""
223235
Gets the next batch of data from the generator and appends to cache.
@@ -348,7 +360,7 @@ def _set_table_html(self) -> None:
348360
dataframe=page_data,
349361
table_id=f"table-{self._table_id}",
350362
orderable_columns=self.orderable_columns,
351-
max_columns=bigframes.options.display.max_columns,
363+
max_columns=self.max_columns,
352364
)
353365

354366
if new_page is not None:
@@ -383,3 +395,10 @@ def _page_size_changed(self, _change: dict[str, Any]) -> None:
383395

384396
# Update the table display
385397
self._set_table_html()
398+
399+
@traitlets.observe("max_columns")
400+
def _max_columns_changed(self, _change: dict[str, Any]) -> None:
401+
"""Handler for when max columns is changed from the frontend."""
402+
if not self._initial_load_complete:
403+
return
404+
self._set_table_html()

bigframes/display/table_widget.css

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,24 @@ body[data-theme='dark'] .bigframes-widget.bigframes-widget {
117117
margin: 0 8px;
118118
}
119119

120-
.bigframes-widget .page-size {
120+
.bigframes-widget .settings {
121121
align-items: center;
122122
display: flex;
123123
flex-direction: row;
124-
gap: 4px;
124+
gap: 16px;
125125
justify-content: end;
126126
}
127127

128-
.bigframes-widget .page-size label {
128+
.bigframes-widget .page-size,
129+
.bigframes-widget .max-columns {
130+
align-items: center;
131+
display: flex;
132+
flex-direction: row;
133+
gap: 4px;
134+
}
135+
136+
.bigframes-widget .page-size label,
137+
.bigframes-widget .max-columns label {
129138
margin-right: 8px;
130139
}
131140

bigframes/display/table_widget.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const ModelProperty = {
2222
ROW_COUNT: 'row_count',
2323
SORT_CONTEXT: 'sort_context',
2424
TABLE_HTML: 'table_html',
25+
MAX_COLUMNS: 'max_columns',
2526
};
2627

2728
const Event = {
@@ -71,6 +72,10 @@ function render({ model, el }) {
7172
attributeFilter: ['class', 'data-theme', 'data-vscode-theme-kind'],
7273
});
7374

75+
// Settings controls container
76+
const settingsContainer = document.createElement('div');
77+
settingsContainer.classList.add('settings');
78+
7479
// Pagination controls
7580
const paginationContainer = document.createElement('div');
7681
paginationContainer.classList.add('pagination');
@@ -102,6 +107,32 @@ function render({ model, el }) {
102107
pageSizeInput.appendChild(option);
103108
}
104109

110+
// Max columns controls
111+
const maxColumnsContainer = document.createElement('div');
112+
maxColumnsContainer.classList.add('max-columns');
113+
const maxColumnsLabel = document.createElement('label');
114+
const maxColumnsInput = document.createElement('select');
115+
116+
maxColumnsLabel.textContent = 'Max columns:';
117+
118+
// 0 represents "All"
119+
const maxColumnOptions = [3, 5, 7, 10, 20, 0];
120+
for (const cols of maxColumnOptions) {
121+
const option = document.createElement('option');
122+
option.value = cols;
123+
option.textContent = cols === 0 ? 'All' : cols;
124+
125+
const currentMax = model.get(ModelProperty.MAX_COLUMNS);
126+
// Handle None/null from python as 0/All
127+
const currentMaxVal =
128+
currentMax === null || currentMax === undefined ? 0 : currentMax;
129+
130+
if (cols === currentMaxVal) {
131+
option.selected = true;
132+
}
133+
maxColumnsInput.appendChild(option);
134+
}
135+
105136
function updateButtonStates() {
106137
const currentPage = model.get(ModelProperty.PAGE);
107138
const pageSize = model.get(ModelProperty.PAGE_SIZE);
@@ -259,6 +290,12 @@ function render({ model, el }) {
259290
}
260291
});
261292

293+
maxColumnsInput.addEventListener(Event.CHANGE, (e) => {
294+
const newVal = Number(e.target.value);
295+
model.set(ModelProperty.MAX_COLUMNS, newVal);
296+
model.save_changes();
297+
});
298+
262299
model.on(Event.CHANGE_TABLE_HTML, handleTableHTMLChange);
263300
model.on(`change:${ModelProperty.ROW_COUNT}`, updateButtonStates);
264301
model.on(`change:${ModelProperty.ERROR_MESSAGE}`, handleErrorMessageChange);
@@ -270,11 +307,19 @@ function render({ model, el }) {
270307
paginationContainer.appendChild(prevPage);
271308
paginationContainer.appendChild(pageIndicator);
272309
paginationContainer.appendChild(nextPage);
310+
273311
pageSizeContainer.appendChild(pageSizeLabel);
274312
pageSizeContainer.appendChild(pageSizeInput);
313+
314+
maxColumnsContainer.appendChild(maxColumnsLabel);
315+
maxColumnsContainer.appendChild(maxColumnsInput);
316+
317+
settingsContainer.appendChild(maxColumnsContainer);
318+
settingsContainer.appendChild(pageSizeContainer);
319+
275320
footer.appendChild(rowCountLabel);
276321
footer.appendChild(paginationContainer);
277-
footer.appendChild(pageSizeContainer);
322+
footer.appendChild(settingsContainer);
278323

279324
el.appendChild(errorContainer);
280325
el.appendChild(tableContainer);

0 commit comments

Comments
 (0)