数据可视化 – 利用Bokeh和Bottle.py在网上展示你的数据

  • xhyl
  • February 5, 2020

本文将展示如何使用python搭建一个网页应用来展示你的数据图表
很多有关于使用python搭建网页应用的文章聚焦在如何教读者搭建一个网页应用(大多是博客),很多关于使用python做数据可视化的文章聚焦在如何教读者使用python的图表库来做可视化(大多是在jupyter notebook环境下)

在数据科学中,通过图表将数据可视化是一个很重要的工作,在开始数据分析之前,通过数据可视化可以帮助我们理解数据,而更重要的是,在完成分析、预测等等过程之后,我们需要通过数据可视化讲结论展示出来。通过网页创建可以交互的图表是展示数据的一个重要手段。

1. 文章重点和项目介绍

本文的重点将是展示如何将bokeh和bottle集成在一起,并部署到服务器上,供他人访问查阅,因此不会在bokeh和bottle,以及pandas的相关代码具体实现细节上面面俱到,但是对于我们实现的代码,还是会进行讲解(可能不会那么深入)。本文将选取中国2017到2019年的AQI数据作为项目的数据集,然后利用这些数据绘制3张表格(一张折线图和两张带分组的柱状图),然后通过bottle和bootstrap前端模板建立一个展示网页,最后会将这个网页应用部署到Heroku上边(这一步作为参考,你可以通过localhost访问本机服务,或者选取其他云服务商的服务器)。

本文使用的数据集和代码实现都可以在下边这个github仓库中找到:
https://github.com/pythonlibrary/bokeh-bottlepy
我已经将数据集进行过清理,数据集中包含规整的从2017年到2019年的各个城市的日AQI平均值。

2. 数据集研究和图表准备

在本节中,和大多数数据分析项目一样,我们将使用jupyter notebook作为我们的环境,因为这个工具能够方便的实现代码修改和及时的代码结果展示。
首先完成最重要的事情,导入必要的python库

1
2
3
4
5
6
7
8
import numpy as np
import pandas as pd

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.transform import dodge

from bokeh.io import output_notebook

然后在notebook中运行,下边代码来初始化bokeh,bokeh可以将图标输出成不同的格式文件,如html等,但是要在notebook中显示,则需要在最开始的时候指明。

1
output_notebook()

成功执行完以后,notebook会提示成功加载bokeh环境,如下:

2.1 导入数据集

我们使用pandas的read_csv方法读入数据集,并将一些城市的数据拿出来,因为读入以后date一列的数据格式不是pandas datatime,我们在这里做一个转换,方便后边绘图使用,因为数据集中还有一些其他空气质量指标例如PM2.5等,我们仅仅选取AQI作为关注重点形成新的数据帧 df

1
2
3
4
5
cities = ['上海', '北京', '杭州', '宁波', '保定', '南京', '苏州', '深圳', '厦门', '广州']
df = pd.read_csv('AQI_merged.csv')
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values(by='date').reset_index(drop=True)
df = df[df['type']=='AQI']

我们的基础数据帧(Dataframe)创建好以后,让我们来看一下它里边包含了什么数据,我们使用如下代码提取2019年的数据,并且在notebook中展示前5条记录。

1
2
df_2019_day = df[df['date']>='2019-01-01']
df_2019_day.head()

可以看出,在数据帧中,按照没一天为行,记录了当天几个城市的AQI值。

2.2 绘制图表

利用导入的数据,我们将绘制3张图表:

  • 2019年上海,北京,深圳三地的每天AQI变化曲线(曲线图)
  • 2019年上海,北京,深圳三地的每月平均AQI对比(柱状图)
  • 2017年到2019年北京每月平均AQI对比(柱状图)

图表1:2019年上海,北京,深圳三地的每天AQI变化曲线

利用刚刚我们创建的df_2019_day数据帧,使用如下代码绘制图表,注意,我们使用了Bokeh提供的ColumnDataSource的方式来给bokeh图表传递数据。

简单说明一下:我们先使用figure创建了一个空的图画,然后用line方法画了上海的数据,然后重复line方法两次在图画上添加了另外两个城市的数据,最后,通过add_tools方法添加了一个鼠标悬停提示,用于显示鼠标位置的AQI值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
source = ColumnDataSource(df_2019_day)

