知识梳理不易,请尊重劳动成果,文章仅发布在CSDN网站上,在其他网站看到该博文均属于未经作者授权的恶意爬取信息
整个项目的页面包含三部分,由上中下三栏构成。项目已上传至 个人Github仓库 。 项目头部是整个面板的标题,中间左侧是筛选条件,中间后两个是依据筛选条件生成的堆叠条状折线图和饼图,下面是地图可视化。
assets文件夹中就是加载的样式和图片,样式这里可以使用之前已经下载好的bootstrap.min.css样式(也可以按照 项目6.2bootstrap组件 中的介绍下载其它的样式模板),本次项目中不需要加载本地图片。
主框架就是app.py项目初始化文件和index.py主程序运行文件,其中app.py文件中的信息较为简单,就是创建一个dash的应用,代码如下。
index.py文件中引入初始化后的应用,然后进行布局及运行初始化设置。布局的主体是上中下三个部分,上面是项目的头部信息,中间是一行三列,下面是一个整体地图。
头部只有一个信息,使用HeaderInfo()填充内容,该函数分配在header.py文件中;而中间左侧是有三个筛选组件,直接用一个函数FilterInfo()填充内容,此函数分配在filteritem.py文件中,中间后两个图形分别用StackBarLineInfo()、PieInfo()两个函数填充,对应分配在stackbarline.py和piechart.py文件中; 下面只有一个图形展示,使用MapInfo()填充内容,该函数分配在mapchart.py文件中,该文件的全部代码如下。
from dash import html
import dash_bootstrap_components as dbc
from app import app
from header import HeaderInfo
from filteritem import FilterInfo
from stackbarline import StackBarLineInfo
from piechart import PieInfo
from mapchart import MapInfo
app.layout = html.Div(
[
html.Div([HeaderInfo()]),
html.Div(
[
dbc.Row(
[
dbc.Col([FilterInfo()]),
dbc.Col([StackBarLineInfo()]),
dbc.Col([PieInfo()])
]
)
]
),
html.Div([MapInfo()])
]
)
if __name__ == '__main__':
app.run_server(debug=True)
剩下的5个文件中的函数初始化内容如下。(api.py和api.ipynb文件初始化是空)
该文件的功能就是显示项目标题,并居中,设置相对简答,全部代码如下。
保存文件后,运行index.py,提示点击网址:http://127.0.0.1:8050/,刷线页面后,输出结果如下。
该文件功能是实现筛选条件的设置,共两个Dropdown组件和一个Rangeslider组件,配合着文字提示,数据使用简单的内容先进行填充,全部代码如下。
from dash import html,dcc
def FilterInfo():
return html.Div(
[
html.Div(
[
html.H3([f'Select Region:']),
dcc.Dropdown(
['a','b','c'],
'a',
id='Region'
)
]
),
html.Div(
[
html.H3([f'Select Country:']),
dcc.Dropdown(
['usa','china','japan'],
'usa',
id='Country'
)
]
),
html.Div(
[
html.H3('Select Year:'),
dcc.RangeSlider(
min=1970,
max=2017,
step=1,
marks=None,
value=[2000,2010],
tooltip={"placement": "bottom", "always_visible": True},
id='Span-year'
)
]
)
]
)
在api.ipynb文件中读取数据,并获取筛选组件中需要的内容:时间跨度、区域和国家信息。首先读取本地的文件,代码及输出内容如下。
文件中的数据是每次恐怖主义发生的记录,因此一年中某一国家可能会发生多起恐怖主义事件,需要将数据进行分组汇总。汇总之前,留心观察数值字段存在着缺失值,因此需要先进行缺失值处理(这里缺失数据默认就是0),然后按照区域、国家和年份进行分组求和统计,代码及输出结果如下。
获取时间跨度。对iyear
字段进行最大、最小值求解即可,代码及输出结果如下。
获取区域标签。直接对region_txt
字段进行唯一值计数,并排除缺失值None
。
获取国家标签。直接对country_txt
字段进行唯一值计数,并排除缺失值None
。
获取完毕后,把内容转移到api.py文件中,并导入到filteritem.py文件中更新数据,此时api.py文件中的代码如下。
import os
os.chdir(r'd:/Data Science/plotly学习/个人/【12. Global terrorism】 Dropdown-Rangeslider-stackbarline-pie-map/')
import pandas as pd
data = pd.read_csv('data/modified_globalterrorismdb_0718dist.csv')
#首先对需要统计的字段进行缺失值填充,然后按照国家、地区、年份对死亡、受伤和攻击类型进行统计
data[['nkill','nwound','attacktype1']] = data[['nkill','nwound','attacktype1']].fillna(0)
df= data.groupby(['region_txt','country_txt','iyear'])[['nkill','nwound','attacktype1']].sum().reset_index()
#获取数据的时间跨度
year_min,year_max = df['iyear'].min(),df['iyear'].max()
#获取Dropdown组件中的标签信息,第一个是区域标签,核实没有缺失值None
region_ls = df['region_txt'].unique().tolist()
#获取Dropdown组件中的标签信息,第二个是国家标签,核实也没有缺失值None
country_ls = df['country_txt'].unique().tolist()
保存文件后,刷新网页,输出内容如下。此处可以优化,区域和国家联动,即选择区域后,国家里面的标签会随着变化,因为国家有区域属性,可以参考官网给的示例Dash App With Chained Callbacks。 这里使用区域和国家同时作为筛选条件相当于也是相同的结果,比如选择北美区域后,再选择中国,依次为筛选条件一定没有结果输出。
该文件的功能是依据中间左侧的筛选条件进行堆叠条状折线图的绘制。先进行基础架构的搭建,关于plotly绘制堆叠条状图,首先要在go.Figure()
中添加两个Bar
图,然后使用update_layout()
方法指定barmode='stack'
,绘制代码如下。
由于StackBarLineInfo()
函数是需要回调生成的内容,所以只需要创建一个容器保留id
即可, 下面回调是依据前面设置的三个组件。数据使用简单的示例,测试是否可以正常显示出想要的图形,下一步才是从api.py中导入已经处理好的数据加以应用。此文件的全部代码如下。
from dash import html
from dash import html,dcc,Input,Output
from app import app
import plotly.graph_objects as go
def StackBarLineInfo():
return html.Div(id='stack-content')
@app.callback(
Output('stack-content','children'),
[Input('Region','value'),Input('Country','value'),Input('Span-year','value')]
)
def update(region,country,span_year):
fig = go.Figure(
[
go.Bar(x=[1,2,3],y=[1,2,3]),
go.Bar(x=[1,2,3],y=[2,3,4]),
go.Scatter(mode='markers + lines',x=[1,2,3],y=[7,8,9])
]
)
fig.update_layout(
barmode = 'stack'
)
return [
html.H3(f'Death,Injured and Attack: {country}'),
dcc.Graph(figure=fig)
]
保存文件后,刷新网址,输出的内容如下。
图形可以满足要求后,更新数据。其中x是代表着Rangeslider组件的筛选年份跨度,第一个Bar图对应着死亡人数,第二个Bar图对应着受伤人数,折线图对应着攻击类型计数汇总(比如:炸弹 武装袭击 暗杀 人质 基础设施袭击 )。在api.ipynb文件中封装一个获取数据的函数get_data()
,括号中的参数就是筛选组件输入的值,代码及应用输出如下。
进一步把死亡、受伤和攻击类型的数据也获取到,融合到get_data()
函数中。
数据获取成功后,将此函数转移到api.py文件中,并导入到stackbarline.py文件中。此时文件中的全部代码和修改内容如下。
保存文件后,刷新网址,输出的内容如下。基本上和最终项目案例效果图中的图形趋势一致,缺乏样式的修改。
该文件针对筛选的条件,对数据中的死亡、受伤和攻击类型进行饼图占比输出。前面已经封装了get_data()
函数,这里直接拿来就可以使用。该文件中的全部代码和修改内容如下。
保存文件后,刷新网址,输出的内容如下。中间的堆叠条状折线图也可以指定name
参数,显示图例的名称,这里在后续的样式中统一进行设置也没有问题。
该文件的功能是根据筛选条件绘制散点地图。plotly中绘制散点地图的官方示例,代码如下。其中第一种方式中的px绘图是对go绘图的进一步简化封装,相当于把里面的一些过程直接封装成函数调用。
首先获取数据,之前的df数据集中只有地区和国家,不包含经纬度信息,需要重新按照需要字段进行分组统计求和,代码及输出结果如下。
将获取地理数据集的代码转移到api.py文件中,并导入到mapchart.py文件中。进一步依据回调的内容筛选数据,并根据官方示例代码中的(1)借助px绘图,此文件的全部代码及修改地方如下。其中使用maxbox绘制地图时,如果要进行风格的选择需要先注册,获取钥匙,然后赋值给accesstoken
参数。具体注册的过程可以参照 plotly绘图进阶篇(地图可视化,动态数据可视化)
保存文件后,刷新网址,输出的内容如下。
在assets文件夹下新建一个setting.css文件,设置整体风格。主要是背景、边缘和字体颜色的设置。
保存文件后,刷新网址,输出的内容如下。
文字已经居中了,就只需要调整一下头部信息与中间信息和浏览器顶部的间距即可,修改样式内容如下。
保存文件后,刷新网址,输出的内容如下。此时头部信息距离中间部分的距离就增大一些。
提示的文字信息左对齐,然后把Dropdown组件和Rangeslider组件的样式设置都可以直接参照项目11中所示。修改的css样式如下。
.card_container {
border-radius: 5px;
background-color: #070707;
margin: 16px;
padding: 15px;
position: relative;
box-shadow: 2px 2px 2px #292929;
}
/* width */
::-webkit-scrollbar {
width: 10px !important;
display: block !important;
}
/* Track */
::-webkit-scrollbar-track {
background: #232c37 !important;
border-radius: 10px !important;
display: block !important;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #363535 !important;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: white !important;
}
#Region *,#Country * {
background-color: #191a1a !important;
color: #f7f8f9 !important;
font-size: 18px !important;
text-align: center;
}
.Select-control{
border: 3px solid #323844 !important;
}
.Select-menu-outer{
overflow: hidden !important;
border-color: #373838 !important;
}
.rc-slider-track{
background-color: rgb(234, 13, 112) !important;
}
.rc-slider-handle{
border-color: rgb(234, 13, 112) !important;
}
.filter_item{
padding-bottom: 30px;
text-align: left;
margin-left: 10px;
}
保存文件后,刷新网址,输出的内容如下。中间部分的筛选组件就设置完成了。
首先添加卡片属性,再修改图像背景以及显示的文字刻度,图例等小细节问题,此文件的全部代码如下。
from dash import html
from dash import html,dcc,Input,Output
from app import app
import plotly.graph_objects as go
from api import get_data
def StackBarLineInfo():
return html.Div(
[
html.Div(id='stack-content')
],className='card_container'
)
@app.callback(
Output('stack-content','children'),
[Input('Region','value'),Input('Country','value'),Input('Span-year','value')]
)
def update(region,country,span_year):
death,injured,attack = get_data(region,country,span_year)
span_year_ls = list(range(span_year[0],span_year[1]+1))
fig = go.Figure(
[
go.Bar(x=span_year_ls,y=death,name='Death'),
go.Bar(x=span_year_ls,y=injured,name='Injured'),
go.Scatter(mode='markers + lines',x=span_year_ls,y=attack,name='Attack')
]
)
fig.update_layout(
barmode = 'stack',
titlefont = {'color':'white','size':20},
font = {'family':'sans-serif','color':'white','size':12},
hovermode = 'closest',
paper_bgcolor = '#070707',
plot_bgcolor = '#070707',
legend = {'orientation':'h','bgcolor':'#070707','xanchor':'center','x': 0.5, 'y': -0.2},
margin = {'r':0,'l':60,'b':100,'t':20},
xaxis = {'title':'Year','color':'white','showline':True,'showgrid':True,'tick0':0,'dtick':1,'gridcolor':'#010915',
'showticklabels':True,'linecolor':'white','linewidth':1,'ticks':'outside','tickfont':{'family':'sans-serif','color':'white','size':12}
},
yaxis = {'title':'Death','color':'white','showline':True,'showgrid':True,'gridcolor':'#010915',
'showticklabels':True,'linecolor':'white','linewidth':1,'ticks':'outside','tickfont':{'family':'sans-serif','color':'white','size':12}
}
)
return [
html.H3(f'Death,Injured and Attack: {country}'),
dcc.Graph(figure=fig)
]
保存文件后,刷新网址,输出的内容如下。
虽然样式都满足要求,但是留心观察发现左侧的图形和中间的图形形状并没有对齐,因此应当考虑是不是card_container属性放置错误,先把所有的该属性放置文件代码全部删除,然后在index.py文件中指定如下。
保存所有修改文件后,再次刷新网页,输出结果如下。此时对齐的问题就全部解决了,样式的设置就是一个微工程,需要根据实际的网页输出再进行调整。
修改图形背景颜色,和图例信息等。
保存所有修改文件后,再次刷新网页,输出结果如下。
添加card_container属性(这个需要在index.py中),背景颜色,字体样色,然后丰富一些交互显示信息hover_data(需要是展示地理数据集中的字段名称),也可以使用go绘制图形后使用hovertext丰富交互信息,参照项目10。
最后修改一下最后的图形的边距,如下。
保存所有修改文件后,再次刷新网页,输出结果如下。
在进行Dropdown组件获取区域和国家信息时,如果不把区域和国家进行关联,那么很可能后续图形都是空白,因为没有满足条件的数据。比如选个北美地区,再选个中国,自然也就没有数据。因此参照官网中关于链式回调 Chained Callbacks的案例,把此处的区域和国家联系起来,即选择区域后,国家只能在对应区域中选取。
按照官网中的示例需要首先有一个all_options
字典变量,其中的键是第一个筛选组件中的元素,值是第二个筛选组件中的元素。值api.ipynb文件中对数据进行处理,将区域和国家也转化成为此类型。
第一步就是按照地区的唯一值把国家进行合并,那么结果就是区域为一列,国家为一列,国家的信息都放置在列表中,并去重。
第二步就是以第一列为键,第二列为值构建字典变量。
第三步把所有的字典数据放置在一个字典中,构建all_options
字典变量。输出只截取部分。
把获取all_options
的代码转移到api.py文件中,并导入到filteritem.py文件中,在最下面添加回调函数。此时filteritem.py文件中的全部代码更新如下。
from dash import html,dcc,Input,Output
from api import year_min,year_max,all_options
from app import app
def FilterInfo():
return html.Div(
[
html.Div(
[
html.H3('Select Region:',className='filter_item'),
dcc.Dropdown(
list(all_options.keys()),
'South Asia',
id='Region'
)
]
),
html.Div(
[
html.H3(f'Select Country:',className='filter_item'),
dcc.Dropdown(
id='Country'
)
],style={'paddingTop':'40px'}
),
html.Div(
[
html.H3('Select Year:',className='filter_item'),
dcc.RangeSlider(
min=year_min,
max=year_max,
step=1,
marks=None,
value=[2000,2010],
tooltip={"placement": "bottom", "always_visible": True},
id='Span-year'
)
],style={'paddingTop':'40px'}
)
]
)
@app.callback(
Output('Country', 'options'),
Input('Region', 'value'))
def set_cities_options(selected_country):
return [{'label': i, 'value': i} for i in all_options[selected_country]]
@app.callback(
Output('Country', 'value'),
Input('Country', 'options'))
def set_cities_value(available_options):
return available_options[0]['value']
保存所有修改文件后,再次刷新网页,输出结果如下。
项目完结,撒花✿✿ヽ(°▽°)ノ✿