Второй взгляд на Dash

Я пока копаю Dash и думаю, что Plotly предлагает множество настроек при создании визуализаций. Использовать их для работы с панелями мониторинга просто, если вы немного знакомы с Python. Если вы новичок в Dash или поигрались с несколькими концепциями и хотите увидеть примеры продвинутых приемов, эта статья поможет вам преодолеть этот пробел! Если вы новичок в Dash и хотите изучить основы создания чего-то классного, ознакомьтесь с моей статьей Intro to Dash:



Улучшение приборной панели

В примере, рассмотренном в моей Вводной статье, я объясняю, как создать приложение интерактивной панели инструментов, которое использует несколько вкладок. Расширяя этот пример, я покажу, как добавить на панель управления следующее:

Фильтры динамических дроплистов
Тепловая карта
Панель навигации

Полный код доступен в конце статьи!

Структура файла приложения

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

tab3.py
navbar.py

Dash Refresher

Dash - это фреймворк для Python, написанный поверх Flask, Plotly.js и React.js, и он абстрагирует сложность каждой из этих технологий в простых в применении компонентах. Приложения Dash состоят из макетов и обратных вызовов:

Макет

Макет состоит из дерева компонентов, которые описывают, как выглядит приложение и как пользователи воспринимают контент.

Обратные вызовы

Обратные вызовы делают приложения Dash интерактивными. Обратные вызовы - это функции Python, которые автоматически вызываются при изменении свойства input. Можно связать обратные вызовы, заставляя одно изменение запускать несколько обновлений во всем приложении.

Соглашение об именовании

Я считаю, что при назначении идентификатора компоненту полезно иметь соглашение об именах. Я всегда использую строчные буквы и разделяю слова тире. Например, «это пример».

А теперь самое интересное ...

Создание динамических фильтров дроплистов

Выпадающие списки с множественным выбором Область и Разнообразие заполняются динамически! Провинция заполняется на основе выбранных стран. Разнообразие заполняется на основе выбранной провинции.

Добавить код на боковую панель

Код для дроплистов нужно добавить в файл sidepanel.py. Используйте основной компонент Dash dcc.Dropdown.

Добавьте список стран

,html.Div([html.P()
     ,html.H5('Country')
     , dcc.Dropdown(id = 'country-drop'
                        ,options=[
                             {'label': i, 'value': i} for i in df.country.unique()],
                        value=['US'],
                        multi=True
                    )])

Я использую html.P (), чтобы список не перекрывался с ползунком цены. Я установил значение по умолчанию на США. Установка для свойства multi значения True позволит пользователю выбирать более одного значения за раз.

Добавьте дроплисты провинции и разнообразия:

,html.Div([html.P()
    ,html.H5('Province')
    , dcc.Dropdown(id = 'province-drop',
                    value=[],
                    multi=True
                )])
,html.Div([html.P()
    ,html.H5('Variety')
    , dcc.Dropdown(id = 'variety-drop',
                    value=[],
                    multi=True
                )])

Обратите внимание, что в код не включены параметры. Это потому, что они будут динамически генерироваться с использованием обратных вызовов!

Создать обратные вызовы

После добавления кода в файл боковой панели создайте обратные вызовы. Обратные вызовы можно добавить в индекс, чтобы файл sidepanel.py оставался чистым. Обратный вызов принимает значение и использует его для фильтрации фрейма данных Wine.

Создать обратный вызов для дроплистов провинции

@app.callback(Output('province-drop', 'options'),
[Input('country-drop', 'value')])
def set_province_options(country):
    
    if len(country)> 0:
        countries = country
        return [{'label': i, 'value': i} for i in sorted(set(df['province'].loc[df['country'].isin(countries)]))]
       
    else:
        countries = []
        return [{'label': i, 'value': i} for i in sorted(set(df['province'].loc[df['country'].isin(countries)]))]

Обратите внимание, что вывод - это параметры для компонента с идентификатором region-drop. значение, передаваемое в обратный вызов, предоставляется компонентом country-drop.

Создание обратного вызова для разнообразных дроплистов

