Обратные вызовы между Da sh страниц - PullRequest
0 голосов
/ 28 мая 2020

Я пытаюсь применить обратные вызовы между разными страницами da sh. Я установил компоненты своего приложения в разных файлах:

  • Hotspot_App.py
  • index.py
  • папка apps
    • GeoPlot.py
    • ScatterPlot.py

Итак, первая страница содержит макет app1, его обратные вызовы и компоненты. Но теперь я хочу создать контрольный список на второй вкладке для значений, которые были выбраны в контрольном списке на вкладке1. Я попытался создать макет app.validation в индексе, но это не сработало. См. Ниже мой код:

Index.py

import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output
import dash_auth
import dash_core_components as dcc

from Hotspot_App_Local import app
from apps import GeoPlot, ScatterPlot




app.title = 'Hotspot Analysis'
auth = dash_auth.BasicAuth(
    app,
    VALID_USERNAME_PASSWORD_PAIRS
)

scr_oord = 'www.animage.com' #dummy
nav_item = dbc.NavItem(dbc.NavLink("website.com", href="https://www.website.com/"))

dropdown_menu = dbc.DropdownMenu(
    children=[
        dbc.DropdownMenuItem("GeoPlot", href= "/page-1"),
        dbc.DropdownMenuItem("ScatterPlot", href= "/page-2"),
    ],
    nav=True,
    in_navbar=True,
    label="Menu",
)

navbar = html.Div([dbc.Navbar(
    dbc.Container(
        [
            html.A(
                # Use row and col to control vertical alignment of logo / brand
                dbc.Row(
                    [
                        dbc.Col(html.Img(src= scr_oord, height="50px")),
                        dbc.Col(dbc.NavbarBrand("\tHotspot Analysis", className="ml-2", style= {'color': 'navy', 'font-weight': 'bold'})),
                    ],
                    align="center",
                    no_gutters=True,
                ),
                href="https://plot.ly",
            ),
            dbc.NavbarToggler(id="navbar-toggler2"),
            dbc.Collapse(
                dbc.Nav(
                    [nav_item, dropdown_menu], className="ml-auto", navbar=True
                ),
                id="navbar-collapse2",
                navbar=True,
            ),
        ]
    ),
    color="white",
    dark= False,
    className="mb-5",
            ),
        html.Div(id= 'page_content')
])


app.layout = html.Div([
    dcc.Location(id= 'tab', refresh= False, pathname= "/page-1"),
    navbar,
    html.Div(id= 'page')
])

app.validation_layout = html.Div([navbar, GeoPlot.layout, ScatterPlot.layout])

@app.callback(Output("page", "children"), [Input("tab", "pathname")])
def display_page(pathname):
    if pathname == "/page-1":
        return GeoPlot.layout
    if pathname == "/page-2":
        return ScatterPlot.layout
    # if not recognised, return 404 message
    return html.P("404 - page not found")

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

app.py

import dash_bootstrap_components as dbc
import dash

app = dash.Dash(__name__, suppress_callback_exceptions=True, external_stylesheets=[dbc.themes.BOOTSTRAP])

server = app.server

Geoplot.py

import dash_bootstrap_components as dbc
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output, State, ALL, MATCH
import plotly.graph_objects as go
import pandas as pd
import flask
import ast
from flask import send_file
import io
import datetime

from Hotspot_App_Local import app
from Cards import Cards

df = pd.read_csv('merged_layers.csv')
df = df.round(2)
available_indicators = ['PercBelowMSL', 'PopDens2015', 'RPC4.5', 'PortDist', 'RPC8.5']
df['text'] = 'TotPop2020: '+ round(df["TotPop2020"], 0).astype(str)


Storedata = dcc.Store(id= 'store_columns')
Dropdown = dcc.Dropdown(id= 'Chose setting',
                       options= [
                           {'label': 'Marine', 'value': 'Marine'},
                           {'label': 'Environmental', 'value': 'Env'},
                           {'label': 'Urban', 'value': 'Urban'},
                           {'label': 'Ports', 'value': 'Ports'}
                                ], value= 'Marine', clearable= False)
