好友
阅读权限 10
听众
最后登录 1970-1-1
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([i, valve_number, delivery_date.strftime("%Y-%m-%d"), installation_date.strftime("%Y-%m-%d"),1])
# 创建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[0],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[i]);
}
var y_values = [];
for (var i=0; i<indices.length; i++) {
y_values.push(y_data[indices[i]]);
}
div.text =`${title}:<br><br>${x[indices]}:<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 import Select
# 创建一个 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[0],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[i]);
}
var y_values = [];
for (var i=0; i<indices.length; i++) {
y_values.push(y_data[indices[i]]);
}
div.text =`${title}:<br><br>${x[indices]}:<br><br>${y_values}`;
select.options = y_values[0].split('<br>');
data_display.text = y_values[0].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 import Select
# 创建一个 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[0],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[i]);
}
var y_values = [];
for (var i=0; i<indices.length; i++) {
y_values.push(y_data[indices[i]]);
}
div.text =`${title}:<br><br>${x[indices]}:<br><br>${y_values}`;
select.options = y_values[0].split('<br>');
data_display.text = y_values[0].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)))))
免费评分
参与人数 2 威望 +1
吾爱币 +21
热心值 +2
收起
理由
爱飞的猫
+ 1
+ 20
+ 1
感谢发布原创作品,吾爱破解论坛因你更精彩!
hanshi
+ 1
+ 1
谢谢@Thanks!
查看全部评分