Need help with connecting ag grid to graph

I’ve been going through some documentation for about 2 hours and I could not get the checkboxes to trigger the callback. This is what I have so far:
@app.callback(
Output(‘central-graph’, ‘figure’),
Input(‘dash-griddy’, ‘selectedRows’) #do i need virtualRows also for input?
)
def update_graph(selected_row):
ticker = selected_row[0][‘ticker’] # ±First selected row’s ticker
df = get_fred_data(ticker) # Fetches data from API

# I need to create a function that takes the ticker, calls get_fred_data('ticker), then change that variable['date'] for x value, 
#variable['value'] for y ^^^ what above should do 


color_map = {
    'WPUFD432': 'red',
    'PCU23821X23821X': 'orange',
    'WPUIP231120': 'yellow',
    'WPU4532': 'green',
    'WPU1322':'blue',
}
line_color = color_map.get(ticker, 'black')  # Get color- black by default 

# now put ticker 

figure={
            'data': [
                {
                    'x': df['date'],         
                    'y': df['value'],
                    'type': 'line',
                    'name': 'Construction For Government',
                    'line': {'width': 3},
                    'hovertemplate': '<br><b>%{x}: </b>%{y:.2f}<extra></extra>',
                }
            ],
            'layout': {
                'title': 'Construction For Government',
                'xaxis': {'title': 'Date', 'hoverformat': ''},
                'yaxis': {'title': 'PPI', 'hoverformat': ''},
                "colorway": [line_color],
                "hovermode": "x",
                'hoverlabel': {
                    'bgcolor': "white",
                    'font_size': 25,
                    'font_family': "Helvetica"
                },
            }
        },
return figure

CONSTRUCTION4GVMT_df = get_fred_data(‘WPUFD432’)
ELECRICALCONTRACTORSNONRESBUILDINGWORK_df = get_fred_data(‘PCU23821X23821X’)
MULTIFAM_df = get_fred_data(‘WPUIP231120’)
ENGRSERVICES_df = get_fred_data(‘WPU4532’)
CEMENT_df = get_fred_data(‘WPU1322’)

Layout to be Displayed on app.py container

layout = html.Div(
[
#graph starts blank, so when the callback updates plots are put on it
dcc.Graph(
figure={
‘data’: [
{
‘x’: CONSTRUCTION4GVMT_df[‘date’],
‘y’: CONSTRUCTION4GVMT_df[‘value’],
‘line’: {‘width’: 3}, #keep this
‘hovertemplate’: ‘
%{x}: %{y:.2f}’, #keep this
}
],

change normal layout here ^ put None for values above or comment it out

‘layout’: {
‘title’: ‘Universal Graph Title’,
‘xaxis’: {‘title’: ‘Date’, ‘hoverformat’: ‘’},
‘yaxis’: {‘title’: ‘PPI’, ‘hoverformat’: ‘’},
“colorway”: [‘black’],
“hovermode”: “x”,
‘hoverlabel’: {
‘bgcolor’: “white”,
‘font_size’: 25,
‘font_family’: “Helvetica”
},
}
},
id=‘central-graph’,
className=“graph-container”
),

    #data grid
    dag.AgGrid(
        id="dash-griddy", #use this id for callbacks to update the graph when check boxes are selected
        columnDefs=column_defs,
        rowData=row_data,
        columnSize= "responsiveSizeToFit",
        defaultColDef={"filter": True},
        # getRowStyle=getRowStyle,       +- why does this not work??
        dashGridOptions={
        "rowSelection": "multiple",
        'suppressRowClickSelection': True,
         'suppressMovableColumns': True,
        },
    ),
],

)

hi @samorouji
Can you please provide a complete and reproducible code example? something that we can run directly locally on our computer. Right now, as written, I can’t use your code and it’s hard to understand.

Here’s the whole code (took out my api key):

import dash
from dash import dcc, html, dash_table, callback, Input, Output
import plotly.express as px
import pandas as pd  # Data science library for data manipulation with data frames
import requests
from markupsafe import escape
import dash_ag_grid as dag
from dash.dependencies import Input, Output, State

from dash_app import app  # Import the Dash app instance (prevents circular imports)



#Fred API Calls (Restful API structure)
# +- create an api call that gets updated data PPI and calculates the YoY change from exactly one year ago so this dashboard has accurate info
API_KEY = 'blurred out'
def get_fred_data(series_id):
    url = f'https://api.stlouisfed.org/fred/series/observations?series_id={series_id}&api_key={API_KEY}&file_type=json'
    response = requests.get(url)
    if response.status_code != 200:
        print(f"Failed to fetch data from FRED API. Status Code: {response.status_code}")
        return pd.DataFrame()  # Return an empty DataFrame
    data = response.json()
    if 'observations' not in data:
        print("No 'observations' key in API response.")
        print(data)  # Print the full API response to see its structure
        return pd.DataFrame()  # Return an empty DataFrame
    # pandas with dataframes -- study up but this is the basics i need for now
    df = pd.DataFrame(data['observations'])
    df['date'] = pd.to_datetime(df['date'])
    df['value'] = pd.to_numeric(df['value'])
    return df



# indices and categories
indices = [
    {'label': 'Construction For Government', 'value': 'WPUFD432'},
    {'label': 'Electrical Contractors Non-Residential', 'value': 'PCU23821X23821X'},
    {'label': 'Multi Family', 'value': 'WPUIP231120'},
    {'label': 'Engineering Services', 'value': 'WPU4532'},
    {'label': 'Cement', 'value': 'WPU1322'},
]
# kinda useless right now - not organizing by dropdown 
category = ['Consumer, Producer & Construction prices', 
           'new, repair & maintenance work by subcontractors', 
           'inputs to construction industries',
           'services important to construction',
           'processed goods important to construction',
           'unprocessed goods important to construction',
           'total compensation, wages & salaries']




