from common import *
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle,Patch
g=pd.read_parquet(R/'games.parquet')
names=['Harvester Games','Freebird Games','Erik Asmussen','Endlessfluff Games','Abbey Games','Blendo Games','Big Robot Ltd','BeautiFun Games','Daniel Mullins Games','Suspicious Developments','David Szymanski','Nolla Games']
plt.rcParams.update({'font.family':['DejaVu Sans','Noto Sans CJK TC'],'font.size':10,'axes.spines.top':False,'axes.spines.right':False})
fig,ax=plt.subplots(figsize=(15,8));colors={'Q':'#d1d7db','M':'#d9b78a','H':'#4c91a8','B':'#78649c'}
def fmt(n):return f'{n/1000:.1f}k' if n>=1000 else str(n)
for y,name in enumerate(names):
    a=g[g.credit.eq(name)].sort_values(['date','appid'])
    for j,row in enumerate(a.itertuples()):
        color=colors[row.review_band] if row.mature else 'white';rect=Rectangle((j-.43,y-.36),.86,.72,facecolor=color,edgecolor='#c4c4c4',lw=.7);rect.set_url(row.game_url);ax.add_patch(rect)
        ax.text(j,y,fmt(row.reviews)+(' *' if not row.mature else ''),ha='center',va='center',fontsize=9,color='white' if row.mature and row.review_band in ['H','B'] else '#222')
ax.set_yticks(np.arange(len(names)),names);ax.invert_yaxis();ax.set_ylim(len(names)-.5,-.8);ax.set_xlim(-.6,8.6);ax.set_xticks(np.arange(9),np.arange(1,10));ax.set_xlabel('Order of included paid Steam release');ax.spines[['left','bottom']].set_visible(False);ax.tick_params(length=0)
ax.set_title('Different shapes of an observed Steam catalog\nCurrent review totals; these rows are selected examples, not equal-age career forecasts.',loc='left',fontweight='bold',fontsize=15,pad=20)
ax.legend(handles=[Patch(facecolor=colors[k],label=label) for k,label in [('Q','Under 100'),('M','100–999'),('H','1,000–9,999'),('B','10,000+')]],loc='upper center',bbox_to_anchor=(.5,-.1),ncol=4,frameon=False)
fig.text(.05,.018,'* Under one year, excluded from mature-sequence statistics. Developer/game links and exact counts are in case_atlas.md; SVG cells also link to games.',fontsize=9,color='#555')
fig.tight_layout(rect=[0,.065,1,1]);fig.savefig(R/'career_sequences.png',dpi=170);fig.savefig(R/'career_sequences.svg');plt.close(fig)
e=pd.read_csv(R/'backcatalog_events.csv');a=e[e.isolated].sort_values('difference');fig,ax=plt.subplots(figsize=(13,8));y=np.arange(len(a))
ax.barh(y,a.pre90,color='#b7cbd2',height=.7,label='90 days before');ax.scatter(a.post90,y,color='#b57345',s=28,zorder=3,label='90 days after')
ax.set_yticks(y,[f'{r.credit}: {r.new_game}' for r in a.itertuples()],fontsize=8);ax.set_xlabel('Reviews arriving for included earlier games (at least one year old)');ax.set_title('Back-catalog response around isolated later releases\nSelected complete paid catalogs; timing does not establish transfer or causation.',loc='left',fontweight='bold');ax.legend(frameon=False);fig.tight_layout();fig.savefig(R/'backcatalog_events.png',dpi=170);fig.savefig(R/'backcatalog_events.svg');plt.close(fig)
