Как использовать динамически генерируемый контент в качестве входных данных для отображения графиков в Plotly Dash? - PullRequest
0 голосов
/ 30 октября 2018

Скажем, у меня есть 3 набора данных с разными именами столбцов. Я хочу создать приложение, построенное на графике, в котором на основе того, какой набор данных выбирает пользователь, оно должно отображать имена столбцов в текстовых областях (горизонтальные полосы, что-то вроде кнопок, но не кликабельные). Пока я могу это сделать, и вот код для этого:

import pandas as pd
import numpy as np
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go

data1 = {'Col1':['A', 'B', 'C', 'D', 'E'],
        'Col2':list(np.random.randn(5))}
data2 = {'Col1':['F', 'G', 'H', 'I', 'J'],
        'Col2':list(np.random.randn(5))}
data3 = {'Col1':['K', 'L', 'M', 'N', 'O'],
        'Col2':list(np.random.randn(5))}

df1 = pd.DataFrame(data1, columns=['Col1', 'Col2'])
df2 = pd.DataFrame(data2, columns=['Col1', 'Col2'])
df3 = pd.DataFrame(data3, columns=['Col1', 'Col2'])


app = dash.Dash()

app.layout = html.Div([
        html.Div([dcc.Dropdown(
                 id='dropdown',
                 options=[{'label': 'Option 1', 'value': 1},
                          {'label': 'Option 2', 'value': 2},
                          {'label': 'Option 3', 'value': 3}],
                 placeholder='Select the dataset...',
                 )]),

        html.Div(id='output')
    ])


@app.callback(
    dash.dependencies.Output('output', 'children'),
    [dash.dependencies.Input('dropdown', 'value')])
def update_output(selected):
    if selected==1:
        df = df1
    elif selected==2:
        df = df2
    elif selected==3:
        df = df3

    columns = df.Col1.values

    divs=[]
    for i in range(len(columns)):
        divs.append(
            dcc.Graph(
                figure=go.Figure(
                    data=[go.Bar(
                                x=[10],
                                y=[columns[i]],
                                orientation = 'h',
                                text=['Column number {}: {}'.format(i+1, columns[i])],
                                textposition = 'inside',
                                marker = dict(color = 'rgb(158,202,225)'),
                            )
                        ],
                    layout=go.Layout(
                        xaxis=dict(showticklabels=False, fixedrange=True),
                        yaxis=dict(showticklabels=False, fixedrange=True),
                    )
                ),
                hoverData='',
                style={'width': 400, 'height':250, 'padding': 1},
                id='dynamic-text-area-'+format(i)
            ),
        )

        divs.append(html.Div(style={'height': '1'}))

    return divs

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

enter image description here

Теперь я хочу добавить еще одну функциональность, которая всякий раз, когда пользователь наводит курсор на любое из этих имен столбцов, и на круговой диаграмме должна отображаться некоторая информация (я буду использовать некоторую простую фиктивную информацию в своем коде ниже для демонстрационных целей). Поэтому я добавил в макет приложения элемент div с именем pie-chart-div и обратный вызов внутри цикла с динамически созданными идентификаторами фигур текстовой области в качестве входных данных для вывода круговой диаграммы, например:

for i in range(len(columns)):
    '''
    ###
    The rest of the stuff written above
    ###
    '''
    @app.callback(dash.dependencies.Output('pie-chart-div', 'children'),
              [dash.dependencies.Input('dropdown', 'value'),
               dash.dependencies.Input('dynamic-text-area-{}'.format(i), 'hoverData')])
        def update_graph(df, hoverData):
            values = [df[df.Col1==hoverData].Col2.iloc[0], sum(df.Col2)]
            labels = ['Selected column','All other columns']
            trace = go.Pie(values=values, labels = labels)
            fig = dcc.Graph(
                id='graph',
                figure={
                    'data': [trace],
                    'layout': {
                        'height': 400,
                        'width': 400,
                        'showlegend': False
                    }
                }
            )
            return fig

Проблема в том, что это выдает ошибку, говорящую о том, что он не может идентифицировать фигуру в макете приложения. И это потому, что dash не может обрабатывать создание динамических фигур, как упоминалось здесь . Вот полная ошибка:

  File "C:\Users\h473\Desktop\example_app.py", line 82, in update_output
    dash.dependencies.Input('dynamic-text-area-{}'.format(i), 'hoverData')])
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 827, in callback
    self._validate_callback(output, inputs, state, events)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 631, in _validate_callback
    ).replace('    ', ''))
