产业链全景图谱生成器
从CSV或xlsx数据表自动生成专业级产业链全景图谱HTML可视化图表,支持上/中/下游三列布局、颜色编码、自动渲染模式选择。支持屏幕展示版和宽幅印刷版两种模式。
已验证适用的产业链类型(60+种):煤化工、煤制油、煤焦化、精细煤化工、煤制气、钢铁、钢材深加工、氯碱化工、钢焦化氢联产、绿色钢铁与高端装备制造、煤焦钢一体化、特钢及高端金属材料、先进钢铁制造、特色装备制造、新能源汽车、智能煤机装备、军民融合装备制造、通用设备制造、新材料、高性能复合材料、碳基新材料、先进化工材料、新能源电池材料、电子信息、LED光电、半导体光电、能源电子、信创、节能环保、大宗工业固废综合利用、粉煤灰综合利用、煤矸石综合利用、冶炼渣综合利用、现代医药、创新药、大健康、现代生物医药、现代中药、低空经济、低空飞行器制造、低空基础设施、低空经济运营服务、无人机、人工智能基础设施、人工智能软件、人工智能应用与服务、具身智能与机器人、人工智能、氢能、氢能基础设施、氢能装备制造、氢燃料电池、制氢、党参中药、农产品精深加工、特优现代农业、小米(谷子)、智慧农业、文化旅游、红色文化旅游、康养度假、生态旅游、文旅景区开发、国防军工装备制造、航空航天装备制造、军民两用技术、军民融合等。
数据格式要求
支持CSV和xlsx两种输入格式。列名可略有不同,脚本会自动检测关键词匹配。
| 标准列名 | 关键词检测 | 说明 | 示例 | |---------|-----------|------|------| | 一级环节 | 含"一级" | 上游/中游/下游大类 | 上游:煤炭资源与核心装备 | | 二级子环节 | 含"二级" | 子分类 | 煤炭开采与洗选 | | 三级子环节 | 含"三级" | 具体环节 | 动力煤开采 | | 代表公司 | 含"公司"/"企业"/"代表" | 企业名称(可选) | 中国神华 | | 环节概述 | 含"概述"/"描述"/"说明" | 环节说明文字(可选) | 直接从事煤矿开采作业… |
一级环节文本中包含"上游""中游""下游"关键词时,自动分配橙/蓝/绿色方案,其他归为灰色"其他"类。
自动渲染模式
- tag模式:数据无企业列时自动启用,三级子环节作为彩色标签排列在二级section内
- box模式:数据含企业列时自动启用,三级子环节作为box,内含企业标签
数据处理规则
- CSV编码自动检测顺序:UTF-8-sig → UTF-8 → GBK → GB2312 → GB18030
- xlsx使用openpyxl读取
- 企业名称在同一个三级环节内自动去重
- 层级分组结构:一级环节 → 二级子环节(sections) → 三级子环节(tags/boxes) → 代表公司(companies)
- Windows环境Python输出中文需设置UTF-8编码(脚本已内置处理)
- xlsx文件被Excel占用时直接save会PermissionError,需先save到临时文件再
shutil.copy2覆盖到目标路径
使用方法
印刷版(默认,宽幅排版)
python scripts/generate_atlas.py data.xlsx --print -o output.html -t "煤化工产业链全景图"
python scripts/generate_atlas.py data.csv --print -o output.html -t "产业链全景图"
屏幕展示版(含图例、统计、描述)
python scripts/generate_atlas.py data.xlsx --screen -o output.html -t "产业链全景图谱"
参数说明
| 参数 | 说明 | 默认值 |
|------|------|--------|
| data_path | 数据文件路径(.csv 或 .xlsx,必填) | — |
| --print | 生成印刷版(默认启用) | true |
| --screen | 生成屏幕展示版(覆盖--print) | false |
| -o / --output | 输出HTML路径 | 与数据文件同目录 |
| -t / --title | 图谱标题 | "产业链全景图" |
| -s / --subtitle | 副标题(仅屏幕版生效) | 空 |
| --max-companies | 每个三级环节最多显示企业数(box模式) | 6 |
印刷版排版规格详解(PRINT_CSS_TAG_MODE / PRINT_CSS_BOX_MODE)
页面设置
- 自定义页面尺寸:387mm × 210mm(宽幅横版)
- @page size: 387mm 210mm; margin: 0mm(由atlas padding控制边距)
- html/body width: 387mm
- atlas: display:flex; flex-direction:column; min-height:206mm; padding:2mm; margin:0 auto
- 背景:浅米色 #F5F0E6
- 全局line-height: 1.15
- 字体:font-family "Microsoft YaHei","PingFang SC","Noto Sans SC","Helvetica Neue",Arial,sans-serif
标题(atlas-title)
- 字号:24pt
- font-weight: 800
- color: #1a252f
- letter-spacing: 2px
- 标题与下方图文间距:7mm(atlas-header margin-bottom)
- 印刷版无副标题
- atlas-header: text-align:center; flex-shrink:0
三列布局(上游/中游/下游)
- .columns: display:flex; gap:5pt; flex:1; align-items:stretch; min-height:0; justify-content:center(三列整体居中)
- .column: flex:0 0 auto; width:fit-content; min-width:0; display:flex; flex-direction:column(每列收缩至刚好容纳内容)
- 列头(col-header):
- padding: 1.2mm 3mm
- color: #fff(白色文字)
- font-size: 15pt
- font-weight: 700
- text-align: center
- letter-spacing: 1px
- border-radius: 3px 3px 0 0
- flex-shrink: 0
- background: 各环节的header_grad渐变色
- 列体(col-body):
- flex:1; display:flex; flex-direction:column
- padding: 1.5mm; gap:0
- border: 2px solid(颜色用该环节primary色)
- border-top: none(与列头衔接)
- border-radius: 0 0 3px 3px
- justify-content: space-between(首段顶部对齐、末段底部对齐,纵向上下两端对齐)
- align-items: center(二级面板水平居中)
二级面板(section)
- width: fit-content; max-width: calc(34.2mm * 3 + 4mm)(收缩至刚好容纳3个标签宽度,防止溢出)
- border: 1.5px solid(颜色用该环节的tag_border浅色)
- border-radius: 2px
- overflow: hidden
- section-header:
- padding: 0.6mm 2mm
- font-size: 13pt
- font-weight: 700
- display: flex; align-items: center; gap: 1.5mm
- background: section_bg色(浅橙/浅蓝/浅绿)
- color: tag_color色
- 左侧色条.bar: width:4px; height:10px; border-radius:1px; flex-shrink:0; background: primary色
- section-body(tag模式):
- padding: 1mm 1.5mm
- display: flex; flex-wrap: wrap
- gap: 0(水平方向无间距)
- row-gap: 3pt(垂直方向标签行间距3pt)
- section-body(box模式):
- padding: 1mm 1.5mm
- display: flex; flex-direction: column; gap: 0.8mm
三级标签(item-tag,tag模式)
- display: flex; align-items: center; justify-content: center; text-align: center(横向纵向双重居中)
- width: 34.2mm(固定宽度)
- padding: 0.4mm 1mm
- font-size: 12pt
- line-height: 1.25
- border-radius: 3px
- border: 1px solid
- word-break: break-all(长名称自动换行)
- 每行排3个标签(由max-width约束)
- 背景色/文字色/边框色:按环节主题色(tag_bg / tag_color / tag_border)
三级面板(box,box模式)
- 背景:#fafbfc
- border-radius: 2px
- padding: 0.6mm 1.5mm
- border: 1px solid rgba(0,0,0,.04)
- box-title: 10pt,700字重,tag_color色,底部虚线分隔
企业标签(company-tag,box模式)
- 每行排3个企业标签
- 标签宽度:calc(33.33% - 1.5mm)
- 标签间距gap:1.5mm
- justify-content: space-between
- ::after伪元素防止末行标签拉伸
- 字号:9pt
- padding: 0.25mm 0.8mm
- text-align: center
- white-space: nowrap + overflow:hidden + text-overflow:ellipsis
- border-radius: 3px
- 印刷版默认每个三级环节最多显示6家企业
颜色方案
| 环节 | 主色 | 深色 | 标签背景 | 标签文字 | 标签边框 | 面板背景 | 渐变头 | |------|------|------|---------|---------|---------|---------|--------| | 上游 | #E67E22 | #D35400 | #FDF2E9 | #C0530A | #F5CBA7 | #FEF7F2 | linear-gradient(135deg,#E67E22,#D35400) | | 中游 | #2471A3 | #1B4F72 | #EBF5FB | #1A5276 | #AED6F1 | #F0F8FD | linear-gradient(135deg,#2980B9,#1F618D) | | 下游 | #229954 | #196F3D | #EAFAF1 | #1B7A43 | #ABEBC6 | #F0FBF5 | linear-gradient(135deg,#27AE60,#1E8449) | | 其他 | #7F8C8D | #566573 | #F2F4F4 | #566573 | #BDC3C7 | #F7F9F9 | linear-gradient(135deg,#95A5A6,#7F8C8D) |
字号规格汇总表(印刷版tag模式)
| 元素 | 字号 | 字重 | 说明 | |------|------|------|------| | 标题 atlas-title | 24pt | 800 | letter-spacing:2px | | 一级面板 col-header | 15pt | 700 | 白色文字,渐变背景 | | 二级面板 section-header | 13pt | 700 | tag_color色,section_bg背景 | | 三级标签 item-tag | 12pt | 正常 | tag_color色,tag_bg背景 | | 企业标签 company-tag(box模式) | 9pt | 正常 | tag_color色,tag_bg背景 | | box标题(box模式) | 10pt | 700 | tag_color色 |
截图生成(高清PNG)
使用Playwright生成4倍retina高清截图,保存到桌面:
from playwright.sync_api import sync_playwright
from PIL import Image
import os, sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
Image.MAX_IMAGE_PIXELS = 300000000
# 页面387mm×210mm → 视口1464×794 @96dpi
# 4倍缩放 → 最终5856×3176像素高清PNG
VIEWPORT_W = 1464 # 387mm @96dpi
VIEWPORT_H = 794 # 210mm @96dpi
SCALE = 4
html_path = os.path.abspath('output.html')
url = 'file:///' + html_path.replace(chr(92), '/') # Windows路径转file:// URL
with sync_playwright() as p:
browser = p.chromium.launch()
context = browser.new_context(
viewport={"width": VIEWPORT_W, "height": VIEWPORT_H},
device_scale_factor=SCALE,
)
page = context.new_page()
page.goto(url)
page.wait_for_load_state("networkidle") # 等待渲染完成
page.screenshot(path="C:/Users/1/Desktop/output.png", full_page=True)
browser.close()
# 生成预览缩略图用于Read工具验证(原图超2000px限制)
img = Image.open("C:/Users/1/Desktop/output.png")
img.thumbnail((1900, 1900), Image.LANCZOS)
img.save("preview.png")
截图参数:
- 视口:1464 × 794(对应387mm × 210mm @96dpi)
- device_scale_factor: 4(4倍retina高清)
- full_page: True(全页捕获,不丢失底部内容)
- page.goto(url) + page.wait_for_load_state("networkidle")(确保渲染完成,无需wait_for_timeout)
- 输出尺寸:5856 × 3176像素
- 保存路径:C:\Users<用户名>\Desktop\
- PIL需设置 Image.MAX_IMAGE_PIXELS = 300000000 防止DecompressionBombError
- Windows路径转file:// URL时用 chr(92) 替代反斜杠转义(避免Python字符串转义问题)
- Python中文输出需 sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='utf-8',errors='replace')
- 截图后用PIL Image.thumbnail((1900,1900))生成预览缩略图,再用Read工具验证渲染效果
屏幕展示版特性(非印刷版)
- 浅米色网格背景(24px方格)
- 顶部统计栏:大环节数、子环节数、细分领域数
- 流向指示器:I → II → III
- 卡片含描述摘要(限2行,box模式)
- 底部图例
- hover动画效果
- max-width: 1920px
CSS陷阱与注意事项
1. flex容器内文本居中
flex容器内的文本节点会被包裹为匿名flex子项,该子项会填满容器宽度,导致justify-content:center无法居中文字。**必须同时设置text-align:center**才能确保文字横向居中。例如"石墨烯与碳纳米材料"等长标签名称需要此双重居中。
2. fit-content + flex-wrap 溢出
对display:flex;flex-wrap:wrap容器使用width:fit-content时,浏览器会取max-content(即所有子项排一行的宽度),导致内容溢出页面(曾出现截图宽度9156px而非5856px的问题)。必须设置max-width约束到目标行宽,如max-width:calc(34.2mm * 3 + 4mm)限制为3个标签宽。
3. row-gap实现仅垂直间距
在flex-wrap:wrap容器中,gap同时控制水平和垂直间距。若只需垂直间距而保持水平无间距,使用gap:0; row-gap:3pt。row-gap仅影响行间垂直间距,不影响同行标签间的水平布局。
4. 列宽收缩链
实现"各列刚好容纳内容"的紧凑布局需要层层收缩:
- item-tag: 固定width:34.2mm
- section: width:fit-content + max-width:calc(34.2mm*3+4mm) → 收缩至3个标签宽
- column: flex:0 0 auto + width:fit-content → 收缩至刚好容纳所有section
- columns: justify-content:center → 三列整体居中
5. 其他注意事项
- 印刷版去除图例、统计栏、描述文字、流向箭头,仅保留核心图谱内容
- 印刷版默认每个三级环节最多6家企业,可用--max-companies调整
- 企业标签固定3列排列,6家=2行3列,超出部分不显示
- 三列纵向使用space-between实现上下两端对齐
- 大面板边框2px主题色,二级面板边框1.5px浅色,视觉层次分明
- 截图必须用full_page=True防止底部内容截断
6. xlsx标签修改工作流
用户生成图谱后常发"XX改成YY"修改xlsx标签,标准流程:
- 用openpyxl加载xlsx,
ws.iter_rows()遍历所有单元格查找匹配(注意:方法是iter_rows()不是iterrows(),iterrows会报AttributeError) - 修改cell.value后,先save到临时文件(如
C:/Users/1/.qoderworkcn/workspace/mrmsovt88oyobi57/tmp_xlsx.xlsx),再用shutil.copy2覆盖到目标路径(避免xlsx被Excel占用时PermissionError) - 重新执行generate_atlas.py生成HTML + 4x截图
import openpyxl, shutil, os
xlsx_path = 'C:/Users/1/Desktop/data.xlsx'
wb = openpyxl.load_workbook(xlsx_path)
ws = wb.active
for row in ws.iter_rows(): # 注意是iter_rows不是iterrows
for cell in row:
if cell.value == '旧标签名':
cell.value = '新标签名'
tmp_path = 'C:/Users/1/.qoderworkcn/workspace/mrmsovt88oyobi57/tmp_xlsx.xlsx'
wb.save(tmp_path)
shutil.copy2(tmp_path, xlsx_path)
os.remove(tmp_path)
示例
# 从xlsx生成印刷版(tag模式,无企业数据)
python scripts/generate_atlas.py 煤化工产业链图谱_三级环节.xlsx --print -o 输出.html -t "煤化工产业链全景图"
# 从xlsx生成煤制油产业链图
python scripts/generate_atlas.py 煤制油产业链图谱_三级环节.xlsx --print -o 输出.html -t "煤制油产业链图"
# 从CSV生成印刷版(box模式,含企业数据)
python scripts/generate_atlas.py 产业链数据.csv --print -o 输出.html -t "产业链全景图"
# 限制每环节最多3家企业
python scripts/generate_atlas.py 产业链数据.csv --print --max-companies 3 -o 输出.html
# 生成屏幕展示版
python scripts/generate_atlas.py 产业链数据.xlsx --screen -o 输出.html -t "产业链全景图谱"
微信扫一扫