p = figure(x_axis_type="datetime", title="2019年AQI日均平均变化曲线", plot_width=900, plot_height=400)
p.line('date', '上海', line_color='blue', legend_label='上海', source=source)
p.line('date', '北京', line_color='green', legend_label='北京', source=source)
p.line('date', '深圳', line_color='orange', legend_label='深圳', source=source)

p.legend.location = "top_right"
p.add_tools(HoverTool(tooltips=[("AQI", "$y")]))
    
show(p)

图表2:2019年上海,北京,深圳三地的每月平均AQI对比

我们想要画出每月平均AQI,而数据帧中包含的是每日的AQI,因此,利用dataframe的groupby方法,可以求得每月的平均值。并新建了一列month来存放月信息。最后通过head方法查看下我们获得的新的数据帧是否包含了按月平均的AQI信息。

1
2
3
4
pd.options.mode.chained_assignment = None
df_2019_day['month'] = df_2019_day['date'].apply(lambda x: x.strftime('%Y-%m'))
df_2019_month = df_2019_day.groupby(by='month').mean().reset_index()
df_2019_month.head()

数据集处理结果符合我们的预期,接下来使用这个数据集绘制第二张图表。因为我们想要比较不同城市同一个月的AQI,因此我们的柱状图需要分组显示,这里使用了bokeh中的dodge方式,每一个dodge为一个城市的数据,并指明了在图表上的相对位置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
source = ColumnDataSource(df_2019_month)

p = figure(x_range=list(df_2019_month['month']), title="2019年AQI", plot_width=900, plot_height=400)

p.vbar(x=dodge('month', -0.25, range=p.x_range), top='上海', width=0.2, color="#c9d9d3", legend_label="上海", source=source)
p.vbar(x=dodge('month', 0, range=p.x_range), top='北京', width=0.2, color="#718dbf", legend_label="北京", source=source)
p.vbar(x=dodge('month', 0.25, range=p.x_range), top='深圳', width=0.2, color="#e84d60", legend_label="深圳", source=source)

p.xgrid.grid_line_color = None
p.y_range.start = 0

p.add_tools(HoverTool(tooltips=[("时间", "@month"), ("上海平均AQI", "@{上海}"), ("北京平均AQI", "@{北京}"), ("深圳平均AQI", "@{深圳}")]))

show(p)

图表3:2017年到2019年北京每月平均AQI对比

跟图表2 类似我们对数据帧进行必要的处理,同时因为我们要显示不同的年月的对比,所以讲年份和月份单独放置到year和month列中。

1
2
3
4
5
df['date_ym'] = df['date'].apply(lambda x: x.strftime('%Y-%m'))
df_month = df.groupby(by='date_ym').mean().reset_index()
df_month['month'] = df_month['date_ym'].apply(lambda x: x.split('-')[-1])
df_month['year'] = df_month['date_ym'].apply(lambda x: x.split('-')[0])
df_month.head()

然后创建3个数据帧,每个仅包含一年的数据

1
2
3
df_2017 = df_month[df_month['year']=='2017'][['month', '北京']]
df_2018 = df_month[df_month['year']=='2018'][['month', '北京']]
df_2019 = df_month[df_month['year']=='2019'][['month', '北京']]

最后,还是通过相同的bokeh方法,绘制新的柱状图。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
source_2017 = ColumnDataSource(df_2017)
source_2018 = ColumnDataSource(df_2018)
source_2019 = ColumnDataSource(df_2019)

p = figure(x_range=list(df_2017['month']), title="2017-2019年北京AQI对比", plot_width=900, plot_height=400)

p.vbar(x=dodge('month', -0.25, range=p.x_range), top='北京', width=0.2, color="#c9d9d3", legend_label="2017", source=source_2017)
p.vbar(x=dodge('month', 0, range=p.x_range), top='北京', width=0.2, color="#718dbf", legend_label="2018", source=source_2018)
p.vbar(x=dodge('month', 0.25, range=p.x_range), top='北京', width=0.2, color="#e84d60", legend_label="2019", source=source_2019)

p.xgrid.grid_line_color = None
p.y_range.start = 0

