The dash_core_components library includes a component called Graph.
主要是讲各种图形

1.概述

  1. **dash_core_components** 库包含一个叫 Graph 的组件;
  2. **Graph** 组件使用开源的**plotly.js** (JavaScript图形库) 渲染交互式数据的可视化;
  3. **Plotly.js** 支持超过35种数据图,可以生成高清的SVG矢量图和高性能的WebGL图;
  4. **dcc.Graph** 组件的 figure**Plotly.py**figure 使用一样的参数。详情可参阅plotly.py文档与图库
  5. **Dash** 组件由一组属性以声明方式进行描述,所有这些属性都可以通过回调函数进行更新。有些属性还可以通过用户交互进行更新,比如,点选下拉按钮 **dcc.Dropdown** 组件的选项,该组件的属性 **value** 值就会改变;
  6. **dcc.Graph** 组件有四个属性,可以通过用户交互进行更新:鼠标悬停(**hoverData**)、单击(**clickData**)、选择区域数据(**selectedData**)、更改布局(**relayoutData**)
    ```python import json import dash import dash_core_components as dcc import dash_html_components as html from dash.dependencies import Input, Output import plotly.express as px import pandas as pd

app = dash.Dash(name)

styles = dict(pre = dict(border = ‘thin lightgrey solid’, overflowX = ‘scroll’)) app.layout = html.Div([ dcc.Graph( id = ‘basic-interactions’, figure = dict(data = [dict(x = [1, 2, 3, 4], y = [4, 1, 3, 5], text = [‘a’, ‘b’, ‘c’, ‘d’], customdata = [‘c.a’, ‘c.b’, ‘c.c’, ‘c.d’], name = ‘trace01’, mode = ‘markers’, marker = dict(size = 12)), dict(x = [1, 2, 3, 4], y = [9, 4, 1, 4], text = [‘w’, ‘x’, ‘y’, ‘z’], customdata = [‘c.w’, ‘c.x’, ‘c.y’, ‘c.z’], name = ‘trace02’, mode = ‘markers’, marker = dict(size = 12))], layout = dict(clickmode = ‘event+select’))), html.Div(className = ‘row’, children = [ html.Div(className = ‘three columns’, children = [ dcc.Markdown(d(“”” 一、悬停数据

  1. 将鼠标悬停在图中的值上。
  2. """)),
  3. html.Pre(id = 'hover-data', style = styles['pre'])]),
  4. html.Div(className = 'three columns',
  5. children = [
  6. dcc.Markdown(d("""
  7. **二、点击数据**
  8. 用鼠标点击图上的点。
  9. """)),
  10. html.Pre(id = 'click-data', style = styles['pre'])]),
  11. html.Div(className = 'three columns',
  12. children = [
  13. dcc.Markdown(d("""
  14. **三、选择数据**
  15. 使用菜单的套索或方框工具,选择图上的点。
  16. 请注意:如果layout.clickmode ='event + select'
  17. 若单击的同时按住shift键,则选择数据的同时,也会累计或取消累计所选的数据。
  18. """)),
  19. html.Pre(id = 'selected-data', style = styles['pre'])]),
  20. html.Div(className = 'three columns',
  21. children = [
  22. dcc.Markdown(d("""
  23. **四、缩放与改变数据布局**
  24. 在图形上点击并拖拽,
  25. 或点击图形菜单的缩放按钮实现缩放。
  26. 点击图例也可以激活此事件。
  27. """)),
  28. html.Pre(id = 'relayout-data', style = styles['pre'])])
  29. ]
  30. )

])

@app.callback(Output(‘hover-data’, ‘children’), [Input(‘basic-interactions’, ‘hoverData’)]) def display_hover_data(hoverData): return json.dumps(hoverData, indent = 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_repayout_data(relayoutData): return json.dumps(relayoutData, indent = 2)

if name == ‘main‘: app.run_server(debug=True)

  1. > ![](https://cdn.nlark.com/yuque/0/2021/webp/2610909/1614333353010-74b0db1e-2e29-40de-81bd-f65a4354b81f.webp#align=left&display=inline&height=644&margin=%5Bobject%20Object%5D&originHeight=644&originWidth=602&size=0&status=done&style=none&width=602)
  2. >
  3. <a name="AJQLI"></a>
  4. # 2.悬停时更新图表
  5. ```python
  6. import arrow as ar
  7. import numpy as np
  8. import pandas as pd
  9. import json
  10. from textwrap import dedent as d
  11. import plotly.graph_objs as go
  12. import dash
  13. import dash_core_components as dcc # 交互式组件
  14. import dash_html_components as html # 代码转html
  15. from dash.dependencies import Input, Output, State # 回调
  16. from jupyter_plotly_dash import JupyterDash # Jupyter中的Dash
  17. import ipywidgets as widgets # Jupyter的滑动条插件
  18. df = pd.read_csv(
  19. 'https://gist.githubusercontent.com/chriddyp/'
  20. 'cb5392c35661370d95f300086accea51/raw/'
  21. '8e0768211f6b747c0db42a9ce9a0937dafcbd8b2/'
  22. 'indicators.csv')
  23. app = JupyterDash('update_graph_id', width = 1000, height = 960)
  24. app.layout = html.Div([
  25. # 设置2个下拉按钮、4个单选按钮
  26. html.Div([
  27. html.Div([
  28. dcc.Dropdown(
  29. id = 'crossfilter-xaxis-column',
  30. options = [{'label': i, 'value': i} for i in df['Indicator Name'].unique()],
  31. value = 'Fertility rate, total (births per woman)'),
  32. dcc.RadioItems(
  33. id = 'crossfilter-xaxis-type',
  34. options = [{'label': i, 'value': i} for i in ['线性', '日志']],
  35. value = '线性',
  36. labelStyle = dict(display = 'inline-block'))],
  37. style = dict(width = '49%', display = 'inline-block')),
  38. html.Div([
  39. dcc.Dropdown(
  40. id = 'crossfilter-yaxis-column',
  41. options = [{'label': i, 'value': i} for i in df['Indicator Name'].unique()],
  42. value = 'Life expectancy at birth, total (years)'),
  43. dcc.RadioItems(
  44. id = 'crossfilter-yaxis-type',
  45. options = [{'label': i, 'value': i} for i in ['线性', '日志']],
  46. value = '线性',
  47. labelStyle = dict(display = 'inline-block'))],
  48. style = dict(width = '49%', display = 'inline-block', float = 'right'))],
  49. style = dict(borderBottom = 'thin lightgrey solid',
  50. backgroundColor = 'rgb(250, 250, 250)', padding = '10px 5px')),
  51. # 设置交互数据对象及默认值
  52. html.Div([
  53. dcc.Graph(
  54. id = 'crossfilter-indicator-scatter',
  55. hoverData = dict(points = [{'customdata': 'Japan'}]))],
  56. style = dict(width = '49%', display = 'inline-block', padding = '0 20')),
  57. # 设置交互的子图表
  58. html.Div([
  59. dcc.Graph(id = 'x-time-series'),
  60. dcc.Graph(id = 'y-time-series')],
  61. style = dict(width = '49%', display = 'inline-block')),
  62. # 设置日期滑动条
  63. html.Div(
  64. dcc.Slider(
  65. id = 'crossfilter-year--slider',
  66. min = df['Year'].min(),
  67. max = df['Year'].max(),
  68. value = df['Year'].max(),
  69. marks = {str(y): str(y) for y in df['Year'].unique()}),
  70. style = dict(widht = '49%', padding = '0px 20px 20px 20px')
  71. )
  72. ])
  73. # 回调2个下拉按钮 + 4个单选按钮
  74. @app.callback(
  75. Output('crossfilter-indicator-scatter', 'figure'),
  76. [Input('crossfilter-xaxis-column', 'value'),
  77. Input('crossfilter-yaxis-column', 'value'),
  78. Input('crossfilter-xaxis-type', 'value'),
  79. Input('crossfilter-yaxis-type', 'value'),
  80. Input('crossfilter-year--slider', 'value')])
  81. def update_graph(xaxis_column_name, yaxis_column_name, xaxis_type, yaxis_type, year_value):
  82. dff = df[df['Year'] == year_value]
  83. return dict(
  84. data = [go.Scatter(
  85. x = dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
  86. y = dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
  87. text = dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
  88. customdata = dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'],
  89. mode = 'markers',
  90. marker = dict(size = 15, opacity = 0.5, line = dict(width = 0.5, color = 'white')))],
  91. layout = dict(
  92. xaxis = dict(title = xaxis_column_name, type = 'linear' if xaxis_type == '线性' else '日志'),
  93. yaxis = dict(title = yaxis_column_name, type = 'linear' if yaxis_type == '线性' else '日志'),
  94. margin = {'l': 40, 'b': 30, 't': 10, 'r': 0}, height=720, hovermode = 'closest'
  95. )
  96. )
  97. # 设置子图表
  98. def create_time_series(df, axis_type, title):
  99. return dict(
  100. data = [go.Scatter(
  101. x = df['Year'],
  102. y = df['Value'],
  103. mode = 'lines+markers')],
  104. layout = dict(
  105. height = 360, margin = {'l': 20, 'b': 30, 't': 10, 'r': 10},
  106. xaxis = dict(showgrid = False),
  107. yaxis = dict(type = 'linear' if axis_type == '线性' else '日志'),
  108. annotations = [
  109. dict(x = 0, y = 0.85, text = title,
  110. showarrow = False, align = 'left',
  111. bgcolor = 'rgba(255, 255, 255, 0.5)',
  112. xref = 'paper', yref = 'paper',
  113. xanchor = 'left', yanchor = 'bottom')]
  114. )
  115. )
  116. # 回调--设置上子图表的交互
  117. @app.callback(
  118. Output('x-time-series', 'figure'),
  119. [Input('crossfilter-indicator-scatter', 'hoverData'),
  120. Input('crossfilter-xaxis-column', 'value'),
  121. Input('crossfilter-xaxis-type', 'value')])
  122. def update_y_timeseries(hoverData, xaxis_column_name, axis_type):
  123. country_name = hoverData['points'][0]['customdata']
  124. dff = df[df['Country Name'] == country_name]
  125. dff = dff[dff['Indicator Name'] == xaxis_column_name]
  126. title = f"<b>{country_name}</b><br>{xaxis_column_name}"
  127. return create_time_series(dff, axis_type, title)
  128. # 回调--设置下子图表的交互
  129. @app.callback(
  130. Output('y-time-series', 'figure'),
  131. [Input('crossfilter-indicator-scatter', 'hoverData'),
  132. Input('crossfilter-yaxis-column', 'value'),
  133. Input('crossfilter-yaxis-type', 'value')])
  134. def update_x_timeseries(hoverData, yaxis_column_name, axis_type):
  135. dff = df[df['Country Name'] == hoverData['points'][0]['customdata']]
  136. dff = dff[dff['Indicator Name'] == yaxis_column_name]
  137. return create_time_series(dff, axis_type, yaxis_column_name)
  138. app

