# -*- coding: utf-8 -*-
"""
办公室平面图生成器 - 左上角为原点版本
功能:基于 seat.xlsx,在 a-r 行 × 每行 22 座位的固定布局中,
绘制完整工位图,已分配高亮,未分配灰显,且 a 行在最上方。
"""
import os
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rcParams
from matplotlib.patches import Rectangle
plt.rcParams['font.sans-serif'] = ['Mabry Pro', 'Noto Sans SC']
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['axes.unicode_minus'] = False
# -------------------------------
# 1. 检查输入文件是否存在
# -------------------------------
if not os.path.exists("seat.xlsx"):
raise FileNotFoundError("请确保当前目录下有 'seat.xlsx' 文件!")
# -------------------------------
# 2. 读取 Excel 数据并构建映射
# -------------------------------
try:
df = pd.read_excel("seat.xlsx")
if len(df.columns) < 3:
raise ValueError("Excel 文件必须至少包含三列。")
df = df.iloc[:, :3]
df.columns = ['seat', 'emp_id', 'dept']
except Exception as e:
raise RuntimeError(f"无法读取 seat.xlsx: {e}")
# 清理数据
df.dropna(subset=['seat'], inplace=True)
df['seat'] = df['seat'].astype(str).str.lower().str.strip()
df['dept'] = df['dept'].astype(str).str.lower().str.strip()
assigned_seats = df.set_index('seat').T.to_dict()
# -------------------------------
# 3. 定义完整布局:a - r 行,每行 1 - 22 号
# -------------------------------
rows = [chr(i) for i in range(ord('c'), ord('r') + 1)] # c 到 r 共 16 行
cols = list(range(1, 23)) # 1 到 22
# 映射字母到 y 坐标:a -> 0, b -> 1, ..., r -> 17
row_map = {letter: idx for idx, letter in enumerate(rows)}
col_map = {num: num - 1 for num in cols} # 数字→索引(等距排列)
use_numeric_x = False # 推荐 False:等间距更整齐
# -------------------------------
# 4. 预生成所有座位的位置
# -------------------------------
seat_positions = {}
for letter in rows:
for num in cols:
seat_id = f"{letter}{num:02d}"
y = row_map[letter] # a=0, b=1, ..., r=17 → 将通过 invert_yaxis() 变成顶部到底部
x = num if use_numeric_x else col_map[num]
seat_positions[seat_id] = (x, y)
# -------------------------------
# 5. 颜色设置
# -------------------------------
dept_colors = {
'S19': '#FF6B6B',
'S13': '#4ECDC4',
'HR': '#E57777',
'S17': '#77C7E5',
'S11': '#E5BC77',
'QM': '#E5DF77',
'RD': '#C2E577',
'先进制程技术': '#7DE577',
'IT': '#77E5B3',
'资材': '#77E5D6',
'厂务': '#E59977',
'总经理室': '#77A4E5',
'环安': '#A977E5',
'外包': '#E577D6',
'客戶服務課': '#E577B3',
'财务': '#E57790',
'外派(非AUS)': '#E57777'
}
default_color = '#F0F0F0'
edge_color = 'lightgray'
# -------------------------------
# 6. 创建绘图
# -------------------------------
fig, ax = plt.subplots(1, figsize=(20, 10))
w, h = 0.9, 0.8 # 工位宽高
# -------------------------------
# 7. 遍历所有座位绘制
# -------------------------------
for seat_id, (x, y) in seat_positions.items():
rect_center_x = x + 0.5 * w if use_numeric_x else x + 0.5
rect_y = y + 0.1 # 矩形底部 y 坐标
is_occupied = seat_id in assigned_seats
if is_occupied:
person = assigned_seats[seat_id]
dept = person['dept']
color = dept_colors.get(dept, '#CCCCCC')
linewidth = 1.8
zorder = 10
else:
color = default_color
linewidth = 0.8
zorder = 1
# 绘制矩形
rect = Rectangle(
(rect_center_x - w/2, rect_y),
w, h,
facecolor=color,
edgecolor=edge_color,
lw=linewidth,
zorder=zorder
)
ax.add_patch(rect)
# 添加座位编号
# ax.text(
# rect_center_x + w/2 - 0.05, rect_y + h - 0.05,
# seat_id,
# ha='right', va='top',
# fontsize=5, color='gray', alpha=0.7,
# zorder=zorder
# )
ax.text(
rect_center_x, rect_y + h * 0.4,
seat_id.upper(),
ha='center', va='center',
fontsize=7, color='black', style='italic',
zorder=zorder + 1
)
# 如果已分配,显示员工信息
if is_occupied:
person = assigned_seats[seat_id]
raw_id = str(person['emp_id']).strip() # 确保是字符串
if raw_id and raw_id[0].isalpha():
formatted_emp_id = raw_id[0].upper() + raw_id[1:] # 首字母大写
else:
formatted_emp_id = raw_id # 没有字母就原样
ax.text(
rect_center_x, rect_y + h * 0.75,
f"{formatted_emp_id}",
ha='center', va='center',
fontsize=8, weight='bold', color='white',
zorder=zorder + 1
)
#
# ax.text(
# rect_center_x, rect_y + h * 0.4,
# person['dept'].upper(),
# ha='center', va='center',
# fontsize=7, color='white', style='italic',
# zorder=zorder + 1
# )
# -------------------------------
# 8. 设置坐标轴范围与标签
# -------------------------------
x_min = min(pos[0] for pos in seat_positions.values())
x_max = max(pos[0] for pos in seat_positions.values())
ax.set_xlim(-0.5, 22)
ax.set_ylim(-0.5, len(rows) + 0.5) # y: 0 ~ 17,后续翻转
# Y 轴标签:a 在顶部
ax.set_yticks([i + 0.5 for i in range(len(rows))])
ax.set_yticklabels([r.upper() for r in rows])
# X 轴标签
if use_numeric_x:
ax.set_xticks(range(1, 23))
else:
ax.set_xticks([col_map[n] + 0.5 for n in cols])
ax.set_xticklabels(cols)
# ax.set_xlabel("Seat Number", fontsize=12, labelpad=10)
# ax.set_title(
# "🏢 Office Floor Plan\nOrigin: Top-Left | Rows a–r (a at top), Seats 01–22",
# fontsize=16, pad=25, weight='bold'
# )
# -------------------------------
# 翻转 Y 轴,使 a 行在最上方
# -------------------------------
ax.invert_yaxis()
# -------------------------------
# 9. 图例
# -------------------------------
used_depts = set(assigned_seats[s]['dept'] for s in assigned_seats if 'dept' in assigned_seats[s])
legend_handles = [plt.Rectangle((0,0),1,1, color=dept_colors[d]) for d in used_depts if d in dept_colors]
legend_labels = [d.upper() for d in used_depts if d in dept_colors]
if legend_handles:
ax.legend(
legend_handles, legend_labels,
loc='upper right',
ncol=15,
fontsize=10,
title_fontsize=11
)
# -------------------------------
# 10. 框
# -------------------------------
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.tick_params(left=False, bottom=False, labelleft=True, labelbottom=True)
# -------------------------------
# 11. 调整布局
# -------------------------------
plt.subplots_adjust(left=0, right=1, top=1, bottom=0)
# -------------------------------
# 12. 保存输出
# -------------------------------
output_pdf = "T1.pdf"
# output_png = "office_floor_plan_top_left.png"
plt.savefig(output_pdf, dpi=200, bbox_inches='tight')
# plt.savefig(output_png, dpi=200, bbox_inches='tight')
print(f"{output_pdf}")
# -------------------------------
# 13. 显示图像
# -------------------------------
# plt.show()
最新发布