p.add_tools(HoverTool(tooltips=[("时间", "@month"), ("AQI", "@{北京}")]))

show(p)

到这里我们的3张图表已经准备好了,但是他们都是在notebook中运行的,后边我们将对这些代码进行简单的转化,并嵌入到bottle网页应用中。

3. Bottle网页应用

bottle是一个超轻量级的python web框架,我们在本文中选择了bottle而没有选择flask或者Django的原因就在于它的超轻量级,可以快速的搭建网页应用,对于以仅仅做数据展示为目的的网页应用,使用bottle可以让你快速上手,让你更专注于数据分析。

我们将采用bootstrap前端模板加bottle内置的模板引擎的方式来实现这个应用,为了快速实现这个目标,我们选取了https://github.com/arsho/bottle-bootstrap 这个项目作为我们的初始代码,所以,本文项目中使用到的网页应用代码99%的实现来自于这个项目,我们仅仅做了一点改动。在本节内容中,我们会讲解一下bottle应用的重点代码和概念。

本文对应的代码可以在 https://github.com/pythonlibrary/bokeh-bottlepy 这个仓库中找到。

3.1 文件夹结构

我们的bokeh-bottlepy项目目录结构如下,其中

  • dataset文件夹:包含了数据集csv文件
  • static文件夹:包含了bootstrap前端框架代码,包括css,JavaScript,以及fonts等,用于以bootstrap的主题来展现html页面
  • views文件夹中:包含我们要如何展示数据的模板,本项目作为入门项目,其中仅仅包含了一个index.tpl文件,作为我们仅有的一个单页面网页的模板,该模板会由bottle应用导入数据来渲染,最总形成用户看到的页面
  • app.py:为我们的入口文件,我们所有的python代码将在这个里边实现,最终运行也是通过:python app.py来启动服务
  • Procfile:涉及到Heroku部署,后边我们会提到

3.2 路由

用python web框架实现的是动态的网页,也就是说网页是在用户访问的时候生成的,路由这个概念对于第一次接触网页应用的人比较陌生,不过其实很简单,通俗的讲,用户在点击一个网页上的链接或按钮,或在浏览器地址栏中访问一个链接的时候,网页服务器端会根据链接的不同做不同的动作,并将结果组织成html并呈现给用户,这一个过程就是路由。

在bottle中实现路由其实就是给每一个url实现一个对应的处理方法。下边的代码就是本项目用到的所有相关的部分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
dirname = '.'

app = Bottle()
debug(True)

@app.route('/static/<filename:re:.*\.css>')
def send_css(filename):
    return static_file(filename, root=dirname+'/static/asset/css')

@app.route('/static/<filename:re:.*\.js>')
def send_js(filename):
    return static_file(filename, root=dirname+'/static/asset/js')

@app.route('/')
def index():
    data = {
            "developer_organization":"pythonlibrary.net"}
    return template('index', data = data)

所有bottle网页应用需要实例化一个Bottle对像,作为服务本身,这里我们起名叫app,同时打开了debug模式,即当访问url的时候,Bottle应用会打印一些调试信息辅助开发人员定位问题。

路由函数的指定是通过@app.route装饰器实现的,这个装饰器的参数就是相对url,例如index函数的路由地址为/,如果本地服务端口为8080,则绝对url为:http://localhost:8080/,用户在访问这个地址的时候index函数将会被调用,而它的返回值就是用户看到的页面,这里是使用了template方法来使用data数据渲染模板,模板的概念我们下一章节会进行介绍。

要做出一个漂亮的页面,需要使用到复杂的JavaScript和css,所幸的是我们选择的bootstrap框架为我们实现了这些复杂部分,我们只需要应用它提供的模组就可以搭建出一个漂亮的网站。

在html中,JavaScript和css也是通过url来访问到的,因此如果要使模板生效,需要告知bottle这些JavaScript和css需要从本地哪个路径中去找,代码中的send_css和send_js函数就是利用bottle 中的static_file函数来通知应用本地的资源在什么位置,而上边的路由地址则是用户访问网页的时候再html中的地址,因此这两个函数实现了,url和本地资源的连接。

3.3 模板实现