#grid Columns and Rows
column_defs = [
    {'headerName':"Category", 'field':'category', 'sortable':True, 'filter':True,"checkboxSelection": True, "headerCheckboxSelection": True,},
    {'headerName':"Indice", 'field':'indice', 'sortable':True, 'filter':True},
    {'headerName':"Ticker", 'field':'ticker', 'sortable':True, 'filter':True},
    {'headerName':"PPI", 'field':'ppi', 'sortable':True, 'filter':True},
    { 'headerName': "YOY%", 'field': "yoy%", 'sortable': True, 'filter': True},
    {'headerName':"Date", 'field':'date', 'sortable':True, 'filter':True, "filter": "agDateColumnFilter", 
     "filterValueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"}, "filterParams": {
                                                                                                        "browserDatePicker": True,
                                                                                                        "minValidYear": 2000,
                                                                                                        "maxValidYear": 2021,
                                                                                                      },
    },
]


row_data = [
    {'category':"Consumer, Producer & Construction prices", "indice":"Construction For Government", "ticker":"WPUFD432", "ppi":167.184, "yoy%":-0.75, "date":"1/24/2024"},
    {'category':"new, repair & maintenance work by subcontractors", "indice":"Electrical Contractors Non-Residential", "ticker":"PCU23821X23821X", "ppi":167.129, "yoy%":-4.70, "date":"1/24/2024"},
    {'category':"inputs to construction industries", "indice":"Multi Family", "ticker":"WPUIP231120", "ppi":149.403, "yoy%":2.09, "date":"1/24/2024"},
    {'category':"services important to construction", "indice":"Engineering Services", "ticker":"WPU4532", "ppi":136.993, "yoy%":1.98, "date":"1/25/2024"},
    {'category':"processed goods important to construction", "indice":"Cement", "ticker":"WPU1322", "ppi":351.768, "yoy%":8.17, "date":"1/24/2024"},
    {'category':"Consumer, Producer & Construction prices", "indice":"Construction For Government", "ticker":"WPUFD432", "ppi":167.184, "yoy%":-0.75, "date":"1/24/2024"},
    {'category':"new, repair & maintenance work by subcontractors", "indice":"Electrical Contractors Non-Residential", "ticker":"PCU23821X23821X", "ppi":167.129, "yoy%":-4.70, "date":"1/24/2024"},
    {'category':"inputs to construction industries", "indice":"Multi Family", "ticker":"WPUIP231120", "ppi":149.403, "yoy%":2.09, "date":"1/24/2024"},
    {'category':"services important to construction", "indice":"Engineering Services", "ticker":"WPU4532", "ppi":136.993, "yoy%":1.98, "date":"1/25/2024"},
    {'category':"processed goods important to construction", "indice":"Cement", "ticker":"WPU1322", "ppi":351.768, "yoy%":8.17, "date":"1/24/2024"},
    {'category':"Consumer, Producer & Construction prices", "indice":"Construction For Government", "ticker":"WPUFD432", "ppi":167.184, "yoy%":-0.75, "date":"1/24/2024"},
    {'category':"new, repair & maintenance work by subcontractors", "indice":"Electrical Contractors Non-Residential", "ticker":"PCU23821X23821X", "ppi":167.129, "yoy%":-4.70, "date":"1/24/2024"},
    {'category':"inputs to construction industries", "indice":"Multi Family", "ticker":"WPUIP231120", "ppi":149.403, "yoy%":2.09, "date":"1/24/2024"},
    {'category':"services important to construction", "indice":"Engineering Services", "ticker":"WPU4532", "ppi":136.993, "yoy%":1.98, "date":"1/25/2024"},
    {'category':"processed goods important to construction", "indice":"Cement", "ticker":"WPU1322", "ppi":351.768, "yoy%":8.17, "date":"1/24/2024"},
]

# +-function for row color change for yoy% change. If positive green, negative red. 
getRowStyle = {
    "styleConditions": [
        {
            "condition": "params.data.yoy% > 0",
            "style": {"backgroundColor": "green"},
        },
        {
            "condition": "params.data.yoy% < 0",
            "style": {"backgroundColor": "red"},
        },
    ],
    "defaultStyle": {"backgroundColor": "grey", "color": "white"},
}



@app.callback(
    Output('central-graph', 'figure'), 
    Input('dash-griddy', 'selectedRows') #do i need virtualRows also for input? 
)
def update_graph(selected_row):
    ticker = selected_row[0]['ticker']  # +-First selected row's ticker
    df = get_fred_data(ticker)  # Fetches data from API 

    # I need to create a function that takes the ticker, calls get_fred_data('ticker), then change that variable['date'] for x value, 
    #variable['value'] for y ^^^ what above should do 

    
    color_map = {
        'WPUFD432': 'red',
        'PCU23821X23821X': 'orange',
        'WPUIP231120': 'yellow',
        'WPU4532': 'green',
        'WPU1322':'blue',
    }
    line_color = color_map.get(ticker, 'black')  # Get color- black by default 

    # now put ticker 

    figure={
                'data': [
                    {
                        'x': df['date'],         
                        'y': df['value'],
                        'type': 'line',
                        'name': 'Construction For Government',
                        'line': {'width': 3},
                        'hovertemplate': '<br><b>%{x}: </b>%{y:.2f}<extra></extra>',
                    }
                ],
                'layout': {
                    'title': 'Construction For Government',
                    'xaxis': {'title': 'Date', 'hoverformat': ''},
                    'yaxis': {'title': 'PPI', 'hoverformat': ''},
                    "colorway": [line_color],
                    "hovermode": "x",
                    'hoverlabel': {
                        'bgcolor': "white",
                        'font_size': 25,
                        'font_family': "Helvetica"
                    },
                }
            },
    return figure








