Plotly: как отобразить график после нажатия кнопки?

#python #plotly #plotly-dash #plotly-python

#python #plotly #сюжетно-тире #plotly-python #plotly-dash

Вопрос:

Я хочу использовать plotly для отображения графика только после нажатия кнопки, но не уверен, как заставить это работать. Мой рисунок хранится в следующем коде

     fig1 = go.Figure(data=plot_data, layout=plot_layout)
  

Затем я определяю макет своего приложения с помощью следующего бита кода:

 app.layout = html.Div([
                #button
                html.Div(className='submit', children=[

                html.Button('Forecast', id='submit', n_clicks=0)
                ]),
                #loading 
                dcc.Loading(
                id="loading-1",
                type="default",
                children=html.Div(id="loading-output-1")
                ),
                #graph
                dcc.Graph(id= 'mpg-scatter',figure=fig),
    
                #hoverdata
                html.Div([
                    dcc.Markdown(id='hoverdata-text')
                ],style={'width':'50%','display':'inline-block'})
])

@app.callback(Output('hoverdata-text','children'),
             [Input('mpg-scatter','hoverData')])

def callback_stats(hoverData):
    return str(hoverData)


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

Но проблема в том, что я хочу, чтобы сначала отображалась только кнопка. Затем, когда кто-то нажимает на кнопку прогноза, появляется функция загрузки, а через секунду отображается график. Я определил компонент dcc.loading, но не уверен, как определить обратный вызов для этой функции.

Ответ №1:


ПРЕДЛОЖЕНИЕ 3 — dcc.Store() и dcc.Loading

В этом предложении используется компонент dcc.Store(), html.Кнопка() и компонент dcc.Loading для создания того, что, как я теперь понимаю, является желаемой настройкой:

  1. Запустите приложение, которое показывает только кнопку.
  2. Нажмите кнопку, чтобы отобразить значок загрузки, а затем
  3. отобразить фигуру.
  4. Нажмите еще раз, чтобы отобразить следующий рисунок в последовательности из трех рисунков.
  5. Начните снова, когда последовательность фигур будет исчерпана.

После запуска приложение будет выглядеть следующим образом:

введите описание изображения здесь

Теперь вы можете нажать Figures один раз, чтобы попасть Figure 1 ниже, но только после просмотра одного из следующих значков загрузки: ['graph', 'cube', 'circle', 'dot', or 'default'] один из которых 'dot' вызовет посттравматическое расстройство и 'cube' является моим любимым:

Загрузка…

введите описание изображения здесь

Рисунок 1

введите описание изображения здесь

Теперь вы не можете продолжать нажимать для Figure 2 и Figure 3 . Я установил время загрузки на Figure 1 не менее 5 секунд, а затем 2 секунды для Figure 2 и Figure 3 . Но вы можете легко это изменить.

Когда вы нажмете более трех раз, мы снова начнем с начала:

введите описание изображения здесь

Надеюсь, я наконец нашел решение для того, что вы на самом деле искали. Настройка в приведенном ниже фрагменте кода основана на настройке, описанной здесь, но, надеюсь, была скорректирована в соответствии с вашими потребностями. Дайте мне знать, как это работает для вас!

 import pandas as pd
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
from jupyter_dash import JupyterDash
import dash_table
from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc
import time
time.sleep(5) # Delay for 5 seconds.

global_df = pd.DataFrame({'value1':[1,2,3,4],
                          'value2':[10,11,12,14]})

# app = JupyterDash(__name__)
app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])

df = pd.DataFrame({'Value 1': [1,2,3],
                   'Value 2':[10,11,12],
                   'Value 3':[14,12,9]})

df.set_index('Value 1', inplace = True)

app.layout = html.Div([
    # The memory store reverts to the default on every page refresh
    dcc.Store(id='memory'),
    # The local store will take the initial data
    # only the first time the page is loaded
    # and keep it until it is cleared.
    # Same as the local store but will lose the data
    # when the browser/tab closes.
    html.Table([
        html.Thead([
            html.Tr(html.Th('Click to launch figure:')),
            html.Tr([
                html.Th(html.Button('Figures', id='memory-button')),
            ]),
        ]),
    ]),    
     dcc.Loading(id = "loading-icon",
                  #'graph', 'cube', 'circle', 'dot', or 'default'
                 type = 'cube',
                children=[html.Div(dcc.Graph(id='click_graph'))])
])