@app.callback(Output('variety-drop', 'options'),
[Input('province-drop', 'value')])
def set_variety_options(province):
   
    if len(province)> 0:
        provinces = province
        return [{'label': i, 'value': i} for i in sorted(set(df['variety'].loc[df['province'].isin(provinces)]))]
       
    else:
        provinces = []
        return [{'label': i, 'value': i} for i in sorted(set(df['variety'].loc[df['province'].isin(provinces)]))]

Обратите внимание, что два обратных вызова почти идентичны! Скопируйте и вставьте, а затем просто обновите переменные до правильных значений.

Добавить тепловую карту

Тепловые карты - популярный способ визуализации данных, поскольку они преобразуют отдельные значения данных в цвета в матрице с использованием цветового спектра от теплого до холодного. Вы получаете размеры X, Y и Z.

Обратите внимание на список под заголовком «Визуализировать:». На тепловой карте отображается средняя оценка или цена по сортам вин и провинциям.

Создайте макет тепловой карты

В файле tab3.py хранится код макета для тепловой карты. Начните с кода выпадающего списка Visualize, используя основной компонент Dash dcc.Dropdown.

html.Div([dcc.Dropdown(id="selected-feature", options=[{"label": i, "value": i} for i in ['price','rating']],
                       value='price'
                , style={"display": "block", "width": "80%"})
                ])

Затем используйте основной компонент Dash dcc.Graph, чтобы загрузить тепловую карту. Я даю компоненту идентификатор ru-my-heatmap.

, html.Div([dcc.Graph(id="ru-my-heatmap"
, style={"margin-right": "auto"
, "margin-left": "auto"
, "width": "80%"
, "height":"700px"})
])

Наконец, соберите все вместе, заключив два компонента в html.Div.

df = transforms.df
layout = html.Div([
     html.Div([html.H3("Visualize:")], style={'textAlign': "Left"})
        , html.Div([dcc.Dropdown(id="selected-feature", options=[{"label": i, "value": i} for i in ['price','rating']],
                       value='price'
                , style={"display": "block", "width": "80%"})
                ])
     , html.Div([dcc.Graph(id="ru-my-heatmap"
                , style={"margin-right": "auto"
                , "margin-left": "auto"
                , "width": "80%"
                , "height":"700px"})]
        )])

Надеюсь, теперь синтаксис становится знакомым!

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

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

Создать обратный вызов

Обратный вызов выводит рисунок для компонента Graph. Определите функцию, которая содержит логику фильтра и трассировку для создания тепловой карты. Он вернет данные и макет графика.

def update_figure(country, province, feature, variety):
return {"data": [trace]
            ,"layout": {                    
                 "xaxis": {"automargin": False}
                 ,"yaxis": {"automargin": True, 'side': "right"}
                 ,"margin": {"t": 10, "l": 30, "r": 100, "b":230}
                    }}

Я передаю в функцию четыре входа из обратного вызова:

Страна
Область
Разнообразие
Выбранная характеристика (цена или рейтинг)

@app.callback(
    Output("ru-my-heatmap", "figure"),
      [Input("country-drop", "value")
    , Input("province-drop", "value")
    , Input("selected-feature", "value")
    , Input("variety-drop", 'value')     
])

Затем определите логику фильтра. Поскольку тепловая карта имеет размерность Z, я использую группу Panda по функциональности, чтобы сгруппировать данные и вычислить среднее значение для каждого сорта.

dff = transforms.df
    dff = dff.groupby(['country','province','variety']).mean().reset_index()
    dff = dff.loc[dff['country'].isin(country)]
if province is None:
        province = []
    if variety is None:
        variety = []
    
  if len(country) > 0 and len(province) > 0 and len(variety) > 0:
        dff = dff.loc[dff['country'].isin(country) & dff['province'].isin(province) & dff['variety'].isin(variety)]
    
  elif len(country) > 0 and len(province) > 0 and len(variety) == 0:
        dff = dff.loc[dff['country'].isin(country) & dff['province'].isin(province)]
    
  elif len(country) > 0 and len(province)== 0 and len(variety) > 0:
      dff = dff.loc[dff['country'].isin(country) & dff['variety'].isin(variety)]
    
  elif len(country) > 0 and len(province)== 0 and len(variety) == 0:
        dff = dff.loc[dff['country'].isin(country)]
    
    else:
        dff

