Giving plotly a second try: Trying to update a dropdown

I’m reading the posts here and everyone seems fantastic. Stackoverflow hates me lol.

So, I think I’m close to one of my micro-issues….after reading here: How to pass values between pages in dash - #7 by marlon

I’m definitely not a programmer. I don’t want to take credit as one, I always state this in my posts. I’m good with details and patterns, and I learn from snippets. I think they call this a “hacker”: trying to grasp open source snippets, and modifying boilerplates to my needs.

Anywho…

From that link, I now have this:

(im not worried about glob security right now, this is just for my own local run, not a production/business/professional)

I have this whole thing based on that boilerplate (thank the lords for @marlon and his snippet)

import dash
from dash import dcc, html
from dash.dependencies import Input, Output
print(dcc.__version__)
app = dash.Dash(__name__)
app.config.suppress_callback_exceptions = True
import os
from os import listdir
import json
import glob
#path = os.path.join("./static/io/json/")
#fils = [ x for x in os.listdir(path) if x.endswith("json") ]
FILE_LIST = glob.glob("./static/io/json/*.json")

''' INDEX LAYOUT '''
indexpage = html.Div(
    # I added this id attribute
    id='indexpage',
    children=[
                dcc.Link('Enter CIK', href='/cikpage'),
                html.Br(),
                dcc.Link('Select a Concept', href='/conceptspage'),
            ],
    # I added this style attribute
    style={'display': 'block', 'line-height':'0', 'height': '0', 'overflow': 'hidden'}
)


''' CIK LAYOUT '''
cikpage_layout = html.Div(
    # I added this id attribute
    id='cikpage_layout',
    children=[
        html.H1('CIK'),
        dcc.Input(id='cikpage-input', placeholder='ENTER CIK', type='text'),
        html.Div(id='cikpage-content'),
        html.Br(),
        dcc.Link('Populate Concepts', href='/conceptspage'),
        html.Br(),
        dcc.Link('Go back home', href='/'),
    ],
    # I added this style attribute
    style={'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'}
)

''' CONCEPTS LAYOUT '''
gaapdata = {}
for fname in FILE_LIST:
    lst = []
    lst.append(fname)
    conceptspage()
    print(cikentry)
    for cikentry in lst:
        with open(cikentry, "r") as f:
            result = json.loads(f.read())
            concepts_list = list(result['facts']['us-gaap'].keys())
            gaapdata = concepts_list
        gaapdata
        conceptspage_layout = html.Div(
            # I added this id attribute
            id='conceptspage_layout',
            children=[
                html.H1('CONCEPTS: Select a Concept'),
        #        dcc.Dropdown(id='conceptspage-dropdown',options=[{'label': gaap, 'value': i} for i in gaap],),
                dcc.Dropdown(gaapdata, id='conceptspage-dropdown'),
                html.Div(id='conceptspage-content'),
                html.Br(),
                dcc.Link('Choose another CIK', href='/cikpage'),
                html.Br(),
                dcc.Link('Go back home', href='/'),
            ],
            # I added this style attribute
            style={'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'}
        )

''' FULL APP LAYOUT '''
app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content',
             # I added this children attribute
             children=[indexpage, cikpage_layout, conceptspage_layout]
             )
])


''' INDEX CALLBACK '''
# Update the index
@app.callback(
    [dash.dependencies.Output(page, 'style') for page in ['indexpage', 'cikpage_layout', 'conceptspage_layout']],
    # I turned the output into a list of pages
    [dash.dependencies.Input('url', 'pathname')])
def display_page(pathname):
    return_value = [{'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'} for _ in range(3)]

    if pathname == '/cikpage':
        return_value[1] = {'height': 'auto', 'display': 'inline-block'}
        return return_value
    elif pathname == '/conceptspage':
        return_value[2] = {'height': 'auto', 'display': 'inline-block'}
        return return_value
    else:
        return_value[0] = {'height': 'auto', 'display': 'inline-block'}
        return return_value


''' CIK PAGE CALLBACK '''
@app.callback(dash.dependencies.Output('cikpage-content', 'children'),
              [dash.dependencies.Input('cikpage-input', 'value')])
def cikpage_input(value):
    return 'You have selected "{}"'.format(value)

''' CONCEPTS PAGE CALLBACK '''
@app.callback(Output('conceptspage-content', 'children'),
              [Input('cikpage-input', 'value')])
def conceptspage(value):
    cikentry = "{}".format(value)
    return 'You selected "{}"'.format(value)
    return cikentry


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

I tried modifying it, to combine index and the CIK page. working on it below….

''' IMPORTS '''
import dash
from dash import dcc, html
from dash.dependencies import Input, Output
print(dcc.__version__)
app = dash.Dash(__name__)
app.config.suppress_callback_exceptions = True
import os
from os import listdir
import json
import glob
''' OS FILES '''

