原始數據為「長格式」(Long Format):每行一條記錄,包含維度(XMID、ysdate)與度量值(SZH)。目標轉為「寬格式」(Wide Format):ysdate 為行,XMID 為列,SZH 為單元格值。
// 原始數據結構
const json = {
"Table1": [
{"XMID": "B0", "ysdate": "2015-01-01T00:00:00", "SZH": 7373.0},
{"XMID": "B0", "ysdate": "2015-01-02T00:00:00", "SZH": 7845.0},
{"XMID": "C0", "ysdate": "2015-01-01T00:00:00", "SZH": 327.0},
// ... 更多記錄
]
};
function pivotToTable(data, rowKey, colKey, valueKey) {
// 1. 分組聚合:以 rowKey 為鍵,收集所有 colKey→valueKey 映射
const rows = {};
const columns = new Set();
data.forEach(record => {
const rowVal = record[rowKey];
const colVal = record[colKey];
const val = record[valueKey];
if (!rows[rowVal]) rows[rowVal] = {};
rows[rowVal][colVal] = val; // 同一 row+col 取最後值,或根據業務合併
columns.add(colVal);
});
// 2. 排序列(保證表頭順序穩定)
const sortedCols = Array.from(columns).sort();
// 3. 生成 HTML 表格
let html = '';
// 表頭
html += '' + rowKey + ' ';
sortedCols.forEach(col => html += '' + col + ' ');
html += ' ';
// 數據行(按行鍵排序)
html += '';
Object.keys(rows).sort().forEach(rowVal => {
html += '' + formatDate(rowVal) + ' ';
sortedCols.forEach(col => {
const cellVal = rows[rowVal][col];
html += '' + (cellVal !== undefined ? cellVal : '') + ' ';
});
html += ' ';
});
html += '
';
return html;
}
// 日期格式化:移除時間部分
function formatDate(isoString) {
return isoString.split('T')[0]; // "2015-01-01T00:00:00" → "2015-01-01"
}
// 使用
const tableHtml = pivotToTable(json.Table1, 'ysdate', 'XMID', 'SZH');
document.getElementById('container').innerHTML = tableHtml;
class DataPivot {
constructor(options = {}) {
this.rowKey = options.rowKey || 'ysdate';
this.colKey = options.colKey || 'XMID';
this.valueKey = options.valueKey || 'SZH';
this.aggregator = options.aggregator || ((a, b) => b); // 預設取最後值
this.dateFormat = options.dateFormat || (s => s.split('T')[0]);
this.colSort = options.colSort || ((a, b) => a.localeCompare(b));
this.rowSort = options.rowSort || ((a, b) => a.localeCompare(b));
this.missingCell = options.missingCell || '';
}
transform(data) {
const rows = {};
const cols = new Set();
data.forEach(r => {
const rv = r[this.rowKey];
const cv = r[this.colKey];
const vv = r[this.valueKey];
if (!rows[rv]) rows[rv] = {};
// 聚合邏輯:首次賦值或自定義聚合
rows[rv][cv] = rows[rv][cv] === undefined ? vv : this.aggregator(rows[rv][cv], vv);
cols.add(cv);
});
const sortedCols = Array.from(cols).sort(this.colSort);
const sortedRows = Object.keys(rows).sort(this.rowSort);
// 構建表頭
let html = '';
html += `${this.rowKey} `;
sortedCols.forEach(c => html += `${c} `);
html += ' ';
// 構建數據行
sortedRows.forEach(rv => {
html += `${this.dateFormat(rv)} `;
sortedCols.forEach(cv => {
const val = rows[rv][cv];
html += `${val !== undefined ? val : this.missingCell} `;
});
html += ' ';
});
html += '
';
return html;
}
}
// 使用範例:求同一天同 XMID 的 SZH 平均值
const pivot = new DataPivot({
aggregator: (a, b) => (a + b) / 2, // 平均值聚合
missingCell: '-' // 空值顯示
});
document.body.innerHTML = pivot.transform(json.Table1);
react-window 或原生 IntersectionObserver 實現requestIdleCallback0.1 + 0.2 = 0.30000000000000004,顯示時用 toFixed(2) 或 Intl.NumberFormatJSON 長表轉寬表的核心是「分組 → 聚合 → 矩陣化」。掌握通用的樞紐演算法,配合配置化聚合策略、排序規則、格式化函數,即可優雅處理各類報表數據轉換需求,遠勝硬編碼特定字段的一次性代碼。