UltraDebug

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: A C D R G Y M Z X S P
公益项目,接受捐赠
查看: 1709|回复: 0
收起左侧

[Python] bokeh库实现项目计划可视化

[复制链接]
Photoshop

主题

0

回帖

UD

新手上路

UID
19
积分
31
注册时间
2022-2-9
最后登录
1970-1-1
2024-3-27 21:30:15 | 显示全部楼层 |阅读模式

bokeh库是一个十分强大的可视化库,生成html文件实现互动式可视化。
模拟一个简单的阀门安装计划。
bokeh库实现项目计划可视化 - Photoshop_UltraDebug

首先,利用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文件,创建出一些模拟图纸),如下:
bokeh库实现项目计划可视化 - Photoshop_UltraDebug

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就可以进入学习界面了。
bokeh库实现项目计划可视化 - Photoshop_UltraDebug

我们就不废话了,直接上代码。

首先,用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还差一些。。。
bokeh库实现项目计划可视化 - Photoshop_UltraDebug

差啥呢?最上方的文本,下方的拖动轴,左侧的显示。

首先,上方的文本好解决,加一个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))

bokeh库实现项目计划可视化 - Photoshop_UltraDebug

再在下方加一个拖动条,需要用到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))

bokeh库实现项目计划可视化 - Photoshop_UltraDebug

最后就是下拉框和图纸了。建立一个下拉框选择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)))))

bokeh库实现项目计划可视化 - Photoshop_UltraDebug

如果你想保持成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)))))
UltraDebug免责声明
✅以上内容均来自网友转发或原创,如存在侵权请发送到站方邮件9003554@qq.com处理。
✅The above content is forwarded or original by netizens. If there is infringement, please send the email to the destination 9003554@qq.com handle.
回复 打印

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|站点地图|UltraDebug ( 滇ICP备2022002049号-2 滇公网安备 53032102000034号)

GMT+8, 2025-6-20 15:54 , Processed in 0.036945 second(s), 9 queries , Redis On.

Powered by Discuz X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表