card2 = Cards('Select The Setting', [Storedata, Dropdown], Header= True)

checkbox = dbc.Checklist(id = 'col_list')
card3 = Cards('Select The Layers', [checkbox], Header= True)

htmlA = html.A([dbc.Button("Store Values", color= 'success', block= True)], id= 'store-values')
#csvbutton = dbc.Button('Store Values', id= 'csv', block= True)
card4 = Cards('', [htmlA])


sizer = 2 * df['PopDens2020'].max() / (30 ** 2)
fig = go.Figure(data=go.Scattermapbox(
        lon = df['center_lon'],
        lat = df['center_lat'],
        text = df['text'],
        mode = 'markers',
        marker= go.scattermapbox.Marker(
            color= df['PopDens2020'],
            cmin = df['PopDens2020'].min(),
            cmax= df['PopDens2020'].max(),
            colorscale = 'Viridis',
            colorbar=dict(
                        title="PopDens2020"
                    ),
            showscale= True,
            size= df['PopDens2020'],
            sizeref= sizer,
            sizemode= 'area'
        ),
        ))

fig.update_layout(
        title= {'text': f'{len(df)} Hotspot Locations',
                'font': {
                            'size': 36,
                            'color': 'darkblue'
                        },
                'xref': 'container',
                'x': 0.5},
        geo_scope='world',
        mapbox_style= 'open-street-map',
        width= 1200,
        height= 800
    )
hotspotgraph = dcc.Graph(id= 'Hotspot_Plot', figure= fig)
card5 = Cards("", [hotspotgraph], Header= False)

rangeslider = html.Div(id='RangeSlider', children=[])
card6 = Cards("Select the Range", [rangeslider], Header= True)

size = dcc.Dropdown(id= 'markersize')
card7 = Cards('Select Markersize', [size], Header= True)

color = dcc.Dropdown(id= 'markercolor')
card8 = Cards('Select Markercolor', [color], Header= True)


layout = html.Div(
    [
        dbc.Row(
            [
                dbc.Col([
                    dbc.Col(dbc.Card(card2, color="light", outline=True)),
                    dbc.Col(dbc.Card(card3, color="light", outline=True)),
                    dbc.Col(dbc.Card(card4, color="light", outline=True)),
                    dbc.Row([
                            dbc.Col(dbc.Card(card7, color="light", outline= True)),
                            dbc.Col(dbc.Card(card8, color="light", outline= True))
                            ])
                        ]),
                dbc.Col(dbc.Card(card5, color="light", outline=True)),
            ],
            className="mb-4",
        ),
        dbc.Row(
            [
                dbc.Col(dbc.Card(card6, color="light", outline=True), md= 12),
            ],
            className="mb-4",
        ),
    ]
)


@app.callback(
    Output('store_columns', 'data'),
    [Input('Chose setting', 'value')]
)
def change_cols(value):
    if value == 'Marine':
        data =  ['PortDist', 'PercBelow-5m', 'RPC4.5', 'RPC8.5']
    if value == 'Urban':
        data =  ['PopDens2020', 'area']
    if value == 'Env':
        data =  ['ManPc2016', 'WWCrAr2018', 'count_PA', 'NearestDist_PA']
    if value == 'Ports':
        data =  ['RPC4.5', 'PortDist']
    return data

@app.callback(
    Output('col_list', 'options'),
    [Input('store_columns','data')],
)
def change_multiselect(data):
    if data is not None:
        return [{'label': i, 'value': i} for i in sorted(data)]

@app.callback(
    Output('col_list', 'value'),
    [Input('store_columns','data')]
)
def change_multiselect(data):
    return sorted(data)

@app.callback(
    Output('markersize', 'options'),
    [Input('col_list','value')]
)
def markersize(values):
    default = ['PopDens2020']
    default.extend(values)
    return [{'label': value, 'value': value} for value in default]

@app.callback(
    Output('markersize', 'value'),
    [Input('col_list','value')]
)
def markersize(values):
    return 'PopDens2020'


