diff --git a/docs/index.html b/docs/index.html index 9517f0a..bd77c01 100644 --- a/docs/index.html +++ b/docs/index.html @@ -270,7 +270,7 @@

Outstanding work

console.timeEnd('merge'); -// console.log(table); + console.log(table); // render the table in the target element document.getElementById('tablan').replaceWith(render.table(table, 'tablan', 'landscape')); diff --git a/lib/node/index.d.ts b/lib/node/index.d.ts index 79e37f8..3a75aac 100644 --- a/lib/node/index.d.ts +++ b/lib/node/index.d.ts @@ -31,11 +31,11 @@ export declare function table(cube: Cube, axes: Axes(keys: Cube>, axes: Axes, onX: boolean): Array>>; +export declare function split(cells: Cube>, axes: Axes, onX: boolean): Array>>; /** * Merge adjacent cells in a split table on the y and/or x axes. - * @param table A table of Cells created by a previous call to splitX or splitY. + * @param cells A table of Cells created by a previous call to splitX or splitY. * @param onX A flag to indicate that cells should be merged on the x axis. * @param onY A flag to indicate that cells should be merged on the y axis. */ -export declare function merge(table: Array>>, onX: boolean, onY: boolean): void; +export declare function merge(cells: Array>>, onX: boolean, onY: boolean): void; diff --git a/lib/node/index.js b/lib/node/index.js index 9e69f25..feb4751 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -11,9 +11,9 @@ exports.merge = exports.split = exports.table = void 0; function table(cube, axes, getKey, onX) { const identity = { index: 0 }; // convert the source data to keys and remove resulting duplicates - const keys = cube.map(row => row.map(table => table.length ? tableCells(table, getKey, identity) : [{ text: '', style: 'empty', index: [], source: [], rows: 1, cols: 1 }])); + const cells = cube.map(row => row.map(table => table.length ? tableCells(table, getKey, identity) : [{ text: '', style: 'empty', index: [], source: [], rows: 1, cols: 1 }])); // create the resultant table - return split(keys, axes, onX); + return split(cells, axes, onX); } exports.table = table; /** @@ -22,24 +22,24 @@ exports.table = table; * @param axes The x and y axes used in the pivot operation to create the cube. * @param onX A flag to indicate if cells in cube containing multiple values should be split on the x axis (if not, the y axis will be used). */ -function split(keys, axes, onX) { +function split(cells, axes, onX) { // calcuate the x and y splits required - const xSplits = axes.x.map((_, iX) => onX ? leastCommonMultiple(keys, row => row[iX].length) : 1); - const ySplits = keys.map(row => onX ? 1 : leastCommonMultiple(row, table => table.length)); + const xSplits = axes.x.map((_, iX) => onX ? leastCommonMultiple(cells, row => row[iX].length) : 1); + const ySplits = cells.map(row => onX ? 1 : leastCommonMultiple(row, table => table.length)); // iterate and expand the y axis based on the split data - return expand(keys, ySplits, (row, ySplit, ysi, iY) => { + return expand(cells, ySplits, (row, ySplit, ysi, iY) => { // iterate and expand the x axis based on the split data - return expand(row, xSplits, (values, xSplit, xsi) => { + return expand(row, xSplits, (cell, xSplit, xsi) => { // generate the cube cells - return { ...values[Math.floor(values.length * (ysi + xsi) / (xSplit * ySplit))] }; + return { ...cell[Math.floor(cell.length * (ysi + xsi) / (xSplit * ySplit))] }; // generate the y axis row header cells }, axes.y[iY].map(criterion => axis(criterion, 'y'))); // generate the x axis column header rows - }, axes.x[0].map((_, iY) => { + }, axes.x[0].map((_, iC) => { // iterate and expand the x axis return expand(axes.x, xSplits, xPoint => { // generate the x axis cells - return axis(xPoint[iY], 'x'); + return axis(xPoint[iC], 'x'); // generate the x/y header }, axes.y[0].map(() => axis({ key: '', value: '' }, 'xy'))); })); @@ -47,22 +47,22 @@ function split(keys, axes, onX) { exports.split = split; /** * Merge adjacent cells in a split table on the y and/or x axes. - * @param table A table of Cells created by a previous call to splitX or splitY. + * @param cells A table of Cells created by a previous call to splitX or splitY. * @param onX A flag to indicate that cells should be merged on the x axis. * @param onY A flag to indicate that cells should be merged on the y axis. */ -function merge(table, onX, onY) { +function merge(cells, onX, onY) { let next; - forEachRev(table, (row, iY) => { - forEachRev(row, (value, iX) => { - if (onY && iY && (next = table[iY - 1][iX]) && keyEquals(next, value) && next.cols === value.cols) { - next.rows += value.rows; - mergeContext(next, value); + forEachRev(cells, (row, iY) => { + forEachRev(row, (cell, iX) => { + if (onY && iY && (next = cells[iY - 1][iX]) && keyEquals(next, cell) && next.cols === cell.cols) { + next.rows += cell.rows; + mergeContext(next, cell); row.splice(iX, 1); } - else if (onX && iX && (next = row[iX - 1]) && keyEquals(next, value) && next.rows === value.rows) { - next.cols += value.cols; - mergeContext(next, value); + else if (onX && iX && (next = row[iX - 1]) && keyEquals(next, cell) && next.rows === cell.rows) { + next.cols += cell.cols; + mergeContext(next, cell); row.splice(iX, 1); } }); @@ -73,11 +73,11 @@ exports.merge = merge; * Merges the context of two adjacent cells. * @hidden */ -function mergeContext(next, value) { - value.index.forEach((index, i) => { +function mergeContext(next, cell) { + cell.index.forEach((index, i) => { if (!next.index.includes(index)) { next.index.push(index); - next.source.push(value.source[i]); + next.source.push(cell.source[i]); } }); } diff --git a/package.json b/package.json index 84c3148..68595ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@steelbreeze/landscape", - "version": "3.4.1", + "version": "3.4.2", "description": "Landscape map viewpoint visualisation", "main": "lib/node/index.js", "module": "lib/node/index.js", diff --git a/src/index.old b/src/index.old deleted file mode 100644 index ed7dee1..0000000 --- a/src/index.old +++ /dev/null @@ -1,198 +0,0 @@ -import { Axes, Cube, Func, Pair, Row, Table } from '@steelbreeze/pivot'; - -/** The final text and class name to use when rendering cells in a table. */ -export interface Key { - /** The text to use in the final table rendering. */ - text: string; - - /** The class name to use in the final table rendering. */ - style: string; -} - -/** An extension of key, adding the number of rows and columns the key will occupy in the final table rendering. */ -export interface Cell extends Key { - /** Unique keys for the source context. */ - index: Array; - - /** The the rows that this this key came from. */ - source: Array; - - /** The number of rows to occupy. */ - rows: number; - - /** The number of columns to occupy. */ - cols: number; -} - -/** - * Generates a table from a cube and it's axis. - * @param cube The source cube. - * @param axes The x and y axes used in the pivot operation to create the cube. - * @param getKey A callback to generate a key containing the text and className used in the table from the source records, - * @param onX A flag to indicate if cells in cube containing multiple values should be split on the x axis (if not, the y axis will be used). - */ -export function table(cube: Cube, axes: Axes, getKey: Func, onX: boolean): Array>> { - const identity = { index: 0 }; - - // convert the source data to keys and remove resulting duplicates - const cells = cube.map(row => row.map(table => table.length ? tableCells(table, getKey, identity) : [{ text: '', style: 'empty', index: [], source: [], rows: 1, cols: 1 }])); - - // create the resultant table - return split(cells, axes, onX); -} - -/** - * Splits a cube of keys into a table, creating mutiple rows or columns where a cell in a cube has multiple values. - * @param cube The source cube. - * @param axes The x and y axes used in the pivot operation to create the cube. - * @param onX A flag to indicate if cells in cube containing multiple values should be split on the x axis (if not, the y axis will be used). - */ -function split(cells: Cube>, axes: Axes, onX: boolean): Array>> { - // calcuate the x and y splits required - const xSplits = axes.x.map((_, iX) => onX ? leastCommonMultiple(cells, row => row[iX].length) : 1); - const ySplits = cells.map(row => onX ? 1 : leastCommonMultiple(row, table => table.length)); - - // iterate and expand the y axis based on the split data - return expand(cells, ySplits, (row, ySplit, ysi, iY) => { - - // iterate and expand the x axis based on the split data - return expand(row, xSplits, (table, xSplit, xsi) => { - - // generate the cube cells - return { ...(table[Math.floor(table.length * (ysi + xsi) * ySplit / xSplit)]) }; // NOTE: clone cells from the cube so subsiquent merge operation work - - // generate the y axis row header cells - }, axes.y[iY].map(criterion => axis(criterion, 'y'))); - - // generate the x axis column header rows - }, axes.x[0].map((_, iX) => { - - // iterate and expand the x axis - return expand(axes.x, xSplits, x => { - - // generate the x axis cells - return axis(x[iX], 'x'); - - // generate the x/y header - }, axes.y[0].map(() => axis({ key: '', value: '' }, 'xy'))); - })); -} - -/** - * Merge adjacent cells in a split table on the y and/or x axes. - * @param table A table of Cells created by a previous call to splitX or splitY. - * @param onX A flag to indicate that cells should be merged on the x axis. - * @param onY A flag to indicate that cells should be merged on the y axis. - */ -export function merge(table: Array>>, onX: boolean, onY: boolean): void { - let next; - - forEachRev(table, (row, iY) => { - forEachRev(row, (value, iX) => { - if (onY && iY && (next = table[iY - 1][iX]) && keyEquals(next, value) && next.cols === value.cols) { - next.rows += value.rows; - mergeContext(next, value); - row.splice(iX, 1); - } else if (onX && iX && (next = row[iX - 1]) && keyEquals(next, value) && next.rows === value.rows) { - next.cols += value.cols; - mergeContext(next, value); - row.splice(iX, 1); - } - }); - }); -} - -/** - * Merges the context of two adjacent cells. - * @hidden - */ -function mergeContext(next: Cell, value: Cell): void { - value.index.forEach((index, i) => { - if (!next.index.includes(index)) { - next.index.push(index); - next.source.push(value.source[i]); - } - }); -} - -/** - * Convert a table of rows into a table of cells. - * @hidden - */ -function tableCells(table: Table, getKey: Func, identity: { index: number }): Table> { - const result: Table> = []; - - for (const row of table) { - const key = getKey(row); - const cell = result.find(cell => keyEquals(cell, key)); - - if (cell) { - cell.index.push(identity.index); - cell.source.push(row); - } else { - result.push({ ...key, index: [identity.index], source: [row], rows: 1, cols: 1 }); - } - - identity.index++; - } - - return result; -} - -/** - * Expands an array using, splitting values into multiple based on a set of corresponding splits then maps the data to a desired structure. - * @hidden - */ -function expand(values: TSource[], splits: number[], callbackfn: (value: TSource, split: number, iSplit: number, iValue: number) => TResult, seed: TResult[]): TResult[] { - values.forEach((value, iValue) => { - const split = splits[iValue]; - - for (let iSplit = 0; iSplit < split; ++iSplit) { - seed.push(callbackfn(value, split, iSplit, iValue)); - } - }); - - return seed; -} - -/** - * A reverse for loop - * @param hidden - */ -function forEachRev(values: Array, callbackfn: (value: TValue, index: number) => void): void { - for (let index = values.length; index--;) { - callbackfn(values[index], index); - } -} - -/** - * Returns the least common multiple of a set of integers generated from an object. - * @hidden - */ -function leastCommonMultiple(source: Array, callbackfn: Func): number { - return source.map(value => callbackfn(value) || 1).reduce((a, b) => (a * b) / greatestCommonFactor(a, b)); -} - -/** - * Returns the greatest common factor of two numbers - * @hidden - */ -function greatestCommonFactor(a: number, b: number): number { - return b ? greatestCommonFactor(b, a % b) : a; -} - -/** - * Compare two keys for equality - * @hidden - */ -function keyEquals(a: Key, b: Key): boolean { - return a.text === b.text && a.style === b.style; -} - -/** - * Creates a cell within a table for a column or row heading. - * @hidden - */ -function axis(pair: Pair, name: string): Cell { - return { text: pair.value, style: `axis ${name} ${pair.key}`, index: [], source: [], rows: 1, cols: 1 }; -} diff --git a/src/index.ts b/src/index.ts index e31b1fe..cb6c564 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,10 +35,10 @@ export function table(cube: Cube, axes: Axes, getK const identity = { index: 0 }; // convert the source data to keys and remove resulting duplicates - const keys = cube.map(row => row.map(table => table.length ? tableCells(table, getKey, identity) : [{ text: '', style: 'empty', index: [], source: [], rows: 1, cols: 1 }])); + const cells = cube.map(row => row.map(table => table.length ? tableCells(table, getKey, identity) : [{ text: '', style: 'empty', index: [], source: [], rows: 1, cols: 1 }])); // create the resultant table - return split(keys, axes, onX); + return split(cells, axes, onX); } /** @@ -47,31 +47,31 @@ export function table(cube: Cube, axes: Axes, getK * @param axes The x and y axes used in the pivot operation to create the cube. * @param onX A flag to indicate if cells in cube containing multiple values should be split on the x axis (if not, the y axis will be used). */ -export function split(keys: Cube>, axes: Axes, onX: boolean): Array>> { +export function split(cells: Cube>, axes: Axes, onX: boolean): Array>> { // calcuate the x and y splits required - const xSplits = axes.x.map((_, iX) => onX ? leastCommonMultiple(keys, row => row[iX].length) : 1); - const ySplits = keys.map(row => onX ? 1 : leastCommonMultiple(row, table => table.length)); + const xSplits = axes.x.map((_, iX) => onX ? leastCommonMultiple(cells, row => row[iX].length) : 1); + const ySplits = cells.map(row => onX ? 1 : leastCommonMultiple(row, table => table.length)); // iterate and expand the y axis based on the split data - return expand(keys, ySplits, (row, ySplit, ysi, iY) => { + return expand(cells, ySplits, (row, ySplit, ysi, iY) => { // iterate and expand the x axis based on the split data - return expand(row, xSplits, (values, xSplit, xsi) => { + return expand(row, xSplits, (cell, xSplit, xsi) => { // generate the cube cells - return { ...values[Math.floor(values.length * (ysi + xsi) / (xSplit * ySplit))] }; + return { ...cell[Math.floor(cell.length * (ysi + xsi) / (xSplit * ySplit))] }; // generate the y axis row header cells }, axes.y[iY].map(criterion => axis(criterion, 'y'))); // generate the x axis column header rows - }, axes.x[0].map((_, iY) => { + }, axes.x[0].map((_, iC) => { // iterate and expand the x axis return expand(axes.x, xSplits, xPoint => { // generate the x axis cells - return axis(xPoint[iY], 'x'); + return axis(xPoint[iC], 'x'); // generate the x/y header }, axes.y[0].map(() => axis({ key: '', value: '' }, 'xy'))); @@ -81,22 +81,22 @@ export function split(keys: Cube>, axes: Axes /** * Merge adjacent cells in a split table on the y and/or x axes. - * @param table A table of Cells created by a previous call to splitX or splitY. + * @param cells A table of Cells created by a previous call to splitX or splitY. * @param onX A flag to indicate that cells should be merged on the x axis. * @param onY A flag to indicate that cells should be merged on the y axis. */ -export function merge(table: Array>>, onX: boolean, onY: boolean): void { +export function merge(cells: Array>>, onX: boolean, onY: boolean): void { let next; - forEachRev(table, (row, iY) => { - forEachRev(row, (value, iX) => { - if (onY && iY && (next = table[iY - 1][iX]) && keyEquals(next, value) && next.cols === value.cols) { - next.rows += value.rows; - mergeContext(next, value); + forEachRev(cells, (row, iY) => { + forEachRev(row, (cell, iX) => { + if (onY && iY && (next = cells[iY - 1][iX]) && keyEquals(next, cell) && next.cols === cell.cols) { + next.rows += cell.rows; + mergeContext(next, cell); row.splice(iX, 1); - } else if (onX && iX && (next = row[iX - 1]) && keyEquals(next, value) && next.rows === value.rows) { - next.cols += value.cols; - mergeContext(next, value); + } else if (onX && iX && (next = row[iX - 1]) && keyEquals(next, cell) && next.rows === cell.rows) { + next.cols += cell.cols; + mergeContext(next, cell); row.splice(iX, 1); } }); @@ -107,11 +107,11 @@ export function merge(table: Array>>, onX: bo * Merges the context of two adjacent cells. * @hidden */ -function mergeContext(next: Cell, value: Cell): void { - value.index.forEach((index, i) => { +function mergeContext(next: Cell, cell: Cell): void { + cell.index.forEach((index, i) => { if (!next.index.includes(index)) { next.index.push(index); - next.source.push(value.source[i]); + next.source.push(cell.source[i]); } }); }