# Create two callbacks for every store.
# add a click to the appropriate store.
@app.callback(Output('memory', 'data'),
              [Input('memory-button', 'n_clicks')],
              [State('memory', 'data')])
def on_click(n_clicks, data):
    if n_clicks is None:
        # prevent the None callbacks is important with the store component.
        # you don't want to update the store for nothing.
        raise PreventUpdate

    # Give a default data dict with 0 clicks if there's no data.
    data = data or {'clicks': 0}
    data['clicks'] = data['clicks']   1
    if data['clicks'] > 3: data['clicks'] = 0
    
    return data

# output the stored clicks in the table cell.
@app.callback(Output('click_graph', 'figure'),
              # Since we use the data prop in an output,
              # we cannot get the initial data on load with the data prop.
              # To counter this, you can use the modified_timestamp
              # as Input and the data as State.
              # This limitation is due to the initial None callbacks
              # https://github.com/plotly/dash-renderer/pull/81
              [Input('memory', 'modified_timestamp')],
              [State('memory', 'data')])
def on_data(ts, data):
    if ts is None:
         #raise PreventUpdate
        fig = go.Figure()
        fig.update_layout(plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
                          yaxis = dict(showgrid=False, zeroline=False, tickfont = dict(color = 'rgba(0,0,0,0)')),
                          xaxis = dict(showgrid=False, zeroline=False, tickfont = dict(color = 'rgba(0,0,0,0)')))
        return(fig)
    data = data or {}
    0
    # plotly
    y = 'Value 2'
    y2 = 'Value 3'
    
    fig = go.Figure()
    fig.update_layout(plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
                          yaxis = dict(showgrid=False, zeroline=False, tickfont = dict(color = 'rgba(0,0,0,0)')),
                          xaxis = dict(showgrid=False, zeroline=False, tickfont = dict(color = 'rgba(0,0,0,0)')))
    
    if data.get('clicks', 0) == 1:
        fig = go.Figure(go.Scatter(name=y, x=df.index, y=df[y], mode = 'lines'))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode = 'lines'))
        fig.update_layout(template='plotly_dark',
                          title = 'Plot number '   str(data.get('clicks', 0)))
    
        # delay only after first click
        time.sleep(2)
    
    if data.get('clicks', 0) == 2:
        fig = go.Figure((go.Scatter(name=y, x=df.index, y=df[y], mode = 'lines')))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode = 'lines'))
        fig.update_layout(template='seaborn',
                          title = 'Plot number '   str(data.get('clicks', 0)))
        
    if data.get('clicks', 0) == 3:
        fig = go.Figure((go.Scatter(name=y, x=df.index, y=df[y], mode = 'lines')))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode = 'lines'))
        fig.update_layout(template='plotly_white',
                          title = 'Plot number '   str(data.get('clicks', 0)))

    # Aesthetics
    fig.update_layout(margin= {'t':30, 'b':0, 'r': 50, 'l': 50, 'pad': 0},
                      hovermode = 'x',
                      legend=dict(x=1,y=0.85),
                      uirevision='constant')
    
    # delay for every figure
    time.sleep(2)
    return fig

app.run_server(mode='external', port = 8070, dev_tools_ui=True,
          dev_tools_hot_reload =True, threaded=True)
  

ПРЕДЛОЖЕНИЕ 2


После небольшого общения мы теперь знаем, что вы хотели бы:

  • сначала отображать только кнопку (вопрос)
  • при однократном нажатии кнопки внизу отображается рис. 1, при 2-м нажатии отображается рис. 2, а при 3-м нажатии отображается рис. 3 (комментарий)

Я создал новую настройку, которая должна соответствовать всем вышеперечисленным критериям. Сначала отображаются только параметры управления. И затем вы можете выбрать, какой рисунок отображать: Fig1, Fig2 or Fig3 . Мне кажется, что это неоптимальный пользовательский интерфейс, если вам нужно циклически просматривать свои рисунки, чтобы выбрать, какой из них вы хотели бы отобразить. Итак, я выбрал переключатели, подобные этому:

введите описание изображения здесь

Теперь вы можете свободно выбрать свою фигуру для отображения или вернуться к тому, чтобы снова ничего не показывать, вот так:

Отображение при запуске или при None выборе:

введите описание изображения здесь

Figure 1 выбрано