@app.callback(
    Output('markercolor', 'options'),
    [Input('col_list','value')]
)
def markersize(values):
    default = ['PopDens2020']
    default.extend(values)
    return [{'label': value, 'value': value} for value in default]

@app.callback(
    Output('markercolor', 'value'),
    [Input('col_list','value')]
)
def markersize(values):
    return 'PopDens2020'

@app.callback(
    Output('RangeSlider', 'children'),
    [Input('col_list', 'value')],
    [State('RangeSlider', 'children')])
def display_dropdowns(value, ranges):
    df = pd.read_csv('merged_layers.csv')
    lst = []
    if value != None:
        for v in value:
            fig = go.Figure()
            fig.add_trace(go.Histogram(x = df[v]))

            fig.add_shape(
                dict(
                    type='line',
                    yref='paper', y0=0, y1=1,
                    x0=df[v].median(), x1=df[v].median()
                )
            )

            hist = dcc.Graph(id={'type': 'histogram', 'index': v}, figure=fig)
            reset_button = dbc.Button('Reset Values',id={'type': 'reset_button', 'index': v}, block= True, color= 'danger')

            card_button = Cards("", [reset_button])

            min = df[v].min()
            max = df[v].max()
            range_slider = dcc.RangeSlider(
                id={
                    'type': 'range_slider',
                    'index': v
                }, tooltip={'always_visible': True, 'placement': 'bottomRight'}, min=min, max=max, value=[min, max], step= 0.01)
            store_minmax = dcc.Store(id={'type': 'store', 'index': v}, data= [min, max])
            card_slider = Cards(f'{v}', [range_slider, store_minmax], Header= True)
            marklen = dcc.Markdown(children= f'**{len(df)} Hotspot Locations**', id={'type': 'len_df', 'index': v},
                                   style= {'textAlign': 'center', 'color': 'darkblue', 'font-size': 25})
            card_hist = Cards(f"", [marklen, hist])
            ap = html.Div([
                dbc.Row(
                    [
                        dbc.Col([
                            dbc.Col(dbc.Card(card_slider, color="light", outline=True)),
                            dbc.Col(dbc.Card(card_button, color= "light", outline= True))], md= 6),
                        dbc.Col(dbc.Card(card_hist, color="light", outline=True), md=6)

                    ],
                    className="mb-4",
                ),
                        ])

            lst.append(ap)
    return lst

@app.callback(
    Output({'type': 'len_df', 'index': ALL}, 'children'),
    [Input({'type': 'range_slider', 'index': ALL}, 'value')],
    [State('col_list', 'value'),
    State({'type': 'len_df', 'index': ALL}, 'children')]
)
def store_len_df(values, cols, text):
    df = pd.read_csv('merged_layers.csv')
    for i in range(len(cols)):
        min = values[i][0]
        max = values[i][1]
        df = df[df[cols[i]].between(min, max)]
    text = [f'**{len(df)} Hotspot Locations**'] * len(text)
    return text


@app.callback(
    Output({'type': 'histogram', 'index': MATCH}, 'figure'),
    [Input({'type': 'range_slider', 'index': MATCH}, 'value')],
     [State({'type': 'histogram', 'index': MATCH}, 'figure')])
def add_verts(range, fig):
    fig['layout']['shapes'] = [{'type': 'line', 'x0': range[0], 'x1': range[0], 'y0': 0, 'y1': 1, 'yref': 'paper'},
                               {'type': 'line', 'x0': range[1], 'x1': range[1], 'y0': 0, 'y1': 1, 'yref': 'paper'}]
    return fig

@app.callback(
    Output({'type': 'range_slider', 'index': MATCH}, 'value'),
    [Input({'type': 'reset_button', 'index': MATCH}, 'n_clicks')],
    [State({'type': 'store', 'index': MATCH}, 'data')])
def reset(click, data):
    return data

