Skip to content

Commit 734302a

Browse files
refactor: rewrite interactive table widget in Angular (#17416)
This PR rewrites the frontend implementation of the interactive `TableWidget` in Angular, replacing the legacy vanilla JS version (`table_widget.js`). This enables more structured state management and paves the way for future UI improvements. ### Key Changes: 1. **Angular Frontend Rewrite**: - Created a new Angular application workspace under `bigframes/display/table_widget_angular`. - Introduced `WidgetStateService` to manage the widget's internal state (pages, sorting, column visibility) and synchronize updates with the Python `anywidget` model. - Rewrote the template, CSS variables (including VS Code dark mode support), pagination controls, and column sorting handlers as Angular components. - Compiled and bundled the Angular workspace into the single distribution file `table_widget_angular.js`, which is now loaded by the Python class. 2. **Backend Refactoring**: - Renamed `DataFrame._get_display_df` to `_process_display_df` and refactored it to return both the processed display DataFrame and its metadata. - Updated `html.py` to standardize representation rendering pipelines, enabling native rendering support for both `DataFrame` and `Series` objects. 3. **Robust Multi-Instance Bootstrapping**: - Configured the Angular bootstrap sequence to use `createApplication()` and manually attach each component instance to its local widget container (`el`). This prevents rendering conflicts and state bleeding when rendering multiple widgets on the same notebook page. 4. **Testing**: - Added a frontend test suite (`tests/js/table_widget_angular.test.js`) to assert that multiple Angular widgets can bootstrap and render distinct model configurations concurrently. - Updated Python backend unit tests under `tests/unit/display/` to conform to the new `_process_display_df` interface. Verified at: go/scrcast/NTkzNjEzNzYyNDM1NDgxNnw4NTI0NjA5My1iMA screen/4NJKSkYEjoYjpxA Fixes #<505414691> 🦕 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent b254faf commit 734302a

15 files changed

Lines changed: 4922 additions & 2490 deletions

File tree

packages/bigframes/bigframes/dataframe.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,7 @@ def __repr__(self) -> str:
819819
column_count=len(self.columns),
820820
)
821821

822-
def _get_display_df(self) -> DataFrame:
822+
def _prepare_display_df(self) -> DataFrame:
823823
"""Process ObjectRef and JSON/nested JSON columns for display."""
824824
df = self
825825
# Arrow/Pandas to_pandas_batches does not support raw JSON/nested JSON

packages/bigframes/bigframes/display/anywidget.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ def _on_initial_load_complete(self, change: dict[str, Any]):
175175

176176
@functools.cached_property
177177
def _esm(self):
178-
"""Load JavaScript code from external file."""
179-
return resources.read_text(bigframes.display, "table_widget.js")
178+
"""Load JavaScript code from the compiled Angular hybrid bundle."""
179+
return resources.read_text(bigframes.display, "table_widget_angular.js")
180180

181181
@functools.cached_property
182182
def _css(self):

packages/bigframes/bigframes/display/html.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import bigframes.formatting_helpers as formatter
3131
from bigframes._config import display_options, options
3232
from bigframes.display import plaintext
33-
from bigframes.series import Series
3433

3534
if typing.TYPE_CHECKING:
3635
import bigframes.dataframe
@@ -192,9 +191,11 @@ def create_html_representation(
192191
total_columns: int,
193192
) -> str:
194193
"""Create an HTML representation of the DataFrame or Series."""
194+
import bigframes.series
195+
195196
opts = options.display
196197
with display_options.pandas_repr(opts):
197-
if isinstance(obj, Series):
198+
if isinstance(obj, bigframes.series.Series):
198199
pd_series = pandas_df.iloc[:, 0]
199200
try:
200201
html_string = pd_series._repr_html_()
@@ -216,7 +217,9 @@ def create_html_representation(
216217
def _get_obj_metadata(
217218
obj: Union[bigframes.dataframe.DataFrame, bigframes.series.Series],
218219
) -> tuple[bool, bool]:
219-
is_series = isinstance(obj, Series)
220+
import bigframes.series
221+
222+
is_series = isinstance(obj, bigframes.series.Series)
220223
if is_series:
221224
has_index = len(obj._block.index_columns) > 0
222225
else:
@@ -233,9 +236,15 @@ def get_anywidget_bundle(
233236
Helper method to create and return the anywidget mimebundle.
234237
This function encapsulates the logic for anywidget display.
235238
"""
239+
import bigframes.series
236240
from bigframes import display
237241

238-
df = obj._get_display_df()
242+
if isinstance(obj, bigframes.series.Series):
243+
df = obj.to_frame()
244+
else:
245+
df = obj
246+
247+
df = df._prepare_display_df()
239248

240249
widget = display.TableWidget(df)
241250
widget_repr_result = widget._repr_mimebundle_(include=include, exclude=exclude)
@@ -283,8 +292,15 @@ def repr_mimebundle_deferred(
283292
def repr_mimebundle_head(
284293
obj: Union[bigframes.dataframe.DataFrame, bigframes.series.Series],
285294
) -> dict[str, str]:
295+
import bigframes.series
296+
286297
opts = options.display
287-
df = obj._get_display_df()
298+
if isinstance(obj, bigframes.series.Series):
299+
df = obj.to_frame()
300+
else:
301+
df = obj
302+
303+
df = df._prepare_display_df()
288304
pandas_df, row_count, query_job = df._block.retrieve_repr_request_results(
289305
opts.max_rows
290306
)

0 commit comments

Comments
 (0)