所有的Python网页框架,在不使用前后端分离的方式开发网页应用的时候,都会包含一个模板的概念,这些框架大部分都继承了自己的模板引擎,bottle中也集成了一个他们称为SimpleTemplate的简单模板引擎,当然你可以选择使用其他第三方的模板引擎,如nijia2,mako等。

所谓模板引擎其实即使基于模板关键字的替换,引擎提供了一系列的语法,引擎可以解析这些语法,做出相应的动作,例如根据不同的情况填入不同的数据,做循环,判断等等,然后其余的内容将保持不变的放到输出中,可以通过python的stringtemplate来类比。

我们这个项目中,index.tpl就是模板,里边包含了SimpleTemplate可以识别的语法以及其他内容,当SimpleTemplate解析index.tpl总的语法,并填入合适的数据,则最终会得到完整的html内容,因此模板是 html + 引擎语法的集合,至于文件后缀tpl则无关紧要,可以使任何你定义的后缀,只是一般tpl代表template。

我们对原始代码的该文件进行一些修改:将head标签中的信息,按照我们的项目进行修改,

1
2
3
<meta name="description" content="Deploy Bokeh Data Visualization with Bottlepy">

<title>China AQI</title>

然后将导航条 navbar div按照我们的要求修改成我们自己的链接,将网页主体container中最上边的文字框改成我们的项目描述。

1
2
3
4
5
6
<div id="navbar" class="navbar-collapse collapse">
  <ul class="nav navbar-nav navbar-right">
  	<li><a href="../">Home</a></li>
  	<li><a href="https://github.com/pythonlibrary/bokeh-bottlepy">On Github</a></li>
  </ul>
</div><!--/.nav-collapse -->
1
2
3
4
5
6
<div class="row">
  <div class="jumbotron">
  <h2>中国AQI数据可视化</h2>
	  <p>这是一个基于bottlepy, bokeh和Bootstrap的一个数据可视化部署的示例项目,采用了中国从2017年到2019年的AQI信息数据作为项目的演示数据。</p>
  </div>
</div>

回到app.py中,在这个文件中下边这段代码,通过template方法实现了对index模板的渲染,这个方法的参数data,将作为数据动态的传入到模板中,相对应的模板中有一个 {{data[“developer_organization”]}} 的语句,这就是模板语法,跟python语法类似,通过dict的方式访问了data变量中的developer_organization键对应的值。

1
2
3
4
5
@app.route('/')
def index():
    data = {
            "developer_organization":"pythonlibrary.net"}
    return template('index', data = data)

3.4 启动网页服务

我们在app.py实现了类似下边这样的入口,如果在终端中运行python app.py,这段代码将被执行,也就可以启动网页服务,服务的端口为8080,同时将host设置为0.0.0.0意思是其他电脑可以访问这台电脑上的服务,如果仅想本机本地访问可以设置为localhost

1
2
3
if __name__ == "__main__":
    port = 8080
    app.run(host="0.0.0.0", port=port, debug=True)

4. 将Bokeh和Bottle集成在一起

4.1 模板修改

首先我们想要在html中显示bokeh生成的图表,需要加载bokeh的JavaScript,通过在index.tpl中添加下边几个CDN的方式来导入。

1
2
3
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-1.4.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-1.4.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-1.4.0.min.js"></script>

然后我们要添加数据图表的占位符(相关的引擎语法代码),当进行模板渲染的时候,会被动态的替换为python代码中提供的内容。

在页面主体container中添加三个图表的占位符

注意:有别于其他数据传入语法,这里在data[“lot1_div”]前边有一个感叹号(!),这个非常重要,如果没有感叹号意味着,传入的数据将被认为是字符串,在渲染的时候会被引号括起来,而我们实际想要填充在这里的是html代码,而不是被双引号括起来的html代码,感叹号就是告知引擎,我们传入是的浏览器可以处理的html或者JavaScript或者css代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="row">
  {{!data["plot1_div"]}}
</div>
</br></br></br></br>
<div class="row">
  {{!data["plot2_div"]}}
</div>
</br></br></br></br>
<div class="row">
  {{!data["plot3_div"]}}
</div>

在body标签后边添加绘制图表使用的JavaScript脚本占位符

{{!data["plot_script"]}}

