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)
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)
尝试将鼠标移到左侧散点图中的点上。请注意右侧的折线图如何根据您悬停的点进行更新。
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)
在每次选择时,三个图形回调都使用每个图的最新选定区域触发。根据选定的点过滤pandas数据框,并重新绘制图形,突出显示选定的点并将选定的区域绘制为虚线矩形。