import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.interpolate import griddata
from matplotlib.colors import LinearSegmentedColormap, Normalize
import os
from matplotlib.colorbar import ColorbarBase
def generate_multi_fsimc_surfaces():
# 1. 数据加载部分
print("从Excel加载原始数据...")
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
excel_path = os.path.join(script_dir, 'fsim_data.xlsx')
df = pd.read_excel(excel_path)
required_columns = ['BOW', 'WAVE']
if not all(col in df.columns for col in required_columns):
missing = [col for col in required_columns if col not in df.columns]
raise ValueError(f"Excel文件缺少必要的列: {missing}")
# 包含 FsimC_DP 的所有 FsimC 列
fsimc_columns = [col for col in df.columns if col.startswith('FsimC') or col == 'FsimC_DP']
if not fsimc_columns:
raise ValueError("未找到任何FsimC列")
print(f"找到 {len(fsimc_columns)} 个FsimC列: {', '.join(fsimc_columns)}")
# 曲面描述信息
column_descriptions = {
'FsimC_10': '10m viewing distance',
'FsimC_20': '20m viewing distance',
'FsimC_30': '30m viewing distance',
'FsimC_40': '40m viewing distance',
'FsimC_50': '50m viewing distance',
'FsimC_60': '60m viewing distance',
'FsimC_70': '70m viewing distance',
'FsimC_80': '80m viewing distance',
'FsimC_90': '90m viewing distance',
'FsimC_100': '100m viewing distance',
'FsimC_DP': 'recommended range' # 新增描述
}
points = df[['BOW', 'WAVE']].values
fsimc_values = {col: df[col].values for col in fsimc_columns}
all_fsimc_min = float('inf')
all_fsimc_max = float('-inf')
for col in fsimc_columns:
col_min = df[col].min()
col_max = df[col].max()
if col_min < all_fsimc_min:
all_fsimc_min = col_min
if col_max > all_fsimc_max:
all_fsimc_max = col_max
z_padding = (all_fsimc_max - all_fsimc_min) * 0.1
z_min = all_fsimc_min - z_padding
z_max = all_fsimc_max + z_padding
print(f"全局FsimC范围: {all_fsimc_min:.4f} 到 {all_fsimc_max:.4f}")
print(f"设置Z轴范围: {z_min:.4f} 到 {z_max:.4f}")
except FileNotFoundError:
print(f"错误: 找不到数据文件 {excel_path}")
print("请确保fsim_data.xlsx文件位于脚本同一目录下")
return
except Exception as e:
print(f"加载数据时出错: {str(e)}")
return
# 2. 创建高分辨率网格
print("创建高分辨率网格...")
x_fine = np.linspace(1, 5, 150)
y_fine = np.linspace(0.1, 1.0, 100)
X_fine, Y_fine = np.meshgrid(x_fine, y_fine)
# 3. 创建3D图表
print("创建3D图表...")
fig = plt.figure(figsize=(18, 14))
ax = fig.add_axes([0.1, 0.07, 0.7, 0.83], projection='3d')
# 4. 定义全局颜色映射
print("创建颜色映射...")
global_cmap = LinearSegmentedColormap.from_list(
"global_cmap",
[(0.1, 0.2, 0.8), (0.2, 0.8, 0.4), (1.0, 0.9, 0.1)],
N=512
)
global_norm = Normalize(vmin=all_fsimc_min, vmax=all_fsimc_max)
alphas = [0.65, 0.55, 0.45, 0.35, 0.25, 0.15]
# 5. 曲面绘制和标签添加
print(f"开始处理 {len(fsimc_columns)} 个曲面并添加标签...")
# 查找固定点(BOW=1, WAVE=1)的网格索引
bow_target = 1.0
wave_target = 1.0
bow_idx = np.abs(x_fine - bow_target).argmin()
wave_idx = np.abs(y_fine - wave_target).argmin()
# 存储标签位置用于避免重叠
label_positions = []
min_spacing = 0.035
base_offset = 0.015
# 预计算所有曲面在固定点的Z值
fixed_point_z = {}
for col_name, values in fsimc_values.items():
Z_fine = griddata(points, values, (X_fine, Y_fine), method='cubic')
fixed_point_z[col_name] = Z_fine[wave_idx, bow_idx]
# 按固定点Z值降序排列(从高到低)- 确保 FsimC_DP 最后绘制
fsimc_columns_without_dp = [col for col in fsimc_columns if col != 'FsimC_DP']
sorted_columns = sorted(fsimc_columns_without_dp, key=lambda col: fixed_point_z[col], reverse=True)
# 如果有 FsimC_DP,则添加到列表末尾以确保最后绘制(最上层)
if 'FsimC_DP' in fsimc_columns:
sorted_columns.append('FsimC_DP')
# 记录上一个标签的高度和标签位置字典
last_label_z = float('-inf')
label_z_values = {}
max_label_z = float('-inf')
# 存储每个列名对应的标签颜色
label_colors = {}
# 第一遍:确定所有标签位置
for col_name in sorted_columns:
base_z = fixed_point_z[col_name]
label_z = base_z + base_offset
if label_z > last_label_z - min_spacing:
label_z = min(label_z, last_label_z - min_spacing)
if label_z < base_z + 0.005:
label_z = base_z + 0.005
last_label_z = label_z
label_z_values[col_name] = label_z
if label_z > max_label_z:
max_label_z = label_z
# 第二遍:绘制曲面和标签
for i, col_name in enumerate(sorted_columns):
values = fsimc_values[col_name]
print(f"正在处理列: {col_name} ({i+1}/{len(sorted_columns)})")
print(" 执行插值...")
Z_fine = griddata(points, values, (X_fine, Y_fine), method='cubic')
# 曲面着色 - 为 FsimC_DP 使用单一红色
if col_name == 'FsimC_DP':
# 创建固定颜色的数组 (纯红色)
facecolors = np.zeros((Z_fine.shape[0]-1, Z_fine.shape[1]-1, 4))
facecolors[:, :, 0] = 1.0 # R
facecolors[:, :, 1] = 0.0 # G
facecolors[:, :, 2] = 0.0 # B
else:
# 常规着色方式
Z_top_left = Z_fine[:-1, :-1]
Z_top_right = Z_fine[:-1, 1:]
Z_bottom_left = Z_fine[1:, :-1]
Z_bottom_right = Z_fine[1:, 1:]
Z_midpoints = (Z_top_left + Z_top_right + Z_bottom_left + Z_bottom_right) / 4.0
facecolors = global_cmap(global_norm(Z_midpoints))
# 设置透明度(延续之前的规律)
alpha_idx = min(i, len(alphas)-1)
alpha = alphas[alpha_idx]
facecolors[:, :, 3] = alpha
print(f" 渲染曲面 (透明度: {alpha:.2f})...")
stride_x = 1
stride_y = 1
if len(x_fine) > 100:
render_density = max(1, int(len(x_fine)/75))
stride_x = render_density
stride_y = max(1, int(render_density/1.5))
surf = ax.plot_surface(
X_fine[::stride_y, ::stride_x],
Y_fine[::stride_y, ::stride_x],
Z_fine[::stride_y, ::stride_x],
facecolors=facecolors[::stride_y, ::stride_x],
shade=False,
rcount=min(300, len(y_fine)),
ccount=min(300, len(x_fine)),
alpha=alpha,
antialiased=True,
edgecolor='black',
linewidth=0.1,
zorder=len(fsimc_columns)-i
)
print(" 添加曲面标签...")
label_x = X_fine[wave_idx, bow_idx]
label_y = Y_fine[wave_idx, bow_idx]
base_z = fixed_point_z[col_name]
# 为 FsimC_DP 使用红色标签,其他使用基于值的颜色
if col_name == 'FsimC_DP':
text_color = (1.0, 0.0, 0.0) # 纯红色
else:
normalized_value = global_norm(base_z)
text_color = global_cmap(normalized_value)[:3] # RGB颜色
# 存储标签颜色
label_colors[col_name] = text_color
# 使用计算的标签高度
label_z = label_z_values[col_name]
# 添加标签(移除标签背景框)
ax.text(
label_x, label_y, label_z,
" " + col_name,
fontsize=11,
color=text_color,
fontweight='bold',
ha='left',
va='center',
zorder=100
)
label_positions.append((label_x, label_y, label_z))
# 6. 调整Z轴上限以容纳标签
if max_label_z > z_max:
print(f"调整Z轴上限以容纳标签: 原上限 {z_max:.4f}, 新上限 {max_label_z + 0.03:.4f}")
z_max = max_label_z + 0.03
# 7. 添加全局注释(无背景框)
print("添加全局注释(无背景框)...")
# 注释位置参数 - 调整后位置 (0.90, 0.10)
annotation_x = 0.90 # 在图形右侧,向左移动避免与颜色条重叠
annotation_y = 0.10 # 图形底部上方,提高位置避免被遮挡
annotation_height = len(fsimc_columns) * 0.02 # 每行高度0.02
# 添加带颜色的注释文本(使用图形坐标)
for i, col in enumerate(fsimc_columns):
text = f"{col}: {column_descriptions.get(col, 'Undefined description')}"
text_y = annotation_y + (len(fsimc_columns) - i - 1) * 0.02
# 使用对应的标签颜色
color = label_colors.get(col, 'darkblue')
# 添加注释文本(无背景框)
plt.figtext(
annotation_x,
text_y,
text,
fontsize=12,
color=color,
ha='right',
va='bottom',
zorder=100,
fontweight='bold' # 加粗文本提高可读性
)
# 8. 坐标轴设置
ax.set_xlabel('BOW (‰)', fontsize=15, labelpad=25)
ax.set_ylabel('WAVE (‰)', fontsize=15, labelpad=25)
ax.set_zlabel('FsimC', fontsize=15, labelpad=25)
title = ax.set_title(
f'Multi-FsimC Analysis ({len(fsimc_columns)} Surfaces)',
fontsize=20,
pad=15,
y=0.98
)
ax.set_xticks(np.linspace(1, 5, 5))
ax.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0])
z_step = 0.05
z_min_rounded = np.floor(z_min / z_step) * z_step
z_max_rounded = np.ceil(z_max / z_step) * z_step
z_ticks = np.arange(z_min_rounded, z_max_rounded + z_step/2, z_step)
if len(z_ticks) > 10:
step_idx = max(1, int(len(z_ticks) / 5))
z_ticks = z_ticks[::step_idx]
ax.set_zticks(z_ticks)
ax.set_zticklabels([f"{z:.2f}" for z in z_ticks])
ax.tick_params(axis='both', which='major', labelsize=11, pad=10)
# 9. 网格和平面设置
print("添加背景网格线...")
grid_params = {
'visible': True,
'linestyle': '--',
'linewidth': 0.5,
'alpha': 0.2,
'color': 'gray'
}
ax.xaxis._axinfo["grid"].update(grid_params)
ax.yaxis._axinfo["grid"].update(grid_params)
ax.zaxis._axinfo["grid"].update(grid_params)
ax.xaxis.pane.fill = False
ax.yaxis.pane.fill = False
ax.zaxis.pane.fill = False
ax.xaxis.pane.set_edgecolor('lightgray')
ax.yaxis.pane.set_edgecolor('lightgray')
ax.zaxis.pane.set_edgecolor('lightgray')
ax.xaxis.pane.set_alpha(0.05)
ax.yaxis.pane.set_alpha(0.05)
ax.zaxis.pane.set_alpha(0.05)
# 10. 视图设置
print("配置轴测图投影...")
ax.set_proj_type('ortho')
ax.view_init(elev=32, azim=42)
ax.set_box_aspect(aspect=(1, 1, 1))
ax.set_xlim(0.5, 5.5)
ax.set_ylim(0.0, 1.1)
ax.set_zlim(z_min, z_max)
# 11. 添加全局颜色条
print("添加全局FsimC颜色条...")
cax = fig.add_axes([0.85, 0.25, 0.02, 0.5])
cbar = ColorbarBase(
cax,
cmap=global_cmap,
norm=global_norm,
orientation='vertical',
label='FsimC Value'
)
cbar.set_label('FsimC Value', fontsize=12, labelpad=15)
cbar.ax.tick_params(labelsize=10)
# 12. 保存和显示
print("保存多曲面轴测图...")
plt.savefig(f'multi_fsimc_surfaces_{len(fsimc_columns)}.png', dpi=350, bbox_inches='tight')
print("渲染完成,显示图表...")
plt.tight_layout(pad=3.0)
plt.show()
if __name__ == "__main__":
generate_multi_fsimc_surfaces()
上述python代码中导出png文件名中曲面数量,不应包含FsimC_DP曲面。将FsimC_DP曲面上部空间生成一个空间体,颜色与FsimC_DP曲面一致,相同透明度,高度仅到现有FsimC值的最大值,调整python代码。