<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>klass</title>
<script src="./pivot.js"></script>
</head>
<body>
<script type="text/javascript">
var data = [
// 姓名、学科、老师、成绩级别、成绩、个人平均分、班级
{a:'张三', b: '语文', c: '陈中国', d: '优', e: 90, f: 86, g: '一班'},
{a:'李四', b: '语文', c: '郑明华', d: '良', e: 85, f: 72, g: '二班'},
{a:'王五', b: '语文', c: '陈中国', d: '中', e: 62, f: 66, g: '三班'},
{a:'赵六', b: '语文', c: '陈中国', d: '差', e: 50, f: 63, g: '四班'},
{a:'李四', b: '数学', c: '刘小辉', d: '优', e: 93, f: 86, g: '三班'},
{a:'李四', b: '英语', c: 'MissLi', d: '良', e: 80, f: 86, g: '三班'},
{a:'李四', b: '政治', c: '郑明华', d: '良', e: 82, f: 86, g: '三班'}
];
var column = [ { dataIndex: 'b' }, { dataIndex: 'g' }, { dataIndex: 'c'} ];
var row = [ { dataIndex: 'a' },{ dataIndex: 'b' },{ dataIndex: 'c'} ];
var value = [
{ dataIndex: 'd', text: '成绩级别' },
{ dataIndex: 'e', text: '成绩' },
{ dataIndex: 'f', text: '个人平均分' }
];
var ret = pivot(data, { column, row, value });
print(ret);
</script>
</body>
</html>
function pivot(data, { column = [], row = [], value = [] }) {
var colKeys = values(column, 'dataIndex');
var rowKeys = values(row, 'dataIndex');
var columnTreeMap = nest(data, colKeys);
var rowTreeMap = nest(data, rowKeys);
var refs = getNodeRefs(rowTreeMap);
var headerNodes = getNodeByDataIndex(columnTreeMap, colKeys.slice(-1)[0]);
var valueKeys = values(value, 'dataIndex');
var result = toList(refs, headerNodes, valueKeys);
return addColumnHeaderAndRowHeader(result, columnTreeMap, headerNodes, column, rowTreeMap, row, value);
}
function columnHeaderGroups(columnTreeMap, row, index = 0, groups = []) {
var len = row.length;
if (!groups[index]) {
groups[index] = [];
while(len--) groups[index].push(null);
}
columnTreeMap.forEach((tm, key) => {
let nodes = tm.get('nodes') - 1;
groups[index].push(key);
while(nodes--) {
groups[index].push(null);
}
if (tm.get('children').size) {
columnHeaderGroups(tm.get('children'), row, index + 1, groups);
}
});
return groups;
}
function getRowHeader(rowTreeMap, row, index = 0, rowHeaders = []) {
rowTreeMap.forEach((tm, key) => {
let ret = row.map(r => {
return r.dataIndex === tm.get('dataIndex') ? key : null;
});
rowHeaders.push(ret);
if (tm.get('children').size) {
getRowHeader(tm.get('children'), row, index + 1, rowHeaders);
}
});
return rowHeaders;
}
function countLeafNodes(treeMap, valueSize = 1) {
var nodes = 0;
treeMap.forEach(tm => {
var count;
if (tm.get('children').size) {
count = countLeafNodes(tm.get('children'), valueSize);
} else {
count = 1;
}
tm.set('nodes', count * valueSize);
nodes += count;
});
return nodes;
}
function addColumnHeaderAndRowHeader(data, columnTreeMap, headerNodes, column, rowTreeMap, row, value) {
var rowHeaders = getRowHeader(rowTreeMap, row);
data = data.map((d, i) => rowHeaders[i].concat(d));
var colTexts = values(value, 'text');
var valueHeaders = headerNodes.reduce((header, tm) => header.concat(colTexts), []);
countLeafNodes(columnTreeMap, value.length);
var groups = columnHeaderGroups(columnTreeMap, row);
var blanks = row.map(r => null);
data.unshift(blanks.concat(valueHeaders));
data = groups.concat(data);
return data;
}
function nest(data, keys, index = 0, treeMap = new Map()) {
if (keys.length === index) {
return treeMap;
}
var key = keys[index];
data.forEach(d => {
if (treeMap.has(d[key])) {
treeMap.get(d[key]).get('data').push(d);
} else {
let obj = new Map()
.set('key', d[key])
.set('dataIndex', key)
.set('data', [d])
.set('ref', record => record[key] === d[key])
.set('children', new Map());
treeMap.set(d[key], obj);
}
});
treeMap.forEach(related => {
nest(related.get('data'), keys, index + 1, related.get('children'));
});
return treeMap;
}
function getNodeRefs(treeMap, refs = [], parentRef) {
treeMap.forEach(tm => {
let fn = tm.get('ref');
if (parentRef) {
refs.push(record => {
return parentRef(record) && fn(record);
});
} else {
refs.push(fn);
}
if (tm.get('children').size) {
getNodeRefs(tm.get('children'), refs, fn);
}
});
return refs;
}
function getNodeByDataIndex(treeMap, dataIndex, nodes = []) {
if (!dataIndex) return nodes;
treeMap.forEach(tm => {
if (tm.get('dataIndex') === dataIndex) {
nodes.push(tm);
}
if (tm.get('children').size) {
getNodeByDataIndex(tm.get('children'), dataIndex, nodes);
}
});
return nodes;
}
function values(collection, key) {
return collection.map(obj => obj[key]);
}
function getValue (items, ref, vk) {
for (var i = 0; i < items.length; i++) {
if (ref(items[i])) {
return items[i][vk];
}
}
return null;
}
function toList(refs, headerNodes, valueKeys) {
var result = [];
refs.forEach((ref, i) => {
result[i] = [];
headerNodes.forEach((tm, j) => {
valueKeys.forEach(vk => {
result[i].push(getValue(tm.get('data'), ref, vk));
});
});
});
return result;
}
function print(rows) {
var tpl = '<table border=1>';
rows.forEach(row => {
tpl += '<tr>';
row.forEach(v => {
tpl += '<td>' + (v || '-') + '</td>';
});
tpl += '</tr>';
});
tpl += '</table>';
document.write(tpl);
}