【Python百日基础系列】Day28 - Dash交互式图形

文章目录

  • 一、简单的交互式 Dash 应用程序
    • 1.1 代码
    • 1.2 页面效果
  • 二、更新悬停图形
    • 2.1 代码
    • 2.2 页面效果
  • 三、通用交叉过滤器组合
    • 3.1 代码
    • 3.2 页面效果

视频讲解:

一、简单的交互式 Dash 应用程序

1.1 代码

import json

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

styles = {
     
    'pre': {
     
        'border': 'thin lightgrey solid',
        'overflowX': 'scroll'
    }
}

df = pd.DataFrame({
     
    "x": [1,2,1,2],
    "y": [1,2,3,4],
    "customdata": [1,2,3,4],
    "fruit": ["apple", "apple", "orange", "orange"]
})

fig = px.scatter(df, x="x", y="y", color="fruit", custom_data=["customdata"])
# 更新图表布局
fig.update_layout(clickmode='event+select')
# 更新图表标记点大小
fig.update_traces(marker_size=20)

app.layout = html.Div([
    # 图表组件
    dcc.Graph(
        id='basic-interactions',
        figure=fig
    ),
	# className='row'指定其子标签为行内标签
    html.Div(className='row', children=[
        html.Div([
            # Markdown组件
            dcc.Markdown("""
                **悬停数据**

                图表中鼠标悬停在标记点上.
            """),
            # 预排版标记组件。被包围在 pre 元素中的文本通常会保留空格和换行符。而文本也会呈现为等宽字体。
            html.Pre(id='hover-data', style=styles['pre'])
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **单击数据**

                图表中鼠标单击的点.
            """),
            html.Pre(id='click-data', style=styles['pre']),
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **选择数据**

                选择图形菜单栏中的套索或矩形工具,
                然后选择图形中的点。

                请注意,如果'layout.clickmode='event+select',
                如果在单击时按住shift键,则选择数据也会累积(或取消累积)所选数据。
            """),
            html.Pre(id='selected-data', style=styles['pre']),
        ], className='three columns'),

        html.Div([
            dcc.Markdown("""
                **缩放和重新显示数据**

                单击并拖动图形进行缩放,或单击图形菜单栏中的缩放按钮。
                单击图例项目也将触发此事件。
            """),
            html.Pre(id='relayout-data', style=styles['pre']),
        ], className='three columns')
    ])
])


@app.callback(
    Output('hover-data', 'children'),
    Input('basic-interactions', 'hoverData'))
def display_hover_data(hoverData):
    """ 接受图表组件的悬停数据,转换为JSON,同时缩进2个字符,返回给悬停数据组件 """
    return json.dumps(hoverData, indent=2)  # 将一个Python数据结构转换为JSON,缩进2个字符


@app.callback(
    Output('click-data', 'children'),
    Input('basic-interactions', 'clickData'))
def display_click_data(clickData):
    return json.dumps(clickData, indent=2)


@app.callback(
    Output('selected-data', 'children'),
    Input('basic-interactions', 'selectedData'))
def display_selected_data(selectedData):
    return json.dumps(selectedData, indent=2)


@app.callback(
    Output('relayout-data', 'children'),
    Input('basic-interactions', 'relayoutData'))
def display_relayout_data(relayoutData):
    return json.dumps(relayoutData, indent=2)


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

1.2 页面效果

【Python百日基础系列】Day28 - Dash交互式图形_第1张图片

二、更新悬停图形

2.1 代码

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input,Output,State
import pandas as pd
import plotly.express as px

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

csv_url = 'https://plotly.github.io/datasets/country_indicators.csv'
df = pd.read_csv('country_indicators.csv')
# 去重取指标名称
available_indicators = df['Indicator Name'].unique()