Полный обратный вызов

Это полный обратный вызов для тепловой карты:

@app.callback(
    Output("ru-my-heatmap", "figure"),
      [Input("country-drop", "value")
    , Input("province-drop", "value")
    , Input("selected-feature", "value")
    , Input("variety-drop", 'value')
       
])
def update_figure(country, province, feature, variety):
     
    dff = transforms.df
    dff = dff.groupby(['country','province','variety']).mean().reset_index()
    dff = dff.loc[dff['country'].isin(country)]
if province is None:
        province = []
    if variety is None:
        variety = []
    
  if len(country) > 0 and len(province) > 0 and len(variety) > 0:
        dff = dff.loc[dff['country'].isin(country) & dff['province'].isin(province) & dff['variety'].isin(variety)]
    
  elif len(country) > 0 and len(province) > 0 and len(variety) == 0:
        dff = dff.loc[dff['country'].isin(country) & dff['province'].isin(province)]
    
  elif len(country) > 0 and len(province)== 0 and len(variety) > 0:
      dff = dff.loc[dff['country'].isin(country) & dff['variety'].isin(variety)]
    
  elif len(country) > 0 and len(province)== 0 and len(variety) == 0:
        dff = dff.loc[dff['country'].isin(country)]
    
    else:
        dff
trace = go.Heatmap(z= dff[feature]
                   , x=dff['variety']
                   , y=dff['province']
                   , hoverongaps = True
                   , colorscale='rdylgn', colorbar={"title": "Average", 'x':-.09}, showscale=True)
    return {"data": [trace]
            ,"layout": {                    
                 "xaxis": {"automargin": False}
                 ,"yaxis": {"automargin": True, 'side': "right"}
                 ,"margin": {"t": 10, "l": 30, "r": 100, "b":230}
                    }}

Обратите внимание, что после фильтрации фрейма данных с помощью логики IF / Else создается след графика. Функция возвращает данные и макет для рисунка тепловой карты.

Добавление панели навигации

Панель навигации упрощает пользователям доступ к дополнительным страницам в приложении. Используя библиотеку компонентов Dash Bootstrap, легко создать отзывчивый заголовок навигации.

В этом примере используется NavbarSimple. Компонент NavbarSimple проще, но менее гибкий, чем компонент Navbar. Navbar допускает больше настроек, но требует больше стандартного кода.

Создайте файл navbar.py

Чтобы код навигационной панели хорошо сочетался с макетом боковой панели, оберните его функцией.

import dash_bootstrap_components as dbc
def Navbar():
    navbar = dbc.NavbarSimple(
        children=[
            dbc.NavItem(dbc.NavLink("Home", href="/index")),
            dbc.DropdownMenu(
                children=[
                    dbc.DropdownMenuItem("Page 2", href="#"),
                    dbc.DropdownMenuItem("Page 3", href="#"),
                ],
                nav=True,
                in_navbar=True,
                label="More",
            ),
        ],
        brand="Wine Dash",
        color="primary",
        dark=True,
    )
    return navbar

Обратите внимание, что значение brand отображается на панели навигации при загрузке панели мониторинга. Добавить дополнительные страницы на панель навигации очень просто. Просто добавьте компоненты dbc.DropdownMenuItem в список дочерних элементов внутри dbc.DropdownMenu.

Собираем все вместе

В файл index.py необходимо добавить код, который будет вызывать панель навигации и вкладку тепловой карты. Я включил макет и обратный вызов в код ниже.

Помимо добавления новых компонентов на панель мониторинга, я обновил обратный вызов для dataTable в tab1.py, чтобы он реагировал на новые фильтры динамического списка.

Обновите файл index.py

from app import app
from tabs import sidepanel, tab1, tab2, tab3, navbar
from database import transforms
app.layout = html.Div([navbar.Navbar()
                        , sidepanel.layout
            ])
