你提出的需求非常清晰:
> **根据座位号规则自动布局:**
> - **首字母相同 → 放在同一行(y 坐标相同)**
> - **数字部分相同 → 放在同一列(x 坐标相同)**
例如:
```
r01, r07, r21 → 都在 R 行
p21 → 在 P 行
r21 和 p21 → 都在 21 列(即同一竖列)
```
这正是一个典型的 **基于命名规则的二维网格自动排布系统**。
---
## ✅ 目标
从座位编号如 `r01`, `r07`, `r21`, `p21` 自动推断其在平面图中的 `(x, y)` 位置,使得:
- 所有 `rxx` → 第 R 行
- 所有 `pxx` → 第 P 行
- 所有 `xx21` → 第 21 列(纵向对齐)
最终生成一张整齐的、符合逻辑的办公室平面图。
---
## 🧠 核心思路
| 座位号 | 字母(行) | 数字(列) |
|--------|------------|------------|
| r01 | r | 1 |
| r07 | r | 7 |
| r21 | r | 21 |
| p21 | p | 21 |
👉 构建映射:
- `row_map`: 将字母映射为 y 轴坐标(如 `'r' -> 0`, `'p' -> 1`)
- `col_map`: 将数字映射为 x 轴坐标(如 `1 -> 0`, `7 -> 1`, `21 -> 2`),或直接用数值定位
---
## ✅ 完整解决方案代码
```python
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
# -------------------------------
# 1. 读取 Excel 数据
# -------------------------------
df = pd.read_excel("seating.xlsx")
df.columns = ['seat', 'emp_id', 'dept']
assignments = df.set_index('seat').T.to_dict()
# -------------------------------
# 2. 解析座位号并构建坐标
# -------------------------------
def parse_seat(seat_id):
"""返回 (letter, number)"""
letter = seat_id[0].lower()
number = int(seat_id[1:])
return letter, number
# 提取所有唯一行(字母)和列(数字)
letters = sorted(set(parse_seat(sid)[0] for sid in assignments.keys()))
numbers = sorted(set(parse_seat(sid)[1] for sid in assignments.keys()))
# 映射到网格索引
row_map = {letter: i for i, letter in enumerate(letters)} # r->0, p->1
col_map = {number: i for i, number in enumerate(numbers)} # 1->0, 7->1, 21->2
# 或者使用实际数字作为 x 坐标(更真实感)
use_numeric_x = True # True: 按数字间距排布;False: 等距排列
seat_positions = {}
for seat_id in assignments:
letter, num = parse_seat(seat_id)
y = row_map[letter] # 行由字母决定
x = num if use_numeric_x else col_map[num] # 列由数字决定(是否按真实数字)
seat_positions[seat_id] = (x, y)
# -------------------------------
# 3. 颜色设置
# -------------------------------
dept_colors = {
'dt': '#FF6B6B',
'hr': '#4ECDC4'
}
# -------------------------------
# 4. 绘图
# -------------------------------
fig, ax = plt.subplots(1, figsize=(12, 6))
w, h = 1.8 if use_numeric_x else 2.0, 1.0 # 工位大小
for seat_id, (x, y) in seat_positions.items():
person = assignments[seat_id]
color = dept_colors.get(person['dept'].lower(), '#CCCCCC')
# 绘制矩形
rect = Rectangle((x - w/2, y), w, h, facecolor=color, edgecolor='black', lw=1.2)
ax.add_patch(rect)
# 文字信息
ax.text(x, y + h * 0.75, f"ID:{person['emp_id']}", ha='center', va='center', fontsize=9, weight='bold', color='white')
ax.text(x, y + h * 0.4, person['dept'].upper(), ha='center', va='center', fontsize=8, color='white', style='italic')
ax.text(x + w/2 - 0.1, y + h - 0.1, seat_id, ha='right', va='top', fontsize=6, color='gray')
# -------------------------------
# 5. 设置坐标轴与样式
# -------------------------------
ax.set_xlim(
min(seat_positions[s][0] for s in seat_positions) - 1,
max(seat_positions[s][0] for s in seat_positions) + 2
)
ax.set_ylim(-0.5, len(row_map) + 0.5)
# Y 轴标签:显示每行对应的字母
ax.set_yticks([row_map[l] + 0.5 for l in row_map])
ax.set_yticklabels([f"Row '{l.upper()}'" for l in row_map])
# X 轴标签:显示列号(数字)
if use_numeric_x:
ax.set_xticks(sorted(set(int(s[1:]) for s in seat_positions)))
ax.set_xlabel("Seat Number (Aligned by Value)")
else:
ax.set_xticks(range(len(numbers)))
ax.set_xticklabels(numbers)
ax.set_xlabel("Seat Column Index")
ax.set_aspect('equal' if use_numeric_x else 'auto')
ax.set_title("Office Layout: Same Letter → Same Row, Same Number → Same Column", fontsize=14, pad=20)
# 图例
legend_handles = [plt.Rectangle((0,0),1,1, color=col) for col in dept_colors.values()]
legend_labels = [d.upper() for d in dept_colors.keys()]
ax.legend(legend_handles, legend_labels, loc='upper right', title="Departments")
# 隐藏外围边框和 ticks
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, labelleft=True)
# -------------------------------
# 6. 手动调整边距(避免 tight_layout 报警)
# -------------------------------
plt.subplots_adjust(left=0.15, right=0.85, top=0.85, bottom=0.15)
# -------------------------------
# 7. 保存并显示
# -------------------------------
plt.savefig("grid_floor_plan.png", dpi=150, bbox_inches='tight')
plt.savefig("grid_floor_plan.pdf", bbox_inches='tight')
plt.show()
```
---
## 🔍 输出效果说明
### ✅ 成功实现:
| 功能 | 是否实现 | 说明 |
|------|----------|------|
| 相同字母 → 同一行 | ✅ | `r01`, `r07`, `r21` 都在 y=0 |
| 相同数字 → 同一列 | ✅ | `r21` 和 `p21` 的 x=21(若 `use_numeric_x=True`) |
| 可视化清晰 | ✅ | 不同部门不同颜色,带图例、标题、标注 |
### 示例布局(当 `use_numeric_x=True`):
```
Y=1: p21
[ID:4] HR
Y=0: r01 r07 r21
[ID:1]DT [ID:3]DT [ID:2]HR
```
- 数字越大越靠右
- 字母排序决定上下顺序(r 在上,p 在下)
---
## 🛠️ 进阶建议
### 1. 使用“压缩列模式”避免稀疏分布
如果你的数字跨度大(比如有 `01` 和 `99`),可以切换 `use_numeric_x=False` 来等距排列。
### 2. 添加垂直线辅助对齐列
```python
for num in set(int(s[1:]) for s in seat_positions):
if use_numeric_x:
ax.axvline(x=num, color='lightgray', linestyle='--', alpha=0.5)
```
帮助肉眼判断哪些是同一列。
### 3. 支持多字符前缀(如 rm01, pt12)
可改为正则提取:
```python
import re
match = re.match(r"([a-zA-Z]+)(\d+)", seat_id)
prefix, num = match.group(1), int(match.group(2))
```
然后按 `prefix` 分组。
---
## ✅ 总结
你现在拥有了一个:
- ✅ **智能解析座位名**
- ✅ **自动按字母分行、按数字分列**
- ✅ **可视化渲染**
- ✅ **可扩展性强**
的完整办公座位布局系统!
---
###