Open Lucid-Will opened 7 months ago
Thanks for reporting this issue.
"Export to CSV" and "Export to Excel" don't take selections into account.
The exported data is available in grid.grid_data_out
after calling get_selected_rows()
(which doesn't have a return value). There seems to be an issue in get_selected_rows()
, which doesn't set the row data in grid._grid_data_up
. I have to look into this.
It does work for get_selected_columns()
(result ingrid.grid_data_out['range"]
) and get_grid()
(result in grid.grid_data_out['cols"]
).
Sure thing. Let me know if/how I can assist. I have a perfect use-case for using this functionality.
Cheers.
@mariobuikhuizen I have a use case too :). I am trying to build a Solara app that renders an ipyaggrid that can crossfilter other Solara components/charts upon rows selection, as shown in this Dash example . But I can't get it to work and I think the issue is due to grid_data_out being one step delayed (async) relative to row selections. I would hate to have to switch to Dash so looking forward to a solution!
Cheers
Can you provide a minimal code example?
Sure, here is a simple Solara app where you can select rows from an ipyaggrid
and display them in a Solara.Dataframe
on the side.
from typing import cast
import ipyaggrid
import solara
@solara.component
def AgGridCrossFiltering(df, grid_options):
dff = solara.use_reactive(df)
grid_data_out = solara.use_reactive({})
def set_grid_data_out():
grid = cast(ipyaggrid.Grid, solara.get_widget(el))
grid_data_out.set(grid.grid_data_out)
print(grid.grid_data_out)
def on_click():
# hack to force a 'dummy' change in dff
some_col = df.columns[0]
all_vals = df[some_col].unique().tolist()
dff.set(df.query(f'{some_col}.isin({all_vals})'))
with solara.Columns([1,1]):
with solara.Card('This is my AG Grid with the data to select from'):
solara.Markdown('Select IDs then `Confirm Selection`. Check printed `grid_data_out` in logs')
with solara.Row():
solara.Button('Confirm Selection', on_click=on_click)
el = ipyaggrid.Grid.element(
grid_data=dff.value,
grid_options=grid_options,
theme='ag-theme-balham',
export_mode='auto'
)
solara.use_effect(set_grid_data_out, [dff.value, grid_options])
with solara.Card('Selected rows will appear below'):
if 'rows' in grid_data_out.value:
solara.DataFrame(grid_data_out.value['rows'])
AgGridCrossFiltering(df, grid_options)
If you run this in a notebook you get:
This works but at the expense of the the somewhat hacky on_click
handler. Moreover, if your data has more than 1000 rows (like mine does) then you have to set export_mode='buttons'
rather than auto
, otherwise it will crash the app. But then this introduces a whole new set of buttons that we don't need and requires an extra click to Export Rows
before confirming the selection. Ideally we would simply have to click on the checkmarks and see the results instantly on the right.
Hope this helps!
You can use the on_[prop]
to respond to changes on [prop]
. If you add on_grid_data_out=grid_data_out.set
to grid.element(...
, it will work.
My use case is a bit different. Conceptually, having a grid element like this in a notebook turns the notebook itself into a console which would make managing tables incredibly easy for end users. It also opens the door for some other really cool use cases in a framework I'm building.
@Lucid-Will , Turns out get_selected_rows()
does work, I was just doing the selection wrong. You can use grid.observe(lambda change: print(change['new']), names=["grid_data_out"])
to get the change after calling get_selected_rows()
@mariobuikhuizen, apologies if I'm doing something incorrect but I tried the above and unfortunately` having the same issue.
# Sample data
data = {
'ID': [1, 2, 3, 4, 5],
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Age': [24, 30, 35, 22, 29],
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
'Subscription': ['Yes', 'No', 'Yes', 'No', 'Yes']
}
# Create DataFrame
pdf_subscription_load = pd.DataFrame(data)
# Preview DataFrame
# display(pdf_subscription_load)
# Set grid options
grid_options = {
'columnDefs': [{'headerName': col, 'field': col, 'checkboxSelection': (col == 'ID'), 'sortable': True, 'filter': True} for col in pdf_subscription_load.columns],
'enableSorting': True,
'enableFilter': True,
'enableColResize': True,
'enableRangeSelection': True,
'rowSelection': 'multiple',
}
# Define grid
grid = Grid(grid_data=pdf_subscription_load,
grid_options=grid_options,
grid_options_multi=[], # Additional grid options for multi-grid setups
columns_fit='size_to_fit',
index=False,
keep_multiindex=False,
compress_data=True,
quick_filter=True, # Enable or disable quick filtering
export_csv=True, # Enable or disable CSV export
export_excel=True, # Enable or disable Excel export
show_toggle_delete=True, # Show or hide delete toggle
show_toggle_edit=True, # Show or hide edit toggle
sync_on_edit=True, # Synchronize edits
sync_grid=True, # Synchronize the grid
paste_from_excel=False, # Allow pasting from Excel
export_mode='buttons', # Set export mode
export_to_df=True, # Export to DataFrame
hide_grid=False, # Hide or show the grid
theme='ag-theme-balham') # Theme
# Assign button
button = widgets.Button(description="Confirm Selection")
# Create on_button_click method
def on_button_click(b):
# Use the get_selected_rows method to capture selected rows
selected_rows_df = grid.get_selected_rows()
# Observe changes in the grid data
grid.observe(lambda change: print(change['new']), names=["grid_data_out"])
# Check if any rows are selected
if selected_rows_df is None:
print("No rows selected")
else:
# Assuming you have a way to display or use the selected rows DataFrame
print(selected_rows_df) # For demonstration, this will print the DataFrame to the output
# Show grid and button
display(grid)
display(button)
# Bind the callback function to the button
button.on_click(on_button_click)
For comparison, the below works as expected but doesn't look nearly as awesome as ipyaggrid or have the intended functionality.
import pandas as pd
import ipywidgets as widgets
# Sample data
data = {
'ID': [1, 2, 3, 4, 5],
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Age': [24, 30, 35, 22, 29],
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
'Subscription': ['Yes', 'No', 'Yes', 'No', 'Yes']
}
# Create DataFrame
pdf_subscription_load = pd.DataFrame(data)
# Create SelectMultiple widget
select = widgets.SelectMultiple(
options=pdf_subscription_load.values.tolist(),
rows=10,
description='Rows',
disabled=False
)
# Assign button
button = widgets.Button(description="Confirm Selection")
# Create on_button_click method
def on_button_click(b):
# Get the selected rows from the SelectMultiple widget
selected_rows = select.value
# Check if any rows are selected
if not selected_rows:
print("No rows selected")
else:
# Create a DataFrame from the selected rows
selected_rows_df = pd.DataFrame(selected_rows, columns=pdf_subscription_load.columns)
# Print the selected rows DataFrame
print(selected_rows_df)
# Show SelectMultiple widget and button
display(select)
display(button)
# Bind the callback function to the button
button.on_click(on_button_click)
Stepping through the documentation samples yields the same results. I tried all output and all return None.
def mklbl(prefix, n):
return ["%s%s" % (prefix, i) for i in range(n)]
miindex = pd.MultiIndex.from_product([mklbl('A', 4),
mklbl('B', 2),
mklbl('C', 4),
mklbl('D', 2)],
names=['RowIdx-1', 'RowIdx-2', 'RowIdx-3', 'RowIdx-4'])
index =['-'.join(col).strip() for col in miindex.values]
micolumns = pd.MultiIndex.from_tuples([('a', 'foo', 'zap'),
('a', 'foo', 'zip'),
('a', 'bar', 'zap'),
('a', 'bar', 'zip'),
('b', 'foo', 'zap'),
('b', 'foo', 'zep'),
('b', 'bah', 'zep'),
('b', 'bah', 'zyp'),
('b', 'bah', 'zap'),
],
names=['ColIdx-{}'.format(i) for i in range(1, 4)])
cols =['-'.join(col).strip() for col in micolumns.values]
data = np.arange(len(miindex) * len(micolumns), dtype=np.float64).reshape((len(miindex),len(micolumns)))
data = data.tolist()
df = pd.DataFrame(data, index=index, columns=cols).sort_index()
df = pd.DataFrame(data, index=index, columns=cols).sort_index()
df.index.name = 'UniqueRow'
df.columns.name = 'UniqueCol'
column_defs_1 = [{'field': df.index.name}] + [{'field': c} for c in df.columns[:5]]
grid_options_1 = {
'columnDefs' : column_defs_1,
'defaultColDef': {'sortable': 'true', 'filter': 'true', 'resizable': 'true'},
'enableRangeSelection': 'true',
'rowSelection': 'multiple',
}
grid1 = Grid(grid_data=df,
grid_options=grid_options_1,
quick_filter=True,
export_csv=False,
export_excel=False,
show_toggle_edit=True,
export_mode='auto',
index=True,
keep_multiindex=False,
theme='ag-theme-fresh')
grid1
print(grid1.grid_data_out.get('grid'))
print(grid1.grid_data_out.get('rows'))
print(grid1.grid_data_out.get('cols'))
print(grid1.grid_data_out.get('range'))
@mariobuikhuizen, apologies if I'm doing something incorrect but I tried the above and unfortunately` having the same issue.
No worries @Lucid-Will. I've modified your example, this should work:
# Sample data
data = {
'ID': [1, 2, 3, 4, 5],
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Age': [24, 30, 35, 22, 29],
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
'Subscription': ['Yes', 'No', 'Yes', 'No', 'Yes']
}
# Create DataFrame
pdf_subscription_load = pd.DataFrame(data)
# Preview DataFrame
# display(pdf_subscription_load)
# Set grid options
grid_options = {
'columnDefs': [{'headerName': col, 'field': col, 'checkboxSelection': (col == 'ID'), 'sortable': True, 'filter': True} for col in pdf_subscription_load.columns],
'enableSorting': True,
'enableFilter': True,
'enableColResize': True,
'enableRangeSelection': True,
'rowSelection': 'multiple',
}
# Define grid
grid = Grid(grid_data=pdf_subscription_load,
grid_options=grid_options,
grid_options_multi=[], # Additional grid options for multi-grid setups
columns_fit='size_to_fit',
index=False,
keep_multiindex=False,
compress_data=True,
quick_filter=True, # Enable or disable quick filtering
export_csv=True, # Enable or disable CSV export
export_excel=True, # Enable or disable Excel export
show_toggle_delete=True, # Show or hide delete toggle
show_toggle_edit=True, # Show or hide edit toggle
sync_on_edit=True, # Synchronize edits
sync_grid=True, # Synchronize the grid
paste_from_excel=False, # Allow pasting from Excel
export_mode='buttons', # Set export mode
export_to_df=True, # Export to DataFrame
hide_grid=False, # Hide or show the grid
theme='ag-theme-balham') # Theme
# Assign button
button = widgets.Button(description="Confirm Selection")
def on_rows_selected(change):
selected_rows_df = change["new"].get("rows")
# Check if any rows are selected
if selected_rows_df is None:
print("No rows selected")
else:
# Assuming you have a way to display or use the selected rows DataFrame
print(selected_rows_df) # For demonstration, this will print the DataFrame to the output
# Observe changes in the grid data
grid.observe(on_rows_selected, names=["grid_data_out"])
# Create on_button_click method
def on_button_click(b):
# Use the get_selected_rows method to capture selected rows
grid.get_selected_rows()
# Show grid and button
display(grid)
display(button)
# Bind the callback function to the button
button.on_click(on_button_click)
@mariobuikhuizen, I really appreciate your support on this! Unfortunately, it appears to be a limitation of the platform I'm trying to run this on (Microsoft Fabric data engineering notebook). I made a few modifications to the update you provided. I'm able to execute the following in VS Code but receive failures when trying to execute in Fabric.
I'll keep testing to see if there's a solution and report back.
Cheers, and thanks again!
#!pip install ipyaggrid -q
import pandas as pd
from ipyaggrid import Grid
import ipywidgets as widgets
# Sample data
data = {
'ID': [1, 2, 3, 4, 5],
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Age': [24, 30, 35, 22, 29],
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
'Subscription': ['Yes', 'No', 'Yes', 'No', 'Yes']
}
# Create DataFrame
pdf_subscription_load = pd.DataFrame(data)
# Preview DataFrame
# display(pdf_subscription_load)
# Set grid options
grid_options = {
'columnDefs': [{'headerName': col, 'field': col, 'checkboxSelection': (col == 'ID'), 'sortable': True, 'filter': True} for col in pdf_subscription_load.columns],
'enableSorting': True,
'enableFilter': True,
'enableColResize': True,
'enableRangeSelection': True,
'rowSelection': 'multiple',
}
# Define grid
grid = Grid(grid_data=pdf_subscription_load,
grid_options=grid_options,
grid_options_multi=[], # Additional grid options for multi-grid setups
columns_fit='size_to_fit',
index=False,
keep_multiindex=False,
compress_data=True,
quick_filter=True, # Enable or disable quick filtering
export_csv=True, # Enable or disable CSV export
export_excel=True, # Enable or disable Excel export
show_toggle_delete=True, # Show or hide delete toggle
show_toggle_edit=True, # Show or hide edit toggle
sync_on_edit=True, # Synchronize edits
sync_grid=True, # Synchronize the grid
paste_from_excel=False, # Allow pasting from Excel
export_mode='auto', # Set export mode
export_to_df=True, # Export to DataFrame
hide_grid=False, # Hide or show the grid
theme='ag-theme-balham') # Theme
# Assign button
button = widgets.Button(description="Confirm Selection")
def on_rows_selected(change):
# Set global variable
global df_selected_rows
# Capture selected rows
selected_rows = change["new"].get("rows")
# Convert selected rows to DataFrame
df_selected_rows = pd.DataFrame(selected_rows)
# Check if any rows are selected
if selected_rows is None:
print("No rows selected")
# Observe changes in the grid data
grid.observe(on_rows_selected, names=["grid_data_out"])
# Create on_button_click method
def on_button_click(b):
# Use the get_selected_rows method to capture selected rows
grid.get_selected_rows()
# Display selected rows
display(df_selected_rows)
# Show grid and button
display(grid)
display(button)
# Bind the callback function to the button
button.on_click(on_button_click)
One comment about calling display(df_selected_rows)
after grid.get_selected_rows()
: this will always be one click behind, because grid.get_selected_rows()
is called later.
One comment about calling
display(df_selected_rows)
aftergrid.get_selected_rows()
: this will always be one click behind, becausegrid.get_selected_rows()
is called later.
I've done more testing today and believe that the observer itself isn't functioning as expected in the notebook environment. I also tried in a Synapse Spark notebook. Adding back the print statements for on_row_selection yields no output.
Quite a bummer, was a pretty awesome use-case. Cheers again for your help on this. I'll keep an eye on the repo and save my testing for retrying later.
Tested code:
#!pip install ipyaggrid -q
import pandas as pd
from ipyaggrid import Grid
import ipywidgets as widgets
# Sample data
data = {
'ID': [1, 2, 3, 4, 5],
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Age': [24, 30, 35, 22, 29],
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'],
'Subscription': ['Yes', 'No', 'Yes', 'No', 'Yes']
}
# Create DataFrame
pdf_subscription_load = pd.DataFrame(data)
# Preview DataFrame
# display(pdf_subscription_load)
# Set grid options
grid_options = {
'columnDefs': [{'headerName': col, 'field': col, 'checkboxSelection': (col == 'ID'), 'sortable': True, 'filter': True} for col in pdf_subscription_load.columns],
'enableSorting': True,
'enableFilter': True,
'enableColResize': True,
'enableRangeSelection': True,
'rowSelection': 'multiple',
}
# Define grid
grid = Grid(grid_data=pdf_subscription_load,
grid_options=grid_options,
grid_options_multi=[], # Additional grid options for multi-grid setups
columns_fit='size_to_fit',
index=False,
keep_multiindex=False,
compress_data=True,
quick_filter=True, # Enable or disable quick filtering
export_csv=True, # Enable or disable CSV export
export_excel=True, # Enable or disable Excel export
show_toggle_delete=True, # Show or hide delete toggle
show_toggle_edit=True, # Show or hide edit toggle
sync_on_edit=True, # Synchronize edits
sync_grid=True, # Synchronize the grid
paste_from_excel=False, # Allow pasting from Excel
export_mode='auto', # Set export mode
export_to_df=True, # Export to DataFrame
hide_grid=False, # Hide or show the grid
theme='ag-theme-balham') # Theme
# Initialize df_selected_rows globally
df_selected_rows = pd.DataFrame()
# Assign button
button = widgets.Button(description="Confirm Selection")
def on_rows_selected(change):
# Set global variable
global df_selected_rows
# Capture selected rows
selected_rows = change["new"].get("rows")
# Convert selected rows to DataFrame
df_selected_rows = pd.DataFrame(selected_rows)
# Check if any rows are selected
if selected_rows is None:
print("No rows selected")
else:
print(selected_rows)
# Observe changes in the grid data
grid.observe(on_rows_selected, names=["grid_data_out"])
# Create on_button_click method
def on_button_click(b):
# Use the get_selected_rows method to capture selected rows
#grid.get_selected_rows()
# Display selected rows
display(df_selected_rows)
# Show grid and button
display(grid)
display(button)
# Bind the callback function to the button
button.on_click(on_button_click)
Results in Jupyter notebook in Visual Studio Code:
Results in Jupyter notebook in Synapse Spark and Fabric notebooks:
No print statements upon select:
Empty dataframe indicating observer not identifying selections:
Grid selections aren't being captured by event listener in a Jupyter notebook. I've tried in both Databricks and Microsoft Spark notebooks, neither of which seem to be working.
The grid itself is being populated. I've tested making selections on the grid by row as well as checkbox. I then tested the Export to CSV, Export Rows, Export Columns, and Export Range Data options. Export to CSV creates the CSV export but of the full grid, not the selections. The other export options do not appear to be functioning at all.
I've tried implementing with a button to call grid.get_selected_rows() and capture the selections into a dataframe but the dataframe is empty. I also tried creating a dataframe in a subsequent cell that calls grid.get_selected_rows() as well, also empty dataframe.
I've attached sample notebook code to recreate.
Sample data
Display the DataFrame
Define the grid options with range selection enabled
Create the Grid widget with configured options
Define a button widget
Define an event handler for the button click event
Attach the event handler to the button
Display the Grid widget and the button