CONSTRUCTION4GVMT_df = get_fred_data('WPUFD432')
ELECRICALCONTRACTORSNONRESBUILDINGWORK_df = get_fred_data('PCU23821X23821X')
MULTIFAM_df = get_fred_data('WPUIP231120')
ENGRSERVICES_df = get_fred_data('WPU4532')
CEMENT_df = get_fred_data('WPU1322')





# Layout to be Displayed on app.py container
layout = html.Div(
    [
        #graph starts blank, so when the callback updates plots are put on it 
        dcc.Graph(
            figure={
                'data': [
                    {
                        'x': CONSTRUCTION4GVMT_df['date'],         
                        'y': CONSTRUCTION4GVMT_df['value'],
                        'line': {'width': 3}, #keep this 
                        'hovertemplate': '<br><b>%{x}: </b>%{y:.2f}<extra></extra>', #keep this 
                    }
                ],
                # change normal layout here ^ put None for values above or comment it out 
                'layout': {
                    'title': 'Universal Graph Title',
                    'xaxis': {'title': 'Date', 'hoverformat': ''},
                    'yaxis': {'title': 'PPI', 'hoverformat': ''},
                    "colorway": ['black'],
                    "hovermode": "x",
                    'hoverlabel': {
                        'bgcolor': "white",
                        'font_size': 25,
                        'font_family': "Helvetica"
                    },
                }
            },
            id='central-graph',
            className="graph-container"
        ),

        #data grid
        html.Div(
            dag.AgGrid(
            id="dash-griddy", #use this id for callbacks to update the graph when check boxes are selected
            columnDefs=column_defs,
            rowData=row_data,
            columnSize= "responsiveSizeToFit",
            defaultColDef={"filter": True},
            # getRowStyle=getRowStyle,       +- why does this not work??
            dashGridOptions={
            "rowSelection": "multiple",
            'suppressRowClickSelection': True,
             'suppressMovableColumns': True,
            },
        ),
            className="ag-grid-div"
        )
        
    ],
)




#initialize this page
dash.register_page(__name__)

Can you help me out. I’d appreciate you looking through my code i can’t figure it out currently.

Hey @samorouji,

as @adamschroeder already menitoned, could you create a small example app which reproduces your issue instead of posting the full code of your app?

We cant just copy&paste the code you provided to reproduce your issue.

import dash
from dash import dcc, html, dash_table, callback, Input, Output
import plotly.express as px
import pandas as pd  # Data science library for data manipulation with data frames
import requests
from markupsafe import escape
import dash_ag_grid as dag
from dash.dependencies import Input, Output, State



#Fred API Calls (Restful API structure)
# +- create an api call that gets updated data PPI and calculates the YoY change from exactly one year ago so this dashboard has accurate info
API_KEY = 'blurred out'
def get_fred_data(series_id):
    url = f'https://api.stlouisfed.org/fred/series/observations?series_id={series_id}&api_key={API_KEY}&file_type=json'
    response = requests.get(url)
    if response.status_code != 200:
        print(f"Failed to fetch data from FRED API. Status Code: {response.status_code}")
        return pd.DataFrame()  # Return an empty DataFrame
    data = response.json()
    if 'observations' not in data:
        print("No 'observations' key in API response.")
        print(data)  # Print the full API response to see its structure
        return pd.DataFrame()  # Return an empty DataFrame
    # pandas with dataframes -- study up but this is the basics i need for now
    df = pd.DataFrame(data['observations'])
    df['date'] = pd.to_datetime(df['date'])
    df['value'] = pd.to_numeric(df['value'])
    return df



# indices and categories
indices = [
    {'label': 'Construction For Government', 'value': 'WPUFD432'},
    {'label': 'Electrical Contractors Non-Residential', 'value': 'PCU23821X23821X'},
    {'label': 'Multi Family', 'value': 'WPUIP231120'},
    {'label': 'Engineering Services', 'value': 'WPU4532'},
    {'label': 'Cement', 'value': 'WPU1322'},
]
# kinda useless right now - not organizing by dropdown 
category = ['Consumer, Producer & Construction prices', 
           'new, repair & maintenance work by subcontractors', 
           'inputs to construction industries',
           'services important to construction',
           'processed goods important to construction',
           'unprocessed goods important to construction',
           'total compensation, wages & salaries']




#grid Columns and Rows
column_defs = [
    {'headerName':"Category", 'field':'category', 'sortable':True, 'filter':True,"checkboxSelection": True, "headerCheckboxSelection": True,},
    {'headerName':"Indice", 'field':'indice', 'sortable':True, 'filter':True},
    {'headerName':"Ticker", 'field':'ticker', 'sortable':True, 'filter':True},
    {'headerName':"PPI", 'field':'ppi', 'sortable':True, 'filter':True},
    { 'headerName': "YOY%", 'field': "yoy%", 'sortable': True, 'filter': True},
    {'headerName':"Date", 'field':'date', 'sortable':True, 'filter':True, "filter": "agDateColumnFilter", 
     "filterValueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"}, "filterParams": {
                                                                                                        "browserDatePicker": True,
                                                                                                        "minValidYear": 2000,
                                                                                                        "maxValidYear": 2021,
                                                                                                      },
    },
]