app.layout = html.Div([
    html.Div([

        html.Div([
            # 下拉列表组件
            dcc.Dropdown(
                id='crossfilter-xaxis-column',
                options=[{
     'label': i, 'value': i} for i in available_indicators],
                value='Fertility rate, total (births per woman)'
            ),
            # 单选组件
            dcc.RadioItems(
                id='crossfilter-xaxis-type',
                options=[{
     'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={
     'display': 'inline-block', 'marginTop': '5px'}
            )
        ],
        # 指定行内标签
        style={
     'width': '49%', 'display': 'inline-block'}),

        html.Div([
            dcc.Dropdown(
                id='crossfilter-yaxis-column',
                options=[{
     'label': i, 'value': i} for i in available_indicators],
                value='Life expectancy at birth, total (years)'
            ),
            dcc.RadioItems(
                id='crossfilter-yaxis-type',
                options=[{
     'label': i, 'value': i} for i in ['Linear', 'Log']],
                value='Linear',
                labelStyle={
     'display': 'inline-block', 'marginTop': '5px'}
            )
        # 指定行内标签
        ], style={
     'width': '49%', 'float': 'right', 'display': 'inline-block'})
    ], style={
     
        'padding': '10px 5px'
    }),

    html.Div([
        # 图表组件
        dcc.Graph(
            id='crossfilter-indicator-scatter',
            hoverData={
     'points': [{
     'customdata': 'Japan'}]}
        )
    ], style={
     'width': '49%', 'display': 'inline-block', 'padding': '0 20'}),
    html.Div([
        # 图表组件
        dcc.Graph(id='x-time-series'),
        # 图表组件
        dcc.Graph(id='y-time-series'),
        # 指定行内标签
    ], style={
     'display': 'inline-block', 'width': '49%'}),

    html.Div(
        # 滑块组件
        dcc.Slider(    
        id='crossfilter-year--slider',
        min=df['Year'].min(),
        max=df['Year'].max(),
        value=df['Year'].max(),
        marks={
     str(year): str(year) for year in df['Year'].unique()},
        step=None
    ), style={
     'width': '49%', 'padding': '0px 20px 20px 20px'})
])


@app.callback(
    Output('crossfilter-indicator-scatter', 'figure'),
    # 五个Input,每个都会触发回调
    [Input('crossfilter-xaxis-column', 'value'),
     Input('crossfilter-yaxis-column', 'value'),
     Input('crossfilter-xaxis-type', 'value'),
     Input('crossfilter-yaxis-type', 'value'),
     Input('crossfilter-year--slider', 'value')])
def update_graph(xaxis_column_name, yaxis_column_name,
                 xaxis_type, yaxis_type,
                 year_value):
    """ 接受五个Input的值,过滤数据,重新绘制图表并返回页面 """
    dff = df[df['Year'] == year_value]
    fig = px.scatter(x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
            y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
            hover_name=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name']
            )
    # 更新跟踪
    fig.update_traces(customdata=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])
    # 更新X轴
    fig.update_xaxes(title=xaxis_column_name, type='linear' if xaxis_type == 'Linear' else 'log')
    # 更新Y轴
    fig.update_yaxes(title=yaxis_column_name, type='linear' if yaxis_type == 'Linear' else 'log')
    # 更新布局,hovermode='closest'紧密型
    fig.update_layout(margin={
     'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')
    return fig


def create_time_series(dff, axis_type, title):
    # 绘制散点图
    fig = px.scatter(dff, x='Year', y='Value')
    # 更新跟踪,点+线,看似折线图,实为散点图
    fig.update_traces(mode='lines+markers')
    # 更新X轴
    fig.update_xaxes(showgrid=False)
    # 更新Y轴
    fig.update_yaxes(type='linear' if axis_type == 'Linear' else 'log')
    # 添加注释
    fig.add_annotation(x=0, y=0.85, xanchor='left', yanchor='bottom',
                       xref='paper', yref='paper', showarrow=False, align='left',
                       text=title)
    # 更新布局
    fig.update_layout(height=225, margin={
     'l': 20, 'b': 30, 'r': 10, 't': 10})
    return fig


@app.callback(
    Output('x-time-series', 'figure'),
    # 3个Input,每个都会触发回调
    [Input('crossfilter-indicator-scatter', 'hoverData'), # 散点图的悬停数据
     Input('crossfilter-xaxis-column', 'value'),
     Input('crossfilter-xaxis-type', 'value')])
def update_y_timeseries(hoverData, xaxis_column_name, axis_type):
    """ 接受左侧散点图的悬停数据和X轴数据,过滤数据,返回绘制散点图函数给右上图 """
    country_name = hoverData['points'][0]['customdata']
    dff = df[df['Country Name'] == country_name]
    dff = dff[dff['Indicator Name'] == xaxis_column_name]
    title = f'{
       country_name}
{ xaxis_column_name}' return create_time_series(dff, axis_type, title) @app.callback( Output('y-time-series', 'figure'), # 3个Input,每个都会触发回调 [Input('crossfilter-indicator-scatter', 'hoverData'), Input('crossfilter-yaxis-column', 'value'), Input('crossfilter-yaxis-type', 'value')]) def update_x_timeseries(hoverData, yaxis_column_name, axis_type): """ 接受左侧散点图的悬停数据和Y轴数据,过滤数据,返回绘制散点图函数给右下图 """ dff = df[df['Country Name'] == hoverData['points'][0]['customdata']] dff = dff[dff['Indicator Name'] == yaxis_column_name] return create_time_series(dff, axis_type, yaxis_column_name) if __name__ == '__main__': app.run_server(debug=True)