path = os.path.join("./static/io/json/", '*.json')
filst = glob.glob(path)
filst
FILE_LIST = glob.glob("./static/io/json/*.json")

''' TRAVERSE FILES ATTEMPT 1'''
def filst():
    path = os.path.join("./static/io/json/")
    fils = [ x for x in os.listdir(path) if x.endswith("json") ]
    for lst in fils:
        lst = '\n'+'\n'.join(fils)
        return lst
filst = filst()

''' TRAVERSE FILES ATTEMPT 2'''
def filst_func():
     ipath = os.path.join("./static/io/json/")
     ifils = [ x for x in os.listdir(ipath) if x.endswith("json") ]
     for lst in ifils:
         for ifil in ifils:
             return ifils
filst_func = filst_func()
print(filst_func)


''' GAAP CONCEPTS: EXTRACT FROM JSON, GENERATE 'LIST' (or dict? etc.), TO POPULATE A DROPDOWN '''

def gaap():
    gaapdata = {}
    for fname in FILE_LIST:
##        i = open(fname, "r")
        lst = []
        lst.append(fname)
        print(lst)
        for cikentry in lst:
            with open(cikentry, "r") as f:
                result = json.loads(f.read())
                concepts_list = list(result['facts']['us-gaap'].keys())
##                concepts_list='\n'.join(concepts_list)
                gaapdata = concepts_list
            return gaapdata
gaap = gaap()
## gaap = gaap.split('\n')
''' INDEX LAYOUT '''

indexpage = html.Div(
    # I added this id attribute
    id='indexpage',
    children=[
                html.H1('Index Page'),
                dcc.Input(id='indexpage-input', placeholder='ENTER CIK', type='text'),
                html.Div(id='indexpage-content'),
                html.Br(),
                dcc.Link('Populate Concepts', href='/conceptspage'),
            ],
    # I added this style attribute
    style={'display': 'block', 'line-height':'0', 'height': '0', 'overflow': 'hidden'}
)
''' CONCEPTS LAYOUT '''

conceptspage_layout = html.Div(
    # I added this id attribute
    id='conceptspage_layout',
    children=[
        html.H1('CONCEPTS: Select a Concept'),
#        dcc.Dropdown(id='conceptspage-dropdown',options=[{'label': gaap, 'value': i} for i in gaap],),
        dcc.Dropdown(gaapdata(), id='conceptspage-dropdown'),
        html.Div(id='conceptspage-content'),
        html.Br(),
        dcc.Link('Go back home', href='/'),
    ],
    # I added this style attribute
    style={'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'}
)
''' FULL APP LAYOUT '''

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content',
             # I added this children attribute
             children=[indexpage, conceptspage_layout]
             )
])
''' CALLBACKS '''

''' INDEX '''
@app.callback(
    [dash.dependencies.Output(page, 'style') for page in ['indexpage', 'conceptspage_layout']],
    # I turned the output into a list of pages
    [dash.dependencies.Input('url', 'pathname')])
def display_page(pathname):
    return_value = [{'display': 'block', 'line-height': '0', 'height': '0', 'overflow': 'hidden'} for _ in range(2)]

    if pathname == '/conceptspage':
        return_value[1] = {'height': 'auto', 'display': 'inline-block'}
        return return_value
    else:
        return_value[0] = {'height': 'auto', 'display': 'inline-block'}
        return return_value

'''  '''
@app.callback(dash.dependencies.Output('indexpage-content', 'children'),
              [dash.dependencies.Input('indexpage-input', 'value')])
def indexpage_input(value):
    return 'You have selected "{}"'.format(value)
''' CONCEPT PAGE DROPDOWN CALLBACKS '''
@app.callback(Output('conceptspage_layout', 'children'),
              [Input('indexpage-input', 'value')])
def conceptspage(value):
    cikentry = "{}".format(value)
    cikentry
    gaapdata = {}
    for fname in FILE_LIST:
        lst = []
        lst.append(fname)
        for cikentry in lst:
            with open(cikentry, "r") as f:
                result = json.loads(f.read())
                concepts_list = list(result['facts']['us-gaap'].keys())
                gaapdata = concepts_list
            gaapdata
    return 'You selected "{}"'.format(value)
    return conceptspage()

And finally…

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

If you’ve made it this far, please don’t hate me.

I don’t know how to pass the result from the textbox of my main index page, as an object/variable (i want to call it “cikentry”) into the function at the bottom.

and then, I don’t know how to pass the result from that function below, as a variable (i want to call it popconn, or gaapdata), back to the layout of a second page, the concepts page (which will have the dropdown)

def func():

I think that’s all it is. I hope that made sense, my head is spinning.