row_data = [
    {'category':"Consumer, Producer & Construction prices", "indice":"Construction For Government", "ticker":"WPUFD432", "ppi":167.184, "yoy%":-0.75, "date":"1/24/2024"},
    {'category':"new, repair & maintenance work by subcontractors", "indice":"Electrical Contractors Non-Residential", "ticker":"PCU23821X23821X", "ppi":167.129, "yoy%":-4.70, "date":"1/24/2024"},
    {'category':"inputs to construction industries", "indice":"Multi Family", "ticker":"WPUIP231120", "ppi":149.403, "yoy%":2.09, "date":"1/24/2024"},
    {'category':"services important to construction", "indice":"Engineering Services", "ticker":"WPU4532", "ppi":136.993, "yoy%":1.98, "date":"1/25/2024"},
    {'category':"processed goods important to construction", "indice":"Cement", "ticker":"WPU1322", "ppi":351.768, "yoy%":8.17, "date":"1/24/2024"},
    {'category':"Consumer, Producer & Construction prices", "indice":"Construction For Government", "ticker":"WPUFD432", "ppi":167.184, "yoy%":-0.75, "date":"1/24/2024"},
    {'category':"new, repair & maintenance work by subcontractors", "indice":"Electrical Contractors Non-Residential", "ticker":"PCU23821X23821X", "ppi":167.129, "yoy%":-4.70, "date":"1/24/2024"},
    {'category':"inputs to construction industries", "indice":"Multi Family", "ticker":"WPUIP231120", "ppi":149.403, "yoy%":2.09, "date":"1/24/2024"},
    {'category':"services important to construction", "indice":"Engineering Services", "ticker":"WPU4532", "ppi":136.993, "yoy%":1.98, "date":"1/25/2024"},
    {'category':"processed goods important to construction", "indice":"Cement", "ticker":"WPU1322", "ppi":351.768, "yoy%":8.17, "date":"1/24/2024"},
    {'category':"Consumer, Producer & Construction prices", "indice":"Construction For Government", "ticker":"WPUFD432", "ppi":167.184, "yoy%":-0.75, "date":"1/24/2024"},
    {'category':"new, repair & maintenance work by subcontractors", "indice":"Electrical Contractors Non-Residential", "ticker":"PCU23821X23821X", "ppi":167.129, "yoy%":-4.70, "date":"1/24/2024"},
    {'category':"inputs to construction industries", "indice":"Multi Family", "ticker":"WPUIP231120", "ppi":149.403, "yoy%":2.09, "date":"1/24/2024"},
    {'category':"services important to construction", "indice":"Engineering Services", "ticker":"WPU4532", "ppi":136.993, "yoy%":1.98, "date":"1/25/2024"},
    {'category':"processed goods important to construction", "indice":"Cement", "ticker":"WPU1322", "ppi":351.768, "yoy%":8.17, "date":"1/24/2024"},
]

# +-function for row color change for yoy% change. If positive green, negative red. 
getRowStyle = {
    "styleConditions": [
        {
            "condition": "params.data.yoy% > 0",
            "style": {"backgroundColor": "green"},
        },
        {
            "condition": "params.data.yoy% < 0",
            "style": {"backgroundColor": "red"},
        },
    ],
    "defaultStyle": {"backgroundColor": "grey", "color": "white"},
}



@app.callback(
    Output('central-graph', 'figure'), 
    Input('dash-griddy', 'selectedRows') #do i need virtualRows also for input? 
)
def update_graph(selected_row):
    ticker = selected_row[0]['ticker']  # +-First selected row's ticker
    df = get_fred_data(ticker)  # Fetches data from API 

    # I need to create a function that takes the ticker, calls get_fred_data('ticker), then change that variable['date'] for x value, 
    #variable['value'] for y ^^^ what above should do 

    
    color_map = {
        'WPUFD432': 'red',
        'PCU23821X23821X': 'orange',
        'WPUIP231120': 'yellow',
        'WPU4532': 'green',
        'WPU1322':'blue',
    }
    line_color = color_map.get(ticker, 'black')  # Get color- black by default 

    # now put ticker 

    figure={
                'data': [
                    {
                        'x': df['date'],         
                        'y': df['value'],
                        'type': 'line',
                        'name': 'Construction For Government',
                        'line': {'width': 3},
                        'hovertemplate': '<br><b>%{x}: </b>%{y:.2f}<extra></extra>',
                    }
                ],
                'layout': {
                    'title': 'Construction For Government',
                    'xaxis': {'title': 'Date', 'hoverformat': ''},
                    'yaxis': {'title': 'PPI', 'hoverformat': ''},
                    "colorway": [line_color],
                    "hovermode": "x",
                    'hoverlabel': {
                        'bgcolor': "white",
                        'font_size': 25,
                        'font_family': "Helvetica"
                    },
                }
            },
    return figure






#instead of these functions create callback to input said data frame to call to fred api to gather data 

CONSTRUCTION4GVMT_df = get_fred_data('WPUFD432')
ELECRICALCONTRACTORSNONRESBUILDINGWORK_df = get_fred_data('PCU23821X23821X')
MULTIFAM_df = get_fred_data('WPUIP231120')
ENGRSERVICES_df = get_fred_data('WPU4532')
CEMENT_df = get_fred_data('WPU1322')





# Layout to be Displayed on app.py container
layout = html.Div(
    [
        #graph starts blank, so when the callback updates plots are put on it 
        dcc.Graph(
            figure={
                'data': [
                    {
                        'x': CONSTRUCTION4GVMT_df['date'],         
                        'y': CONSTRUCTION4GVMT_df['value'],
                        'line': {'width': 3}, #keep this 
                        'hovertemplate': '<br><b>%{x}: </b>%{y:.2f}<extra></extra>', #keep this 
                    }
                ],
                # change normal layout here ^ put None for values above or comment it out 
                'layout': {
                    'title': 'Universal Graph Title',
                    'xaxis': {'title': 'Date', 'hoverformat': ''},
                    'yaxis': {'title': 'PPI', 'hoverformat': ''},
                    "colorway": ['black'],
                    "hovermode": "x",
                    'hoverlabel': {
                        'bgcolor': "white",
                        'font_size': 25,
                        'font_family': "Helvetica"
                    },
                }
            },
            id='central-graph',
            className="graph-container"
        ),

        #data grid
        html.Div(
            dag.AgGrid(
            id="dash-griddy", #use this id for callbacks to update the graph when check boxes are selected
            columnDefs=column_defs,
            rowData=row_data,
            columnSize= "responsiveSizeToFit",
            defaultColDef={"filter": True},
            # getRowStyle=getRowStyle,       +- why does this not work??
            dashGridOptions={
            "rowSelection": "multiple",
            'suppressRowClickSelection': True,
             'suppressMovableColumns': True,
            },
        ),
            className="ag-grid-div"
        )
        
    ],
)