2.2 页面效果

尝试将鼠标移到左侧散点图中的点上。请注意右侧的折线图如何根据您悬停的点进行更新。

【Python百日基础系列】Day28 - Dash交互式图形_第2张图片

三、通用交叉过滤器组合

3.1 代码

import dash
from dash import dcc
from dash import html
import numpy as np
import pandas as pd
from dash.dependencies import Input, Output, State
import plotly.express as px

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# 创建一个六列数据df
np.random.seed(0)  # no-display
df = pd.DataFrame({
     "列 " + str(i+1): np.random.rand(30) for i in range(6)})
# className='row'指定最外层div为行内显示
app.layout = html.Div([
    html.Div(
        # 图表组件,'displayModeBar': False 隐藏工具栏
        dcc.Graph(id='g1', config={
     'displayModeBar': False}),
        className='four columns'    # 1/3 页面宽度
    ),
    html.Div(
        dcc.Graph(id='g2', config={
     'displayModeBar': False}),
        className='four columns'
        ),
    html.Div(
        dcc.Graph(id='g3', config={
     'displayModeBar': False}),
        className='four columns'
    )
], className='row')     # 行内显示

def get_figure(df, x_col, y_col, selectedpoints, selectedpoints_local):

    if selectedpoints_local and selectedpoints_local['range']:
        ranges = selectedpoints_local['range']
        selection_bounds = {
     'x0': ranges['x'][0], 'x1': ranges['x'][1],
                            'y0': ranges['y'][0], 'y1': ranges['y'][1]}
    else:
        selection_bounds = {
     'x0': np.min(df[x_col]), 'x1': np.max(df[x_col]),
                            'y0': np.min(df[y_col]), 'y1': np.max(df[y_col])}
    # 使用“selectedpoints”属性设置要选择的点
    # 并使用“selected”和“unselected”设置这些点的样式`
    fig = px.scatter(df, x=df[x_col], y=df[y_col], text=df.index)

    fig.update_traces(selectedpoints=selectedpoints,
                      customdata=df.index,
                      mode='markers+text', marker={
      'color': 'rgba(0, 116, 217, 0.7)', 'size': 20 }, unselected={
     'marker': {
      'opacity': 0.3 }, 'textfont': {
      'color': 'rgba(0, 0, 0, 0)' } })

    fig.update_layout(margin={
     'l': 20, 'r': 0, 'b': 15, 't': 5}, dragmode='select', hovermode=False)

    fig.add_shape(dict({
     'type': 'rect',
                        'line': {
      'width': 1, 'dash': 'dot', 'color': 'darkgrey' } },
                       **selection_bounds))
    return fig

# 这个回调定义了3个figure, 作为其3个选择的交集的函数
@app.callback(
    # 三组输出
    Output('g1', 'figure'),
    Output('g2', 'figure'),
    Output('g3', 'figure'),
    # 三组输入
    Input('g1', 'selectedData'),
    Input('g2', 'selectedData'),
    Input('g3', 'selectedData')
)
def callback(selection1, selection2, selection3):
    """ 接受三个图表的选定数据,筛选共同元素,返回数据给Input的三个图表 """
    selectedpoints = df.index
    for selected_data in [selection1, selection2, selection3]:
        if selected_data and selected_data['points']:
            #  numpy.intersect1d(ar1, ar2)返回两个数组中共同的元素
            selectedpoints = np.intersect1d(selectedpoints,
                [p['customdata'] for p in selected_data['points']])

    return [get_figure(df, "列 1", "列 2", selectedpoints, selection1),
            get_figure(df, "列 3", "列 4", selectedpoints, selection2),
            get_figure(df, "列 5", "列 6", selectedpoints, selection3)]


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

3.2 页面效果

【Python百日基础系列】Day28 - Dash交互式图形_第3张图片
在每次选择时,三个图形回调都使用每个图的最新选定区域触发。根据选定的点过滤pandas数据框,并重新绘制图形,突出显示选定的点并将选定的区域绘制为虚线矩形。

你可能感兴趣的:(Python,python)