将数据转换为具有 multi-level 个标题的 HTML 枢轴 table

Turn data into an HTML pivot table with multi-level headings

我正在寻找一个函数,该函数接受带有 A、B、C、D 列的简单数据 table,并为旋转的 [=16] 提供 HTML 标记=] 具有行标题(例如,1 级标题 A、2 级标题 B)和列标题(如 1 级 C、2 级 D、3 级 E)的任意组合。

这是一个函数,它获取数据行(数组的数组或普通对象的数组),以及 columns/fields 的 indexes/keys 来对数据进行分组和透视。它生成可用于 table 元素的内部 HTML。

/*
 Create table HTML: only pass data (no labels). 
 Pass column indexes/keys for:
 - the column with the values for the cells, 
 - the row headers, and 
 - the column headers
 Optionally provide callback for aggregating data that ends up in same cell
*/
function pivotTable(data, valueColumn, groupColumns, pivotColumns, aggregator = x=>x.length ? x[0] : "") {
    // Helper function
    function span(...args) {
        return [" rowspan", " colspan"].map((dim, i) =>
            args[i] > 1 ? dim + '="' + args[i] + '"' : ""
        ).join``;
    }

    // Structure dimensions:
    const [groups, pivots] = [groupColumns, pivotColumns].map(columns => {
        const colMap = {};
        for (const row of data) {
            columns.reduce((acc, colNo) => (acc[row[colNo]] = acc[row[colNo]] || {}), colMap);  
        }
        const headers = columns.map(col => []);
        const tree = (function process(colMap, y=0, x=0) {
            return Object.entries(colMap).reduce((acc, [title, obj]) => {
                const result = process(obj, y+1, x);
                const size = result.size || 1;
                result.title = title;
                acc.children[title] = result;
                acc.size += size;
                headers[y].push(result);
                x += size;
                return acc;
            }, { title: null, x, size: 0, children: {} });
        })(colMap);
        tree.headers = headers;
        return tree;
    });
    
    // Collect data in matrix, per dimensions:
    const matrix = Array.from({length:groups.size}, row => Array.from({length:pivots.size}, cell => []));
    for (const row of data) {
        const y = groupColumns.reduce((acc, colNo) => acc.children[row[colNo]], groups).x;
        const x = pivotColumns.reduce((acc, colNo) => acc.children[row[colNo]], pivots).x;
        matrix[y][x].push(row[valueColumn]);
    }
    matrix.forEach(row => row.forEach((values,i) => row[i] = aggregator(values)));
    
    // Produce HTML for column headers:
    let html = `<tr><th${span(groupColumns.length, pivotColumns.length)}></th>${
        pivots.headers.map(header => header.map(o => `<th${span(1, o.size)}>${o.title}</th>`).join``).join("</tr>\n<tr>")
    }</tr>`;

    // Produce HTML for the row headers and cells:
    let startRow = true;
    let y = 0;
    (function loop(children, depth=0) {
        Object.values(children).map(group => {
            if (startRow) html += "<tr>";
            startRow = false;
            html += `<th${span(group.size, 1)}>${group.title}</th>`;
            loop(group.children, depth+1);
        });
        if (depth >= groupColumns.length) {
            html += `${matrix[y].map(value => `<td>${value}</td>`).join``}</tr>\n`;
            startRow = true;
            y++;
        }
    })(groups.children);
    
    return html;
}