if __name__ == '__main__':
    app.run_server(debug=True)

My fault. I fixed it. I’d appreciate some help.

hi @samorouji
I’m not sure if you’re using an API key that is needed. And it also looks like you grabbed this from a multipage Dash app (there is not app = Dash(), and layout is an object).

As a result, the code you provided cannot run on my computer. Try to start a new app.py file in a new environment, and you will see that your code cannot run on its own. Once you get the code to run on its own, please share that with us.

Hello @samorouji,

As far as the getRowStyle, you need to do this:

getRowStyle = {
    "styleConditions": [
        {
            "condition": "params.data['yoy%'] > 0",
            "style": {"backgroundColor": "green"},
        },
        {
            "condition": "params.data['yoy%'] < 0",
            "style": {"backgroundColor": "red"},
        },
    ],
    "defaultStyle": {"backgroundColor": "grey", "color": "white"},
}

% is a reserved character, so thats why you can just do params.data.yoy%

Here is a modified version to make it work, however, I can see that the requests are going through to query your data, I cannot confirm the data coming back from the api:

import dash
from dash import dcc, html, dash_table, callback, Input, Output, State, no_update, Dash
import plotly.express as px
import pandas as pd  # Data science library for data manipulation with data frames
import requests
from markupsafe import escape
import dash_ag_grid as dag

# Fred API Calls (Restful API structure)
# +- create an api call that gets updated data PPI and calculates the YoY change from exactly one year ago so this dashboard has accurate info
API_KEY = 'blurred out'

app = Dash(__name__)

def get_fred_data(series_id):
    print(series_id)
    # url = f'https://api.stlouisfed.org/fred/series/observations?series_id={series_id}&api_key={API_KEY}&file_type=json'
    # response = requests.get(url)
    # if response.status_code != 200:
    #     print(f"Failed to fetch data from FRED API. Status Code: {response.status_code}")
    #     return pd.DataFrame()  # Return an empty DataFrame
    # data = response.json()
    # if 'observations' not in data:
    #     print("No 'observations' key in API response.")
    #     print(data)  # Print the full API response to see its structure
    #     return pd.DataFrame()  # Return an empty DataFrame
    # # pandas with dataframes -- study up but this is the basics i need for now
    # df = pd.DataFrame(data['observations'])
    # df['date'] = pd.to_datetime(df['date'])
    # df['value'] = pd.to_numeric(df['value'])
    return no_update


# indices and categories
indices = [
    {'label': 'Construction For Government', 'value': 'WPUFD432'},
    {'label': 'Electrical Contractors Non-Residential', 'value': 'PCU23821X23821X'},
    {'label': 'Multi Family', 'value': 'WPUIP231120'},
    {'label': 'Engineering Services', 'value': 'WPU4532'},
    {'label': 'Cement', 'value': 'WPU1322'},
]
# kinda useless right now - not organizing by dropdown
category = ['Consumer, Producer & Construction prices',
            'new, repair & maintenance work by subcontractors',
            'inputs to construction industries',
            'services important to construction',
            'processed goods important to construction',
            'unprocessed goods important to construction',
            'total compensation, wages & salaries']

# grid Columns and Rows
column_defs = [
    {'headerName': "Category", 'field': 'category', 'sortable': True, 'filter': True, "checkboxSelection": True,
     "headerCheckboxSelection": True, },
    {'headerName': "Indice", 'field': 'indice', 'sortable': True, 'filter': True},
    {'headerName': "Ticker", 'field': 'ticker', 'sortable': True, 'filter': True},
    {'headerName': "PPI", 'field': 'ppi', 'sortable': True, 'filter': True},
    {'headerName': "YOY%", 'field': "yoy%", 'sortable': True, 'filter': True},
    {'headerName': "Date", 'field': 'date', 'sortable': True, 'filter': True, "filter": "agDateColumnFilter",
     "filterValueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"}, "filterParams": {
        "browserDatePicker": True,
        "minValidYear": 2000,
        "maxValidYear": 2021,
    },
     },
]