@app.callback(Output('tabs-content', 'children'),
              [Input('tabs', 'value')])
def render_content(tab):
    if tab == 'tab-1':
        return tab1.layout
    elif tab == 'tab-2':
       return tab2.layout
    elif tab == 'tab-3':
       return tab3.layout

Примечание tab3 и navbar импортируются из вкладок.
Примечание navbar.Navbar () было добавлено в вызов app.layout.
Обратите внимание, что функция render_content была обновлена ​​для вывода tab3.layout.

Полный код

Вся новая фильтрация включена в приведенный ниже код index.py.

Далее

В моем следующем руководстве по Dash я добавлю несколько страниц, которые можно будет выбрать на панели навигации, в качестве примера маршрутизации URL. Я также представлю основной компонент Dash Store.

App.py

Поскольку я добавляю обратные вызовы к элементам, которых нет в app.layout, поскольку они распределены по файлам, я установил suppress_callback_exceptions = True

import dash
import dash_bootstrap_components as dbc
app = dash.Dash(__name__, external_stylesheets = [dbc.themes.BOOTSTRAP])server = app.serverapp.config.suppress_callback_exceptions = True

Index.py

Этот код был обновлен и теперь включает новые фильтры на боковой панели. Это файл, который будет запускаться в консоли. App.layout возвращает sidepanel.layout. Он также содержит большинство обратных вызовов, используемых в приложении. Функциональность боковой панели записана в обратные вызовы.

import dash
import plotly
import dash_core_components as dcc
import dash_html_components as html 
import dash_table
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import sqlite3
import pandas as pd
from app import app
from tabs import sidepanel, tab1, tab2, tab3, navbar
from database import transforms
app.layout = html.Div([navbar.Navbar()
                        , sidepanel.layout
            ])
@app.callback(Output('tabs-content', 'children'),
              [Input('tabs', 'value')])
def render_content(tab):
    if tab == 'tab-1':
        return tab1.layout
    elif tab == 'tab-2':
       return tab2.layout
    elif tab == 'tab-3':
       return tab3.layout
operators = [['ge ', '>='],
             ['le ', '<='],
             ['lt ', '<'],
             ['gt ', '>'],
             ['ne ', '!='],
             ['eq ', '='],
             ['contains '],
             ['datestartswith ']]
def split_filter_part(filter_part):
    for operator_type in operators:
        for operator in operator_type:
            if operator in filter_part:
                name_part, value_part = filter_part.split(operator, 1)
                name = name_part[name_part.find('{') + 1: name_part.rfind('}')]
value_part = value_part.strip()
                v0 = value_part[0]
                if (v0 == value_part[-1] and v0 in ("'", '"', '`')):
                    value = value_part[1: -1].replace('\\' + v0, v0)
                else:
                    try:
                        value = float(value_part)
                    except ValueError:
                        value = value_part
# word operators need spaces after them in the filter string,
                # but we don't want these later
                return name, operator_type[0].strip(), value
return [None] * 3
@app.callback(
    Output('table-sorting-filtering', 'data')
    , [Input('table-sorting-filtering', "page_current")
     , Input('table-sorting-filtering', "page_size")
     , Input('table-sorting-filtering', 'sort_by')
     , Input('table-sorting-filtering', 'filter_query')
     , Input('rating-95', 'value')
     , Input('price-slider', 'value')
     , Input('country-drop', 'value')
     , Input('province-drop', 'value')
     , Input('variety-drop', 'value') 
     ])
def update_table(page_current, page_size, sort_by, filter, ratingcheck, prices, country, province, variety):
    filtering_expressions = filter.split(' && ')
    dff = transforms.df
low = prices[0]
    high = prices[1]
