Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 12 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
#

A regular Javascript library for the browser, `regular-table` exports
a single [Custom
Element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)
a [Custom Element](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)
named `<regular-table>`,
which renders a regular HTML `<table>` to a `fixed` 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:
sets. Use it to build Data Grids, Spreadsheets, Pivot Tables, File Trees, or
anytime you need:

* Just a regular `<table>`.
* Virtually rendered for high-performance.
Expand Down Expand Up @@ -122,14 +121,10 @@ this rectangular `slice` of `DATA`:

```javascript
function getDataSlice(x0, y0, x1, y1) {
const data = DATA.slice(x0, x1).map(col => col.slice(y0, y1));
const num_columns = DATA.length;
const num_rows = DATA[0].length;

return {
num_rows,
num_columns,
data
num_rows: num_rows = DATA[0].length,
num_columns: DATA.length,
data: DATA.slice(x0, x1).map(col => col.slice(y0, y1))
};
}
```
Expand Down Expand Up @@ -181,7 +176,7 @@ scroll, more data will be fetched from `getDataSlice()`, and parts of the
</regular-table>
```

## Column and Row Headers
### Column and Row Headers

`regular-table` can also generate Hierarchial Row and Column Headers, using
`<th>` elements which layout in a `fixed` position within the virtual table.
Expand Down Expand Up @@ -222,16 +217,11 @@ const COLUMN_HEADERS = [
];

function getDataSlice(x0, y0, x1, y1) {
const data = DATA.slice(x0, x1).map(col => col.slice(y0, y1));
const column_headers = COLUMN_NAMES.slice(x0, x1);
const num_columns = DATA.length;
const num_rows = DATA[0].length;

return {
column_headers,
num_rows,
num_columns,
data
column_headers: COLUMN_NAMES.slice(x0, x1),
num_rows: DATA[0].length,
num_columns: DATA.length,
data: DATA.slice(x0, x1).map(col => col.slice(y0, y1));
};
}
```
Expand Down Expand Up @@ -280,7 +270,7 @@ Resulting HTML:
</regular-table>
```

## Hierarchial/Group Headers
### Hierarchial/Group Headers

`regular-table` supports multiple `<tr>` of `<th>`, and also uses `colspan` and
`rowspan` to merge simple consecutive names, which allows description of simple
Expand Down
3 changes: 1 addition & 2 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ module.exports = {
node: "12",
ios: "13",
},
useBuiltIns: "usage",
corejs: 3,
},
],
],
Expand All @@ -21,4 +19,5 @@ module.exports = {
"./scripts/babel-plugin-html-template.js",
"./scripts/babel-plugin-css-template.js",
],
sourceMaps: true,
};
53 changes: 26 additions & 27 deletions examples/perspective_headers.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@
border-right: 1px solid #ddd;
}

regular-table tbody th:not(:last-of-type) {
vertical-align: top;
max-width: 40px;
min-width: 40px;
}


regular-table tbody th:last-of-type {
max-width: 100px;
min-width: 100px;
}

</style>
</head>

Expand All @@ -41,8 +53,6 @@
<regular-table></regular-table>

<script>
const TEMPLATE = document.createElement("template");

const FORMATTERS = {
datetime: Intl.DateTimeFormat("en-us"),
date: Intl.DateTimeFormat("en-us"),
Expand All @@ -55,24 +65,13 @@
};

class PerspectiveDataModel {
_tree_header_levels(path, is_open, is_leaf) {
const tree_levels = path.map(() => '<span class="pd-tree-group"></span>');
if (!is_leaf) {
const group_icon = is_open ? "remove" : "add";
const tree_button = `<span class="pd-row-header-icon">${group_icon}</span>`;
tree_levels.push(tree_button);
}

return tree_levels.join("");
}

_tree_header(path, is_leaf, is_open) {
const name = path.length === 0 ? "TOTAL" : path[path.length - 1];
const header_classes = is_leaf ? "pd-group-name pd-group-leaf" : "pd-group-name";
const tree_levels = this._tree_header_levels(path, is_open, is_leaf);
const header_text = name;
TEMPLATE.innerHTML = `<span class="pd-tree-container">${tree_levels}<span class="${header_classes}">${header_text}</span></span>`;
return TEMPLATE.content.firstChild;
_tree_header(paths = []) {
return paths.map((path) =>
path
.slice(0, path.length - 1)
.fill("")
.concat(path[path.length - 1])
);
}

_format(parts, val) {
Expand All @@ -83,7 +82,7 @@
return FORMATTERS[type]?.format(val) || val;
}

async getData(x0, y0, x1, y1) {
async dataListener(x0, y0, x1, y1) {
let columns = {};
if (x1 - x0 > 0 && y1 - y0 > 0) {
columns = await this.view.to_columns({
Expand All @@ -106,13 +105,13 @@
return {
num_rows: this._num_rows,
num_columns: this._column_paths.length,
row_headers: (columns.__ROW_PATH__ || []).map((x) => [this._tree_header(x, x.length === 3, true)]),
row_headers: this._tree_header(columns.__ROW_PATH__),
column_headers,
data,
};
}

applyStyle({detail: regularTable}) {
styleListener({detail: regularTable}) {
for (const td of regularTable.querySelectorAll("td, thead tr:last-child th")) {
const metadata = regularTable.get_meta(td);
let type;
Expand Down Expand Up @@ -184,12 +183,12 @@
columns: ["Sales", "Profit"],
});

const data_model = new window.PerspectiveDataModel();
await data_model.set_view(table, view);
const model = new window.PerspectiveDataModel();
await model.set_view(table, view);

const regular = document.getElementsByTagName("regular-table")[0];
regular.addStyleListener(data_model.applyStyle.bind(data_model));
regular.setDataListener(data_model.getData.bind(data_model));
regular.addStyleListener(model.styleListener.bind(model));
regular.setDataListener(model.dataListener.bind(model));
await regular.draw();
});
</script>
Expand Down
15 changes: 7 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,17 @@
"babel.config.js"
],
"scripts": {
"prebuild": "mkdirp dist/esm dist/css",
"build:babel": "babel src/js --source-maps --out-dir dist/esm",
"build:webpack": "webpack --color",
"build:babel": "babel src/js --out-dir dist/esm",
"build:webpack": "webpack",
"build:less": "lessc src/less/material.less dist/css/material.css",
"build": "npm-run-all build:babel build:webpack build:less",
"build": "npm-run-all -p build:*",
"clean": "rimraf dist",
"lint": "eslint src examples/*.html",
"fix": "eslint src examples/*.html --fix",
"test": "yarn jest --verbose",
"fix": "yarn lint --fix",
"test": "jest --noStackTrace",
"start": "http-server",
"watch:babel": "babel src/js --source-maps --watch --out-dir dist/esm",
"watch:webpack": "webpack --watch --color",
"watch:babel": "babel src/js --watch --out-dir dist/esm",
"watch:webpack": "webpack --watch",
"watch": "npm-run-all -p watch:*"
},
"publishConfig": {
Expand Down
2 changes: 1 addition & 1 deletion scripts/sync_gist.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ for (const file in hashes) {

// Retarget source assets to jsdelivr
let source = fs.readFileSync(`examples/${file}.html`).toString();
source = source.replace(/\.\.\/node_modules\//g, `https://cdn.jsdelivr.net/npm/@${pkg.version}/`);
source = source.replace(/\.\.\/node_modules\//g, `https://cdn.jsdelivr.net/npm/`);
source = source.replace(/\.\.\//g, `https://cdn.jsdelivr.net/npm/regular-table@${pkg.version}/`);

fs.writeFileSync(`dist/${hashes[file]}/index.html`, source);
Expand Down
24 changes: 22 additions & 2 deletions src/js/custom_element.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,34 @@ export class RegularViewModel extends RegularViewEventModel {
return key;
}

setDataListener(view) {
/**
*
* @param {Function<Promise<DataResponse>>} dataListener
* `dataListener` is called by to request a rectangular section of data
* for a virtual viewport, (x0, y0, x1, y1), and returns a `DataReponse`
* object with this structure:
* ```
* column_headers: num_columns:
* [["X00", ["X00", ["X00", X > 3
* "X0"], "X1"], "X2"]]
*
* row_headers: data:
* [["Y00", "Y0"] [["A", [true, [0,
* ["Y00", "Y1"] "B", false, 1,
* ["Y00", "Y2"]] "C"], true]], 2]]
*
* num_rows:
* Y > 3
* ```
*/
setDataListener(dataListener) {
let schema = {};
let config = {
row_pivots: [],
column_pivots: [],
};

this._invalid_schema = true;
this._view_cache = {view, config, schema};
this._view_cache = {view: dataListener, config, schema};
}
}
2 changes: 1 addition & 1 deletion src/js/scroll_panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ export class RegularVirtualTableViewModel extends HTMLElement {
scrollTo(x, y, ncols, nrows) {
const row_height = this._virtual_panel.offsetHeight / nrows;
this.scrollTop = row_height * y;
this.scrollLeft = (x / (this._max_scroll_column() || ncols)) * this.scrollWidth;
this.scrollLeft = (x / (this._max_scroll_column() || ncols)) * (this.scrollWidth - this.offsetWidth);
}

/**
Expand Down
20 changes: 8 additions & 12 deletions src/js/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class RegularTableViewModel {
*/
autosize_cells(last_cells) {
while (last_cells.length > 0) {
const [cell, metadata] = last_cells.shift();
const [cell, metadata] = last_cells.pop();
const offsetWidth = cell.offsetWidth;
this._column_sizes.row_height = this._column_sizes.row_height || cell.offsetHeight;
this._column_sizes.indices[metadata.cidx] = offsetWidth;
Expand Down Expand Up @@ -99,22 +99,16 @@ export class RegularTableViewModel {
id_column,
first_col,
};
cont_body = this.body.draw(
container_height,
column_state,
{
...view_state,
cidx_offset: 0,
},
true
);
cont_body = this.body.draw(container_height, column_state, {...view_state, cidx_offset: 0}, true);
const cont_head = this.header.draw(config, column_name, Array(view_cache.config.column_pivots.length + 1).fill(""), undefined, 0, row_index_length, undefined);
first_col = false;
view_state.viewport_width += this._column_sizes.indices[0] || cont_body.td?.offsetWidth || cont_head.th.offsetWidth;
view_state.row_height = view_state.row_height || cont_body.row_height;
cidx = row_headers[0].length;
if (!preserve_width) {
last_cells.push([cont_body.td || cont_head.th, cont_body.metadata || cont_head.metadata]);
for (const {td, metadata} of cont_body.tds) {
last_cells.push([td || cont_head.th, metadata || cont_head.metadata]);
}
}
}

Expand Down Expand Up @@ -149,7 +143,9 @@ export class RegularTableViewModel {
cidx++;
dcidx++;
if (!preserve_width) {
last_cells.push([cont_body.td || cont_head.th, cont_body.metadata || cont_head.metadata]);
for (const {td, metadata} of cont_body.tds) {
last_cells.push([td || cont_head.th, metadata || cont_head.metadata]);
}
}

if (view_state.viewport_width > container_width) {
Expand Down
Loading