bokeh库实现项目计划可视化
bokeh库是一个十分强大的可视化库,生成html文件实现互动式可视化。模拟一个简单的阀门安装计划。
首先,利用Python模拟出数据。
```
import pandas as pd
import random
from datetime import datetime, timedelta
# 生成随机字母
def random_letter():
return random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
# 生成随机数字
def random_number():
return ''.join(random.choices('0123456789', k=4))
# 生成随机日期(只有年月日)
def random_date(start_date, end_date):
delta = end_date - start_date
random_days = random.randint(0, delta.days)
return start_date + timedelta(days=random_days)
# 创建日期范围
start_date_delivery = datetime(2024, 4, 1)
end_date_delivery = datetime(2024, 6, 30)
start_date_installation = datetime(2024, 5, 1)
end_date_installation = datetime(2024, 8, 31)
# 创建数据列表
data = []
for i in range(1, 1201):
valve_number = f"G{random_letter()}V-{random_number()}"
delivery_date = random_date(start_date_delivery, end_date_delivery)
installation_date = random_date(start_date_installation, end_date_installation)
# 确保到货日期早于安装日期
while delivery_date >= installation_date:
delivery_date = random_date(start_date_delivery, end_date_delivery)
installation_date = random_date(start_date_installation, end_date_installation)
data.append()
# 创建DataFrame
df = pd.DataFrame(data, columns=['No', 'Tag', 'ETA', 'VIP','Qty'])
#No 序号
#Tag 阀门编号
#ETA 到货计划
#VIP 阀门安装计划
#Qty 数量
# 保存为Excel文件
df.to_excel('valve_schedule.xlsx', index=False)
```
再根据数据模拟出文件(利用一个pdf文件,创建出一些模拟图纸),如下:
```
import os
import shutil
def create_folders_and_copy_file(folder_names, base_directory, file_to_copy):
for folder_name in folder_names:
folder_path = os.path.join(base_directory, folder_name)
os.makedirs(folder_path, exist_ok=True)
print(f"Created folder: {folder_path}")
# 提取列表中的名称
file_name_prefix = folder_name
# 复制文件到新创建的文件夹并命名
for suffix in ['总图', '布置图', '详图', '底座图纸']:
new_file_name = f"{file_name_prefix}{suffix}.pdf"
destination_file_path = os.path.join(folder_path, new_file_name)
shutil.copy(file_to_copy, destination_file_path)
print(f"Copied file {file_to_copy} to {destination_file_path}")
# 定义文件夹名称列表、基础目录和要复制的文件路径
folder_names = list(df.Tag)
base_directory = 'pic'
file_to_copy = '1.pdf'
# 调用函数创建文件夹并复制文件
create_folders_and_copy_file(folder_names, base_directory, file_to_copy)
```
数据准备好了就可以搞了。
首先登录官网,按照提示进行安装。
https://docs.bokeh.org/en/latest/docs/first_steps/installation.html
pip install bokeh
安装完之后,可以根据官网的学习平台学习,点击网页上方的Turorial就可以进入学习界面了。
我们就不废话了,直接上代码。
首先,用pandas处理下数据。
```
import pandas as pd
# 读取电子表格中的Sheet1数据
df = pd.read_excel('valve_schedule.xlsx', sheet_name='Sheet1',engine = 'calamine')
# 打印Sheet1数据的前几行
print(df.head())
#按日期统计数量
```
简单说一下“calamine”这个引擎,比openpyxl快了很多倍,需要安装。
```
pip install python-calamine -i https://pypi.tuna.tsinghua.edu.cn/simple/
```
```
df['VIP'] = pd.to_datetime(df['VIP'])
max_date = df['VIP'].max()
min_date = df['VIP'].min()
df['VIP'] = df['VIP'].dt.strftime('%Y-%m-%d')
#按日期统计数量
date_quantity_install = df.groupby('VIP')['Qty'].agg(sum).reset_index()
x_date_install = list(date_quantity_install.VIP.array)
y_quantiy_install = list(date_quantity_install.Qty.array)
#按日期统计Tag号
date_tags = df.groupby('VIP')['Tag'].agg(lambda x: '<br>'.join(x)).reset_index()
z_tag_install = list(date_tags['Tag'])
```
不做解释了,比较基础一些数据清洗。
下面该bokeh上场了。
先将数据转成bokeh特有的数据类型,这个在官方教程里有介绍。然后又创建了一个柱状图,但是此时柱状图里没有数据。
```
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
#将数据转成DataSource,bokeh特有的数据类型。
source_install = ColumnDataSource(data = {'x': x_date_install,
'y': y_quantiy_install,
'z':z_tag_install,}
)
# 创建柱状图
plot_install_plan = figure( x_range=source_install.data["x"],
title="安装计划",
#width = 1000,
height=350,
tooltips=[("日期", " @x"),("数量", " @y"),("位号", " @z{safe}"),],
toolbar_location="above",
)
```
将数据添加到柱状图里。
```
# 添加柱状图
bars = plot_install_plan.vbar(
x="x",
top="y",
source=source_install,
#legend_label="安装计划",
width=0.6,
)
# 在柱状图上添加文本标签
plot_install_plan.text(x='x', y='y', text='y', text_align='center', text_color = 'red',
text_baseline='bottom', source=source_install)
```
最后再简单优化一下显示,就可以show一下了。
```
# customize the appearance of the grid and x-axis
from bokeh.models import NumeralTickFormatter
from bokeh.plotting import show
plot_install_plan.xgrid.grid_line_color = None
plot_install_plan.yaxis.formatter = NumeralTickFormatter(format="0,0")
plot_install_plan.xaxis.major_label_orientation = 0.8# rotate labels by roughly pi/4
show(plot_install_plan)
```
结果是这样的,与上面的gif还差一些。。。
差啥呢?最上方的文本,下方的拖动轴,左侧的显示。
首先,上方的文本好解决,加一个DIV就行了,show的时候用column组合一下。
```
from bokeh.models import Div
from bokeh.layouts import column
div_top = Div(text="""<h3>某某项目: </h3><a href="https://en.wikipedia.org/wiki/HTML">项目名称</a>位于某某地方,计划投资某某亿,工期一年。。。。
<li>项目工期:2024年1月-2024年10月</li><li>项目地址:某某市某某区</li> <li>质量第一,安全第一!</li>""",
align = 'start',height=120)
#.......
show(column(div_top,plot_install_plan))
```
再在下方加一个拖动条,需要用到RangeSlider。
```
from bokeh.models import RangeSlider
#增加一个拖动条
range_slider = RangeSlider(
title="Adjust x-axis range",
start=1,
end=(max_date-min_date).days,
step=1,
value=(35, 45),
)
range_slider.js_link("value", plot_install_plan.x_range, "start", attr_selector=0)
range_slider.js_link("value", plot_install_plan.x_range, "end", attr_selector=1)
show(column(div_top,plot_install_plan,range_slider))
```
最麻烦的就是左侧显示了,显示是根据鼠标所在的触发位置信息显示,这就需要用到callback了,这个官方教程里没有详细介绍,这个跟html还相关,自己研究研究吧。
```
from bokeh.models import CustomJS, Div, TapTool
# 添加 Tap 工具
taptool = TapTool()
plot_install_plan.add_tools(taptool)
#创建一个div
div = Div(width=150)
# 定义点击事件的 JavaScript 回调
callback = CustomJS(args=dict(div=div,source=bars.data_source,x_axis=plot_install_plan.xaxis,y_data=bars.data_source.data['z'],title = '安装计划',
), code="""
// 获取绘图数据
var data = source.data;
// 获取x值
var x = data['x'];
var indices = cb_data.source.selected.indices;
console.log("点击了索引:" + indices);
var indices = cb_data.source.selected.indices;
console.log("点击了索引:" + indices);
var y_indices = [];
for (var i=0; i<indices.length; i++) {
y_indices.push(indices);
}
var y_values = [];
for (var i=0; i<indices.length; i++) {
y_values.push(y_data]);
}
div.text =`${title}:<br><br>${x}:<br><br>${y_values}`;
""")
# 将回调附加到 Tap 工具
taptool.callback = callback
#。。。。。。。
show(row(column(div_top,plot_install_plan,range_slider),div))
```
最后就是下拉框和图纸了。建立一个下拉框选择tag,再建立一个div用于显示图纸,再在图纸上加上超链接就行了。
下拉框的options从上一步中获取,修改上一步的callback。
```
from bokeh.models importSelect
# 创建一个 Div 元素用于显示被点击的数据
selected_display = Div(text="", width=400, height=50)
# 创建一个下拉框来选择数据
select = Select(title="选择数据:", options=[])
# 定义点击事件的 JavaScript 回调
callback = CustomJS(args=dict(div=div,source=bars.data_source,x_axis=plot_install_plan.xaxis,y_data=bars.data_source.data['z'],title = '安装计划',
select=select,), code="""
// 获取绘图数据
var data = source.data;
// 获取x值
var x = data['x'];
var indices = cb_data.source.selected.indices;
console.log("点击了索引:" + indices);
var indices = cb_data.source.selected.indices;
console.log("点击了索引:" + indices);
var y_indices = [];
for (var i=0; i<indices.length; i++) {
y_indices.push(indices);
}
var y_values = [];
for (var i=0; i<indices.length; i++) {
y_values.push(y_data]);
}
div.text =`${title}:<br><br>${x}:<br><br>${y_values}`;
select.options = y_values.split('<br>');
data_display.text = y_values.split('<br>');
""")
# 将回调附加到 Tap 工具
taptool.callback = callback
# 将回调函数绑定到下拉框的'on_change'事件上
callback1 = CustomJS(args=dict(
select=select,selected_display=selected_display), code="""
var selected_value = select.value;
selected_display.text = `<h2>资料清单</h2>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}" target="_blank">${selected_value}资料文件夹</a></li>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}/${selected_value}总图.pdf" target="_blank">${selected_value}总图</a></li>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}/${selected_value}布置图.pdf" target="_blank">${selected_value}布置图</a></li>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}/${selected_value}详图.pdf" target="_blank">${selected_value}详图</a></li>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}/${selected_value}底座图纸.pdf" target="_blank">${selected_value}底座图纸</a></li>`;
""")
select.js_on_change('value', callback1)
# customize the appearance of the grid and x-axis
from bokeh.models import NumeralTickFormatter
from bokeh.plotting import show
plot_install_plan.xgrid.grid_line_color = None
plot_install_plan.yaxis.formatter = NumeralTickFormatter(format="0,0")
plot_install_plan.xaxis.major_label_orientation = 0.8# rotate labels by roughly pi/4
show(row(column(div_top,row(column(plot_install_plan,range_slider),div,column(select,selected_display)))))
```
如果你想保持成html文件,那就在show前加一个output_file("responsive_plot.html")
全部代码如下(不包含数据模拟):
```
import pandas as pd
# 读取电子表格中的Sheet1数据
df = pd.read_excel('valve_schedule.xlsx', sheet_name='Sheet1',engine = 'calamine')
# 打印Sheet1数据的前几行
print(df.head())
#按日期统计数量
df['VIP'] = pd.to_datetime(df['VIP'])
max_date = df['VIP'].max()
min_date = df['VIP'].min()
df['VIP'] = df['VIP'].dt.strftime('%Y-%m-%d')
days_total = (max_date-min_date).days
#按日期统计数量
date_quantity_install = df.groupby('VIP')['Qty'].agg(sum).reset_index()
x_date_install = list(date_quantity_install.VIP.array)
y_quantiy_install = list(date_quantity_install.Qty.array)
#按日期统计Tag号
date_tags = df.groupby('VIP')['Tag'].agg(lambda x: '<br>'.join(x)).reset_index()
z_tag_install = list(date_tags['Tag'])
from bokeh.models import ColumnDataSource,NumeralTickFormatter, Div, RangeSlider, CustomJS, Div, Select
from bokeh.plotting import figure
from bokeh.layouts import column, row
source_install = ColumnDataSource(data = {'x': x_date_install,
'y': y_quantiy_install,
'z':z_tag_install,}
)
TOOLTIPS_install = [
("日期", " @x"),
("数量", " @y"),
("位号", " @z{safe}"),]
# 创建柱状图
plot_install_plan = figure( x_range=source_install.data["x"],
title="安装计划",
#width = 1000,
height=350,
tooltips=TOOLTIPS_install,
toolbar_location="above",
)
# 添加柱状图
bars = plot_install_plan.vbar(
x="x",
top="y",
source=source_install,
#legend_label="安装计划",
width=0.6,
)
# 在柱状图上添加文本标签
plot_install_plan.text(x='x', y='y', text='y', text_align='center', text_color = 'red',
text_baseline='bottom', source=source_install)
div_top = Div(text="""<h3>某某项目: </h3><a href="https://en.wikipedia.org/wiki/HTML">项目名称</a>位于某某地方,计划投资某某亿,工期一年。。。。
<li>项目工期:2024年1月-2024年10月</li><li>项目地址:某某市某某区</li> <li>质量第一,安全第一!</li>""",
align = 'start',height=120)
#增加一个拖动条
range_slider = RangeSlider(
title="Adjust x-axis range",
start=1,
end=(max_date-min_date).days,
step=1,
value=(35, 45),
)
range_slider.js_link("value", plot_install_plan.x_range, "start", attr_selector=0)
range_slider.js_link("value", plot_install_plan.x_range, "end", attr_selector=1)
from bokeh.models import CustomJS, Div, TapTool
# 添加 Tap 工具
taptool = TapTool()
plot_install_plan.add_tools(taptool)
#创建一个div
div = Div(width=150)
from bokeh.models importSelect
# 创建一个 Div 元素用于显示被点击的数据
selected_display = Div(text="", width=400, height=50)
# 创建一个下拉框来选择数据
select = Select(title="选择数据:", options=[])
# 定义点击事件的 JavaScript 回调
callback = CustomJS(args=dict(div=div,source=bars.data_source,x_axis=plot_install_plan.xaxis,y_data=bars.data_source.data['z'],title = '安装计划',
select=select,), code="""
// 获取绘图数据
var data = source.data;
// 获取x值
var x = data['x'];
var indices = cb_data.source.selected.indices;
console.log("点击了索引:" + indices);
var indices = cb_data.source.selected.indices;
console.log("点击了索引:" + indices);
var y_indices = [];
for (var i=0; i<indices.length; i++) {
y_indices.push(indices);
}
var y_values = [];
for (var i=0; i<indices.length; i++) {
y_values.push(y_data]);
}
div.text =`${title}:<br><br>${x}:<br><br>${y_values}`;
select.options = y_values.split('<br>');
data_display.text = y_values.split('<br>');
""")
# 将回调附加到 Tap 工具
taptool.callback = callback
# 将回调函数绑定到下拉框的'on_change'事件上
callback1 = CustomJS(args=dict(
select=select,selected_display=selected_display), code="""
var selected_value = select.value;
selected_display.text = `<h2>资料清单</h2>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}" target="_blank">${selected_value}资料文件夹</a></li>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}/${selected_value}总图.pdf" target="_blank">${selected_value}总图</a></li>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}/${selected_value}布置图.pdf" target="_blank">${selected_value}布置图</a></li>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}/${selected_value}详图.pdf" target="_blank">${selected_value}详图</a></li>
<li><a href="D:/work/python/bokeh_keshihua/pic/${selected_value}/${selected_value}底座图纸.pdf" target="_blank">${selected_value}底座图纸</a></li>`;
""")
select.js_on_change('value', callback1)
# customize the appearance of the grid and x-axis
from bokeh.models import NumeralTickFormatter
from bokeh.plotting import show
plot_install_plan.xgrid.grid_line_color = None
plot_install_plan.yaxis.formatter = NumeralTickFormatter(format="0,0")
plot_install_plan.xaxis.major_label_orientation = 0.8# rotate labels by roughly pi/4
#output_file("responsive_plot.html")
show(row(column(div_top,row(column(plot_install_plan,range_slider),div,column(select,selected_display)))))
``` 可以呀,楼主给的这个技术贴不错 calamine不支持pandas1.3.5 好像很厉害的样子 简单的补充2点。首先是pandas,处理数据贼拉好用,但是呢太复杂了学习成本有点高,我不经常用就忘怎么用了,以前都是用openpyxl或xlwings读取表格自己写代码处理。。。。不仅慢不说还蛮烦。后来有了AI,那就爽到起飞了,告诉AI你表格的第一列第二列。。。。是哈,再告诉AI你的需求,让他用pandas处理,就不用自己记那么多命令了。calamine确实只支持新版pandas。。。。。
其次是CustomJS,根据命名就能看出来时客户JS,JS就是JavaScript,可以理解成像Python一样的编程语言,Python运行需要Python解析器,而JavaScript可以直接被浏览器识别。如果仅仅会Python这个CustomJS还真不好写。建议你去B站花点时间去入入门。
CustomJS(args=dict(), code="""""")里面我们就放了2个参数,一个args,dict里面可以放很多参数,例如elect=select,等号前是一个自定义变量,你可以任意命名,用于后面code里引用,等号后面就是Python前面那些数据,例如select=select,前面的select是自定义变量,可以修改,例如修改为s,后面的select就是前面代码里的数据, 当后面的code引用时,就用等号前面的变量名s。code就是我们自己写的一些js了,最后bokeh就会一起加上去,最终生成一个html文件。 感谢楼主分享资源 楼主666!谢谢无私分享,收藏学习。
页:
[1]