如何流暢地在前端渲染數萬個數據點的晶圓地圖。
對 Wafer BI 來說,最核心的功能莫過於「晶圓熱圖 (Wafer Heatmap)」。一片晶圓上可能有成千上萬個晶粒 (Die),如果用傳統 DOM 元素去渲染,瀏覽器直接卡死給你看。所以我們導入 ECharts,靠 Canvas 繪製撐住流暢度。
先看成果——這是深色主題下的晶圓詳情頁,滑鼠移到任何一個 Die 上都有即時 Tooltip 顯示座標與量測值:

▲ Dark Mode 晶圓熱圖:hover 任一 Die 即時顯示座標與 Thickness 數值
前兩天分別把 Java 跟 Python 服務裝進了容器,React 前端當然也不例外——這個系列的主軸是 K8S,任何要進叢集的服務都得先變成 Image。services/frontend/Dockerfile 一樣是多階段構建,只是這次的第二階段不是「跑起來」,而是「把靜態檔案交給 Nginx」:
# Stage 1: Build
FROM node:22-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Stage 2: Production
FROM nginx:alpine
# Copy the built files from the build stage
COPY --from=build /app/dist /usr/share/nginx/html
# Add nginx config to handle SPA routing if needed
RUN echo 'server { \
listen 80; \
location / { \
root /usr/share/nginx/html; \
index index.html; \
try_files $uri $uri/ /index.html; \
} \
}' > /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
跟 Day 3 的 COPY pom.xml / Day 4 的 COPY requirements.txt 是同一個道理——先 COPY package*.json 裝依賴,再 COPY . . 複製原始碼,改一行 UI 程式碼不會讓 npm install 重跑。第二階段用 nginx:alpine 這個超輕量基底,只裝進 build 完的靜態檔(dist/),Node.js、npm、TypeScript 編譯器全部留在第一階段,不會進到最終 Image 裡。
try_files $uri $uri/ /index.html 這行是 SPA (Single Page Application) 路由的關鍵:React Router 的前端路由(例如 /wafer/Lot1/1)在伺服器端根本不存在對應的檔案,Nginx 找不到就會回退(fallback)到 index.html,剩下的路由交給 React Router 在瀏覽器端接管——少了這行,重新整理頁面會直接 404。
實際建置過程,npm install 裝了 184 個套件、vite build 把 TypeScript 編譯加打包一次搞定:

▲ docker build 前端 Image:npm install 14.3 秒、tsc + vite build 4.43 秒完成,產物壓縮後只有 460KB
在專案的 services/frontend/src/App.tsx 中,我們定義了 getHeatmapOption 函數,這就是畫出那張晶圓圖的核心魔法。
以下是簡化後的核心配置程式碼:
const getHeatmapOption = (data: any, isThumbnail = false) => {
if (!data) return {};
const gridSize = 30; // 假設網格大小為 30x30
const categories = Array.from({ length: gridSize }, (_, i) => i + 1);
return {
tooltip: {
show: !isThumbnail,
formatter: (params: any) => {
const [x, y, val] = params.data;
return `Die (${x}, ${y})<br/>Value: ${val.toFixed(2)}`;
}
},
// 隱藏 X 軸與 Y 軸,讓畫面更乾淨
xAxis: { type: 'category', data: categories, show: false },
yAxis: { type: 'category', data: categories, show: false, inverse: true },
// 定義色彩映射,根據數值高低顯示從藍到紅的漸層
visualMap: {
min: data.min,
max: data.max,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: '0%',
inRange: {
color: ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
}
},
// 畫一個大圓圈,模擬晶圓的邊界 (Wafer Edge)
graphic: [{
type: 'circle',
shape: { cx: '50%', cy: '50%', r: isThumbnail ? '40%' : '42%' },
style: {
fill: 'transparent',
stroke: 'rgba(150, 150, 150, 0.5)',
lineWidth: 2,
shadowBlur: 10,
shadowColor: 'rgba(0,0,0,0.3)'
},
silent: true,
left: 'center',
top: 'center',
z: 10
}],
// 將實際的測試數據映射為熱圖座標點
series: [{
name: 'Wafer Map',
type: 'heatmap',
data: data.data.map((item: any) => [item[0] - 1, item[1] - 1, item[2]]),
label: { show: false },
itemStyle: { borderWidth: 0 }
}]
};
};
注意 isThumbnail 這個參數的小心機:同一個函數同時服務「批次概覽」的 25 張縮圖(關掉 Tooltip、縮小晶圓邊界)跟「晶圓詳情」的大圖,一魚兩吃。
工程師光看熱圖不夠,還要看分布。「統計分析」頁面用 ECharts 的 boxplot 畫出整個批次 25 片晶圓的參數變異,搭配趨勢圖跟線性回歸:

▲ Dark Mode 統計分析:箱型圖 (Boxplot)、批次趨勢與回歸公式
箱型圖的資料是 Day 4 的 FastAPI 後端算好的(/api/stats/{lot_id} 回傳每片晶圓的五數概括),前端只負責畫——計算歸後端、渲染歸前端,這個分工是效能的關鍵。
series: [{
name: 'Boxplot',
type: 'boxplot',
data: statsData.boxplot, // 後端算好的 [min, Q1, median, Q3, max]
}]
上面截圖裡箱型圖旁邊那條紅色虛線(回歸線),是另一組圖表疊出來的——scatter(實際散點)加一條 line(回歸線),兩個 series 畫在同一個座標系上:
series: [
{
name: 'Wafer Mean',
data: regressionData.means,
type: 'scatter',
symbolSize: 10,
itemStyle: { color: 'var(--accent-color)' }
},
{
name: 'Regression Line',
data: regressionData.regression_line,
type: 'line',
lineStyle: { width: 3, type: 'dashed', color: '#f46d43' },
symbol: 'none'
}
]
type: 'scatter' 標出每片晶圓的實際平均值,type: 'line' 疊上後端(/api/regression/{lot_id})算好的回歸線,symbol: 'none' 讓這條線不要在每個資料點畫一個小圓點、乾淨地只顯示線段。ECharts 的 series 是陣列,本來就是設計成「疊圖」用的——散點加趨勢線、長條加折線這種混合圖表,全部都是同一個 series 陣列裡塞不同 type 而已,不用另外拉第二個圖表元件。
這個統計分析頁面另外還掛了 toolbox(縮放、還原、存成圖片)跟 dataZoom(滑鼠滾輪縮放 + 底部拖曳軸),批次一多、晶圓數量上百片時,使用者自己框選想看的區間,不用死盯著擠成一團的資料點。
除了 ECharts 本身的渲染能力,前端還加了主題切換(Light/Dark Mode),搭配 Lucide Icons 做出有點科技感的側邊欄。上面兩張截圖就是 Dark Mode——切換主題其實只是改 body 的一個 class,所有圖表顏色透過 CSS 變數自動跟著走。
會花這個工不是為了炫技,而是我真心相信:一個讓 User 眼睛一亮的介面,使用率會差非常多。
large: true 跟 largeThreshold,犧牲一點渲染特效換取流暢操作。notMerge={true} 給 echarts-for-react,避免新舊 option 合併造成殘影跟記憶體堆積(這個坑我踩過,畫面疊圖的時候還以為顯卡壞了 哭啊)。到今天為止,三大異構主角(Java、Python、React)都到齊,也都住進了容器。明天來談安全:API Gateway 的路由設計與 JWT 統一認證,讓這些服務只對該開的門開放。