// Living species occupy stable depth-first lanes; extinct ancestors are reparented by World.sample(). export function lineageOrder(species){ const byId=new Map(species.map(s=>[s.id,s])),ordered=[],seen=new Set(); const visit=(s,depth)=>{if(seen.has(s.id))return;seen.add(s.id);ordered.push({s,depth});species.filter(c=>c.parent===s.id).sort((a,b)=>a.origin-b.origin||a.id-b.id).forEach(c=>visit(c,depth+1))}; species.filter(s=>!byId.has(s.parent)).sort((a,b)=>a.id-b.id).forEach(s=>visit(s,0)); for(const s of species)visit(s,0); return ordered; } export function lineageColor(s,layer='plants'){ if(layer==='trophic')return s.trophicRole==='primary-consumer'?'#a7d46d':s.trophicRole==='secondary-consumer'?'#e9c976':s.trophicRole==='tertiary-consumer'?'#d88979':'#aa8bc7'; if(layer==='habitat')return s.g.waterAffinity<1/3?'#d5bc88':s.g.waterAffinity<2/3?'#86c0a1':'#78b8da'; return s.color; } export function drawLineage(ctx,{width:w,height:h,species,hist,scale=1,layer='plants'}){ const ordered=lineageOrder(species),byId=new Map(species.map(s=>[s.id,s])); ctx.setTransform(scale,0,0,scale,0,0);ctx.clearRect(0,0,w,h); const compact=w<500,left=compact?66:115,right=w-(compact?37:57),first=hist[0]?.time??0,last=hist.at(-1)?.time??first,span=Math.max(1,last-first),x=time=>left+Math.max(0,Math.min(1,(time-first)/span))*(right-left); ctx.fillStyle='#899c94';ctx.font='12px system-ui';ctx.textAlign='right';ctx.fillText('種',left-12,20);ctx.textAlign='left';ctx.fillText('現在',right+8,20); for(const [i,{s}] of ordered.entries()){ const y=36+i*31;ctx.strokeStyle='#2c3c3c';ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(left,y);ctx.lineTo(right,y);ctx.stroke(); ctx.fillStyle=lineageColor(s,layer);ctx.textAlign='right';ctx.font='12px system-ui';ctx.fillText(compact?s.name.replace('種 ','#'):s.name,left-8,y+4);ctx.textAlign='left';ctx.fillStyle='#dce7df';ctx.fillText(s.count.toLocaleString(),right+8,y+4); } const lanes=new Map(ordered.map((v,i)=>[v.s.id,36+i*31])); for(const {s} of ordered){if(!lanes.has(s.parent)||s.origin>last)continue;const cx=x(Math.max(first,s.origin)),py=lanes.get(s.parent),cy=lanes.get(s.id),birthCount=hist.find(v=>v.time>=s.origin)?.counts[s.id]||s.count,width=Math.max(1.5,Math.min(16,1+Math.sqrt(birthCount)*.78));ctx.strokeStyle=lineageColor(s,layer);ctx.globalAlpha=s.origin