dff = dff.loc[(dff['price'] >= low) & (dff['price'] <= high)]
if province is None:
        province = []
    if variety is None:
        variety = []
    
    if len(country) > 0 and len(province) > 0 and len(variety) > 0:
        dff = dff.loc[dff['country'].isin(country) & dff['province'].isin(province) & dff['variety'].isin(variety)]
    elif len(country) > 0 and len(province) > 0 and len(variety) == 0:
        dff = dff.loc[dff['country'].isin(country) & dff['province'].isin(province)]
    elif len(country) > 0 and len(province)== 0 and len(variety) > 0:
        dff = dff.loc[dff['country'].isin(country) & dff['variety'].isin(variety)]
    elif len(country) > 0 and len(province)== 0 and len(variety) == 0:
        dff = dff.loc[dff['country'].isin(country)]
    else:
        dff
if ratingcheck == ['Y']:
        dff = dff.loc[dff['rating'] >= 95]
    else:
        dff
for filter_part in filtering_expressions:
        col_name, operator, filter_value = split_filter_part(filter_part)
if operator in ('eq', 'ne', 'lt', 'le', 'gt', 'ge'):
            # these operators match pandas series operator method names
            dff = dff.loc[getattr(dff[col_name], operator)(filter_value)]
        elif operator == 'contains':
            dff = dff.loc[dff[col_name].str.contains(filter_value)]
        elif operator == 'datestartswith':
            # this is a simplification of the front-end filtering logic,
            # only works with complete fields in standard format
            dff = dff.loc[dff[col_name].str.startswith(filter_value)]
if len(sort_by):
        dff = dff.sort_values(
            [col['column_id'] for col in sort_by],
            ascending=[
                col['direction'] == 'asc'
                for col in sort_by
            ],
            inplace=False
        )
page = page_current
    size = page_size
    return dff.iloc[page * size: (page + 1) * size].to_dict('records')
if __name__ == '__main__':
    app.run_server(debug = True)

База данных ›transforms.py

Файл преобразований содержит любые преобразования, которые должны пройти данные перед вызовом в приложение.

import dash
import dash_bootstrap_components as dbc
import pandas as pd
import sqlite3
from dash.dependencies import Input, Outputconn = sqlite3.connect(r"C:\Users\MTGro\Desktop\coding\wineApp\db\wine_data.sqlite")
c = conn.cursor()df = pd.read_sql("select * from wine_data", conn)df = df[['country', 'description', 'rating', 'price', 'province', 'title','variety','winery','color','varietyID']]

Вкладки ›navbar.py

Используя компоненты Bootstrap Dash, я реализую простую навигационную панель и оборачиваю ее функцией, чтобы она хорошо сочеталась с боковой панелью.

import dash_bootstrap_components as dbc
def Navbar():
    navbar = dbc.NavbarSimple(
        children=[
            dbc.NavItem(dbc.NavLink("Home", href="/index")),
            dbc.DropdownMenu(
                children=[
                    dbc.DropdownMenuItem("Page 2", href="#"),
                    dbc.DropdownMenuItem("Page 3", href="#"),
                ],
                nav=True,
                in_navbar=True,
                label="More",
            ),
        ],
        brand="Wine Dash",
        color="primary",
        dark=True,
    )
    return navbar

Вкладки ›sidepanel.py

Вот где проявляется сила Bootstrap CSS. Сетка макета позволяет легко контролировать внешний вид макета. В dash-bootstrap-components есть три основных компонента макета: Container, Row и Col.