введите описание изображения здесь

Вы все еще не предоставили образец данных, поэтому я все еще использую свои синтетические данные из Suggestion 1 и скорее позволяю различным макетам указывать, какой рисунок показан. Я надеюсь, что это соответствует вашим потребностям, поскольку казалось, что вы хотели бы иметь разные макеты для разных фигур.

Завершите код 2

 from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, ClientsideFunction
import dash_bootstrap_components as dbc
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output
import numpy as np
from plotly.subplots import make_subplots
import plotly.express as px
pd.options.plotting.backend = "plotly"
from datetime import datetime

palette = px.colors.qualitative.Plotly

# sample data
df = pd.DataFrame({'Prices': [1,10,7,5, np.nan, np.nan, np.nan],
                    'Predicted_prices':[np.nan, np.nan, np.nan, 5, 8,6,9]})

# app setup
app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])

# controls
controls = dbc.Card(
      [dbc.FormGroup(
            [
                dbc.Label("Options"),
                                dcc.RadioItems(id="display_figure", 
                                options=[   {'label': 'None', 'value': 'Nope'},
                                            {'label': 'Figure 1', 'value': 'Figure1'},
                                            {'label': 'Figure 2', 'value': 'Figure2'},
                                            {'label': 'Figure 3', 'value': 'Figure3'}
                                ],
                                value='Nope',
                                labelStyle={'display': 'inline-block', 'width': '10em', 'line-height':'0.5em'}
                                ) 
            ], 
        ),
        dbc.FormGroup(
            [dbc.Label(""),]
        ),
    ],
    body=True,
    style = {'font-size': 'large'})

app.layout = dbc.Container(
    [
        html.H1("Button for predictions"),
        html.Hr(),
        dbc.Row([
            dbc.Col([controls],xs = 4),
            dbc.Col([
                dbc.Row([
                    dbc.Col(dcc.Graph(id="predictions")),
                ])
            ]),
        ]),
        html.Br(),
        dbc.Row([
 
        ]), 
    ],
    fluid=True,
)

@app.callback(
    Output("predictions", "figure"),
    [Input("display_figure", "value"),

    ],
)
def make_graph(display_figure):

    # main trace
    y = 'Prices'
    y2 = 'Predicted_prices'
#     print(display_figure)
    if 'Nope' in display_figure:
        fig = go.Figure()
        fig.update_layout(plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
                          yaxis = dict(showgrid=False, zeroline=False, tickfont = dict(color = 'rgba(0,0,0,0)')),
                          xaxis = dict(showgrid=False, zeroline=False, tickfont = dict(color = 'rgba(0,0,0,0)')))
        return fig

    if 'Figure1' in display_figure:
        fig = go.Figure(go.Scatter(name=y, x=df.index, y=df[y], mode = 'lines'))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode = 'lines'))
        fig.update_layout(template='plotly_dark')

    # prediction trace
    if 'Figure2' in display_figure:
        fig = go.Figure((go.Scatter(name=y, x=df.index, y=df[y], mode = 'lines')))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode = 'lines'))
        fig.update_layout(template='seaborn')

    if 'Figure3' in display_figure:
        fig = go.Figure((go.Scatter(name=y, x=df.index, y=df[y], mode = 'lines')))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode = 'lines'))
        fig.update_layout(template='plotly_white')

    # Aesthetics
    fig.update_layout(margin= {'t':30, 'b':0, 'r': 0, 'l': 0, 'pad': 0})
    fig.update_layout(hovermode = 'x')
    fig.update_layout(showlegend=True, legend=dict(x=1,y=0.85))
    fig.update_layout(uirevision='constant')
    fig.update_layout(title = "Prices and predictions")

    return(fig)

app.run_server(mode='external', port = 8005)
  

ПРЕДЛОЖЕНИЕ 1


Это предложение будет сосредоточено непосредственно на:

Я хочу использовать plotly для отображения графика только после нажатия кнопки

Это означает, что я не предполагаю, что это dcc.Loading() должно быть частью ответа.


Я считаю, что dcc.Checklist() это чрезвычайно универсальный и удобный компонент. И при правильной настройке он будет отображаться как кнопка, на которую нужно нажать (или опция, которая должна быть отмечена), чтобы запустить определенные функции или визуализации.