// Demo where data is structured as array of arrays:
const data = [["lemma","form","tense","mood","voice","person","number"],["πιστεύω","πεπίστευκα","perf","ind","act","1st","sg"],["πιστεύω","πεπιστεύκαμεν","perf","ind","act","1st","pl"],["πιστεύω","πεπίστευκας","perf","ind","act","2nd","sg"],["πιστεύω","πεπιστεύκατε","perf","ind","act","2nd","pl"],["πιστεύω","πεπιστεύκειν","perf","inf","act","",""],["πιστεύω","πεπιστεύκειν","plup","ind","act","1st","sg"],["πιστεύω","πεπιστεύκεισαν","plup","ind","act","3rd","pl"],["πιστεύω","πεπίστευκεν","perf","ind","act","3rd","sg"],["πιστεύω","πεπίστευκεν","perf","inf","act","",""],["πιστεύω","πεπίστευκεν","perf","inf","act","3rd","sg"],["πιστεύω","πεπίστευκεν","plup","ind","act","3rd","pl"],["πιστεύω","πεπιστευκέναι","perf","inf","act","",""],["πιστεύω","πεπίστευμαι","perf","ind","mp","1st","sg"],["πιστεύω","πεπίστευμαι","perf","ind","pass","1st","sg"],["πιστεύω","πεπίστευνται","perf","ind","mid","3rd","pl"],["πιστεύω","πεπίστευνται","perf","ind","mp","3rd","pl"],["πιστεύω","ἐπεπίστευντο","plup","ind","mp","3rd","pl"],["πιστεύω","πεπίστευται","perf","ind","mid","3rd","sg"],["πιστεύω","πεπίστευται","perf","ind","mp","3rd","sg"],["πιστεύω","ἐπεπίστευτο","plup","ind","mp","3rd","sg"],["πιστεύω","πιστεῦσαι","aor","imperat","mid","2nd","sg"],["πιστεύω","πιστεῦσαι","aor","inf","act","",""],["πιστεύω","πιστεῦσαι","aor","inf","act","3rd","sg"],["πιστεύω","πιστεῦσαι","aor","opt","act","3rd","sg"],["πιστεύω","πιστεῦσαν","aor","ind","act","3rd","pl"],["πιστεύω","πιστεύῃ","pres","ind","act","3rd","sg"],["πιστεύω","πιστεύῃ","pres","ind","mp","2nd","sg"],["πιστεύω","πιστεύῃ","pres","subj","act","3rd","sg"],["πιστεύω","πιστεύῃ","pres","subj","mp","2nd","sg"],["πιστεύω","πίστευε","imperf","ind","act","3rd","sg"],["πιστεύω","πίστευε","pres","imperat","act","2nd","sg"],["πιστεύω","πιστεύει","pres","ind","act","3rd","sg"],["πιστεύω","πιστεύει","pres","ind","mp","2nd","sg"],["πιστεύω","πιστεύειν","pres","inf","act","",""],["πιστεύω","πιστεύεις","pres","ind","act","2nd","sg"],["πιστεύω","ἐπίστευεν","imperf","ind","act","3rd","sg"],["πιστεύω","πιστεύεσθαι","pres","inf","mp","",""],["πιστεύω","πιστεύεται","pres","ind","mp","3rd","sg"],["πιστεύω","πιστεύετε","imperf","ind","act","2nd","pl"],["πιστεύω","πιστεύετε","pres","imperat","act","2nd","pl"],["πιστεύω","πιστεύετε","pres","ind","act","2nd","pl"],["πιστεύω","πιστευέτω","pres","imperat","act","3rd","sg"],["πιστεύω","πιστεύητε","pres","subj","act","2nd","pl"],["πιστεύω","πιστευθῇ","aor","subj","pass","3rd","sg"],["πιστεύω","ἐπιστεύθη","aor","ind","pass","3rd","sg"],["πιστεύω","πιστεύσῃ","aor","subj","act","3rd","sg"],["πιστεύω","πιστεύσῃ","aor","subj","mid","2nd","sg"],["πιστεύω","πιστεύσῃ","fut","ind","mid","2nd","sg"],["πιστεύω","πιστεύῃς","pres","subj","act","2nd","sg"],["πιστεύω","ἐπίστευσα","aor","ind","act","1st","sg"],["πιστεύω","πιστεύσαι","aor","imperat","mid","2nd","sg"],["πιστεύω","πιστεύσαι","aor","inf","act","",""],["πιστεύω","πιστεύσαι","aor","inf","act","3rd","sg"],["πιστεύω","πιστεύσαι","aor","opt","act","3rd","sg"],["πιστεύω","ἐπιστεύσαμεν","aor","ind","act","1st","pl"],["πιστεύω","ἐπίστευσαν","aor","ind","act","3rd","pl"],["πιστεύω","πιστευσάντων","aor","imperat","act","3rd","pl"],["πιστεύω","πιστεύσας","aor","ind","act","2nd","sg"],["πιστεύω","ἐπίστευσας","aor","ind","act","2nd","sg"],["πιστεύω","πιστεύσατε","aor","imperat","act","2nd","pl"],["πιστεύω","ἐπιστεύσατε","aor","ind","act","2nd","pl"],["πιστεύω","ἐπίστευσε","aor","ind","act","3rd","sg"],["πιστεύω","πιστεύσει","aor","subj","act","3rd","sg"],["πιστεύω","πιστεύσει","fut","ind","act","3rd","sg"],["πιστεύω","πιστεύσει","fut","ind","mid","2nd","sg"],["πιστεύω","πιστεύσειν","fut","inf","act","",""],["πιστεύω","πιστεύσεις","aor","subj","act","2nd","sg"],["πιστεύω","πιστεύσεις","fut","ind","act","2nd","sg"],["πιστεύω","ἐπίστευσεν","aor","ind","act","3rd","sg"],["πιστεύω","πιστεύσετε","aor","subj","act","2nd","pl"],["πιστεύω","πιστεύσετε","fut","ind","act","2nd","pl"],["πιστεύω","πιστεύσητε","aor","subj","act","2nd","pl"],["πιστεύω","πιστεύσητε","fut","ind","act","2nd","pl"],["πιστεύω","πιστεύσομεν","aor","subj","act","1st","pl"],["πιστεύω","πιστεύσομεν","fut","ind","act","1st","pl"],["πιστεύω","πίστευσον","aor","imperat","act","2nd","sg"],["πιστεύω","πιστεύσουσι","fut","ind","act","3rd","pl"],["πιστεύω","πιστεύσουσιν","aor","subj","act","3rd","pl"],["πιστεύω","πιστεύσουσιν","fut","ind","act","3rd","pl"],["πιστεύω","πιστεύσῃς","aor","subj","act","2nd","sg"],["πιστεύω","πιστεύσω","aor","ind","mid","2nd","sg"],["πιστεύω","πιστεύω","pres","subj","act","1st","sg"],["πιστεύω","πιστεύωμεν","pres","subj","act","1st","pl"]];
const html = pivotTable(data.slice(1), 1, [0, 6, 5], [2, 3, 4]);
document.querySelector("table").innerHTML = html;
table,
tr,
td,
th {
  border: 1px solid;
  border-collapse: collapse;
}
<table></table>

如果您的原始数据仅在 HTML table 中可用,则首先使用如下代码从 table 中提取数据:

function fromHtml(table) {
    const results = [];
    for (const row of table.rows) {
        results.push(Array.from(row.cells, cell => cell.textContent.trim()));
    }
    return results;
}

您还可以选择将数据组织为普通对象数组:

[
    {lemma: "πιστεύω", form: "πεπίστευκα", tense: "perf", mood: "ind", voice: "act", person: "1st", number "sg"},
    {lemma: "πιστεύω", form: "πεπιστεύκαμεν", tense: "perf", mood: "ind", voice: "act", person: "1st", number: "pl"},
    /* ... */
];

在这种情况下,对函数的调用将如下所示:

const html = pivotTable(data, "form", ["lemma", "number", "person"], ["tense", "mood", "voice"]);