row_data = [
    {'category': "Consumer, Producer & Construction prices", "indice": "Construction For Government",
     "ticker": "WPUFD432", "ppi": 167.184, "yoy%": -0.75, "date": "1/24/2024"},
    {'category': "new, repair & maintenance work by subcontractors", "indice": "Electrical Contractors Non-Residential",
     "ticker": "PCU23821X23821X", "ppi": 167.129, "yoy%": -4.70, "date": "1/24/2024"},
    {'category': "inputs to construction industries", "indice": "Multi Family", "ticker": "WPUIP231120", "ppi": 149.403,
     "yoy%": 2.09, "date": "1/24/2024"},
    {'category': "services important to construction", "indice": "Engineering Services", "ticker": "WPU4532",
     "ppi": 136.993, "yoy%": 1.98, "date": "1/25/2024"},
    {'category': "processed goods important to construction", "indice": "Cement", "ticker": "WPU1322", "ppi": 351.768,
     "yoy%": 8.17, "date": "1/24/2024"},
    {'category': "Consumer, Producer & Construction prices", "indice": "Construction For Government",
     "ticker": "WPUFD432", "ppi": 167.184, "yoy%": -0.75, "date": "1/24/2024"},
    {'category': "new, repair & maintenance work by subcontractors", "indice": "Electrical Contractors Non-Residential",
     "ticker": "PCU23821X23821X", "ppi": 167.129, "yoy%": -4.70, "date": "1/24/2024"},
    {'category': "inputs to construction industries", "indice": "Multi Family", "ticker": "WPUIP231120", "ppi": 149.403,
     "yoy%": 2.09, "date": "1/24/2024"},
    {'category': "services important to construction", "indice": "Engineering Services", "ticker": "WPU4532",
     "ppi": 136.993, "yoy%": 1.98, "date": "1/25/2024"},
    {'category': "processed goods important to construction", "indice": "Cement", "ticker": "WPU1322", "ppi": 351.768,
     "yoy%": 8.17, "date": "1/24/2024"},
    {'category': "Consumer, Producer & Construction prices", "indice": "Construction For Government",
     "ticker": "WPUFD432", "ppi": 167.184, "yoy%": -0.75, "date": "1/24/2024"},
    {'category': "new, repair & maintenance work by subcontractors", "indice": "Electrical Contractors Non-Residential",
     "ticker": "PCU23821X23821X", "ppi": 167.129, "yoy%": -4.70, "date": "1/24/2024"},
    {'category': "inputs to construction industries", "indice": "Multi Family", "ticker": "WPUIP231120", "ppi": 149.403,
     "yoy%": 2.09, "date": "1/24/2024"},
    {'category': "services important to construction", "indice": "Engineering Services", "ticker": "WPU4532",
     "ppi": 136.993, "yoy%": 1.98, "date": "1/25/2024"},
    {'category': "processed goods important to construction", "indice": "Cement", "ticker": "WPU1322", "ppi": 351.768,
     "yoy%": 8.17, "date": "1/24/2024"},
]

# +-function for row color change for yoy% change. If positive green, negative red.
getRowStyle = {
    "styleConditions": [
        {
            "condition": "params.data['yoy%'] > 0",
            "style": {"backgroundColor": "green"},
        },
        {
            "condition": "params.data['yoy%'] < 0",
            "style": {"backgroundColor": "red"},
        },
    ],
    "defaultStyle": {"backgroundColor": "grey", "color": "white"},
}


@app.callback(
    Output('central-graph', 'figure'),
    Input('dash-griddy', 'selectedRows'),  # do i need virtualRows also for input?
    prevent_initial_call=True
)
def update_graph(selected_row):
    if not selected_row:
        return {'data': [], 'layout': {'title': 'Construction For Government',
            'xaxis': {'title': 'Date', 'hoverformat': ''},
            'yaxis': {'title': 'PPI', 'hoverformat': ''},
            "colorway": [],
            "hovermode": "x",
            'hoverlabel': {
                'bgcolor': "white",
                'font_size': 25,
                'font_family': "Helvetica"
            },}}
    ticker = selected_row[0]['ticker']  # +-First selected row's ticker
    df = get_fred_data(ticker)  # Fetches data from API
    return df

    # I need to create a function that takes the ticker, calls get_fred_data('ticker), then change that variable['date'] for x value,
    # variable['value'] for y ^^^ what above should do

    color_map = {
        'WPUFD432': 'red',
        'PCU23821X23821X': 'orange',
        'WPUIP231120': 'yellow',
        'WPU4532': 'green',
        'WPU1322': 'blue',
    }
    line_color = color_map.get(ticker, 'black')  # Get color- black by default

    # now put ticker

    figure = {
        'data': [
            {
                'x': df['date'],
                'y': df['value'],
                'type': 'line',
                'name': 'Construction For Government',
                'line': {'width': 3},
                'hovertemplate': '<br><b>%{x}: </b>%{y:.2f}<extra></extra>',
            }
        ],
        'layout': {
            'title': 'Construction For Government',
            'xaxis': {'title': 'Date', 'hoverformat': ''},
            'yaxis': {'title': 'PPI', 'hoverformat': ''},
            "colorway": [line_color],
            "hovermode": "x",
            'hoverlabel': {
                'bgcolor': "white",
                'font_size': 25,
                'font_family': "Helvetica"
            },
        }
    },
    return figure


# instead of these functions create callback to input said data frame to call to fred api to gather data

CONSTRUCTION4GVMT_df = get_fred_data('WPUFD432')
ELECRICALCONTRACTORSNONRESBUILDINGWORK_df = get_fred_data('PCU23821X23821X')
MULTIFAM_df = get_fred_data('WPUIP231120')
ENGRSERVICES_df = get_fred_data('WPU4532')
CEMENT_df = get_fred_data('WPU1322')

# Layout to be Displayed on app.py container
layout = html.Div(
    [
        # graph starts blank, so when the callback updates plots are put on it
        dcc.Graph(
            figure={
                'data': [
                    {
                        # 'x': CONSTRUCTION4GVMT_df['date'],
                        # 'y': CONSTRUCTION4GVMT_df['value'],
                        'line': {'width': 3},  # keep this
                        'hovertemplate': '<br><b>%{x}: </b>%{y:.2f}<extra></extra>',  # keep this
                    }
                ],
                # change normal layout here ^ put None for values above or comment it out
                'layout': {
                    'title': 'Universal Graph Title',
                    'xaxis': {'title': 'Date', 'hoverformat': ''},
                    'yaxis': {'title': 'PPI', 'hoverformat': ''},
                    "colorway": ['black'],
                    "hovermode": "x",
                    'hoverlabel': {
                        'bgcolor': "white",
                        'font_size': 25,
                        'font_family': "Helvetica"
                    },
                }
            },
            id='central-graph',
            className="graph-container"
        ),

        # data grid
        html.Div(
            dag.AgGrid(
                id="dash-griddy",  # use this id for callbacks to update the graph when check boxes are selected
                columnDefs=column_defs,
                rowData=row_data,
                columnSize="responsiveSizeToFit",
                defaultColDef={"filter": True},
                getRowStyle=getRowStyle,
                dashGridOptions={
                    "rowSelection": "multiple",
                    'suppressRowClickSelection': True,
                    'suppressMovableColumns': True,
                },
            ),
            className="ag-grid-div"
        )

    ],
)