Вот базовая настройка:

 dcc.Checklist(
    id="display_columns",                    
    options=[{"label": col   ' ', "value": col} for col in df.columns],
    value=[df.columns[0]],
    labelStyle={'display': 'inline-block', 'width': '12em', 'line-height':'0.5em'}
  

И вот как это будет выглядеть:

введите описание изображения здесь

Наряду с, среди прочего, следующими несколькими строками, dcc.Checklist() компонент позволит вам включать и выключать Prediction трассировку по своему усмотрению.

 # main trace
y = 'Prices'
fig = make_subplots(specs=[[{"secondary_y": True}]])
if 'Prices' in display_columns:
    fig.add_trace(go.Scatter(name=y, x=df.index, y=df[y], mode = 'lines'), secondary_y=False)

# prediction trace
if 'Predicted_prices' in display_columns:
    fig.add_trace(go.Scatter(name = 'predictions', x=df.index, y=df['Predicted_prices'], mode = 'lines'), secondary_y=False
  

В дополнение к этому, эта настройка легко позволит вам обрабатывать несколько прогнозов для нескольких трасс, если вы захотите расширить этот пример дальше. Попробуйте и дайте мне знать, как это работает у вас. А если что-то непонятно, то мы можем углубиться в детали, когда вы найдете время.

Вот как будет выглядеть приложение с активированными прогнозами и без них:

ВЫКЛ

введите описание изображения здесь

НА

введите описание изображения здесь

Полный код:

 from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, ClientsideFunction
import dash_bootstrap_components as dbc
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output
import numpy as np
from plotly.subplots import make_subplots
import plotly.express as px
pd.options.plotting.backend = "plotly"
from datetime import datetime

palette = px.colors.qualitative.Plotly

# sample data
df = pd.DataFrame({'Prices': [1,10,7,5, np.nan, np.nan, np.nan],
                    'Predicted_prices':[np.nan, np.nan, np.nan, 5, 8,6,9]})

# app setup
app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])

# input controls
controls = dbc.Card(
      [dbc.FormGroup(
            [
                dbc.Label("Options"),
                                dcc.Checklist(
                                    id="display_columns",                    
                                    options=[{"label": col   ' ', "value": col} for col in df.columns],
                                    value=[df.columns[0]],
                                    labelStyle={'display': 'inline-block', 'width': '12em', 'line-height':'0.5em'}
                    #clearable=False,
                    #multi = True
                ),
            ], 
        ),

        dbc.FormGroup(
            [dbc.Label(""),]
        ),
    ],
    body=True,
    style = {'font-size': 'large'})

app.layout = dbc.Container(
    [
        html.H1("Button for predictions"),
        html.Hr(),
        dbc.Row([
            dbc.Col([controls],xs = 4),
            dbc.Col([
                dbc.Row([
                    dbc.Col(dcc.Graph(id="predictions")),
                ])
            ]),
        ]),
        html.Br(),
        dbc.Row([
 
        ]), 
    ],
    fluid=True,
)

@app.callback(
    Output("predictions", "figure"),
    [Input("display_columns", "value"),

    ],
)
def make_graph(display_columns):

    # main trace
    y = 'Prices'
    fig = make_subplots(specs=[[{"secondary_y": True}]])
    if 'Prices' in display_columns:
        fig.add_trace(go.Scatter(name=y, x=df.index, y=df[y], mode = 'lines'), secondary_y=False)
    
    # prediction trace
    if 'Predicted_prices' in display_columns:
        fig.add_trace(go.Scatter(name = 'predictions', x=df.index, y=df['Predicted_prices'], mode = 'lines'), secondary_y=False)
    
    # Aesthetics
    fig.update_layout(margin= {'t':30, 'b':0, 'r': 0, 'l': 0, 'pad': 0})
    fig.update_layout(hovermode = 'x')
    fig.update_layout(showlegend=True, legend=dict(x=1,y=0.85))
    fig.update_layout(uirevision='constant')
    fig.update_layout(template='plotly_dark',
                      plot_bgcolor='#272B30', 
                      paper_bgcolor='#272B30')
    fig.update_layout(title = "Prices and predictions")
    return(fig)

app.run_server(mode='external', port = 8005)
  

Комментарии:

1. Привет @vestland, у меня похожая проблема, и я опубликовал выше подробности. Пожалуйста, дайте мне знать, как отображать статические изображения (уже сгенерированные) в зависимости от нажатой кнопки. Спасибо