这里模板中未来用到的图表div和JavaScript脚本将会由bokeh生成,并有bottle渲染,我们会在加下来这一章节说明。

4.2 Python代码集成

将 2.2 章节中在notebook中调试成功的代码转换为函数,并实现到app.py中,注意原本在notebook中显示图表我们使用了show(p)的方法,在网页应用中我们仅仅是通过return p将图表对象返回,返回值将通过bottle提供的方法进行处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def get_df_from_source():
    ''' get dataframes from the source dataset, only take the data of some big cities
    '''
    cities = ['上海', '北京', '杭州', '宁波', '保定', '南京', '苏州', '深圳', '厦门', '广州']
    df = pd.read_csv(dirname+'/dataset/AQI_merged.csv')
    df['date'] = pd.to_datetime(df['date'])
    df = df.sort_values(by='date').reset_index(drop=True)
    df = df[df['type']=='AQI']
    return df

def draw_daily_AQI(mini_date, df):
    year = mini_date.split('-')[0]
    df_day = df[df['date']>=mini_date]
    source = ColumnDataSource(df_day)

    p = figure(x_axis_type="datetime", title="{}年AQI日均平均变化曲线".format(year), plot_width=1150, plot_height=400)
    p.line('date', '上海', line_color='blue', legend_label='上海', source=source)
    p.line('date', '北京', line_color='green', legend_label='北京', source=source)
    p.line('date', '深圳', line_color='orange', legend_label='深圳', source=source)

    p.legend.location = "top_right"
    p.add_tools(HoverTool(tooltips=[("AQI", "$y")]))

    return p
        
def draw_month_AQI(mini_date, df):
    year = mini_date.split('-')[0]
    df_day = df[df['date']>=mini_date]

    df_day['month'] = df_day['date'].apply(lambda x: x.strftime('%Y-%m'))
    df_month = df_day.groupby(by='month').mean().reset_index()

    source = ColumnDataSource(df_month)

    p = figure(x_range=list(df_month['month']), title="2019年AQI", plot_width=1150, plot_height=400)
    p.vbar(x=dodge('month', -0.25, range=p.x_range), top='上海', width=0.2, color="#c9d9d3", legend_label="上海", source=source)
    p.vbar(x=dodge('month', 0, range=p.x_range), top='北京', width=0.2, color="#718dbf", legend_label="北京", source=source)
    p.vbar(x=dodge('month', 0.25, range=p.x_range), top='深圳', width=0.2, color="#e84d60", legend_label="深圳", source=source)
    p.xgrid.grid_line_color = None
    p.y_range.start = 0
    p.add_tools(HoverTool(tooltips=[("时间", "@month"), ("上海平均AQI", "@{上海}"), ("北京平均AQI", "@{北京}"), ("深圳平均AQI", "@{深圳}")]))
        
    return p

def draw_year_AQI(df):
    df['date_ym'] = df['date'].apply(lambda x: x.strftime('%Y-%m'))
    df_month = df.groupby(by='date_ym').mean().reset_index()
    df_month['month'] = df_month['date_ym'].apply(lambda x: x.split('-')[-1])
    df_month['year'] = df_month['date_ym'].apply(lambda x: x.split('-')[0])

    df_2017 = df_month[df_month['year']=='2017'][['month', '北京']]
    df_2018 = df_month[df_month['year']=='2018'][['month', '北京']]
    df_2019 = df_month[df_month['year']=='2019'][['month', '北京']]

    source_2017 = ColumnDataSource(df_2017)
    source_2018 = ColumnDataSource(df_2018)
    source_2019 = ColumnDataSource(df_2019)

    p = figure(x_range=list(df_2017['month']), title="2017-2019年北京AQI对比", plot_width=1150, plot_height=400)

    p.vbar(x=dodge('month', -0.25, range=p.x_range), top='北京', width=0.2, color="#c9d9d3", legend_label="2017", source=source_2017)
    p.vbar(x=dodge('month', 0, range=p.x_range), top='北京', width=0.2, color="#718dbf", legend_label="2018", source=source_2018)
    p.vbar(x=dodge('month', 0.25, range=p.x_range), top='北京', width=0.2, color="#e84d60", legend_label="2019", source=source_2019)

    p.xgrid.grid_line_color = None
    p.y_range.start = 0

    p.add_tools(HoverTool(tooltips=[("时间", "@month"), ("AQI", "@{北京}")]))

    return p

