console-table-printer icon indicating copy to clipboard operation
console-table-printer copied to clipboard

Feature request: print multiple tables, side-by-side

Open jrr opened this issue 4 years ago • 4 comments

Thanks for this library. I really appreciate how easy it was to quickly understand and apply. Nice job on the docs and API design!

I found myself printing a couple narrow tables, where they could have fit side-by-side in my terminal. Something like this:

       Table One               Table Two 
┌───────┬──────┬──────┐ ┌───────┬──────┬──────┐
│ Foo   │ Bar  │ Baz  │ │ Foo   │ Bar  │ Baz  │
├───────┼──────┼──────┤ ├───────┼──────┼──────┤
│ aaa   │   13 │    8 │ │ aaa   │   43 │    8 │
│ bbb   │   99 │   10 │ │ bbb   │   12 │   10 │
│ ccc   │   91 │    9 │ │ ccc   │   12 │    9 │
│ ddd   │   45 │    6 │ │ ddd   │   75 │    6 │
│ eee   │   32 │    4 │ │ eee   │   62 │    4 │
│ fff   │   31 │    4 │ │ fff   │   61 │    4 │
│ ggg   │   26 │    3 │ │ ggg   │   56 │    3 │
│ hhh   │   12 │    7 │ └───────┴──────┴──────┘
│ iii   │   21 │    7 │ 
└───────┴──────┴──────┘ 

Would you be open to a PR that adds something like this? I'm thinking the API could be something like:

import { printTables } from "console-table-printer";
// ...
printTables(table1,table2);

jrr avatar Jun 20 '21 01:06 jrr

It would be fun to generalize this to n tables, but for now here's a quick-and-dirty solution for two:

function printTables(table1: Table, table2: Table) {
  const t1 = table1.render().split("\n");
  const t2 = table2.render().split("\n");

  const t1EmptyRow = " ".repeat(t1[1].length);

  const together = _.zip(t1, t2).map(
    ([a, b]) => `${a || t1EmptyRow} ${b || ""}`
  );

  together.forEach((line) => console.log(line));
}

jrr avatar Jun 20 '21 02:06 jrr

@jrr thanks for the idea.

It does not seem to be a very Common usecase. If this is preferred by many others I will reconsider. I will keep this Issue open to see how many people prefers this.

ayonious avatar Jul 02 '21 12:07 ayonious

@jrr

It would be fun to generalize this to n tables, but for now here's a quick-and-dirty solution for two:

Old Code

const zip = (...args) => {
    // https://stackoverflow.com/a/55648257/13077523
    const iterators = args.map((arr) => arr[Symbol.iterator]());
    let iterateInstances = iterators.map((i) => i.next());
    ret = [];
    while (iterateInstances["some"]((it) => !it.done)) {
        ret.push(iterateInstances.map((it) => it.value));
        iterateInstances = iterators.map((i) => i.next());
    }
    return ret;
};

const printTables = (...tables) => {
    const tablesLines = tables.map((table) => table.render().split("\n"));
    const emptyRows = tablesLines.map((t) => " ".repeat(t[1].length));
    const together = [];

    zip(...tablesLines).forEach((row) => {
        let out = "";

        row.forEach((t, index) => {
            out += t || emptyRows[index];
            out += " ";
        });
        together.push(out);
    });

    together.forEach((line) => console.log(line));
};

There you go!

Honestly you could directly console.log(out) w/o saving to an array, but oh well.

This does not take into account the console size, so it is bound to break on a big number of tables.

New code that considers the terminal's width.
const zip = (...args) => {
    // https://stackoverflow.com/a/55648257/13077523
    const iterators = args.map((arr) => arr[Symbol.iterator]());
    let iterateInstances = iterators.map((i) => i.next());
    ret = [];
    while (iterateInstances["some"]((it) => !it.done)) {
        ret.push(iterateInstances.map((it) => it.value));
        iterateInstances = iterators.map((i) => i.next());
    }
    return ret;
};

const printTables = (...tables) => {
    const tablesLines = tables.map((table) => table.render().split("\n"));
    const emptyRows = tablesLines.map((t) => " ".repeat(t[1].length));
    const together = [];
    const groups = [];

    if (
        emptyRows.reduce((a, b) => a + b).length + emptyRows.length - 2 >
        (process.stdout.columns || 80)
    ) {
        let group = [];
        let size = emptyRows.length - 1;
        let index = 0;

        while (emptyRows[index] && size < (process.stdout.columns || 80)) {
            if (
                size + emptyRows[index].length >
                (process.stdout.columns || 80)
            ) {
                size = 0;
                groups.push(Array.from(group));
                group.length = 0;
            } else {
                group.push(tablesLines[index]);
                size += emptyRows[index].length;
                index++;
            }
        }
        if (group.length != 0) {
            groups.push(group);
        }
    } else {
        groups.push(tablesLines);
    }
    for (const group of groups) {
        zip(...group).forEach((row) => {
            let out = "";

            row.forEach((t, index) => {
                out += t || emptyRows[index];
                out += " ";
            });
            together.push(out.trim());
        });
    }

    together.forEach((line) => console.log(line));
};
Example output if the console's width is 40 characters long
    Table 1         Table 2    
┌───────┬─────┐ ┌───────┬─────┐
│ index │ num │ │ index │ num │
├───────┼─────┤ ├───────┼─────┤
│     0 │   1 │ │     0 │   1 │
│     1 │   2 │ │     1 │   2 │
│     0 │   1 │ │     0 │   1 │
│     1 │   2 │ │     1 │   2 │
└───────┴─────┘ └───────┴─────┘
Example output if the console's width is 20 characters long
    Table 1    
┌───────┬─────┐
│ index │ num │
├───────┼─────┤
│     0 │   1 │
│     1 │   2 │
│     0 │   1 │
│     1 │   2 │
└───────┴─────┘
    Table 2    
┌───────┬─────┐
│ index │ num │
├───────┼─────┤
│     0 │   1 │
│     1 │   2 │
│     0 │   1 │
│     1 │   2 │
└───────┴─────┘

ArjixWasTaken avatar Aug 05 '21 13:08 ArjixWasTaken

Wow, I wasted 4 hours of my life trying to make it consider the terminal's width.... But at least I did it!

I'll never do shit like this again. 🤣🤣🤣

ArjixWasTaken avatar Aug 05 '21 22:08 ArjixWasTaken