Aurelian-Chen/NovasX2
0
1import os2os.environ["STREAMLIT_BROWSER_GATHER_USAGE_STATS"] = "false"3os.environ["STREAMLIT_SERVER_ENABLE_WEBSOCKET_COMPRESSION"] = "false"4 5import pandas as pd6import plotly.express as px7import plotly.graph_objects as go8import streamlit as st9import numpy as np10from typing import Dict, List, Optional11from pricing_module import get_categories, get_platforms, get_follower_breakpoints, get_price12 13def format_large_number(num: float, is_english: bool = False) -> str:14 """将大数格式化为易读形式,保留两位小数,支持中英文格式15 16 Args:17 num: 要格式化的数字18 is_english: 是否使用英文格式 (True 使用K/M/B, False 使用万/亿)19 """20 # 移除多余小数位,保留最多两位小数21 def clean_decimal(n: float) -> str:22 s = f"{n:.2f}"23 # 如果小数部分是.00,则只返回整数部分24 if s.endswith('.00'):25 return s[:-3]26 # 如果小数部分以0结尾,则去掉末尾的027 elif s.endswith('0'):28 return s[:-1]29 return s30 31 if is_english:32 if num >= 1_000_000_000: # Billion33 return f"{clean_decimal(num/1_000_000_000)}B"34 elif num >= 1_000_000: # Million35 return f"{clean_decimal(num/1_000_000)}M"36 elif num >= 1_000: # Thousand37 return f"{clean_decimal(num/1_000)}K"38 else:39 return clean_decimal(num)40 else:41 if num >= 100_000_000: # 亿42 return f"{clean_decimal(num/100_000_000)}亿"43 elif num >= 10_000: # 万44 return f"{clean_decimal(num/10_000)}万"45 elif num >= 1_000: # 千46 return f"{clean_decimal(num/1_000)}千"47 else:48 return clean_decimal(num)49 50def format_price(price: float, is_english: bool = False) -> str:51 """将价格格式化为易读形式,保留两位小数,支持中英文格式52 53 Args:54 price: 要格式化的价格55 is_english: 是否使用英文格式 (True 使用$, False 使用¥)56 """57 # 移除多余小数位,保留最多两位小数58 def clean_decimal(n: float) -> str:59 s = f"{n:.2f}"60 # 如果小数部分是.00,则只返回整数部分61 if s.endswith('.00'):62 return s[:-3]63 # 如果小数部分以0结尾,则去掉末尾的064 elif s.endswith('0'):65 return s[:-1]66 return s67 68 if is_english:69 if price >= 1_000_000:70 return f"${clean_decimal(price/1_000_000)}M"71 elif price >= 1_000:72 return f"${clean_decimal(price/1_000)}K"73 else:74 return f"${clean_decimal(price)}"75 else:76 if price >= 10_000:77 return f"¥{clean_decimal(price/10_000)}万"78 else:79 return f"¥{clean_decimal(price)}"80 81def create_platform_comparison_chart(prices: Dict[str, float], title: str = "各平台报价对比") -> go.Figure:82 """创建平台报价对比柱状图"""83 platforms = list(prices.keys())84 values = list(prices.values())85 86 # 使用更饱和的蓝色系配色方案创建柱状图,提高对比度87 colors = ['rgba(65, 105, 225, 0.95)', 'rgba(100, 149, 237, 0.95)', 88 'rgba(30, 144, 255, 0.95)', 'rgba(70, 130, 180, 0.95)']89 90 fig = go.Figure()91 for i, (platform, value) in enumerate(zip(platforms, values)):92 fig.add_trace(go.Bar(93 x=[platform], 94 y=[value],95 name=platform,96 marker_color=colors[i % len(colors)],97 text=[format_price(value)],98 textposition='outside', # 将文本放在柱状图外部,提高可见性99 textfont=dict(100 color='white', # 确保文本为白色101 size=14, # 增大字体大小102 family="Arial, sans-serif", # 使用清晰的字体103 ),104 marker=dict(105 line=dict(106 width=1,107 color='rgba(255, 255, 255, 0.5)' # 添加白色边框增强对比度108 )109 )110 ))111 112 fig.update_layout(113 title={114 'text': title,115 'font': {'size': 18, 'color': 'white'}, # 增大标题字体116 'y': 0.95, # 调整标题位置117 },118 plot_bgcolor='rgba(0,0,0,0.1)', # 轻微的黑色背景,增强对比度119 paper_bgcolor='rgba(0,0,0,0)',120 font=dict(121 color='white',122 size=14, # 增大整体字体大小123 family="Arial, sans-serif" # 使用清晰的字体124 ),125 showlegend=False,126 height=400, # 适当增加高度,使内容更清晰127 autosize=True, # 启用自动大小调整128 yaxis=dict(129 title='报价 (元)',130 title_font={'size': 15, 'color': 'white'}, # 轴标题字体大小和颜色131 gridcolor='rgba(255,255,255,0.2)', # 增加网格线对比度132 tickfont={'size': 13, 'color': 'white'}, # 轴刻度字体大小和颜色133 tickmode='auto',134 nticks=6, # 控制Y轴刻度数量135 showgrid=True,136 zeroline=True,137 zerolinecolor='rgba(255,255,255,0.3)', # 零线颜色138 zerolinewidth=1139 ),140 xaxis=dict(141 title='社交媒体平台',142 title_font={'size': 15, 'color': 'white'}, # 轴标题字体大小和颜色143 tickfont={'size': 13, 'color': 'white'}, # 轴刻度字体大小和颜色144 tickangle=0, # 保持刻度标签水平145 ),146 margin=dict(l=20, r=20, t=80, b=20), # 增加顶部边距,给标题更多空间147 )148 149 return fig150 151def create_follower_price_curve(category: str, max_followers: float = 2000000, 152 steps: int = 50) -> go.Figure:153 """创建粉丝量-价格曲线图"""154 platforms = get_platforms()155 follower_range = np.linspace(0, max_followers, steps)156 157 fig = go.Figure()158 159 # 使用更明亮的颜色以提高对比度160 colors = ['#4169E1', '#9370DB', '#6A5ACD', '#20B2AA']161 162 for i, platform in enumerate(platforms):163 prices = [get_price(platform, category, followers) for followers in follower_range]164 165 fig.add_trace(go.Scatter(166 x=follower_range,167 y=prices,168 mode='lines+markers', # 添加标记点,增强可读性169 name=platform,170 line=dict(color=colors[i % len(colors)], width=4), # 增加线宽171 marker=dict(size=6, opacity=0.7), # 添加小标记点172 hovertemplate="粉丝数: %{x:,.0f}<br>价格: %{y:.2f}元<extra></extra>",173 ))174 175 # 添加粉丝基准点标记176 breakpoints = get_follower_breakpoints()177 if len(breakpoints) > 0:178 for bp in breakpoints:179 if bp <= max_followers:180 fig.add_vline(181 x=bp, 182 line=dict(color='rgba(255,255,255,0.4)', dash='dash', width=1.5), # 增加线的可见度183 annotation_text=format_large_number(bp),184 annotation_position="top right",185 annotation_font=dict(color="white", size=12) # 确保标注文字清晰186 )187 188 fig.update_layout(189 title={190 'text': f'{category}类别不同粉丝量的价格曲线',191 'font': {'size': 18, 'color': 'white'} # 增大标题字体192 },193 xaxis_title={194 'text': '粉丝数量',195 'font': {'size': 14, 'color': 'white'} # 轴标题字体196 },197 yaxis_title={198 'text': '价格 (元)',199 'font': {'size': 14, 'color': 'white'} # 轴标题字体200 },201 plot_bgcolor='rgba(0,0,0,0)',202 paper_bgcolor='rgba(0,0,0,0)',203 font=dict(204 color='white',205 size=13, # 增大整体字体大小206 family="Arial, sans-serif" # 使用清晰的字体207 ),208 height=450, # 减少高度,更适合移动端209 autosize=True, # 启用自动大小调整210 xaxis=dict(211 gridcolor='rgba(255,255,255,0.2)', # 增加网格线对比度212 tickvals=breakpoints,213 ticktext=[format_large_number(bp) for bp in breakpoints],214 tickfont={'size': 12, 'color': 'white'} # 确保刻度标签清晰215 ),216 yaxis=dict(217 gridcolor='rgba(255,255,255,0.2)', # 增加网格线对比度218 tickfont={'size': 12, 'color': 'white'} # 确保刻度标签清晰219 ),220 legend=dict(221 orientation="h",222 yanchor="bottom",223 y=1.02,224 xanchor="right",225 x=1,226 font=dict(size=13, color="white"), # 增加图例字体大小227 bgcolor="rgba(0,0,0,0.4)", # 添加半透明背景提高对比度228 bordercolor="rgba(255,255,255,0.3)",229 borderwidth=1230 ),231 margin=dict(l=20, r=20, t=100, b=40), # 显著增加顶部和底部边距,解决移动端标题重叠问题232 )233 234 return fig235 236def create_category_comparison_radar(followers: float, platform: Optional[str] = None) -> go.Figure:237 """创建不同类别的价格雷达图"""238 categories = get_categories()239 240 if platform:241 # 单一平台的所有类别242 values = [get_price(platform, category, followers) for category in categories]243 244 fig = go.Figure()245 fig.add_trace(go.Scatterpolar(246 r=values,247 theta=categories,248 fill='toself',249 name=platform,250 line=dict(251 color='rgba(65, 105, 225, 0.9)', # 增加颜色饱和度252 width=3 # 增加线宽253 ),254 fillcolor='rgba(65, 105, 225, 0.3)', # 增加填充透明度255 hovertemplate='%{theta}: %{r:,.0f}元<extra></extra>' # 添加悬浮提示256 ))257 258 title = f'{platform}平台在{format_large_number(followers)}粉丝量下各类别价格'259 else:260 # 所有平台的所有类别261 platforms = get_platforms()262 263 fig = go.Figure()264 # 使用更饱和的颜色265 colors = ['rgba(65, 105, 225, 0.9)', 'rgba(138, 43, 226, 0.9)', 266 'rgba(72, 61, 139, 0.9)', 'rgba(106, 90, 205, 0.9)']267 268 for i, platform in enumerate(platforms):269 values = [get_price(platform, category, followers) for category in categories]270 271 fig.add_trace(go.Scatterpolar(272 r=values,273 theta=categories,274 fill='toself',275 name=platform,276 line=dict(277 color=colors[i % len(colors)],278 width=3 # 增加线宽279 ),280 fillcolor=colors[i % len(colors)].replace('0.9', '0.3'), # 调整填充透明度281 hovertemplate='%{theta}: %{r:,.0f}元<extra></extra>' # 添加悬浮提示282 ))283 284 title = f'各平台在{format_large_number(followers)}粉丝量下不同类别价格对比'285 286 fig.update_layout(287 polar=dict(288 radialaxis=dict(289 visible=True,290 gridcolor='rgba(255,255,255,0.25)', # 增加网格线对比度291 linecolor='rgba(255,255,255,0.5)', # 增加轴线对比度292 angle=45, # 调整径向轴标签角度293 tickfont=dict(size=12, color='white') # 径向轴刻度标签294 ),295 angularaxis=dict(296 gridcolor='rgba(255,255,255,0.25)', # 增加网格线对比度297 linecolor='rgba(255,255,255,0.5)', # 增加轴线对比度298 tickfont=dict(size=11, color='white', family="Arial, sans-serif") # 角度轴刻度标签299 ),300 bgcolor='rgba(0,0,0,0)',301 ),302 title={303 'text': title,304 'font': {'size': 18, 'color': 'white'} # 增大标题字体305 },306 plot_bgcolor='rgba(0,0,0,0)',307 paper_bgcolor='rgba(0,0,0,0)',308 font=dict(309 color='white',310 size=13,311 family="Arial, sans-serif"312 ),313 height=500, # 减少高度,更适合移动端314 autosize=True, # 启用自动大小调整315 legend=dict(316 orientation="h",317 yanchor="bottom",318 y=-0.1,319 xanchor="center",320 x=0.5,321 font=dict(size=13, color="white"), # 增加图例字体大小322 bgcolor="rgba(0,0,0,0.4)", # 添加半透明背景提高对比度323 bordercolor="rgba(255,255,255,0.3)",324 borderwidth=1325 ),326 margin=dict(l=40, r=40, t=100, b=50), # 显著增加顶部和底部边距,解决移动端标题重叠问题327 )328 329 return fig330 331def create_platform_coefficient_heatmap(selected_categories: Optional[List[str]] = None) -> go.Figure:332 """创建平台系数热力图"""333 from pricing_module import COEFF_MATRIX334 335 platforms = get_platforms()336 337 if not selected_categories:338 categories = get_categories()339 else:340 categories = selected_categories341 342 # 准备热力图数据343 data = []344 for category in categories:345 row = [COEFF_MATRIX[category].get(platform, 1.0) for platform in platforms]346 data.append(row)347 348 df = pd.DataFrame(data, index=categories, columns=platforms)349 350 # 创建热力图 - 使用更适合深色背景的配色方案351 fig = px.imshow(352 df, 353 labels=dict(x="平台", y="内容类别", color="系数"),354 x=platforms,355 y=categories,356 color_continuous_scale='Plasma', # 改用Plasma配色方案,在黑色背景上更加清晰357 aspect="auto",358 zmin=0.5, # 设置最小值使颜色对比更明显359 zmax=2.0, # 设置最大值360 )361 362 fig.update_layout(363 title={364 'text': '内容类别在不同平台的系数热力图',365 'font': {'size': 18, 'color': 'white'} # 增大标题字体366 },367 plot_bgcolor='rgba(0,0,0,0)',368 paper_bgcolor='rgba(0,0,0,0)',369 font=dict(370 color='white',371 size=13,372 family="Arial, sans-serif"373 ),374 height=max(350, len(categories) * 18), # 减少高度,更适合移动端375 autosize=True, # 启用自动大小调整376 xaxis=dict(377 title={378 'text': '平台',379 'font': {'size': 14, 'color': 'white'}380 },381 side='top',382 tickfont={'size': 12, 'color': 'white'}383 ),384 yaxis=dict(385 title={386 'text': '内容类别',387 'font': {'size': 14, 'color': 'white'}388 },389 tickfont={'size': 12, 'color': 'white'}390 ),391 margin=dict(l=20, r=40, t=100, b=40), # 显著增加顶部和底部边距,为颜色条提供更多空间392 coloraxis=dict(393 colorbar=dict(394 title={395 'text': '系数值',396 'font': {'size': 12, 'color': 'white'}397 },398 tickfont={'color': 'white', 'size': 12},399 outlinecolor='rgba(255,255,255,0.3)',400 outlinewidth=1401 )402 )403 )404 405 # 添加文本标注406 for i in range(len(categories)):407 for j in range(len(platforms)):408 # 根据系数值确定文本颜色,确保在各种背景下都清晰可见409 value = df.iloc[i, j]410 if value <= 0.8:411 text_color = "white" # 低值区域用白色文本412 elif value <= 1.2:413 text_color = "yellow" # 中值区域用黄色文本414 else:415 text_color = "black" # 高值区域用黑色文本416 417 fig.add_annotation(418 x=j,419 y=i,420 text=f"{value:.2f}",421 showarrow=False,422 font=dict(423 color=text_color,424 size=12,425 family="Arial, sans-serif"426 )427 )428 429 return fig430 431def create_top_categories_chart(followers: float, platform: str, n: int = 10) -> go.Figure:432 """创建指定平台下报价最高的前N个类别图表"""433 categories = get_categories()434 435 prices = []436 for category in categories:437 price = get_price(platform, category, followers)438 prices.append((category, price))439 440 # 按价格降序排序441 prices.sort(key=lambda x: x[1], reverse=True)442 443 # 取前N个444 top_categories = [p[0] for p in prices[:n]]445 top_prices = [p[1] for p in prices[:n]]446 447 # 创建水平条形图 - 使用渐变色使图表更加美观448 color_scale = px.colors.sequential.Blues_r # 使用反转的蓝色渐变色449 # 生成一个颜色梯度,让排名靠前的类别颜色更深450 colors = [color_scale[int(i * (len(color_scale)-1) / (n-1) if n > 1 else 0)] for i in range(n)]451 452 fig = go.Figure()453 fig.add_trace(go.Bar(454 y=top_categories,455 x=top_prices,456 orientation='h',457 marker=dict(458 color=colors,459 opacity=0.9, # 增加不透明度460 line=dict(width=1, color='rgba(255,255,255,0.3)') # 添加细微边框461 ),462 text=[format_price(p) for p in top_prices],463 textposition='auto',464 textfont=dict(465 color='white', # 确保文本为白色466 size=13, # 增大字体大小467 family="Arial, sans-serif" # 使用清晰的字体468 ),469 hovertemplate='%{y}: %{x:,.0f}元<extra></extra>'470 ))471 472 fig.update_layout(473 title={474 'text': f'{platform}平台{format_large_number(followers)}粉丝量下报价最高的{n}个类别',475 'font': {'size': 18, 'color': 'white'} # 增大标题字体476 },477 plot_bgcolor='rgba(0,0,0,0)',478 paper_bgcolor='rgba(0,0,0,0)',479 font=dict(480 color='white',481 size=13,482 family="Arial, sans-serif"483 ),484 height=max(400, n * 28), # 减少高度,更适合移动端,但仍保持动态调整485 autosize=True, # 启用自动大小调整486 xaxis=dict(487 title={488 'text': '报价 (元)',489 'font': {'size': 14, 'color': 'white'}490 },491 gridcolor='rgba(255,255,255,0.2)', # 增加网格线对比度492 tickfont={'size': 12, 'color': 'white'}, # 确保刻度标签清晰493 showgrid=True494 ),495 yaxis=dict(496 title={497 'text': '内容类别',498 'font': {'size': 14, 'color': 'white'}499 },500 categoryorder='total ascending',501 tickfont={'size': 12, 'color': 'white'} # 确保刻度标签清晰502 ),503 margin=dict(l=20, r=20, t=100, b=40), # 显著增加顶部和底部边距,解决移动端标题重叠问题504 )505 506 return fig507 