This commit is contained in:
2026-02-03 01:44:51 +08:00
parent 5cc8c5bd9a
commit 03110c6f66
4 changed files with 131 additions and 0 deletions

View File

@@ -484,6 +484,133 @@ def plot_scenario_comparison(
return fig
def plot_scenario_comparison_combined(
results: List[Dict],
water_demand: Dict,
alpha: float = None,
save_dir: str = '/Volumes/Files/code/mm/20260130_b/p3'
):
"""绘制方案对比图Energy和Time合并显示- 马卡龙色系简约版"""
# 缩小图片尺寸,相对增大字体
fig, axes = plt.subplots(1, 2, figsize=(10, 4.5))
scenarios = [r['scenario_name_cn'] for r in results]
scenario_labels = ['S1\nElevator', 'S2\nRocket', 'S3\nCombined\n(All)', 'S4\nCombined\n(Low-Lat)']
# 马卡龙色系 - 中偏低饱和度柔和色调
macaron_colors = ['#A8D8B9', '#F7C5A8', '#B5C7E3', '#D4B8E0'] # 薄荷绿、杏橙、雾蓝、藤紫
macaron_colors_dark = ['#7BC08F', '#E8A07A', '#8AA5C9', '#B896C7'] # 深色版本用于边框
x = np.arange(len(scenarios))
width = 0.38
# 获取alpha值
if alpha is None:
alpha = water_demand.get('comfort_factor', 1)
# ========== 图1: 首批运输 (Energy + Time) ==========
ax1 = axes[0]
ax1_twin = ax1.twinx()
initial_energy = [r['initial_energy_gj'] / 1000 for r in results] # 转换为TJ
initial_days = [r['initial_days'] for r in results]
# 左轴: Energy (TJ) - 实心柱状图
bars_energy = ax1.bar(x - width/2, initial_energy, width, color=macaron_colors,
edgecolor=macaron_colors_dark, linewidth=1.2, label='Energy (TJ)')
ax1.set_ylabel('Energy (TJ)', fontsize=11, color='#444444', fontweight='medium')
ax1.tick_params(axis='y', labelcolor='#444444', labelsize=9)
ax1.set_ylim(0, max(initial_energy) * 1.25)
# 右轴: Time (Days) - 斜线填充柱状图
bars_time = []
for i, (xi, day) in enumerate(zip(x + width/2, initial_days)):
bar = ax1_twin.bar(xi, day, width, color='white',
edgecolor=macaron_colors_dark[i], linewidth=1.5,
hatch='///', label='Time (Days)' if i == 0 else '')
bars_time.append(bar[0])
ax1_twin.set_ylabel('Days', fontsize=11, color='#666666', fontweight='medium')
ax1_twin.tick_params(axis='y', labelcolor='#666666', labelsize=9)
ax1_twin.set_ylim(0, max(initial_days) * 1.25)
# 添加数值标注 - 简洁风格
for bar, val in zip(bars_energy, initial_energy):
ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + max(initial_energy)*0.02,
f'{val:.1f}', ha='center', fontsize=8, color='#333333')
for bar, val in zip(bars_time, initial_days):
ax1_twin.text(bar.get_x() + bar.get_width()/2, bar.get_height() + max(initial_days)*0.02,
f'{val:.1f}', ha='center', fontsize=8, color='#666666')
ax1.set_xticks(x)
ax1.set_xticklabels(scenario_labels, fontsize=9)
ax1.spines['top'].set_visible(False)
ax1_twin.spines['top'].set_visible(False)
ax1.grid(True, alpha=0.2, axis='y', linestyle='--')
# 简化图例
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor='#B5C7E3', edgecolor='#8AA5C9', label='Energy'),
Patch(facecolor='white', edgecolor='#8AA5C9', hatch='///', label='Time')]
ax1.legend(handles=legend_elements, loc='upper right', fontsize=8, framealpha=0.9)
# ========== 图2: 每月补充 (Energy + Time) ==========
ax2 = axes[1]
ax2_twin = ax2.twinx()
monthly_energy = [r['monthly_energy_gj'] / 1000 for r in results] # 转换为TJ
monthly_days = [r['monthly_days'] for r in results]
# 左轴: Energy (TJ) - 实心柱状图
bars_energy2 = ax2.bar(x - width/2, monthly_energy, width, color=macaron_colors,
edgecolor=macaron_colors_dark, linewidth=1.2, label='Energy (TJ)')
ax2.set_ylabel('Energy (TJ)', fontsize=11, color='#444444', fontweight='medium')
ax2.tick_params(axis='y', labelcolor='#444444', labelsize=9)
ax2.set_ylim(0, max(monthly_energy) * 1.25)
# 右轴: Time (Days) - 斜线填充柱状图
bars_time2 = []
for i, (xi, day) in enumerate(zip(x + width/2, monthly_days)):
bar = ax2_twin.bar(xi, day, width, color='white',
edgecolor=macaron_colors_dark[i], linewidth=1.5,
hatch='///', label='Time (Days)' if i == 0 else '')
bars_time2.append(bar[0])
ax2_twin.set_ylabel('Days', fontsize=11, color='#666666', fontweight='medium')
ax2_twin.tick_params(axis='y', labelcolor='#666666', labelsize=9)
ax2_twin.set_ylim(0, max(monthly_days) * 1.25)
# 添加数值标注 - 简洁风格
for bar, val in zip(bars_energy2, monthly_energy):
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + max(monthly_energy)*0.02,
f'{val:.1f}', ha='center', fontsize=8, color='#333333')
for bar, val in zip(bars_time2, monthly_days):
ax2_twin.text(bar.get_x() + bar.get_width()/2, bar.get_height() + max(monthly_days)*0.02,
f'{val:.1f}', ha='center', fontsize=8, color='#666666')
ax2.set_xticks(x)
ax2.set_xticklabels(scenario_labels, fontsize=9)
ax2.spines['top'].set_visible(False)
ax2_twin.spines['top'].set_visible(False)
ax2.grid(True, alpha=0.2, axis='y', linestyle='--')
# 简化图例
ax2.legend(handles=legend_elements, loc='upper right', fontsize=8, framealpha=0.9)
# 设置背景色为浅灰白色
fig.patch.set_facecolor('#FAFAFA')
for ax in [ax1, ax2]:
ax.set_facecolor('#FAFAFA')
plt.tight_layout()
filename = f'{save_dir}/water_transport_combined_alpha{int(alpha)}.png'
plt.savefig(filename, dpi=150, bbox_inches='tight', facecolor='#FAFAFA')
print(f"合并方案对比图已保存至: {filename}")
return fig
def plot_water_demand_breakdown(
water_demand: Dict,
save_dir: str = '/Volumes/Files/code/mm/20260130_b/p3'
@@ -1063,6 +1190,10 @@ def run_water_analysis():
plot_scenario_comparison(all_results[1], all_demands[1])
plot_annual_comparison(all_results[1])
# 为每个alpha值生成合并图
for alpha in comfort_factors:
plot_scenario_comparison_combined(all_results[alpha], all_demands[alpha], alpha)
# 多场景对比图
plot_multi_scenario_comparison(all_results, all_demands)