import dash
import plotly
import dash_core_components as dcc
import dash_html_components as html 
import dash_bootstrap_components as dbc 
import dash_table
import pandas
from dash.dependencies import Input, Output
from app import app
from tabs import tab1, tab2
from database import transforms
df = transforms.df
min_p=df.price.min()
max_p=df.price.max()
layout = html.Div([
    html.H1('Wine Dash')
    ,dbc.Row([dbc.Col(
        html.Div([
         html.H2('Filters')
        , dcc.Checklist(id='rating-95'
        , options = [
            {'label':'Only rating >= 95 ', 'value':'Y'}
        ])
        ,html.Div([html.P()
                ,html.H5('Price Slider')
                ,dcc.RangeSlider(id='price-slider'
                            ,min = min_p
                            ,max= max_p
                            , marks = {0: '$0',
                                        500: '$500',
                                        1000: '$1000',
                                        1500: '$1500',
                                        2000: '$2000',
                                        2500: '$2500',
                                        3000: '$3000',
                                       }
                            , value = [0,3300]
                            )
                        
                            ])
        ,html.Div([html.P()
            ,html.H5('Country')
            , dcc.Dropdown(id = 'country-drop'
                        ,options=[
                             {'label': i, 'value': i} for i in df.country.unique()
                        ],
                        value=['US'],
                        multi=True
                    )  
        ])
,html.Div([html.P()
            ,html.H5('Province')
            , dcc.Dropdown(id = 'province-drop',
                            value=[],
                            multi=True
                        )])
,html.Div([html.P()
            ,html.H5('Variety')
            , dcc.Dropdown(id = 'variety-drop',
                            value=[],
                            multi=True
                        )])
], style={'marginBottom': 50, 'marginTop': 25, 'marginLeft':15, 'marginRight':15}
        )#end div
    , width=3) # End col
,dbc.Col(html.Div([
            dcc.Tabs(id="tabs", value='tab-1', children=[
                    dcc.Tab(label='Data Table', value='tab-1'),
                    dcc.Tab(label='Scatter Plot', value='tab-2'),
                    dcc.Tab(label='Heatmap Plot', value='tab-3'),
                ])
            , html.Div(id='tabs-content')
        ]), width=9)
        ]) #end row
    
    ])#end div
@app.callback(Output('province-drop', 'options'),
[Input('country-drop', 'value')])
def set_province_options(country):
    
    if len(country)> 0:
        countries = country
        return [{'label': i, 'value': i} for i in sorted(set(df['province'].loc[df['country'].isin(countries)]))]
       
    else:
        countries = []
        return [{'label': i, 'value': i} for i in sorted(set(df['province'].loc[df['country'].isin(countries)]))]
@app.callback(Output('variety-drop', 'options'),
[Input('province-drop', 'value')])
def set_variety_options(province):
# if province is None:
    #     provinces = []
    
    if len(province)> 0:
        provinces = province
        return [{'label': i, 'value': i} for i in sorted(set(df['variety'].loc[df['province'].isin(provinces)]))]
       
    else:
        provinces = []
        return [{'label': i, 'value': i} for i in sorted(set(df['variety'].loc[df['province'].isin(provinces)]))]

Вкладки ›Tab1.py

Это DataTable из примера в начале статьи. Обратные вызовы находятся в индексе. Обратные вызовы включают функцию фильтрации с использованием компонентов боковой панели.

import dash
import plotly
import dash_core_components as dcc
import dash_html_components as html 
import dash_bootstrap_components as dbc 
import dash_table
import pandas as pd
from dash.dependencies import Input, Outputfrom app import app 
from database import transformsdf = transforms.dfPAGE_SIZE = 50layout =html.Div(dash_table.DataTable(
                            id='table-sorting-filtering',
                            columns=[
                                {'name': i, 'id': i, 'deletable': True} for i in df[['country','description','rating','price','province','title','variety','winery','color']]
                            ],
                            style_table={'height':'750px'
                                ,'overflowX': 'scroll'},style_data_conditional=[
                                {
                                    'if': {'row_index': 'odd'},
                                    'backgroundColor': 'rgb(248, 248, 248)'
                                }
                            ],
                            style_cell={
                                'height': '90',
                                # all three widths are needed
                                'minWidth': '140px', 'width': '140px', 'maxWidth': '140px', 'textAlign': 'left'
                                ,'whiteSpace': 'normal'
                            }
                            ,style_cell_conditional=[
                                {'if': {'column_id': 'description'},
                                'width': '48%'},
                                {'if': {'column_id': 'title'},
                                'width': '18%'},
                            ]
                            , page_current= 0,
                            page_size= PAGE_SIZE,
                            page_action='custom',filter_action='custom',
                            filter_query='',sort_action='custom',
                            sort_mode='multi',
                            sort_by=[]
                        )
                        )

Вкладки ›Tab2.py