The thing is, I can populate the dropdown! by adjusting things around….but, it only populates from one json file from the folder.

When I try to give another entry in the textbox, the dropdown just takes from the same file… In fact, I can leave the textbox blank, and the dropdown just populates from that one file it selects. I know the problem has to do with the way i’m traversing a directory and listing the files.

P.S. : i had the same problem with flask+jinja. I was able to create 2 textboxes, populate a dropdown, but I couldn’t figure out how to pass on a selected value back to the next thing (which was a graph in that case)

after this, I want to pass a selection from the dropdown, as a variable, “concept”, for the y axis on a graph with pandas.

sorry to bother anyone. i understand its alot.

i was also trying to avoid the dbc library, since it’s not officially released by the plotly group. anyway,

i’m looking at these examples, maybe i can maneuver on my own.

Could you summarize where you at?
I understand you are trying to update a DropDown. What exactly? The options?

Where exactly do you have problems?

Another comment on pages: It seem that you are trying to create a multipage app. I highly suggest the corresponding feature in dash for that:

1 Like

Hi @shrykull

No need to avoid the DBC library. It’s a popular well maintained library that’s been available since 2020. To date, it has almost 40 million downloads.

Downloads

Using DBC library will make it easier to make apps with responsive layouts and give you access to many components not available in Plotly libraries. It will also give your app a consistent theme.

Plotly makes these features available in their Enterprise offering, but for open source they recommend third party libraries like DBC or Dash Mantine Components– you can see this in their Dash in 20 Minutes tutorial

1 Like

Hi. I’ll try to reduce all that to one question.

@app.callback(Output('conceptspage-content', 'children'),
              [Input('cikpage-input', 'value')])

def conceptspage(value):
    cikentry = "{}".format(value)
    return cikentry
    return 'You selected "{}"'.format(value)

I want to assign a variable called ‘cikentry’ to what I enter in a textbox. I want a dropdown to be updated based on this.

I am populating the dropdown using this next piece, but It selects and populates the dropdown from only one file, each time.

I can’t figure out how use this entire thing inside of a layout, I want to replace “fil” with my entry from the textbox.

def gaap():
    gaapdata = {}
    path = os.path.join("./static/io/json/", '*.json')
    filst = glob.glob(path)
    for fil in filst:
        with open(fil, "r") as f:
            result = json.loads(f.read())
            concepts_list = list(result['facts']['us-gaap'].keys())
#            concepts_list='\n'.join(concepts_list)
            gaapdata = concepts_list
            return gaapdata

I was thinking if maybe a callback needs to be wrapped inside a layout. Is this proper, or even possible? Maybe my approach is wrong. I think my problem is a python problem in general.

maybe i need something like this…

def jsonloads():
    for fil in filst:
        if cikentry == True:
            fil = f"{cikentry.upper()}"+'.json'
            i = open(fil, "r")
            result = json.loads(i.read())
    return result
jsonloads()


def concepts_list():
        concepts_list = list(jsonloads()['facts']['us-gaap'].keys())
        return concepts_list
concepts_list()

update:

I’ve decided to keep trying with this example i found. maybe I’ll have an easier time passing a value by using a dropdown for the file list itself, and populate a second dropdown based on a selection. I’m not sure how that first list will deal with 18,000 json filesnames, for starters, but this example also doesn’t show anything until I start typing the first letters. Thats good, I’m thinking it will work.

Please forgive me for my lack of terminology and skill with this, overall. Programming is definitely not my thing.

from dash import Dash, dcc, html, dcc, Input, Output, State, callback
from dash.exceptions import PreventUpdate
import os
from os import listdir
import glob
path = os.path.join("./static/")
fils = [ x for x in os.listdir(path) if x.endswith("json") ]

options = [{"label": x, "value": x} for x in fils]
app = Dash()
app.layout = html.Div([
    html.Div([
        "Single dynamic Dropdown",
        dcc.Dropdown(id="my-dynamic-dropdown")
    ]),
    html.Div([
        "Multi dynamic Dropdown",
        dcc.Dropdown(id="my-multi-dynamic-dropdown", multi=True),
    ]),
])

Unfortunately I am still not sure what you want to do. I understood you need to do something like this:

import dash
from dash import dcc, html, Input, Output, State, callback
from dash.exceptions import PreventUpdate

# Initialize the Dash app
app = dash.Dash(__name__)

# Define the app layout
app.layout = html.Div([
    dcc.Input(
        id='input',
        type='text',
        placeholder='Start typing...',
        value='',
        debounce=True
    ),

    dcc.Dropdown(
        id='dropdown',
        options=[],
        placeholder='Select an option...',
        style={'width': '300px'}
    ),

    html.Div(id='output', style={'marginTop': '20px'})
])


