1// The crossover that isn't â TSMC AI revenue by customer, anonymized (v6.1) 2// v6.1: anonymized per editorial policy â GPU vendors A/B, CSP1âCSP3; composition 3// bars show percentages instead of dollar values; analyst-estimates framing. 4// Revision: drills now show percentages only (no absolute $ values on the chart 5// surface or in drill cards); "Nomura" withdrawn from chart copy â the source 6// line retains the attribution per citation discipline. 7// Stacked composition bars 2025/27F (v8.6: 2026F retired) + the ASIC camp's share 8// rising toward a 50% line it never reaches. Data: V52.value_split (Nomura Fig. 35, 9// PDF-verified). Share labels are approximations (~1/4, ~1/3); exact values in drills. 10(() => { 11 const host = document.getElementById("revenue-split-chart"); 12 if (!host) return; 13 const D = (window.V52 && V52.value_split) || null; 14 const body = U.frame(host, { 15 title: "å¹¶ä¸åå¨çåè¶ ï¼TSMC AI æ¶å ¥æå®¢æ·æåï¼åæå¸ä¼°è®¡ï¼æªç»å ¬å¸ç¡®è®¤ï¼", 16 sub: "å TSMC AI æ¶å ¥ç¾åæ¯ Â· 墨 = GPU éµè¥ · è = ASIC éµè¥ · 2025 对 2027F", 17 src: "Broker research", 18 }); 19 if (!D) { body.innerHTML = '<p style="font-family:monospace;font-size:12px;color:#8595a6;padding:20px 0">value-split æ°æ®éæªå è½½ã</p>'; return; } 20 21 // å¿ååå表ï¼å¾é¢ä¸ç¨ï¼drill ä¿çåå¼ï¼ä¸å°æ å°ï¼ 22 const NAME = { "NVDA": "GPU åå A", "AMD": "GPU åå B", "Google TPU": "CSP1", 23 "AWS Trainium": "CSP2", "Meta MTIA": "CSP3", "HBM base die": "HBM åºç¡è£¸ç", "Other xPUs": "å ¶ä»" }; 24 const anon = s => String(s).replace(/Google(?!'s)/g, "CSP1").replace(/NVDA/g, "GPU åå A") 25 .replace(/AWS/g, "CSP2").replace(/Meta/g, "CSP3").replace(/AMD/g, "GPU åå B"); 26 const SHORT = { "GPU åå A": "GPU A", "GPU åå B": "GPU B", "CSP1": "CSP1", 27 "CSP2": "CSP2", "CSP3": "CSP3", "HBM åºç¡è£¸ç": "HBM", "å ¶ä»": "å ¶ä»" }; 28 29 const canvas = document.createElement("canvas"); 30 canvas.style.cssText = "width:100%;height:420px;display:block;cursor:pointer"; 31 canvas.setAttribute("data-drill-keep", "1"); 32 body.appendChild(canvas); 33 const { fit, ctx } = U.bindCanvas(canvas); 34 let view = fit(); 35 window.addEventListener("resize", () => { view = fit(); draw(true); }); 36 37 const MONO = U.FONTS.mono, SANS = U.FONTS.sans; 38 // ä¸å¾ä¸è²ç³»ï¼GPU éµè¥å¢¨é¶ï¼ASIC éµè¥çµè䏿ï¼å ¶ä½å¢¨é¶æµ è²ï¼æ®µ key æ¥èªæ°æ®å±ï¼ä¸å¯æ¹ï¼ 39 const COL = { navy: "#051c2c", navyLo: "#42566a", blue: U.PAL.red, copper: U.PAL.redHi, purple: U.PAL.blueLo, grey: U.PAL.inkLo, greyLo: "#dbe2ea" }; 40 41 // v8.6ï¼å 2026F 䏿±ï¼åªç 2025 ä¸ 2027Fï¼ä»½é¢æ ç¾çº¦æ°åï¼~1/4 / ~1/3ï¼ï¼ç²¾ç¡®å¼ç drill 42 const IDX = [0, 2]; 43 const YEARS = IDX.map(i => D.years[i]); 44 const TOT = IDX.map(i => D.totals[i]); 45 const SHARE = IDX.map(i => D.derived.asic_share[i]); 46 const SHARE_TXT = ["~1/4", "~1/3"]; 47 48 let hits = [], anim = 0, done = false; 49 50 function draw(skip) { 51 if (!done && !skip) { anim = U.clamp(anim + 0.018, 0, 1); if (anim >= 1) done = true; } 52 ctx.clearRect(0, 0, view.w, view.h); 53 hits = []; 54 const W = view.w; 55 const padL = 70, padR = 40; 56 const barW = W - padL - padR; 57 58 // ââ ä¸åï¼ä¸æ ¹ç»æå å æ¨ªæ¡ ââ 59 ctx.font = "700 10.5px " + MONO; ctx.fillStyle = U.PAL.inkMd; ctx.textAlign = "left"; 60 ctx.fillText("ææ Â· å TSMC AI æ¶å ¥ï¼%", padL, 26); 61 62 const barH = 34, gapY = 26, y0 = 44; 63 YEARS.forEach((yr, yi) => { 64 const y = y0 + yi * (barH + gapY); 65 const total = TOT[yi]; 66 const reveal = U.clamp(anim * 3.2 - yi * 0.6, 0, 1); 67 ctx.font = "700 10.5px " + MONO; ctx.fillStyle = U.PAL.ink; ctx.textAlign = "left"; 68 ctx.fillText(yr + (yr.includes("F") ? " · 估计" : ""), padL, y - 7); 69 let acc = 0; 70 D.segments.forEach(seg => { 71 const v = seg.vals[IDX[yi]]; 72 if (v <= 0) return; 73 const w = (v / total) * barW * reveal; 74 ctx.fillStyle = COL[seg.color]; 75 ctx.fillRect(padL + (acc / total) * barW, y, Math.max(0, w - 1), barH); 76 const segHit = { x: padL + (acc / total) * barW, y, w: Math.max(6, (v / total) * barW), h: barH, 77 drill: { title: NAME[seg.key] + " · " + yr, value: (v / total * 100).toFixed(1) + "% · å TSMC AI æ¶å ¥ï¼" + yr + "ï¼", 78 sub: (seg.camp === "GPU" ? "GPU éµè¥ï¼åå A + Bï¼" : seg.camp === "ASIC" ? "ASIC éµè¥ï¼CSP1âCSP3ï¼" : "两大éµè¥ä¹å¤") + " · å¸åç ç©¶åæå¸ä¼°è
78®¡ââæªç»å ¬å¸ç¡®è®¤ã", 79 source: D.source } }; 80 hits.push(segHit); 81 if (reveal > 0.85 && (v / total) > 0.07) { 82 ctx.font = "700 9.5px " + MONO; 83 const lab = SHORT[NAME[seg.key]] + " " + (v / total * 100).toFixed(1) + "%"; 84 if ((v / total) * barW >= ctx.measureText(lab).width + 10) { 85 ctx.fillStyle = seg.color === "navy" || seg.color === "navyLo" || seg.color === "blue" || seg.color === "copper" ? "#fff" : U.PAL.ink; 86 ctx.textAlign = "left"; 87 ctx.fillText(lab, padL + (acc / total) * barW + 5, y + barH / 2 + 3); 88 } 89 } 90 acc += v; 91 }); 92 }); 93 94 // éµè¥å¾ä¾ï¼å ¨è²æ¿ï¼å¿ååï¼ 95 const legY = y0 + YEARS.length * (barH + gapY) + 6; 96 const leg = [["GPU åå A", COL.navy], ["GPU åå B", COL.navyLo], ["CSP1", COL.blue], ["CSP2", COL.copper], ["CSP3", COL.purple], ["HBM/å ¶ä»", COL.grey]]; 97 let lx = padL; 98 leg.forEach(([t, c]) => { 99 ctx.fillStyle = c; ctx.fillRect(lx, legY - 8, 9, 9); 100 ctx.font = "9px " + MONO; ctx.fillStyle = U.PAL.inkMd; ctx.textAlign = "left"; 101 ctx.fillText(t, lx + 12, legY); 102 lx += ctx.measureText(t).width + 26; 103 }); 104 ctx.font = "9px " + MONO; ctx.fillStyle = U.PAL.inkLo; 105 ctx.fillText("æ±æ¡å½ä¸åè³ 100%ï¼ä¸æ¹ä»½é¢ææªåæ´æ°å¼è®¡ç®", padL, legY + 16); 106 107 // ââ ä¸åï¼ASIC ä»½é¢ vs 50% 线 ââ 108 const sy0 = legY + 54, sy1 = view.h - 56; 109 ctx.font = "700 10.5px " + MONO; ctx.fillStyle = U.PAL.inkMd; ctx.textAlign = "left"; 110 ctx.fillText("ASIC éµè¥å TSMC AI æ¶å ¥ä»½é¢ · å¯¹ç § 50% åè¶ çº¿", padL, sy0 - 14); 111 112 const sX = v => padL + (v / 55) * barW; 113 // 50% 线 114 ctx.strokeStyle = U.PAL.neg; ctx.lineWidth = 1.4; ctx.setLineDash([5, 3]); 115 ctx.beginPath(); ctx.moveTo(sX(50), sy0); ctx.lineTo(sX(50), sy1); ctx.stroke(); 116 ctx.setLineDash([]); 117 ctx.font = "700 9.5px " + MONO; ctx.fillStyle = U.PAL.neg; ctx.textAlign = "right"; 118 ctx.fillText("50% ââå¹¶ä¸åå¨çåè¶ ", sX(50) - 8, sy0 + 10); 119 // ç½æ ¼ 120 ctx.strokeStyle = "rgba(5,28,44,.08)"; ctx.lineWidth = 1; 121 [0, 10, 20, 30, 40].forEach(v => { 122 ctx.beginPath(); ctx.moveTo(sX(v), sy0); ctx.lineTo(sX(v), sy1); ctx.stroke(); 123 ctx.font = "8.5px " + MONO; ctx.fillStyle = U.PAL.inkLo; ctx.textAlign = "center"; 124 ctx.fillText(v + "%", sX(v), sy1 + 14); 125 }); 126 // ä»½é¢æçº¿ï¼2025 â 2027F 两ç¹ï¼ 127 ctx.strokeStyle = COL.blue; ctx.lineWidth = 2.2; 128 ctx.beginPath(); 129 SHARE.forEach((v, i) => { 130 const px = sX(v * (skip ? 1 : U.clamp(anim * 2 - i * 0.3, 0.001, 1))), py = sy1 - 30; 131 i === 0 ? ctx.moveTo(sX(v * U.clamp(anim * 2, 0.001, 1)), py) : ctx.lineTo(px, py); 132 }); 133 ctx.stroke(); 134 SHARE.forEach((v, i) => { 135 const py = sy1 - 30; 136 const px = sX(v); 137 ctx.fillStyle = COL.blue; 138 ctx.beginPath(); ctx.arc(px, py, 5, 0, U.TAU); ctx.fill(); 139 ctx.strokeStyle = "#fff"; ctx.lineWidth = 1.5; ctx.stroke(); 140 ctx.font = "700 12px " + MONO; ctx.fillStyle = COL.blue; ctx.textAlign = "center"; 141 ctx.fillText(SHARE_TXT[i], px, py - 12); 142 ctx.font = "9px " + MONO; ctx.fillStyle = U.PAL.inkLo; 143 ctx.fillText(YEARS[i], px, py + 18); 144 hits.push({ x: px - 22, y: py - 22, w: 44, h: 44, 145 drill: { title: "ASIC éµè¥ä»½é¢ · " + YEARS[i], value: SHARE_TXT[i] + "ï¼" + v.toFixed(1) + "%ï¼ Â· å TSMC AI æ¶å ¥", 146 sub: "GPU éµè¥ " + D.derived.gpu_share[IDX[i]] + "%ï¼å ¶ä¸ GPU åå A ä¸å®¶ " + D.derived.nvda_share[IDX[i]] + "%ï¼ã" + anon(D.derived.note), 147 source: D.source } }); 148 }); 149 150 // è½èç¹è¡å é¤ï¼v8.8ï¼ä¸ §9b æ£æ "stays far from the 50% line" å A2 drill ç 151 // GPU â66% éå¤ï¼50% çº¿ä¸ ~1/4 ~1/3 æ æ³¨å·²èªè¶³ï¼ 152 153 if (!done && !skip) requestAnimationFrame(() => draw()); 154 } 155 156 canvas.addEventListener("click", e => { 157 const r = canvas.getBoundingClientRect(); 158 const x = e.clientX - r.left, y = e.clientY - r.top; 159 for (let i = hits.length - 1; i >= 0; i--) { 160 const h = hits[i]; 161 if (x >= h.x && x <= h.x + h.w && y >= h.y && y <= h.y + h.h) { 162 U.showDrill({ ...h.drill, x: e.clientX, y: e.clientY }); 163 return; 164 } 165 } 166 }); 167 168 new IntersectionObserver(entries => { 169 for (const e of entries) if (e.isIntersecting && !anim) requestAnimationFrame(() => draw()); 170 }, { threshold: 0.12 }).observe(host); 171 draw(true); 172})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.