13819693-f74e21a4f95c25f0[1].gif

3.交叉过滤

  1. # 数据源
  2. np.random.seed(0) # 使每次生成的随机数相同
  3. df = pd.DataFrame({f"Column {i}": np.random.rand(30) + i*10 for i in range(6)})
  4. app = JupyterDash('Cross_filter_id', width = 1000)
  5. app.layout = html.Div(
  6. className = 'row',
  7. children = [
  8. html.Div(dcc.Graph(id = 'g1', config = dict(displayModeBar = False)), className = 'four columns'),
  9. html.Div(dcc.Graph(id = 'g2', config = dict(displayModeBar = False)), className = 'four columns'),
  10. html.Div(dcc.Graph(id = 'g3', config = dict(displayModeBar = False)), className = 'four columns'),
  11. ]
  12. )
  13. # high_light为返回函数的函数
  14. def high_light(x, y):
  15. def callback(*selectedDatas):
  16. selectedpoints = df.index
  17. # 按选定的点,从Dataframe中过滤数据
  18. for i, selected_data in enumerate(selectedDatas):
  19. if selected_data is not None:
  20. selected_index = [p['customdata'] for p in selected_data['points']]
  21. if len(selected_index) > 0:
  22. selectedpoints = np.intersect1d(selectedpoints, selected_index)
  23. # 设置图表
  24. fig = dict(
  25. data = [dict(
  26. x = df[x], y = df[y], text = df.index, textposition = 'top', selectedpoints = selectedpoints,
  27. customdata = df.index, type = 'scatter', mode = 'markers+text',
  28. textfont = dict(color = 'rgba(30, 30, 30, 1)'),
  29. marker = dict(color = 'rgba(0, 116, 217, 0.7)', size = 12,
  30. line = dict(color = 'rgb(0, 116, 217)', width = 0.5)),
  31. unselected = dict(marker = dict(opacity = 0.3), textfont = dict(color = 'rgba(0, 0, 0, 0)'))
  32. )], # 使未选择的透明
  33. layout = dict(clickmode = 'event+select', showlegend = False, hovermode = 'closest', height = 230,
  34. margin = {'l': 15, 'r': 0, 'b': 15, 't': 5}, dragmode = 'select')
  35. )
  36. # 将矩形显示到先前选定的区域
  37. shape = dict(type = 'rect', line = dict(width = 1, dash = 'dot', color = 'darkgrey'))
  38. if selectedDatas[0] and selectedDatas[0]['range']:
  39. fig['layout']['shapes'] = [dict({
  40. 'x0': selectedDatas[0]['range']['x'][0],
  41. 'x1': selectedDatas[0]['range']['x'][1],
  42. 'y0': selectedDatas[0]['range']['y'][0],
  43. 'y1': selectedDatas[0]['range']['y'][1]},
  44. **shape)]
  45. else:
  46. fig['layout']['shapes'] = [dict({
  47. 'type': 'rect',
  48. 'x0': np.min(df[x]),
  49. 'x1': np.max(df[x]),
  50. 'y0': np.min(df[y]),
  51. 'y1': np.max(df[y])},
  52. **shape)]
  53. return fig
  54. return callback
  55. # app.callback是一个装饰器
  56. app.callback(
  57. Output('g1', 'figure'),
  58. [Input('g1', 'selectedData'),
  59. Input('g2', 'selectedData'),
  60. Input('g3', 'selectedData')]
  61. )(high_light('Column 0', 'Column 1'))
  62. app.callback(
  63. Output('g2', 'figure'),
  64. [Input('g2', 'selectedData'),
  65. Input('g1', 'selectedData'),
  66. Input('g3', 'selectedData')]
  67. )(high_light('Column 2', 'Column 3'))
  68. app.callback(
  69. Output('g3', 'figure'),
  70. [Input('g3', 'selectedData'),
  71. Input('g1', 'selectedData'),
  72. Input('g2', 'selectedData')]
  73. )(high_light('Column 4', 'Column 5'))
  74. app

13819693-f501e6a2bdc36229[1].gif

  • 示例是对由6列数据集,两两组成的3个散点图表,进行交叉筛选的通用示例。对单个散点图进行选择数据区域(或单击),则另外2个散点图会仅显示筛选的数据;
  • 对多维数据集进行筛选和可视化,最好选用平行坐标图的方式。

    4.Dash交互局限性

  1. 点击图上的点不能累加:不能累加已经点击的图点数量,也不支持对某个图点进行反选;
  2. 目前还不能自定义悬停交互及选择框的样式。