Need help with regular-table?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

jpmorganchase
190 Stars 18 Forks Apache License 2.0 319 Commits 14 Opened issues

Description

A regular <table> library, for async and virtual data models.

Services available

!
?

Need anything else?

Contributors list

regular-table

NPM Version Build Status

A Javascript library for the browser,

regular-table
exports a custom element named
,
which renders a regular HTML 
 to a 
sticky
position within a scollable viewport. Only visible cells are rendered and queried from a natively
async
virtual data model, making
regular-table
ideal for enormous or remote data sets. Use it to build Data Grids, Spreadsheets, Pivot Tables, File Trees, or anytime you need:
  • Just a regular
    
    
.
  • Virtually rendered for high-performance.
  • async
    data model handles slow, remote, enormous, and/or distributed backends.
  • Easy to style, works with any regular CSS for
    
    
  • .
  • Small bundle size, no dependencies.
  • Examples

    |||| |:--|:--|:--| |twobillionrows|canvasdatamodel|perspectiveheaders| |two_billion_rows|canvas_data_model|perspective_headers| |minesweeper|filebrowser|spreadsheet| |minesweeper|file_browser|spreadsheet|

    Features

    |||| |:--|:--|:--| |rowmouseselection|columnmouseselection|areamouseselection| |row_mouse_selection|column_mouse_selection|area_mouse_selection| |row_stripes| |row_stripes|

    Documentation

    What follows functions as a quick-start guide, and will explain the basics of the Virtual Data Models, Styling and Interaction APIs. Complete API docs and documented examples are also available.

    Installation

    Include via a CDN like JSDelivr:

    Or, add to your project via

    yarn
    :
    yarn add regular-table
    

    ... then import into your asset bundle.

    import "regular-table";
    import "regular-table/dist/css/material.css";
    

     Custom Element

    regular-table
    exports no symbols, only the
     Custom Element 
    which is registered as a module import side-effect.  Once loaded,
    
     can be used just like any other 
    HTMLElement
    , using regular browser APIs:
    const regularTable = document.createElement("regular-table");
    document.body.appendChild(regularTable);
    

    ... or from regular HTML:

    ... or from your library of choice, as long as it supports regular HTML! Here's an example for React/JSX:

    const App = () => ;
    ReactDOM.render(, document.getElementById("root"));
    

    .setDataListener()
    Virtual Data Model

    Let's start with with a simple data model, a two dimensional

    Array
    . This one is very small at 3 columns x 6 rows, but even for very small data sets,
    regular-table
    won't read your entire dataset at once. Instead, we'll need to write a simple virtual data model to access
    DATA
    and
    COLUMN_NAMES
    indirectly.
    const DATA = [
        [0, 1, 2, 3, 4, 5],
        ["A", "B", "C", "D", "E", "F"],
        [true, false, true, false, true, false],
    ];
    

    When clipped by the scrollable viewport, you may end up with a

    of just a rectangular region of
    DATA
    , rather than the entire set. A simple viewport 2x2 may yield this
    :
    0 A
    1 B
    {
        "num_rows": 26,
        "num_columns": 3,
        "data": [
            [0, 1],
            ["A", "B"]
        ]
    }
    

    Here's a an implementation for this simple virtual data model, the function

    getDataSlice()
    . This function is called by your
     whenever it needs more data, with coordinate arguments,
    
    (x0, y0)
    to
    (x1, y1)
    . Only this region is needed to render the viewport, so
    getDataSlice()
    returns this rectangular
    slice
    of
    DATA
    . For the window (0, 0) to (2, 2),
    getDataSlice()
    would generate an Object as above, containing the
    data
    slice, as well as the overall dimensions of
    DATA
    itself (
    num_rows
    ,
    num_columns
    ), for sizing the scroll area. To render this virtual data model to a regular HTML
    
    , register this data
    model via the 
    setDataListener()
    method:
    function getDataSlice(x0, y0, x1, y1) {
        return {
            num_rows: (num_rows = DATA[0].length),
            num_columns: DATA.length,
            data: DATA.slice(x0, x1).map((col) => col.slice(y0, y1)),
        };
    }
    
    

    regularTable.setDataListener(getDataSlice);

    This will render your regular HTML

    ! Your DOM will look something like this, depending on the size of your viewport. Notice there are fewer rows and columns in the resulting HTML, e.g. the column
    Column 3 (boolean)
    - as you scroll, more data will be fetched from
    getDataSlice()
    , and parts of the
    will redrawn or extended as needed.
    <table>
        <tbody>
            <tr>
                <td>0</td>
                <td>A</td>
            </tr>
            <tr>
                <td>1</td>
                <td>B</td>
            </tr>
        </tbody>
    </table>

    virtual_mode
    Option

    regular-table
    supports four modes of virtual scrolling, which can be configured via the
    virtual_mode
    optional argument. Note that using a
    virtual_mode
    other than the default
    "both"
    will render the entire
    along the non-virtual axis(es), and may cause rendering performance degradation.
    • "both" (default) virtualizes scrolling on both axes.
    • "vertical" only virtualizes vertical (y) scrolling.
    • "horizontal" only virtualizes horizontal (x) scrolling.
    • "none" disable all scroll virtualization.
    table.setDataListener(listener, {virtual_mode: "vertical"})
    

    Column and Row Headers

    regular-table
    can also generate Hierarchial Row and Column Headers, using
    ), or Row Headers (the first
    children of each 
    tbody tr
    ), via the
    column_headers
    and
    row_headers
    properties (respectively) of your data model's
    Response
    object. This can be renderered with
    column_headers
    , a two dimensional
    Array
    which must be of length
    x1 - x0
    , one
    Array
    for every column in your
    data
    window.
    elements which layout in a
    fixed
    position within the virtual table. It can generate Column Headers (within the
    Column 1 (number) Column 2 (string)
    0 A
    1 B
    {
        "num_rows": 26,
        "num_columns": 3,
        "data": [
            [0, 1],
            ["A", "B"]
        ],
        "column_headers": [
            ["Column 1 (number)"],
            ["Column 2 (string)"]
        ]
    }
    

    Hierarchial/Group Headers

    regular-table
    supports multiple
     of 
    , and also uses 
    colspan
    and
    rowspan
    to merge simple consecutive names, which allows description of simple Row and Column Group Hierarchies such as this:
    Colgroup 1
    Column 1 Column 2
    Rowgroup 1 Row 1 0 A
    Row 2 1 B
    {
        "num_rows": 26,
        "num_columns": 3,
        "data": [
            [0, 1],
            ["A", "B"]
        ],
        "row_headers": [
            ["Rowgroup 1", "Row 1"],
            ["Rowgroup 1", "Row 2"]
        ],
        "column_headers": [
            ["Colgroup 1", "Column 1"],
            ["Colgroup 1", "Column 2"]
        ]
    }
    

    Note that in the rendered HTML, for these Row and Column

    Array
    , repeated elements in a sequence will be automatically merged via
    rowspan
    and
    colspan
    attributes. In this example, e.g.
    "Rowgroup 1"
    will only output to one
     node in the resulting 
    
    .
    
    

    async
    Data Models

    With an

    async
    data model, it's easy to serve
    getDataSlice()
    remotely from
    node.js
    or re-implement the JSON response protocol in any language. Just return a
    Promise()
    from, or use an
    async
    function as an argument to,
    setDataListener()
    . Your
     won't render until the
    
    Promise
    is resolved, nor will it call your data model function again until the current call is resolved or rejected. The following
    async
    example uses a Web Worker, but the same principle applies to Web Sockets,
    readFile()
    or any other asynchronous source. Returning a
    Promise
    blocks rendering until the Web Worker replies:
    // Browser
    
    

    let callback;

    worker.addEventListener("message", (event) => { callback(event.data); });

    regularTable.setDataListener((...viewport) => { return new Promise(function (resolve) { callback = resolve; worker.postMessage(viewport); }); });

    // Web Worker
    
    self.addEventListener("message", async (event) => {
        const response = await getDataSlice.apply(null, event.data);
        self.postMessage(response);
    });
    

    .addStyleListener()
    and
    getMeta()
    Styling

    regular-table
    can be styled trivially with just regular CSS for
    .
    // Zebra striping!
    regular-table tr:nth-child(even) td {
        background: rgba(0,0,0,0.2);
    }
    

    However, CSS alone cannot select on properties of your data - if you scroll this example, the 2nd row will always be the striped one. Some other data-reliant style examples include:

    • Styling a specific column in the virtual data set, as
    may represent a different column based on horizontal scroll position.
  • Styling cells by value, +/-, heatmaps, categories, etc.
  • Styling cells based on data within-or-outside of the virtual viewport, grouping depth, grouping categories, etc.
  • To make CSS that is virtual-data-model-aware, you'll need to use

    addStyleListener()
    , which invokes a callback whenever the
    
     is
    re-rendered, such as through API invocations of 
    draw()
    and user-initiated events such as scrolling. Within this optionally
    async
    callback, you can select
    ,
    , etc. elements via regular DOM API methods like
    querySelectorAll()
    .
    // Only select row_headers!
    table.addStyleListener(() => {
        for (const th of table.querySelectorAll("tbody th")) {
            style_th(th);
        }
    });
    

    Once you've selected the

    and
    you want to paint,
    getMeta()
    will return a
    MetaData
    record of information about the HTMLElement's virtual position. This example uses
    meta.x
    , the position in
    data
    -space, to make virtual-scroll-aware zebra striping.
    function style_th(th) {
        const meta = table.getMeta(th);
        th.classList.toggle("zebra-striped", meta.x % 2 === 0);
    }
    
    .zebra-striped {
        background-color: rgba(0,0,0,0.2);
    }
    

    .invalidate()

    To prevent DOM renders,

     conserves DOM calls like 
    offsetWidth
    to an internal cache. When a
    or
    's
    width
    is modified within a callback to
    .addStyleListener()
    , you must indicate to
     that
    its dimensions have changed in order to invalidate this cache, or you may not
    end up with enough rendered columns to fill the screen!
    
    

    A call to

    invalidate()
    that does not need new columns only imparts a small runtime overhead to re-calculate virtual width per async draw iteration, but should be used conservatively if possible. Calling
    invalidate()
    outside of a callback to
    .addStyleListener()
    will throw an
    Error
    .
    table.addStyleListener(() => {
        for (const th of table.querySelectorAll("tbody th")) {
            th.style.maxWidth = "20px";
        }
        table.invalidate();
    });
    

    .addEventListener()
    Interaction

     is a normal 
    HTMLElement
    ! Use the
    regular-table
    API in concert with regular DOM API methods that work on other
    HTMLElement
    to create advanced functionality, such as this example of virtual row select:
    const selected_rows = [];
    
    

    table.addEventListener("mousedown", (event) => { const meta = table.getMeta(event.target); if (meta && meta.y >= 0) { selected_rows.push(meta.y); table.draw(); } });

    table.addStyleListener(() => { for (const td of table.querySelectorAll("td")) { const meta = table.getMeta(td); td.classList.toggle("row-selected", selected_rows.includes(meta.y)); } });

    Advanced examples can be found in the

    examples
    directory, and in the
    bl.ocks
    example gallery
    .

    Pivots, Filters, Sorts, and Column Expressions with
    perspective

    regular-table
    is natively compatible with
    perspective
    , a WebAssembly streaming visualization engine. By using a
    persective.Table
    as a Virtual Data Nodel, it becomes simple to achieve user-driven row and column pivots, filters, sorts, and column expressions, as well as charts and persistent layouts, from high-frequency updating data.

    Development

    First install

    dev_dependencies
    :
    yarn
    

    Build the library

    yarn build
    

    Run the test suite

    yarn test
    

    Start the example server at

    http://localhost:8080/examples/

    yarn start
    

    We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.