dash.exceptions.NonExistantIdException:
Attempting to assign a callback to the
component with the id "dynamic-text-area-0" but no
components with id "dynamic-text-area-0" exist in the
app's layout.


Here is a list of IDs in layout:
['dropdown', 'output', 'pie-chart-div']


If you are assigning callbacks to components
that are generated by other callbacks
(and therefore not in the initial layout), then
you can suppress this exception by setting
`app.config['suppress_callback_exceptions']=True`.

Итак, я попытался поставить в строку

app.config['suppress_callback_exceptions']=True

, но это дает еще одну ошибку о том, что pie-chart-div уже использовался и не может быть использован повторно:

  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\_compat.py", line 35, in reraise
    raise value
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\flask\app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 911, in dispatch
    return self.callback_map[target_id]['callback'](*args)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 851, in add_context
    output_value = func(*args, **kwargs)
  File "C:\Users\h473\Desktop\NPS SHAP Dashboard\ex.py", line 902, in update_output
    dash.dependencies.Input('dynamic-text-area-{}'.format(i), 'hoverData')])
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 827, in callback
    self._validate_callback(output, inputs, state, events)
  File "C:\Users\h473\AppData\Local\Programs\Python\Python35\lib\site-packages\dash\dash.py", line 708, in _validate_callback
    output.component_property).replace('    ', ''))
dash.exceptions.CantHaveMultipleOutputs:
You have already assigned a callback to the output
with ID "pie-chart-div" and property "children". An output can only have
a single callback function. Try combining your inputs and
callback functions together into one function. 

Итак, как мне это исправить и заставить это работать?


Вот мой полный код:

import pandas as pd
import numpy as np
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go

data1 = {'Col1':['A', 'B', 'C', 'D', 'E'],
        'Col2':list(np.random.randn(5))}
data2 = {'Col1':['F', 'G', 'H', 'I', 'J'],
        'Col2':list(np.random.randn(5))}
data3 = {'Col1':['K', 'L', 'M', 'N', 'O'],
        'Col2':list(np.random.randn(5))}

df1 = pd.DataFrame(data1, columns=['Col1', 'Col2'])
df2 = pd.DataFrame(data2, columns=['Col1', 'Col2'])
df3 = pd.DataFrame(data3, columns=['Col1', 'Col2'])


app = dash.Dash()

app.layout = html.Div([
        html.Div([dcc.Dropdown(
                 id='dropdown',
                 options=[{'label': 'Dataset 1', 'value': 1},
                          {'label': 'Dataset 2', 'value': 2},
                          {'label': 'Dataset 3', 'value': 3}],
                 placeholder='Select the dataset...',
                 )]),

        html.Div(id='output'),

        html.Div(id='pie-chart-div')
    ])

@app.callback(
    dash.dependencies.Output('output', 'children'),
    [dash.dependencies.Input('dropdown', 'value')])
def update_output(selected):
    if selected==1:
        df = df1
    elif selected==2:
        df = df2
    elif selected==3:
        df = df3

    columns = df.Col1.values

    divs=[]
    for i in range(len(columns)):
        divs.append(
            dcc.Graph(
                figure=go.Figure(
                    data=[go.Bar(
                                x=[10],
                                y=[columns[i]],
                                orientation = 'h',
                                text=['Column number {}: {}'.format(i+1, columns[i])],
                                textposition = 'inside',
                                marker = dict(color = 'rgb(158,202,225)'),
                            )
                        ],
                    layout=go.Layout(
                        xaxis=dict(showticklabels=False, fixedrange=True),
                        yaxis=dict(showticklabels=False, fixedrange=True),
                    )
                ),
                hoverData='',
                style={'width': 400, 'height':250, 'padding': 1},
                id='dynamic-text-area-{}'.format(i)
            ),
        )

        divs.append(html.Div(style={'height': '1'}))




        @app.callback(dash.dependencies.Output('pie-chart-div', 'children'),
              [dash.dependencies.Input('dropdown', 'value'),
               dash.dependencies.Input('dynamic-text-area-{}'.format(i), 'hoverData')])
        def update_graph(df, hoverData):
            values = [df[df.Col1==hoverData].Col2.iloc[0], sum(df.Col2)]
            labels = ['Selected column','All other columns']
            trace = go.Pie(values=values, labels = labels)
            fig = dcc.Graph(
                id='graph',
                figure={
                    'data': [trace],
                    'layout': {
                        'height': 400,
                        'width': 400,
                        'showlegend': False
                    }
                }
            )
            return fig



    return divs



if __name__ == '__main__':
    app.run_server(debug=True)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...