app.layout = layout

if __name__ == '__main__':
    app.run_server(debug=True)

Obviously, uncomment the api call, to continue troubleshooting you should be printing at different increments.

1 Like

Can you check my code below, I put it in a runnable format & simplified my attempt at the callback. Thanks.

As far as your code, you have an extra , here:

image

Once you remove it, it will display on the graph the first selection.

Now, as far as your code, here is with some other adjustments:

import dash
from dash import dcc, html, dash_table, callback, Input, Output
import plotly.express as px
import pandas as pd  # Data science library for data manipulation with data frames
import requests
from markupsafe import escape
import dash_ag_grid as dag
import dash_mantine_components as dmc  # goated framework

# Initializing the Dash app  (this needs to be at the very top of the script to work)
app = dash.Dash(__name__, suppress_callback_exceptions=True)
app.title = "Construction Economics Dashboard"  # title of web application


# Link hamburger menu's status to the drawer's status (open or closed)
@app.callback(
    Output("burger-button", "opened"),  # Component ID and property to update
    Input("drawer-simple", "opened")  # Component ID and property to monitor
)
def sync_burger_button_with_drawer(drawer_opened):
    return drawer_opened


# Callback for drawer to open/close when hamburger icon clicked
@app.callback(
    Output("drawer-simple", "opened"),  # Component ID and property to update
    Input("burger-button", "opened"),  # Component ID and property to monitor
    prevent_initial_call=True,
)
def toggle_drawer(n_clicks):
    return n_clicks is not None and n_clicks % 2 == 1


# Fred API Calls (Restful API structure)
# +- create an api call that gets updated data PPI and calculates the YoY change from exactly one year ago so this dashboard has accurate info
API_KEY = '238c2f4f77c7540ea63514a8e2b51806'


def get_fred_data(series_id):
    url = f'https://api.stlouisfed.org/fred/series/observations?series_id={series_id}&api_key={API_KEY}&file_type=json'
    response = requests.get(url)
    if response.status_code != 200:
        print(f"Failed to fetch data from FRED API. Status Code: {response.status_code}")
        return pd.DataFrame()  # Return an empty DataFrame
    data = response.json()
    if 'observations' not in data:
        print("No 'observations' key in API response.")
        print(data)  # Print the full API response to see its structure
        return pd.DataFrame()  # Return an empty DataFrame
    # pandas with dataframes -- study up but this is the basics i need for now
    df = pd.DataFrame(data['observations'])
    df['date'] = pd.to_datetime(df['date'])
    df['value'] = pd.to_numeric(df['value'])
    return df


# indices and categories
indices = [
    {'label': 'Construction For Government', 'value': 'WPUFD432'},
    {'label': 'Electrical Contractors Non-Residential', 'value': 'PCU23821X23821X'},
    {'label': 'Multi Family', 'value': 'WPUIP231120'},
    {'label': 'Engineering Services', 'value': 'WPU4532'},
    {'label': 'Cement', 'value': 'WPU1322'},
]
# kinda useless right now - not organizing by dropdown
category = ['Consumer, Producer & Construction prices',
            'new, repair & maintenance work by subcontractors',
            'inputs to construction industries',
            'services important to construction',
            'processed goods important to construction',
            'unprocessed goods important to construction',
            'total compensation, wages & salaries']

# grid Columns and Rows
column_defs = [
    {'headerName': "Category", 'field': 'category', 'sortable': True, 'filter': True, "checkboxSelection": True,
     "headerCheckboxSelection": True, },
    {'headerName': "Indice", 'field': 'indice', 'sortable': True, 'filter': True},
    {'headerName': "Ticker", 'field': 'ticker', 'sortable': True, 'filter': True},
    {'headerName': "PPI", 'field': 'ppi', 'sortable': True, 'filter': True},
    {'headerName': "YOY%", 'field': "yoy%", 'sortable': True, 'filter': True,
     'cellStyle': {
         # conditional for color - if up green, down red, 0 change white
         "styleConditions": [
             {
                 "condition": "params.value > 0",
                 "style": {"backgroundColor": "mediumaquamarine"},
             },
             {
                 "condition": "params.value < 0",
                 "style": {"backgroundColor": "lightcoral"},
             },
         ],
         # Default style if no rules apply
         "defaultStyle": {"backgroundColor": "white"},
     }},
    {'headerName': "Date", 'field': 'date', 'sortable': True, 'filter': True, "filter": "agDateColumnFilter",
     "filterValueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
     "filterParams": {
         "browserDatePicker": True,
         "minValidYear": 2000,
         "maxValidYear": 2021,
     },
     },
]

