1.数据可视化技术概述
1.1 前端开发可视化技术
Web开发是指创建和维护网站或网络应用程序的过程,涉及从前端用户界面到后端数据管理和逻辑处理的所有方面。Web开发是一个多领域交叉的活动,它融合了设计、编码、数据库管理、服务器配置等多种技能。
而前端开发可视化技术是web开发的一部分内容,常用的前端开发可视化技术有ECharts、D3.js、Highcharts 等。
ECharts 是一个由百度开源的 JavaScript 图表库,提供了丰富的图表类型,包括折线图、柱状图、饼图、地图、热力图等,并且支持自定义图表。其特点是:
- 丰富的图表类型:几乎涵盖了所有常见的数据展示需求。
- 高度可定制:可以通过配置项调整图表的样式和行为。
- 交互性:支持鼠标事件,如缩放、漫游、提示框等。
- 性能优化:对于大数据量有较好的性能表现。
- 跨平台:兼容各种浏览器和移动设备。
D3.js (Data-Driven Documents) 是一个强大的 JavaScript 库,用于操作文档中的数据并将其转换为可视化。D3.js 更偏向于数据驱动的设计,允许开发者完全控制每个细节。其特点是:
- 灵活性:可以完全控制SVG元素,适用于复杂和定制化的可视化需求。
- 数据绑定:自动将数据与DOM元素关联。
- 过渡和动画:可以创建平滑的动画效果。
Highcharts 是一个商业化的图表库,也提供免费的版本。它专注于易于集成和快速生成图表。其特点是:
- 易用性:提供简单而强大的API。
- 多平台支持:除了Web,还支持移动设备和桌面应用。
1.2 Python可视化技术
Python是数据科学和数据分析领域中最流行的语言之一,它拥有丰富的可视化库,可以满足从简单的数据探索到复杂的交互式图表的各种需求。matplotlib、seaborn和pyecharts是三个常用的Python可视化库。
matplotlib是Python中历史最悠久、最基础的可视化库之一。它提供了大量的绘图API,可以创建静态、动态、交互式的图表。matplotlib的灵活性很高,允许用户精确地控制图表的每一个细节,从线条样式、标记到轴标签、图例等。其特点是:
- 高度可定制:可以控制图表的几乎每一部分。
- 广泛的图表类型:包括线图、散点图、条形图、直方图、饼图、3D图表等。
- 支持多种输出格式:可以将图表保存为PNG、PDF、EPS、SVG等格式。
- 社区支持:有大量的文档、教程和示例代码。
使用场景有:
- 适用于需要高度定制和精确控制的科研和工程应用。
- 数据探索和初步分析。
- 创建出版质量的图表。
seaborn建立在matplotlib之上,旨在提供更高级的接口,简化了许多常见数据可视化任务,特别是在统计图形方面。seaborn的设计哲学是使得创建复杂的统计图表变得容易,同时保持美观的默认样式。其特点是:
- 统计图形:专门针对统计数据的分布和关系。
- 美观的默认样式:默认的色彩方案和字体设置更现代、美观。
- 集成数据分析库:与pandas数据结构紧密集成,方便数据处理和绘图。
- 高级图形:如热力图、小提琴图、联合图等。
使用场景有:
- 数据分析和统计可视化,特别是对于复杂数据集的分布和相关性研究。
- 快速生成专业级的统计图表,无需过多的样式定制。
pyecharts是一个用于生成ECharts图表的Python库,ECharts是由百度开发的一个强大的JavaScript图表库。pyecharts允许开发者在Python环境中使用ECharts的全部功能,生成的图表可以嵌入到Web应用中。可以参考其官网:https://pyecharts.org/#/zh-cn/intro,进行学习。其特点是:
- 丰富的图表类型:提供多种动态和交互式图表,包括地图、桑基图、漏斗图、K线图等。
- 交互性:生成的图表支持鼠标事件、缩放、漫游等交互功能。
- Web兼容性:生成的图表可以直接在Web浏览器中查看和操作。
- 中文支持:内置中文标签和注释,适合中文数据的可视化。
使用场景有:
- 创建动态和交互式的Web图表,适合集成到Web应用或数据大屏中。
- 大数据量的可视化,pyecharts在处理大量数据时表现良好。
- 需要高度交互性和动态效果的图表展示。
1.3 专业数据可视化软件
随着数据可视化技术的发展,专用可视化软件成为部分企业开发数据可视化应用的选择,目前比较流行的数据可视化软件有:Tableau、Power BI等。
Tableau 是一款流行的商业智能工具,用于创建交互式的仪表板和报告。其特点是:
- 拖放界面:无需编程知识即可创建可视化。
- 数据连接:支持多种数据源,包括本地文件、数据库和云服务。
- 共享和协作:可以轻松地在组织内部分享可视化。
Microsoft Power BI 是一个业务分析服务,提供数据可视化和报告功能。其特点是:
- 企业级集成:与Office 365和其他Microsoft产品无缝集成。
自服务和IT管理:支持自服务BI同时提供IT控制选项。
我们就用ECharts来演示一下
ECharts
基础折线图:
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line',
smooth: true
}
]
};
基础柱状图:
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar'
}
]
};
某站点用户Access From:
option = {
title: {
text: 'Referer of a Website',
subtext: 'Fake Data',
left: 'center'
},
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
基础散点图:
option = {
xAxis: {},
yAxis: {},
series: [
{
symbolSize: 20,
data: [
[10.0, 8.04],
[8.07, 6.95],
[13.0, 7.58],
[9.05, 8.81],
[11.0, 8.33],
[14.0, 7.66],
[13.4, 6.81],
[10.0, 6.33],
[14.0, 8.96],
[12.5, 6.82],
[9.15, 7.2],
[11.5, 7.2],
[3.03, 4.23],
[12.2, 7.83],
[2.02, 4.47],
[1.05, 3.33],
[4.05, 4.96],
[6.03, 7.24],
[12.0, 6.26],
[12.0, 8.84],
[7.08, 5.82],
[5.02, 5.68]
],
type: 'scatter'
}
]
};
基础 K 线图:
option = {
xAxis: {
data: ['2017-10-24', '2017-10-25', '2017-10-26', '2017-10-27']
},
yAxis: {},
series: [
{
type: 'candlestick',
data: [
[20, 34, 10, 38],
[40, 35, 30, 50],
[31, 38, 33, 44],
[38, 15, 5, 42]
]
}
]
};
笛卡尔坐标系上的热力图:
// prettier-ignore
const hours = [
'12a', '1a', '2a', '3a', '4a', '5a', '6a',
'7a', '8a', '9a', '10a', '11a',
'12p', '1p', '2p', '3p', '4p', '5p',
'6p', '7p', '8p', '9p', '10p', '11p'
];
// prettier-ignore
const days = [
'Saturday', 'Friday', 'Thursday',
'Wednesday', 'Tuesday', 'Monday', 'Sunday'
];
// prettier-ignore
const data = [[0, 0, 5], [0, 1, 1], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 2], [0, 12, 4], [0, 13, 1], [0, 14, 1], [0, 15, 3], [0, 16, 4], [0, 17, 6], [0, 18, 4], [0, 19, 4], [0, 20, 3], [0, 21, 3], [0, 22, 2], [0, 23, 5], [1, 0, 7], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [1, 6, 0], [1, 7, 0], [1, 8, 0], [1, 9, 0], [1, 10, 5], [1, 11, 2], [1, 12, 2], [1, 13, 6], [1, 14, 9], [1, 15, 11], [1, 16, 6], [1, 17, 7], [1, 18, 8], [1, 19, 12], [1, 20, 5], [1, 21, 5], [1, 22, 7], [1, 23, 2], [2, 0, 1], [2, 1, 1], [2, 2, 0], [2, 3, 0], [2, 4, 0], [2, 5, 0], [2, 6, 0], [2, 7, 0], [2, 8, 0], [2, 9, 0], [2, 10, 3], [2, 11, 2], [2, 12, 1], [2, 13, 9], [2, 14, 8], [2, 15, 10], [2, 16, 6], [2, 17, 5], [2, 18, 5], [2, 19, 5], [2, 20, 7], [2, 21, 4], [2, 22, 2], [2, 23, 4], [3, 0, 7], [3, 1, 3], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [3, 6, 0], [3, 7, 0], [3, 8, 1], [3, 9, 0], [3, 10, 5], [3, 11, 4], [3, 12, 7], [3, 13, 14], [3, 14, 13], [3, 15, 12], [3, 16, 9], [3, 17, 5], [3, 18, 5], [3, 19, 10], [3, 20, 6], [3, 21, 4], [3, 22, 4], [3, 23, 1], [4, 0, 1], [4, 1, 3], [4, 2, 0], [4, 3, 0], [4, 4, 0], [4, 5, 1], [4, 6, 0], [4, 7, 0], [4, 8, 0], [4, 9, 2], [4, 10, 4], [4, 11, 4], [4, 12, 2], [4, 13, 4], [4, 14, 4], [4, 15, 14], [4, 16, 12], [4, 17, 1], [4, 18, 8], [4, 19, 5], [4, 20, 3], [4, 21, 7], [4, 22, 3], [4, 23, 0], [5, 0, 2], [5, 1, 1], [5, 2, 0], [5, 3, 3], [5, 4, 0], [5, 5, 0], [5, 6, 0], [5, 7, 0], [5, 8, 2], [5, 9, 0], [5, 10, 4], [5, 11, 1], [5, 12, 5], [5, 13, 10], [5, 14, 5], [5, 15, 7], [5, 16, 11], [5, 17, 6], [5, 18, 0], [5, 19, 5], [5, 20, 3], [5, 21, 4], [5, 22, 2], [5, 23, 0], [6, 0, 1], [6, 1, 0], [6, 2, 0], [6, 3, 0], [6, 4, 0], [6, 5, 0], [6, 6, 0], [6, 7, 0], [6, 8, 0], [6, 9, 0], [6, 10, 1], [6, 11, 0], [6, 12, 2], [6, 13, 1], [6, 14, 3], [6, 15, 4], [6, 16, 0], [6, 17, 0], [6, 18, 0], [6, 19, 0], [6, 20, 1], [6, 21, 2], [6, 22, 2], [6, 23, 6]]
.map(function (item) {
return [item[1], item[0], item[2] || '-'];
});
option = {
tooltip: {
position: 'top'
},
grid: {
height: '50%',
top: '10%'
},
xAxis: {
type: 'category',
data: hours,
splitArea: {
show: true
}
},
yAxis: {
type: 'category',
data: days,
splitArea: {
show: true
}
},
visualMap: {
min: 0,
max: 10,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: '15%'
},
series: [
{
name: 'Punch Card',
type: 'heatmap',
data: data,
label: {
show: true
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
漏斗图:
option = {
title: {
text: 'Funnel'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
dataView: { readOnly: false },
restore: {},
saveAsImage: {}
}
},
legend: {
data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
},
series: [
{
name: 'Funnel',
type: 'funnel',
left: '10%',
top: 60,
bottom: 60,
width: '80%',
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
label: {
show: true,
position: 'inside'
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid'
}
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
emphasis: {
label: {
fontSize: 20
}
},
data: [
{ value: 60, name: 'Visit' },
{ value: 40, name: 'Inquiry' },
{ value: 20, name: 'Order' },
{ value: 80, name: 'Click' },
{ value: 100, name: 'Show' }
]
}
]
};
基础雷达图:
option = {
title: {
text: 'Basic Radar Chart'
},
legend: {
data: ['Allocated Budget', 'Actual Spending']
},
radar: {
// shape: 'circle',
indicator: [
{ name: 'Sales', max: 6500 },
{ name: 'Administration', max: 16000 },
{ name: 'Information Technology', max: 30000 },
{ name: 'Customer Support', max: 38000 },
{ name: 'Development', max: 52000 },
{ name: 'Marketing', max: 25000 }
]
},
series: [
{
name: 'Budget vs spending',
type: 'radar',
data: [
{
value: [4200, 3000, 20000, 35000, 50000, 18000],
name: 'Allocated Budget'
},
{
value: [5000, 14000, 28000, 26000, 42000, 21000],
name: 'Actual Spending'
}
]
}
]
};
ECharts可以画的远不如此
有需要的也可以搜索此网站自行查询Apache EChartsApache ECharts
我们来用ECharts来画一张数据大屏
以柳州螺狮粉为例,画出它的数据大屏
首先准备好一个文件夹
-
static:
- css: 用于存放CSS文件,这些文件定义了网页的样式。
- font: 用于存放字体文件,这些文件可以用于网页上的自定义字体。
- images: 用于存放图片文件,这些文件可以用于网页上的图像展示。
- js: 用于存放JavaScript文件,这些文件用于实现网页上的交互功能。
-
templates: 通常用于存放HTML模板文件,这些文件定义了网页的结构和布局。在Web框架如Flask中,这些模板文件会被渲染并返回给客户端。
-
app.py: 这是一个Python文件,通常用于定义Web应用的主程序。在Flask框架中,
app.py
文件中会定义Flask应用实例,并配置路由、视图函数等。 -
test.ipynb: 这是一个Jupyter Notebook文件,通常用于数据科学项目中的数据探索、分析和可视化。Jupyter Notebook是一种交互式计算环境,支持代码和文本的混合编写。
-
utils.py: 这是一个Python文件,通常用于存放一些通用的辅助函数或类,这些函数或类可以在项目中被多个地方使用。
写一个python文件
from flask import Flask, request, jsonify, render_template,redirect,url_for
app = Flask(__name__)
import utils
import pandas as pd
import random
# 预设的账号密码和图形页面映射
# 模拟用户数据存储,实际应用中应使用数据库来保存用户信息。
USERS = {
'12345': {'password': '123', 'graph': 'charts.html'},# 用户ID: {密码, 图形文件名}
}
@app.route('/')
def index():
"""
主页路由。
当用户访问网站根路径时,此函数会被调用,它会渲染并返回index.html模板。
"""
return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
"""
登录页面路由,支持GET和POST请求。
- GET请求:当用户直接访问/login路径时,显示登录表单(login.html)。
- POST请求:处理用户提交的登录信息,验证用户名和密码是否匹配。
如果匹配成功,则重定向到图形页面;否则,重定向回登录页面。
"""
if request.method == 'POST':
# 从POST请求中获取用户输入的用户名和密码
username = request.form['username']
password = request.form['password']
# 尝试从模拟的用户字典中获取用户信息
user_info = USERS.get(username)
# 验证用户名和密码是否正确
if user_info and user_info['password'] == password:
# 登录成功,重定向到对应的图形页面
return redirect(url_for('graph', graph=user_info['graph']))
else:
# 登录失败,重定向回登录页面
return redirect(url_for('login'))
# 如果是GET请求,或者POST请求但验证失败,渲染登录页面
return render_template('login.html')
@app.route('/graph/<graph>')
def graph(graph):
"""
图形页面路由,接受一个名为'graph'的URL参数,该参数指定了要渲染的图形HTML文件名。
此函数尝试根据提供的图形文件名渲染相应的HTML页面。
"""
return render_template(graph)
# 柱形图数据
@app.route('/bar')
def bar():
"""
定义一个Flask路由,当用户访问'/bar'路径时触发此函数。
此函数负责获取并处理各省的总销量数据,最后以JSON格式返回给客户端。
"""
# 从utils模块中调用get_province_data函数来读取MySQL数据库中的省销量数据
bar_data = utils.get_province_data()
# 将获取到的数据转换成pandas DataFrame对象,方便后续的数据处理
df_pro = pd.DataFrame(bar_data,columns=['省份','总销量']) #把数据转成表格
# 对总销量数据进行处理:将每个销量值除以10000,并保留两位小数
df_pro['总销量'] = (df_pro['总销量']/10000).round(2)
# 对DataFrame按照'总销量'列进行降序排序,并取前10行
# 这样可以确保只展示销量最高的10个省份
df_pro = df_pro.sort_values(by='总销量',ascending=False).head(10)
return jsonify(df_pro.to_dict(orient='list'))
# 折线图数据
@app.route('/line')
def line():
"""
定义一个Flask路由,当用户访问'/line'路径时触发此函数。
此函数负责获取并处理每日销售数据,最后以JSON格式返回给客户端。
"""
# 从utils模块中调用get_daily_data函数来获取原始销售数据
line_data = utils.get_daily_data()
# 将获取到的数据转换成pandas DataFrame对象,方便后续的数据处理
df = pd.DataFrame(line_data, columns=['日期','销量'])
# 对销量数据进行处理:将每个销量值除以10000,并保留两位小数
df['销量'] = (df['销量'] / 10000).round(2)
# 处理日期列,将其转换为月-日的字符串格式(例如:03-15)
df['日期'] = df['日期'].dt.strftime('%m-%d')
# 使用pandas的to_dict方法将DataFrame转换为字典,orient='list'参数表示将每列作为列表
return jsonify(df.to_dict(orient='list'))
# 地图数据-all
@app.route('/map')
def map():
"""
定义一个Flask路由,当用户访问'/map'路径时触发此函数。
此函数负责获取并处理各省的销售数据和辣度数据,最后以JSON格式返回给客户端。
"""
# 从utils模块中调用get_province_data函数来读取MySQL数据库中的省销量数据
map_data = utils.get_province_data()
# 从utils模块中调用get_spicy_data函数来读取MySQL数据库中的省辣度数据
map_spicy = utils.get_spicy_data()
# 初始化两个空列表,用于存储处理后的销售数据和辣度数据
list_map = []
spicy_map = []
# 遍历map_data,将每个元组转换为字典格式,并添加到list_map列表中
# 每个字典包含两个键:'name'(省份名称)和'value'(销售数据)
for i in map_data:
list_map.append({'name':i[0], 'value':i[1]}) #对数据库中的数据处理成 地图 所需要的格式[{},{}]
# 遍历map_spicy,将每个元组转换为字典格式,并添加到spicy_map列表中
# 每个字典包含两个键:'name'(省份名称)和'value'(辣度数据)
for i in map_spicy:
spicy_map.append({'name':i[0], 'value':i[1]})
# 将两个列表封装在一个字典中
data = {
'list_map': list_map, # 销售数据列表
'spicy_map': spicy_map # 辣度数据列表
}
# 使用Flask的jsonify函数将Python字典转换为JSON响应对象,并返回给客户端
return jsonify(data)
# 饼图数据
@app.route('/taste')
def pie():
"""
定义一个Flask路由,当用户访问'/taste'路径时触发此函数。
此函数负责获取并处理不同口味的销售数据,最后以JSON格式返回给客户端。
"""
# 从utils模块中调用get_taste_data函数来读取MySQL数据库中的口味销售数据
pie_data = utils.get_taste_data()
# 将获取到的数据转换成pandas DataFrame对象,方便后续的数据处理
# 这里指定了六列的名称:'原味', '加辣', '酸菜', '番茄', '加臭', '升级'
df = pd.DataFrame(pie_data, columns=['原味', '加辣', '酸菜', '番茄', '加臭', '升级'])
# 初始化一个空列表,用于存储处理后的口味销售数据
list_pie = []
# 遍历DataFrame的列名,将每个列名及其对应的第一个值(假设是总销量)转换为字典格式,并添加到list_pie列表中
# 每个字典包含两个键:'name'(口味名称)和'value'(销量数据)
for i in df:
list_pie.append({'name':i,'value':df[i][0]}) #转成字典格式
# 使用Flask的jsonify函数将Python列表转换为JSON响应对象,并返回给客户端
return jsonify(list_pie)
# 词云图数据
@app.route('/word')
def wordCloud():
"""
定义一个Flask路由,当用户访问'/word'路径时触发此函数。
此函数负责获取各城市的名称数据,并为每个城市随机生成一个权重值,
最后以JSON格式返回给客户端,用于绘制词云图。
"""
# 从utils模块中调用get_city_data函数来读取MySQL数据库中的城市名称数据
city_data = utils.get_city_data()
# 初始化一个空列表,用于存储处理后的城市名称及其对应的随机权重值
list_word = []
# 遍历city_data,假设city_data是一个嵌套的列表(每个元素也是一个列表)
# 对于每个内部列表中的每个城市名称,生成一个随机权重值,并将其转换为字典格式添加到list_word列表中
for x in city_data:
for y in x:
list_word.append({'name':y, 'value':random.randint(1,10000)})
# 使用Flask的jsonify函数将Python列表转换为JSON响应对象,并返回给客户端
return jsonify(list_word)
# 今日销量
@app.route('/today')
def today_sale():
"""
定义一个Flask路由,当用户访问'/today'路径时触发此函数。
此函数负责获取今日的销售数据,并以JSON格式返回给客户端。
"""
# 从utils模块中调用get_today_data函数来读取今日的销售数据
today_sale = utils.get_today_data()
# 使用Flask的jsonify函数将Python对象转换为JSON响应对象,并返回给客户端
return jsonify(today_sale)
# 累计销量
@app.route('/total')
def total_sale():
"""
定义一个Flask路由,当用户访问'/todal'路径时触发此函数。
此函数负责获取累计的销售数据,并以JSON格式返回给客户端。
"""
# 从utils模块中调用get_today_data函数来读取累计的销售数据
total_sale = utils.get_total_data()
# 使用Flask的jsonify函数将Python对象转换为JSON响应对象,并返回给客户端
return jsonify(total_sale)
# 数据大屏的路由
@app.route('/')
def charts():
return render_template('charts.html')
# json数据显示中文
app.config['JSON_AS_ASCII'] = False
if __name__ == '__main__':
app.run('0.0.0.0', debug=True)
Flask
是Web应用的核心类。request
用于处理HTTP请求。jsonify
用于将Python对象转换为JSON格式响应。render_template
用于渲染HTML模板。redirect
和url_for
用于重定向到其他页面或路由
USERS = {
'12345': {'password': '123', 'graph': 'charts.html'}, # 用户ID: {密码, 图形文件名}
}
这是一个模拟的用户字典,实际应用中应该使用数据库来存储用户信息。每个用户有一个唯一的ID、密码以及对应的图形页面。
@app.route('/')
def index():
return render_template('index.html')
定义了主页的路由,当用户访问根路径时,会渲染并返回index.html
页面
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user_info = USERS.get(username)
if user_info and user_info['password'] == password:
return redirect(url_for('graph', graph=user_info['graph']))
else:
return redirect(url_for('login'))
return render_template('login.html')
- 处理用户的登录逻辑,支持GET和POST请求。
- 如果是POST请求,则验证用户名和密码是否匹配。如果匹配成功,重定向到对应的图形页面;否则,重定向回登录页面。
- 如果是GET请求,或者POST请求但验证失败,渲染登录页面。
@app.route('/graph/<graph>') def graph(graph): return render_template(graph)
根据URL参数
graph
指定的图形文件名,渲染相应的HTML页面。 -
数据接口
接下来是一系列的数据接口,用于提供不同类型的图表数据(柱状图、折线图、地图、饼图、词云图等):
- 柱状图数据 (
/bar
): 获取各省的总销量数据,并按销量降序排序后返回前10个省份的数据。 - 折线图数据 (
/line
): 获取每日销售数据,并处理日期格式后返回。 - 地图数据 (
/map
): 获取各省的销售数据和辣度数据,分别处理后以列表形式返回。 - 饼图数据 (
/taste
): 获取不同口味的销售数据,并将其转换为适合饼图显示的格式。 - 词云图数据 (
/word
): 获取各城市的名称数据,并为每个城市随机生成一个权重值,用于绘制词云图。 - 今日销量 (
/today
) 和 累计销量 (/total
): 分别获取今日和累计的销售数据。@app.route('/') def charts(): return render_template('charts.html')
定义了一个数据大屏的路由,实际上这个路由与主页路由冲突了,因为都是
/
。通常应该使用不同的路径来区分它们,比如/charts
。
app.config['JSON_AS_ASCII'] = False
设置Flask应用的配置项,确保返回的JSON响应中包含的中文字符不会被转义为ASCII编码。
if __name__ == '__main__':
app.run('0.0.0.0', debug=True)
app.run('0.0.0.0', debug=True)
启动Flask应用,监听所有网络接口,并启用调试模式。这在开发环境中非常有用,因为它会在代码更改时自动重启服务器,并提供有用的错误信息。
创建一个HTML文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据大屏</title>
<script src="../static/js/echarts.min.js"></script>
<script src="../static/js/jquery-3.7.1.min.js"></script>
<script src="../static/js/china.js"></script>
<link rel="stylesheet" href="../static/css/charts.css" />
</head>
<body>
<div class="title">袋装螺蛳粉销量数据大屏</div>
<div class="mainbox">
<div class="column-1">
<div class="charts" id="bar"></div>
<div class="charts" id="line"></div>
</div>
<div class="column-2">
<div class="number-title">
<div>今日销量</div>
<div>累计销量</div>
</div>
<div class="number">
<div id="today">000000</div>
<div id="total">000000</div>
</div>
<div class="option">
<label for="select">产品类型</label>
<select name="select" id="select">
<option value="all">所有</option>
<option value="spicy">加辣款</option>
</select>
</div>
<div class="map" id="map"></div>
</div>
<div class="column-1">
<div class="charts" id="taste"></div>
<div class="charts" id="word"></div>
</div>
</div>
<script src="../static/js/bar.js"></script>
<script src="../static/js/line.js"></script>
<script src="../static/js/map.js"></script>
<script src="../static/js/taste.js"></script>
<script src="https://assets.pyecharts.org/assets/echarts.min.js"></script>
<script src="https://assets.pyecharts.org/assets/echarts-wordcloud.min.js"></script>
<script src="../static/js/word.js"></script>
<script src="../static/js/today.js"></script>
<script src="../static/js/total.js"></script>
</body>
</html>
<!DOCTYPE html>
声明文档类型为HTML5。<html lang="en">
设置文档的语言为英语。如果页面内容是中文,可以改为lang="zh"
。<meta charset="UTF-8">
设置字符编码为UTF-8,确保页面可以正确显示中文字符。<meta name="viewport" content="width=device-width, initial-scale=1.0">
设置视口(viewport),使页面在移动设备上能够正确缩放。<title>数据大屏</title>
设置页面标题为“数据大屏”。<script>
标签引入了多个JavaScript文件:echarts.min.js
: ECharts库的核心文件,用于绘制图表。jquery-3.7.1.min.js
: jQuery库,简化DOM操作和AJAX请求。china.js
: ECharts的中国地图扩展文件,用于绘制中国地图。
<link>
标签引入了CSS文件charts.css
,用于设置页面样式。<div class="title">袋装螺蛳粉销量数据大屏</div>
: 页面顶部的标题,显示“袋装螺蛳粉销量数据大屏”。<div class="mainbox">
: 主要内容区域,包含三个列布局(column-1
和column-2
)。- 第一列 (
column-1
):- 包含两个图表容器:柱状图 (
id="bar"
) 和折线图 (id="line"
).
- 包含两个图表容器:柱状图 (
- 第二列 (
column-2
):- 数字显示区:
number-title
显示“今日销量”和“累计销量”的标签。number
包含两个div
,分别用于显示今日销量 (id="today"
) 和累计销量 (id="total"
),初始值为000000
。
- 选择器:
option
包含一个下拉菜单 (select
),用户可以选择查看“所有”或“加辣款”的销售数据。
- 地图:
id="map"
用于绘制中国地图,展示各省的销售数据。
- 数字显示区:
- 第三列 (
column-1
):- 包含两个图表容器:饼图 (
id="taste"
) 和词云图 (id="word"
). - 这些
<script>
标签引入了多个JavaScript文件,每个文件负责初始化和更新特定的图表或数据:bar.js
: 初始化并更新柱状图。line.js
: 初始化并更新折线图。map.js
: 初始化并更新中国地图。taste.js
: 初始化并更新饼图。word.js
: 初始化并更新词云图。today.js
: 更新今日销量数据。total.js
: 更新累计销量数据。
- 另外,还引入了两个来自
pyecharts
的CDN资源:echarts.min.js
: ECharts库的核心文件。echarts-wordcloud.min.js
: ECharts的词云图扩展插件。
- 包含两个图表容器:饼图 (
- 第一列 (
柱形图:
var chartDom_bar = document.getElementById('bar');
var myChart_bar = echarts.init(chartDom_bar);
// 配置项
option_bar = {
title: {
text: '各省份销量柱形图',
left: 'center', // 标题居中
textStyle: { // 标题文字样式
color: 'white', // 设置标题颜色为深灰色
fontSize: 14, // 设置字体大小(可选)
fontWeight: 'bold' // 设置字体加粗(可选)
}
},
axisLabel: {
color: '#fff' // 设置xy轴数值标签颜色
},
xAxis: {
type: 'category',
data: [],
},
yAxis: {
type: 'value'
},
series: [{
data: [],
type: 'bar',
}],
tooltip: {}
};
// ajax获取路由数据
$.ajax({
url: "/bar",
success: function (data) {
option_bar.xAxis.data = data['省份']
option_bar.series[0].data = data['总销量']
// 配置项作用到图形中
myChart_bar.setOption(option_bar);
},
error: function (xhr, type, errorThrown) { }
})
document.getElementById('bar')
: 获取HTML中id="bar"
的DOM元素,这个元素是用来容纳柱状图的容器。echarts.init(chartDom_bar)
: 使用ECharts库初始化一个图表实例,绑定到chartDom_bar
元素上。返回的myChart_bar
对象就是ECharts的图表实例,后续可以通过它来设置图表的配置项和数据。title
: 配置图表的标题。text
: 标题文本为“各省份销量柱形图”。left: 'center'
: 标题居中显示。textStyle
: 设置标题的文字样式,包括颜色、字体大小和字体加粗。
axisLabel
: 设置x轴和y轴的标签颜色为白色,确保在深色背景下清晰可见。xAxis
: 配置x轴为类别轴(type: 'category'
),初始时data
为空数组,稍后会通过AJAX请求填充实际的省份数据。yAxis
: 配置y轴为数值轴(type: 'value'
)。series
: 定义图表的数据系列,类型为柱状图(type: 'bar'
),初始时data
为空数组,稍后会通过AJAX请求填充实际的销量数据。tooltip
: 工具提示框组件,目前未配置具体属性,可以后续根据需要添加更多的配置。$.ajax
: 使用jQuery的ajax
方法发起HTTP GET请求,获取服务器端提供的销售数据。url: "/bar"
: 请求的URL路径为/bar
,这是Flask应用中的一个路由,负责返回各省的总销量数据。success: function (data)
: 当请求成功时,回调函数会接收服务器返回的数据(data
),并进行以下操作:option_bar.xAxis.data = data['省份']
: 将返回的省份
数据赋值给x轴的data
属性,即x轴的类别数据。option_bar.series[0].data = data['总销量']
: 将返回的总销量
数据赋值给系列的data
属性,即柱状图的高度数据。myChart_bar.setOption(option_bar)
: 将更新后的配置项应用到图表实例myChart_bar
上,从而动态更新图表的显示内容。
error: function (xhr, type, errorThrown)
: 当请求失败时,回调函数会输出错误信息到控制台,帮助调试问题。
折线图:
var chartDom_line = document.getElementById('line');
var myChart_line = echarts.init(chartDom_line);
// 配置项
option_line = {
title: {
text: '每日销量折线图',
left: 'center', // 标题居中
textStyle: { // 标题文字样式
color: 'white', // 设置标题颜色为深灰色
fontSize: 14, // 设置字体大小(可选)
fontWeight: 'bold' // 设置字体加粗(可选)
}
},
xAxis: {
type: 'category',
data: [],
},
yAxis: {
type: 'value'
},
series: [{
data: [],
type: 'line',
}],
tooltip: {}
};
// ajax获取路由数据
$.ajax({
url: "/line",
success: function (data) {
option_line.xAxis.data = data['日期']
option_line.series[0].data = data['销量']
// 配置项作用到图形中
myChart_line.setOption(option_line);
},
error: function (xhr, type, errorThrown) { }
})
地图数据:
// 选择DOM并创建echarts图形对象
var chartDom_map = document.getElementById('map');
var myChart_map = echarts.init(chartDom_map);
var colors = {
'normal':['#006d77', '#00e9ff', '#a3f7ff'],
'spicy': ['#FFF59D', '#FFCA28', '#E64A19']
}
// 配置项
var option_map = {
title: {
text: '各省份销量地图',
left: 'center', // 标题居中
textStyle: { // 标题文字样式
color: 'white', // 设置标题颜色为深灰色
fontSize: 18, // 设置字体大小(可选)
}
},
series: [
{
type: "map",
mapType: "china",
label: {
show: true
},
roam: true,
data: [],
}
],
tooltip: {},
visualMap: {
inRange: {
color: colors['normal']
},
textStyle: {
color: 'orange'
},
min: 0,
max: 500000,
calculable: true
}
};
// ajax获取路由数据
// 使用 jQuery 的 $.ajax 方法发起一个异步 HTTP 请求
$.ajax({
// 设置请求的URL,这里指向服务器端的 "/map" 路径
url: "/map",
// 定义当请求成功时的回调函数
success: function (data) {
// 当从服务器接收到的数据(data)成功返回后执行以下操作
// 更新图表系列数据。假设 'option_map' 是已经定义好的图表配置对象,
// 'series[0].data' 指的是第一个数据系列的数据。
// 'data['list_map']' 是从服务器响应中获取的地图数据列表。
option_map.series[0].data = data['list_map'];
// 将更新后的配置项应用到名为 'myChart_map' 的 ECharts 实例上,
// 使图表显示最新的数据。
myChart_map.setOption(option_map);
// 为下拉选择框(id 为 'select' 的元素)绑定 change 事件处理函数。
// 当用户改变下拉选项时触发此函数。
$('#select').change(function () {
// 获取当前选中的下拉菜单值
var value = $(this).val();
// 根据选中的值来更新地图显示内容
if (value === 'spicy') {
// 如果选择了 "加辣款" 选项
// 更新图表数据为 'spicy_map' 数据集,这应该是与加辣款相关的销售数据
option_map.series[0].data = data['spicy_map'];
// 更改视觉映射的颜色范围为 'spicy' 类型的颜色方案
option_map.visualMap.inRange.color = colors['spicy'];
// 更新图表标题为 "加辣款销量地图"
option_map.title.text = '加辣款销量地图';
// 应用新的配置项,刷新图表显示
myChart_map.setOption(option_map);
} else if (value === 'all') {
// 如果选择了 "所有款" 选项
// 将图表数据恢复为原始的 'list_map' 数据集,即所有产品的销售数据
option_map.series[0].data = data['list_map'];
// 恢复默认颜色方案
option_map.visualMap.inRange.color = colors['normal'];
// 更新图表标题为 "各省份销量地图"
option_map.title.text = '各省份销量地图';
// 应用新的配置项,刷新图表显示
myChart_map.setOption(option_map);
}
});
},
// 定义当请求失败时的回调函数
error: function (xhr, type, errorThrown) {
// xhr 表示 XMLHttpRequest 对象
// type 表示错误类型
// errorThrown 表示捕获的异常信息
// 这里可以添加错误处理逻辑,比如显示错误消息给用户
}
});
饼图:
// 选择DOM并创建echarts图形对象
var chartDom_pie = document.getElementById('taste');
var myChart_pie = echarts.init(chartDom_pie);
// 配置项
option_pie = {
title: {
text: '不同口味螺蛳粉分布图',
left: 'center', // 标题居中
textStyle: { // 标题文字样式
color: '#fff', // 设置标题颜色
fontSize: 28, // 设置字体大小(可选)
}
},
series: [{
data: [],
// name: '访问来源',
type: 'pie',
radius: ['40%', '70%'], // 设置内外半径,实现环形饼图
label: {
formatter: '{a|{b}: {c}}\n{b|占比: {d}%}', // 设置标签的文本格式
width: 100,
height: 50,
lineHeight: 30,
backgroundColor: '#ADCCFF', // 标签背景颜色
borderColor: '#8C8D8E', // 标签边框颜色
borderWidth: 1,
borderRadius: 4,
// position: 'inside',
rich: {
a: {
align: 'center',
fontWeight: 'bold',
fontSize: 14
},
b: {
color: '#fff',
backgroundColor: '#4C5058',
borderRadius: 4,
align: 'center',
padding: [5, 4],
lineHeight: 10,
fontWeight: 'bold',
fontSize: 12
}
}
},
}],
};
// ajax获取路由数据
$.ajax({
url: "/taste",
success: function (data) {
option_pie.series[0].data = data
// 配置项作用到图形中
myChart_pie.setOption(option_pie);
},
error: function (xhr, type, errorThrown) { }
})
词云图:
// 选择DOM并创建echarts图形对象
var chartDom_word = document.getElementById('word');
var myChart_word = echarts.init(chartDom_word);
// 配置项
var option_word = {
series: [
{
data: [],
type: 'wordCloud',
shape: 'circle',
width: '100%',
height: '100%',
left: 'center',
top: 'center',
gridSize: 5,
sizeRange: [0.5, 30],
rotationRange: [-90, 90],
rotationStep: 45,
textStyle: {
fontFamily: 'sans-serif',
normal: {
color: function () {
return 'rgb(' + [
Math.round(Math.random() * 95 + 160),
Math.round(Math.random() * 95 + 160),
Math.round(Math.random() * 95 + 160)
].join(',') + ')';
}
}
}
}
]
};
// ajax获取路由数据
$.ajax({
url: "/word",
success: function (data) {
option_word.series[0].data = data
// 配置项作用到图形中
myChart_word.setOption(option_word);
},
error: function (xhr, type, errorThrown) { }
})
在处理亿些些细节
这样就画好了
详细代码可以下载资源