PablocFonseca / streamlit-aggrid

Implementation of Ag-Grid component for Streamlit

Home Page:https://pypi.org/project/streamlit-aggrid/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Sparklines ?

dcowden opened this issue · comments

Hi, thank you for the excellent python module!

I'm trying to use AGGrid's relatively new ( October 2021) Sparklines feature:

https://ag-grid.com/javascript-data-grid/sparklines-overview/

I am using these libs, which at the time of this writing are the latest:

streamlit             1.11.1
streamlit-aggrid      0.2.3.post2

I think this simple example should yeild a grid with sparklines in the history column:

import pandas as pd
import streamlit as st
from st_aggrid import AgGrid,GridUpdateMode, GridOptionsBuilder,DataReturnMode,JsCode

def compute_simple_data():
    return pd.DataFrame({
        'name': [ 'a','b'] ,
        'history' : [ [1,2,0,0,1,0,2],[1,0,0,0,2,2,2,2,] ]})

simple_data = compute_simple_data()


gb = GridOptionsBuilder.from_dataframe(simple_data)
gb.configure_side_bar()
gb.configure_column('history', cellRenderer='agSparklineCellRenderer') 
gridOptions = gb.build()

print(gridOptions)
print(simple_data.info())
g = AgGrid(
    simple_data,
    gridOptions=gridOptions,
    allow_unsafe_jscode=True,
    enable_enterprise_modules=True
)

But it renders an empty column, like this:

image

I have a feeling that this has to do with the format of the data. pandas reports the column as an object:

Data columns (total 2 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   name     2 non-null      object
 1   history  2 non-null      object
dtypes: object(2)
memory usage: 160.0+ bytes

And Aggrid sparkline expects a numeric javascript array

I know that an attempt is being made to apply the agSparkLineCellRenderer because if we comment out the configure_column call, we get the expected result:
image

Using browser console, there are no javascript errors reported (other than the AGgrid license warning).

I also tried applying type='number' and type='numeric' in the column properties, with the same blank column behavior.

I suspect i'm missing a pretty simple data conversion issue here, or alternately it could be that the aggride version being used is older.

Help would be appreciated!

@dcowden
You can do it by JS injections (one for the data and one for the params of the sparkline)
sparkline_returned_data JS is attached to valueGetter & sparkline_params JS is attached to cellRendererParams when you configure History column
result :
image

My example (test.csv) is done on following data
name;History
a;[1,2,0,0,1,0,2]
b;[1,0,0,0,2,2,2,2]

full example code :

	sparkline_params = JsCode("""
	  function(params) {
		  return {
					sparklineOptions: {
						type: 'line',
						line: {
						  stroke: 'rgb(124, 255, 178)',
						  strokeWidth: 2,
						},
						padding: {
						  top: 5,
						  bottom: 5,
						},
						marker: {
						  size: 3,
						  shape: 'diamond',
						},
						highlightStyle: {
						  size: 10,
						},
					}
			};
	};
	""")

	sparkline_returned_data = JsCode("""
	  function(params) {
			value_to_be_parsed = params.data.History.replace(/['"]+/g, '').replace(/[\])}[{(]/g, '');
			list_of_int = value_to_be_parsed.split(',').map(function(item) {return parseInt(item);});
			return list_of_int;
	  }
	""")		
	
	test_df = pd.read_csv('test.csv',  sep=';')
	gb_test = GridOptionsBuilder.from_dataframe(test_df)  
	gb_test.configure_column('History',valueGetter=**sparkline_returned_data**, cellRenderer='agSparklineCellRenderer', cellRendererParams=**sparkline_params**)
	gridOptions_test = gb_test.build()	
	grid_response_test = AgGrid(test_df,
							gridOptions = gridOptions_test,
							height=200,
							width='100%',
							data_return_mode=DataReturnMode.FILTERED,
							update_mode=GridUpdateMode.MODEL_CHANGED,
							fit_columns_on_grid_load=True,
							allow_unsafe_jscode=True,
							enable_enterprise_modules=True,
							reload_data=True,
							key='select_grid_test', 
							theme='light')	

@Hurikaine thank you very much! After digging around, I had learned that the solution might involve a valuegetter, because it appears that the data is a string by the time it gets to the javascript layer.

The above solution works, but I was hoping to avoid having to parse a number-list encoded as a string. A simple but unsafe eval() would probably also work.

I think this is the line of code that creates the problem is:

https://github.com/PablocFonseca/streamlit-aggrid/blob/main/st_aggrid/__init__.py#L55

Here, even if the dataframe has a column with a list of numbers, it is rendered as a string. It would be ideal if it was smarter, and tested for a list, rendering a list. instead of converting to a string, which then has to be undone on the client side

following up, this sample solution uses [dangerous] eval, but illustrates another working solution:

py
import pandas as pd
import streamlit as st
from st_aggrid import AgGrid,GridUpdateMode, GridOptionsBuilder,DataReturnMode,JsCode

def compute_simple_data():
    return pd.DataFrame({
        'name': [ 'a','b'] ,
        'history' : [ [1,2,0,0,1,0,2],[1,0,0,0,2,2,2,2,] ]})

simple_data = compute_simple_data()

gb = GridOptionsBuilder.from_dataframe(simple_data)
gb.configure_side_bar()
gb.configure_column('history', cellRenderer='agSparklineCellRenderer') 
gridOptions = gb.build()

gridOptions['columnDefs'].append({
    'field': 'history2',
    'cellRenderer': 'agSparklineCellRenderer',
    'valueGetter' : 'eval(data.history)'
})


g = AgGrid(
    simple_data,
    gridOptions=gridOptions,
    allow_unsafe_jscode=True,
    enable_enterprise_modules=True
)

This is not a bug, but an enhancement.

The ideal behavior would be for __parse_row_data to detect lists, and convert them to a native list, instead of converting to a string and requiring lists to be re-parsed on the client side.

Actually the example in the question works for me with

streamlit         1.12.2
streamlit-aggrid   0.3.3

... without JS injection, evals, allow_unsafe_jscode.

In other words, the following:

import pandas as pd
import streamlit as st
from st_aggrid import AgGrid, GridOptionsBuilder

simple_data = pd.DataFrame(
    {
        "name": ["a", "b"],
        "history": [
            [1, 2, 0, 0, 1, 0, 2],
            [1, 0, 0, 0, 2, 2, 2, 2],
        ],
    }
)

gb = GridOptionsBuilder.from_dataframe(simple_data)
gb.configure_side_bar()
gb.configure_column(
    "history",
    cellRenderer="agSparklineCellRenderer",
    cellRendererParams={
        "sparklineOptions": {
            "type": "line",
            "line": {"stroke": "#91cc75", "strokeWidth": 2},
        }
    },
)
gridOptions = gb.build()

g = AgGrid(
    simple_data,
    gridOptions=gridOptions,
)

... produces

image