@app.callback(
    Output('Hotspot_Plot', 'figure'),
    [Input({'type': 'range_slider', 'index': ALL}, 'value'),
     Input('markersize', 'value'),
     Input('markercolor', 'value')],
    [State('col_list', 'value'),
    State('Hotspot_Plot', 'figure'),]
)
def update_figure(values, col_size, col_color, cols, fig):

    if cols is not None and len(values) > 0 and len(cols) > 0:
        df = pd.read_csv('merged_layers.csv')

        if col_size != col_color:
            df['text'] = f'{col_size}: ' + round(df[col_size], 0).astype(str) + '\n' +  f'{col_color}: ' + round(df[col_color], 0).astype(str)
        else:
            df['text'] = f'{col_size}: ' + round(df[col_size], 0).astype(str)

        size_series = df[col_size]
        for value in df[col_size]:
            if value < 0:
                size_series += abs(df[col_size].min()) + 0.01 #No Negative Values Allowed
                break

        color_series = df[col_color]

        sizer = 2 * df[col_size].max() / (30 ** 2)

        for i in range(len(cols)):
            min = values[i][0]
            max = values[i][1]
            df = df[df[cols[i]].between(min, max)]


        fig['data'][0]['lat'] = df['center_lat']
        fig['data'][0]['lon'] = df['center_lon']
        fig['data'][0]['marker'] = go.scattermapbox.Marker(
                color= color_series,
                cmin = color_series.min(),
                cmax= color_series.max(),
                colorscale = 'Viridis',
                colorbar=dict(
                    title= col_color
                ),
                showscale=True,
                size= size_series,
                sizeref=sizer,
                sizemode='area'
        )
        fig['data'][0]['text'] = df['text']

        fig['layout']['title']['text'] = f'{len(df)} Hotspot Locations'


    return fig


@app.callback(
    Output('store-values', 'href'),
    [Input('col_list', 'value'),
     Input({'type': 'range_slider', 'index': ALL}, 'value'),
    Input('Chose setting', 'value')])

def update_link(columns, ranges, setting):
    return '/dash/urlToDownload?value={}*{}*{}'.format(columns, ranges, setting)


@app.server.route('/dash/urlToDownload')
def download_excel():
    data = flask.request.args.get('value').split('*')

    columns = ast.literal_eval(data[0])
    ranges = ast.literal_eval(data[1])
    setting = data[2]
    minlst, maxlst= [], []
    for r in ranges:
        min, max = r
        minlst.append(min)
        maxlst.append(max)
    range_df = pd.DataFrame(list(zip(columns, minlst, maxlst)), columns=['Column', 'Min_Value', 'Max_Value'])

    df = pd.read_csv('Dash/merged_layers.csv')
    for (i, range) in enumerate(ranges):
        min, max = range
        df = df[df[columns[i]].between(min, max)]
    center_lon = df['center_lon'].values
    center_lat = df['center_lat'].values
    country = df['country'].values
    loc_df = pd.DataFrame(list(zip(center_lon, center_lat, country)), columns= ['center_lon', 'center_lat', 'country'])
    loc_df = loc_df.sort_values('country')

    buf = io.BytesIO()
    excel_writer = pd.ExcelWriter(buf, engine="xlsxwriter")
    range_df.to_excel(excel_writer, sheet_name="Ranges", index=False)
    loc_df.to_excel(excel_writer, sheet_name="Locations", index=False)
    excel_writer.save()
    excel_data = buf.getvalue()
    buf.seek(0)
    now = datetime.datetime.now()
    today = str(now.year) + '-' + str(now.month) + '-' + str(now.day)+ ' ' + str(now.hour) + str(":") + str(now.minute)
    return send_file(
        buf,
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        attachment_filename=f"{setting}_{today}.xlsx",
        as_attachment=True,
        cache_timeout=0
    )

Scatterplot.py

import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State, ALL, MATCH

from Hotspot_App_Local import app

from Cards import Cards

test = dcc.Checklist(id= 'scatterlst')

card1 = Cards('Test', test, Header= True)

layout = html.Div(
    dbc.Row(dbc.Card(card1, color="light", outline=True))
)

@app.callback(
    Output('scatterlst', 'value'),
    [Input('col_list','value')]
)
def change_multiselect(data):
    print('hi')
    print(data)
    return sorted(data)

Проблема в том, что Scatterplot.py не распознает индексы из Geoplot.py.

...