From 24fe1e63c3b9c713e6c810dc2b782b707a5f8729 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Sun, 31 May 2020 21:22:43 -0400 Subject: [PATCH 1/2] Add documentation, remove perspective methods --- README.md | 34 +++----- babel.config.js | 3 +- examples/perspective_headers.html | 12 +-- package.json | 15 ++-- scripts/sync_gist.js | 2 +- src/js/custom_element.js | 24 +++++- src/js/scroll_panel.js | 2 +- src/js/utils.js | 127 ------------------------------ webpack.config.js | 5 -- 9 files changed, 50 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 84b47541..31367eb3 100644 --- a/README.md +++ b/README.md @@ -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 ``, which renders a regular HTML `` 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 `
`. * Virtually rendered for high-performance. @@ -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)) }; } ``` @@ -181,7 +176,7 @@ scroll, more data will be fetched from `getDataSlice()`, and parts of the ``` -## Column and Row Headers +### Column and Row Headers `regular-table` can also generate Hierarchial Row and Column Headers, using `` of `
` elements which layout in a `fixed` position within the virtual table. @@ -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)); }; } ``` @@ -280,7 +270,7 @@ Resulting HTML: ``` -## Hierarchial/Group Headers +### Hierarchial/Group Headers `regular-table` supports multiple `
`, and also uses `colspan` and `rowspan` to merge simple consecutive names, which allows description of simple diff --git a/babel.config.js b/babel.config.js index 3ec94cdc..dd53c5cf 100644 --- a/babel.config.js +++ b/babel.config.js @@ -8,8 +8,6 @@ module.exports = { node: "12", ios: "13", }, - useBuiltIns: "usage", - corejs: 3, }, ], ], @@ -21,4 +19,5 @@ module.exports = { "./scripts/babel-plugin-html-template.js", "./scripts/babel-plugin-css-template.js", ], + sourceMaps: true, }; diff --git a/examples/perspective_headers.html b/examples/perspective_headers.html index e7d88388..d073a00f 100644 --- a/examples/perspective_headers.html +++ b/examples/perspective_headers.html @@ -83,7 +83,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({ @@ -112,7 +112,7 @@ }; } - 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; @@ -184,12 +184,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(); }); diff --git a/package.json b/package.json index 942fac4c..3631af0e 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/scripts/sync_gist.js b/scripts/sync_gist.js index 9fc81002..ca6eff25 100644 --- a/scripts/sync_gist.js +++ b/scripts/sync_gist.js @@ -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); diff --git a/src/js/custom_element.js b/src/js/custom_element.js index 7f5e5708..31116e2a 100644 --- a/src/js/custom_element.js +++ b/src/js/custom_element.js @@ -97,7 +97,27 @@ export class RegularViewModel extends RegularViewEventModel { return key; } - setDataListener(view) { + /** + * + * @param {Function>} 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: [], @@ -105,6 +125,6 @@ export class RegularViewModel extends RegularViewEventModel { }; this._invalid_schema = true; - this._view_cache = {view, config, schema}; + this._view_cache = {view: dataListener, config, schema}; } } diff --git a/src/js/scroll_panel.js b/src/js/scroll_panel.js index 0a914c59..5451c64f 100644 --- a/src/js/scroll_panel.js +++ b/src/js/scroll_panel.js @@ -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); } /** diff --git a/src/js/utils.js b/src/js/utils.js index 4d1f3bcf..8d1084f4 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -10,47 +10,6 @@ import {DEBUG} from "./constants"; -/****************************************************************************** - * - * Events - * - */ - -export async function getCellConfig({view, config}, row_idx, col_idx) { - const row_pivots = config.row_pivots; - const column_pivots = config.column_pivots; - const start_row = row_idx >= 0 ? row_idx : 0; - const end_row = start_row + 1; - const r = await view.to_json({start_row, end_row}); - const row_paths = r.map((x) => x.__ROW_PATH__); - const row_pivots_values = row_paths[0] || []; - const row_filters = row_pivots - .map((pivot, index) => { - const pivot_value = row_pivots_values[index]; - return pivot_value ? [pivot, "==", pivot_value] : undefined; - }) - .filter((x) => x); - const column_index = row_pivots.length > 0 ? col_idx + 1 : col_idx; - const column_paths = Object.keys(r[0])[column_index]; - const result = {row: r[0]}; - let column_filters = []; - if (column_paths) { - const column_pivot_values = column_paths.split("|"); - result.column_names = [column_pivot_values[column_pivot_values.length - 1]]; - column_filters = column_pivots - .map((pivot, index) => { - const pivot_value = column_pivot_values[index]; - return pivot_value ? [pivot, "==", pivot_value] : undefined; - }) - .filter((x) => x) - .filter(([, , value]) => value !== "__ROW_PATH__"); - } - - const filters = config.filter.concat(row_filters).concat(column_filters); - result.config = {filters}; - return result; -} - /****************************************************************************** * * Profling @@ -152,89 +111,3 @@ export function throttlePromise(target, property, descriptor) { }; return descriptor; } - -export function get_type_config(type) { - const config = {}; - if (default_types[type]) { - Object.assign(config, default_types[type]); - } - if (config.type) { - const props = get_type_config(config.type); - Object.assign(props, config); - return props; - } else { - return config; - } -} - -export const default_types = { - /** - * `types` are the type-specific configuration options. Each key is the - * name of a perspective type; their values are configuration objects for - * that type. - */ - types: { - float: { - /** - * Which filter operator should be the default when a column of this - * type is pivotted. - */ - filter_operator: "==", - - /** - * Which aggregate should be the default when a column of this type - * is pivotted. - */ - aggregate: "sum", - - /** - * The format object for this type. Can be either an - * `toLocaleString()` `options` object for this type (or supertype), - * or a function which returns the formatted string for this type. - */ - format: { - style: "decimal", - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }, - }, - string: { - filter_operator: "==", - aggregate: "count", - }, - integer: { - filter_operator: "==", - aggregate: "sum", - format: {}, - }, - boolean: { - filter_operator: "==", - aggregate: "count", - }, - datetime: { - filter_operator: "==", - aggregate: "count", - format: { - week: "numeric", - year: "numeric", - month: "numeric", - day: "numeric", - hour: "numeric", - minute: "numeric", - second: "numeric", - }, - null_value: -1, - }, - date: { - filter_operator: "==", - aggregate: "count", - format: { - week: "numeric", - year: "numeric", - month: "numeric", - day: "numeric", - }, - null_value: -1, - }, - }, -}; diff --git a/webpack.config.js b/webpack.config.js index 6ca6b48d..de275174 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,11 +13,6 @@ module.exports = { }, ], }, - performance: { - hints: false, - maxEntrypointSize: 512000, - maxAssetSize: 512000, - }, output: { filename: "regular-table.js", library: "regular-table", From 63e5ef543897d2ba66e704e258202f841755fdb0 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Sun, 31 May 2020 21:37:38 -0400 Subject: [PATCH 2/2] CSS-only filesystem-like row tree rendering --- examples/perspective_headers.html | 41 ++++++++++---------- src/js/table.js | 20 ++++------ src/js/tbody.js | 53 ++++++++++++++++++++------ src/js/thead.js | 18 ++++----- src/js/view_model.js | 26 +++++++++++-- test/perspective_headers.test.js | 8 +--- test/two_billion_rows/metadata.test.js | 6 +-- 7 files changed, 105 insertions(+), 67 deletions(-) diff --git a/examples/perspective_headers.html b/examples/perspective_headers.html index d073a00f..b88f79b2 100644 --- a/examples/perspective_headers.html +++ b/examples/perspective_headers.html @@ -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; + } + @@ -41,8 +53,6 @@