row_data = [
    {'category': "Consumer, Producer & Construction prices", "indice": "Construction For Government",
     "ticker": "WPUFD432", "ppi": 167.184, "yoy%": -0.75, "date": "1/24/2024"},
    {'category': "new, repair & maintenance work by subcontractors", "indice": "Electrical Contractors Non-Residential",
     "ticker": "PCU23821X23821X", "ppi": 167.129, "yoy%": -4.70, "date": "1/24/2024"},
    {'category': "inputs to construction industries", "indice": "Multi Family", "ticker": "WPUIP231120", "ppi": 149.403,
     "yoy%": 2.09, "date": "1/24/2024"},
    {'category': "services important to construction", "indice": "Engineering Services", "ticker": "WPU4532",
     "ppi": 136.993, "yoy%": 1.98, "date": "1/25/2024"},
    {'category': "processed goods important to construction", "indice": "Cement", "ticker": "WPU1322", "ppi": 351.768,
     "yoy%": 8.17, "date": "1/24/2024"},
    {'category': "Consumer, Producer & Construction prices", "indice": "Construction For Government",
     "ticker": "WPUFD432", "ppi": 167.184, "yoy%": -0.75, "date": "1/24/2024"},
    {'category': "new, repair & maintenance work by subcontractors", "indice": "Electrical Contractors Non-Residential",
     "ticker": "PCU23821X23821X", "ppi": 167.129, "yoy%": -4.70, "date": "1/24/2024"},
    {'category': "inputs to construction industries", "indice": "Multi Family", "ticker": "WPUIP231120", "ppi": 149.403,
     "yoy%": 2.09, "date": "1/24/2024"},
    {'category': "services important to construction", "indice": "Engineering Services", "ticker": "WPU4532",
     "ppi": 136.993, "yoy%": 1.98, "date": "1/25/2024"},
    {'category': "processed goods important to construction", "indice": "Cement", "ticker": "WPU1322", "ppi": 351.768,
     "yoy%": 8.17, "date": "1/24/2024"},
    {'category': "Consumer, Producer & Construction prices", "indice": "Construction For Government",
     "ticker": "WPUFD432", "ppi": 167.184, "yoy%": -0.75, "date": "1/24/2024"},
    {'category': "new, repair & maintenance work by subcontractors", "indice": "Electrical Contractors Non-Residential",
     "ticker": "PCU23821X23821X", "ppi": 167.129, "yoy%": -4.70, "date": "1/24/2024"},
    {'category': "inputs to construction industries", "indice": "Multi Family", "ticker": "WPUIP231120", "ppi": 149.403,
     "yoy%": 2.09, "date": "1/24/2024"},
    {'category': "services important to construction", "indice": "Engineering Services", "ticker": "WPU4532",
     "ppi": 136.993, "yoy%": 1.98, "date": "1/25/2024"},
    {'category': "processed goods important to construction", "indice": "Cement", "ticker": "WPU1322", "ppi": 351.768,
     "yoy%": 8.17, "date": "1/24/2024"},
]


@app.callback(
    Output('central-graph', 'figure'),
    Input('my-grid', 'selectedRows'),
    # Input('my-grid', 'virtualRowData'),   virtual row data is a list of dictionaries which holds the filtered elements of each row- we need selected rows NOT THIS
)
def update_graph(selected_row):
    if not selected_row:
        return {}
    line_colors = []
    data = []
    color_map = {
        'WPUFD432': 'red',
        'PCU23821X23821X': 'orange',
        'WPUIP231120': 'yellow',
        'WPU4532': 'green',
        'WPU1322': 'blue',
    }
    for row in selected_row:  # +-First selected row's ticker
        ticker_data = get_fred_data(row['ticker'])  # should Fetch data from API
        data.append({
                'x': ticker_data['date'],
                'y': ticker_data['value'],
                'type': 'line',
                'name': row['indice'],
                'line': {'width': 3},
                'hovertemplate': '<br><b>%{x}: </b>%{y:.2f}<extra></extra>',
            })

        # above should takes the ticker, calls get_fred_data('ticker)
        # then change that variable['date'] for x value,
        # variable['value'] for y


        line_colors.append(color_map.get(row['ticker'], 'black'))  # Get color- black by default

    # graph inside of figure, which gets updated - put my default values
    figure = {
        'data': data,
        'layout': {
            'title': 'Construction For Government',
            'xaxis': {'title': 'Date', 'hoverformat': ''},
            'yaxis': {'title': 'PPI', 'hoverformat': ''},
            "colorway": line_colors,
            "hovermode": "x",
            'hoverlabel': {
                'bgcolor': "white",
                'font_size': 25,
                'font_family': "Helvetica"
            },
        },
        'frames': []
    }
    return figure


CONSTRUCTION4GVMT_df = get_fred_data('WPUFD432')
ELECRICALCONTRACTORSNONRESBUILDINGWORK_df = get_fred_data('PCU23821X23821X')
MULTIFAM_df = get_fred_data('WPUIP231120')
ENGRSERVICES_df = get_fred_data('WPU4532')
CEMENT_df = get_fred_data('WPU1322')

# Layout of the app
app.layout = html.Div(
    [
        # graph starts blank, so when the callback updates plots are put on it
        dcc.Graph(
            id='central-graph',
            className="graph-container"
        ),

        # data grid
        html.Div(
            dag.AgGrid(
                id="my-grid",  # use this id for callbacks to update the graph when check boxes are selected
                columnDefs=column_defs,
                rowData=row_data,
                columnSize="responsiveSizeToFit",
                defaultColDef={"filter": True},
                #  getRowStyle=getRowStyle,       +- why does this not work??
                dashGridOptions={
                    "rowSelection": "multiple",
                    'suppressRowClickSelection': True,
                    'suppressMovableColumns': True,
                },
            ),
            id="div-my-grid"  # will use this id's children for the callback output
            , className="div-my-grid"  # classes are for styling
        )

    ],
)

# This runs the app (this should be at the very bottom of the script to work) - Flask web HTTP protocol
if __name__ == '__main__':
    app.run_server(debug=True)

And here is the output:

Thanks so much, I was losing my mind over this problem.

With debug mode on, it said invalid figure received, looking at this message it said type was an array when expecting an object which led me to look at the figure definition. :slight_smile:

2 Likes

The all-too illusive comma :laughing:
I’ve lost weeks of my life due to destructively hidden commas!

2 Likes