# Callback to update dropdown options based on input
@callback(
    Output('dropdown', 'options'),
    Input('input', 'value'),
    State('dropdown', 'options')
)
def update_dropdown_options(value, existing_options):
    if not value:
        raise PreventUpdate
    else:
        if value in existing_options:
            raise PreventUpdate
        else:
            existing_options.append(value)
        return existing_options


# Optional: Callback to show selected value
@callback(
    Output('output', 'children'),
    Input('dropdown', 'value')
)
def display_selected_value(selected_value):
    if selected_value:
        return f"You selected: {selected_value}"
    return "No selection made"


# Run the app
if __name__ == '__main__':
    app.run(debug=True)
1 Like

I wanted something more like this: python - Dynamically updating dropdown options with 2 Inputs in callback Dash - Stack Overflow

The second dropdown gets updated based on the first one.

Or, the reverse of this: Connecting a Text Area and a Dropdown

A conditional text input updates/populates a dropdown’s values, which are coming from loading csv/json files.

I was able to populate the dropdown but not selectively….all the items are showing up from all the files.

Ideally, I’d like to not load it all at the same time and have it show up when I start typing, but i’ve already found examples on that part. Thanks anyway.

I was able to do it, I’m making it look nice before I show you! :smiling_face_with_three_hearts:

2 Likes

I had problems uploading / hosting. Someone helped me post it on his git. I called it “Secapp”

forgot to mention: when you load the page, enter a cik number in the textbox.I only provided a few for samples.

CIK0000832488
CIK0000832489.
CIK0000832818
CIK0000832847
CIK0000832988
CIK0001338460

it still needs a lot of work as it shows. i’m going to try to do the comparison between companies next. I’m playing around with the multi=true dropdown to maybe achieve that

1 Like

i was able to get a dropdown, with PreventUpdate, using dash :slight_smile:

still a work in progress with the multi=true

I probably have no right to rant, considering my skill level, but i really hate pandas and python lol.

I wonder if any other dataframe library could be easier to use? I read briefly about polars, ibis, spark, duckdb

import dash
from dash import Dash, html, dcc, callback, Output, Input, MATCH, State
from dash.exceptions import PreventUpdate
import pandas as pd
import json
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
from plotly.io import to_html
import os
from os import listdir

path = os.path.join("./static/output/")
fils = [ x for x in os.listdir(path) if x.endswith("csv") ]

options = [{"label": x, "value": x} for x in fils]
app = Dash(__name__)
#app.config.suppress_callback_exceptions = True
app.layout = html.Div([
    html.Div(["CIK:",dcc.Dropdown(id="multidd", multi=True), ]),
    html.Div(["GRAPH:",dcc.Graph(id="dynagraph") ]), ])

@callback(
    Output("multidd", "options"),
    Input("multidd", "search_value"),
    State("multidd", "value"))
def update_multi_options(search_value, value):
    if not search_value:
        raise PreventUpdate
    return [o for o in options if search_value in o["label"] or o["value"] in (value or [])]

@callback(
    Output("dynagraph", "figure"),
    Input("multidd", "value"))
def update_options(search_value):
    if not search_value:
        raise PreventUpdate
    CIK = "{}".format(search_value)
    CIK = CIK.strip("['']")
    print(CIK)
    df = pd.read_csv(f'./static/output/{CIK}')

    dates = df['filing_date'].iloc[:].values[0:]
    concepts = df.columns[0:].values[3:]
    concept = (list(df.iloc[:1, 0]))
    fig = go.Figure().add_trace(go.Scatter(x=df.index,y=df[df.columns[0]],visible=True))
    fig.add_annotation(
        text="<a href='http://localhost:5000' target='_self' style='opacity:50;color:red'>Go Back</a>",
        font=dict(size=20),
        x=0, y=1,
        showarrow=False,
        xref="paper", yref="paper",
        xanchor="left", yanchor="bottom")

    updatemenu = []
    buttons = []
    for col in df.columns[3:]:
        buttons.append(dict(method='restyle',label=col,visible=True,args=[{'y':[df[col]],'x':[df.index],'type':'scatter'}, [0]],))
    mymenu = dict()
    updatemenu.append(mymenu)
    updatemenu[0]['buttons'] = buttons
    updatemenu[0]['direction'] = 'down'
    updatemenu[0]['showactive'] = True
    updatemenu[0]['x'] = 0.5
    updatemenu[0]['xanchor'] = 'center'
    updatemenu[0]['y'] = 1.0
    updatemenu[0]['yanchor'] = 'top'
    updatemenu[0]['font'] = dict(size=20)
    fig.update_layout(showlegend=True, updatemenus=updatemenu, height=900)
#    htmlfig = pio.to_html(fig, full_html=True, include_plotlyjs=True)
    return fig

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

SOURCES:

1 Like