Вместо использования Graph компонента Dash Core я использую Plotly’s Scattergl для повышения производительности визуализации набора данных. ScatterGL может использовать графический процессор вместо процессора, поэтому он, как правило, обеспечивает лучшую производительность для больших наборов данных.

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc 
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output
import dash_table
from app import app
from database import transformsdf = transforms.dflayout = html.Div(
            id='table-paging-with-graph-container',
            className="five columns"
        )@app.callback(Output('table-paging-with-graph-container', "children"),
[Input('rating-95', 'value')
, Input('price-slider', 'value')
])def update_graph(ratingcheck, prices):
    dff = dflow = prices[0]
    high = prices[1]dff = dff.loc[(dff['price'] >= low) & (dff['price'] <= high)]
    
    if ratingcheck == ['Y']:
       dff = dff.loc[dff['rating'] >= 95]
    else:
        dfftrace1 = go.Scattergl(x = dff['rating']
                        , y = dff['price']
                        , mode='markers'
                        , opacity=0.7
                        , marker={
                                'size': 8
                                , 'line': {'width': 0.5, 'color': 'white'}
                                }
                        , name='Price v Rating'
                    )
    return html.Div([
        dcc.Graph(
            id='rating-price'
            , figure={
                'data': [trace1],
                'layout': dict(
                    xaxis={'type': 'log', 'title': 'Rating'},
                    yaxis={'title': 'Price'},
                    margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
                    legend={'x': 0, 'y': 1},
                    hovermode='closest'
                )
            }
        )
    ])

Вкладки ›tab3.py

Вкладка 3 содержит макет тепловой карты и обратный вызов. Помните, что фрейм данных преобразуется с помощью groupby, поэтому в качестве значения Z можно использовать среднее значение.

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objs as go
import pandas as pd
from app import app 
from database import transforms
df = transforms.df
layout = html.Div([
        html.Div([html.H3("Visualize:")], style={'textAlign': "Left"})
        , html.Div([dcc.Dropdown(id="selected-feature", options=[{"label": i, "value": i} for i in ['price','rating']],
                       value='price'
                       , style={"display": "block", "width": "80%"})
                ])
     , html.Div([dcc.Graph(id="ru-my-heatmap"
                            , style={"margin-right": "auto", "margin-left": "auto", "width": "80%", "height":"700px"})]
        )])
@app.callback(
    Output("ru-my-heatmap", "figure"),
      [Input("country-drop", "value")
    , Input("province-drop", "value")
    , Input("selected-feature", "value")
    , Input("variety-drop", 'value')
       
])
def update_figure(country, province, feature, variety):
     
    dff = transforms.df
    dff = dff.groupby(['country','province','variety']).mean().reset_index()
    dff = dff.loc[dff['country'].isin(country)]
if province is None:
        province = []
    if variety is None:
        variety = []
    
    if len(country) > 0 and len(province) > 0 and len(variety) > 0:
        dff = dff.loc[dff['country'].isin(country) & dff['province'].isin(province) & dff['variety'].isin(variety)]
    
    elif len(country) > 0 and len(province) > 0 and len(variety) == 0:
        dff = dff.loc[dff['country'].isin(country) & dff['province'].isin(province)]
    
    elif len(country) > 0 and len(province)== 0 and len(variety) > 0:
        dff = dff.loc[dff['country'].isin(country) & dff['variety'].isin(variety)]
    
    elif len(country) > 0 and len(province)== 0 and len(variety) == 0:
        dff = dff.loc[dff['country'].isin(country)]
    
    else:
        dff
trace = go.Heatmap(z= dff[feature]
                   , x=dff['variety']
                   , y=dff['province']
                   , hoverongaps = True
                   , colorscale='rdylgn', colorbar={"title": "Average", 'x':-.09}, showscale=True)
    return {"data": [trace]
            ,"layout": {                    
                   "xaxis": {"automargin": False}
                    ,"yaxis": {"automargin": True, 'side': "right"}
                    ,"margin": {"t": 10, "l": 30, "r": 100, "b":230}
                    }}

Его можно найти на github здесь:



Благодарю вас!

- Эрик Клеппен