三个绘图函数返回了图表对象p,我们如果能够让bottle来渲染图表对象,从而实现在网页中的图表展示呢?bokeh提供了一个components方法,可以接收图表对象作为参数,而返回绘图使用的JavaScript脚本和图表div,因此修改我们的index路由函数为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@app.route('/')
def index():
    df = get_df_from_source()
    plot1 = draw_daily_AQI('2019-01-01', df=df)
    plot2 = draw_month_AQI('2019-01-01', df=df)
    plot3 = draw_year_AQI(df=df)
    plots_data = components((plot1, plot2, plot3))

    data = {
            "plot_script":plots_data[0],
            "plot1_div":plots_data[1][0],
            "plot2_div":plots_data[1][1],
            "plot3_div":plots_data[1][2],
            "developer_organization":"pythonlibrary.net"}
    return template('index', data = data)

在这里,index.tpl模板中的data字典中的plot1_div,plot2_div,plot3_div以及plot_script将被动态的渲染替换。最终实现了将图表展示在网页上的目的。

你可以clone本项目的仓库来尝试运行,或者直接访问http://china-aqi-data-visulazition.herokuapp.com/ 来查看效果

5. 部署应用到Heroku

这部分内容跟怎么将数据图表展示在网页上没有直接的关系,仅仅是一种可选的免费云服务,可以供你来共享你的页面,或者了解网页部署。但其实不同的服务可能部署的方式并不相同,因此如果你要部署你的网页到其他服务提供商,可能这里的知识完全不适用。

在Heroku上用户可以免费部署有限的网络应用,同时过程也非常的简单,只需要实现一个Procfile文件,Heroku系统就知道怎么运行你的服务了。我们的项目中Procfile使用如下代码,跟我们本地运行服务类似。

web: python app.py

而针对app.py的入口代码,需要将port改为从环境变量读取,因为Heroku会动态的为应用分配端口,如果指定一个固定值,则会因为Heroku没有打开其的对外访问,而导致用户无法访问该服务。

1
2
3
if __name__ == "__main__":
    port=int(os.environ.get("PORT", 8080))
    app.run(host="0.0.0.0", port=port, debug=True

最后用户可以在Heroku页面上选择将github仓库和应用连接在一起,那么系统会自动的从github拉取最新代码然后启动服务。

6. 参考文档

类似文章

PyQt5教程4 – 发现局域网的其他客户端

PyQt5教程4 – 发现局域网的其他客户端

  • xhyl
  • January 29, 2020

到目前为止我们已经有了比较完整的用户交互界面,在使用airdrop发送文件的时候,用户可以选择发送给哪一台设备,在这一章中,我们在PyQt5教程3 – 发送页面进度条,自定义信号槽和线程 的基础上在WiFi Drop上实现类似的功能,即当我们选中文件发送的时候,接下来软件需要能够搜索到局域网中其他的客户端。

阅读更多
PyQt5教程3 – 发送页面进度条,自定义信号槽和线程

PyQt5教程3 – 发送页面进度条,自定义信号槽和线程

  • xhyl
  • January 22, 2020

在airdrop,当用户想要发送文件出去的时候,需要选择目标机器或者用户,在随后的发送中还会显示发送进度,针对这一交互逻辑,在WiFidrop中,我们也将设计相同的交互逻辑,我们要增加一个发送页面,在这个页面上将能够显示我们可以发送的对象,以及一个进度条。 这一篇文章中,我们将继续在 PyQt5教程2:主页面和拖动 的基础上,加入发送页面,和一些相应的逻辑。

阅读更多
PyQt5教程2:主页面和拖动

PyQt5教程2:主页面和拖动

  • xhyl
  • January 15, 2020

这一篇文章中,我们要实现的是主界面和主界面上需要支持的一些功能,我们将使用Qt Creator来创建我们的基本主界面。 1. UI设计 打开Qt Creator,在文件菜单中选择“新建文件或项目”,因为我们只是用Qt Creator来设计UI,所以我们只用它来创建和编辑.

阅读更多