1

I am creating a word search app using Dash by Plotly - I have seen some other similar questions to mine out there, but none seem to hit my direct point. I want to have a user enter a query into a Dash object, in my case a dcc.Input, and have that input create a DataFrame (or a dt.DataTable if someone can explain how to further manipulate those properly). Most the examples on Dash's website have a pre-built DataFrame, if not pre-built, no examples show an @app.callback creating a DataFrame.

So... step by step where I am

  1. Here is my app.layout. I want to pass an input that creates a DataFrame/table. Then, pass that resulting table to some graphs (starting with one for simplicity).
app.layout = html.Div([
        html.H2('Enter a text query'),
        html.H6('Searching multiple words will create an AND statement where \
                \n |valve leak| will return records with valve and leak. Or, \
                \n you can use " " to search for specific phrases like "valve leak".'),
        dcc.Input(id='searchId', value='Enter Search Term', type='text'),
        html.Button('Submit', id='button', n_clicks=0),
        dcc.Graph(id='tableGraph', figure='fig'),
        html.Button('Update Graph', id='graph', n_clicks=0),
        dt.DataTable(style_cell={
                'whiteSpace': 'normal',
                'height': 'auto',
                'textAlign': 'left'
                }, id='queryTable',

                )
        ])
  1. Here is the first search callback. Right now, I am attempting to use a global df to 'export' the DataFrame from the function. A problem is that Dash does not really allow DataFrame returns (or does it? not really sure how to extract my search DataFrame). This does output the table properly via data, columns
@app.callback(
    [Output(component_id='queryTable', component_property='data'), 
     Output(component_id='queryTable', component_property='columns')],        
    [Input(component_id='button', component_property='n_clicks')],
    [State('searchId', 'value')]
)    
        
def update_Frame(n_clicks, value):
    if n_clicks > 0:
        with index.searcher() as searcher:
            parser = QueryParser("content", index.schema)
            myquery = parser.parse(value)
            results = searcher.search(myquery, limit=None)
            #print(results[0:10])
            print("Documents Containing ", value, ": ", len(results), "\n")
            
            global df
            df = pd.DataFrame([i['date'], i['site'], i['ticket'], i.score, i['docId'],i['content']] for i in results)
            df.columns=['Reported Date', 'Site','Ticket ID', 'Score', 'Document ID', 'Content']
            
            columns = [{'name': col, 'id': col} for col in df.columns]
            data = df.to_dict(orient='records')
            return data, columns
  1. Now, if I had the DataFrame, I would pass it to another callback to manipulate and create figures. My attempt is to assign the global df in a new callback, but that does not work.
@app.callback(
        Output(component_id='tableGraph', component_property='figure'),
        [Input(component_id='graph', component_property='n_clicks')]
        )


def updateFig(n_clicks):
    if n_clicks > 0:
        frame = df
        frame = frame.sort_values(by='Reported Date')
        #fig = px.line(df, x='Reported Date', y='Score', title=value)
            
        frame['Avg'] = frame['Score'].rolling(window=10).mean()

            
        # Test
            
        abc = frame.loc[frame['Site'] =='ABC']
            
        # real
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=abc['Reported Date'], y=abc['Score'],
                mode='markers', 
                marker_color='BLUE',
                name='ABC', 
                text="Site: " + abc['Site'].map(str) + " " + "Ticket: "+ abc['Ticket ID'].map(str)))

        # There is a good bit more of figure trace stuff here, but I am shortening it. 
     
        print(fig)
        return fig

It seems that Python is recognizing the correct frame, and when I print fig the console shows what looks to be the correct Dash object. However, no figure appears on the actual test website. My main question is: How can I pass a variable to a Dash object and ultimately a callback to create an initial DataFrame to pass to further Dash objects?

Thank you for reading a long question

2 Answers 2

4

You could use dcc.Store. The dcc.Store component works like a session based storage. For your case you would have two callbacks then.

First define the Store component in your Frontend section:

dcc.Store(id='memory')

The first callback where you output the genereated data into the dcc.Store component.

@app.callback(Output('memory', 'data'), [Input('button', 'n_clicks')])

The second callback where you fetch the data from the storage to show graphs/plots or anything else

@app.callback(Output('queryTable', 'data'), [Input('memory', 'data')])
Sign up to request clarification or add additional context in comments.

1 Comment

Hmm I definitely like the idea and haven't seen dcc.Store yet. I will have to play around a bit more with it, but I am afraid some of the larger possible DataFrames may be over the limit.
0

If I understand correctly, your user input from dcc.Input is used as a search query and that generates your main dataframe lets say op_df.

Edit: Not sure of what exactly you are generating in your df, but a psuedo code to give you some pointers:

def generate_df(user_input):
  created_dict = {'col': user_input, 'value': user_input * 3}
  op_df = pd.DataFrame(created_dict)
  return op_df

Now to display this op_df, you can make use of plotly graph_object's `dataTables. Here is the official documentation for dataTables. As an example, in your layout part, you would have :

dcc.Graph(
  id='main_table',
  figure=mn_table,
  style = {'width':'50%', 'height':'30%'} #
)

And you can then generate mn_table as:

mn_table = go.Figure(data=[go.Table(
 header=dict(fill_color='white', line_color='black'),
 cells=dict(values=[op_df['Col_name'], op_df['Values']],
     fill_color='white',
     align='left',
     font_size=16,
     line_color='black',
     height=25
 ))
])

Later in the callback you can pass in the user input and call the function(generate_df) that calculates or generates your op_df. Edit2: Psuedo code for callback:

@app.callback(Output('main_table', 'figure'),
              [Input('user_ip', 'value')]
def refresh_df(user_input):
   new_table = generate_df(user_input)
   return new_table

6 Comments

I do agree that global df is probably not the best route. However, I do not fully understand your answer. I do not understand how you are passing the 'returned op_df` to the next object, in your case a figure. The search must create the DataFrame first. Then, take that DataFrame and pass it to other object. I can create all the tables and graphs I want from there.
I've edited my answer to give you an idea of a function to generate your df. Let me know if you need more clarification or help.
An @app.callback does not allow for a DataFrame to be returned though.. Raises InvalidCallbackReturnValue. User must input something into a Dash object and return a usable passable DataFrame
No callback should return whatever it is that your Output of the callback is expecting. Added another snippet in my answer.
Yes, I understand that. That is the problem, in the initial search, a user will enter a term into the dcc.Input via button. n_clicks requires a callback output which cannot be a DataFrame. How can I create a DataFrame from a input value and n_clicks? Forget about the tables a graphs.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.